package app.fedilab.fedilabtube.drawer; /* Copyright 2020 Thomas Schneider * * This file is a part of TubeLab * * This program is free software; you can redistribute it and/or modify it under the terms of the * GNU General Public License as published by the Free Software Foundation; either version 3 of the * License, or (at your option) any later version. * * TubeLab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * Public License for more details. * * You should have received a copy of the GNU General Public License along with TubeLab; if not, * see . */ import static app.fedilab.fedilabtube.client.RetrofitPeertubeAPI.ActionType.FOLLOW; import static app.fedilab.fedilabtube.client.RetrofitPeertubeAPI.ActionType.UNFOLLOW; import static app.fedilab.fedilabtube.viewmodel.TimelineVM.TimelineType.MY_VIDEOS; import static app.fedilab.fedilabtube.viewmodel.TimelineVM.TimelineType.SEPIA_SEARCH; import android.annotation.SuppressLint; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.EditText; import android.widget.ImageView; import android.widget.LinearLayout; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.widget.PopupMenu; import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelStoreOwner; import androidx.recyclerview.widget.RecyclerView; import com.bumptech.glide.Glide; import com.bumptech.glide.RequestBuilder; import com.bumptech.glide.load.resource.bitmap.CenterCrop; import com.bumptech.glide.load.resource.bitmap.RoundedCorners; import com.bumptech.glide.request.RequestOptions; import com.bumptech.glide.request.target.CustomTarget; import com.bumptech.glide.request.transition.Transition; import java.util.ArrayList; import java.util.List; import java.util.Map; import app.fedilab.fedilabtube.activities.PeertubeActivity; import app.fedilab.fedilabtube.activities.PeertubeEditUploadActivity; import app.fedilab.fedilabtube.R; import app.fedilab.fedilabtube.activities.ShowChannelActivity; import app.fedilab.fedilabtube.client.APIResponse; import app.fedilab.fedilabtube.client.RetrofitPeertubeAPI; import app.fedilab.fedilabtube.client.data.AccountData; import app.fedilab.fedilabtube.client.data.ChannelData; import app.fedilab.fedilabtube.client.data.PlaylistData; import app.fedilab.fedilabtube.client.data.VideoData; import app.fedilab.fedilabtube.client.entities.PlaylistExist; import app.fedilab.fedilabtube.client.entities.Report; import app.fedilab.fedilabtube.databinding.DrawerPeertubeBinding; import app.fedilab.fedilabtube.helper.Helper; import app.fedilab.fedilabtube.helper.HelperInstance; import app.fedilab.fedilabtube.viewmodel.PlaylistsVM; import app.fedilab.fedilabtube.viewmodel.PostActionsVM; import app.fedilab.fedilabtube.viewmodel.TimelineVM; import es.dmoral.toasty.Toasty; import jp.wasabeef.glide.transformations.BlurTransformation; public class PeertubeAdapter extends RecyclerView.Adapter { private final List videos; public RelationShipListener relationShipListener; public PlaylistListener playlistListener; private Context context; private TimelineVM.TimelineType timelineType; private boolean sepiaSearch; private ChannelData.Channel forChannel; private AccountData.Account forAccount; public PeertubeAdapter(List videos, TimelineVM.TimelineType timelineType, boolean sepiaSearch, ChannelData.Channel forChannel, AccountData.Account forAccount) { this.videos = videos; this.timelineType = timelineType; this.sepiaSearch = sepiaSearch || timelineType == SEPIA_SEARCH; this.forChannel = forChannel; this.forAccount = forAccount; } public PeertubeAdapter(List videos) { this.videos = videos; } @NonNull @Override public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { context = parent.getContext(); DrawerPeertubeBinding itemBinding = DrawerPeertubeBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false); return new ViewHolder(itemBinding); } @Override public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) { final PeertubeAdapter.ViewHolder holder = (PeertubeAdapter.ViewHolder) viewHolder; final VideoData.Video video = videos.get(position); if (video == null) { return; } SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); String userId = sharedpreferences.getString(Helper.PREF_KEY_ID, ""); assert userId != null; boolean videoInList = sharedpreferences.getBoolean(context.getString(R.string.set_video_in_list_choice), false); boolean ownVideos; if (timelineType == TimelineVM.TimelineType.MY_VIDEOS) { ownVideos = true; } else { ownVideos = Helper.isVideoOwner(context, video); } String instance = null; if (sepiaSearch) { instance = video.getAccount().getHost(); } else if (forChannel != null) { instance = forChannel.getHost(); } else if (forAccount != null) { instance = forAccount.getHost(); } holder.binding.peertubeAccountName.setText(video.getChannel().getAcct()); Helper.loadGiF(context, instance, video.getChannel().getAvatar() != null ? video.getChannel().getAvatar().getPath() : null, holder.binding.peertubeProfile); holder.binding.peertubeTitle.setText(video.getName()); if (video.isLive()) { holder.binding.peertubeDuration.setText(R.string.live); holder.binding.peertubeDuration.setBackgroundResource(R.drawable.rounded_live); } else { holder.binding.peertubeDuration.setText(Helper.secondsToString(video.getDuration())); holder.binding.peertubeDuration.setBackgroundResource(R.drawable.rounded_corner); } holder.binding.peertubeDate.setText(String.format(" - %s", Helper.dateDiff(context, video.getCreatedAt()))); holder.binding.peertubeViews.setText(context.getString(R.string.number_view_video, Helper.withSuffix(video.getViews()))); boolean blur = sharedpreferences.getString(context.getString(R.string.set_video_sensitive_choice), Helper.BLUR).compareTo("blur") == 0 && video.isNsfw(); if (videoInList) { Helper.loadGiF(context, instance, video.getThumbnailPath(), holder.binding.peertubeVideoImageSmall, blur); holder.binding.peertubeVideoImageSmall.setVisibility(View.VISIBLE); holder.binding.previewContainer.setVisibility(View.GONE); } else { loadImage(holder.binding.peertubeVideoImage, instance, video.getPreviewPath(), video.getThumbnailPath(), blur); holder.binding.peertubeVideoImageSmall.setVisibility(View.GONE); holder.binding.previewContainer.setVisibility(View.VISIBLE); } //For Overview Videos: boolean values for displaying title is managed in the fragment if (video.isHasTitle()) { holder.binding.headerTitle.setVisibility(View.VISIBLE); switch (video.getTitleType()) { case TAG: holder.binding.headerTitle.setText(String.format("#%s", video.getTitle())); break; case CHANNEL: case CATEGORY: holder.binding.headerTitle.setText(String.format("%s", video.getTitle())); break; } } else { holder.binding.headerTitle.setVisibility(View.GONE); } if (!ownVideos) { holder.binding.peertubeProfile.setOnClickListener(v -> { Intent intent = new Intent(context, ShowChannelActivity.class); Bundle b = new Bundle(); b.putParcelable("channel", video.getChannel()); b.putBoolean("sepia_search", sepiaSearch || forChannel != null); if (sepiaSearch || forChannel != null) { b.putString("peertube_instance", video.getAccount().getHost()); } intent.putExtras(b); context.startActivity(intent); }); } holder.binding.moreActions.setOnClickListener(view -> { PopupMenu popup = new PopupMenu(context, holder.binding.moreActions); popup.getMenuInflater() .inflate(R.menu.video_drawer_menu, popup.getMenu()); if (timelineType == MY_VIDEOS) { popup.getMenu().findItem(R.id.action_report).setVisible(false); popup.getMenu().findItem(R.id.action_follow).setVisible(false); } else { popup.getMenu().findItem(R.id.action_edit).setVisible(false); if (relationShipListener == null || relationShipListener.getRelationShip() == null || relationShipListener.getRelationShip().size() == 0) { popup.getMenu().findItem(R.id.action_follow).setVisible(false); } else { popup.getMenu().findItem(R.id.action_follow).setVisible(true); if (relationShipListener.getRelationShip().containsKey(video.getChannel().getAcct()) && relationShipListener.getRelationShip().get(video.getChannel().getAcct())) { popup.getMenu().findItem(R.id.action_follow).setTitle(context.getString(R.string.action_unfollow)); } else { popup.getMenu().findItem(R.id.action_follow).setTitle(context.getString(R.string.action_follow)); } } } popup.getMenu().findItem(R.id.action_playlist).setVisible(playlistListener != null && playlistListener.getPlaylist() != null && playlistListener.getPlaylist().size() != 0); popup.setOnMenuItemClickListener(item -> { int itemId = item.getItemId(); if (itemId == R.id.action_follow) { if (relationShipListener.getRelationShip().containsKey(video.getChannel().getAcct()) && relationShipListener.getRelationShip().get(video.getChannel().getAcct())) { relationShipListener.getRelationShip().put(video.getChannel().getAcct(), false); popup.getMenu().findItem(R.id.action_follow).setTitle(context.getString(R.string.action_follow)); boolean confirm_unfollow = sharedpreferences.getBoolean(Helper.SET_UNFOLLOW_VALIDATION, true); if (confirm_unfollow) { AlertDialog.Builder unfollowConfirm = new AlertDialog.Builder(context); unfollowConfirm.setTitle(context.getString(R.string.unfollow_confirm)); unfollowConfirm.setMessage(video.getChannel().getAcct()); unfollowConfirm.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss()); unfollowConfirm.setPositiveButton(R.string.yes, (dialog, which) -> { PostActionsVM viewModel = new ViewModelProvider((ViewModelStoreOwner) context).get(PostActionsVM.class); viewModel.post(UNFOLLOW, video.getChannel().getAcct(), null).observe((LifecycleOwner) context, apiResponse -> manageVIewPostActions(UNFOLLOW, apiResponse)); dialog.dismiss(); }); unfollowConfirm.show(); } else { PostActionsVM viewModel = new ViewModelProvider((ViewModelStoreOwner) context).get(PostActionsVM.class); viewModel.post(UNFOLLOW, video.getChannel().getAcct(), null).observe((LifecycleOwner) context, apiResponse -> manageVIewPostActions(UNFOLLOW, apiResponse)); } } else { relationShipListener.getRelationShip().put(video.getChannel().getAcct(), true); popup.getMenu().findItem(R.id.action_follow).setTitle(context.getString(R.string.action_unfollow)); PostActionsVM viewModel = new ViewModelProvider((ViewModelStoreOwner) context).get(PostActionsVM.class); viewModel.post(FOLLOW, video.getChannel().getAcct(), null).observe((LifecycleOwner) context, apiResponse -> manageVIewPostActions(FOLLOW, apiResponse)); } } else if (itemId == R.id.action_playlist) { PlaylistsVM viewModelOwnerPlaylist = new ViewModelProvider((ViewModelStoreOwner) context).get(PlaylistsVM.class); viewModelOwnerPlaylist.manage(PlaylistsVM.action.GET_PLAYLISTS, null, null).observe((LifecycleOwner) context, apiResponse -> manageVIewPlaylists(video, apiResponse)); } else if (itemId == R.id.action_edit) { Intent intent = new Intent(context, PeertubeEditUploadActivity.class); Bundle b = new Bundle(); b.putString("video_id", video.getUuid()); intent.putExtras(b); context.startActivity(intent); } else if (itemId == R.id.action_report) { AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(context); LayoutInflater inflater1 = ((Activity) context).getLayoutInflater(); View dialogView = inflater1.inflate(R.layout.popup_report, new LinearLayout(context), false); dialogBuilder.setView(dialogView); EditText report_content = dialogView.findViewById(R.id.report_content); dialogBuilder.setNeutralButton(R.string.cancel, (dialog2, id) -> dialog2.dismiss()); dialogBuilder.setPositiveButton(R.string.report, (dialog2, id) -> { if (report_content.getText().toString().trim().length() == 0) { Toasty.info(context, context.getString(R.string.report_comment_size), Toasty.LENGTH_LONG).show(); } else { PostActionsVM viewModel = new ViewModelProvider((ViewModelStoreOwner) context).get(PostActionsVM.class); Report report = new Report(); Report.VideoReport videoReport = new Report.VideoReport(); videoReport.setId(video.getId()); report.setVideo(videoReport); report.setReason(report_content.getText().toString()); viewModel.report(report).observe((LifecycleOwner) context, apiResponse -> manageVIewPostActions(RetrofitPeertubeAPI.ActionType.REPORT_VIDEO, apiResponse)); dialog2.dismiss(); } }); AlertDialog alertDialog2 = dialogBuilder.create(); alertDialog2.show(); } return true; }); popup.show(); }); holder.binding.bottomContainer.setOnClickListener(v -> { Intent intent = new Intent(context, PeertubeActivity.class); Bundle b = new Bundle(); b.putString("video_id", video.getId()); b.putString("video_uuid", video.getUuid()); b.putBoolean("isMyVideo", ownVideos); b.putBoolean("sepia_search", sepiaSearch); b.putParcelable("video", video); if (sepiaSearch) { b.putString("peertube_instance", video.getAccount().getHost()); } intent.putExtras(b); context.startActivity(intent); }); holder.binding.peertubeVideoImage.setOnClickListener(v -> { Intent intent = new Intent(context, PeertubeActivity.class); Bundle b = new Bundle(); b.putString("video_id", video.getId()); b.putParcelable("video", video); b.putString("video_uuid", video.getUuid()); b.putBoolean("isMyVideo", ownVideos); b.putBoolean("sepia_search", sepiaSearch); if (sepiaSearch) { b.putString("peertube_instance", video.getAccount().getHost()); } intent.putExtras(b); context.startActivity(intent); }); } public void manageVIewPlaylists(VideoData.Video video, APIResponse apiResponse) { if (apiResponse.getError() != null) { return; } if (apiResponse.getPlaylists() != null && apiResponse.getPlaylists().size() > 0) { androidx.appcompat.app.AlertDialog.Builder builder = new androidx.appcompat.app.AlertDialog.Builder(context); builder.setTitle(R.string.modify_playlists); List ownerPlaylists = apiResponse.getPlaylists(); if (ownerPlaylists == null) { return; } String[] label = new String[ownerPlaylists.size()]; boolean[] checked = new boolean[ownerPlaylists.size()]; int i = 0; List playlistsForVideo = playlistListener.getPlaylist().get(video.getId()); for (PlaylistData.Playlist playlist : ownerPlaylists) { checked[i] = false; if (playlistsForVideo != null) { for (PlaylistExist playlistExist : playlistsForVideo) { if (playlistExist != null && playlistExist.getPlaylistId().compareTo(playlist.getId()) == 0) { checked[i] = true; break; } } } label[i] = playlist.getDisplayName(); i++; } builder.setMultiChoiceItems(label, checked, (dialog, which, isChecked) -> { PlaylistsVM playlistsViewModel = new ViewModelProvider((ViewModelStoreOwner) context).get(PlaylistsVM.class); if (isChecked) { //Add to playlist playlistsViewModel.manage(PlaylistsVM.action.ADD_VIDEOS, ownerPlaylists.get(which), video.getUuid()).observe((LifecycleOwner) context, apiResponse3 -> addElement(ownerPlaylists.get(which).getId(), video.getId(), apiResponse3)); } else { //Remove from playlist String elementInPlaylistId = null; for (PlaylistExist playlistExist : video.getPlaylistExists()) { if (playlistExist.getPlaylistId().compareTo(ownerPlaylists.get(which).getId()) == 0) { elementInPlaylistId = playlistExist.getPlaylistElementId(); } } playlistsViewModel.manage(PlaylistsVM.action.DELETE_VIDEOS, ownerPlaylists.get(which), elementInPlaylistId); playlistListener.getPlaylist().remove(video.getId()); } }); builder.setPositiveButton(R.string.close, (dialog, which) -> dialog.dismiss()); androidx.appcompat.app.AlertDialog dialog = builder.create(); dialog.show(); } } public void addElement(String playlistId, String videoId, APIResponse apiResponse) { if (apiResponse != null && apiResponse.getActionReturn() != null) { PlaylistExist playlistExist = new PlaylistExist(); playlistExist.setPlaylistId(playlistId); playlistExist.setPlaylistElementId(apiResponse.getActionReturn()); List playlistExistList = playlistListener.getPlaylist().get(videoId); if (playlistExistList == null) { playlistExistList = new ArrayList<>(); } playlistExistList.add(playlistExist); playlistListener.getPlaylist().put(videoId, playlistExistList); } } @Override public long getItemId(int position) { return position; } @Override public int getItemCount() { return videos.size(); } public void manageVIewPostActions(RetrofitPeertubeAPI.ActionType statusAction, APIResponse apiResponse) { if (statusAction == RetrofitPeertubeAPI.ActionType.REPORT_VIDEO) { Toasty.success(context, context.getString(R.string.successful_video_report), Toasty.LENGTH_LONG).show(); } else if (statusAction == RetrofitPeertubeAPI.ActionType.UNFOLLOW) { Bundle b = new Bundle(); b.putString("receive_action", apiResponse.getTargetedId()); Intent intentBC = new Intent(Helper.RECEIVE_ACTION); intentBC.putExtras(b); } } @SuppressLint("CheckResult") private void loadImage(ImageView target, String instance, String urlPreview, String thumbnail, boolean blur) { if (urlPreview == null || urlPreview.startsWith("null")) { urlPreview = thumbnail; } if (instance != null) { urlPreview = "https://" + instance + urlPreview; thumbnail = "https://" + instance + thumbnail; } else { urlPreview = "https://" + HelperInstance.getLiveInstance(context) + urlPreview; thumbnail = "https://" + HelperInstance.getLiveInstance(context) + thumbnail; } RequestBuilder requestBuilder = Glide.with(context) .asDrawable(); if (blur) { requestBuilder.apply(new RequestOptions().transform(new BlurTransformation(50, 3), new CenterCrop(), new RoundedCorners(10))); } else { requestBuilder.apply(new RequestOptions().transform(new CenterCrop(), new RoundedCorners(10))); } final Drawable[] initialResource = new Drawable[1]; String finalUrlPreview = urlPreview; requestBuilder.load(thumbnail).into( new CustomTarget() { @Override public void onResourceReady(@NonNull final Drawable resource, Transition transition) { target.setImageDrawable(resource); initialResource[0] = resource; RequestBuilder requestBuilder = Glide.with(context) .asDrawable(); if (blur) { requestBuilder.apply(new RequestOptions().transform(new BlurTransformation(50, 3), new CenterCrop(), new RoundedCorners(10))); } else { requestBuilder.apply(new RequestOptions().transform(new CenterCrop(), new RoundedCorners(10))); } requestBuilder.load(finalUrlPreview).into( new CustomTarget() { @Override public void onResourceReady(@NonNull final Drawable resource, Transition transition) { target.setImageDrawable(resource); } @Override public void onLoadCleared(@Nullable Drawable placeholder) { } @Override public void onLoadFailed(@Nullable Drawable errorDrawable) { target.setImageDrawable(initialResource[0]); } } ); } @Override public void onLoadCleared(@Nullable Drawable placeholder) { } } ); } public interface RelationShipListener { Map getRelationShip(); } public interface PlaylistListener { Map> getPlaylist(); } static class ViewHolder extends RecyclerView.ViewHolder { DrawerPeertubeBinding binding; ViewHolder(DrawerPeertubeBinding itemView) { super(itemView.getRoot()); binding = itemView; } } }