mirror of
				https://github.com/TeamNewPipe/NewPipe
				synced 2025-10-30 23:03:00 +00:00 
			
		
		
		
	Replace CommentRepliesFragment with bottom sheet composable, improve previews
This commit is contained in:
		| @@ -44,7 +44,6 @@ import android.widget.FrameLayout; | |||||||
| import android.widget.Spinner; | import android.widget.Spinner; | ||||||
|  |  | ||||||
| import androidx.annotation.NonNull; | import androidx.annotation.NonNull; | ||||||
| import androidx.annotation.Nullable; |  | ||||||
| import androidx.appcompat.app.ActionBar; | import androidx.appcompat.app.ActionBar; | ||||||
| import androidx.appcompat.app.ActionBarDrawerToggle; | import androidx.appcompat.app.ActionBarDrawerToggle; | ||||||
| import androidx.appcompat.app.AppCompatActivity; | import androidx.appcompat.app.AppCompatActivity; | ||||||
| @@ -52,7 +51,6 @@ import androidx.core.app.ActivityCompat; | |||||||
| import androidx.core.view.GravityCompat; | import androidx.core.view.GravityCompat; | ||||||
| import androidx.drawerlayout.widget.DrawerLayout; | import androidx.drawerlayout.widget.DrawerLayout; | ||||||
| import androidx.fragment.app.Fragment; | import androidx.fragment.app.Fragment; | ||||||
| import androidx.fragment.app.FragmentContainerView; |  | ||||||
| import androidx.fragment.app.FragmentManager; | import androidx.fragment.app.FragmentManager; | ||||||
| import androidx.preference.PreferenceManager; | 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.BackPressable; | ||||||
| import org.schabi.newpipe.fragments.MainFragment; | import org.schabi.newpipe.fragments.MainFragment; | ||||||
| import org.schabi.newpipe.fragments.detail.VideoDetailFragment; | 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.fragments.list.search.SearchFragment; | ||||||
| import org.schabi.newpipe.local.feed.notifications.NotificationWorker; | import org.schabi.newpipe.local.feed.notifications.NotificationWorker; | ||||||
| import org.schabi.newpipe.player.Player; | 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 |         // interacts with a fragment inside fragment_holder so all back presses should be | ||||||
|         // handled by it |         // handled by it | ||||||
|         if (bottomSheetHiddenOrCollapsed()) { |         if (bottomSheetHiddenOrCollapsed()) { | ||||||
|             final FragmentManager fm = getSupportFragmentManager(); |             final var fm = getSupportFragmentManager(); | ||||||
|             final Fragment fragment = fm.findFragmentById(R.id.fragment_holder); |             final var fragment = fm.findFragmentById(R.id.fragment_holder); | ||||||
|             // If current fragment implements BackPressable (i.e. can/wanna handle back press) |             // If current fragment implements BackPressable (i.e. can/wanna handle back press) | ||||||
|             // delegate the back press to it |             // delegate the back press to it | ||||||
|             if (fragment instanceof BackPressable) { |             if (fragment instanceof BackPressable backPressable && backPressable.onBackPressed()) { | ||||||
|                 if (((BackPressable) fragment).onBackPressed()) { |  | ||||||
|                 return; |                 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); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|         } else { |         } else { | ||||||
|             final Fragment fragmentPlayer = getSupportFragmentManager() |             final var fragmentPlayer = getSupportFragmentManager() | ||||||
|                     .findFragmentById(R.id.fragment_player_holder); |                     .findFragmentById(R.id.fragment_player_holder); | ||||||
|             // If current fragment implements BackPressable (i.e. can/wanna handle back press) |             // If current fragment implements BackPressable (i.e. can/wanna handle back press) | ||||||
|             // delegate the back press to it |             // delegate the back press to it | ||||||
|             if (fragmentPlayer instanceof BackPressable) { |             if (fragmentPlayer instanceof BackPressable backPressable | ||||||
|                 if (!((BackPressable) fragmentPlayer).onBackPressed()) { |                     && !backPressable.onBackPressed()) { | ||||||
|                 BottomSheetBehavior.from(mainBinding.fragmentPlayerHolder) |                 BottomSheetBehavior.from(mainBinding.fragmentPlayerHolder) | ||||||
|                         .setState(BottomSheetBehavior.STATE_COLLAPSED); |                         .setState(BottomSheetBehavior.STATE_COLLAPSED); | ||||||
|                 } |  | ||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| @@ -647,15 +633,9 @@ public class MainActivity extends AppCompatActivity { | |||||||
|      * </pre> |      * </pre> | ||||||
|      */ |      */ | ||||||
|     private void onHomeButtonPressed() { |     private void onHomeButtonPressed() { | ||||||
|         final FragmentManager fm = getSupportFragmentManager(); |         final var fm = getSupportFragmentManager(); | ||||||
|         final Fragment fragment = fm.findFragmentById(R.id.fragment_holder); |  | ||||||
|  |  | ||||||
|         if (fragment instanceof CommentRepliesFragment) { |         if (!NavigationHelper.tryGotoSearchFragment(fm)) { | ||||||
|             // 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 search fragment wasn't found in the backstack go to the main fragment |             // If search fragment wasn't found in the backstack go to the main fragment | ||||||
|             NavigationHelper.gotoMainFragment(fm); |             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() { |     private boolean bottomSheetHiddenOrCollapsed() { | ||||||
|         final BottomSheetBehavior<FrameLayout> bottomSheetBehavior = |         final BottomSheetBehavior<FrameLayout> bottomSheetBehavior = | ||||||
|                 BottomSheetBehavior.from(mainBinding.fragmentPlayerHolder); |                 BottomSheetBehavior.from(mainBinding.fragmentPlayerHolder); | ||||||
|   | |||||||
| @@ -12,7 +12,9 @@ import androidx.compose.foundation.layout.padding | |||||||
| import androidx.compose.foundation.layout.size | import androidx.compose.foundation.layout.size | ||||||
| import androidx.compose.foundation.layout.width | import androidx.compose.foundation.layout.width | ||||||
| import androidx.compose.foundation.shape.CircleShape | import androidx.compose.foundation.shape.CircleShape | ||||||
|  | import androidx.compose.material3.ExperimentalMaterial3Api | ||||||
| import androidx.compose.material3.MaterialTheme | import androidx.compose.material3.MaterialTheme | ||||||
|  | import androidx.compose.material3.ModalBottomSheet | ||||||
| import androidx.compose.material3.Surface | import androidx.compose.material3.Surface | ||||||
| import androidx.compose.material3.Text | import androidx.compose.material3.Text | ||||||
| import androidx.compose.material3.TextButton | 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.tooling.preview.PreviewParameterProvider | ||||||
| import androidx.compose.ui.unit.dp | import androidx.compose.ui.unit.dp | ||||||
| import androidx.fragment.app.FragmentActivity | import androidx.fragment.app.FragmentActivity | ||||||
|  | import androidx.paging.Pager | ||||||
|  | import androidx.paging.PagingConfig | ||||||
| import coil.compose.AsyncImage | import coil.compose.AsyncImage | ||||||
| import org.schabi.newpipe.R | import org.schabi.newpipe.R | ||||||
| import org.schabi.newpipe.extractor.Page | import org.schabi.newpipe.extractor.Page | ||||||
| @@ -60,10 +64,12 @@ fun rememberParsedText(commentText: Description): AnnotatedString { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @OptIn(ExperimentalMaterial3Api::class) | ||||||
| @Composable | @Composable | ||||||
| fun Comment(comment: CommentsInfoItem) { | fun Comment(comment: CommentsInfoItem) { | ||||||
|     val context = LocalContext.current |     val context = LocalContext.current | ||||||
|     var isExpanded by rememberSaveable { mutableStateOf(false) } |     var isExpanded by rememberSaveable { mutableStateOf(false) } | ||||||
|  |     var showReplies by rememberSaveable { mutableStateOf(false) } | ||||||
|  |  | ||||||
|     Surface(color = MaterialTheme.colorScheme.background) { |     Surface(color = MaterialTheme.colorScheme.background) { | ||||||
|         Row( |         Row( | ||||||
| @@ -139,22 +145,29 @@ fun Comment(comment: CommentsInfoItem) { | |||||||
|                     } |                     } | ||||||
|  |  | ||||||
|                     if (comment.replies != null) { |                     if (comment.replies != null) { | ||||||
|                         TextButton(onClick = { |                         TextButton(onClick = { showReplies = true }) { | ||||||
|                             NavigationHelper.openCommentRepliesFragment( |                             val text = pluralStringResource( | ||||||
|                                 context as FragmentActivity, comment |  | ||||||
|                             ) |  | ||||||
|                         }) { |  | ||||||
|                             Text( |  | ||||||
|                                 text = pluralStringResource( |  | ||||||
|                                 R.plurals.replies, comment.replyCount, comment.replyCount.toString() |                                 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( | fun CommentsInfoItem( | ||||||
|   | |||||||
| @@ -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" |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -45,7 +45,7 @@ fun CommentSection( | |||||||
| @Preview(name = "Dark mode", uiMode = Configuration.UI_MODE_NIGHT_YES) | @Preview(name = "Dark mode", uiMode = Configuration.UI_MODE_NIGHT_YES) | ||||||
| @Composable | @Composable | ||||||
| private fun CommentSectionPreview() { | private fun CommentSectionPreview() { | ||||||
|     val comments = (0..100).map { |     val comments = (1..100).map { | ||||||
|         CommentsInfoItem( |         CommentsInfoItem( | ||||||
|             commentText = Description("Comment $it", Description.PLAIN_TEXT), |             commentText = Description("Comment $it", Description.PLAIN_TEXT), | ||||||
|             uploaderName = "Test" |             uploaderName = "Test" | ||||||
| @@ -69,7 +69,7 @@ private fun CommentRepliesPreview() { | |||||||
|         isPinned = true, |         isPinned = true, | ||||||
|         isHeartedByUploader = true |         isHeartedByUploader = true | ||||||
|     ) |     ) | ||||||
|     val replies = (0..100).map { |     val replies = (1..100).map { | ||||||
|         CommentsInfoItem( |         CommentsInfoItem( | ||||||
|             commentText = Description("Reply $it", Description.PLAIN_TEXT), |             commentText = Description("Reply $it", Description.PLAIN_TEXT), | ||||||
|             uploaderName = "Test" |             uploaderName = "Test" | ||||||
|   | |||||||
| @@ -9,7 +9,6 @@ import android.content.Context; | |||||||
| import android.content.Intent; | import android.content.Intent; | ||||||
| import android.net.Uri; | import android.net.Uri; | ||||||
| import android.os.Build; | import android.os.Build; | ||||||
| import android.os.Bundle; |  | ||||||
| import android.util.Log; | import android.util.Log; | ||||||
| import android.widget.Toast; | 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.MainFragment; | ||||||
| import org.schabi.newpipe.fragments.detail.VideoDetailFragment; | import org.schabi.newpipe.fragments.detail.VideoDetailFragment; | ||||||
| import org.schabi.newpipe.fragments.list.channel.ChannelFragment; | 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.kiosk.KioskFragment; | ||||||
| import org.schabi.newpipe.fragments.list.playlist.PlaylistFragment; | import org.schabi.newpipe.fragments.list.playlist.PlaylistFragment; | ||||||
| import org.schabi.newpipe.fragments.list.search.SearchFragment; | 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, |     public static void openPlaylistFragment(final FragmentManager fragmentManager, | ||||||
|                                             final int serviceId, final String url, |                                             final int serviceId, final String url, | ||||||
|                                             @NonNull final String name) { |                                             @NonNull final String name) { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Isira Seneviratne
					Isira Seneviratne