1
0
mirror of https://github.com/TeamNewPipe/NewPipe synced 2024-07-05 17:18:42 +00:00

Replace CommentRepliesFragment with bottom sheet composable, improve previews

This commit is contained in:
Isira Seneviratne 2024-06-23 08:57:51 +05:30
parent 9e4c123348
commit 9af8756d48
5 changed files with 35 additions and 173 deletions

View File

@ -44,7 +44,6 @@ import android.widget.FrameLayout;
import android.widget.Spinner;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.ActionBarDrawerToggle;
import androidx.appcompat.app.AppCompatActivity;
@ -52,7 +51,6 @@ import androidx.core.app.ActivityCompat;
import androidx.core.view.GravityCompat;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentContainerView;
import androidx.fragment.app.FragmentManager;
import androidx.preference.PreferenceManager;
@ -71,7 +69,6 @@ import org.schabi.newpipe.extractor.services.peertube.PeertubeInstance;
import org.schabi.newpipe.fragments.BackPressable;
import org.schabi.newpipe.fragments.MainFragment;
import org.schabi.newpipe.fragments.detail.VideoDetailFragment;
import org.schabi.newpipe.fragments.list.comments.CommentRepliesFragment;
import org.schabi.newpipe.fragments.list.search.SearchFragment;
import org.schabi.newpipe.local.feed.notifications.NotificationWorker;
import org.schabi.newpipe.player.Player;
@ -557,33 +554,22 @@ public class MainActivity extends AppCompatActivity {
// interacts with a fragment inside fragment_holder so all back presses should be
// handled by it
if (bottomSheetHiddenOrCollapsed()) {
final FragmentManager fm = getSupportFragmentManager();
final Fragment fragment = fm.findFragmentById(R.id.fragment_holder);
final var fm = getSupportFragmentManager();
final var fragment = fm.findFragmentById(R.id.fragment_holder);
// If current fragment implements BackPressable (i.e. can/wanna handle back press)
// delegate the back press to it
if (fragment instanceof BackPressable) {
if (((BackPressable) fragment).onBackPressed()) {
return;
}
} else if (fragment instanceof CommentRepliesFragment) {
// expand DetailsFragment if CommentRepliesFragment was opened
// to show the top level comments again
// Expand DetailsFragment if CommentRepliesFragment was opened
// and no other CommentRepliesFragments are on top of the back stack
// to show the top level comments again.
openDetailFragmentFromCommentReplies(fm, false);
if (fragment instanceof BackPressable backPressable && backPressable.onBackPressed()) {
return;
}
} else {
final Fragment fragmentPlayer = getSupportFragmentManager()
final var fragmentPlayer = getSupportFragmentManager()
.findFragmentById(R.id.fragment_player_holder);
// If current fragment implements BackPressable (i.e. can/wanna handle back press)
// delegate the back press to it
if (fragmentPlayer instanceof BackPressable) {
if (!((BackPressable) fragmentPlayer).onBackPressed()) {
BottomSheetBehavior.from(mainBinding.fragmentPlayerHolder)
.setState(BottomSheetBehavior.STATE_COLLAPSED);
}
if (fragmentPlayer instanceof BackPressable backPressable
&& !backPressable.onBackPressed()) {
BottomSheetBehavior.from(mainBinding.fragmentPlayerHolder)
.setState(BottomSheetBehavior.STATE_COLLAPSED);
return;
}
}
@ -647,15 +633,9 @@ public class MainActivity extends AppCompatActivity {
* </pre>
*/
private void onHomeButtonPressed() {
final FragmentManager fm = getSupportFragmentManager();
final Fragment fragment = fm.findFragmentById(R.id.fragment_holder);
final var fm = getSupportFragmentManager();
if (fragment instanceof CommentRepliesFragment) {
// Expand DetailsFragment if CommentRepliesFragment was opened
// and no other CommentRepliesFragments are on top of the back stack
// to show the top level comments again.
openDetailFragmentFromCommentReplies(fm, true);
} else if (!NavigationHelper.tryGotoSearchFragment(fm)) {
if (!NavigationHelper.tryGotoSearchFragment(fm)) {
// If search fragment wasn't found in the backstack go to the main fragment
NavigationHelper.gotoMainFragment(fm);
}
@ -853,67 +833,6 @@ public class MainActivity extends AppCompatActivity {
}
}
private void openDetailFragmentFromCommentReplies(
@NonNull final FragmentManager fm,
final boolean popBackStack
) {
// obtain the name of the fragment under the replies fragment that's going to be popped
@Nullable final String fragmentUnderEntryName;
if (fm.getBackStackEntryCount() < 2) {
fragmentUnderEntryName = null;
} else {
fragmentUnderEntryName = fm.getBackStackEntryAt(fm.getBackStackEntryCount() - 2)
.getName();
}
// the root comment is the comment for which the user opened the replies page
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) {
fm.popBackStackImmediate();
}
// only expand the bottom sheet back if there are no more nested comment replies fragments
// stacked under the one that is currently being popped
if (CommentRepliesFragment.TAG.equals(fragmentUnderEntryName)) {
return;
}
final BottomSheetBehavior<FragmentContainerView> behavior = BottomSheetBehavior
.from(mainBinding.fragmentPlayerHolder);
// do not return to the comment if the details fragment was closed
if (behavior.getState() == BottomSheetBehavior.STATE_HIDDEN) {
return;
}
// scroll to the root comment once the bottom sheet expansion animation is finished
behavior.addBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
@Override
public void onStateChanged(@NonNull final View bottomSheet,
final int newState) {
if (newState == BottomSheetBehavior.STATE_EXPANDED) {
final Fragment detailFragment = fm.findFragmentById(
R.id.fragment_player_holder);
if (detailFragment instanceof VideoDetailFragment && rootComment != null) {
// should always be the case
((VideoDetailFragment) detailFragment).scrollToComment(rootComment);
}
behavior.removeBottomSheetCallback(this);
}
}
@Override
public void onSlide(@NonNull final View bottomSheet, final float slideOffset) {
// not needed, listener is removed once the sheet is expanded
}
});
behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
}
private boolean bottomSheetHiddenOrCollapsed() {
final BottomSheetBehavior<FrameLayout> bottomSheetBehavior =
BottomSheetBehavior.from(mainBinding.fragmentPlayerHolder);

View File

@ -12,7 +12,9 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
@ -38,6 +40,8 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.dp
import androidx.fragment.app.FragmentActivity
import androidx.paging.Pager
import androidx.paging.PagingConfig
import coil.compose.AsyncImage
import org.schabi.newpipe.R
import org.schabi.newpipe.extractor.Page
@ -60,10 +64,12 @@ fun rememberParsedText(commentText: Description): AnnotatedString {
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun Comment(comment: CommentsInfoItem) {
val context = LocalContext.current
var isExpanded by rememberSaveable { mutableStateOf(false) }
var showReplies by rememberSaveable { mutableStateOf(false) }
Surface(color = MaterialTheme.colorScheme.background) {
Row(
@ -139,22 +145,29 @@ fun Comment(comment: CommentsInfoItem) {
}
if (comment.replies != null) {
TextButton(onClick = {
NavigationHelper.openCommentRepliesFragment(
context as FragmentActivity, comment
)
}) {
Text(
text = pluralStringResource(
R.plurals.replies, comment.replyCount, comment.replyCount.toString()
)
TextButton(onClick = { showReplies = true }) {
val text = pluralStringResource(
R.plurals.replies, comment.replyCount, comment.replyCount.toString()
)
Text(text = text)
}
}
}
}
}
}
if (showReplies) {
ModalBottomSheet(onDismissRequest = { showReplies = false }) {
val flow = remember(comment) {
Pager(PagingConfig(pageSize = 20, enablePlaceholders = false)) {
CommentsSource(comment.serviceId, comment.url, comment.replies)
}.flow
}
CommentSection(parentComment = comment, flow = flow)
}
}
}
fun CommentsInfoItem(

View File

@ -1,56 +0,0 @@
package org.schabi.newpipe.fragments.list.comments
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.fragment.app.Fragment
import androidx.paging.Pager
import androidx.paging.PagingConfig
import org.schabi.newpipe.extractor.comments.CommentsInfoItem
import org.schabi.newpipe.ktx.serializable
import org.schabi.newpipe.ui.theme.AppTheme
import org.schabi.newpipe.util.Localization
class CommentRepliesFragment : Fragment() {
lateinit var comment: CommentsInfoItem
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
comment = requireArguments().serializable<CommentsInfoItem>(COMMENT_KEY)!!
val activity = requireActivity() as AppCompatActivity
val bar = activity.supportActionBar!!
bar.setDisplayShowTitleEnabled(true)
bar.title = Localization.replyCount(activity, comment.replyCount)
return ComposeView(activity).apply {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
setContent {
val flow = remember(comment) {
Pager(PagingConfig(pageSize = 20, enablePlaceholders = false)) {
CommentsSource(comment.serviceId, comment.url, comment.replies)
}.flow
}
AppTheme {
CommentSection(parentComment = comment, flow = flow)
}
}
}
}
companion object {
@JvmField
val TAG = CommentRepliesFragment::class.simpleName!!
const val COMMENT_KEY = "comment"
}
}

View File

@ -45,7 +45,7 @@ fun CommentSection(
@Preview(name = "Dark mode", uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun CommentSectionPreview() {
val comments = (0..100).map {
val comments = (1..100).map {
CommentsInfoItem(
commentText = Description("Comment $it", Description.PLAIN_TEXT),
uploaderName = "Test"
@ -69,7 +69,7 @@ private fun CommentRepliesPreview() {
isPinned = true,
isHeartedByUploader = true
)
val replies = (0..100).map {
val replies = (1..100).map {
CommentsInfoItem(
commentText = Description("Reply $it", Description.PLAIN_TEXT),
uploaderName = "Test"

View File

@ -9,7 +9,6 @@ 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;
@ -46,7 +45,6 @@ import org.schabi.newpipe.extractor.stream.VideoStream;
import org.schabi.newpipe.fragments.MainFragment;
import org.schabi.newpipe.fragments.detail.VideoDetailFragment;
import org.schabi.newpipe.fragments.list.channel.ChannelFragment;
import org.schabi.newpipe.fragments.list.comments.CommentRepliesFragment;
import org.schabi.newpipe.fragments.list.kiosk.KioskFragment;
import org.schabi.newpipe.fragments.list.playlist.PlaylistFragment;
import org.schabi.newpipe.fragments.list.search.SearchFragment;
@ -502,18 +500,6 @@ 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, CommentRepliesFragment.class, bundle,
CommentRepliesFragment.TAG)
.addToBackStack(CommentRepliesFragment.TAG)
.commit();
}
public static void openPlaylistFragment(final FragmentManager fragmentManager,
final int serviceId, final String url,
@NonNull final String name) {