mirror of
https://github.com/TeamNewPipe/NewPipe
synced 2024-12-23 16:40:32 +00:00
Update replies fragment to use the comment composable as well
This commit is contained in:
parent
1620668966
commit
341cc37ce7
@ -106,7 +106,7 @@ android {
|
||||
}
|
||||
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion = "1.5.13"
|
||||
kotlinCompilerExtensionVersion = "1.5.14"
|
||||
}
|
||||
}
|
||||
|
||||
@ -289,11 +289,15 @@ dependencies {
|
||||
implementation "org.ocpsoft.prettytime:prettytime:5.0.8.Final"
|
||||
|
||||
// Jetpack Compose
|
||||
implementation(platform('androidx.compose:compose-bom:2024.05.00'))
|
||||
implementation(platform('androidx.compose:compose-bom:2024.06.00'))
|
||||
implementation 'androidx.compose.material3:material3'
|
||||
implementation 'androidx.activity:activity-compose'
|
||||
implementation 'androidx.compose.ui:ui-tooling-preview'
|
||||
|
||||
// Paging
|
||||
implementation 'androidx.paging:paging-rxjava3:3.3.0'
|
||||
implementation 'androidx.paging:paging-compose:3.3.0'
|
||||
|
||||
/** Debugging **/
|
||||
// Memory leak detection
|
||||
debugImplementation "com.squareup.leakcanary:leakcanary-object-watcher-android:${leakCanaryVersion}"
|
||||
|
@ -66,7 +66,6 @@ import org.schabi.newpipe.databinding.ToolbarLayoutBinding;
|
||||
import org.schabi.newpipe.error.ErrorUtil;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.services.peertube.PeertubeInstance;
|
||||
import org.schabi.newpipe.fragments.BackPressable;
|
||||
@ -868,10 +867,9 @@ public class MainActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
// the root comment is the comment for which the user opened the replies page
|
||||
@Nullable final CommentRepliesFragment repliesFragment =
|
||||
(CommentRepliesFragment) fm.findFragmentByTag(CommentRepliesFragment.TAG);
|
||||
@Nullable final CommentsInfoItem rootComment =
|
||||
repliesFragment == null ? null : repliesFragment.getCommentsInfoItem();
|
||||
final var repliesFragment = (CommentRepliesFragment)
|
||||
fm.findFragmentByTag(CommentRepliesFragment.TAG);
|
||||
final var rootComment = repliesFragment == null ? null : repliesFragment.getComment();
|
||||
|
||||
// sometimes this function pops the backstack, other times it's handled by the system
|
||||
if (popBackStack) {
|
||||
|
@ -73,6 +73,7 @@ fun Comment(comment: CommentsInfoItem) {
|
||||
color = MaterialTheme.colorScheme.secondary
|
||||
)
|
||||
|
||||
// TODO: Handle HTML and Markdown formats.
|
||||
Text(
|
||||
text = comment.commentText.content,
|
||||
// If the comment is expanded, we display all its content
|
||||
@ -110,16 +111,33 @@ fun Comment(comment: CommentsInfoItem) {
|
||||
}
|
||||
}
|
||||
|
||||
fun CommentsInfoItem(
|
||||
serviceId: Int = 1,
|
||||
url: String = "",
|
||||
name: String = "",
|
||||
commentText: Description,
|
||||
uploaderName: String,
|
||||
textualUploadDate: String = "5 months ago",
|
||||
likeCount: Int = 100,
|
||||
isHeartedByUploader: Boolean = true,
|
||||
isPinned: Boolean = true,
|
||||
) = CommentsInfoItem(serviceId, url, name).apply {
|
||||
this.commentText = commentText
|
||||
this.uploaderName = uploaderName
|
||||
this.textualUploadDate = textualUploadDate
|
||||
this.likeCount = likeCount
|
||||
this.isHeartedByUploader = isHeartedByUploader
|
||||
this.isPinned = isPinned
|
||||
}
|
||||
|
||||
@Preview(name = "Light mode", uiMode = Configuration.UI_MODE_NIGHT_NO)
|
||||
@Preview(name = "Dark mode", uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
fun CommentPreview() {
|
||||
val comment = CommentsInfoItem(1, "", "")
|
||||
comment.commentText = Description("Hello world!\n\nThis line should be hidden by default.", Description.PLAIN_TEXT)
|
||||
comment.uploaderName = "Test"
|
||||
comment.likeCount = 100
|
||||
comment.isHeartedByUploader = true
|
||||
comment.isPinned = true
|
||||
private fun CommentPreview() {
|
||||
val comment = CommentsInfoItem(
|
||||
commentText = Description("Hello world!\n\nThis line should be hidden by default.", Description.PLAIN_TEXT),
|
||||
uploaderName = "Test",
|
||||
)
|
||||
|
||||
AppTheme {
|
||||
Comment(comment)
|
||||
|
@ -0,0 +1,60 @@
|
||||
package org.schabi.newpipe.fragments.list.comments
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.paging.PagingData
|
||||
import androidx.paging.compose.collectAsLazyPagingItems
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import org.schabi.newpipe.extractor.comments.CommentsInfoItem
|
||||
import org.schabi.newpipe.extractor.stream.Description
|
||||
import org.schabi.newpipe.ui.theme.AppTheme
|
||||
|
||||
@Composable
|
||||
fun CommentReplies(
|
||||
comment: CommentsInfoItem,
|
||||
flow: Flow<PagingData<CommentsInfoItem>>,
|
||||
disposables: CompositeDisposable
|
||||
) {
|
||||
val replies = flow.collectAsLazyPagingItems()
|
||||
|
||||
Column {
|
||||
CommentRepliesHeader(comment = comment, disposables = disposables)
|
||||
HorizontalDivider(thickness = 1.dp)
|
||||
LazyColumn {
|
||||
items(replies.itemCount) {
|
||||
Comment(comment = replies[it]!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(name = "Light mode", uiMode = Configuration.UI_MODE_NIGHT_NO)
|
||||
@Preview(name = "Dark mode", uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun CommentRepliesPreview() {
|
||||
val comment = CommentsInfoItem(
|
||||
commentText = Description("Hello world!", Description.PLAIN_TEXT),
|
||||
uploaderName = "Test",
|
||||
)
|
||||
|
||||
val reply1 = CommentsInfoItem(
|
||||
commentText = Description("This is a reply", Description.PLAIN_TEXT),
|
||||
uploaderName = "Test 2",
|
||||
)
|
||||
val reply2 = CommentsInfoItem(
|
||||
commentText = Description("This is another reply.<br>This is another line.", Description.HTML),
|
||||
uploaderName = "Test 3",
|
||||
)
|
||||
val flow = flowOf(PagingData.from(listOf(reply1, reply2)))
|
||||
|
||||
AppTheme {
|
||||
CommentReplies(comment = comment, flow = flow, disposables = CompositeDisposable())
|
||||
}
|
||||
}
|
@ -4,104 +4,50 @@ import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import icepick.State
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.paging.Pager
|
||||
import androidx.paging.PagingConfig
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||
import org.schabi.newpipe.R
|
||||
import org.schabi.newpipe.error.UserAction
|
||||
import org.schabi.newpipe.extractor.ListExtractor.InfoItemsPage
|
||||
import org.schabi.newpipe.extractor.comments.CommentsInfoItem
|
||||
import org.schabi.newpipe.fragments.list.BaseListInfoFragment
|
||||
import org.schabi.newpipe.info_list.ItemViewMode
|
||||
import org.schabi.newpipe.ktx.serializable
|
||||
import org.schabi.newpipe.ui.theme.AppTheme
|
||||
import org.schabi.newpipe.util.ExtractorHelper
|
||||
import org.schabi.newpipe.util.Localization
|
||||
import java.util.Queue
|
||||
import java.util.function.Supplier
|
||||
|
||||
class CommentRepliesFragment() : BaseListInfoFragment<CommentsInfoItem, CommentRepliesInfo>(UserAction.REQUESTED_COMMENT_REPLIES) {
|
||||
/**
|
||||
* @return the comment to which the replies are shown
|
||||
*/
|
||||
@State
|
||||
lateinit var commentsInfoItem: CommentsInfoItem // the comment to show replies of
|
||||
class CommentRepliesFragment : Fragment() {
|
||||
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, "")
|
||||
}
|
||||
lateinit var comment: CommentsInfoItem
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
return inflater.inflate(R.layout.fragment_comments, container, false)
|
||||
}
|
||||
comment = requireArguments().serializable<CommentsInfoItem>(COMMENT_KEY)!!
|
||||
return ComposeView(requireContext()).apply {
|
||||
setContent {
|
||||
val flow = remember(comment) {
|
||||
Pager(PagingConfig(pageSize = 20, enablePlaceholders = false)) {
|
||||
CommentRepliesSource(comment)
|
||||
}.flow
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
disposables.clear()
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
override fun getListHeaderSupplier(): Supplier<View> {
|
||||
return Supplier {
|
||||
ComposeView(requireContext()).apply {
|
||||
setContent {
|
||||
AppTheme {
|
||||
CommentRepliesHeader(commentsInfoItem, disposables)
|
||||
}
|
||||
AppTheme {
|
||||
CommentReplies(comment = comment, flow = flow, disposables = 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
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
disposables.clear()
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmField
|
||||
val TAG: String = CommentRepliesFragment::class.java.simpleName
|
||||
val TAG = CommentRepliesFragment::class.simpleName!!
|
||||
|
||||
const val COMMENT_KEY = "comment"
|
||||
}
|
||||
}
|
||||
|
@ -136,14 +136,8 @@ fun CommentRepliesHeader(comment: CommentsInfoItem, disposables: CompositeDispos
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(
|
||||
name = "Light mode",
|
||||
uiMode = Configuration.UI_MODE_NIGHT_NO
|
||||
)
|
||||
@Preview(
|
||||
name = "Dark mode",
|
||||
uiMode = Configuration.UI_MODE_NIGHT_YES
|
||||
)
|
||||
@Preview(name = "Light mode", uiMode = Configuration.UI_MODE_NIGHT_NO)
|
||||
@Preview(name = "Dark mode", uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
fun CommentRepliesHeaderPreview() {
|
||||
val disposables = CompositeDisposable()
|
||||
|
@ -1,22 +0,0 @@
|
||||
package org.schabi.newpipe.fragments.list.comments;
|
||||
|
||||
import org.schabi.newpipe.extractor.ListInfo;
|
||||
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
|
||||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
public final class CommentRepliesInfo extends ListInfo<CommentsInfoItem> {
|
||||
/**
|
||||
* This class is used to wrap the comment replies page into a ListInfo object.
|
||||
*
|
||||
* @param comment the comment from which to get replies
|
||||
* @param name will be shown as the fragment title
|
||||
*/
|
||||
public CommentRepliesInfo(final CommentsInfoItem comment, final String name) {
|
||||
super(comment.getServiceId(),
|
||||
new ListLinkHandler("", "", "", Collections.emptyList(), null), name);
|
||||
setNextPage(comment.getReplies());
|
||||
setRelatedItems(Collections.emptyList()); // since it must be non-null
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package org.schabi.newpipe.fragments.list.comments
|
||||
|
||||
import androidx.paging.PagingState
|
||||
import androidx.paging.rxjava3.RxPagingSource
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import org.schabi.newpipe.extractor.Page
|
||||
import org.schabi.newpipe.extractor.comments.CommentsInfoItem
|
||||
import org.schabi.newpipe.util.ExtractorHelper
|
||||
|
||||
class CommentRepliesSource(
|
||||
private val commentsInfoItem: CommentsInfoItem,
|
||||
) : RxPagingSource<Page, CommentsInfoItem>() {
|
||||
override fun loadSingle(params: LoadParams<Page>): Single<LoadResult<Page, CommentsInfoItem>> {
|
||||
val nextPage = params.key ?: commentsInfoItem.replies
|
||||
return ExtractorHelper.getMoreCommentItems(commentsInfoItem.serviceId, commentsInfoItem.url, nextPage)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.map { LoadResult.Page(it.items, null, it.nextPage) }
|
||||
}
|
||||
|
||||
override fun getRefreshKey(state: PagingState<Page, CommentsInfoItem>) = null
|
||||
}
|
@ -1,9 +1,25 @@
|
||||
package org.schabi.newpipe.ktx
|
||||
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import androidx.core.os.BundleCompat
|
||||
import java.io.Serializable
|
||||
import kotlin.reflect.safeCast
|
||||
|
||||
inline fun <reified T : Parcelable> Bundle.parcelableArrayList(key: String?): ArrayList<T>? {
|
||||
return BundleCompat.getParcelableArrayList(this, key, T::class.java)
|
||||
}
|
||||
|
||||
inline fun <reified T : Serializable> Bundle.serializable(key: String?): T? {
|
||||
return getSerializable(this, key, T::class.java)
|
||||
}
|
||||
|
||||
fun <T : Serializable> getSerializable(bundle: Bundle, key: String?, clazz: Class<T>): T? {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
bundle.getSerializable(key, clazz)
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
clazz.kotlin.safeCast(bundle.getSerializable(key))
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
@ -503,8 +504,11 @@ public final class NavigationHelper {
|
||||
|
||||
public static void openCommentRepliesFragment(@NonNull final FragmentActivity activity,
|
||||
@NonNull final CommentsInfoItem comment) {
|
||||
final var bundle = new Bundle();
|
||||
bundle.putSerializable(CommentRepliesFragment.COMMENT_KEY, comment);
|
||||
|
||||
defaultTransaction(activity.getSupportFragmentManager())
|
||||
.replace(R.id.fragment_holder, new CommentRepliesFragment(comment),
|
||||
.replace(R.id.fragment_holder, CommentRepliesFragment.class, bundle,
|
||||
CommentRepliesFragment.TAG)
|
||||
.addToBackStack(CommentRepliesFragment.TAG)
|
||||
.commit();
|
||||
|
@ -1,7 +1,7 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.9.23'
|
||||
ext.kotlin_version = '1.9.24'
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
|
Loading…
Reference in New Issue
Block a user