mirror of
				https://github.com/TeamNewPipe/NewPipe
				synced 2025-10-30 14:52:59 +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 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); | ||||
|   | ||||
| @@ -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( | ||||
|   | ||||
| @@ -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) | ||||
| @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" | ||||
|   | ||||
| @@ -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) { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Isira Seneviratne
					Isira Seneviratne