diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsInfoItemHolder.java index fb144574acf..9fe4b543d4f 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsInfoItemHolder.java @@ -5,6 +5,8 @@ import android.widget.ImageView; import android.widget.TextView; +import androidx.recyclerview.widget.RecyclerView; + import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.comments.CommentsInfoItem; @@ -34,12 +36,17 @@ public class CommentsInfoItemHolder extends CommentsMiniInfoItemHolder { public final TextView itemTitleView; private final ImageView itemHeartView; + private final RepliesHandler repliesHandler; public CommentsInfoItemHolder(final InfoItemBuilder infoItemBuilder, final ViewGroup parent) { super(infoItemBuilder, R.layout.list_comments_item, parent); itemTitleView = itemView.findViewById(R.id.itemTitleView); itemHeartView = itemView.findViewById(R.id.detail_heart_image_view); + + final TextView showReplies = itemView.findViewById(R.id.showReplies); + final RecyclerView repliesView = itemView.findViewById(R.id.replyRecycleView); + repliesHandler = new RepliesHandler(showReplies, repliesView); } @Override @@ -55,5 +62,7 @@ public void updateFromItem(final InfoItem infoItem, itemTitleView.setText(item.getUploaderName()); itemHeartView.setVisibility(item.isHeartedByUploader() ? View.VISIBLE : View.GONE); + + repliesHandler.checkForReplies(item); } } diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/RepliesHandler.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/RepliesHandler.java new file mode 100644 index 00000000000..18affbb45ae --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/RepliesHandler.java @@ -0,0 +1,192 @@ +package org.schabi.newpipe.info_list.holder; + +import android.annotation.SuppressLint; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import org.schabi.newpipe.extractor.ListExtractor; +import org.schabi.newpipe.extractor.comments.CommentsInfo; +import org.schabi.newpipe.extractor.comments.CommentsInfoItem; +import org.schabi.newpipe.info_list.InfoListAdapter; +import org.schabi.newpipe.util.ExtractorHelper; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.Callable; + +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; +import io.reactivex.rxjava3.annotations.NonNull; +import io.reactivex.rxjava3.core.Single; +import io.reactivex.rxjava3.core.SingleObserver; +import io.reactivex.rxjava3.disposables.Disposable; +import io.reactivex.rxjava3.schedulers.Schedulers; + +public class RepliesHandler { + private final List cachedReplies; + private final TextView showReplies; + private final RecyclerView repliesView; + + public RepliesHandler(final TextView showReplies, final RecyclerView recyclerView) { + this.repliesView = recyclerView; + repliesView.setAdapter(new InfoListAdapter(repliesView.getContext())); + repliesView.setLayoutManager(new LinearLayoutManager(repliesView.getContext())); + + this.showReplies = showReplies; + this.cachedReplies = new ArrayList<>(); + } + + static class GetMoreItemsCallable implements + Callable> { + private CommentsInfo parentCommentInfo; + private CommentsInfoItem parentInfoItem; + + public void setCallableParameters( + final CommentsInfo commentInfo, final CommentsInfoItem infoItem) { + parentCommentInfo = commentInfo; + parentInfoItem = infoItem; + } + + @Override + public ListExtractor.InfoItemsPage call() throws Exception { + return CommentsInfo.getMoreItems(parentCommentInfo, parentInfoItem.getReplies()); + } + } + + private Single> + repliesSingle(final CommentsInfo parentCommentInfo, + final CommentsInfoItem parentInfoItem) { + + final GetMoreItemsCallable getMoreItems = new GetMoreItemsCallable(); + getMoreItems.setCallableParameters(parentCommentInfo, parentInfoItem); + return Single.fromCallable(getMoreItems); + + } + + private SingleObserver> + repliesObserver(final CommentsInfoItem parentInfoItem) { + + return new SingleObserver>() { + + @SuppressLint("SetTextI18n") + @Override + public void onSubscribe(@NonNull final Disposable d) { + showReplies.setText("Setting up replies"); + } + + @Override + public void onSuccess(@NonNull final + ListExtractor.InfoItemsPage + commentsInfoItemInfoItemsPage) { + + final List actualList + = commentsInfoItemInfoItemsPage.getItems(); + + cachedReplies.addAll(actualList); + addRepliesToUI(parentInfoItem); + } + + @SuppressLint("SetTextI18n") + @Override + public void onError(@NonNull final Throwable e) { + showReplies.setText("Error getting replies"); + } + }; + } + + private SingleObserver + repliesInfoObserver(final CommentsInfoItem parentInfoItem) { + + return new SingleObserver() { + + @SuppressLint("SetTextI18n") + @Override + public void onSubscribe(@NonNull final Disposable d) { + showReplies.setText("Downloading Replies"); + } + + @Override + public void onSuccess(@NonNull final CommentsInfo commentsInfo) { + final Single> + getRepliesInfoSingle = + repliesSingle(commentsInfo, parentInfoItem); + + final SingleObserver> + getRepliesInfoObserver = repliesObserver(parentInfoItem); + + getRepliesInfoSingle + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(getRepliesInfoObserver); + } + + @SuppressLint("SetTextI18n") + @Override + public void onError(@NonNull final Throwable e) { + showReplies.setText("Error getting replies"); + } + }; + } + + + public void addRepliesToUI(final CommentsInfoItem parentInfoItem) { + ((InfoListAdapter) Objects.requireNonNull(repliesView.getAdapter())) + .setInfoItemList(cachedReplies); + + final ViewGroup.MarginLayoutParams params = + (ViewGroup.MarginLayoutParams) repliesView.getLayoutParams(); + params.topMargin = 45; + + repliesView.setMinimumHeight(100); + repliesView.setHasFixedSize(true); + parentInfoItem.setRepliesOpen(true); + showReplies.setVisibility(View.GONE); + repliesView.setVisibility(View.VISIBLE); + } + + public void downloadReplies(final CommentsInfoItem parentInfoItem) { + + final Single parentInfoSingle = ExtractorHelper.getCommentsInfo( + parentInfoItem.getServiceId(), + parentInfoItem.getUrl(), + false + ); + + final SingleObserver singleInfoRepliesInfoObserver + = repliesInfoObserver(parentInfoItem); + + parentInfoSingle + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(singleInfoRepliesInfoObserver); + } + + public void addReplies(final CommentsInfoItem parentInfoItem) { + if (parentInfoItem.getReplies() == null) { + return; + } + + if (cachedReplies.isEmpty()) { + downloadReplies(parentInfoItem); + } else { + addRepliesToUI(parentInfoItem); + } + } + + public void checkForReplies(final CommentsInfoItem item) { + if (item.getReplies() == null) { + repliesView.setVisibility(View.GONE); + showReplies.setVisibility(View.GONE); + } else if (item.getRepliesOpen()) { + addReplies(item); + } else { + repliesView.setVisibility(View.GONE); + showReplies.setVisibility(View.VISIBLE); + showReplies.setOnClickListener(v -> addReplies(item)); + } + } +} diff --git a/app/src/main/res/layout/list_comments_item.xml b/app/src/main/res/layout/list_comments_item.xml index fb526aa0639..0a8bb8c282f 100644 --- a/app/src/main/res/layout/list_comments_item.xml +++ b/app/src/main/res/layout/list_comments_item.xml @@ -123,4 +123,34 @@ android:textSize="@dimen/video_item_search_upload_date_text_size" tools:text="1 year ago" /> + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 897ec0af8b3..09ded2e7d48 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -704,4 +704,5 @@ Error at Show Channel Details Loading Channel Details… + Show Replies