From 09b507c2c0d87e944cfec24bfea4fd81d4e8ef41 Mon Sep 17 00:00:00 2001 From: Dessalines Date: Wed, 20 Nov 2024 10:16:41 -0500 Subject: [PATCH 1/4] Getting multi-pane home working. --- app/build.gradle.kts | 17 +- app/src/main/java/com/jerboa/MainActivity.kt | 1269 +++++++++-------- app/src/main/java/com/jerboa/Utils.kt | 8 + .../java/com/jerboa/model/PostViewModel.kt | 7 +- .../ui/components/common/AccountHelpers.kt | 1 + .../ui/components/common/DrawerItems.kt | 4 + .../components/community/CommunityScreen.kt | 2 + .../ui/components/home/BottomNavScreen.kt | 11 +- .../home/HomeAndPostDetailScreen.kt | 153 ++ .../home/{HomeActivity.kt => HomePane.kt} | 17 +- .../components/person/PersonProfileScreen.kt | 2 + .../ui/components/post/PostListingCard.kt | 3 +- .../jerboa/ui/components/post/PostListings.kt | 113 +- .../post/{PostScreen.kt => PostPane.kt} | 16 +- .../com/jerboa/util/BlurTransformation.kt | 2 +- 15 files changed, 916 insertions(+), 709 deletions(-) create mode 100644 app/src/main/java/com/jerboa/ui/components/home/HomeAndPostDetailScreen.kt rename app/src/main/java/com/jerboa/ui/components/home/{HomeActivity.kt => HomePane.kt} (97%) rename app/src/main/java/com/jerboa/ui/components/post/{PostScreen.kt => PostPane.kt} (98%) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a0727a616..aa88e6a74 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -16,14 +16,13 @@ plugins { apply(from = "update_instances.gradle.kts") android { - buildToolsVersion = "34.0.0" - compileSdk = 34 + compileSdk = 35 defaultConfig { applicationId = "com.jerboa" namespace = "com.jerboa" minSdk = 26 - targetSdk = 34 + targetSdk = 35 versionCode = 77 versionName = "0.0.77" @@ -97,10 +96,6 @@ android { } } -composeCompiler { - featureFlags = setOf(ComposeFeatureFlag.StrongSkipping) -} - baselineProfile { mergeIntoMain = true saveInSrc = true @@ -116,6 +111,7 @@ dependencies { // https://github.com/LemmyNet/jerboa/pull/1502#issuecomment-2137935525 // val composeBom = platform("androidx.compose:compose-bom:2024.05.00") + // Compose BOM val composeBom = platform("dev.chrisbanes.compose:compose-bom:2024.08.00-alpha02") api(composeBom) implementation("androidx.activity:activity-ktx") @@ -124,6 +120,13 @@ dependencies { androidTestApi(composeBom) testImplementation("androidx.arch.core:core-testing:2.2.0") + // Adaptive layouts + // TODO still need the alphas for pane features + implementation("androidx.compose.material3.adaptive:adaptive:1.1.0-alpha06") + implementation("androidx.compose.material3.adaptive:adaptive-layout:1.1.0-alpha06") + implementation("androidx.compose.material3.adaptive:adaptive-navigation:1.1.0-alpha06") + implementation("androidx.compose.material3:material3-adaptive-navigation-suite") + implementation("me.zhanghai.compose.preference:library:1.1.1") // Markdown support diff --git a/app/src/main/java/com/jerboa/MainActivity.kt b/app/src/main/java/com/jerboa/MainActivity.kt index bc68ce1bf..a82405d7d 100644 --- a/app/src/main/java/com/jerboa/MainActivity.kt +++ b/app/src/main/java/com/jerboa/MainActivity.kt @@ -15,13 +15,16 @@ import androidx.compose.animation.ExitTransition import androidx.compose.animation.slideInHorizontally import androidx.compose.animation.slideOutHorizontally import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.DrawerValue +import androidx.compose.material3.Surface import androidx.compose.material3.rememberDrawerState import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable @@ -61,7 +64,7 @@ import com.jerboa.ui.components.imageviewer.ImageViewerScreen import com.jerboa.ui.components.inbox.InboxScreen import com.jerboa.ui.components.login.LoginScreen import com.jerboa.ui.components.person.PersonProfileScreen -import com.jerboa.ui.components.post.PostScreen +import com.jerboa.ui.components.post.PostPane import com.jerboa.ui.components.post.create.CreatePostScreen import com.jerboa.ui.components.post.edit.PostEditScreen import com.jerboa.ui.components.privatemessage.CreatePrivateMessageScreen @@ -115,708 +118,710 @@ class MainActivity : AppCompatActivity() { JerboaTheme( appSettings = appSettings, ) { - val appState = rememberJerboaAppState() + Surface(modifier = Modifier.fillMaxSize()) { + val appState = rememberJerboaAppState() - val showConfirmationDialog = remember { mutableStateOf(false) } + val showConfirmationDialog = remember { mutableStateOf(false) } - if (showConfirmationDialog.value) { - ShowConfirmationDialog({ showConfirmationDialog.value = false }, ::finish) - } + if (showConfirmationDialog.value) { + ShowConfirmationDialog({ showConfirmationDialog.value = false }, ::finish) + } - DisposableEffect(appSettings.backConfirmationMode) { - when (appSettings.backConfirmationMode.toEnum()) { - BackConfirmationMode.Toast -> { - this@MainActivity.addConfirmationToast(appState.navController, ctx) - } - BackConfirmationMode.Dialog -> { - this@MainActivity.addConfirmationDialog(appState.navController) { showConfirmationDialog.value = true } + DisposableEffect(appSettings.backConfirmationMode) { + when (appSettings.backConfirmationMode.toEnum()) { + BackConfirmationMode.Toast -> { + this@MainActivity.addConfirmationToast(appState.navController, ctx) + } + BackConfirmationMode.Dialog -> { + this@MainActivity.addConfirmationDialog(appState.navController) { showConfirmationDialog.value = true } + } + BackConfirmationMode.None -> {} } - BackConfirmationMode.None -> {} - } - onDispose { - disposeConfirmation() + onDispose { + disposeConfirmation() + } } - } - MarkdownHelper.init( - appState, - appSettings.useCustomTabs, - appSettings.usePrivateTabs, - object : BetterLinkMovementMethod.OnLinkLongClickListener { - override fun onLongClick( - textView: TextView, - url: String, - ): Boolean { - appState.showLinkPopup(url) - return true - } - }, - ) - - LinkDropDownMenu( - appState.linkDropdownExpanded.value, - appState::hideLinkPopup, - appState, - appSettings.useCustomTabs, - appSettings.usePrivateTabs, - ) - - ShowChangelog(appSettingsViewModel = appSettingsViewModel) - - val drawerState = rememberDrawerState(DrawerValue.Closed) - - NavHost( - route = Route.Graph.ROOT, - navController = appState.navController, - startDestination = Route.HOME, - enterTransition = { - slideInHorizontally { it } - }, - exitTransition = - { + MarkdownHelper.init( + appState, + appSettings.useCustomTabs, + appSettings.usePrivateTabs, + object : BetterLinkMovementMethod.OnLinkLongClickListener { + override fun onLongClick( + textView: TextView, + url: String, + ): Boolean { + appState.showLinkPopup(url) + return true + } + }, + ) + + LinkDropDownMenu( + appState.linkDropdownExpanded.value, + appState::hideLinkPopup, + appState, + appSettings.useCustomTabs, + appSettings.usePrivateTabs, + ) + + ShowChangelog(appSettingsViewModel = appSettingsViewModel) + + val drawerState = rememberDrawerState(DrawerValue.Closed) + + NavHost( + route = Route.Graph.ROOT, + navController = appState.navController, + startDestination = Route.HOME, + enterTransition = { + slideInHorizontally { it } + }, + exitTransition = + { + // No animation for image viewer + if (this.targetState.destination.route == Route.VIEW) { + ExitTransition.None + } else { + slideOutHorizontally { -it } + } + }, + popEnterTransition = { // No animation for image viewer - if (this.targetState.destination.route == Route.VIEW) { - ExitTransition.None + if (this.initialState.destination.route == Route.VIEW) { + EnterTransition.None } else { - slideOutHorizontally { -it } + slideInHorizontally { -it } + } + }, + popExitTransition = { + slideOutHorizontally { + it } }, - popEnterTransition = { - // No animation for image viewer - if (this.initialState.destination.route == Route.VIEW) { - EnterTransition.None - } else { - slideInHorizontally { -it } - } - }, - popExitTransition = { - slideOutHorizontally { - it - } - }, - ) { - composable( - route = Route.LOGIN, - deepLinks = - DEFAULT_LEMMY_INSTANCES.map { instance -> - navDeepLink { uriPattern = "$instance/login" } - }, ) { - LoginScreen( - appState = appState, - accountViewModel = accountViewModel, - siteViewModel = siteViewModel, - ) - } + composable( + route = Route.LOGIN, + deepLinks = + DEFAULT_LEMMY_INSTANCES.map { instance -> + navDeepLink { uriPattern = "$instance/login" } + }, + ) { + LoginScreen( + appState = appState, + accountViewModel = accountViewModel, + siteViewModel = siteViewModel, + ) + } - composable(route = Route.HOME) { - BottomNavScreen( - appState = appState, - accountViewModel = accountViewModel, - siteViewModel = siteViewModel, - appSettingsViewModel = appSettingsViewModel, - appSettings = appSettings, - drawerState = drawerState, - ) - } + composable(route = Route.HOME) { + BottomNavScreen( + appState = appState, + accountViewModel = accountViewModel, + siteViewModel = siteViewModel, + appSettingsViewModel = appSettingsViewModel, + appSettings = appSettings, + drawerState = drawerState, + ) + } - composable( - route = Route.COMMUNITY_FROM_ID, - arguments = - listOf( - navArgument(Route.CommunityFromIdArgs.ID) { - type = Route.CommunityFromIdArgs.ID_TYPE - }, - ), - ) { - val args = Route.CommunityFromIdArgs(it) - - CommunityScreen( - communityArg = Either.Left(args.id), - appState = appState, - accountViewModel = accountViewModel, - appSettingsViewModel = appSettingsViewModel, - showVotingArrowsInListView = appSettings.showVotingArrowsInListView, - siteViewModel = siteViewModel, - useCustomTabs = appSettings.useCustomTabs, - usePrivateTabs = appSettings.usePrivateTabs, - blurNSFW = appSettings.blurNSFW.toEnum(), - showPostLinkPreviews = appSettings.showPostLinkPreviews, - markAsReadOnScroll = appSettings.markAsReadOnScroll, - postActionBarMode = appSettings.postActionBarMode.toEnum(), - swipeToActionPreset = appSettings.swipeToActionPreset.toEnum(), - ) - } + composable( + route = Route.COMMUNITY_FROM_ID, + arguments = + listOf( + navArgument(Route.CommunityFromIdArgs.ID) { + type = Route.CommunityFromIdArgs.ID_TYPE + }, + ), + ) { + val args = Route.CommunityFromIdArgs(it) - // Only necessary for community deeplinks - composable( - route = Route.COMMUNITY_FROM_URL, - deepLinks = - listOf( - navDeepLink { uriPattern = Route.COMMUNITY_FROM_URL }, - ), - arguments = - listOf( - navArgument(Route.CommunityFromUrlArgs.NAME) { - type = Route.CommunityFromUrlArgs.NAME_TYPE - }, - navArgument(Route.CommunityFromUrlArgs.INSTANCE) { - type = Route.CommunityFromUrlArgs.INSTANCE_TYPE - }, - ), - ) { - val args = Route.CommunityFromUrlArgs(it) - // Could be instance/c/community@otherinstance ({instance}/c/{name}) - // Name could contain its instance already, thus we check for it - val qualifiedName = - if (args.name.contains("@")) { - args.name - } else { - "${args.name}@${args.instance}" - } + CommunityScreen( + communityArg = Either.Left(args.id), + appState = appState, + accountViewModel = accountViewModel, + appSettingsViewModel = appSettingsViewModel, + showVotingArrowsInListView = appSettings.showVotingArrowsInListView, + siteViewModel = siteViewModel, + useCustomTabs = appSettings.useCustomTabs, + usePrivateTabs = appSettings.usePrivateTabs, + blurNSFW = appSettings.blurNSFW.toEnum(), + showPostLinkPreviews = appSettings.showPostLinkPreviews, + markAsReadOnScroll = appSettings.markAsReadOnScroll, + postActionBarMode = appSettings.postActionBarMode.toEnum(), + swipeToActionPreset = appSettings.swipeToActionPreset.toEnum(), + ) + } - CommunityScreen( - communityArg = Either.Right(qualifiedName), - appState = appState, - accountViewModel = accountViewModel, - appSettingsViewModel = appSettingsViewModel, - showVotingArrowsInListView = appSettings.showVotingArrowsInListView, - siteViewModel = siteViewModel, - useCustomTabs = appSettings.useCustomTabs, - usePrivateTabs = appSettings.usePrivateTabs, - blurNSFW = appSettings.blurNSFW.toEnum(), - showPostLinkPreviews = appSettings.showPostLinkPreviews, - markAsReadOnScroll = appSettings.markAsReadOnScroll, - postActionBarMode = appSettings.postActionBarMode.toEnum(), - swipeToActionPreset = appSettings.swipeToActionPreset.toEnum(), - ) - } + // Only necessary for community deeplinks + composable( + route = Route.COMMUNITY_FROM_URL, + deepLinks = + listOf( + navDeepLink { uriPattern = Route.COMMUNITY_FROM_URL }, + ), + arguments = + listOf( + navArgument(Route.CommunityFromUrlArgs.NAME) { + type = Route.CommunityFromUrlArgs.NAME_TYPE + }, + navArgument(Route.CommunityFromUrlArgs.INSTANCE) { + type = Route.CommunityFromUrlArgs.INSTANCE_TYPE + }, + ), + ) { + val args = Route.CommunityFromUrlArgs(it) + // Could be instance/c/community@otherinstance ({instance}/c/{name}) + // Name could contain its instance already, thus we check for it + val qualifiedName = + if (args.name.contains("@")) { + args.name + } else { + "${args.name}@${args.instance}" + } + + CommunityScreen( + communityArg = Either.Right(qualifiedName), + appState = appState, + accountViewModel = accountViewModel, + appSettingsViewModel = appSettingsViewModel, + showVotingArrowsInListView = appSettings.showVotingArrowsInListView, + siteViewModel = siteViewModel, + useCustomTabs = appSettings.useCustomTabs, + usePrivateTabs = appSettings.usePrivateTabs, + blurNSFW = appSettings.blurNSFW.toEnum(), + showPostLinkPreviews = appSettings.showPostLinkPreviews, + markAsReadOnScroll = appSettings.markAsReadOnScroll, + postActionBarMode = appSettings.postActionBarMode.toEnum(), + swipeToActionPreset = appSettings.swipeToActionPreset.toEnum(), + ) + } - composable(route = Route.COMMUNITY_SIDEBAR) { - CommunitySidebarScreen( - appState = appState, - onClickBack = appState::popBackStack, - showAvatar = siteViewModel.showAvatar(), - ) - } + composable(route = Route.COMMUNITY_SIDEBAR) { + CommunitySidebarScreen( + appState = appState, + onClickBack = appState::popBackStack, + showAvatar = siteViewModel.showAvatar(), + ) + } - composable( - route = Route.PROFILE_FROM_ID, - arguments = - listOf( - navArgument(Route.ProfileFromIdArgs.ID) { - type = Route.ProfileFromIdArgs.ID_TYPE - }, - navArgument(Route.ProfileFromIdArgs.SAVED) { - defaultValue = Route.ProfileFromIdArgs.SAVED_DEFAULT - type = Route.ProfileFromIdArgs.SAVED_TYPE - }, - ), - ) { - val args = Route.ProfileFromIdArgs(it) - PersonProfileScreen( - personArg = Either.Left(args.id), - savedMode = args.saved, - appState = appState, - accountViewModel = accountViewModel, - appSettingsViewModel = appSettingsViewModel, - showVotingArrowsInListView = appSettings.showVotingArrowsInListView, - siteViewModel = siteViewModel, - useCustomTabs = appSettings.useCustomTabs, - usePrivateTabs = appSettings.usePrivateTabs, - blurNSFW = appSettings.blurNSFW.toEnum(), - showPostLinkPreviews = appSettings.showPostLinkPreviews, - drawerState = drawerState, - onBack = appState::popBackStack, - markAsReadOnScroll = appSettings.markAsReadOnScroll, - postActionBarMode = appSettings.postActionBarMode.toEnum(), - swipeToActionPreset = appSettings.swipeToActionPreset.toEnum(), - ) - } + composable( + route = Route.PROFILE_FROM_ID, + arguments = + listOf( + navArgument(Route.ProfileFromIdArgs.ID) { + type = Route.ProfileFromIdArgs.ID_TYPE + }, + navArgument(Route.ProfileFromIdArgs.SAVED) { + defaultValue = Route.ProfileFromIdArgs.SAVED_DEFAULT + type = Route.ProfileFromIdArgs.SAVED_TYPE + }, + ), + ) { + val args = Route.ProfileFromIdArgs(it) + PersonProfileScreen( + personArg = Either.Left(args.id), + savedMode = args.saved, + appState = appState, + accountViewModel = accountViewModel, + appSettingsViewModel = appSettingsViewModel, + showVotingArrowsInListView = appSettings.showVotingArrowsInListView, + siteViewModel = siteViewModel, + useCustomTabs = appSettings.useCustomTabs, + usePrivateTabs = appSettings.usePrivateTabs, + blurNSFW = appSettings.blurNSFW.toEnum(), + showPostLinkPreviews = appSettings.showPostLinkPreviews, + drawerState = drawerState, + onBack = appState::popBackStack, + markAsReadOnScroll = appSettings.markAsReadOnScroll, + postActionBarMode = appSettings.postActionBarMode.toEnum(), + swipeToActionPreset = appSettings.swipeToActionPreset.toEnum(), + ) + } - // Necessary for deep links - composable( - route = Route.PROFILE_FROM_URL, - deepLinks = - listOf( - navDeepLink { uriPattern = Route.PROFILE_FROM_URL }, - ), - arguments = - listOf( - navArgument(Route.ProfileFromUrlArgs.NAME) { - type = Route.ProfileFromUrlArgs.NAME_TYPE - }, - navArgument(Route.ProfileFromUrlArgs.INSTANCE) { - type = Route.ProfileFromUrlArgs.INSTANCE_TYPE - }, - ), - ) { - val args = Route.ProfileFromUrlArgs(it) - val qualifiedName = "${args.name}@${args.instance}" - PersonProfileScreen( - personArg = Either.Right(qualifiedName), - savedMode = false, - appState = appState, - accountViewModel = accountViewModel, - appSettingsViewModel = appSettingsViewModel, - showVotingArrowsInListView = appSettings.showVotingArrowsInListView, - siteViewModel = siteViewModel, - useCustomTabs = appSettings.useCustomTabs, - usePrivateTabs = appSettings.usePrivateTabs, - blurNSFW = appSettings.blurNSFW.toEnum(), - showPostLinkPreviews = appSettings.showPostLinkPreviews, - drawerState = drawerState, - onBack = appState::popBackStack, - markAsReadOnScroll = appSettings.markAsReadOnScroll, - postActionBarMode = appSettings.postActionBarMode.toEnum(), - swipeToActionPreset = appSettings.swipeToActionPreset.toEnum(), - ) - } + // Necessary for deep links + composable( + route = Route.PROFILE_FROM_URL, + deepLinks = + listOf( + navDeepLink { uriPattern = Route.PROFILE_FROM_URL }, + ), + arguments = + listOf( + navArgument(Route.ProfileFromUrlArgs.NAME) { + type = Route.ProfileFromUrlArgs.NAME_TYPE + }, + navArgument(Route.ProfileFromUrlArgs.INSTANCE) { + type = Route.ProfileFromUrlArgs.INSTANCE_TYPE + }, + ), + ) { + val args = Route.ProfileFromUrlArgs(it) + val qualifiedName = "${args.name}@${args.instance}" + PersonProfileScreen( + personArg = Either.Right(qualifiedName), + savedMode = false, + appState = appState, + accountViewModel = accountViewModel, + appSettingsViewModel = appSettingsViewModel, + showVotingArrowsInListView = appSettings.showVotingArrowsInListView, + siteViewModel = siteViewModel, + useCustomTabs = appSettings.useCustomTabs, + usePrivateTabs = appSettings.usePrivateTabs, + blurNSFW = appSettings.blurNSFW.toEnum(), + showPostLinkPreviews = appSettings.showPostLinkPreviews, + drawerState = drawerState, + onBack = appState::popBackStack, + markAsReadOnScroll = appSettings.markAsReadOnScroll, + postActionBarMode = appSettings.postActionBarMode.toEnum(), + swipeToActionPreset = appSettings.swipeToActionPreset.toEnum(), + ) + } - composable( - route = Route.COMMUNITY_LIST, - arguments = - listOf( - navArgument(Route.CommunityListArgs.SELECT) { - defaultValue = Route.CommunityListArgs.SELECT_DEFAULT - type = Route.CommunityListArgs.SELECT_TYPE - }, - ), - ) { - val args = Route.CommunityListArgs(it) - CommunityListScreen( - appState = appState, - selectMode = args.select, - blurNSFW = appSettings.blurNSFW.toEnum(), - drawerState = drawerState, - followList = siteViewModel.getFollowList(), - showAvatar = siteViewModel.showAvatar(), - ) - } + composable( + route = Route.COMMUNITY_LIST, + arguments = + listOf( + navArgument(Route.CommunityListArgs.SELECT) { + defaultValue = Route.CommunityListArgs.SELECT_DEFAULT + type = Route.CommunityListArgs.SELECT_TYPE + }, + ), + ) { + val args = Route.CommunityListArgs(it) + CommunityListScreen( + appState = appState, + selectMode = args.select, + blurNSFW = appSettings.blurNSFW.toEnum(), + drawerState = drawerState, + followList = siteViewModel.getFollowList(), + showAvatar = siteViewModel.showAvatar(), + ) + } - composable( - route = Route.CREATE_POST, - deepLinks = - listOf( - navDeepLink { mimeType = "text/plain" }, - navDeepLink { mimeType = "image/*" }, - ), - ) { - val activity = ctx.findActivity() - val text = activity?.intent?.getStringExtra(Intent.EXTRA_TEXT) ?: "" - val image = - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - activity?.intent?.getParcelableExtra( - Intent.EXTRA_STREAM, - Uri::class.java, - ) + composable( + route = Route.CREATE_POST, + deepLinks = + listOf( + navDeepLink { mimeType = "text/plain" }, + navDeepLink { mimeType = "image/*" }, + ), + ) { + val activity = ctx.findActivity() + val text = activity?.intent?.getStringExtra(Intent.EXTRA_TEXT) ?: "" + val image = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + activity?.intent?.getParcelableExtra( + Intent.EXTRA_STREAM, + Uri::class.java, + ) + } else { + @Suppress("DEPRECATION") + activity?.intent?.getParcelableExtra(Intent.EXTRA_STREAM) as? Uri + } + // url and body will be empty everytime except when there is EXTRA TEXT in the intent + var url = "" + var body = "" + if (Patterns.WEB_URL.matcher(text).matches()) { + url = text.padUrlWithHttps() } else { - @Suppress("DEPRECATION") - activity?.intent?.getParcelableExtra(Intent.EXTRA_STREAM) as? Uri + body = text } - // url and body will be empty everytime except when there is EXTRA TEXT in the intent - var url = "" - var body = "" - if (Patterns.WEB_URL.matcher(text).matches()) { - url = text.padUrlWithHttps() - } else { - body = text - } - CreatePostScreen( - appState = appState, - accountViewModel = accountViewModel, - initialUrl = url, - initialBody = body, - initialImage = image, - ) - activity?.intent?.replaceExtras(Bundle()) - } + CreatePostScreen( + appState = appState, + accountViewModel = accountViewModel, + initialUrl = url, + initialBody = body, + initialImage = image, + ) + activity?.intent?.replaceExtras(Bundle()) + } - composable( - route = Route.INBOX, - deepLinks = - DEFAULT_LEMMY_INSTANCES.map { instance -> - navDeepLink { uriPattern = "$instance/inbox" } - }, - ) { - InboxScreen( - appState = appState, - accountViewModel = accountViewModel, - siteViewModel = siteViewModel, - blurNSFW = appSettings.blurNSFW.toEnum(), - drawerState = drawerState, - ) - } + composable( + route = Route.INBOX, + deepLinks = + DEFAULT_LEMMY_INSTANCES.map { instance -> + navDeepLink { uriPattern = "$instance/inbox" } + }, + ) { + InboxScreen( + appState = appState, + accountViewModel = accountViewModel, + siteViewModel = siteViewModel, + blurNSFW = appSettings.blurNSFW.toEnum(), + drawerState = drawerState, + ) + } - composable( - route = Route.REGISTRATION_APPLICATIONS, - ) { - RegistrationApplicationsScreen( - appState = appState, - accountViewModel = accountViewModel, - siteViewModel = siteViewModel, - drawerState = drawerState, - ) - } + composable( + route = Route.REGISTRATION_APPLICATIONS, + ) { + RegistrationApplicationsScreen( + appState = appState, + accountViewModel = accountViewModel, + siteViewModel = siteViewModel, + drawerState = drawerState, + ) + } - composable( - route = Route.REGISTRATION_APPLICATIONS, - ) { - RegistrationApplicationsScreen( - appState = appState, - accountViewModel = accountViewModel, - siteViewModel = siteViewModel, - drawerState = drawerState, - ) - } + composable( + route = Route.REGISTRATION_APPLICATIONS, + ) { + RegistrationApplicationsScreen( + appState = appState, + accountViewModel = accountViewModel, + siteViewModel = siteViewModel, + drawerState = drawerState, + ) + } - composable( - route = Route.REPORTS, - ) { - ReportsScreen( - appState = appState, - accountViewModel = accountViewModel, - siteViewModel = siteViewModel, - drawerState = drawerState, - blurNSFW = appSettings.blurNSFW.toEnum(), - ) - } + composable( + route = Route.REPORTS, + ) { + ReportsScreen( + appState = appState, + accountViewModel = accountViewModel, + siteViewModel = siteViewModel, + drawerState = drawerState, + blurNSFW = appSettings.blurNSFW.toEnum(), + ) + } - composable( - route = Route.POST, - deepLinks = - DEFAULT_LEMMY_INSTANCES.map { instance -> - navDeepLink { uriPattern = "$instance/post/{${Route.PostArgs.ID}}" } - }, - arguments = - listOf( - navArgument(Route.PostArgs.ID) { - type = Route.PostArgs.ID_TYPE + composable( + route = Route.POST, + deepLinks = + DEFAULT_LEMMY_INSTANCES.map { instance -> + navDeepLink { uriPattern = "$instance/post/{${Route.PostArgs.ID}}" } }, - ), - ) { - val args = Route.PostArgs(it) + arguments = + listOf( + navArgument(Route.PostArgs.ID) { + type = Route.PostArgs.ID_TYPE + }, + ), + ) { + val args = Route.PostArgs(it) + + SwipeToNavigateBack( + appSettings.postNavigationGestureMode.toEnumSafe(), + appState::navigateUp, + ) { + PostPane( + postOrCommentId = Either.Left(args.id), + accountViewModel = accountViewModel, + appState = appState, + showCollapsedCommentContent = appSettings.showCollapsedCommentContent, + showActionBarByDefault = appSettings.showCommentActionBarByDefault, + showVotingArrowsInListView = appSettings.showVotingArrowsInListView, + showParentCommentNavigationButtons = appSettings.showParentCommentNavigationButtons, + navigateParentCommentsWithVolumeButtons = appSettings.navigateParentCommentsWithVolumeButtons, + siteViewModel = siteViewModel, + useCustomTabs = appSettings.useCustomTabs, + usePrivateTabs = appSettings.usePrivateTabs, + blurNSFW = appSettings.blurNSFW.toEnum(), + showPostLinkPreview = appSettings.showPostLinkPreviews, + postActionBarMode = appSettings.postActionBarMode.toEnum(), + swipeToActionPreset = appSettings.swipeToActionPreset.toEnum(), + ) + } + } - SwipeToNavigateBack( - appSettings.postNavigationGestureMode.toEnumSafe(), - appState::navigateUp, + composable( + route = Route.COMMENT, + deepLinks = + DEFAULT_LEMMY_INSTANCES.map { instance -> + navDeepLink { uriPattern = "$instance/comment/{${Route.CommentArgs.ID}}" } + }, + arguments = + listOf( + navArgument(Route.CommentArgs.ID) { + type = Route.CommentArgs.ID_TYPE + }, + ), ) { - PostScreen( - id = Either.Left(args.id), + val args = Route.CommentArgs(it) + PostPane( + postOrCommentId = Either.Right(args.id), accountViewModel = accountViewModel, appState = appState, + useCustomTabs = appSettings.useCustomTabs, + usePrivateTabs = appSettings.usePrivateTabs, showCollapsedCommentContent = appSettings.showCollapsedCommentContent, showActionBarByDefault = appSettings.showCommentActionBarByDefault, showVotingArrowsInListView = appSettings.showVotingArrowsInListView, showParentCommentNavigationButtons = appSettings.showParentCommentNavigationButtons, navigateParentCommentsWithVolumeButtons = appSettings.navigateParentCommentsWithVolumeButtons, siteViewModel = siteViewModel, - useCustomTabs = appSettings.useCustomTabs, - usePrivateTabs = appSettings.usePrivateTabs, blurNSFW = appSettings.blurNSFW.toEnum(), showPostLinkPreview = appSettings.showPostLinkPreviews, postActionBarMode = appSettings.postActionBarMode.toEnum(), swipeToActionPreset = appSettings.swipeToActionPreset.toEnum(), ) } - } - composable( - route = Route.COMMENT, - deepLinks = - DEFAULT_LEMMY_INSTANCES.map { instance -> - navDeepLink { uriPattern = "$instance/comment/{${Route.CommentArgs.ID}}" } - }, - arguments = - listOf( - navArgument(Route.CommentArgs.ID) { - type = Route.CommentArgs.ID_TYPE - }, - ), - ) { - val args = Route.CommentArgs(it) - PostScreen( - id = Either.Right(args.id), - accountViewModel = accountViewModel, - appState = appState, - useCustomTabs = appSettings.useCustomTabs, - usePrivateTabs = appSettings.usePrivateTabs, - showCollapsedCommentContent = appSettings.showCollapsedCommentContent, - showActionBarByDefault = appSettings.showCommentActionBarByDefault, - showVotingArrowsInListView = appSettings.showVotingArrowsInListView, - showParentCommentNavigationButtons = appSettings.showParentCommentNavigationButtons, - navigateParentCommentsWithVolumeButtons = appSettings.navigateParentCommentsWithVolumeButtons, - siteViewModel = siteViewModel, - blurNSFW = appSettings.blurNSFW.toEnum(), - showPostLinkPreview = appSettings.showPostLinkPreviews, - postActionBarMode = appSettings.postActionBarMode.toEnum(), - swipeToActionPreset = appSettings.swipeToActionPreset.toEnum(), - ) - } + composable( + route = Route.COMMENT_REPLY, + ) { + CommentReplyScreen( + accountViewModel = accountViewModel, + appState = appState, + siteViewModel = siteViewModel, + ) + } - composable( - route = Route.COMMENT_REPLY, - ) { - CommentReplyScreen( - accountViewModel = accountViewModel, - appState = appState, - siteViewModel = siteViewModel, - ) - } + composable(route = Route.SITE_SIDEBAR) { + SiteSidebarScreen( + appState = appState, + siteViewModel = siteViewModel, + ) + } - composable(route = Route.SITE_SIDEBAR) { - SiteSidebarScreen( - appState = appState, - siteViewModel = siteViewModel, - ) - } + composable(route = Route.SITE_LEGAL) { + SiteLegalScreen( + siteViewModel = siteViewModel, + onBackClick = appState::popBackStack, + ) + } - composable(route = Route.SITE_LEGAL) { - SiteLegalScreen( - siteViewModel = siteViewModel, - onBackClick = appState::popBackStack, - ) - } + composable(route = Route.COMMENT_EDIT) { + CommentEditScreen( + appState = appState, + accountViewModel = accountViewModel, + ) + } - composable(route = Route.COMMENT_EDIT) { - CommentEditScreen( - appState = appState, - accountViewModel = accountViewModel, - ) - } + composable(route = Route.POST_EDIT) { + PostEditScreen( + accountViewModel = accountViewModel, + appState = appState, + ) + } - composable(route = Route.POST_EDIT) { - PostEditScreen( - accountViewModel = accountViewModel, - appState = appState, - ) - } + composable(route = Route.PRIVATE_MESSAGE_REPLY) { + PrivateMessageReplyScreen( + appState = appState, + accountViewModel = accountViewModel, + siteViewModel = siteViewModel, + onBack = appState::popBackStack, + onProfile = appState::toProfile, + ) + } - composable(route = Route.PRIVATE_MESSAGE_REPLY) { - PrivateMessageReplyScreen( - appState = appState, - accountViewModel = accountViewModel, - siteViewModel = siteViewModel, - onBack = appState::popBackStack, - onProfile = appState::toProfile, - ) - } + composable( + route = Route.POST_REMOVE, + ) { + PostRemoveScreen( + appState = appState, + accountViewModel = accountViewModel, + ) + } - composable( - route = Route.POST_REMOVE, - ) { - PostRemoveScreen( - appState = appState, - accountViewModel = accountViewModel, - ) - } + composable( + route = Route.POST_REMOVE, + ) { + PostRemoveScreen( + appState = appState, + accountViewModel = accountViewModel, + ) + } - composable( - route = Route.POST_REMOVE, - ) { - PostRemoveScreen( - appState = appState, - accountViewModel = accountViewModel, - ) - } + composable( + route = Route.COMMENT_REMOVE, + ) { + CommentRemoveScreen( + appState = appState, + accountViewModel = accountViewModel, + ) + } - composable( - route = Route.COMMENT_REMOVE, - ) { - CommentRemoveScreen( - appState = appState, - accountViewModel = accountViewModel, - ) - } + composable( + route = Route.BAN_PERSON, + ) { + BanPersonScreen( + appState = appState, + accountViewModel = accountViewModel, + ) + } - composable( - route = Route.BAN_PERSON, - ) { - BanPersonScreen( - appState = appState, - accountViewModel = accountViewModel, - ) - } + composable( + route = Route.BAN_FROM_COMMUNITY, + ) { + BanFromCommunityScreen( + appState = appState, + accountViewModel = accountViewModel, + ) + } - composable( - route = Route.BAN_FROM_COMMUNITY, - ) { - BanFromCommunityScreen( - appState = appState, - accountViewModel = accountViewModel, - ) - } + composable( + route = Route.POST_REPORT, + arguments = + listOf( + navArgument(Route.PostReportArgs.ID) { + type = Route.PostReportArgs.ID_TYPE + }, + ), + ) { + val args = Route.PostReportArgs(it) + CreatePostReportScreen( + postId = args.id, + accountViewModel = accountViewModel, + onBack = appState::navigateUp, + ) + } - composable( - route = Route.POST_REPORT, - arguments = - listOf( - navArgument(Route.PostReportArgs.ID) { - type = Route.PostReportArgs.ID_TYPE - }, - ), - ) { - val args = Route.PostReportArgs(it) - CreatePostReportScreen( - postId = args.id, - accountViewModel = accountViewModel, - onBack = appState::navigateUp, - ) - } + composable( + route = Route.POST_LIKES, + arguments = + listOf( + navArgument(Route.PostLikesArgs.ID) { + type = Route.PostLikesArgs.ID_TYPE + }, + ), + ) { + val args = Route.PostLikesArgs(it) + PostLikesScreen( + appState = appState, + postId = args.id, + onBack = appState::navigateUp, + ) + } - composable( - route = Route.POST_LIKES, - arguments = - listOf( - navArgument(Route.PostLikesArgs.ID) { - type = Route.PostLikesArgs.ID_TYPE - }, - ), - ) { - val args = Route.PostLikesArgs(it) - PostLikesScreen( - appState = appState, - postId = args.id, - onBack = appState::navigateUp, - ) - } + composable( + route = Route.COMMENT_LIKES, + arguments = + listOf( + navArgument(Route.CommentLikesArgs.ID) { + type = Route.CommentLikesArgs.ID_TYPE + }, + ), + ) { + val args = Route.CommentLikesArgs(it) + CommentLikesScreen( + appState = appState, + commentId = args.id, + onBack = appState::navigateUp, + ) + } - composable( - route = Route.COMMENT_LIKES, - arguments = - listOf( - navArgument(Route.CommentLikesArgs.ID) { - type = Route.CommentLikesArgs.ID_TYPE - }, - ), - ) { - val args = Route.CommentLikesArgs(it) - CommentLikesScreen( - appState = appState, - commentId = args.id, - onBack = appState::navigateUp, - ) - } + composable( + route = Route.COMMENT_REPORT, + arguments = + listOf( + navArgument(Route.CommentReportArgs.ID) { + type = Route.CommentReportArgs.ID_TYPE + }, + ), + ) { + val args = Route.CommentReportArgs(it) + CreateCommentReportScreen( + commentId = args.id, + accountViewModel = accountViewModel, + onBack = appState::navigateUp, + ) + } - composable( - route = Route.COMMENT_REPORT, - arguments = - listOf( - navArgument(Route.CommentReportArgs.ID) { - type = Route.CommentReportArgs.ID_TYPE - }, - ), - ) { - val args = Route.CommentReportArgs(it) - CreateCommentReportScreen( - commentId = args.id, - accountViewModel = accountViewModel, - onBack = appState::navigateUp, - ) - } + composable(route = Route.SETTINGS) { + SettingsScreen( + accountViewModel = accountViewModel, + onBack = appState::popBackStack, + onClickAbout = appState::toAbout, + onClickAccountSettings = appState::toAccountSettings, + onClickBlocks = appState::toBlockView, + onClickLookAndFeel = appState::toLookAndFeel, + onClickBackupAndRestore = appState::toBackupAndRestore, + ) + } - composable(route = Route.SETTINGS) { - SettingsScreen( - accountViewModel = accountViewModel, - onBack = appState::popBackStack, - onClickAbout = appState::toAbout, - onClickAccountSettings = appState::toAccountSettings, - onClickBlocks = appState::toBlockView, - onClickLookAndFeel = appState::toLookAndFeel, - onClickBackupAndRestore = appState::toBackupAndRestore, - ) - } + composable(route = Route.LOOK_AND_FEEL) { + LookAndFeelScreen( + appSettingsViewModel = appSettingsViewModel, + onBack = appState::popBackStack, + ) + } - composable(route = Route.LOOK_AND_FEEL) { - LookAndFeelScreen( - appSettingsViewModel = appSettingsViewModel, - onBack = appState::popBackStack, - ) - } + composable( + route = Route.ACCOUNT_SETTINGS, + deepLinks = + DEFAULT_LEMMY_INSTANCES.map { instance -> + navDeepLink { uriPattern = "$instance/settings" } + }, + ) { + AccountSettingsScreen( + accountViewModel = accountViewModel, + siteViewModel = siteViewModel, + accountSettingsViewModel = accountSettingsViewModel, + onBack = appState::popBackStack, + ) + } - composable( - route = Route.ACCOUNT_SETTINGS, - deepLinks = - DEFAULT_LEMMY_INSTANCES.map { instance -> - navDeepLink { uriPattern = "$instance/settings" } - }, - ) { - AccountSettingsScreen( - accountViewModel = accountViewModel, - siteViewModel = siteViewModel, - accountSettingsViewModel = accountSettingsViewModel, - onBack = appState::popBackStack, - ) - } + composable(route = Route.BACKUP_AND_RESTORE) { + BackupAndRestoreScreen( + onBack = appState::popBackStack, + ) + } - composable(route = Route.BACKUP_AND_RESTORE) { - BackupAndRestoreScreen( - onBack = appState::popBackStack, - ) - } + composable(route = Route.ABOUT) { + AboutScreen( + useCustomTabs = appSettings.useCustomTabs, + usePrivateTabs = appSettings.usePrivateTabs, + onBack = appState::popBackStack, + onClickCrashLogs = appState::toCrashLogs, + openLinkRaw = appState::openLinkRaw, + ) + } - composable(route = Route.ABOUT) { - AboutScreen( - useCustomTabs = appSettings.useCustomTabs, - usePrivateTabs = appSettings.usePrivateTabs, - onBack = appState::popBackStack, - onClickCrashLogs = appState::toCrashLogs, - openLinkRaw = appState::openLinkRaw, - ) - } + composable(route = Route.BLOCK_VIEW) { + BlocksScreen( + siteViewModel = siteViewModel, + onBack = appState::popBackStack, + ) + } - composable(route = Route.BLOCK_VIEW) { - BlocksScreen( - siteViewModel = siteViewModel, - onBack = appState::popBackStack, - ) - } + composable(route = Route.CRASH_LOGS) { + CrashLogsScreen( + onClickBack = appState::popBackStack, + ) + } - composable(route = Route.CRASH_LOGS) { - CrashLogsScreen( - onClickBack = appState::popBackStack, - ) - } + composable( + route = Route.VIEW, + arguments = + listOf( + navArgument(Route.ViewArgs.URL) { + type = Route.ViewArgs.URL_TYPE + }, + ), + enterTransition = { EnterTransition.None }, + exitTransition = { ExitTransition.None }, + popEnterTransition = { EnterTransition.None }, + popExitTransition = { ExitTransition.None }, + ) { + val args = Route.ViewArgs(it) - composable( - route = Route.VIEW, - arguments = - listOf( - navArgument(Route.ViewArgs.URL) { - type = Route.ViewArgs.URL_TYPE - }, - ), - enterTransition = { EnterTransition.None }, - exitTransition = { ExitTransition.None }, - popEnterTransition = { EnterTransition.None }, - popExitTransition = { ExitTransition.None }, - ) { - val args = Route.ViewArgs(it) + ImageViewerScreen(url = args.url, appState = appState) + } - ImageViewerScreen(url = args.url, appState = appState) - } + composable( + route = Route.CREATE_PRIVATE_MESSAGE, + arguments = + listOf( + navArgument(Route.CreatePrivateMessageArgs.PERSON_ID) { + type = Route.CreatePrivateMessageArgs.PERSON_ID_TYPE + }, + navArgument(Route.CreatePrivateMessageArgs.PERSON_NAME) { + type = Route.CreatePrivateMessageArgs.PERSON_NAME_TYPE + }, + ), + ) { + val args = Route.CreatePrivateMessageArgs(it) - composable( - route = Route.CREATE_PRIVATE_MESSAGE, - arguments = - listOf( - navArgument(Route.CreatePrivateMessageArgs.PERSON_ID) { - type = Route.CreatePrivateMessageArgs.PERSON_ID_TYPE - }, - navArgument(Route.CreatePrivateMessageArgs.PERSON_NAME) { - type = Route.CreatePrivateMessageArgs.PERSON_NAME_TYPE - }, - ), - ) { - val args = Route.CreatePrivateMessageArgs(it) - - CreatePrivateMessageScreen( - args.personId, - args.personName, - accountViewModel, - appState::popBackStack, - ) + CreatePrivateMessageScreen( + args.personId, + args.personName, + accountViewModel, + appState::popBackStack, + ) + } } } } diff --git a/app/src/main/java/com/jerboa/Utils.kt b/app/src/main/java/com/jerboa/Utils.kt index c7f982f31..2a608642f 100644 --- a/app/src/main/java/com/jerboa/Utils.kt +++ b/app/src/main/java/com/jerboa/Utils.kt @@ -1500,3 +1500,11 @@ fun Context.getVersionCode(): Int = } fun Int.toBool() = this > 0 + +sealed interface SelectionVisibilityState { + object NoSelection : SelectionVisibilityState + + data class ShowSelection( + val selectedItem: Item, + ) : SelectionVisibilityState +} diff --git a/app/src/main/java/com/jerboa/model/PostViewModel.kt b/app/src/main/java/com/jerboa/model/PostViewModel.kt index d6d5692ad..0744d78a3 100644 --- a/app/src/main/java/com/jerboa/model/PostViewModel.kt +++ b/app/src/main/java/com/jerboa/model/PostViewModel.kt @@ -49,7 +49,7 @@ import it.vercruysse.lemmyapi.dto.ListingType import kotlinx.coroutines.launch class PostViewModel( - val id: Either, + var id: Either, ) : ViewModel() { var postRes: ApiState by mutableStateOf(ApiState.Empty) private set @@ -79,6 +79,11 @@ class PostViewModel( this.getData() } + fun reInitializeWithNewId(id: Either) { + this.id = id + this.getData() + } + fun updateSortType(sortType: CommentSortType) { this.sortType = sortType } diff --git a/app/src/main/java/com/jerboa/ui/components/common/AccountHelpers.kt b/app/src/main/java/com/jerboa/ui/components/common/AccountHelpers.kt index 97b58faa7..68538e3b2 100644 --- a/app/src/main/java/com/jerboa/ui/components/common/AccountHelpers.kt +++ b/app/src/main/java/com/jerboa/ui/components/common/AccountHelpers.kt @@ -27,6 +27,7 @@ fun getCurrentAccount(accountViewModel: AccountViewModel): Account { return acc } +// TODO why is this necessary when .toEnum() exists? fun getPostViewMode(appSettingsViewModel: AppSettingsViewModel): PostViewMode = getEnumFromIntSetting(appSettingsViewModel.appSettings) { it.postViewMode diff --git a/app/src/main/java/com/jerboa/ui/components/common/DrawerItems.kt b/app/src/main/java/com/jerboa/ui/components/common/DrawerItems.kt index a346bf2c5..969a5c856 100644 --- a/app/src/main/java/com/jerboa/ui/components/common/DrawerItems.kt +++ b/app/src/main/java/com/jerboa/ui/components/common/DrawerItems.kt @@ -97,3 +97,7 @@ fun IconAndTextDrawerItemWithMorePreview() { more = true, ) } + +@Composable +fun selectedItemContainerColor(selected: Boolean) = + if (!selected) MaterialTheme.colorScheme.surface else MaterialTheme.colorScheme.surfaceVariant diff --git a/app/src/main/java/com/jerboa/ui/components/community/CommunityScreen.kt b/app/src/main/java/com/jerboa/ui/components/community/CommunityScreen.kt index 6b52391b6..b02695a3e 100644 --- a/app/src/main/java/com/jerboa/ui/components/community/CommunityScreen.kt +++ b/app/src/main/java/com/jerboa/ui/components/community/CommunityScreen.kt @@ -25,6 +25,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel import arrow.core.Either import com.jerboa.JerboaAppState import com.jerboa.R +import com.jerboa.SelectionVisibilityState import com.jerboa.api.ApiState import com.jerboa.datatypes.BanFromCommunityData import com.jerboa.db.entity.isAnon @@ -426,6 +427,7 @@ fun CommunityScreen( postActionBarMode = postActionBarMode, showPostAppendRetry = communityViewModel.postsRes is ApiState.AppendingFailure, swipeToActionPreset = swipeToActionPreset, + selectionState = SelectionVisibilityState.NoSelection, ) } diff --git a/app/src/main/java/com/jerboa/ui/components/home/BottomNavScreen.kt b/app/src/main/java/com/jerboa/ui/components/home/BottomNavScreen.kt index 93b6698e7..a3637719c 100644 --- a/app/src/main/java/com/jerboa/ui/components/home/BottomNavScreen.kt +++ b/app/src/main/java/com/jerboa/ui/components/home/BottomNavScreen.kt @@ -242,21 +242,14 @@ fun BottomNavScreen( startDestination = NavTab.Home.name, ) { composable(route = NavTab.Home.name) { - HomeScreen( + HomeAndPostDetailScreen( appState = appState, homeViewModel = homeViewModel, accountViewModel = accountViewModel, siteViewModel = siteViewModel, appSettingsViewModel = appSettingsViewModel, - showVotingArrowsInListView = appSettings.showVotingArrowsInListView, - useCustomTabs = appSettings.useCustomTabs, - usePrivateTabs = appSettings.usePrivateTabs, + appSettings = appSettings, drawerState = drawerState, - blurNSFW = appSettings.blurNSFW.toEnum(), - showPostLinkPreviews = appSettings.showPostLinkPreviews, - markAsReadOnScroll = appSettings.markAsReadOnScroll, - postActionBarMode = appSettings.postActionBarMode.toEnum(), - swipeToActionPreset = appSettings.swipeToActionPreset.toEnum(), padding = padding, ) } diff --git a/app/src/main/java/com/jerboa/ui/components/home/HomeAndPostDetailScreen.kt b/app/src/main/java/com/jerboa/ui/components/home/HomeAndPostDetailScreen.kt new file mode 100644 index 000000000..277aa5566 --- /dev/null +++ b/app/src/main/java/com/jerboa/ui/components/home/HomeAndPostDetailScreen.kt @@ -0,0 +1,153 @@ +package com.jerboa.ui.components.home + +import android.annotation.SuppressLint +import androidx.activity.compose.BackHandler +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.ExperimentalSharedTransitionApi +import androidx.compose.animation.SharedTransitionLayout +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.material3.DrawerState +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi +import androidx.compose.material3.adaptive.layout.AnimatedPane +import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffold +import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffoldRole +import androidx.compose.material3.adaptive.layout.PaneAdaptedValue +import androidx.compose.material3.adaptive.layout.PaneExpansionDragHandle +import androidx.compose.material3.adaptive.layout.rememberPaneExpansionState +import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaffoldNavigator +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.res.stringResource +import arrow.core.Either +import com.jerboa.JerboaAppState +import com.jerboa.R +import com.jerboa.SelectionVisibilityState +import com.jerboa.db.entity.AppSettings +import com.jerboa.model.AccountViewModel +import com.jerboa.model.AppSettingsViewModel +import com.jerboa.model.HomeViewModel +import com.jerboa.model.SiteViewModel +import com.jerboa.toEnum +import com.jerboa.ui.components.post.PostPane +import it.vercruysse.lemmyapi.datatypes.PostId +import kotlinx.coroutines.launch + +@SuppressLint("UnusedContentLambdaTargetStateParameter") +@OptIn( + ExperimentalMaterial3Api::class, + ExperimentalFoundationApi::class, + ExperimentalMaterial3AdaptiveApi::class, + ExperimentalSharedTransitionApi::class, + ExperimentalLayoutApi::class, +) +@Composable +fun HomeAndPostDetailScreen( + appState: JerboaAppState, + homeViewModel: HomeViewModel, + accountViewModel: AccountViewModel, + siteViewModel: SiteViewModel, + appSettingsViewModel: AppSettingsViewModel, + appSettings: AppSettings, + drawerState: DrawerState, + padding: PaddingValues, +) { + val scope = rememberCoroutineScope() + + var selectedPostId: PostId? by rememberSaveable { mutableStateOf(null) } + + val navigator = rememberListDetailPaneScaffoldNavigator() + val isListAndDetailVisible = + navigator.scaffoldValue[ListDetailPaneScaffoldRole.Detail] == PaneAdaptedValue.Companion.Expanded && + navigator.scaffoldValue[ListDetailPaneScaffoldRole.List] == PaneAdaptedValue.Companion.Expanded + val isDetailVisible = + navigator.scaffoldValue[ListDetailPaneScaffoldRole.Detail] == PaneAdaptedValue.Companion.Expanded + + BackHandler(enabled = navigator.canNavigateBack()) { + scope.launch { + navigator.navigateBack() + } + } + + SharedTransitionLayout { + AnimatedContent( + targetState = isListAndDetailVisible, + label = stringResource(R.string.bottomBar_label_home), + ) { + ListDetailPaneScaffold( + directive = navigator.scaffoldDirective, + value = navigator.scaffoldValue, + listPane = { + val currentSelectedId = selectedPostId + val selectionState = + if (isDetailVisible && currentSelectedId != null) { + SelectionVisibilityState.ShowSelection(currentSelectedId) + } else { + SelectionVisibilityState.NoSelection + } + + AnimatedPane { + HomePane( + appState = appState, + homeViewModel = homeViewModel, + accountViewModel = accountViewModel, + siteViewModel = siteViewModel, + appSettingsViewModel = appSettingsViewModel, + showVotingArrowsInListView = appSettings.showVotingArrowsInListView, + useCustomTabs = appSettings.useCustomTabs, + usePrivateTabs = appSettings.usePrivateTabs, + blurNSFW = appSettings.blurNSFW.toEnum(), + postActionBarMode = appSettings.postActionBarMode.toEnum(), + swipeToActionPreset = appSettings.swipeToActionPreset.toEnum(), + showPostLinkPreviews = appSettings.showPostLinkPreviews, + markAsReadOnScroll = appSettings.markAsReadOnScroll, + drawerState = drawerState, + padding = padding, + onPostClick = { postView -> + selectedPostId = postView.post.id + scope.launch { + navigator.navigateTo(ListDetailPaneScaffoldRole.Detail) + } + }, + selectionState = selectionState, + ) + } + }, + detailPane = { + AnimatedPane { + selectedPostId?.let { + PostPane( + postOrCommentId = Either.Left(it), + accountViewModel = accountViewModel, + appState = appState, + showCollapsedCommentContent = appSettings.showCollapsedCommentContent, + showActionBarByDefault = appSettings.showCommentActionBarByDefault, + showVotingArrowsInListView = appSettings.showVotingArrowsInListView, + showParentCommentNavigationButtons = appSettings.showParentCommentNavigationButtons, + navigateParentCommentsWithVolumeButtons = appSettings.navigateParentCommentsWithVolumeButtons, + siteViewModel = siteViewModel, + useCustomTabs = appSettings.useCustomTabs, + usePrivateTabs = appSettings.usePrivateTabs, + blurNSFW = appSettings.blurNSFW.toEnum(), + showPostLinkPreview = appSettings.showPostLinkPreviews, + postActionBarMode = appSettings.postActionBarMode.toEnum(), + swipeToActionPreset = appSettings.swipeToActionPreset.toEnum(), + ) + } + } + }, + paneExpansionState = rememberPaneExpansionState(navigator.scaffoldValue), + paneExpansionDragHandle = { state -> + PaneExpansionDragHandle(state, MaterialTheme.colorScheme.onSurfaceVariant) + }, + ) + } + } +} diff --git a/app/src/main/java/com/jerboa/ui/components/home/HomeActivity.kt b/app/src/main/java/com/jerboa/ui/components/home/HomePane.kt similarity index 97% rename from app/src/main/java/com/jerboa/ui/components/home/HomeActivity.kt rename to app/src/main/java/com/jerboa/ui/components/home/HomePane.kt index 73f99bc53..e937661b5 100644 --- a/app/src/main/java/com/jerboa/ui/components/home/HomeActivity.kt +++ b/app/src/main/java/com/jerboa/ui/components/home/HomePane.kt @@ -35,6 +35,7 @@ import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.testTagsAsResourceId import com.jerboa.JerboaAppState import com.jerboa.R +import com.jerboa.SelectionVisibilityState import com.jerboa.api.ApiState import com.jerboa.datatypes.BanFromCommunityData import com.jerboa.db.entity.Account @@ -73,6 +74,7 @@ import it.vercruysse.lemmyapi.datatypes.HidePost import it.vercruysse.lemmyapi.datatypes.LockPost import it.vercruysse.lemmyapi.datatypes.MarkPostAsRead import it.vercruysse.lemmyapi.datatypes.PersonView +import it.vercruysse.lemmyapi.datatypes.PostId import it.vercruysse.lemmyapi.datatypes.PostView import it.vercruysse.lemmyapi.datatypes.SavePost import it.vercruysse.lemmyapi.datatypes.Tagline @@ -80,7 +82,7 @@ import kotlinx.coroutines.launch @OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class) @Composable -fun HomeScreen( +fun HomePane( appState: JerboaAppState, homeViewModel: HomeViewModel, accountViewModel: AccountViewModel, @@ -96,8 +98,10 @@ fun HomeScreen( postActionBarMode: PostActionBarMode, swipeToActionPreset: SwipeToActionPreset, padding: PaddingValues, + onPostClick: (PostView) -> Unit, + selectionState: SelectionVisibilityState, ) { - Log.d("jerboa", "got to home screen") + Log.d("jerboa", "got to home pane") val scope = rememberCoroutineScope() val postListState = homeViewModel.lazyListState @@ -176,6 +180,8 @@ fun HomeScreen( snackbarHostState = snackbarHostState, postActionBarMode = postActionBarMode, swipeToActionPreset = swipeToActionPreset, + onPostClick = onPostClick, + selectionState = selectionState, ) } }, @@ -225,6 +231,8 @@ fun MainPostListingsContent( markAsReadOnScroll: Boolean, postActionBarMode: PostActionBarMode, swipeToActionPreset: SwipeToActionPreset, + onPostClick: (PostView) -> Unit, + selectionState: SelectionVisibilityState, ) { val ctx = LocalContext.current val scope = rememberCoroutineScope() @@ -296,9 +304,7 @@ fun MainPostListingsContent( ) } }, - onPostClick = { postView -> - appState.toPost(id = postView.post.id) - }, + onPostClick = onPostClick, onSaveClick = { postView -> account.doIfReadyElseDisplayInfo( appState, @@ -440,6 +446,7 @@ fun MainPostListingsContent( postActionBarMode = postActionBarMode, showPostAppendRetry = homeViewModel.postsRes is ApiState.AppendingFailure, swipeToActionPreset = swipeToActionPreset, + selectionState = selectionState, ) } } diff --git a/app/src/main/java/com/jerboa/ui/components/person/PersonProfileScreen.kt b/app/src/main/java/com/jerboa/ui/components/person/PersonProfileScreen.kt index 1036f4dc5..cfc0be905 100644 --- a/app/src/main/java/com/jerboa/ui/components/person/PersonProfileScreen.kt +++ b/app/src/main/java/com/jerboa/ui/components/person/PersonProfileScreen.kt @@ -39,6 +39,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel import arrow.core.Either import com.jerboa.JerboaAppState import com.jerboa.R +import com.jerboa.SelectionVisibilityState import com.jerboa.api.ApiState import com.jerboa.commentsToFlatNodes import com.jerboa.datatypes.BanFromCommunityData @@ -642,6 +643,7 @@ fun UserTabs( postActionBarMode = postActionBarMode, showPostAppendRetry = personProfileViewModel.personDetailsRes is ApiState.AppendingFailure, swipeToActionPreset = swipeToActionPreset, + selectionState = SelectionVisibilityState.NoSelection, ) } diff --git a/app/src/main/java/com/jerboa/ui/components/post/PostListingCard.kt b/app/src/main/java/com/jerboa/ui/components/post/PostListingCard.kt index 9796ae45c..b28873000 100644 --- a/app/src/main/java/com/jerboa/ui/components/post/PostListingCard.kt +++ b/app/src/main/java/com/jerboa/ui/components/post/PostListingCard.kt @@ -1,6 +1,7 @@ package com.jerboa.ui.components.post import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Arrangement @@ -990,7 +991,7 @@ fun PostTitleBlock( showIfRead: Boolean, blurNSFW: BlurNSFW, ) { - val imagePost = postView.post.url?.let { getPostType(it) == PostType.Image } ?: false + val imagePost = postView.post.url?.let { getPostType(it) == PostType.Image } == true if (imagePost && expandedImage) { PostTitleAndImageLink( diff --git a/app/src/main/java/com/jerboa/ui/components/post/PostListings.kt b/app/src/main/java/com/jerboa/ui/components/post/PostListings.kt index e55b4dc3f..8fe4e2b46 100644 --- a/app/src/main/java/com/jerboa/ui/components/post/PostListings.kt +++ b/app/src/main/java/com/jerboa/ui/components/post/PostListings.kt @@ -1,5 +1,7 @@ package com.jerboa.ui.components.post +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding @@ -16,6 +18,7 @@ import androidx.compose.ui.platform.testTag import androidx.compose.ui.tooling.preview.Preview import com.jerboa.JerboaAppState import com.jerboa.PostViewMode +import com.jerboa.SelectionVisibilityState import com.jerboa.datatypes.BanFromCommunityData import com.jerboa.datatypes.PostFeatureData import com.jerboa.datatypes.sampleLinkPostView @@ -29,7 +32,8 @@ import com.jerboa.feat.default import com.jerboa.rememberJerboaAppState import com.jerboa.ui.components.common.RetryLoadingPosts import com.jerboa.ui.components.common.TriggerWhenReachingEnd -import com.jerboa.ui.theme.SMALL_PADDING +import com.jerboa.ui.components.common.selectedItemContainerColor +import com.jerboa.ui.theme.MEDIUM_PADDING import it.vercruysse.lemmyapi.datatypes.Community import it.vercruysse.lemmyapi.datatypes.LocalUserVoteDisplayMode import it.vercruysse.lemmyapi.datatypes.Person @@ -82,6 +86,7 @@ fun PostListings( postActionBarMode: PostActionBarMode, showPostAppendRetry: Boolean, swipeToActionPreset: SwipeToActionPreset, + selectionState: SelectionVisibilityState, ) { LazyColumn( state = listState, @@ -97,58 +102,69 @@ fun PostListings( items = posts, contentType = { _, _ -> "Post" }, ) { index, postView -> - PostListing( - postView = postView, - admins = admins, - moderators = moderators, - useCustomTabs = useCustomTabs, - usePrivateTabs = usePrivateTabs, - onUpvoteClick = onUpvoteClick, - onDownvoteClick = onDownvoteClick, - onReplyClick = onReplyClick, - onPostClick = onPostClick, - onSaveClick = onSaveClick, - onCommunityClick = onCommunityClick, - onEditPostClick = onEditPostClick, - onDeletePostClick = onDeletePostClick, - onHidePostClick = onHidePostClick, - onReportClick = onReportClick, - onRemoveClick = onRemoveClick, - onBanPersonClick = onBanPersonClick, - onBanFromCommunityClick = onBanFromCommunityClick, - onLockPostClick = onLockPostClick, - onFeaturePostClick = onFeaturePostClick, - onViewVotesClick = onViewPostVotesClick, - onPersonClick = onPersonClick, - showCommunityName = showCommunityName, - fullBody = false, - account = account, - postViewMode = postViewMode, - showVotingArrowsInListView = showVotingArrowsInListView, - enableDownVotes = enableDownVotes, - showAvatar = showAvatar, - blurNSFW = blurNSFW, - appState = appState, - showPostLinkPreview = showPostLinkPreviews, - showIfRead = showIfRead, - voteDisplayMode = voteDisplayMode, - postActionBarMode = postActionBarMode, - swipeToActionPreset = swipeToActionPreset, - ).let { - if (!postView.read && markAsReadOnScroll) { - DisposableEffect(key1 = postView.post.id) { - onDispose { - if (listState.isScrollInProgress && index < listState.firstVisibleItemIndex) { - onMarkAsRead(postView) + + val selected = when (selectionState) { + is SelectionVisibilityState.ShowSelection -> selectionState.selectedItem == postView.post.id + SelectionVisibilityState.NoSelection -> false + } + + Column( + modifier = Modifier + .background(selectedItemContainerColor(selected)), + ) { + PostListing( + postView = postView, + admins = admins, + moderators = moderators, + useCustomTabs = useCustomTabs, + usePrivateTabs = usePrivateTabs, + onUpvoteClick = onUpvoteClick, + onDownvoteClick = onDownvoteClick, + onReplyClick = onReplyClick, + onPostClick = onPostClick, + onSaveClick = onSaveClick, + onCommunityClick = onCommunityClick, + onEditPostClick = onEditPostClick, + onDeletePostClick = onDeletePostClick, + onHidePostClick = onHidePostClick, + onReportClick = onReportClick, + onRemoveClick = onRemoveClick, + onBanPersonClick = onBanPersonClick, + onBanFromCommunityClick = onBanFromCommunityClick, + onLockPostClick = onLockPostClick, + onFeaturePostClick = onFeaturePostClick, + onViewVotesClick = onViewPostVotesClick, + onPersonClick = onPersonClick, + showCommunityName = showCommunityName, + fullBody = false, + account = account, + postViewMode = postViewMode, + showVotingArrowsInListView = showVotingArrowsInListView, + enableDownVotes = enableDownVotes, + showAvatar = showAvatar, + blurNSFW = blurNSFW, + appState = appState, + showPostLinkPreview = showPostLinkPreviews, + showIfRead = showIfRead, + voteDisplayMode = voteDisplayMode, + postActionBarMode = postActionBarMode, + swipeToActionPreset = swipeToActionPreset, + ).let { + if (!postView.read && markAsReadOnScroll) { + DisposableEffect(key1 = postView.post.id) { + onDispose { + if (listState.isScrollInProgress && index < listState.firstVisibleItemIndex) { + onMarkAsRead(postView) + } } } } } + HorizontalDivider( + modifier = Modifier.padding(top = MEDIUM_PADDING), + color = MaterialTheme.colorScheme.surfaceVariant, + ) } - HorizontalDivider( - modifier = Modifier.padding(vertical = SMALL_PADDING), - color = MaterialTheme.colorScheme.surfaceVariant, - ) } if (showPostAppendRetry) { @@ -204,5 +220,6 @@ fun PreviewPostListings() { showPostAppendRetry = false, swipeToActionPreset = SwipeToActionPreset.TwoSides, onReplyClick = {}, + selectionState = SelectionVisibilityState.NoSelection, ) } diff --git a/app/src/main/java/com/jerboa/ui/components/post/PostScreen.kt b/app/src/main/java/com/jerboa/ui/components/post/PostPane.kt similarity index 98% rename from app/src/main/java/com/jerboa/ui/components/post/PostScreen.kt rename to app/src/main/java/com/jerboa/ui/components/post/PostPane.kt index c0e851ded..13202f86b 100644 --- a/app/src/main/java/com/jerboa/ui/components/post/PostScreen.kt +++ b/app/src/main/java/com/jerboa/ui/components/post/PostPane.kt @@ -116,8 +116,8 @@ object PostViewReturn { ExperimentalComposeUiApi::class, ) @Composable -fun PostScreen( - id: Either, +fun PostPane( + postOrCommentId: Either, siteViewModel: SiteViewModel, accountViewModel: AccountViewModel, appState: JerboaAppState, @@ -133,13 +133,19 @@ fun PostScreen( postActionBarMode: PostActionBarMode, swipeToActionPreset: SwipeToActionPreset, ) { - Log.d("jerboa", "got to post screen") + Log.d("jerboa", "got to post pane") val ctx = LocalContext.current val account = getCurrentAccount(accountViewModel = accountViewModel) - val postViewModel: PostViewModel = viewModel(factory = PostViewModel.Companion.Factory(id)) + val postViewModel: PostViewModel = viewModel(factory = PostViewModel.Companion.Factory(postOrCommentId)) + + // Some hacky code required to update the screen for tablet view + // Necessary because the viewmodel initializes only once. + if (postViewModel.id !== postOrCommentId) { + postViewModel.reInitializeWithNewId(postOrCommentId) + } appState.ConsumeReturn(PostEditReturn.POST_VIEW, postViewModel::updatePost) appState.ConsumeReturn(PostRemoveReturn.POST_VIEW, postViewModel::updatePost) @@ -483,7 +489,7 @@ fun PostScreen( val commentTree = buildCommentsTree( commentsRes.data.comments, - id.fold( + postOrCommentId.fold( { null }, { it }, ), diff --git a/app/src/main/java/com/jerboa/util/BlurTransformation.kt b/app/src/main/java/com/jerboa/util/BlurTransformation.kt index 66ac5e5d6..a8fd9bee4 100644 --- a/app/src/main/java/com/jerboa/util/BlurTransformation.kt +++ b/app/src/main/java/com/jerboa/util/BlurTransformation.kt @@ -58,7 +58,7 @@ private suspend fun Bitmap.blur( val width = (sentBitmap.width * scale).roundToInt() val height = (sentBitmap.height * scale).roundToInt() sentBitmap = Bitmap.createScaledBitmap(sentBitmap, width, height, false) - val bitmap = sentBitmap.copy(sentBitmap.config, true) + val bitmap = sentBitmap.copy(sentBitmap.config!!, true) if (radius < 1) { return@withContext null } From fbecf47236e1e2b032cd5ea4333d3abd44d7273d Mon Sep 17 00:00:00 2001 From: Dessalines Date: Wed, 20 Nov 2024 11:19:10 -0500 Subject: [PATCH 2/4] Adding navigation bar scaffold. --- app/src/main/java/com/jerboa/MainActivity.kt | 4 +- .../jerboa/ui/components/common/AppBars.kt | 139 +++++------ .../{BottomNavScreen.kt => HomeNavScreen.kt} | 221 +++++++++--------- 3 files changed, 178 insertions(+), 186 deletions(-) rename app/src/main/java/com/jerboa/ui/components/home/{BottomNavScreen.kt => HomeNavScreen.kt} (57%) diff --git a/app/src/main/java/com/jerboa/MainActivity.kt b/app/src/main/java/com/jerboa/MainActivity.kt index a82405d7d..42758b0fb 100644 --- a/app/src/main/java/com/jerboa/MainActivity.kt +++ b/app/src/main/java/com/jerboa/MainActivity.kt @@ -57,7 +57,7 @@ import com.jerboa.ui.components.common.SwipeToNavigateBack import com.jerboa.ui.components.community.CommunityScreen import com.jerboa.ui.components.community.list.CommunityListScreen import com.jerboa.ui.components.community.sidebar.CommunitySidebarScreen -import com.jerboa.ui.components.home.BottomNavScreen +import com.jerboa.ui.components.home.HomeNavScreen import com.jerboa.ui.components.home.legal.SiteLegalScreen import com.jerboa.ui.components.home.sidebar.SiteSidebarScreen import com.jerboa.ui.components.imageviewer.ImageViewerScreen @@ -215,7 +215,7 @@ class MainActivity : AppCompatActivity() { } composable(route = Route.HOME) { - BottomNavScreen( + HomeNavScreen( appState = appState, accountViewModel = accountViewModel, siteViewModel = siteViewModel, diff --git a/app/src/main/java/com/jerboa/ui/components/common/AppBars.kt b/app/src/main/java/com/jerboa/ui/components/common/AppBars.kt index 5750e2d01..02bcbfe5e 100644 --- a/app/src/main/java/com/jerboa/ui/components/common/AppBars.kt +++ b/app/src/main/java/com/jerboa/ui/components/common/AppBars.kt @@ -1,7 +1,6 @@ package com.jerboa.ui.components.common import android.annotation.SuppressLint -import android.app.Activity import androidx.annotation.StringRes import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateFloatAsState @@ -26,8 +25,9 @@ import androidx.compose.material.icons.filled.KeyboardArrowDown import androidx.compose.material.icons.filled.KeyboardArrowUp import androidx.compose.material.icons.outlined.* import androidx.compose.material3.* +import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffold +import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScope import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment @@ -38,7 +38,6 @@ import androidx.compose.ui.draw.scale import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.testTag @@ -112,99 +111,87 @@ fun SimpleTopAppBarPreview() { } } -@Composable -fun BottomAppBarAll( +fun NavigationSuiteScope.adaptiveNavigationBar( selectedTab: NavTab, - onSelect: (NavTab) -> Unit, + onSelectTab: (NavTab) -> Unit, userViewType: UserViewType, unreadCounts: Long, unreadAppCount: Long?, unreadReportCount: Long?, showTextDescriptionsInNavbar: Boolean, ) { - // Check for preview mode - if (LocalContext.current is Activity) { - val window = (LocalContext.current as Activity).window - val colorScheme = MaterialTheme.colorScheme - - DisposableEffect(Unit) { - window.navigationBarColor = colorScheme.surfaceContainer.toArgb() - - onDispose { - window.navigationBarColor = colorScheme.background.toArgb() - } + for (tab in NavTab.getEntries(userViewType)) { + val selected = tab == selectedTab + val iconBadgeCount = when (tab) { + NavTab.Inbox -> unreadCounts + NavTab.RegistrationApplications -> unreadAppCount + NavTab.Reports -> unreadReportCount + else -> null } - } - // If descriptions are hidden, make the bar shorter - val modifier = if (showTextDescriptionsInNavbar) Modifier else Modifier.navigationBarsPadding().height(56.dp) - NavigationBar( - modifier = modifier, - ) { - for (tab in NavTab.getEntries(userViewType)) { - val selected = tab == selectedTab - val iconBadgeCount = when (tab) { - NavTab.Inbox -> unreadCounts - NavTab.RegistrationApplications -> unreadAppCount - NavTab.Reports -> unreadReportCount - else -> null - } - NavigationBarItem( - icon = { - NavbarIconAndBadge( - iconBadgeCount = iconBadgeCount, - icon = - if (selected) { - tab.iconFilled - } else { - tab.iconOutlined - }, - contentDescription = stringResource(tab.contentDescriptionId), + item( + icon = { + NavbarIconAndBadge( + iconBadgeCount = iconBadgeCount, + icon = + if (selected) { + tab.iconFilled + } else { + tab.iconOutlined + }, + contentDescription = stringResource(tab.contentDescriptionId), + ) + }, + label = { + if (showTextDescriptionsInNavbar) { + Text( + textAlign = TextAlign.Center, + fontSize = TextUnit(10f, TextUnitType.Sp), + text = stringResource(tab.textId), ) - }, - label = { - if (showTextDescriptionsInNavbar) { - Text( - textAlign = TextAlign.Center, - fontSize = TextUnit(10f, TextUnitType.Sp), - text = stringResource(tab.textId), - ) - } - }, - selected = selected, - onClick = { - onSelect(tab) - }, - ) - } + } + }, + selected = selected, + onClick = { + onSelectTab(tab) + }, + ) } } @Preview @Composable -fun BottomAppBarAllPreview() { - BottomAppBarAll( - selectedTab = NavTab.Home, - onSelect = {}, - unreadCounts = 30, - unreadAppCount = 2, - unreadReportCount = 8, - userViewType = UserViewType.AdminOnly, - showTextDescriptionsInNavbar = true, +fun AdaptiveNavigationBarPreview() { + NavigationSuiteScaffold( + navigationSuiteItems = { + adaptiveNavigationBar( + selectedTab = NavTab.Home, + onSelectTab = {}, + unreadCounts = 30, + unreadAppCount = 2, + unreadReportCount = 8, + userViewType = UserViewType.AdminOnly, + showTextDescriptionsInNavbar = true, + ) + }, ) } @Preview @Composable -fun BottomAppBarAllNoDescriptionsPreview() { - BottomAppBarAll( - selectedTab = NavTab.Home, - onSelect = {}, - unreadCounts = 30, - unreadAppCount = null, - unreadReportCount = null, - userViewType = UserViewType.Normal, - showTextDescriptionsInNavbar = false, +fun AdaptiveNavigationBarNoDescriptionsPreview() { + NavigationSuiteScaffold( + navigationSuiteItems = { + adaptiveNavigationBar( + selectedTab = NavTab.Home, + onSelectTab = {}, + unreadCounts = 30, + unreadAppCount = 2, + unreadReportCount = 8, + userViewType = UserViewType.AdminOnly, + showTextDescriptionsInNavbar = false, + ) + }, ) } diff --git a/app/src/main/java/com/jerboa/ui/components/home/BottomNavScreen.kt b/app/src/main/java/com/jerboa/ui/components/home/HomeNavScreen.kt similarity index 57% rename from app/src/main/java/com/jerboa/ui/components/home/BottomNavScreen.kt rename to app/src/main/java/com/jerboa/ui/components/home/HomeNavScreen.kt index a3637719c..91563575c 100644 --- a/app/src/main/java/com/jerboa/ui/components/home/BottomNavScreen.kt +++ b/app/src/main/java/com/jerboa/ui/components/home/HomeNavScreen.kt @@ -20,6 +20,7 @@ import androidx.compose.material3.ModalDrawerSheet import androidx.compose.material3.ModalNavigationDrawer import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffold import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue @@ -52,9 +53,9 @@ import com.jerboa.model.AppSettingsViewModel import com.jerboa.model.HomeViewModel import com.jerboa.model.SiteViewModel import com.jerboa.toEnum -import com.jerboa.ui.components.common.BottomAppBarAll import com.jerboa.ui.components.common.GuardAccount import com.jerboa.ui.components.common.JerboaSnackbarHost +import com.jerboa.ui.components.common.adaptiveNavigationBar import com.jerboa.ui.components.community.list.CommunityListScreen import com.jerboa.ui.components.drawer.MainDrawer import com.jerboa.ui.components.inbox.InboxScreen @@ -143,7 +144,7 @@ enum class NavTab( ExperimentalComposeUiApi::class, ) @Composable -fun BottomNavScreen( +fun HomeNavScreen( appState: JerboaAppState, accountViewModel: AccountViewModel, siteViewModel: SiteViewModel, @@ -221,127 +222,131 @@ fun BottomNavScreen( }, modifier = Modifier.semantics { testTagsAsResourceId = true }, content = { - Scaffold( - snackbarHost = { JerboaSnackbarHost(snackbarHostState) }, - bottomBar = { + NavigationSuiteScaffold( + navigationSuiteItems = { if (appSettings.showBottomNav && acc !== GuardAccount) { - BottomAppBarAll( + adaptiveNavigationBar( selectedTab = selectedTab, + userViewType = account.userViewType(), unreadCounts = siteViewModel.unreadCount, unreadAppCount = siteViewModel.unreadAppCount, unreadReportCount = siteViewModel.unreadReportCount, showTextDescriptionsInNavbar = appSettings.showTextDescriptionsInNavbar, - userViewType = account.userViewType(), - onSelect = onSelectTab, + onSelectTab = onSelectTab, ) } }, - ) { padding -> - NavHost( - navController = bottomNavController, - startDestination = NavTab.Home.name, - ) { - composable(route = NavTab.Home.name) { - HomeAndPostDetailScreen( - appState = appState, - homeViewModel = homeViewModel, - accountViewModel = accountViewModel, - siteViewModel = siteViewModel, - appSettingsViewModel = appSettingsViewModel, - appSettings = appSettings, - drawerState = drawerState, - padding = padding, - ) - } + ) { + Scaffold( + snackbarHost = { JerboaSnackbarHost(snackbarHostState) }, + content = { padding -> + NavHost( + navController = bottomNavController, + startDestination = NavTab.Home.name, + ) { + composable(route = NavTab.Home.name) { + HomeAndPostDetailScreen( + appState = appState, + homeViewModel = homeViewModel, + accountViewModel = accountViewModel, + siteViewModel = siteViewModel, + appSettingsViewModel = appSettingsViewModel, + appSettings = appSettings, + drawerState = drawerState, + padding = padding, + ) + } - composable(route = NavTab.Search.name) { - CommunityListScreen( - appState = appState, - selectMode = false, - followList = siteViewModel.getFollowList(), - blurNSFW = appSettings.blurNSFW.toEnum(), - drawerState = drawerState, - showAvatar = siteViewModel.showAvatar(), - padding = padding, - ) - } + composable(route = NavTab.Search.name) { + CommunityListScreen( + appState = appState, + selectMode = false, + followList = siteViewModel.getFollowList(), + blurNSFW = appSettings.blurNSFW.toEnum(), + drawerState = drawerState, + showAvatar = siteViewModel.showAvatar(), + padding = padding, + ) + } - composable(route = NavTab.Inbox.name) { - InboxScreen( - appState = appState, - accountViewModel = accountViewModel, - siteViewModel = siteViewModel, - blurNSFW = appSettings.blurNSFW.toEnum(), - drawerState = drawerState, - padding = padding, - ) - } + composable(route = NavTab.Inbox.name) { + InboxScreen( + appState = appState, + accountViewModel = accountViewModel, + siteViewModel = siteViewModel, + blurNSFW = appSettings.blurNSFW.toEnum(), + drawerState = drawerState, + padding = padding, + ) + } - composable(route = NavTab.RegistrationApplications.name) { - RegistrationApplicationsScreen( - appState = appState, - accountViewModel = accountViewModel, - siteViewModel = siteViewModel, - drawerState = drawerState, - padding = padding, - ) - } + composable(route = NavTab.RegistrationApplications.name) { + RegistrationApplicationsScreen( + appState = appState, + accountViewModel = accountViewModel, + siteViewModel = siteViewModel, + drawerState = drawerState, + padding = padding, + ) + } - composable(route = NavTab.Reports.name) { - ReportsScreen( - appState = appState, - accountViewModel = accountViewModel, - siteViewModel = siteViewModel, - drawerState = drawerState, - blurNSFW = appSettings.blurNSFW.toEnum(), - padding = padding, - ) - } + composable(route = NavTab.Reports.name) { + ReportsScreen( + appState = appState, + accountViewModel = accountViewModel, + siteViewModel = siteViewModel, + drawerState = drawerState, + blurNSFW = appSettings.blurNSFW.toEnum(), + padding = padding, + ) + } - composable(route = NavTab.Saved.name) { - PersonProfileScreen( - personArg = Either.Left(account.id), - savedMode = true, - appState = appState, - accountViewModel = accountViewModel, - appSettingsViewModel = appSettingsViewModel, - showVotingArrowsInListView = appSettings.showVotingArrowsInListView, - siteViewModel = siteViewModel, - useCustomTabs = appSettings.useCustomTabs, - usePrivateTabs = appSettings.usePrivateTabs, - blurNSFW = appSettings.blurNSFW.toEnum(), - showPostLinkPreviews = appSettings.showPostLinkPreviews, - drawerState = drawerState, - onBack = null, - markAsReadOnScroll = appSettings.markAsReadOnScroll, - postActionBarMode = appSettings.postActionBarMode.toEnum(), - swipeToActionPreset = appSettings.swipeToActionPreset.toEnum(), - padding = padding, - ) - } + composable(route = NavTab.Saved.name) { + PersonProfileScreen( + personArg = Either.Left(account.id), + savedMode = true, + appState = appState, + accountViewModel = accountViewModel, + appSettingsViewModel = appSettingsViewModel, + showVotingArrowsInListView = appSettings.showVotingArrowsInListView, + siteViewModel = siteViewModel, + useCustomTabs = appSettings.useCustomTabs, + usePrivateTabs = appSettings.usePrivateTabs, + blurNSFW = appSettings.blurNSFW.toEnum(), + showPostLinkPreviews = appSettings.showPostLinkPreviews, + drawerState = drawerState, + onBack = null, + markAsReadOnScroll = appSettings.markAsReadOnScroll, + postActionBarMode = appSettings.postActionBarMode.toEnum(), + swipeToActionPreset = appSettings.swipeToActionPreset.toEnum(), + padding = padding, + ) + } - composable(route = NavTab.Profile.name) { - PersonProfileScreen( - personArg = Either.Left(account.id), - savedMode = false, - appState = appState, - accountViewModel = accountViewModel, - appSettingsViewModel = appSettingsViewModel, - showVotingArrowsInListView = appSettings.showVotingArrowsInListView, - siteViewModel = siteViewModel, - useCustomTabs = appSettings.useCustomTabs, - usePrivateTabs = appSettings.usePrivateTabs, - blurNSFW = appSettings.blurNSFW.toEnum(), - showPostLinkPreviews = appSettings.showPostLinkPreviews, - drawerState = drawerState, - onBack = null, - markAsReadOnScroll = appSettings.markAsReadOnScroll, - postActionBarMode = appSettings.postActionBarMode.toEnum(), - swipeToActionPreset = appSettings.swipeToActionPreset.toEnum(), - padding = padding, - ) - } - } + composable(route = NavTab.Profile.name) { + PersonProfileScreen( + personArg = Either.Left(account.id), + savedMode = false, + appState = appState, + accountViewModel = accountViewModel, + appSettingsViewModel = appSettingsViewModel, + showVotingArrowsInListView = appSettings.showVotingArrowsInListView, + siteViewModel = siteViewModel, + useCustomTabs = appSettings.useCustomTabs, + usePrivateTabs = appSettings.usePrivateTabs, + blurNSFW = appSettings.blurNSFW.toEnum(), + showPostLinkPreviews = appSettings.showPostLinkPreviews, + drawerState = drawerState, + onBack = null, + markAsReadOnScroll = appSettings.markAsReadOnScroll, + postActionBarMode = appSettings.postActionBarMode.toEnum(), + swipeToActionPreset = appSettings.swipeToActionPreset.toEnum(), + padding = padding, + ) + } + } + }, + ) } }, ) From 78b4915693f9016ce6dc4b8c3f19e7681b0c373b Mon Sep 17 00:00:00 2001 From: Dessalines Date: Wed, 20 Nov 2024 13:15:36 -0500 Subject: [PATCH 3/4] Fixing back button. --- app/src/main/java/com/jerboa/MainActivity.kt | 2 ++ .../jerboa/ui/components/home/HomeAndPostDetailScreen.kt | 6 ++++++ app/src/main/java/com/jerboa/ui/components/post/PostPane.kt | 3 ++- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/jerboa/MainActivity.kt b/app/src/main/java/com/jerboa/MainActivity.kt index 42758b0fb..bf5274989 100644 --- a/app/src/main/java/com/jerboa/MainActivity.kt +++ b/app/src/main/java/com/jerboa/MainActivity.kt @@ -522,6 +522,7 @@ class MainActivity : AppCompatActivity() { showPostLinkPreview = appSettings.showPostLinkPreviews, postActionBarMode = appSettings.postActionBarMode.toEnum(), swipeToActionPreset = appSettings.swipeToActionPreset.toEnum(), + onClickBack = appState::popBackStack, ) } } @@ -556,6 +557,7 @@ class MainActivity : AppCompatActivity() { showPostLinkPreview = appSettings.showPostLinkPreviews, postActionBarMode = appSettings.postActionBarMode.toEnum(), swipeToActionPreset = appSettings.swipeToActionPreset.toEnum(), + onClickBack = appState::popBackStack, ) } diff --git a/app/src/main/java/com/jerboa/ui/components/home/HomeAndPostDetailScreen.kt b/app/src/main/java/com/jerboa/ui/components/home/HomeAndPostDetailScreen.kt index 277aa5566..bac4fdfac 100644 --- a/app/src/main/java/com/jerboa/ui/components/home/HomeAndPostDetailScreen.kt +++ b/app/src/main/java/com/jerboa/ui/components/home/HomeAndPostDetailScreen.kt @@ -139,6 +139,12 @@ fun HomeAndPostDetailScreen( showPostLinkPreview = appSettings.showPostLinkPreviews, postActionBarMode = appSettings.postActionBarMode.toEnum(), swipeToActionPreset = appSettings.swipeToActionPreset.toEnum(), + onClickBack = { + scope.launch { + selectedPostId = null + navigator.navigateBack() + } + }, ) } } diff --git a/app/src/main/java/com/jerboa/ui/components/post/PostPane.kt b/app/src/main/java/com/jerboa/ui/components/post/PostPane.kt index 13202f86b..c36a84be7 100644 --- a/app/src/main/java/com/jerboa/ui/components/post/PostPane.kt +++ b/app/src/main/java/com/jerboa/ui/components/post/PostPane.kt @@ -132,6 +132,7 @@ fun PostPane( showPostLinkPreview: Boolean, postActionBarMode: PostActionBarMode, swipeToActionPreset: SwipeToActionPreset, + onClickBack: () -> Unit, ) { Log.d("jerboa", "got to post pane") @@ -232,7 +233,7 @@ fun PostPane( navigationIcon = { IconButton( modifier = Modifier.testTag("jerboa:back"), - onClick = appState::navigateUp, + onClick = onClickBack, ) { Icon( Icons.AutoMirrored.Outlined.ArrowBack, From e6546ccd2c6cd8ad2aa7e6c641def1f2c149c8fd Mon Sep 17 00:00:00 2001 From: Dessalines Date: Wed, 20 Nov 2024 17:03:38 -0500 Subject: [PATCH 4/4] Updating to stable compose bom. --- app/build.gradle.kts | 7 +------ .../java/com/jerboa/ui/components/common/AccountHelpers.kt | 4 +++- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index aa88e6a74..b80fb4a9a 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -106,13 +106,8 @@ dependencies { // Exporting / importing DB helper implementation("com.github.dessalines:room-db-export-import:0.1.0") - // Unfortunately, ui tooling, and the markdown thing, still brings in the other material2 dependencies - // This is the "official" composeBom, but it breaks the imageviewer until 1.7 is released. See: - // https://github.com/LemmyNet/jerboa/pull/1502#issuecomment-2137935525 - // val composeBom = platform("androidx.compose:compose-bom:2024.05.00") - // Compose BOM - val composeBom = platform("dev.chrisbanes.compose:compose-bom:2024.08.00-alpha02") + val composeBom = platform("androidx.compose:compose-bom:2024.11.00") api(composeBom) implementation("androidx.activity:activity-ktx") implementation("androidx.activity:activity-compose") diff --git a/app/src/main/java/com/jerboa/ui/components/common/AccountHelpers.kt b/app/src/main/java/com/jerboa/ui/components/common/AccountHelpers.kt index 68538e3b2..a4e4437ad 100644 --- a/app/src/main/java/com/jerboa/ui/components/common/AccountHelpers.kt +++ b/app/src/main/java/com/jerboa/ui/components/common/AccountHelpers.kt @@ -27,7 +27,9 @@ fun getCurrentAccount(accountViewModel: AccountViewModel): Account { return acc } -// TODO why is this necessary when .toEnum() exists? +/** + * A special case of toEnumSafe, where the int still exists in the DB. + */ fun getPostViewMode(appSettingsViewModel: AppSettingsViewModel): PostViewMode = getEnumFromIntSetting(appSettingsViewModel.appSettings) { it.postViewMode