1
0
mirror of https://github.com/TeamNewPipe/NewPipe synced 2024-12-25 01:20:34 +00:00

Use reply header composable in fragment

This commit is contained in:
Isira Seneviratne 2024-05-12 08:49:15 +05:30
parent 644a345b55
commit e05d97732e
4 changed files with 100 additions and 303 deletions

View File

@ -871,7 +871,7 @@ public class MainActivity extends AppCompatActivity {
@Nullable final CommentRepliesFragment repliesFragment = @Nullable final CommentRepliesFragment repliesFragment =
(CommentRepliesFragment) fm.findFragmentByTag(CommentRepliesFragment.TAG); (CommentRepliesFragment) fm.findFragmentByTag(CommentRepliesFragment.TAG);
@Nullable final CommentsInfoItem rootComment = @Nullable final CommentsInfoItem rootComment =
repliesFragment == null ? null : repliesFragment.getCommentsInfoItem(); repliesFragment == null ? null : repliesFragment.commentsInfoItem;
// sometimes this function pops the backstack, other times it's handled by the system // sometimes this function pops the backstack, other times it's handled by the system
if (popBackStack) { if (popBackStack) {

View File

@ -1,170 +1,104 @@
package org.schabi.newpipe.fragments.list.comments; package org.schabi.newpipe.fragments.list.comments
import static org.schabi.newpipe.util.ServiceHelper.getServiceById; import android.os.Bundle
import android.view.LayoutInflater
import android.os.Bundle; import android.view.View
import android.view.LayoutInflater; import android.view.ViewGroup
import android.view.View; import androidx.compose.ui.platform.ComposeView
import android.view.ViewGroup; import icepick.State
import io.reactivex.rxjava3.core.Single
import androidx.annotation.NonNull; import io.reactivex.rxjava3.disposables.CompositeDisposable
import androidx.annotation.Nullable; import org.schabi.newpipe.R
import androidx.constraintlayout.widget.ConstraintLayout; import org.schabi.newpipe.error.UserAction
import androidx.core.text.HtmlCompat; import org.schabi.newpipe.extractor.ListExtractor.InfoItemsPage
import org.schabi.newpipe.extractor.comments.CommentsInfoItem
import org.schabi.newpipe.R; import org.schabi.newpipe.fragments.list.BaseListInfoFragment
import org.schabi.newpipe.databinding.CommentRepliesHeaderBinding; import org.schabi.newpipe.info_list.ItemViewMode
import org.schabi.newpipe.error.UserAction; import org.schabi.newpipe.util.ExtractorHelper
import org.schabi.newpipe.extractor.ListExtractor; import org.schabi.newpipe.util.Localization
import org.schabi.newpipe.extractor.comments.CommentsInfoItem; import java.util.Queue
import org.schabi.newpipe.fragments.list.BaseListInfoFragment; import java.util.function.Supplier
import org.schabi.newpipe.info_list.ItemViewMode;
import org.schabi.newpipe.util.DeviceUtils;
import org.schabi.newpipe.util.ExtractorHelper;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.image.CoilHelper;
import org.schabi.newpipe.util.image.ImageStrategy;
import org.schabi.newpipe.util.text.TextLinkifier;
import java.util.Queue;
import java.util.function.Supplier;
import icepick.State;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
public final class CommentRepliesFragment
extends BaseListInfoFragment<CommentsInfoItem, CommentRepliesInfo> {
public static final String TAG = CommentRepliesFragment.class.getSimpleName();
@State
CommentsInfoItem commentsInfoItem; // the comment to show replies of
private final CompositeDisposable disposables = new CompositeDisposable();
/*//////////////////////////////////////////////////////////////////////////
// Constructors and lifecycle
//////////////////////////////////////////////////////////////////////////*/
// only called by the Android framework, after which readFrom is called and restores all data
public CommentRepliesFragment() {
super(UserAction.REQUESTED_COMMENT_REPLIES);
}
public CommentRepliesFragment(@NonNull final CommentsInfoItem commentsInfoItem) {
this();
this.commentsInfoItem = commentsInfoItem;
// setting "" as title since the title will be properly set right after
setInitialData(commentsInfoItem.getServiceId(), commentsInfoItem.getUrl(), "");
}
@Nullable
@Override
public View onCreateView(@NonNull final LayoutInflater inflater,
@Nullable final ViewGroup container,
@Nullable final Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_comments, container, false);
}
@Override
public void onDestroyView() {
disposables.clear();
super.onDestroyView();
}
@Override
protected Supplier<View> getListHeaderSupplier() {
return () -> {
final CommentRepliesHeaderBinding binding = CommentRepliesHeaderBinding
.inflate(activity.getLayoutInflater(), itemsList, false);
final CommentsInfoItem item = commentsInfoItem;
// load the author avatar
CoilHelper.INSTANCE.loadAvatar(binding.authorAvatar, item.getUploaderAvatars());
binding.authorAvatar.setVisibility(ImageStrategy.shouldLoadImages()
? View.VISIBLE : View.GONE);
// setup author name and comment date
binding.authorName.setText(item.getUploaderName());
binding.uploadDate.setText(Localization.relativeTimeOrTextual(
getContext(), item.getUploadDate(), item.getTextualUploadDate()));
binding.authorTouchArea.setOnClickListener(
v -> NavigationHelper.openCommentAuthorIfPresent(requireActivity(), item));
// setup like count, hearted and pinned
binding.thumbsUpCount.setText(
Localization.likeCount(requireContext(), item.getLikeCount()));
// for heartImage goneMarginEnd was used, but there is no way to tell ConstraintLayout
// not to use a different margin only when both the next two views are gone
((ConstraintLayout.LayoutParams) binding.thumbsUpCount.getLayoutParams())
.setMarginEnd(DeviceUtils.dpToPx(
(item.isHeartedByUploader() || item.isPinned() ? 8 : 16),
requireContext()));
binding.heartImage.setVisibility(item.isHeartedByUploader() ? View.VISIBLE : View.GONE);
binding.pinnedImage.setVisibility(item.isPinned() ? View.VISIBLE : View.GONE);
// setup comment content
TextLinkifier.fromDescription(binding.commentContent, item.getCommentText(),
HtmlCompat.FROM_HTML_MODE_LEGACY, getServiceById(item.getServiceId()),
item.getUrl(), disposables, null);
return binding.getRoot();
};
}
/*//////////////////////////////////////////////////////////////////////////
// State saving
//////////////////////////////////////////////////////////////////////////*/
@Override
public void writeTo(final Queue<Object> objectsToSave) {
super.writeTo(objectsToSave);
objectsToSave.add(commentsInfoItem);
}
@Override
public void readFrom(@NonNull final Queue<Object> savedObjects) throws Exception {
super.readFrom(savedObjects);
commentsInfoItem = (CommentsInfoItem) savedObjects.poll();
}
/*//////////////////////////////////////////////////////////////////////////
// Data loading
//////////////////////////////////////////////////////////////////////////*/
@Override
protected Single<CommentRepliesInfo> loadResult(final boolean forceLoad) {
return Single.fromCallable(() -> new CommentRepliesInfo(commentsInfoItem,
// the reply count string will be shown as the activity title
Localization.replyCount(requireContext(), commentsInfoItem.getReplyCount())));
}
@Override
protected Single<ListExtractor.InfoItemsPage<CommentsInfoItem>> loadMoreItemsLogic() {
// commentsInfoItem.getUrl() should contain the url of the original
// ListInfo<CommentsInfoItem>, which should be the stream url
return ExtractorHelper.getMoreCommentItems(
serviceId, commentsInfoItem.getUrl(), currentNextPage);
}
/*//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////*/
@Override
protected ItemViewMode getItemViewMode() {
return ItemViewMode.LIST;
}
class CommentRepliesFragment() : BaseListInfoFragment<CommentsInfoItem, CommentRepliesInfo>(UserAction.REQUESTED_COMMENT_REPLIES) {
/** /**
* @return the comment to which the replies are shown * @return the comment to which the replies are shown
*/ */
public CommentsInfoItem getCommentsInfoItem() { @State
return commentsInfoItem; lateinit var commentsInfoItem: CommentsInfoItem // the comment to show replies of
private val disposables = CompositeDisposable()
constructor(commentsInfoItem: CommentsInfoItem) : this() {
this.commentsInfoItem = commentsInfoItem
// setting "" as title since the title will be properly set right after
setInitialData(commentsInfoItem.serviceId, commentsInfoItem.url, "")
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return inflater.inflate(R.layout.fragment_comments, container, false)
}
override fun onDestroyView() {
disposables.clear()
super.onDestroyView()
}
override fun getListHeaderSupplier(): Supplier<View> {
return Supplier {
ComposeView(requireContext()).apply {
setContent {
CommentRepliesHeader(commentsInfoItem, disposables)
}
}
}
}
/*//////////////////////////////////////////////////////////////////////////
// State saving
////////////////////////////////////////////////////////////////////////// */
override fun writeTo(objectsToSave: Queue<Any>) {
super.writeTo(objectsToSave)
objectsToSave.add(commentsInfoItem)
}
@Throws(Exception::class)
override fun readFrom(savedObjects: Queue<Any>) {
super.readFrom(savedObjects)
commentsInfoItem = savedObjects.poll() as CommentsInfoItem
}
/*//////////////////////////////////////////////////////////////////////////
// Data loading
////////////////////////////////////////////////////////////////////////// */
override fun loadResult(forceLoad: Boolean): Single<CommentRepliesInfo> {
return Single.fromCallable {
CommentRepliesInfo(
commentsInfoItem, // the reply count string will be shown as the activity title
Localization.replyCount(requireContext(), commentsInfoItem.replyCount)
)
}
}
override fun loadMoreItemsLogic(): Single<InfoItemsPage<CommentsInfoItem?>>? {
// commentsInfoItem.getUrl() should contain the url of the original
// ListInfo<CommentsInfoItem>, which should be the stream url
return ExtractorHelper.getMoreCommentItems(
serviceId, commentsInfoItem.url, currentNextPage
)
}
/*//////////////////////////////////////////////////////////////////////////
// Utils
////////////////////////////////////////////////////////////////////////// */
override fun getItemViewMode(): ItemViewMode {
return ItemViewMode.LIST
}
companion object {
@JvmField
val TAG: String = CommentRepliesFragment::class.java.simpleName
} }
} }

View File

@ -98,8 +98,8 @@ fun CommentRepliesHeader(comment: CommentsInfoItem, disposables: CompositeDispos
}, },
update = { view -> update = { view ->
// setup comment content // setup comment content
TextLinkifier.fromDescription(view, comment.commentText, TextLinkifier.fromDescription(
HtmlCompat.FROM_HTML_MODE_LEGACY, view, comment.commentText, HtmlCompat.FROM_HTML_MODE_LEGACY,
ServiceHelper.getServiceById(comment.serviceId), comment.url, disposables, ServiceHelper.getServiceById(comment.serviceId), comment.url, disposables,
null null
) )

View File

@ -1,137 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/authorAvatar"
android:layout_width="42dp"
android:layout_height="42dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:focusable="false"
android:src="@drawable/placeholder_person"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:shapeAppearance="@style/CircularImageView"
tools:ignore="RtlHardcoded" />
<org.schabi.newpipe.views.NewPipeTextView
android:id="@+id/authorName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:ellipsize="end"
android:lines="1"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textSize="16sp"
app:layout_constraintBottom_toTopOf="@+id/uploadDate"
app:layout_constraintEnd_toStartOf="@+id/thumbsUpImage"
app:layout_constraintStart_toEndOf="@+id/authorAvatar"
app:layout_constraintTop_toTopOf="@+id/authorAvatar"
tools:text="@tools:sample/lorem/random" />
<org.schabi.newpipe.views.NewPipeTextView
android:id="@+id/uploadDate"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:ellipsize="end"
android:lines="1"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textSize="14sp"
app:layout_constraintBottom_toBottomOf="@+id/authorAvatar"
app:layout_constraintEnd_toStartOf="@+id/thumbsUpImage"
app:layout_constraintStart_toEndOf="@+id/authorAvatar"
app:layout_constraintTop_toBottomOf="@+id/authorName"
tools:text="5 months ago" />
<ImageView
android:id="@+id/thumbsUpImage"
android:layout_width="21sp"
android:layout_height="21sp"
android:layout_marginEnd="@dimen/video_item_detail_like_margin"
android:contentDescription="@string/detail_likes_img_view_description"
android:src="@drawable/ic_thumb_up"
app:layout_constraintBottom_toBottomOf="@+id/authorAvatar"
app:layout_constraintEnd_toStartOf="@+id/thumbsUpCount"
app:layout_constraintTop_toTopOf="@+id/authorAvatar" />
<org.schabi.newpipe.views.NewPipeTextView
android:id="@+id/thumbsUpCount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:gravity="center"
android:lines="1"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="14sp"
app:layout_constraintBottom_toBottomOf="@+id/authorAvatar"
app:layout_constraintEnd_toStartOf="@+id/heartImage"
app:layout_constraintTop_toTopOf="@+id/authorAvatar"
tools:text="12M" />
<ImageView
android:id="@+id/heartImage"
android:layout_width="21sp"
android:layout_height="21sp"
android:layout_marginEnd="4dp"
android:contentDescription="@string/detail_heart_img_view_description"
android:src="@drawable/ic_heart"
app:layout_constraintBottom_toBottomOf="@+id/authorAvatar"
app:layout_constraintEnd_toStartOf="@+id/pinnedImage"
app:layout_constraintTop_toTopOf="@+id/authorAvatar"
app:layout_goneMarginEnd="16dp" />
<View
android:id="@+id/authorTouchArea"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_margin="8dp"
android:background="?attr/selectableItemBackground"
app:layout_constraintBottom_toTopOf="@+id/commentContent"
app:layout_constraintEnd_toStartOf="@+id/thumbsUpImage"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/pinnedImage"
android:layout_width="21sp"
android:layout_height="21sp"
android:layout_marginEnd="16dp"
android:contentDescription="@string/detail_pinned_comment_view_description"
android:src="@drawable/ic_pin"
app:layout_constraintBottom_toBottomOf="@+id/authorAvatar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/authorAvatar" />
<org.schabi.newpipe.views.NewPipeTextView
android:id="@+id/commentContent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textSize="14sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/authorAvatar"
tools:text="@tools:sample/lorem/random[10]" />
<View
android:layout_width="match_parent"
android:layout_height="1px"
android:layout_marginTop="16dp"
android:layout_marginBottom="8dp"
android:background="?attr/separator_color"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/commentContent" />
</androidx.constraintlayout.widget.ConstraintLayout>