From e201b79388b4231f62d9430af957168dfb4d13ca Mon Sep 17 00:00:00 2001 From: xz-dev Date: Sun, 25 Sep 2022 16:48:26 +0800 Subject: [PATCH 01/25] Show comment replies in DialogFragment --- .../fragments/detail/VideoDetailFragment.java | 2 +- .../fragments/list/BaseListFragment.java | 13 ++++ .../list/comments/CommentReplyDialog.java | 72 +++++++++++++++++++ .../list/comments/CommentsFragment.java | 25 +++++-- .../newpipe/info_list/InfoItemBuilder.java | 10 +++ .../newpipe/info_list/InfoListAdapter.java | 4 ++ .../holder/CommentsMiniInfoItemHolder.java | 19 +++++ .../schabi/newpipe/util/ExtractorHelper.java | 43 ++++++++--- .../main/res/layout/dialog_comment_reply.xml | 38 ++++++++++ .../main/res/layout/list_comments_item.xml | 14 ++++ .../res/layout/list_comments_mini_item.xml | 14 ++++ app/src/main/res/values/strings.xml | 6 ++ 12 files changed, 244 insertions(+), 16 deletions(-) create mode 100644 app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentReplyDialog.java create mode 100644 app/src/main/res/layout/dialog_comment_reply.xml diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index 09e0857910b..63dfdddc5cf 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -960,7 +960,7 @@ private void initTabs() { if (shouldShowComments()) { pageAdapter.addFragment( - CommentsFragment.getInstance(serviceId, url, title), COMMENTS_TAB_TAG); + CommentsFragment.getInstance(serviceId, url, title, null), COMMENTS_TAB_TAG); tabIcons.add(R.drawable.ic_comment); tabContentDescriptions.add(R.string.comments_tab_description); } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java index 9e7cb757ccc..344087260a0 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java @@ -23,9 +23,11 @@ import org.schabi.newpipe.R; import org.schabi.newpipe.error.ErrorUtil; import org.schabi.newpipe.extractor.InfoItem; +import org.schabi.newpipe.extractor.Page; import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.fragments.BaseStateFragment; import org.schabi.newpipe.fragments.OnScrollBelowItemsListener; +import org.schabi.newpipe.fragments.list.comments.CommentReplyDialog; import org.schabi.newpipe.info_list.InfoListAdapter; import org.schabi.newpipe.info_list.dialog.InfoItemDialog; import org.schabi.newpipe.util.NavigationHelper; @@ -283,6 +285,17 @@ public void held(final StreamInfoItem selectedItem) { infoListAdapter.setOnCommentsSelectedListener(this::onItemSelected); + infoListAdapter.setOnCommentsReplyListener(selectedItem -> { + try { + onItemSelected(selectedItem); + final Page reply = selectedItem.getReplies(); + CommentReplyDialog.show(getFM(), selectedItem.getServiceId(), + reply != null ? reply.getUrl() : null, selectedItem.getName(), reply); + } catch (final Exception e) { + ErrorUtil.showUiErrorSnackbar(this, "Opening comment reply fragment", e); + } + }); + // Ensure that there is always a scroll listener (e.g. when rotating the device) useNormalItemListScrollListener(); } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentReplyDialog.java b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentReplyDialog.java new file mode 100644 index 00000000000..2e64e4117d5 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentReplyDialog.java @@ -0,0 +1,72 @@ +package org.schabi.newpipe.fragments.list.comments; + +import android.os.Bundle; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageButton; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.FragmentManager; + +import com.google.android.material.bottomsheet.BottomSheetDialogFragment; + +import org.schabi.newpipe.R; +import org.schabi.newpipe.extractor.Page; +import org.schabi.newpipe.util.Constants; + +import icepick.State; + +public class CommentReplyDialog extends BottomSheetDialogFragment { + + @State + protected int serviceId = Constants.NO_SERVICE_ID; + @State + protected String name; + @State + protected String url; + @State + protected Page replies; + + public static CommentReplyDialog getInstance(final int serviceId, final String url, + final String name, final Page replies) { + final CommentReplyDialog instance = new CommentReplyDialog(); + instance.setInitialData(serviceId, url, name, replies); + return instance; + } + + public static void show(final FragmentManager fragmentManager, + final int serviceId, final String url, + final String name, final Page replies) { + final CommentReplyDialog instance = getInstance(serviceId, url, name, replies); + instance.show(fragmentManager, instance.getTag()); + } + + @Nullable + @Override + public View onCreateView(@NonNull final LayoutInflater inflater, + @Nullable final ViewGroup container, + @Nullable final Bundle savedInstanceState) { + + final View view = inflater.inflate(R.layout.dialog_comment_reply, container, + false); + final ImageButton backButton = view.findViewById(R.id.backButton); + backButton.setOnClickListener(v -> dismiss()); + final CommentsFragment commentsFragment = CommentsFragment.getInstance( + serviceId, url, name, replies + ); + getChildFragmentManager().beginTransaction() + .add(R.id.commentFragment, commentsFragment).commit(); + return view; + } + + protected void setInitialData(final int sid, + final String u, final String title, final Page repliesPage) { + this.serviceId = sid; + this.url = u; + this.name = !TextUtils.isEmpty(title) ? title : ""; + this.replies = repliesPage; + } +} diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentsFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentsFragment.java index 3b092cc2885..f34fb6b2325 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentsFragment.java @@ -14,6 +14,7 @@ import org.schabi.newpipe.R; import org.schabi.newpipe.error.UserAction; import org.schabi.newpipe.extractor.ListExtractor; +import org.schabi.newpipe.extractor.Page; import org.schabi.newpipe.extractor.comments.CommentsInfo; import org.schabi.newpipe.extractor.comments.CommentsInfoItem; import org.schabi.newpipe.fragments.list.BaseListInfoFragment; @@ -26,12 +27,14 @@ public class CommentsFragment extends BaseListInfoFragment { private final CompositeDisposable disposables = new CompositeDisposable(); + private Page replies; + private TextView emptyStateDesc; public static CommentsFragment getInstance(final int serviceId, final String url, - final String name) { + final String name, final Page replyPage) { final CommentsFragment instance = new CommentsFragment(); - instance.setInitialData(serviceId, url, name); + instance.setInitialData(serviceId, url, name, replyPage); return instance; } @@ -39,6 +42,12 @@ public CommentsFragment() { super(UserAction.REQUESTED_COMMENTS); } + protected void setInitialData(final int sid, final String u, + final String title, final Page repliesPage) { + this.replies = repliesPage; + super.setInitialData(sid, u, title); + } + @Override protected void initViews(final View rootView, final Bundle savedInstanceState) { super.initViews(rootView, savedInstanceState); @@ -74,7 +83,11 @@ protected Single> loadMoreItemsLog @Override protected Single loadResult(final boolean forceLoad) { - return ExtractorHelper.getCommentsInfo(serviceId, url, forceLoad); + if (replies == null) { + return ExtractorHelper.getCommentsInfo(serviceId, url, forceLoad); + } else { + return ExtractorHelper.getCommentsReplyInfo(serviceId, url, forceLoad, replies); + } } /*////////////////////////////////////////////////////////////////////////// @@ -99,11 +112,13 @@ public void handleResult(@NonNull final CommentsInfo result) { //////////////////////////////////////////////////////////////////////////*/ @Override - public void setTitle(final String title) { } + public void setTitle(final String title) { + } @Override public void onCreateOptionsMenu(@NonNull final Menu menu, - @NonNull final MenuInflater inflater) { } + @NonNull final MenuInflater inflater) { + } @Override protected boolean isGridLayout() { diff --git a/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java b/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java index 68f19ee9714..5e081a101af 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java @@ -54,6 +54,7 @@ public class InfoItemBuilder { private OnClickGesture onChannelSelectedListener; private OnClickGesture onPlaylistSelectedListener; private OnClickGesture onCommentsSelectedListener; + private OnClickGesture onCommentsReplyListener; public InfoItemBuilder(final Context context) { this.context = context; @@ -130,4 +131,13 @@ public void setOnCommentsSelectedListener( final OnClickGesture onCommentsSelectedListener) { this.onCommentsSelectedListener = onCommentsSelectedListener; } + + public OnClickGesture getOnCommentsReplyListener() { + return onCommentsReplyListener; + } + + public void setOnCommentsReplyListener( + final OnClickGesture onCommentsReplyListener) { + this.onCommentsReplyListener = onCommentsReplyListener; + } } diff --git a/app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java b/app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java index fb27593e7e0..714266149a7 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java @@ -110,6 +110,10 @@ public void setOnCommentsSelectedListener(final OnClickGesture infoItemBuilder.setOnCommentsSelectedListener(listener); } + public void setOnCommentsReplyListener(final OnClickGesture listener) { + infoItemBuilder.setOnCommentsReplyListener(listener); + } + public void setUseMiniVariant(final boolean useMiniVariant) { this.useMiniVariant = useMiniVariant; } diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java index b900750a8f3..c3a6999fb4c 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java @@ -1,5 +1,6 @@ package org.schabi.newpipe.info_list.holder; +import android.content.res.Resources; import android.text.TextUtils; import android.text.method.LinkMovementMethod; import android.text.style.URLSpan; @@ -7,6 +8,7 @@ import android.util.Log; import android.view.View; import android.view.ViewGroup; +import android.widget.Button; import android.widget.ImageView; import android.widget.RelativeLayout; import android.widget.TextView; @@ -43,6 +45,7 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder { private final TextView itemContentView; private final TextView itemLikesCountView; private final TextView itemPublishedTime; + private final Button itemContentReplyButton; private String commentText; private String streamUrl; @@ -77,6 +80,7 @@ public String transformUrl(final Matcher match, final String url) { itemLikesCountView = itemView.findViewById(R.id.detail_thumbs_up_count_view); itemPublishedTime = itemView.findViewById(R.id.itemPublishedTime); itemContentView = itemView.findViewById(R.id.itemCommentContentView); + itemContentReplyButton = itemView.findViewById(R.id.itemContentReplyButton); commentHorizontalPadding = (int) infoItemBuilder.getContext() .getResources().getDimension(R.dimen.comments_horizontal_padding); @@ -140,6 +144,21 @@ public void updateFromItem(final InfoItem infoItem, itemPublishedTime.setText(item.getTextualUploadDate()); } + if (item.getReplies() != null) { + final Resources resources = itemView.getResources(); + final String commentCountText = resources.getQuantityString( + R.plurals.comment_reply_count, + 1, 1 + ); + itemContentReplyButton.setVisibility(View.VISIBLE); + itemContentReplyButton.setText(commentCountText); + itemContentReplyButton.setOnClickListener( + view -> itemBuilder.getOnCommentsReplyListener().selected(item) + ); + } else { + itemContentReplyButton.setVisibility(View.GONE); + } + itemView.setOnClickListener(view -> { toggleEllipsize(); if (itemBuilder.getOnCommentsSelectedListener() != null) { diff --git a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java index 27009efd192..89b7f8fed93 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java @@ -150,6 +150,28 @@ public static Single> getFeedInfoFallbackToChannelInfo( return maybeFeedInfo.switchIfEmpty(getChannelInfo(serviceId, url, true)); } + public static Single getCommentsReplyInfo(final int serviceId, final String url, + final boolean forceLoad, + final Page replyPage) { + checkServiceId(serviceId); + return checkCache(forceLoad, serviceId, + url + "?reply_placeholder_id=" + replyPage.getId(), + InfoItem.InfoType.COMMENT, + Single.fromCallable(() -> { + final var info = CommentsInfo.getInfo( + NewPipe.getService(serviceId), url); + // use CommentsInfo make a info template + final var replies = CommentsInfo.getMoreItems( + NewPipe.getService(serviceId), info, replyPage); + info.setRelatedItems(replies.getItems()); + // push replies to info, replace original comments + info.setNextPage(null); + // comment replies haven't next page + return info; + } + )); + } + public static Single getCommentsInfo(final int serviceId, final String url, final boolean forceLoad) { checkServiceId(serviceId); @@ -228,7 +250,7 @@ private static Single checkCache(final boolean forceLoad, load = actualLoadFromNetwork; } else { load = Maybe.concat(ExtractorHelper.loadFromCache(serviceId, url, infoType), - actualLoadFromNetwork.toMaybe()) + actualLoadFromNetwork.toMaybe()) .firstElement() // Take the first valid .toSingle(); } @@ -239,10 +261,10 @@ private static Single checkCache(final boolean forceLoad, /** * Default implementation uses the {@link InfoCache} to get cached results. * - * @param the item type's class that extends {@link Info} - * @param serviceId the service to load from - * @param url the URL to load - * @param infoType the {@link InfoItem.InfoType} of the item + * @param the item type's class that extends {@link Info} + * @param serviceId the service to load from + * @param url the URL to load + * @param infoType the {@link InfoItem.InfoType} of the item * @return a {@link Single} that loads the item */ private static Maybe loadFromCache(final int serviceId, final String url, @@ -273,11 +295,12 @@ public static boolean isCached(final int serviceId, final String url, * Formats the text contained in the meta info list as HTML and puts it into the text view, * while also making the separator visible. If the list is null or empty, or the user chose not * to see meta information, both the text view and the separator are hidden - * @param metaInfos a list of meta information, can be null or empty - * @param metaInfoTextView the text view in which to show the formatted HTML + * + * @param metaInfos a list of meta information, can be null or empty + * @param metaInfoTextView the text view in which to show the formatted HTML * @param metaInfoSeparator another view to be shown or hidden accordingly to the text view - * @param disposables disposables created by the method are added here and their lifecycle - * should be handled by the calling class + * @param disposables disposables created by the method are added here and their lifecycle + * should be handled by the calling class */ public static void showMetaInfoInTextView(@Nullable final List metaInfos, final TextView metaInfoTextView, @@ -286,7 +309,7 @@ public static void showMetaInfoInTextView(@Nullable final List metaInf final Context context = metaInfoTextView.getContext(); if (metaInfos == null || metaInfos.isEmpty() || !PreferenceManager.getDefaultSharedPreferences(context).getBoolean( - context.getString(R.string.show_meta_info_key), true)) { + context.getString(R.string.show_meta_info_key), true)) { metaInfoTextView.setVisibility(View.GONE); metaInfoSeparator.setVisibility(View.GONE); diff --git a/app/src/main/res/layout/dialog_comment_reply.xml b/app/src/main/res/layout/dialog_comment_reply.xml new file mode 100644 index 00000000000..ca3d95441e3 --- /dev/null +++ b/app/src/main/res/layout/dialog_comment_reply.xml @@ -0,0 +1,38 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/list_comments_item.xml b/app/src/main/res/layout/list_comments_item.xml index ad73c5ff43e..32799bbaea9 100644 --- a/app/src/main/res/layout/list_comments_item.xml +++ b/app/src/main/res/layout/list_comments_item.xml @@ -108,4 +108,18 @@ android:textSize="@dimen/video_item_search_upload_date_text_size" tools:text="1 year ago" /> +