diff --git a/app/src/main/java/app/fedilab/fedilabtube/client/PeertubeService.java b/app/src/main/java/app/fedilab/fedilabtube/client/PeertubeService.java index 7592ad8..e2a3221 100644 --- a/app/src/main/java/app/fedilab/fedilabtube/client/PeertubeService.java +++ b/app/src/main/java/app/fedilab/fedilabtube/client/PeertubeService.java @@ -142,6 +142,13 @@ public interface PeertubeService { @Field("nsfwPolicy") String nsfwPolicy ); + @Multipart + @POST("video-channels/{channelHandle}/avatar/pick") + Call updateChannelProfilePicture( + @Header("Authorization") String credentials, + @Path("channelHandle") String channelHandle, + @Part MultipartBody.Part avatarfile); + @Multipart @POST("users/me/avatar/pick") Call updateProfilePicture( diff --git a/app/src/main/java/app/fedilab/fedilabtube/client/RetrofitPeertubeAPI.java b/app/src/main/java/app/fedilab/fedilabtube/client/RetrofitPeertubeAPI.java index 4c1e25d..0872325 100644 --- a/app/src/main/java/app/fedilab/fedilabtube/client/RetrofitPeertubeAPI.java +++ b/app/src/main/java/app/fedilab/fedilabtube/client/RetrofitPeertubeAPI.java @@ -603,7 +603,7 @@ public class RetrofitPeertubeAPI { if (filename == null) { filename = "my_image." + mime; } - RequestBody requestFile = RequestBody.create(MediaType.parse("image/" + mime), imageBytes); + RequestBody requestFile = RequestBody.create(imageBytes, MediaType.parse("image/" + mime)); return MultipartBody.Part.createFormData(paramName, filename, requestFile); } @@ -976,7 +976,7 @@ public class RetrofitPeertubeAPI { } bodyThumbnail = createFile("avatarfile", thumbnail, thumbnailName); } - if (previewfile != null) { + if (previewfile != null && thumbnail != null) { DocumentFile documentFile = DocumentFile.fromSingleUri(_context, thumbnail); String previewfileName = null; if (documentFile != null) { @@ -987,22 +987,22 @@ public class RetrofitPeertubeAPI { } catch (IOException e) { e.printStackTrace(); } - RequestBody channelId = RequestBody.create(MediaType.parse("text/plain"), videoParams.getChannelId()); - RequestBody description = RequestBody.create(MediaType.parse("text/plain"), videoParams.getDescription()); - RequestBody language = RequestBody.create(MediaType.parse("text/plain"), videoParams.getLanguage()); - RequestBody license = RequestBody.create(MediaType.parse("text/plain"), videoParams.getLicence()); - RequestBody name = RequestBody.create(MediaType.parse("text/plain"), videoParams.getName()); + RequestBody channelId = RequestBody.create(videoParams.getChannelId(), MediaType.parse("text/plain")); + RequestBody description = RequestBody.create(videoParams.getDescription(), MediaType.parse("text/plain")); + RequestBody language = RequestBody.create(videoParams.getLanguage(), MediaType.parse("text/plain")); + RequestBody license = RequestBody.create(videoParams.getLicence(), MediaType.parse("text/plain")); + RequestBody name = RequestBody.create(videoParams.getName(), MediaType.parse("text/plain")); List tags = null; if (videoParams.getTags() != null && videoParams.getTags().size() > 0) { tags = new ArrayList<>(); for (String tag : videoParams.getTags()) { - tags.add(RequestBody.create(MediaType.parse("text/plain"), tag)); + tags.add(RequestBody.create(tag, MediaType.parse("text/plain"))); } } RequestBody support = null; if (videoParams.getSupport() != null) { - support = RequestBody.create(MediaType.parse("text/plain"), videoParams.getSupport()); + support = RequestBody.create(videoParams.getSupport(), MediaType.parse("text/plain")); } @@ -1213,7 +1213,7 @@ public class RetrofitPeertubeAPI { * @param channelParams PlaylistParams * @return APIResponse */ - public APIResponse createOrUpdateChannel(ChannelsVM.action apiAction, String channelId, ChannelParams channelParams) { + public APIResponse createOrUpdateChannel(ChannelsVM.action apiAction, String channelId, ChannelParams channelParams, Uri avatar) { PeertubeService peertubeService = init(); APIResponse apiResponse = new APIResponse(); @@ -1236,6 +1236,21 @@ public class RetrofitPeertubeAPI { setError(apiResponse, response.code(), response.errorBody()); } } + if (avatar != null) { + DocumentFile documentFile = DocumentFile.fromSingleUri(_context, avatar); + String avatarfileName = null; + if (documentFile != null) { + avatarfileName = documentFile.getName(); + } + MultipartBody.Part bodyThumbnail = createFile("avatarfile", avatar, avatarfileName); + Call updateProfilePicture = peertubeService.updateChannelProfilePicture(getToken(), channelId, bodyThumbnail); + Response responseAvatar = updateProfilePicture.execute(); + if (responseAvatar.isSuccessful()) { + UserMe.AvatarResponse avatarResponse = responseAvatar.body(); + } else { + setError(apiResponse, responseAvatar.code(), responseAvatar.errorBody()); + } + } } catch (IOException e) { Error error = new Error(); error.setError(_context.getString(R.string.toast_error)); @@ -1365,13 +1380,13 @@ public class RetrofitPeertubeAPI { APIResponse apiResponse = new APIResponse(); MultipartBody.Part body = null; if (thumbnail != null) { - RequestBody requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), thumbnail); + RequestBody requestFile = RequestBody.create(thumbnail, MediaType.parse("multipart/form-data")); body = MultipartBody.Part.createFormData("image", thumbnail.getName(), requestFile); } try { - RequestBody displayName = RequestBody.create(MediaType.parse("text/plain"), playlistParams.getDisplayName()); - RequestBody description = RequestBody.create(MediaType.parse("text/plain"), playlistParams.getDescription()); - RequestBody channelId = RequestBody.create(MediaType.parse("text/plain"), playlistParams.getVideoChannelId()); + RequestBody displayName = RequestBody.create(playlistParams.getDisplayName(), MediaType.parse("text/plain")); + RequestBody description = RequestBody.create(playlistParams.getDescription(), MediaType.parse("text/plain")); + RequestBody channelId = RequestBody.create(playlistParams.getVideoChannelId(), MediaType.parse("text/plain")); if (apiAction == PlaylistsVM.action.CREATE_PLAYLIST) { Call stringCall = peertubeService.addPlaylist(getToken(), displayName, description, playlistParams.getPrivacy(), channelId, body); Response response = stringCall.execute(); diff --git a/app/src/main/java/app/fedilab/fedilabtube/fragment/DisplayChannelsFragment.java b/app/src/main/java/app/fedilab/fedilabtube/fragment/DisplayChannelsFragment.java index 8b59547..2e72c87 100644 --- a/app/src/main/java/app/fedilab/fedilabtube/fragment/DisplayChannelsFragment.java +++ b/app/src/main/java/app/fedilab/fedilabtube/fragment/DisplayChannelsFragment.java @@ -14,8 +14,12 @@ package app.fedilab.fedilabtube.fragment; * You should have received a copy of the GNU General Public License along with TubeLab; if not, * see . */ +import android.Manifest; import android.app.Activity; import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Looper; @@ -25,20 +29,22 @@ import android.view.ViewGroup; import android.view.WindowManager; import android.view.inputmethod.InputMethodManager; import android.widget.Button; -import android.widget.EditText; -import android.widget.LinearLayout; import android.widget.RelativeLayout; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; import androidx.fragment.app.Fragment; import androidx.lifecycle.ViewModelProvider; import androidx.recyclerview.widget.DividerItemDecoration; import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; -import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.resource.bitmap.CenterCrop; +import com.bumptech.glide.load.resource.bitmap.RoundedCorners; +import com.bumptech.glide.request.RequestOptions; import com.google.android.material.floatingactionbutton.FloatingActionButton; import java.util.ArrayList; @@ -49,30 +55,37 @@ import app.fedilab.fedilabtube.client.APIResponse; import app.fedilab.fedilabtube.client.RetrofitPeertubeAPI; import app.fedilab.fedilabtube.client.data.ChannelData; import app.fedilab.fedilabtube.client.entities.ChannelParams; +import app.fedilab.fedilabtube.databinding.AddChannelBinding; +import app.fedilab.fedilabtube.databinding.FragmentRecyclerviewBinding; import app.fedilab.fedilabtube.drawer.ChannelListAdapter; import app.fedilab.fedilabtube.helper.Helper; import app.fedilab.fedilabtube.viewmodel.ChannelsVM; import es.dmoral.toasty.Toasty; +import static app.fedilab.fedilabtube.PeertubeUploadActivity.MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE; + public class DisplayChannelsFragment extends Fragment implements ChannelListAdapter.AllChannelRemoved, ChannelListAdapter.EditAlertDialog { private Context context; + private static final int PICK_AVATAR = 467; private ChannelListAdapter channelListAdapter; private List channels; private RelativeLayout mainLoader, nextElementLoader, textviewNoAction; - private SwipeRefreshLayout swipeRefreshLayout; private String name; - private RecyclerView lv_channels; private View rootView; - private FloatingActionButton action_button; private boolean myChannels; + private FloatingActionButton action_button; + private FragmentRecyclerviewBinding binding; + private AddChannelBinding bindingDialog; + private Uri inputData; @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - rootView = inflater.inflate(R.layout.fragment_recyclerview, container, false); + binding = FragmentRecyclerviewBinding.inflate(LayoutInflater.from(context)); + rootView = binding.getRoot(); context = getContext(); Bundle bundle = this.getArguments(); channels = new ArrayList<>(); @@ -83,7 +96,6 @@ public class DisplayChannelsFragment extends Fragment implements ChannelListAdap } - swipeRefreshLayout = rootView.findViewById(R.id.swipeContainer); if (getActivity() != null) { action_button = getActivity().findViewById(R.id.action_button); @@ -93,8 +105,7 @@ public class DisplayChannelsFragment extends Fragment implements ChannelListAdap } } - lv_channels = rootView.findViewById(R.id.lv_elements); - lv_channels.addItemDecoration(new DividerItemDecoration(context, DividerItemDecoration.VERTICAL)); + binding.lvElements.addItemDecoration(new DividerItemDecoration(context, DividerItemDecoration.VERTICAL)); mainLoader = rootView.findViewById(R.id.loader); nextElementLoader = rootView.findViewById(R.id.loading_next); textviewNoAction = rootView.findViewById(R.id.no_action); @@ -103,13 +114,13 @@ public class DisplayChannelsFragment extends Fragment implements ChannelListAdap channelListAdapter = new ChannelListAdapter(this.channels, myChannels); channelListAdapter.allChannelRemoved = this; channelListAdapter.editAlertDialog = this; - lv_channels.setAdapter(channelListAdapter); + binding.lvElements.setAdapter(channelListAdapter); final LinearLayoutManager mLayoutManager; mLayoutManager = new LinearLayoutManager(context); - lv_channels.setLayoutManager(mLayoutManager); + binding.lvElements.setLayoutManager(mLayoutManager); - swipeRefreshLayout.setOnRefreshListener(this::pullToRefresh); + binding.swipeContainer.setOnRefreshListener(this::pullToRefresh); ChannelsVM viewModel = new ViewModelProvider(this).get(ChannelsVM.class); if (name != null) { viewModel.get(RetrofitPeertubeAPI.DataType.CHANNELS_FOR_ACCOUNT, name).observe(DisplayChannelsFragment.this.requireActivity(), this::manageViewChannels); @@ -119,6 +130,25 @@ public class DisplayChannelsFragment extends Fragment implements ChannelListAdap return rootView; } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == PICK_AVATAR && resultCode == Activity.RESULT_OK) { + if (data == null || data.getData() == null) { + Toasty.error(context, getString(R.string.toot_select_image_error), Toast.LENGTH_LONG).show(); + return; + } + inputData = data.getData(); + Glide.with(context) + .load(inputData) + .thumbnail(0.1f) + .apply(new RequestOptions().transform(new CenterCrop(), new RoundedCorners(10))) + .into(bindingDialog.profilePicture); + + } + } + @Override public void onResume() { super.onResume(); @@ -153,8 +183,7 @@ public class DisplayChannelsFragment extends Fragment implements ChannelListAdap } public void scrollToTop() { - if (lv_channels != null) - lv_channels.setAdapter(channelListAdapter); + binding.lvElements.setAdapter(channelListAdapter); } private void manageViewChannels(APIResponse apiResponse) { @@ -162,7 +191,7 @@ public class DisplayChannelsFragment extends Fragment implements ChannelListAdap nextElementLoader.setVisibility(View.GONE); if (apiResponse.getError() != null) { Toasty.error(context, apiResponse.getError().getError(), Toast.LENGTH_LONG).show(); - swipeRefreshLayout.setRefreshing(false); + binding.swipeContainer.setRefreshing(false); return; } List channels = apiResponse.getChannels(); @@ -178,18 +207,18 @@ public class DisplayChannelsFragment extends Fragment implements ChannelListAdap channelListAdapter = new ChannelListAdapter(this.channels, myChannels); channelListAdapter.allChannelRemoved = DisplayChannelsFragment.this; channelListAdapter.editAlertDialog = DisplayChannelsFragment.this; - lv_channels.setAdapter(channelListAdapter); + binding.lvElements.setAdapter(channelListAdapter); } else { channelListAdapter.notifyItemRangeChanged(currentPosition, channels.size()); } } - swipeRefreshLayout.setRefreshing(false); + binding.swipeContainer.setRefreshing(false); } public void pullToRefresh() { channels = new ArrayList<>(); - swipeRefreshLayout.setRefreshing(true); + binding.swipeContainer.setRefreshing(true); ChannelsVM viewModel = new ViewModelProvider(this).get(ChannelsVM.class); if (name != null) { viewModel.get(RetrofitPeertubeAPI.DataType.CHANNELS_FOR_ACCOUNT, name).observe(DisplayChannelsFragment.this.requireActivity(), this::manageViewChannels); @@ -206,48 +235,82 @@ public class DisplayChannelsFragment extends Fragment implements ChannelListAdap public void manageAlert(ChannelParams oldChannelValues) { + AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(context); - LayoutInflater inflater1 = ((Activity) context).getLayoutInflater(); - View dialogView = inflater1.inflate(R.layout.add_channel, new LinearLayout(context), false); - dialogBuilder.setView(dialogView); - EditText display_name = dialogView.findViewById(R.id.display_name); - EditText name = dialogView.findViewById(R.id.name); - EditText description = dialogView.findViewById(R.id.description); + bindingDialog = AddChannelBinding.inflate(LayoutInflater.from(context), null, false); + dialogBuilder.setView(bindingDialog.getRoot()); + if (oldChannelValues != null) { - display_name.setText(oldChannelValues.getDisplayName()); - name.setText(oldChannelValues.getName()); - description.setText(oldChannelValues.getDescription()); - name.setEnabled(false); + bindingDialog.displayName.setText(oldChannelValues.getDisplayName()); + bindingDialog.name.setText(oldChannelValues.getName()); + bindingDialog.description.setText(oldChannelValues.getDescription()); + bindingDialog.name.setEnabled(false); } dialogBuilder.setPositiveButton(R.string.validate, null); dialogBuilder.setNegativeButton(R.string.cancel, (dialog, id) -> dialog.dismiss()); AlertDialog alertDialog = dialogBuilder.create(); + int position; + if (oldChannelValues == null) { + position = -1; + } else { + position = 0; + for (ChannelData.Channel channel : channels) { + if (channel.getName().compareTo(oldChannelValues.getName()) == 0) { + break; + } + position++; + } + } + bindingDialog.selectFile.setOnClickListener(v -> { + if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_EXTERNAL_STORAGE) != + PackageManager.PERMISSION_GRANTED) { + ActivityCompat.requestPermissions((Activity) context, + new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, + MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE); + return; + } + + Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType("*/*"); + String[] mimetypes = {"image/*"}; + intent.putExtra(Intent.EXTRA_MIME_TYPES, mimetypes); + startActivityForResult(intent, PICK_AVATAR); + }); + if (position >= 0) { + Helper.loadGiF(context, channels.get(position).getAvatar() != null ? channels.get(position).getAvatar().getPath() : null, bindingDialog.profilePicture); + } else { + Glide.with(context) + .load(R.drawable.missing_peertube) + .thumbnail(0.1f) + .apply(new RequestOptions().transform(new CenterCrop(), new RoundedCorners(10))) + .into(bindingDialog.profilePicture); + } + int finalPosition = position; alertDialog.setOnShowListener(dialogInterface -> { Button button = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE); button.setOnClickListener(view -> { - if (display_name.getText() != null && display_name.getText().toString().trim().length() > 0 && name.getText() != null && name.getText().toString().trim().length() > 0) { + if (bindingDialog.displayName.getText() != null && bindingDialog.displayName.getText().toString().trim().length() > 0 && bindingDialog.name.getText() != null && bindingDialog.name.getText().toString().trim().length() > 0) { ChannelParams channelCreation = new ChannelParams(); - channelCreation.setDisplayName(display_name.getText().toString().trim()); - channelCreation.setName(name.getText().toString().trim()); - if (description.getText() != null && description.getText().toString().trim().length() > 0) { - channelCreation.setDescription(description.getText().toString().trim()); + channelCreation.setDisplayName(bindingDialog.displayName.getText().toString().trim()); + channelCreation.setName(bindingDialog.name.getText().toString().trim()); + if (bindingDialog.description.getText() != null && bindingDialog.description.getText().toString().trim().length() > 0) { + channelCreation.setDescription(bindingDialog.description.getText().toString().trim()); } new Thread(() -> { APIResponse apiResponse; if (oldChannelValues == null) { - apiResponse = new RetrofitPeertubeAPI(context).createOrUpdateChannel(ChannelsVM.action.CREATE_CHANNEL, null, channelCreation); + apiResponse = new RetrofitPeertubeAPI(context).createOrUpdateChannel(ChannelsVM.action.CREATE_CHANNEL, null, channelCreation, inputData); } else { - apiResponse = new RetrofitPeertubeAPI(context).createOrUpdateChannel(ChannelsVM.action.UPDATE_CHANNEL, channelCreation.getName() + "@" + Helper.getLiveInstance(context), channelCreation); + apiResponse = new RetrofitPeertubeAPI(context).createOrUpdateChannel(ChannelsVM.action.UPDATE_CHANNEL, channelCreation.getName() + "@" + Helper.getLiveInstance(context), channelCreation, inputData); } Handler mainHandler = new Handler(Looper.getMainLooper()); Runnable myRunnable = () -> { - if (getActivity() == null) - return; if (oldChannelValues == null) { ChannelData.Channel channel = new ChannelData.Channel(); channel.setId(apiResponse.getActionReturn()); @@ -257,16 +320,10 @@ public class DisplayChannelsFragment extends Fragment implements ChannelListAdap channels.add(0, channel); channelListAdapter.notifyItemInserted(0); } else { - int position = 0; - for (ChannelData.Channel channel : channels) { - if (channel.getName().compareTo(oldChannelValues.getName()) == 0) { - channel.setDescription(channelCreation.getDescription()); - channel.setDisplayName(channelCreation.getDisplayName()); - break; - } - position++; - } - channelListAdapter.notifyItemChanged(position); + channels.get(finalPosition).setName(channelCreation.getName()); + channels.get(finalPosition).setDisplayName(channelCreation.getDisplayName()); + channels.get(finalPosition).setDescription(channelCreation.getDescription()); + channelListAdapter.notifyItemChanged(finalPosition); } if (action_button != null) { action_button.setEnabled(true); @@ -292,7 +349,7 @@ public class DisplayChannelsFragment extends Fragment implements ChannelListAdap //Hide keyboard InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); assert imm != null; - imm.hideSoftInputFromWindow(display_name.getWindowToken(), 0); + imm.hideSoftInputFromWindow(bindingDialog.displayName.getWindowToken(), 0); }); if (alertDialog.getWindow() != null) alertDialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); diff --git a/app/src/main/res/layout/add_channel.xml b/app/src/main/res/layout/add_channel.xml index 00a9b18..0216965 100644 --- a/app/src/main/res/layout/add_channel.xml +++ b/app/src/main/res/layout/add_channel.xml @@ -24,6 +24,30 @@ android:orientation="vertical" android:padding="15dp"> + + + + +