diff --git a/app/build.gradle b/app/build.gradle index 38bab13a9..a09a186a5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -85,6 +85,7 @@ dependencies { implementation 'com.google.accompanist:accompanist-pager:0.30.1' implementation 'com.google.accompanist:accompanist-pager-indicators:0.30.1' implementation 'com.google.accompanist:accompanist-flowlayout:0.30.1' + implementation "com.google.accompanist:accompanist-navigation-animation:0.30.1" // LiveData implementation 'androidx.compose.runtime:runtime-livedata:1.4.3' diff --git a/app/src/main/java/com/jerboa/MainActivity.kt b/app/src/main/java/com/jerboa/MainActivity.kt index 02d31b274..8f24650b6 100644 --- a/app/src/main/java/com/jerboa/MainActivity.kt +++ b/app/src/main/java/com/jerboa/MainActivity.kt @@ -1,27 +1,18 @@ package com.jerboa import android.app.Application -import android.content.Intent import android.graphics.Color import android.graphics.drawable.ColorDrawable -import android.net.Uri -import android.os.Build import android.os.Bundle -import android.util.Patterns import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.viewModels -import androidx.compose.runtime.LaunchedEffect +import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState import androidx.compose.ui.platform.LocalContext -import androidx.navigation.NavType -import androidx.navigation.compose.NavHost -import androidx.navigation.compose.composable -import androidx.navigation.compose.rememberNavController -import androidx.navigation.navArgument -import androidx.navigation.navDeepLink -import arrow.core.Either +import com.google.accompanist.navigation.animation.AnimatedNavHost +import com.google.accompanist.navigation.animation.rememberAnimatedNavController import com.jerboa.db.AccountRepository import com.jerboa.db.AccountViewModel import com.jerboa.db.AccountViewModelFactory @@ -29,42 +20,52 @@ import com.jerboa.db.AppDB import com.jerboa.db.AppSettingsRepository import com.jerboa.db.AppSettingsViewModel import com.jerboa.db.AppSettingsViewModelFactory -import com.jerboa.ui.components.comment.edit.CommentEditActivity +import com.jerboa.nav.aboutScreen +import com.jerboa.nav.accountSettingsScreen +import com.jerboa.nav.commentEditScreen +import com.jerboa.nav.commentReplyScreen +import com.jerboa.nav.commentReportScreen +import com.jerboa.nav.commentScreen +import com.jerboa.nav.communityListScreen +import com.jerboa.nav.communityScreen +import com.jerboa.nav.communityScreenFromUrl +import com.jerboa.nav.communitySideBarScreen +import com.jerboa.nav.createPostScreen +import com.jerboa.nav.defaultEnterTransition +import com.jerboa.nav.defaultExitTransition +import com.jerboa.nav.defaultPopEnterTransition +import com.jerboa.nav.defaultPopExitTransition +import com.jerboa.nav.homeRoutePattern +import com.jerboa.nav.homeScreen +import com.jerboa.nav.inboxScreen +import com.jerboa.nav.loginScreen +import com.jerboa.nav.lookAndFeelScreen +import com.jerboa.nav.postEditScreen +import com.jerboa.nav.postReportScreen +import com.jerboa.nav.postScreen +import com.jerboa.nav.privateMessageReplyScreen +import com.jerboa.nav.profileScreen +import com.jerboa.nav.profileScreenFromUrl +import com.jerboa.nav.settingsScreen +import com.jerboa.nav.siteSideBarScreen import com.jerboa.ui.components.comment.edit.CommentEditViewModel -import com.jerboa.ui.components.comment.reply.CommentReplyActivity import com.jerboa.ui.components.comment.reply.CommentReplyViewModel import com.jerboa.ui.components.common.MarkdownHelper import com.jerboa.ui.components.common.ShowChangelog import com.jerboa.ui.components.common.getCurrentAccount import com.jerboa.ui.components.common.getCurrentAccountSync -import com.jerboa.ui.components.community.CommunityActivity import com.jerboa.ui.components.community.CommunityViewModel -import com.jerboa.ui.components.community.list.CommunityListActivity import com.jerboa.ui.components.community.list.CommunityListViewModel -import com.jerboa.ui.components.community.sidebar.CommunitySidebarActivity import com.jerboa.ui.components.home.* -import com.jerboa.ui.components.inbox.InboxActivity import com.jerboa.ui.components.inbox.InboxViewModel -import com.jerboa.ui.components.login.LoginActivity import com.jerboa.ui.components.login.LoginViewModel -import com.jerboa.ui.components.person.PersonProfileActivity import com.jerboa.ui.components.person.PersonProfileViewModel -import com.jerboa.ui.components.post.PostActivity import com.jerboa.ui.components.post.PostViewModel -import com.jerboa.ui.components.post.create.CreatePostActivity import com.jerboa.ui.components.post.create.CreatePostViewModel -import com.jerboa.ui.components.post.edit.PostEditActivity import com.jerboa.ui.components.post.edit.PostEditViewModel -import com.jerboa.ui.components.privatemessage.PrivateMessageReplyActivity import com.jerboa.ui.components.report.CreateReportViewModel -import com.jerboa.ui.components.report.comment.CreateCommentReportActivity -import com.jerboa.ui.components.report.post.CreatePostReportActivity -import com.jerboa.ui.components.settings.SettingsActivity -import com.jerboa.ui.components.settings.about.AboutActivity -import com.jerboa.ui.components.settings.account.AccountSettingsActivity import com.jerboa.ui.components.settings.account.AccountSettingsViewModel import com.jerboa.ui.components.settings.account.AccountSettingsViewModelFactory -import com.jerboa.ui.components.settings.lookandfeel.LookAndFeelActivity import com.jerboa.ui.theme.JerboaTheme class JerboaApplication : Application() { @@ -88,7 +89,7 @@ class MainActivity : ComponentActivity() { private val commentEditViewModel by viewModels() private val postEditViewModel by viewModels() private val createReportViewModel by viewModels() - private val accountSettingsViewModel by viewModels() { + private val accountSettingsViewModel by viewModels { AccountSettingsViewModelFactory((application as JerboaApplication).accountRepository) } private val accountViewModel: AccountViewModel by viewModels { @@ -98,10 +99,15 @@ class MainActivity : ComponentActivity() { AppSettingsViewModelFactory((application as JerboaApplication).appSettingsRepository) } + @OptIn(ExperimentalAnimationApi::class) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - MarkdownHelper.init(this, appSettingsViewModel.appSettings.value?.useCustomTabs ?: true, appSettingsViewModel.appSettings.value?.usePrivateTabs ?: false) + MarkdownHelper.init( + this, + appSettingsViewModel.appSettings.value?.useCustomTabs ?: true, + appSettingsViewModel.appSettings.value?.usePrivateTabs ?: false + ) window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) val accountSync = getCurrentAccountSync(accountViewModel) @@ -114,503 +120,226 @@ class MainActivity : ComponentActivity() { JerboaTheme( appSettings = appSettings, ) { - val navController = rememberNavController() + val navController = rememberAnimatedNavController() val ctx = LocalContext.current ShowChangelog(appSettingsViewModel = appSettingsViewModel) - NavHost( + AnimatedNavHost( navController = navController, - startDestination = "home", + startDestination = homeRoutePattern, + enterTransition = { defaultEnterTransition }, + exitTransition = { defaultExitTransition }, + popEnterTransition = { defaultPopEnterTransition }, + popExitTransition = { defaultPopExitTransition } ) { - composable( - route = "login", - deepLinks = DEFAULT_LEMMY_INSTANCES.map { instance -> - navDeepLink { uriPattern = "$instance/login" } - }, - ) { - LoginActivity( - navController = navController, - loginViewModel = loginViewModel, - accountViewModel = accountViewModel, - siteViewModel = siteViewModel, - homeViewModel = homeViewModel, - ) - } - composable( - route = "home", - ) { - HomeActivity( - navController = navController, - homeViewModel = homeViewModel, - accountViewModel = accountViewModel, - siteViewModel = siteViewModel, - postEditViewModel = postEditViewModel, - appSettingsViewModel = appSettingsViewModel, - showVotingArrowsInListView = appSettings?.showVotingArrowsInListView ?: true, - ) - } - composable( - route = "community/{id}", - arguments = listOf( - navArgument("id") { - type = NavType.IntType - }, - ), - ) { - LaunchedEffect(Unit) { - val communityId = it.arguments?.getInt("id")!! - val idOrName = Either.Left(communityId) - - communityViewModel.fetchCommunity( - idOrName = idOrName, - auth = account?.jwt, - ) - - communityViewModel.fetchPosts( - communityIdOrName = idOrName, - account = account, - clear = true, - ctx = ctx, - ) - } - - CommunityActivity( - navController = navController, - communityViewModel = communityViewModel, - accountViewModel = accountViewModel, - homeViewModel = homeViewModel, - postEditViewModel = postEditViewModel, - communityListViewModel = communityListViewModel, - appSettingsViewModel = appSettingsViewModel, - showVotingArrowsInListView = appSettings?.showVotingArrowsInListView ?: true, - siteViewModel = siteViewModel, - ) - } - // Only necessary for community deeplinks - composable( - route = "{instance}/c/{name}", - deepLinks = listOf( - navDeepLink { uriPattern = "{instance}/c/{name}" }, - ), - arguments = listOf( - navArgument("name") { - type = NavType.StringType - }, - navArgument("instance") { - type = NavType.StringType - }, - ), - ) { - LaunchedEffect(Unit) { - val name = it.arguments?.getString("name")!! - val instance = it.arguments?.getString("instance")!! - val idOrName = Either.Right("$name@$instance") - - communityViewModel.fetchCommunity( - idOrName = idOrName, - auth = account?.jwt, - ) - - communityViewModel.fetchPosts( - communityIdOrName = idOrName, - account = account, - clear = true, - ctx = ctx, - ) - } - - CommunityActivity( - navController = navController, - communityViewModel = communityViewModel, - communityListViewModel = communityListViewModel, - accountViewModel = accountViewModel, - homeViewModel = homeViewModel, - postEditViewModel = postEditViewModel, - appSettingsViewModel = appSettingsViewModel, - showVotingArrowsInListView = appSettings?.showVotingArrowsInListView ?: true, - siteViewModel = siteViewModel, - ) - } - composable( - route = "profile/{id}?saved={saved}", - arguments = listOf( - navArgument("id") { - type = NavType.IntType - }, - navArgument("saved") { - defaultValue = false - type = NavType.BoolType - }, - ), - ) { - val savedMode = it.arguments?.getBoolean("saved")!! - - LaunchedEffect(Unit) { - val personId = it.arguments?.getInt("id")!! - val idOrName = Either.Left(personId) - - personProfileViewModel.fetchPersonDetails( - idOrName = idOrName, - account = account, - clearPersonDetails = true, - clearPostsAndComments = true, - ctx = ctx, - changeSavedOnly = savedMode, - ) - } - - PersonProfileActivity( - savedMode = savedMode, - navController = navController, - personProfileViewModel = personProfileViewModel, - accountViewModel = accountViewModel, - homeViewModel = homeViewModel, - commentEditViewModel = commentEditViewModel, - commentReplyViewModel = commentReplyViewModel, - postEditViewModel = postEditViewModel, - appSettingsViewModel = appSettingsViewModel, - showVotingArrowsInListView = appSettings?.showVotingArrowsInListView ?: true, - siteViewModel = siteViewModel, - ) - } - // Necessary for deep links - composable( - route = "{instance}/u/{name}", - deepLinks = listOf( - navDeepLink { uriPattern = "{instance}/u/{name}" }, - ), - arguments = listOf( - navArgument("name") { - type = NavType.StringType - }, - navArgument("instance") { - type = NavType.StringType - }, - ), - ) { - LaunchedEffect(Unit) { - val name = it.arguments?.getString("name")!! - val instance = it.arguments?.getString("instance")!! - val idOrName = Either.Right("$name@$instance") - - personProfileViewModel.fetchPersonDetails( - idOrName = idOrName, - account = account, - clearPersonDetails = true, - clearPostsAndComments = true, - ctx = ctx, - ) - } - - PersonProfileActivity( - savedMode = false, - navController = navController, - personProfileViewModel = personProfileViewModel, - accountViewModel = accountViewModel, - homeViewModel = homeViewModel, - commentEditViewModel = commentEditViewModel, - commentReplyViewModel = commentReplyViewModel, - postEditViewModel = postEditViewModel, - appSettingsViewModel = appSettingsViewModel, - showVotingArrowsInListView = appSettings?.showVotingArrowsInListView ?: true, - siteViewModel = siteViewModel, - ) - } - composable( - route = "communityList?select={select}", - arguments = listOf( - navArgument("select") { - defaultValue = false - type = NavType.BoolType - }, - ), - ) { - // Whenever navigating here, reset the list with your followed communities - communityListViewModel.setCommunityListFromFollowed(siteViewModel) - - CommunityListActivity( - navController = navController, - accountViewModel = accountViewModel, - homeViewModel = homeViewModel, - appSettingsViewModel = appSettingsViewModel, - communityListViewModel = communityListViewModel, - selectMode = it.arguments?.getBoolean("select")!!, - ) - } - composable( - route = "createPost", - 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 - } else { - body = text - } - - CreatePostActivity( - navController = navController, - accountViewModel = accountViewModel, - createPostViewModel = createPostViewModel, - communityListViewModel = communityListViewModel, - _url = url, - _body = body, - _image = image, - ) - activity?.intent?.replaceExtras(Bundle()) - } - composable( - route = "inbox", - deepLinks = DEFAULT_LEMMY_INSTANCES.map { instance -> - navDeepLink { uriPattern = "$instance/inbox" } - }, - ) { - if (account != null) { - LaunchedEffect(Unit) { - inboxViewModel.fetchReplies( - account = account, - clear = true, - ctx = ctx, - ) - inboxViewModel.fetchPersonMentions( - account = account, - clear = true, - ctx = ctx, - ) - inboxViewModel.fetchPrivateMessages( - account = account, - clear = true, - ctx = ctx, - ) - } - } - - InboxActivity( - navController = navController, - appSettingsViewModel = appSettingsViewModel, - inboxViewModel = inboxViewModel, - accountViewModel = accountViewModel, - homeViewModel = homeViewModel, - commentReplyViewModel = commentReplyViewModel, - siteViewModel = siteViewModel, - ) - } - composable( - route = "post/{id}", - deepLinks = DEFAULT_LEMMY_INSTANCES.map { instance -> - navDeepLink { uriPattern = "$instance/post/{id}" } - }, - arguments = listOf( - navArgument("id") { - type = NavType.IntType - }, - ), - ) { - LaunchedEffect(Unit) { - val postId = it.arguments?.getInt("id")!! - postViewModel.fetchPost( - id = Either.Left(postId), - account = account, - clear = true, - ctx = ctx, - ) - } - PostActivity( - postViewModel = postViewModel, - accountViewModel = accountViewModel, - commentEditViewModel = commentEditViewModel, - commentReplyViewModel = commentReplyViewModel, - postEditViewModel = postEditViewModel, - navController = navController, - appSettingsViewModel = appSettingsViewModel, - showCollapsedCommentContent = appSettings?.showCollapsedCommentContent ?: false, - showActionBarByDefault = appSettings?.showCommentActionBarByDefault ?: false, - showVotingArrowsInListView = appSettings?.showVotingArrowsInListView ?: true, - siteViewModel = siteViewModel, - ) - } - composable( - route = "comment/{id}", - deepLinks = DEFAULT_LEMMY_INSTANCES.map { instance -> - navDeepLink { uriPattern = "$instance/comment/{id}" } - }, - arguments = listOf( - navArgument("id") { - type = NavType.IntType - }, - ), - ) { - LaunchedEffect(Unit) { - val commentId = it.arguments?.getInt("id")!! - postViewModel.fetchPost( - id = Either.Right(commentId), - account = account, - clear = true, - ctx = ctx, - ) - } - PostActivity( - postViewModel = postViewModel, - accountViewModel = accountViewModel, - commentEditViewModel = commentEditViewModel, - commentReplyViewModel = commentReplyViewModel, - postEditViewModel = postEditViewModel, - navController = navController, - appSettingsViewModel = appSettingsViewModel, - showCollapsedCommentContent = appSettings?.showCollapsedCommentContent ?: false, - showActionBarByDefault = appSettings?.showCommentActionBarByDefault ?: true, - showVotingArrowsInListView = appSettings?.showVotingArrowsInListView ?: true, - siteViewModel = siteViewModel, - ) - } - composable( - route = "commentReply", - ) { - CommentReplyActivity( - commentReplyViewModel = commentReplyViewModel, - postViewModel = postViewModel, - accountViewModel = accountViewModel, - personProfileViewModel = personProfileViewModel, - navController = navController, - siteViewModel = siteViewModel, - ) - } - composable( - route = "siteSidebar", - ) { - SiteSidebarActivity( - siteViewModel = siteViewModel, - navController = navController, - ) - } - composable( - route = "communitySidebar", - ) { - CommunitySidebarActivity( - communityViewModel = communityViewModel, - navController = navController, - ) - } - composable( - route = "commentEdit", - ) { - CommentEditActivity( - commentEditViewModel = commentEditViewModel, - accountViewModel = accountViewModel, - navController = navController, - personProfileViewModel = personProfileViewModel, - postViewModel = postViewModel, - ) - } - composable( - route = "postEdit", - ) { - PostEditActivity( - postEditViewModel = postEditViewModel, - communityViewModel = communityViewModel, - accountViewModel = accountViewModel, - navController = navController, - personProfileViewModel = personProfileViewModel, - postViewModel = postViewModel, - homeViewModel = homeViewModel, - ) - } - composable( - route = "privateMessageReply", - ) { - PrivateMessageReplyActivity( - inboxViewModel = inboxViewModel, - accountViewModel = accountViewModel, - navController = navController, - siteViewModel = siteViewModel, - ) - } - composable( - route = "commentReport/{id}", - arguments = listOf( - navArgument("id") { - type = NavType.IntType - }, - ), - ) { - createReportViewModel.setCommentId(it.arguments?.getInt("id")!!) - CreateCommentReportActivity( - createReportViewModel = createReportViewModel, - accountViewModel = accountViewModel, - navController = navController, - ) - } - composable( - route = "postReport/{id}", - arguments = listOf( - navArgument("id") { - type = NavType.IntType - }, - ), - ) { - createReportViewModel.setPostId(it.arguments?.getInt("id")!!) - CreatePostReportActivity( - createReportViewModel = createReportViewModel, - accountViewModel = accountViewModel, - navController = navController, - ) - } - composable( - route = "settings", - ) { - SettingsActivity( - navController = navController, - accountViewModel = accountViewModel, - ) - } - composable( - route = "lookAndFeel", - ) { - LookAndFeelActivity( - navController = navController, - appSettingsViewModel = appSettingsViewModel, - ) - } - composable( - route = "accountSettings", - deepLinks = DEFAULT_LEMMY_INSTANCES.map { instance -> - navDeepLink { uriPattern = "$instance/settings" } - }, - ) { - AccountSettingsActivity( - navController = navController, - accountViewModel = accountViewModel, - siteViewModel = siteViewModel, - accountSettingsViewModel = accountSettingsViewModel, - ) - } - composable( - route = "about", - ) { - AboutActivity( - navController = navController, - useCustomTabs = appSettings?.useCustomTabs ?: true, - usePrivateTabs = appSettings?.usePrivateTabs ?: false, - ) - } + loginScreen( + navController = navController, + loginViewModel = loginViewModel, + accountViewModel = accountViewModel, + siteViewModel = siteViewModel, + homeViewModel = homeViewModel, + ) + + homeScreen( + navController = navController, + homeViewModel = homeViewModel, + accountViewModel = accountViewModel, + siteViewModel = siteViewModel, + postEditViewModel = postEditViewModel, + appSettingsViewModel = appSettingsViewModel, + communityListViewModel = communityListViewModel, + inboxViewModel = inboxViewModel, + commentReplyViewModel = commentReplyViewModel, + personProfileViewModel = personProfileViewModel, + commentEditViewModel = commentEditViewModel, + appSettings = appSettings, + ) + + communityScreen( + navController = navController, + communityViewModel = communityViewModel, + accountViewModel = accountViewModel, + postEditViewModel = postEditViewModel, + communityListViewModel = communityListViewModel, + appSettingsViewModel = appSettingsViewModel, + siteViewModel = siteViewModel, + account = account, + appSettings = appSettings, + ctx = ctx, + ) + + communityScreenFromUrl( + navController = navController, + communityViewModel = communityViewModel, + accountViewModel = accountViewModel, + postEditViewModel = postEditViewModel, + communityListViewModel = communityListViewModel, + appSettingsViewModel = appSettingsViewModel, + siteViewModel = siteViewModel, + account = account, + appSettings = appSettings, + ctx = ctx, + ) + + profileScreen( + navController = navController, + personProfileViewModel = personProfileViewModel, + accountViewModel = accountViewModel, + commentEditViewModel = commentEditViewModel, + commentReplyViewModel = commentReplyViewModel, + postEditViewModel = postEditViewModel, + appSettingsViewModel = appSettingsViewModel, + siteViewModel = siteViewModel, + account = account, + appSettings = appSettings, + ) + + profileScreenFromUrl( + navController = navController, + personProfileViewModel = personProfileViewModel, + accountViewModel = accountViewModel, + commentEditViewModel = commentEditViewModel, + commentReplyViewModel = commentReplyViewModel, + postEditViewModel = postEditViewModel, + appSettingsViewModel = appSettingsViewModel, + siteViewModel = siteViewModel, + account = account, + appSettings = appSettings, + ctx = ctx, + ) + + communityListScreen( + navController = navController, + accountViewModel = accountViewModel, + communityListViewModel = communityListViewModel, + siteViewModel = siteViewModel, + ) + + createPostScreen( + navController = navController, + accountViewModel = accountViewModel, + createPostViewModel = createPostViewModel, + communityListViewModel = communityListViewModel, + ctx = ctx, + ) + + inboxScreen( + navController = navController, + inboxViewModel = inboxViewModel, + accountViewModel = accountViewModel, + homeViewModel = homeViewModel, + commentReplyViewModel = commentReplyViewModel, + siteViewModel = siteViewModel, + account = account, + ctx = ctx, + ) + + postScreen( + navController = navController, + postViewModel = postViewModel, + accountViewModel = accountViewModel, + commentEditViewModel = commentEditViewModel, + commentReplyViewModel = commentReplyViewModel, + postEditViewModel = postEditViewModel, + appSettingsViewModel = appSettingsViewModel, + siteViewModel = siteViewModel, + account = account, + appSettings = appSettings, + ctx = ctx, + ) + + commentScreen( + navController = navController, + postViewModel = postViewModel, + accountViewModel = accountViewModel, + commentEditViewModel = commentEditViewModel, + commentReplyViewModel = commentReplyViewModel, + postEditViewModel = postEditViewModel, + appSettingsViewModel = appSettingsViewModel, + siteViewModel = siteViewModel, + account = account, + appSettings = appSettings, + ctx = ctx, + ) + + commentReplyScreen( + commentReplyViewModel = commentReplyViewModel, + postViewModel = postViewModel, + accountViewModel = accountViewModel, + personProfileViewModel = personProfileViewModel, + navController = navController, + siteViewModel = siteViewModel, + ) + + siteSideBarScreen( + siteViewModel = siteViewModel, + navController = navController, + ) + + communitySideBarScreen( + communityViewModel = communityViewModel, + navController = navController, + ) + + commentEditScreen( + commentEditViewModel = commentEditViewModel, + accountViewModel = accountViewModel, + navController = navController, + personProfileViewModel = personProfileViewModel, + postViewModel = postViewModel, + ) + + postEditScreen( + postEditViewModel = postEditViewModel, + communityViewModel = communityViewModel, + accountViewModel = accountViewModel, + navController = navController, + personProfileViewModel = personProfileViewModel, + postViewModel = postViewModel, + homeViewModel = homeViewModel, + ) + + privateMessageReplyScreen( + inboxViewModel = inboxViewModel, + accountViewModel = accountViewModel, + navController = navController, + siteViewModel = siteViewModel, + ) + + commentReportScreen( + createReportViewModel = createReportViewModel, + accountViewModel = accountViewModel, + navController = navController, + ) + + postReportScreen( + createReportViewModel = createReportViewModel, + accountViewModel = accountViewModel, + navController = navController, + ) + + settingsScreen( + navController = navController, + accountViewModel = accountViewModel, + ) + + lookAndFeelScreen( + navController = navController, + appSettingsViewModel = appSettingsViewModel, + ) + + accountSettingsScreen( + navController = navController, + accountViewModel = accountViewModel, + siteViewModel = siteViewModel, + accountSettingsViewModel = accountSettingsViewModel, + ) + + aboutScreen( + navController = navController, + appSettings = appSettings, + ) } } } diff --git a/app/src/main/java/com/jerboa/nav/AboutNavigation.kt b/app/src/main/java/com/jerboa/nav/AboutNavigation.kt new file mode 100644 index 000000000..f7a391526 --- /dev/null +++ b/app/src/main/java/com/jerboa/nav/AboutNavigation.kt @@ -0,0 +1,26 @@ +package com.jerboa.nav + +import androidx.compose.animation.ExperimentalAnimationApi +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import com.google.accompanist.navigation.animation.composable +import com.jerboa.db.AppSettings +import com.jerboa.ui.components.settings.about.AboutActivity + +private const val aboutRoutePattern = "about" + +@OptIn(ExperimentalAnimationApi::class) +fun NavGraphBuilder.aboutScreen( + navController: NavController, + appSettings: AppSettings? +) { + composable( + route = aboutRoutePattern, + ) { + AboutActivity( + navController = navController, + useCustomTabs = appSettings?.useCustomTabs ?: true, + usePrivateTabs = appSettings?.usePrivateTabs ?: false, + ) + } +} diff --git a/app/src/main/java/com/jerboa/nav/AccountSettingsNavigation.kt b/app/src/main/java/com/jerboa/nav/AccountSettingsNavigation.kt new file mode 100644 index 000000000..6f5bc0a91 --- /dev/null +++ b/app/src/main/java/com/jerboa/nav/AccountSettingsNavigation.kt @@ -0,0 +1,36 @@ +package com.jerboa.nav + +import androidx.compose.animation.ExperimentalAnimationApi +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import androidx.navigation.navDeepLink +import com.google.accompanist.navigation.animation.composable +import com.jerboa.DEFAULT_LEMMY_INSTANCES +import com.jerboa.db.AccountViewModel +import com.jerboa.ui.components.home.SiteViewModel +import com.jerboa.ui.components.settings.account.AccountSettingsActivity +import com.jerboa.ui.components.settings.account.AccountSettingsViewModel + +private const val accountSettingsRoutePattern = "accountSettings" + +@OptIn(ExperimentalAnimationApi::class) +fun NavGraphBuilder.accountSettingsScreen( + navController: NavController, + accountSettingsViewModel: AccountSettingsViewModel, + accountViewModel: AccountViewModel, + siteViewModel: SiteViewModel, +) { + composable( + route = accountSettingsRoutePattern, + deepLinks = DEFAULT_LEMMY_INSTANCES.map { instance -> + navDeepLink { uriPattern = "${instance}/settings" } + }, + ) { + AccountSettingsActivity( + navController = navController, + accountViewModel = accountViewModel, + siteViewModel = siteViewModel, + accountSettingsViewModel = accountSettingsViewModel, + ) + } +} diff --git a/app/src/main/java/com/jerboa/nav/CommentEditNavigation.kt b/app/src/main/java/com/jerboa/nav/CommentEditNavigation.kt new file mode 100644 index 000000000..5a7eedc7c --- /dev/null +++ b/app/src/main/java/com/jerboa/nav/CommentEditNavigation.kt @@ -0,0 +1,34 @@ +package com.jerboa.nav + +import androidx.compose.animation.ExperimentalAnimationApi +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import com.google.accompanist.navigation.animation.composable +import com.jerboa.db.AccountViewModel +import com.jerboa.ui.components.comment.edit.CommentEditActivity +import com.jerboa.ui.components.comment.edit.CommentEditViewModel +import com.jerboa.ui.components.person.PersonProfileViewModel +import com.jerboa.ui.components.post.PostViewModel + +private const val commentEditRoutePattern = "commentEdit" + +@OptIn(ExperimentalAnimationApi::class) +fun NavGraphBuilder.commentEditScreen( + accountViewModel: AccountViewModel, + navController: NavController, + commentEditViewModel: CommentEditViewModel, + personProfileViewModel: PersonProfileViewModel, + postViewModel: PostViewModel, +) { + composable( + route = commentEditRoutePattern, + ) { + CommentEditActivity( + commentEditViewModel = commentEditViewModel, + accountViewModel = accountViewModel, + navController = navController, + personProfileViewModel = personProfileViewModel, + postViewModel = postViewModel, + ) + } +} diff --git a/app/src/main/java/com/jerboa/nav/CommentNavigation.kt b/app/src/main/java/com/jerboa/nav/CommentNavigation.kt new file mode 100644 index 000000000..524589d99 --- /dev/null +++ b/app/src/main/java/com/jerboa/nav/CommentNavigation.kt @@ -0,0 +1,88 @@ +package com.jerboa.nav + +import android.content.Context +import androidx.compose.animation.ExperimentalAnimationApi +import androidx.compose.runtime.LaunchedEffect +import androidx.navigation.NavBackStackEntry +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavType +import androidx.navigation.navArgument +import androidx.navigation.navDeepLink +import arrow.core.Either +import com.google.accompanist.navigation.animation.composable +import com.jerboa.DEFAULT_LEMMY_INSTANCES +import com.jerboa.db.Account +import com.jerboa.db.AccountViewModel +import com.jerboa.db.AppSettings +import com.jerboa.db.AppSettingsViewModel +import com.jerboa.ui.components.comment.edit.CommentEditViewModel +import com.jerboa.ui.components.comment.reply.CommentReplyViewModel +import com.jerboa.ui.components.home.SiteViewModel +import com.jerboa.ui.components.post.PostActivity +import com.jerboa.ui.components.post.PostViewModel +import com.jerboa.ui.components.post.edit.PostEditViewModel + +private class CommentArgs(val id: Int) { + constructor(navBackStackEntry: NavBackStackEntry) : + this(navBackStackEntry.arguments?.getInt(ID)!!) + + companion object { + const val ID = "id" + } +} + +private const val commentRoutePattern = "comment/{${CommentArgs.ID}}" + +@OptIn(ExperimentalAnimationApi::class) +fun NavGraphBuilder.commentScreen( + postViewModel: PostViewModel, + siteViewModel: SiteViewModel, + accountViewModel: AccountViewModel, + commentEditViewModel: CommentEditViewModel, + commentReplyViewModel: CommentReplyViewModel, + postEditViewModel: PostEditViewModel, + navController: NavController, + appSettingsViewModel: AppSettingsViewModel, + account: Account?, + appSettings: AppSettings?, + ctx: Context, +) { + composable( + route = commentRoutePattern, + deepLinks = DEFAULT_LEMMY_INSTANCES.map { instance -> + navDeepLink { uriPattern = "${instance}/${commentRoutePattern}" } + }, + arguments = listOf( + navArgument(CommentArgs.ID) { + type = NavType.IntType + }, + ), + ) { + LaunchedEffect(Unit) { + val args = CommentArgs(it) + postViewModel.fetchPost( + id = Either.Right(args.id), + account = account, + clear = true, + ctx = ctx, + ) + } + PostActivity( + navController = navController, + postViewModel = postViewModel, + accountViewModel = accountViewModel, + commentEditViewModel = commentEditViewModel, + commentReplyViewModel = commentReplyViewModel, + postEditViewModel = postEditViewModel, + appSettingsViewModel = appSettingsViewModel, + showCollapsedCommentContent = appSettings?.showCollapsedCommentContent + ?: false, + showActionBarByDefault = appSettings?.showCommentActionBarByDefault + ?: true, + showVotingArrowsInListView = appSettings?.showVotingArrowsInListView + ?: true, + siteViewModel = siteViewModel, + ) + } +} diff --git a/app/src/main/java/com/jerboa/nav/CommentReplyNavigation.kt b/app/src/main/java/com/jerboa/nav/CommentReplyNavigation.kt new file mode 100644 index 000000000..1d5b11d78 --- /dev/null +++ b/app/src/main/java/com/jerboa/nav/CommentReplyNavigation.kt @@ -0,0 +1,37 @@ +package com.jerboa.nav + +import androidx.compose.animation.ExperimentalAnimationApi +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import com.google.accompanist.navigation.animation.composable +import com.jerboa.db.AccountViewModel +import com.jerboa.ui.components.comment.reply.CommentReplyActivity +import com.jerboa.ui.components.comment.reply.CommentReplyViewModel +import com.jerboa.ui.components.home.SiteViewModel +import com.jerboa.ui.components.person.PersonProfileViewModel +import com.jerboa.ui.components.post.PostViewModel + +private const val commentReplyRoutePattern = "commentReply"; + +@OptIn(ExperimentalAnimationApi::class) +fun NavGraphBuilder.commentReplyScreen( + commentReplyViewModel: CommentReplyViewModel, + accountViewModel: AccountViewModel, + personProfileViewModel: PersonProfileViewModel, + postViewModel: PostViewModel, + navController: NavController, + siteViewModel: SiteViewModel, +) { + composable( + route = commentReplyRoutePattern, + ) { + CommentReplyActivity( + commentReplyViewModel = commentReplyViewModel, + postViewModel = postViewModel, + accountViewModel = accountViewModel, + personProfileViewModel = personProfileViewModel, + navController = navController, + siteViewModel = siteViewModel, + ) + } +} diff --git a/app/src/main/java/com/jerboa/nav/CommentReportNavigation.kt b/app/src/main/java/com/jerboa/nav/CommentReportNavigation.kt new file mode 100644 index 000000000..b99a44eff --- /dev/null +++ b/app/src/main/java/com/jerboa/nav/CommentReportNavigation.kt @@ -0,0 +1,47 @@ +package com.jerboa.nav + +import androidx.compose.animation.ExperimentalAnimationApi +import androidx.navigation.NavBackStackEntry +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavType +import androidx.navigation.navArgument +import com.google.accompanist.navigation.animation.composable +import com.jerboa.db.AccountViewModel +import com.jerboa.ui.components.report.CreateReportViewModel +import com.jerboa.ui.components.report.comment.CreateCommentReportActivity + +private class CommentReportArgs(val id: Int) { + constructor(navBackStackEntry: NavBackStackEntry) : + this(navBackStackEntry.arguments?.getInt(ID)!!) + + companion object { + const val ID = "id" + } +} + +private const val commentReportRoutePattern = "commentReport/{${CommentReportArgs.ID}}" + +@OptIn(ExperimentalAnimationApi::class) +fun NavGraphBuilder.commentReportScreen( + accountViewModel: AccountViewModel, + navController: NavController, + createReportViewModel: CreateReportViewModel, +) { + composable( + route = commentReportRoutePattern, + arguments = listOf( + navArgument(CommentReportArgs.ID) { + type = NavType.IntType + }, + ), + ) { + val args = CommentReportArgs(it) + createReportViewModel.setCommentId(args.id) + CreateCommentReportActivity( + createReportViewModel = createReportViewModel, + accountViewModel = accountViewModel, + navController = navController, + ) + } +} diff --git a/app/src/main/java/com/jerboa/nav/CommunityListNavigation.kt b/app/src/main/java/com/jerboa/nav/CommunityListNavigation.kt new file mode 100644 index 000000000..57c4897e0 --- /dev/null +++ b/app/src/main/java/com/jerboa/nav/CommunityListNavigation.kt @@ -0,0 +1,64 @@ +package com.jerboa.nav + +import androidx.compose.animation.ExperimentalAnimationApi +import androidx.navigation.NavBackStackEntry +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavType +import androidx.navigation.navArgument +import com.google.accompanist.navigation.animation.composable +import com.jerboa.db.AccountViewModel +import com.jerboa.ui.components.community.list.CommunityListActivity +import com.jerboa.ui.components.community.list.CommunityListViewModel +import com.jerboa.ui.components.home.SiteViewModel + +private class CommunityListArgs(val select: Boolean) { + constructor(navBackStackEntry: NavBackStackEntry) : + this(navBackStackEntry.arguments?.getBoolean(SELECT)!!) + + companion object { + const val SELECT = "select" + } +} + +private const val communityListRoutePattern = "communityList?select={${CommunityListArgs.SELECT}}" + +fun NavBackStackEntry.bottomNavIsSearch() = destination.route == communityListRoutePattern; + +@OptIn(ExperimentalAnimationApi::class) +fun NavGraphBuilder.communityListScreen( + navController: NavController, + communityListViewModel: CommunityListViewModel, + accountViewModel: AccountViewModel, + siteViewModel: SiteViewModel, +) { + composable( + route = communityListRoutePattern, + arguments = listOf( + navArgument(CommunityListArgs.SELECT) { + defaultValue = false + type = NavType.BoolType + }, + ), + ) { + val args = CommunityListArgs(it) + + // Whenever navigating here, reset the list with your followed communities + communityListViewModel.setCommunityListFromFollowed(siteViewModel) + + CommunityListActivity( + navController = navController, + accountViewModel = accountViewModel, + communityListViewModel = communityListViewModel, + selectMode = args.select, + ) + } +} + +fun NavController.bottomNavSelectSearch() { + navigate(communityListRoutePattern) { + launchSingleTop = true + popUpTo(0) + } +} + diff --git a/app/src/main/java/com/jerboa/nav/CommunityNavigation.kt b/app/src/main/java/com/jerboa/nav/CommunityNavigation.kt new file mode 100644 index 000000000..10c78006d --- /dev/null +++ b/app/src/main/java/com/jerboa/nav/CommunityNavigation.kt @@ -0,0 +1,85 @@ +package com.jerboa.nav + +import android.content.Context +import androidx.compose.animation.ExperimentalAnimationApi +import androidx.compose.runtime.LaunchedEffect +import androidx.navigation.NavBackStackEntry +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavType +import androidx.navigation.navArgument +import arrow.core.Either +import com.google.accompanist.navigation.animation.composable +import com.jerboa.db.Account +import com.jerboa.db.AccountViewModel +import com.jerboa.db.AppSettings +import com.jerboa.db.AppSettingsViewModel +import com.jerboa.ui.components.community.CommunityActivity +import com.jerboa.ui.components.community.CommunityViewModel +import com.jerboa.ui.components.community.list.CommunityListViewModel +import com.jerboa.ui.components.home.SiteViewModel +import com.jerboa.ui.components.post.edit.PostEditViewModel + +private class CommunityArgs(val id: Int) { + constructor(navBackStackEntry: NavBackStackEntry) : + this(id = navBackStackEntry.arguments?.getInt(ID)!!) + + companion object { + const val ID = "id" + } +} + +private const val communityRoutePattern = "community/{${CommunityArgs.ID}}" + +@OptIn(ExperimentalAnimationApi::class) +fun NavGraphBuilder.communityScreen( + navController: NavController, + communityViewModel: CommunityViewModel, + communityListViewModel: CommunityListViewModel, + accountViewModel: AccountViewModel, + postEditViewModel: PostEditViewModel, + appSettingsViewModel: AppSettingsViewModel, + siteViewModel: SiteViewModel, + account: Account?, + appSettings: AppSettings?, + ctx: Context, +) { + composable( + route = communityRoutePattern, + arguments = listOf( + navArgument(CommunityArgs.ID) { + type = NavType.IntType + }, + ), + ) { + LaunchedEffect(Unit) { + val args = CommunityArgs(it) + + val idOrName = Either.Left(args.id) + + communityViewModel.fetchCommunity( + idOrName = idOrName, + auth = account?.jwt, + ) + + communityViewModel.fetchPosts( + communityIdOrName = idOrName, + account = account, + clear = true, + ctx = ctx, + ) + } + + CommunityActivity( + navController = navController, + communityViewModel = communityViewModel, + accountViewModel = accountViewModel, + postEditViewModel = postEditViewModel, + communityListViewModel = communityListViewModel, + appSettingsViewModel = appSettingsViewModel, + showVotingArrowsInListView = appSettings?.showVotingArrowsInListView + ?: true, + siteViewModel = siteViewModel, + ) + } +} diff --git a/app/src/main/java/com/jerboa/nav/CommunityNavigationByUrl.kt b/app/src/main/java/com/jerboa/nav/CommunityNavigationByUrl.kt new file mode 100644 index 000000000..88c2a1f95 --- /dev/null +++ b/app/src/main/java/com/jerboa/nav/CommunityNavigationByUrl.kt @@ -0,0 +1,97 @@ +package com.jerboa.nav + +import android.content.Context +import androidx.compose.animation.ExperimentalAnimationApi +import androidx.compose.runtime.LaunchedEffect +import androidx.navigation.NavBackStackEntry +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavType +import androidx.navigation.navArgument +import androidx.navigation.navDeepLink +import arrow.core.Either +import com.google.accompanist.navigation.animation.composable +import com.jerboa.db.Account +import com.jerboa.db.AccountViewModel +import com.jerboa.db.AppSettings +import com.jerboa.db.AppSettingsViewModel +import com.jerboa.ui.components.community.CommunityActivity +import com.jerboa.ui.components.community.CommunityViewModel +import com.jerboa.ui.components.community.list.CommunityListViewModel +import com.jerboa.ui.components.home.SiteViewModel +import com.jerboa.ui.components.post.edit.PostEditViewModel + +private class CommunityByUrlArgs(val instance: String, val name: String) { + constructor(navBackStackEntry: NavBackStackEntry) : this( + instance = navBackStackEntry.arguments?.getString(INSTANCE)!!, + name = navBackStackEntry.arguments?.getString(NAME)!!, + ) + + companion object { + const val INSTANCE = "instance" + const val NAME = "name" + } +} + +private const val communityByUrlRoutePattern = + "{${CommunityByUrlArgs.INSTANCE}}/c/{${CommunityByUrlArgs.NAME}}" + +@OptIn(ExperimentalAnimationApi::class) +fun NavGraphBuilder.communityScreenFromUrl( + navController: NavController, + communityViewModel: CommunityViewModel, + communityListViewModel: CommunityListViewModel, + accountViewModel: AccountViewModel, + postEditViewModel: PostEditViewModel, + appSettingsViewModel: AppSettingsViewModel, + siteViewModel: SiteViewModel, + account: Account?, + appSettings: AppSettings?, + ctx: Context, +) { + // Only necessary for community deeplinks + composable( + route = communityByUrlRoutePattern, + deepLinks = listOf( + navDeepLink { uriPattern = communityByUrlRoutePattern }, + ), + arguments = listOf( + navArgument(CommunityByUrlArgs.INSTANCE) { + type = NavType.StringType + }, + navArgument(CommunityByUrlArgs.NAME) { + type = NavType.StringType + }, + ), + ) { + LaunchedEffect(Unit) { + val args = CommunityByUrlArgs(it) + + val idOrName = Either.Right("${args.name}@${args.instance}") + + communityViewModel.fetchCommunity( + idOrName = idOrName, + auth = account?.jwt, + ) + + communityViewModel.fetchPosts( + communityIdOrName = idOrName, + account = account, + clear = true, + ctx = ctx, + ) + } + + CommunityActivity( + navController = navController, + communityViewModel = communityViewModel, + communityListViewModel = communityListViewModel, + accountViewModel = accountViewModel, + postEditViewModel = postEditViewModel, + appSettingsViewModel = appSettingsViewModel, + showVotingArrowsInListView = appSettings?.showVotingArrowsInListView + ?: true, + siteViewModel = siteViewModel, + ) + } +} diff --git a/app/src/main/java/com/jerboa/nav/CommunitySideBarNavigation.kt b/app/src/main/java/com/jerboa/nav/CommunitySideBarNavigation.kt new file mode 100644 index 000000000..639577fbc --- /dev/null +++ b/app/src/main/java/com/jerboa/nav/CommunitySideBarNavigation.kt @@ -0,0 +1,25 @@ +package com.jerboa.nav + +import androidx.compose.animation.ExperimentalAnimationApi +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import com.google.accompanist.navigation.animation.composable +import com.jerboa.ui.components.community.CommunityViewModel +import com.jerboa.ui.components.community.sidebar.CommunitySidebarActivity + +private const val communitySideBarRoutePattern = "communitySidebar" + +@OptIn(ExperimentalAnimationApi::class) +fun NavGraphBuilder.communitySideBarScreen( + communityViewModel: CommunityViewModel, + navController: NavController, +) { + composable( + route = communitySideBarRoutePattern, + ) { + CommunitySidebarActivity( + communityViewModel = communityViewModel, + navController = navController, + ) + } +} diff --git a/app/src/main/java/com/jerboa/nav/CreatePostNavigation.kt b/app/src/main/java/com/jerboa/nav/CreatePostNavigation.kt new file mode 100644 index 000000000..2d075b931 --- /dev/null +++ b/app/src/main/java/com/jerboa/nav/CreatePostNavigation.kt @@ -0,0 +1,69 @@ +package com.jerboa.nav + +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.os.Bundle +import android.util.Patterns +import androidx.compose.animation.ExperimentalAnimationApi +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import androidx.navigation.navDeepLink +import com.google.accompanist.navigation.animation.composable +import com.jerboa.db.AccountViewModel +import com.jerboa.findActivity +import com.jerboa.ui.components.community.list.CommunityListViewModel +import com.jerboa.ui.components.post.create.CreatePostActivity +import com.jerboa.ui.components.post.create.CreatePostViewModel + +private const val createPostRoutePattern = "createPost"; + +@OptIn(ExperimentalAnimationApi::class) +fun NavGraphBuilder.createPostScreen( + navController: NavController, + accountViewModel: AccountViewModel, + createPostViewModel: CreatePostViewModel, + communityListViewModel: CommunityListViewModel, + ctx: Context, +) { + composable( + route = createPostRoutePattern, + 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 + } else { + body = text + } + + CreatePostActivity( + navController = navController, + accountViewModel = accountViewModel, + createPostViewModel = createPostViewModel, + communityListViewModel = communityListViewModel, + _url = url, + _body = body, + _image = image, + ) + activity?.intent?.replaceExtras(Bundle()) + } +} diff --git a/app/src/main/java/com/jerboa/nav/FeedNavigation.kt b/app/src/main/java/com/jerboa/nav/FeedNavigation.kt new file mode 100644 index 000000000..819870579 --- /dev/null +++ b/app/src/main/java/com/jerboa/nav/FeedNavigation.kt @@ -0,0 +1,54 @@ +package com.jerboa.nav + +import androidx.compose.animation.ExperimentalAnimationApi +import androidx.compose.material3.DrawerState +import androidx.navigation.NavBackStackEntry +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import com.google.accompanist.navigation.animation.composable +import com.jerboa.db.AccountViewModel +import com.jerboa.db.AppSettings +import com.jerboa.db.AppSettingsViewModel +import com.jerboa.ui.components.home.FeedActivity +import com.jerboa.ui.components.home.HomeViewModel +import com.jerboa.ui.components.home.SiteViewModel +import com.jerboa.ui.components.post.edit.PostEditViewModel + +private const val feedRoutePattern = "feed" + +fun bottomNavDefaultRoute() = feedRoutePattern; + +fun NavBackStackEntry.bottomNavIsHome(): Boolean = destination.route == feedRoutePattern + +@OptIn(ExperimentalAnimationApi::class) +fun NavGraphBuilder.feedScreen( + navController: NavController, + homeViewModel: HomeViewModel, + accountViewModel: AccountViewModel, + siteViewModel: SiteViewModel, + postEditViewModel: PostEditViewModel, + appSettingsViewModel: AppSettingsViewModel, + drawerState: DrawerState, + appSettings: AppSettings?, +) { + composable(feedRoutePattern) { + FeedActivity( + navController = navController, + homeViewModel = homeViewModel, + accountViewModel = accountViewModel, + siteViewModel = siteViewModel, + postEditViewModel = postEditViewModel, + appSettingsViewModel = appSettingsViewModel, + showVotingArrowsInListView = appSettings?.showVotingArrowsInListView + ?: true, + drawerState = drawerState, + ) + } +} + +fun NavController.bottomNavSelectHome() { + navigate(feedRoutePattern) { + launchSingleTop = true + popUpTo(0) + } +} diff --git a/app/src/main/java/com/jerboa/nav/HomeNavigation.kt b/app/src/main/java/com/jerboa/nav/HomeNavigation.kt new file mode 100644 index 000000000..5290eb36a --- /dev/null +++ b/app/src/main/java/com/jerboa/nav/HomeNavigation.kt @@ -0,0 +1,61 @@ +package com.jerboa.nav + +import androidx.compose.animation.ExperimentalAnimationApi +import androidx.compose.material3.DrawerState +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import com.google.accompanist.navigation.animation.composable +import com.jerboa.db.AccountViewModel +import com.jerboa.db.AppSettings +import com.jerboa.db.AppSettingsViewModel +import com.jerboa.ui.components.comment.edit.CommentEditViewModel +import com.jerboa.ui.components.comment.reply.CommentReplyViewModel +import com.jerboa.ui.components.community.list.CommunityListViewModel +import com.jerboa.ui.components.home.HomeViewModel +import com.jerboa.ui.components.home.HomeActivity +import com.jerboa.ui.components.home.SiteViewModel +import com.jerboa.ui.components.inbox.InboxViewModel +import com.jerboa.ui.components.person.PersonProfileViewModel +import com.jerboa.ui.components.post.edit.PostEditViewModel + +const val homeRoutePattern = "home" + +@OptIn(ExperimentalAnimationApi::class) +fun NavGraphBuilder.homeScreen( + navController: NavController, + homeViewModel: HomeViewModel, + accountViewModel: AccountViewModel, + siteViewModel: SiteViewModel, + postEditViewModel: PostEditViewModel, + appSettingsViewModel: AppSettingsViewModel, + communityListViewModel: CommunityListViewModel, + inboxViewModel: InboxViewModel, + commentReplyViewModel: CommentReplyViewModel, + personProfileViewModel: PersonProfileViewModel, + commentEditViewModel: CommentEditViewModel, + appSettings: AppSettings?, +) { + composable(route = homeRoutePattern) { + HomeActivity( + navController = navController, + homeViewModel = homeViewModel, + accountViewModel = accountViewModel, + siteViewModel = siteViewModel, + postEditViewModel = postEditViewModel, + appSettingsViewModel = appSettingsViewModel, + communityListViewModel = communityListViewModel, + inboxViewModel = inboxViewModel, + commentReplyViewModel = commentReplyViewModel, + personProfileViewModel = personProfileViewModel, + commentEditViewModel = commentEditViewModel, + showVotingArrowsInListView = appSettings?.showVotingArrowsInListView ?: true, + appSettings = appSettings, + ) + } +} + +fun NavController.loginSuccessGoToHome() { + navigate(homeRoutePattern) { + popUpTo(0) + } +} diff --git a/app/src/main/java/com/jerboa/nav/InboxNavigation.kt b/app/src/main/java/com/jerboa/nav/InboxNavigation.kt new file mode 100644 index 000000000..544f2844f --- /dev/null +++ b/app/src/main/java/com/jerboa/nav/InboxNavigation.kt @@ -0,0 +1,77 @@ +package com.jerboa.nav + +import android.content.Context +import androidx.compose.animation.ExperimentalAnimationApi +import androidx.compose.runtime.LaunchedEffect +import androidx.navigation.NavBackStackEntry +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import androidx.navigation.navDeepLink +import com.google.accompanist.navigation.animation.composable +import com.jerboa.DEFAULT_LEMMY_INSTANCES +import com.jerboa.db.Account +import com.jerboa.db.AccountViewModel +import com.jerboa.ui.components.comment.reply.CommentReplyViewModel +import com.jerboa.ui.components.home.HomeViewModel +import com.jerboa.ui.components.home.SiteViewModel +import com.jerboa.ui.components.inbox.InboxActivity +import com.jerboa.ui.components.inbox.InboxViewModel + +private const val inboxRoutePattern = "inbox" + +fun NavBackStackEntry.bottomNavIsInbox() = destination.route == inboxRoutePattern; + +@OptIn(ExperimentalAnimationApi::class) +fun NavGraphBuilder.inboxScreen( + navController: NavController, + inboxViewModel: InboxViewModel, + homeViewModel: HomeViewModel, + accountViewModel: AccountViewModel, + commentReplyViewModel: CommentReplyViewModel, + siteViewModel: SiteViewModel, + account: Account?, + ctx: Context, +) { + composable( + route = inboxRoutePattern, + deepLinks = DEFAULT_LEMMY_INSTANCES.map { instance -> + navDeepLink { uriPattern = "${instance}/${inboxRoutePattern}" } + }, + ) { + if (account != null) { + LaunchedEffect(Unit) { + inboxViewModel.fetchReplies( + account = account, + clear = true, + ctx = ctx, + ) + inboxViewModel.fetchPersonMentions( + account = account, + clear = true, + ctx = ctx, + ) + inboxViewModel.fetchPrivateMessages( + account = account, + clear = true, + ctx = ctx, + ) + } + } + + InboxActivity( + navController = navController, + inboxViewModel = inboxViewModel, + accountViewModel = accountViewModel, + homeViewModel = homeViewModel, + commentReplyViewModel = commentReplyViewModel, + siteViewModel = siteViewModel, + ) + } +} + +fun NavController.bottomNavSelectInbox() { + navigate(inboxRoutePattern) { + launchSingleTop = true + popUpTo(0) + } +} diff --git a/app/src/main/java/com/jerboa/nav/LoginNavigation.kt b/app/src/main/java/com/jerboa/nav/LoginNavigation.kt new file mode 100644 index 000000000..e711398c7 --- /dev/null +++ b/app/src/main/java/com/jerboa/nav/LoginNavigation.kt @@ -0,0 +1,43 @@ +package com.jerboa.nav + +import androidx.compose.animation.ExperimentalAnimationApi +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import androidx.navigation.navDeepLink +import com.google.accompanist.navigation.animation.composable +import com.jerboa.DEFAULT_LEMMY_INSTANCES +import com.jerboa.db.AccountViewModel +import com.jerboa.ui.components.home.HomeViewModel +import com.jerboa.ui.components.home.SiteViewModel +import com.jerboa.ui.components.login.LoginActivity +import com.jerboa.ui.components.login.LoginViewModel + +private const val loginRoutePattern = "login" + +@OptIn(ExperimentalAnimationApi::class) +fun NavGraphBuilder.loginScreen( + navController: NavController, + loginViewModel: LoginViewModel, + accountViewModel: AccountViewModel, + siteViewModel: SiteViewModel, + homeViewModel: HomeViewModel, +) { + composable( + route = loginRoutePattern, + deepLinks = DEFAULT_LEMMY_INSTANCES.map { instance -> + navDeepLink { uriPattern = "${instance}/${loginRoutePattern}" } + }, + ) { + LoginActivity( + navController = navController, + loginViewModel = loginViewModel, + accountViewModel = accountViewModel, + siteViewModel = siteViewModel, + homeViewModel = homeViewModel, + ) + } +} + +fun NavController.showLogin() { + navigate(loginRoutePattern) +} diff --git a/app/src/main/java/com/jerboa/nav/LookAndFeelNavigation.kt b/app/src/main/java/com/jerboa/nav/LookAndFeelNavigation.kt new file mode 100644 index 000000000..16deac551 --- /dev/null +++ b/app/src/main/java/com/jerboa/nav/LookAndFeelNavigation.kt @@ -0,0 +1,25 @@ +package com.jerboa.nav + +import androidx.compose.animation.ExperimentalAnimationApi +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import com.google.accompanist.navigation.animation.composable +import com.jerboa.db.AppSettingsViewModel +import com.jerboa.ui.components.settings.lookandfeel.LookAndFeelActivity + +private const val lookAndFeelRoutePattern = "lookAndFeel" + +@OptIn(ExperimentalAnimationApi::class) +fun NavGraphBuilder.lookAndFeelScreen( + navController: NavController, + appSettingsViewModel: AppSettingsViewModel, +) { + composable( + route = lookAndFeelRoutePattern, + ) { + LookAndFeelActivity( + navController = navController, + appSettingsViewModel = appSettingsViewModel, + ) + } +} diff --git a/app/src/main/java/com/jerboa/nav/PostEditNavigation.kt b/app/src/main/java/com/jerboa/nav/PostEditNavigation.kt new file mode 100644 index 000000000..1cfa114eb --- /dev/null +++ b/app/src/main/java/com/jerboa/nav/PostEditNavigation.kt @@ -0,0 +1,40 @@ +package com.jerboa.nav + +import androidx.compose.animation.ExperimentalAnimationApi +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import com.google.accompanist.navigation.animation.composable +import com.jerboa.db.AccountViewModel +import com.jerboa.ui.components.community.CommunityViewModel +import com.jerboa.ui.components.home.HomeViewModel +import com.jerboa.ui.components.person.PersonProfileViewModel +import com.jerboa.ui.components.post.PostViewModel +import com.jerboa.ui.components.post.edit.PostEditActivity +import com.jerboa.ui.components.post.edit.PostEditViewModel + +private const val postEditRoutePattern = "postEdit" + +@OptIn(ExperimentalAnimationApi::class) +fun NavGraphBuilder.postEditScreen( + accountViewModel: AccountViewModel, + postEditViewModel: PostEditViewModel, + navController: NavController, + postViewModel: PostViewModel, + personProfileViewModel: PersonProfileViewModel, + communityViewModel: CommunityViewModel, + homeViewModel: HomeViewModel, +) { + composable( + route = postEditRoutePattern, + ) { + PostEditActivity( + postEditViewModel = postEditViewModel, + communityViewModel = communityViewModel, + accountViewModel = accountViewModel, + navController = navController, + personProfileViewModel = personProfileViewModel, + postViewModel = postViewModel, + homeViewModel = homeViewModel, + ) + } +} diff --git a/app/src/main/java/com/jerboa/nav/PostNavigation.kt b/app/src/main/java/com/jerboa/nav/PostNavigation.kt new file mode 100644 index 000000000..40af8da4f --- /dev/null +++ b/app/src/main/java/com/jerboa/nav/PostNavigation.kt @@ -0,0 +1,88 @@ +package com.jerboa.nav + +import android.content.Context +import androidx.compose.animation.ExperimentalAnimationApi +import androidx.compose.runtime.LaunchedEffect +import androidx.navigation.NavBackStackEntry +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavType +import androidx.navigation.navArgument +import androidx.navigation.navDeepLink +import arrow.core.Either +import com.google.accompanist.navigation.animation.composable +import com.jerboa.DEFAULT_LEMMY_INSTANCES +import com.jerboa.db.Account +import com.jerboa.db.AccountViewModel +import com.jerboa.db.AppSettings +import com.jerboa.db.AppSettingsViewModel +import com.jerboa.ui.components.comment.edit.CommentEditViewModel +import com.jerboa.ui.components.comment.reply.CommentReplyViewModel +import com.jerboa.ui.components.home.SiteViewModel +import com.jerboa.ui.components.post.PostActivity +import com.jerboa.ui.components.post.PostViewModel +import com.jerboa.ui.components.post.edit.PostEditViewModel + +private class PostArgs(val id: Int) { + constructor(navBackStackEntry: NavBackStackEntry) : + this(navBackStackEntry.arguments?.getInt(ID)!!) + + companion object { + const val ID = "id" + } +} + +private const val postRoutePattern = "post/{${PostArgs.ID}}" + +@OptIn(ExperimentalAnimationApi::class) +fun NavGraphBuilder.postScreen( + navController: NavController, + postViewModel: PostViewModel, + siteViewModel: SiteViewModel, + accountViewModel: AccountViewModel, + commentEditViewModel: CommentEditViewModel, + commentReplyViewModel: CommentReplyViewModel, + postEditViewModel: PostEditViewModel, + appSettingsViewModel: AppSettingsViewModel, + account: Account?, + appSettings: AppSettings?, + ctx: Context, + ) { + composable( + route = postRoutePattern, + deepLinks = DEFAULT_LEMMY_INSTANCES.map { instance -> + navDeepLink { uriPattern = "$instance/${postRoutePattern}" } + }, + arguments = listOf( + navArgument(PostArgs.ID) { + type = NavType.IntType + }, + ), + ) { + LaunchedEffect(Unit) { + val args = PostArgs(it) + postViewModel.fetchPost( + id = Either.Left(args.id), + account = account, + clear = true, + ctx = ctx, + ) + } + PostActivity( + postViewModel = postViewModel, + accountViewModel = accountViewModel, + commentEditViewModel = commentEditViewModel, + commentReplyViewModel = commentReplyViewModel, + postEditViewModel = postEditViewModel, + appSettingsViewModel = appSettingsViewModel, + showCollapsedCommentContent = appSettings?.showCollapsedCommentContent + ?: false, + showActionBarByDefault = appSettings?.showCommentActionBarByDefault + ?: false, + showVotingArrowsInListView = appSettings?.showVotingArrowsInListView + ?: true, + siteViewModel = siteViewModel, + navController = navController + ) + } +} diff --git a/app/src/main/java/com/jerboa/nav/PostReportNavigation.kt b/app/src/main/java/com/jerboa/nav/PostReportNavigation.kt new file mode 100644 index 000000000..bc98eee9d --- /dev/null +++ b/app/src/main/java/com/jerboa/nav/PostReportNavigation.kt @@ -0,0 +1,47 @@ +package com.jerboa.nav + +import androidx.compose.animation.ExperimentalAnimationApi +import androidx.navigation.NavBackStackEntry +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavType +import androidx.navigation.navArgument +import com.google.accompanist.navigation.animation.composable +import com.jerboa.db.AccountViewModel +import com.jerboa.ui.components.report.CreateReportViewModel +import com.jerboa.ui.components.report.post.CreatePostReportActivity + +private class PostReportArgs(val id: Int) { + constructor(navBackStackEntry: NavBackStackEntry) : + this(navBackStackEntry.arguments?.getInt(ID)!!) + + companion object { + const val ID = "id" + } +} + +private const val postReportRoutePattern ="postReport/{${PostReportArgs.ID}}" + +@OptIn(ExperimentalAnimationApi::class) +fun NavGraphBuilder.postReportScreen( + accountViewModel: AccountViewModel, + navController: NavController, + createReportViewModel: CreateReportViewModel, +) { + composable( + route = postReportRoutePattern, + arguments = listOf( + navArgument(PostReportArgs.ID) { + type = NavType.IntType + }, + ), + ) { + val args = PostReportArgs(it) + createReportViewModel.setPostId(args.id) + CreatePostReportActivity( + createReportViewModel = createReportViewModel, + accountViewModel = accountViewModel, + navController = navController, + ) + } +} diff --git a/app/src/main/java/com/jerboa/nav/PrivateMessageReplyNavigation.kt b/app/src/main/java/com/jerboa/nav/PrivateMessageReplyNavigation.kt new file mode 100644 index 000000000..c14b1d406 --- /dev/null +++ b/app/src/main/java/com/jerboa/nav/PrivateMessageReplyNavigation.kt @@ -0,0 +1,31 @@ +package com.jerboa.nav + +import androidx.compose.animation.ExperimentalAnimationApi +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import com.google.accompanist.navigation.animation.composable +import com.jerboa.db.AccountViewModel +import com.jerboa.ui.components.home.SiteViewModel +import com.jerboa.ui.components.inbox.InboxViewModel +import com.jerboa.ui.components.privatemessage.PrivateMessageReplyActivity + +private const val privateMessageReplyRoutePattern ="privateMessageReply" + +@OptIn(ExperimentalAnimationApi::class) +fun NavGraphBuilder.privateMessageReplyScreen( + inboxViewModel: InboxViewModel, + accountViewModel: AccountViewModel, + navController: NavController, + siteViewModel: SiteViewModel, +) { + composable( + route = privateMessageReplyRoutePattern, + ) { + PrivateMessageReplyActivity( + inboxViewModel = inboxViewModel, + accountViewModel = accountViewModel, + navController = navController, + siteViewModel = siteViewModel, + ) + } +} diff --git a/app/src/main/java/com/jerboa/nav/ProfileNavigation.kt b/app/src/main/java/com/jerboa/nav/ProfileNavigation.kt new file mode 100644 index 000000000..5ba01f6ec --- /dev/null +++ b/app/src/main/java/com/jerboa/nav/ProfileNavigation.kt @@ -0,0 +1,116 @@ +package com.jerboa.nav + +import androidx.compose.animation.ExperimentalAnimationApi +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.ui.platform.LocalContext +import androidx.navigation.NavBackStackEntry +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavType +import androidx.navigation.navArgument +import arrow.core.Either +import com.google.accompanist.navigation.animation.composable +import com.jerboa.db.Account +import com.jerboa.db.AccountViewModel +import com.jerboa.db.AppSettings +import com.jerboa.db.AppSettingsViewModel +import com.jerboa.ui.components.comment.edit.CommentEditViewModel +import com.jerboa.ui.components.comment.reply.CommentReplyViewModel +import com.jerboa.ui.components.home.SiteViewModel +import com.jerboa.ui.components.person.PersonProfileActivity +import com.jerboa.ui.components.person.PersonProfileViewModel +import com.jerboa.ui.components.post.edit.PostEditViewModel + +private class ProfileArgs(val id: Int, val saved: Boolean) { + constructor(navBackStackEntry: NavBackStackEntry) : this( + id = navBackStackEntry.arguments?.getInt(ID)!!, + saved = navBackStackEntry.arguments?.getBoolean(SAVED)!! + ) + + companion object { + const val ID = "id" + const val SAVED = "saved" + } +} + +private const val profileRoutePattern = "profile/{${ProfileArgs.ID}}?saved={${ProfileArgs.SAVED}}" + +fun NavBackStackEntry.bottomNavIsProfile() = + destination.route == profileRoutePattern && !ProfileArgs(this).saved; + +fun NavBackStackEntry.bottomNavIsSaved() = + destination.route == profileRoutePattern && ProfileArgs(this).saved; + +fun NavController.bottomNavSelectSaved(profileId: Int) { + val route = "profile/${profileId}?saved=${true}" + navigate(route) { + launchSingleTop = true + popUpTo(0) + } +} + +fun NavController.bottomNavSelectProfile(profileId: Int) { + val route = "profile/${profileId}" + navigate(route) { + launchSingleTop = true + popUpTo(0) + } +} + +@OptIn(ExperimentalAnimationApi::class) +fun NavGraphBuilder.profileScreen( + navController: NavController, + personProfileViewModel: PersonProfileViewModel, + accountViewModel: AccountViewModel, + commentEditViewModel: CommentEditViewModel, + commentReplyViewModel: CommentReplyViewModel, + postEditViewModel: PostEditViewModel, + appSettingsViewModel: AppSettingsViewModel, + siteViewModel: SiteViewModel, + account: Account?, + appSettings: AppSettings?, +) { + composable( + route = profileRoutePattern, + arguments = listOf( + navArgument(ProfileArgs.ID) { + type = NavType.IntType + }, + navArgument(ProfileArgs.SAVED) { + defaultValue = false + type = NavType.BoolType + }, + ), + ) { + val args = ProfileArgs(it) + val ctx = LocalContext.current + + LaunchedEffect(Unit) { + val personId = args.id + + val idOrName = Either.Left(personId) + personProfileViewModel.fetchPersonDetails( + idOrName = idOrName, + account = account, + clearPersonDetails = true, + clearPostsAndComments = true, + ctx = ctx, + changeSavedOnly = args.saved, + ) + } + + PersonProfileActivity( + navController = navController, + savedMode = args.saved, + personProfileViewModel = personProfileViewModel, + accountViewModel = accountViewModel, + commentEditViewModel = commentEditViewModel, + commentReplyViewModel = commentReplyViewModel, + postEditViewModel = postEditViewModel, + appSettingsViewModel = appSettingsViewModel, + showVotingArrowsInListView = appSettings?.showVotingArrowsInListView + ?: true, + siteViewModel = siteViewModel, + ) + } +} diff --git a/app/src/main/java/com/jerboa/nav/ProfileNavigationByUrl.kt b/app/src/main/java/com/jerboa/nav/ProfileNavigationByUrl.kt new file mode 100644 index 000000000..be3077af4 --- /dev/null +++ b/app/src/main/java/com/jerboa/nav/ProfileNavigationByUrl.kt @@ -0,0 +1,98 @@ +package com.jerboa.nav + +import android.content.Context +import androidx.compose.animation.ExperimentalAnimationApi +import androidx.compose.runtime.LaunchedEffect +import androidx.navigation.NavBackStackEntry +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavType +import androidx.navigation.navArgument +import androidx.navigation.navDeepLink +import arrow.core.Either +import com.google.accompanist.navigation.animation.composable +import com.jerboa.db.Account +import com.jerboa.db.AccountViewModel +import com.jerboa.db.AppSettings +import com.jerboa.db.AppSettingsViewModel +import com.jerboa.ui.components.comment.edit.CommentEditViewModel +import com.jerboa.ui.components.comment.reply.CommentReplyViewModel +import com.jerboa.ui.components.community.CommunityActivity +import com.jerboa.ui.components.community.CommunityViewModel +import com.jerboa.ui.components.community.list.CommunityListViewModel +import com.jerboa.ui.components.home.SiteViewModel +import com.jerboa.ui.components.person.PersonProfileActivity +import com.jerboa.ui.components.person.PersonProfileViewModel +import com.jerboa.ui.components.post.edit.PostEditViewModel + +private class ProfileByUrlArgs(val instance: String, val name: String) { + constructor(navBackStackEntry: NavBackStackEntry) : this( + instance = navBackStackEntry.arguments?.getString(INSTANCE)!!, + name = navBackStackEntry.arguments?.getString(NAME)!!, + ) + + companion object { + const val INSTANCE = "instance" + const val NAME = "name" + } +} + +private const val profileByUrlRoutePattern = "{${ProfileByUrlArgs.INSTANCE}}/u/{${ProfileByUrlArgs.NAME}}" + +@OptIn(ExperimentalAnimationApi::class) +fun NavGraphBuilder.profileScreenFromUrl( + navController: NavController, + personProfileViewModel: PersonProfileViewModel, + accountViewModel: AccountViewModel, + commentEditViewModel: CommentEditViewModel, + commentReplyViewModel: CommentReplyViewModel, + postEditViewModel: PostEditViewModel, + appSettingsViewModel: AppSettingsViewModel, + siteViewModel: SiteViewModel, + account: Account?, + appSettings: AppSettings?, + ctx: Context, +) { + // Only necessary for community deeplinks + composable( + route = profileByUrlRoutePattern, + deepLinks = listOf( + navDeepLink { uriPattern = profileByUrlRoutePattern }, + ), + arguments = listOf( + navArgument(ProfileByUrlArgs.INSTANCE) { + type = NavType.StringType + }, + navArgument(ProfileByUrlArgs.NAME) { + type = NavType.StringType + }, + ), + ) { + LaunchedEffect(Unit) { + val args = ProfileByUrlArgs(it) + val idOrName = Either.Right("${args.name}@${args.instance}") + + personProfileViewModel.fetchPersonDetails( + idOrName = idOrName, + account = account, + clearPersonDetails = true, + clearPostsAndComments = true, + ctx = ctx, + ) + } + + PersonProfileActivity( + navController = navController, + savedMode = false, + personProfileViewModel = personProfileViewModel, + accountViewModel = accountViewModel, + commentEditViewModel = commentEditViewModel, + commentReplyViewModel = commentReplyViewModel, + postEditViewModel = postEditViewModel, + appSettingsViewModel = appSettingsViewModel, + showVotingArrowsInListView = appSettings?.showVotingArrowsInListView + ?: true, + siteViewModel = siteViewModel, + ) + } +} diff --git a/app/src/main/java/com/jerboa/nav/SettingsNavigation.kt b/app/src/main/java/com/jerboa/nav/SettingsNavigation.kt new file mode 100644 index 000000000..9d9bfc4ac --- /dev/null +++ b/app/src/main/java/com/jerboa/nav/SettingsNavigation.kt @@ -0,0 +1,25 @@ +package com.jerboa.nav + +import androidx.compose.animation.ExperimentalAnimationApi +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import com.google.accompanist.navigation.animation.composable +import com.jerboa.db.AccountViewModel +import com.jerboa.ui.components.settings.SettingsActivity + +private const val settingsRoutePattern = "settings" + +@OptIn(ExperimentalAnimationApi::class) +fun NavGraphBuilder.settingsScreen( + navController: NavController, + accountViewModel: AccountViewModel, +) { + composable( + route = settingsRoutePattern, + ) { + SettingsActivity( + navController = navController, + accountViewModel = accountViewModel, + ) + } +} diff --git a/app/src/main/java/com/jerboa/nav/SiteSideBarNavigation.kt b/app/src/main/java/com/jerboa/nav/SiteSideBarNavigation.kt new file mode 100644 index 000000000..5b646268a --- /dev/null +++ b/app/src/main/java/com/jerboa/nav/SiteSideBarNavigation.kt @@ -0,0 +1,25 @@ +package com.jerboa.nav + +import androidx.compose.animation.ExperimentalAnimationApi +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import com.google.accompanist.navigation.animation.composable +import com.jerboa.ui.components.home.SiteSidebarActivity +import com.jerboa.ui.components.home.SiteViewModel + +private const val siteSideBarRoutePattern = "siteSidebar" + +@OptIn(ExperimentalAnimationApi::class) +fun NavGraphBuilder.siteSideBarScreen( + siteViewModel: SiteViewModel, + navController: NavController, +) { + composable( + route = siteSideBarRoutePattern, + ) { + SiteSidebarActivity( + siteViewModel = siteViewModel, + navController = navController, + ) + } +} diff --git a/app/src/main/java/com/jerboa/nav/Transitions.kt b/app/src/main/java/com/jerboa/nav/Transitions.kt new file mode 100644 index 000000000..0f9976705 --- /dev/null +++ b/app/src/main/java/com/jerboa/nav/Transitions.kt @@ -0,0 +1,25 @@ +package com.jerboa.nav + +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.slideInHorizontally +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutHorizontally +import androidx.compose.animation.slideOutVertically +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.runtime.Composable +import androidx.navigation.NavController + +val noEnterTransition = fadeIn(tween(0)) +val noPopExitTransition = fadeOut(tween(0)) + +val defaultEnterTransition = slideInHorizontally(tween(300)) { w -> w } +val defaultExitTransition = slideOutHorizontally(tween(300)) { w -> -w } +val defaultPopEnterTransition = slideInHorizontally(tween(300)) { w -> -w } +val defaultPopExitTransition = slideOutHorizontally(tween(300)) { w -> w } + +fun NavController.canPop() = previousBackStackEntry != null diff --git a/app/src/main/java/com/jerboa/ui/components/comment/edit/CommentEdit.kt b/app/src/main/java/com/jerboa/ui/components/comment/edit/CommentEdit.kt index 4a94e316c..446e7d2dc 100644 --- a/app/src/main/java/com/jerboa/ui/components/comment/edit/CommentEdit.kt +++ b/app/src/main/java/com/jerboa/ui/components/comment/edit/CommentEdit.kt @@ -20,6 +20,7 @@ import androidx.navigation.NavController import androidx.navigation.compose.rememberNavController import com.jerboa.R import com.jerboa.db.Account +import com.jerboa.ui.components.common.DefaultBackButton import com.jerboa.ui.components.common.MarkdownTextField @Composable @@ -51,18 +52,7 @@ fun CommentEditHeader( } } }, - navigationIcon = { - IconButton( - onClick = { - navController.popBackStack() - }, - ) { - Icon( - Icons.Outlined.Close, - contentDescription = stringResource(R.string.comment_edit_back), - ) - } - }, + navigationIcon = { DefaultBackButton(navController) }, ) } diff --git a/app/src/main/java/com/jerboa/ui/components/comment/reply/CommentReply.kt b/app/src/main/java/com/jerboa/ui/components/comment/reply/CommentReply.kt index 67571b7ea..5161f1586 100644 --- a/app/src/main/java/com/jerboa/ui/components/comment/reply/CommentReply.kt +++ b/app/src/main/java/com/jerboa/ui/components/comment/reply/CommentReply.kt @@ -28,6 +28,7 @@ import com.jerboa.db.Account import com.jerboa.ui.components.comment.CommentNodeHeader import com.jerboa.ui.components.comment.mentionnode.CommentMentionNodeHeader import com.jerboa.ui.components.comment.replynode.CommentReplyNodeHeader +import com.jerboa.ui.components.common.DefaultBackButton import com.jerboa.ui.components.common.MarkdownTextField import com.jerboa.ui.components.post.PostNodeHeader import com.jerboa.ui.theme.LARGE_PADDING @@ -62,18 +63,7 @@ fun CommentReplyHeader( } } }, - navigationIcon = { - IconButton( - onClick = { - navController.popBackStack() - }, - ) { - Icon( - Icons.Outlined.Close, - contentDescription = stringResource(R.string.comment_reply_back), - ) - } - }, + navigationIcon = { DefaultBackButton(navController) }, ) } 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 a826bd4ee..c92715f16 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 @@ -13,10 +13,7 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Bookmarks -import androidx.compose.material.icons.filled.Email import androidx.compose.material.icons.filled.Home -import androidx.compose.material.icons.filled.Person import androidx.compose.material.icons.filled.Search import androidx.compose.material.icons.outlined.* import androidx.compose.material3.* @@ -69,14 +66,7 @@ fun SimpleTopAppBar( text = text, ) }, - navigationIcon = { - IconButton(onClick = { navController.popBackStack() }) { - Icon( - Icons.Outlined.ArrowBack, - contentDescription = stringResource(R.string.topAppBar_back), - ) - } - }, + navigationIcon = { DefaultBackButton(navController) }, ) } diff --git a/app/src/main/java/com/jerboa/ui/components/common/DefaultBackButton.kt b/app/src/main/java/com/jerboa/ui/components/common/DefaultBackButton.kt new file mode 100644 index 000000000..77fb4613c --- /dev/null +++ b/app/src/main/java/com/jerboa/ui/components/common/DefaultBackButton.kt @@ -0,0 +1,37 @@ +package com.jerboa.ui.components.common + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.ArrowBack +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.res.stringResource +import androidx.navigation.NavController +import com.jerboa.R +import com.jerboa.nav.canPop + +@Composable +fun DefaultBackButton(navController: NavController) { + var canPop by remember { mutableStateOf(false) } + navController.addOnDestinationChangedListener { controller, _, _ -> + canPop = controller.canPop() + } + + var pressedBackButton by remember { mutableStateOf(false) } + + if (canPop || pressedBackButton) { + IconButton(onClick = { + navController.navigateUp() + }) { + pressedBackButton = true + Icon( + imageVector = Icons.Outlined.ArrowBack, + contentDescription = stringResource(R.string.topAppBar_back) + ) + } + } +} diff --git a/app/src/main/java/com/jerboa/ui/components/community/Community.kt b/app/src/main/java/com/jerboa/ui/components/community/Community.kt index bbe99a1d8..376fe70e1 100644 --- a/app/src/main/java/com/jerboa/ui/components/community/Community.kt +++ b/app/src/main/java/com/jerboa/ui/components/community/Community.kt @@ -18,6 +18,7 @@ import com.jerboa.datatypes.CommunityView import com.jerboa.datatypes.SortType import com.jerboa.datatypes.SubscribedType import com.jerboa.datatypes.sampleCommunityView +import com.jerboa.ui.components.common.DefaultBackButton import com.jerboa.ui.components.common.IconAndTextDrawerItem import com.jerboa.ui.components.common.LargerCircularIcon import com.jerboa.ui.components.common.PictrsBannerImage @@ -179,14 +180,7 @@ fun CommunityHeader( selectedSortType = selectedSortType, ) }, - navigationIcon = { - IconButton(onClick = { navController.popBackStack() }) { - Icon( - Icons.Outlined.ArrowBack, - contentDescription = stringResource(R.string.community_back), - ) - } - }, + navigationIcon = { DefaultBackButton(navController) }, actions = { IconButton(onClick = { showSortOptions = !showSortOptions diff --git a/app/src/main/java/com/jerboa/ui/components/community/CommunityActivity.kt b/app/src/main/java/com/jerboa/ui/components/community/CommunityActivity.kt index d435e78ec..00131ceed 100644 --- a/app/src/main/java/com/jerboa/ui/components/community/CommunityActivity.kt +++ b/app/src/main/java/com/jerboa/ui/components/community/CommunityActivity.kt @@ -33,11 +33,9 @@ import com.jerboa.db.AccountViewModel import com.jerboa.db.AppSettingsViewModel import com.jerboa.loginFirstToast import com.jerboa.scrollToTop -import com.jerboa.ui.components.common.BottomAppBarAll import com.jerboa.ui.components.common.getCurrentAccount import com.jerboa.ui.components.common.getPostViewMode import com.jerboa.ui.components.community.list.CommunityListViewModel -import com.jerboa.ui.components.home.HomeViewModel import com.jerboa.ui.components.home.SiteViewModel import com.jerboa.ui.components.post.PostListings import com.jerboa.ui.components.post.edit.PostEditViewModel @@ -49,7 +47,6 @@ fun CommunityActivity( communityViewModel: CommunityViewModel, communityListViewModel: CommunityListViewModel, accountViewModel: AccountViewModel, - homeViewModel: HomeViewModel, postEditViewModel: PostEditViewModel, appSettingsViewModel: AppSettingsViewModel, showVotingArrowsInListView: Boolean, @@ -251,34 +248,5 @@ fun CommunityActivity( ) } }, - bottomBar = { - BottomAppBarAll( - showBottomNav = appSettingsViewModel.appSettings.value?.showBottomNav, - screen = "communityList", - unreadCounts = homeViewModel.unreadCountResponse, - onClickProfile = { - account?.id?.also { - navController.navigate(route = "profile/$it") - } ?: run { - loginFirstToast(ctx) - } - }, - onClickInbox = { - account?.also { - navController.navigate(route = "inbox") - } ?: run { - loginFirstToast(ctx) - } - }, - onClickSaved = { - account?.id?.also { - navController.navigate(route = "profile/$it?saved=${true}") - } ?: run { - loginFirstToast(ctx) - } - }, - navController = navController, - ) - }, ) } diff --git a/app/src/main/java/com/jerboa/ui/components/community/list/CommunityList.kt b/app/src/main/java/com/jerboa/ui/components/community/list/CommunityList.kt index 8fd478814..a4a0608ac 100644 --- a/app/src/main/java/com/jerboa/ui/components/community/list/CommunityList.kt +++ b/app/src/main/java/com/jerboa/ui/components/community/list/CommunityList.kt @@ -24,19 +24,19 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.tooling.preview.Preview import androidx.navigation.NavController -import androidx.navigation.compose.rememberNavController import com.jerboa.R import com.jerboa.datatypes.CommunityFollowerView import com.jerboa.datatypes.CommunitySafe import com.jerboa.datatypes.CommunityView import com.jerboa.datatypes.sampleCommunityView +import com.jerboa.ui.components.common.DefaultBackButton import com.jerboa.ui.components.common.simpleVerticalScrollbar import com.jerboa.ui.components.community.CommunityLinkLarger import com.jerboa.ui.components.community.CommunityLinkLargerWithUserCount @Composable fun CommunityListHeader( - navController: NavController = rememberNavController(), + navController: NavController, search: String, onSearchChange: (search: String) -> Unit, ) { @@ -58,18 +58,7 @@ fun CommunityListHeader( ) } }, - navigationIcon = { - IconButton( - onClick = { - navController.popBackStack() - }, - ) { - Icon( - Icons.Outlined.Close, - contentDescription = stringResource(R.string.community_list_back), - ) - } - }, + navigationIcon = { DefaultBackButton(navController) } ) } diff --git a/app/src/main/java/com/jerboa/ui/components/community/list/CommunityListActivity.kt b/app/src/main/java/com/jerboa/ui/components/community/list/CommunityListActivity.kt index c224a8d3b..6df39da65 100644 --- a/app/src/main/java/com/jerboa/ui/components/community/list/CommunityListActivity.kt +++ b/app/src/main/java/com/jerboa/ui/components/community/list/CommunityListActivity.kt @@ -22,11 +22,7 @@ import androidx.compose.ui.platform.LocalContext import androidx.navigation.NavController import com.jerboa.DEBOUNCE_DELAY import com.jerboa.db.AccountViewModel -import com.jerboa.db.AppSettingsViewModel -import com.jerboa.loginFirstToast -import com.jerboa.ui.components.common.BottomAppBarAll import com.jerboa.ui.components.common.getCurrentAccount -import com.jerboa.ui.components.home.HomeViewModel import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -38,8 +34,6 @@ fun CommunityListActivity( navController: NavController, communityListViewModel: CommunityListViewModel, accountViewModel: AccountViewModel, - homeViewModel: HomeViewModel, - appSettingsViewModel: AppSettingsViewModel, selectMode: Boolean = false, ) { Log.d("jerboa", "got to community list activity") @@ -91,35 +85,6 @@ fun CommunityListActivity( ) } }, - bottomBar = { - BottomAppBarAll( - showBottomNav = appSettingsViewModel.appSettings.value?.showBottomNav, - screen = "communityList", - unreadCounts = homeViewModel.unreadCountResponse, - onClickProfile = { - account?.id?.also { - navController.navigate(route = "profile/$it") - } ?: run { - loginFirstToast(ctx) - } - }, - onClickInbox = { - account?.also { - navController.navigate(route = "inbox") - } ?: run { - loginFirstToast(ctx) - } - }, - onClickSaved = { - account?.id?.also { - navController.navigate(route = "profile/$it?saved=${true}") - } ?: run { - loginFirstToast(ctx) - } - }, - navController = navController, - ) - }, ) } } diff --git a/app/src/main/java/com/jerboa/ui/components/home/FeedActivity.kt b/app/src/main/java/com/jerboa/ui/components/home/FeedActivity.kt new file mode 100644 index 000000000..1962d6dc8 --- /dev/null +++ b/app/src/main/java/com/jerboa/ui/components/home/FeedActivity.kt @@ -0,0 +1,394 @@ +package com.jerboa.ui.components.home + +import android.content.Context +import android.util.Log +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Add +import androidx.compose.material3.DrawerState +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.FabPosition +import androidx.compose.material3.FloatingActionButton +import androidx.compose.material3.Icon +import androidx.compose.material3.LinearProgressIndicator +import androidx.compose.material3.Scaffold +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.TopAppBarScrollBehavior +import androidx.compose.material3.rememberTopAppBarState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.navigation.NavController +import com.jerboa.R +import com.jerboa.VoteType +import com.jerboa.closeDrawer +import com.jerboa.db.Account +import com.jerboa.db.AccountViewModel +import com.jerboa.db.AppSettingsViewModel +import com.jerboa.fetchInitialData +import com.jerboa.loginFirstToast +import com.jerboa.nav.bottomNavSelectInbox +import com.jerboa.nav.bottomNavSelectProfile +import com.jerboa.nav.bottomNavSelectSaved +import com.jerboa.nav.bottomNavSelectSearch +import com.jerboa.nav.showLogin +import com.jerboa.scrollToTop +import com.jerboa.ui.components.common.getCurrentAccount +import com.jerboa.ui.components.common.getPostViewMode +import com.jerboa.ui.components.post.PostListings +import com.jerboa.ui.components.post.edit.PostEditViewModel +import kotlinx.coroutines.CoroutineScope + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun FeedActivity( + navController: NavController, + homeViewModel: HomeViewModel, + accountViewModel: AccountViewModel, + siteViewModel: SiteViewModel, + postEditViewModel: PostEditViewModel, + appSettingsViewModel: AppSettingsViewModel, + showVotingArrowsInListView: Boolean, + drawerState: DrawerState, +) { + Log.d("jerboa", "got to home activity") + + val scope = rememberCoroutineScope() + val postListState = rememberLazyListState() + val snackbarHostState = remember { SnackbarHostState() } + val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(rememberTopAppBarState()) + val ctx = LocalContext.current + val account = getCurrentAccount(accountViewModel) + + Scaffold( + modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), + snackbarHost = { SnackbarHost(snackbarHostState) }, + topBar = { + MainTopBar( + scope = scope, + postListState = postListState, + drawerState = drawerState, + homeViewModel = homeViewModel, + appSettingsViewModel = appSettingsViewModel, + account = account, + ctx = ctx, + navController = navController, + scrollBehavior = scrollBehavior, + ) + }, + content = { padding -> + MainPostListingsContent( + padding = padding, + homeViewModel = homeViewModel, + siteViewModel = siteViewModel, + postEditViewModel = postEditViewModel, + appSettingsViewModel = appSettingsViewModel, + account = account, + ctx = ctx, + navController = navController, + postListState = postListState, + showVotingArrowsInListView = showVotingArrowsInListView, + ) + }, + floatingActionButtonPosition = FabPosition.End, + floatingActionButton = { + FloatingActionButton( + onClick = { + account?.also { + navController.navigate("createPost") + } ?: run { + loginFirstToast(ctx) + } + }, + ) { + Icon( + imageVector = Icons.Outlined.Add, + contentDescription = stringResource(R.string.floating_createPost), + ) + } + }, + ) +} + +@Composable +fun MainPostListingsContent( + homeViewModel: HomeViewModel, + siteViewModel: SiteViewModel, + postEditViewModel: PostEditViewModel, + account: Account?, + ctx: Context, + navController: NavController, + padding: PaddingValues, + postListState: LazyListState, + appSettingsViewModel: AppSettingsViewModel, + showVotingArrowsInListView: Boolean, +) { + PostListings( + listState = postListState, + padding = padding, + posts = homeViewModel.posts, + taglines = siteViewModel.siteRes?.taglines, + postViewMode = getPostViewMode(appSettingsViewModel), + onUpvoteClick = { postView -> + homeViewModel.likePost( + voteType = VoteType.Upvote, + postView = postView, + account = account, + ctx = ctx, + ) + }, + onDownvoteClick = { postView -> + homeViewModel.likePost( + voteType = VoteType.Downvote, + postView = postView, + account = account, + ctx = ctx, + ) + }, + onPostClick = { postView -> + navController.navigate(route = "post/${postView.post.id}") + }, + onSaveClick = { postView -> + account?.also { acct -> + homeViewModel.savePost( + postView = postView, + account = acct, + ctx = ctx, + ) + } + }, + onBlockCommunityClick = { + account?.also { acct -> + homeViewModel.blockCommunity( + community = it, + account = acct, + ctx = ctx, + ) + } + }, + onBlockCreatorClick = { + account?.also { acct -> + homeViewModel.blockCreator( + creator = it, + account = acct, + ctx = ctx, + ) + } + }, + onEditPostClick = { postView -> + postEditViewModel.initialize(postView) + navController.navigate("postEdit") + }, + onDeletePostClick = { postView -> + account?.also { acct -> + homeViewModel.deletePost( + postView = postView, + account = acct, + ctx = ctx, + ) + } + }, + onReportClick = { postView -> + navController.navigate("postReport/${postView.post.id}") + }, + onCommunityClick = { community -> + navController.navigate(route = "community/${community.id}") + }, + onPersonClick = { personId -> + navController.navigate(route = "profile/$personId") + }, + onSwipeRefresh = { + homeViewModel.fetchPosts( + account = account, + clear = true, + ctx = ctx, + ) + }, + loading = homeViewModel.loading.value && + homeViewModel.page.value == 1 && + homeViewModel.posts.isNotEmpty(), + isScrolledToEnd = { + if (homeViewModel.posts.size > 0) { + homeViewModel.fetchPosts( + account = account, + nextPage = true, + ctx = ctx, + ) + } + }, + account = account, + showVotingArrowsInListView = showVotingArrowsInListView, + enableDownVotes = siteViewModel.siteRes?.site_view?.local_site?.enable_downvotes ?: true, + showAvatar = siteViewModel.siteRes?.my_user?.local_user_view?.local_user?.show_avatars + ?: true, + ) +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun MainDrawer( + siteViewModel: SiteViewModel, + navController: NavController, + bottomNavController: NavController, + accountViewModel: AccountViewModel, + homeViewModel: HomeViewModel, + scope: CoroutineScope, + ctx: Context, + drawerState: DrawerState, +) { + val accounts = accountViewModel.allAccounts.value + val account = getCurrentAccount(accountViewModel) + + Drawer( + myUserInfo = siteViewModel.siteRes?.my_user, + unreadCounts = homeViewModel.unreadCountResponse, + accountViewModel = accountViewModel, + navController = navController, + isOpen = drawerState.isOpen, + onSwitchAccountClick = { acct -> + accountViewModel.removeCurrent() + accountViewModel.setCurrent(acct.id) + + fetchInitialData( + account = acct, + siteViewModel = siteViewModel, + homeViewModel = homeViewModel, + ) + + closeDrawer(scope, drawerState) + }, + onSignOutClick = { + accounts?.also { accts -> + account?.also { + accountViewModel.delete(it) + val updatedList = accts.toMutableList() + updatedList.remove(it) + + if (updatedList.isNotEmpty()) { + accountViewModel.setCurrent(updatedList[0].id) + } + fetchInitialData( + account = updatedList.getOrNull(0), + siteViewModel = siteViewModel, + homeViewModel = homeViewModel, + ) + + closeDrawer(scope, drawerState) + } + } + }, + onClickListingType = { listingType -> + homeViewModel.fetchPosts( + account = account, + clear = true, + changeListingType = listingType, + ctx = ctx, + ) + closeDrawer(scope, drawerState) + }, + onCommunityClick = { community -> + navController.navigate(route = "community/${community.id}") + closeDrawer(scope, drawerState) + }, + onClickProfile = { + account?.id?.also { + bottomNavController.bottomNavSelectProfile(it) + closeDrawer(scope, drawerState) + } ?: { + navController.showLogin() + } + }, + onClickSaved = { + account?.id?.also { + bottomNavController.bottomNavSelectSaved(it) + closeDrawer(scope, drawerState) + } ?: { + navController.showLogin() + } + }, + onClickInbox = { + account?.also { + bottomNavController.bottomNavSelectInbox() + } ?: run { + navController.showLogin() + } + closeDrawer(scope, drawerState) + }, + onClickSettings = { + navController.navigate(route = "settings") + closeDrawer(scope, drawerState) + }, + onClickCommunities = { + bottomNavController.bottomNavSelectSearch() + closeDrawer(scope, drawerState) + }, + ) +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun MainTopBar( + scope: CoroutineScope, + postListState: LazyListState, + drawerState: DrawerState, + homeViewModel: HomeViewModel, + appSettingsViewModel: AppSettingsViewModel, + account: Account?, + ctx: Context, + navController: NavController, + scrollBehavior: TopAppBarScrollBehavior, +) { + Column { + HomeHeader( + scope = scope, + scrollBehavior = scrollBehavior, + drawerState = drawerState, + navController = navController, + selectedSortType = homeViewModel.sortType.value, + selectedListingType = homeViewModel.listingType.value, + selectedPostViewMode = getPostViewMode(appSettingsViewModel), + onClickSortType = { sortType -> + scrollToTop(scope, postListState) + homeViewModel.fetchPosts( + account = account, + clear = true, + changeSortType = sortType, + ctx = ctx, + ) + }, + onClickListingType = { listingType -> + scrollToTop(scope, postListState) + homeViewModel.fetchPosts( + account = account, + clear = true, + changeListingType = listingType, + ctx = ctx, + ) + }, + onClickPostViewMode = { + appSettingsViewModel.updatedPostViewMode(it.ordinal) + }, + onClickRefresh = { + scrollToTop(scope, postListState) + homeViewModel.fetchPosts( + account = account, + clear = true, + ctx = ctx, + ) + }, + ) + if (homeViewModel.loading.value) { + LinearProgressIndicator(modifier = Modifier.fillMaxWidth()) + } + } +} diff --git a/app/src/main/java/com/jerboa/ui/components/home/HomeActivity.kt b/app/src/main/java/com/jerboa/ui/components/home/HomeActivity.kt index c59faf328..7436d4815 100644 --- a/app/src/main/java/com/jerboa/ui/components/home/HomeActivity.kt +++ b/app/src/main/java/com/jerboa/ui/components/home/HomeActivity.kt @@ -1,55 +1,84 @@ package com.jerboa.ui.components.home -import android.content.Context +import android.app.Activity import android.util.Log -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.lazy.LazyListState -import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.animation.ExperimentalAnimationApi +import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Add -import androidx.compose.material3.DrawerState +import androidx.compose.material.icons.filled.Bookmarks +import androidx.compose.material.icons.filled.Email +import androidx.compose.material.icons.filled.Home +import androidx.compose.material.icons.filled.Person +import androidx.compose.material.icons.filled.Search +import androidx.compose.material.icons.outlined.Bookmarks +import androidx.compose.material.icons.outlined.Email +import androidx.compose.material.icons.outlined.Home +import androidx.compose.material.icons.outlined.Person +import androidx.compose.material.icons.outlined.Search import androidx.compose.material3.DrawerValue -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.FabPosition -import androidx.compose.material3.FloatingActionButton -import androidx.compose.material3.Icon -import androidx.compose.material3.LinearProgressIndicator +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ModalDrawerSheet import androidx.compose.material3.ModalNavigationDrawer +import androidx.compose.material3.NavigationBar +import androidx.compose.material3.NavigationBarItem import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState -import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.material3.TopAppBarScrollBehavior +import androidx.compose.material3.Text import androidx.compose.material3.rememberDrawerState -import androidx.compose.material3.rememberTopAppBarState +import androidx.compose.material3.surfaceColorAtElevation import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier -import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp import androidx.navigation.NavController +import androidx.navigation.compose.currentBackStackEntryAsState +import com.google.accompanist.navigation.animation.AnimatedNavHost +import com.google.accompanist.navigation.animation.rememberAnimatedNavController import com.jerboa.R -import com.jerboa.VoteType -import com.jerboa.closeDrawer -import com.jerboa.db.Account +import com.jerboa.datatypes.api.GetUnreadCountResponse import com.jerboa.db.AccountViewModel +import com.jerboa.db.AppSettings import com.jerboa.db.AppSettingsViewModel -import com.jerboa.fetchInitialData -import com.jerboa.loginFirstToast -import com.jerboa.scrollToTop -import com.jerboa.ui.components.common.BottomAppBarAll +import com.jerboa.nav.bottomNavDefaultRoute +import com.jerboa.nav.bottomNavIsHome +import com.jerboa.nav.bottomNavIsInbox +import com.jerboa.nav.bottomNavIsProfile +import com.jerboa.nav.bottomNavIsSaved +import com.jerboa.nav.bottomNavIsSearch +import com.jerboa.nav.bottomNavSelectHome +import com.jerboa.nav.bottomNavSelectInbox +import com.jerboa.nav.bottomNavSelectProfile +import com.jerboa.nav.bottomNavSelectSaved +import com.jerboa.nav.bottomNavSelectSearch +import com.jerboa.nav.communityListScreen +import com.jerboa.nav.feedScreen +import com.jerboa.nav.inboxScreen +import com.jerboa.nav.noEnterTransition +import com.jerboa.nav.noPopExitTransition +import com.jerboa.nav.profileScreen +import com.jerboa.nav.showLogin +import com.jerboa.ui.components.comment.edit.CommentEditViewModel +import com.jerboa.ui.components.comment.reply.CommentReplyViewModel +import com.jerboa.ui.components.common.InboxIconAndBadge import com.jerboa.ui.components.common.getCurrentAccount -import com.jerboa.ui.components.common.getPostViewMode -import com.jerboa.ui.components.post.PostListings +import com.jerboa.ui.components.community.list.CommunityListViewModel +import com.jerboa.ui.components.inbox.InboxViewModel +import com.jerboa.ui.components.person.PersonProfileViewModel import com.jerboa.ui.components.post.edit.PostEditViewModel -import kotlinx.coroutines.CoroutineScope +import com.jerboa.unreadCountTotal -@OptIn(ExperimentalMaterial3Api::class) +@OptIn(ExperimentalAnimationApi::class) @Composable fun HomeActivity( navController: NavController, @@ -59,17 +88,35 @@ fun HomeActivity( postEditViewModel: PostEditViewModel, appSettingsViewModel: AppSettingsViewModel, showVotingArrowsInListView: Boolean, + communityListViewModel: CommunityListViewModel, + inboxViewModel: InboxViewModel, + commentReplyViewModel: CommentReplyViewModel, + personProfileViewModel: PersonProfileViewModel, + commentEditViewModel: CommentEditViewModel, + appSettings: AppSettings?, ) { Log.d("jerboa", "got to home activity") val scope = rememberCoroutineScope() - val postListState = rememberLazyListState() val snackbarHostState = remember { SnackbarHostState() } - val drawerState = rememberDrawerState(DrawerValue.Closed) - val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(rememberTopAppBarState()) + val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed) val ctx = LocalContext.current val account = getCurrentAccount(accountViewModel) + var selectedBottomNavBarType by rememberSaveable { mutableStateOf(BottomNavBarType.Home) } + val bottomNavController = rememberAnimatedNavController() + val navBackStackEntry by bottomNavController.currentBackStackEntryAsState() + LaunchedEffect(navBackStackEntry) { + selectedBottomNavBarType = when { + navBackStackEntry?.bottomNavIsHome() ?: false -> BottomNavBarType.Home + navBackStackEntry?.bottomNavIsSearch() ?: false -> BottomNavBarType.Search + navBackStackEntry?.bottomNavIsInbox() ?: false -> BottomNavBarType.Inbox + navBackStackEntry?.bottomNavIsSaved() ?: false -> BottomNavBarType.Saved + navBackStackEntry?.bottomNavIsProfile() ?: false -> BottomNavBarType.Profile + else -> BottomNavBarType.Home + } + } + ModalNavigationDrawer( drawerState = drawerState, drawerContent = { @@ -78,6 +125,7 @@ fun HomeActivity( MainDrawer( siteViewModel = siteViewModel, navController = navController, + bottomNavController = bottomNavController, accountViewModel = accountViewModel, homeViewModel = homeViewModel, scope = scope, @@ -89,79 +137,95 @@ fun HomeActivity( }, content = { Scaffold( - modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), snackbarHost = { SnackbarHost(snackbarHostState) }, - topBar = { - MainTopBar( - scope = scope, - postListState = postListState, - drawerState = drawerState, - homeViewModel = homeViewModel, - appSettingsViewModel = appSettingsViewModel, - account = account, - ctx = ctx, - navController = navController, - scrollBehavior = scrollBehavior, - ) - }, content = { padding -> - MainPostListingsContent( - padding = padding, - homeViewModel = homeViewModel, - siteViewModel = siteViewModel, - postEditViewModel = postEditViewModel, - appSettingsViewModel = appSettingsViewModel, - account = account, - ctx = ctx, - navController = navController, - postListState = postListState, - showVotingArrowsInListView = showVotingArrowsInListView, - ) - }, - floatingActionButtonPosition = FabPosition.End, - floatingActionButton = { - FloatingActionButton( - onClick = { - account?.also { - navController.navigate("createPost") - } ?: run { - loginFirstToast(ctx) - } - }, + AnimatedNavHost( + navController = bottomNavController, + startDestination = bottomNavDefaultRoute(), + modifier = Modifier.padding(), + enterTransition = { noEnterTransition }, + popExitTransition = { noPopExitTransition }, ) { - Icon( - imageVector = Icons.Outlined.Add, - contentDescription = stringResource(R.string.floating_createPost), + feedScreen( + navController = navController, + homeViewModel = homeViewModel, + accountViewModel = accountViewModel, + siteViewModel = siteViewModel, + postEditViewModel = postEditViewModel, + appSettingsViewModel = appSettingsViewModel, + drawerState = drawerState, + appSettings = appSettings, + ) + + communityListScreen( + navController = navController, + accountViewModel = accountViewModel, + communityListViewModel = communityListViewModel, + siteViewModel = siteViewModel, + ) + + inboxScreen( + navController = navController, + inboxViewModel = inboxViewModel, + accountViewModel = accountViewModel, + homeViewModel = homeViewModel, + commentReplyViewModel = commentReplyViewModel, + siteViewModel = siteViewModel, + account = account, + ctx = ctx, + ) + + profileScreen( + navController = navController, + personProfileViewModel = personProfileViewModel, + accountViewModel = accountViewModel, + commentEditViewModel = commentEditViewModel, + commentReplyViewModel = commentReplyViewModel, + postEditViewModel = postEditViewModel, + appSettingsViewModel = appSettingsViewModel, + siteViewModel = siteViewModel, + account = account, + appSettings = appSettings, ) } }, bottomBar = { - BottomAppBarAll( + BottomNavBar( showBottomNav = appSettingsViewModel.appSettings.value?.showBottomNav, - screen = "home", + selectedType = selectedBottomNavBarType, unreadCounts = homeViewModel.unreadCountResponse, - onClickProfile = { - account?.id?.also { - navController.navigate(route = "profile/$it") - } ?: run { - loginFirstToast(ctx) - } + onClickHome = { + selectedBottomNavBarType = BottomNavBarType.Home + bottomNavController.bottomNavSelectHome() + }, + onClickSearch = { + selectedBottomNavBarType = BottomNavBarType.Search + bottomNavController.bottomNavSelectSearch() }, onClickInbox = { account?.also { - navController.navigate(route = "inbox") + selectedBottomNavBarType = BottomNavBarType.Inbox + bottomNavController.bottomNavSelectInbox() } ?: run { - loginFirstToast(ctx) + navController.showLogin() } }, onClickSaved = { account?.id?.also { - navController.navigate(route = "profile/$it?saved=${true}") + selectedBottomNavBarType = BottomNavBarType.Saved + bottomNavController.bottomNavSelectSaved(it) } ?: run { - loginFirstToast(ctx) + navController.showLogin() + } + }, + onClickProfile = { + account?.id?.also { + selectedBottomNavBarType = BottomNavBarType.Profile + bottomNavController.bottomNavSelectProfile(it) + } ?: run { + navController.showLogin() } }, - navController = navController, ) }, ) @@ -169,269 +233,93 @@ fun HomeActivity( ) } -@Composable -fun MainPostListingsContent( - homeViewModel: HomeViewModel, - siteViewModel: SiteViewModel, - postEditViewModel: PostEditViewModel, - account: Account?, - ctx: Context, - navController: NavController, - padding: PaddingValues, - postListState: LazyListState, - appSettingsViewModel: AppSettingsViewModel, - showVotingArrowsInListView: Boolean, -) { - PostListings( - listState = postListState, - padding = padding, - posts = homeViewModel.posts, - taglines = siteViewModel.siteRes?.taglines, - postViewMode = getPostViewMode(appSettingsViewModel), - onUpvoteClick = { postView -> - homeViewModel.likePost( - voteType = VoteType.Upvote, - postView = postView, - account = account, - ctx = ctx, - ) - }, - onDownvoteClick = { postView -> - homeViewModel.likePost( - voteType = VoteType.Downvote, - postView = postView, - account = account, - ctx = ctx, - ) - }, - onPostClick = { postView -> - navController.navigate(route = "post/${postView.post.id}") - }, - onSaveClick = { postView -> - account?.also { acct -> - homeViewModel.savePost( - postView = postView, - account = acct, - ctx = ctx, - ) - } - }, - onBlockCommunityClick = { - account?.also { acct -> - homeViewModel.blockCommunity( - community = it, - account = acct, - ctx = ctx, - ) - } - }, - onBlockCreatorClick = { - account?.also { acct -> - homeViewModel.blockCreator( - creator = it, - account = acct, - ctx = ctx, - ) - } - }, - onEditPostClick = { postView -> - postEditViewModel.initialize(postView) - navController.navigate("postEdit") - }, - onDeletePostClick = { postView -> - account?.also { acct -> - homeViewModel.deletePost( - postView = postView, - account = acct, - ctx = ctx, - ) - } - }, - onReportClick = { postView -> - navController.navigate("postReport/${postView.post.id}") - }, - onCommunityClick = { community -> - navController.navigate(route = "community/${community.id}") - }, - onPersonClick = { personId -> - navController.navigate(route = "profile/$personId") - }, - onSwipeRefresh = { - homeViewModel.fetchPosts( - account = account, - clear = true, - ctx = ctx, - ) - }, - loading = homeViewModel.loading.value && - homeViewModel.page.value == 1 && - homeViewModel.posts.isNotEmpty(), - isScrolledToEnd = { - if (homeViewModel.posts.size > 0) { - homeViewModel.fetchPosts( - account = account, - nextPage = true, - ctx = ctx, - ) - } - }, - account = account, - showVotingArrowsInListView = showVotingArrowsInListView, - enableDownVotes = siteViewModel.siteRes?.site_view?.local_site?.enable_downvotes ?: true, - showAvatar = siteViewModel.siteRes?.my_user?.local_user_view?.local_user?.show_avatars ?: true, - ) +enum class BottomNavBarType { + Home, Search, Inbox, Saved, Profile, } -@OptIn(ExperimentalMaterial3Api::class) @Composable -fun MainDrawer( - siteViewModel: SiteViewModel, - navController: NavController, - accountViewModel: AccountViewModel, - homeViewModel: HomeViewModel, - scope: CoroutineScope, - ctx: Context, - drawerState: DrawerState, +fun BottomNavBar( + selectedType: BottomNavBarType, + unreadCounts: GetUnreadCountResponse? = null, + showBottomNav: Boolean? = true, + onClickHome: () -> Unit, + onClickSearch: () -> Unit, + onClickSaved: () -> Unit, + onClickProfile: () -> Unit, + onClickInbox: () -> Unit, ) { - val accounts = accountViewModel.allAccounts.value - val account = getCurrentAccount(accountViewModel) - - Drawer( - myUserInfo = siteViewModel.siteRes?.my_user, - unreadCounts = homeViewModel.unreadCountResponse, - accountViewModel = accountViewModel, - navController = navController, - isOpen = drawerState.isOpen, - onSwitchAccountClick = { acct -> - accountViewModel.removeCurrent() - accountViewModel.setCurrent(acct.id) + val totalUnreads = unreadCounts?.let { unreadCountTotal(it) } - fetchInitialData( - account = acct, - siteViewModel = siteViewModel, - homeViewModel = homeViewModel, - ) + if (showBottomNav == true) { + // Check for preview mode + if (LocalContext.current is Activity) { + val window = (LocalContext.current as Activity).window + val colorScheme = MaterialTheme.colorScheme - closeDrawer(scope, drawerState) - }, - onSignOutClick = { - accounts?.also { accts -> - account?.also { - accountViewModel.delete(it) - val updatedList = accts.toMutableList() - updatedList.remove(it) + DisposableEffect(Unit) { + window.navigationBarColor = colorScheme.surfaceColorAtElevation(3.dp).toArgb() - if (updatedList.isNotEmpty()) { - accountViewModel.setCurrent(updatedList[0].id) - } - fetchInitialData( - account = updatedList.getOrNull(0), - siteViewModel = siteViewModel, - homeViewModel = homeViewModel, - ) - - closeDrawer(scope, drawerState) + onDispose { + window.navigationBarColor = colorScheme.background.toArgb() } } - }, - onClickListingType = { listingType -> - homeViewModel.fetchPosts( - account = account, - clear = true, - changeListingType = listingType, - ctx = ctx, - ) - closeDrawer(scope, drawerState) - }, - onCommunityClick = { community -> - navController.navigate(route = "community/${community.id}") - closeDrawer(scope, drawerState) - }, - onClickProfile = { - account?.id?.also { - navController.navigate(route = "profile/$it") - closeDrawer(scope, drawerState) - } - }, - onClickSaved = { - account?.id?.also { - navController.navigate(route = "profile/$it?saved=${true}") - closeDrawer(scope, drawerState) - } - }, - onClickInbox = { - account?.also { - navController.navigate(route = "inbox") - } ?: run { - loginFirstToast(ctx) - } - closeDrawer(scope, drawerState) - }, - onClickSettings = { - navController.navigate(route = "settings") - closeDrawer(scope, drawerState) - }, - onClickCommunities = { - navController.navigate(route = "communityList") - closeDrawer(scope, drawerState) - }, - ) -} + } -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun MainTopBar( - scope: CoroutineScope, - postListState: LazyListState, - drawerState: DrawerState, - homeViewModel: HomeViewModel, - appSettingsViewModel: AppSettingsViewModel, - account: Account?, - ctx: Context, - navController: NavController, - scrollBehavior: TopAppBarScrollBehavior, -) { - Column { - HomeHeader( - scope = scope, - scrollBehavior = scrollBehavior, - drawerState = drawerState, - navController = navController, - selectedSortType = homeViewModel.sortType.value, - selectedListingType = homeViewModel.listingType.value, - selectedPostViewMode = getPostViewMode(appSettingsViewModel), - onClickSortType = { sortType -> - scrollToTop(scope, postListState) - homeViewModel.fetchPosts( - account = account, - clear = true, - changeSortType = sortType, - ctx = ctx, - ) - }, - onClickListingType = { listingType -> - scrollToTop(scope, postListState) - homeViewModel.fetchPosts( - account = account, - clear = true, - changeListingType = listingType, - ctx = ctx, - ) - }, - onClickPostViewMode = { - appSettingsViewModel.updatedPostViewMode(it.ordinal) - }, - onClickRefresh = { - scrollToTop(scope, postListState) - homeViewModel.fetchPosts( - account = account, - clear = true, - ctx = ctx, + NavigationBar { + for (type in BottomNavBarType.values()) { + val selected = type == selectedType + NavigationBarItem( + icon = { + InboxIconAndBadge( + iconBadgeCount = if (type == BottomNavBarType.Inbox) totalUnreads else null, + icon = if (selected) { + when (type) { + BottomNavBarType.Home -> Icons.Filled.Home + BottomNavBarType.Search -> Icons.Filled.Search + BottomNavBarType.Inbox -> Icons.Filled.Email + BottomNavBarType.Saved -> Icons.Filled.Bookmarks + BottomNavBarType.Profile -> Icons.Filled.Person + } + } else { + when (type) { + BottomNavBarType.Home -> Icons.Outlined.Home + BottomNavBarType.Search -> Icons.Outlined.Search + BottomNavBarType.Inbox -> Icons.Outlined.Email + BottomNavBarType.Saved -> Icons.Outlined.Bookmarks + BottomNavBarType.Profile -> Icons.Outlined.Person + } + }, + contentDescription = stringResource(when (type) { + BottomNavBarType.Home -> R.string.bottomBar_home + BottomNavBarType.Search -> R.string.bottomBar_search + BottomNavBarType.Inbox -> R.string.bottomBar_inbox + BottomNavBarType.Saved -> R.string.bottomBar_bookmarks + BottomNavBarType.Profile -> R.string.bottomBar_profile + }), + ) + }, + label = { + Text( + text = stringResource(when (type) { + BottomNavBarType.Home -> R.string.bottomBar_label_home + BottomNavBarType.Search -> R.string.bottomBar_label_search + BottomNavBarType.Inbox -> R.string.bottomBar_label_inbox + BottomNavBarType.Saved -> R.string.bottomBar_label_bookmarks + BottomNavBarType.Profile -> R.string.bottomBar_label_profile + }), + color = MaterialTheme.colorScheme.onSurface, + ) + }, + selected = selected, + onClick = when (type) { + BottomNavBarType.Home -> onClickHome + BottomNavBarType.Search -> onClickSearch + BottomNavBarType.Inbox -> onClickInbox + BottomNavBarType.Saved -> onClickSaved + BottomNavBarType.Profile -> onClickProfile + }, ) - }, - ) - if (homeViewModel.loading.value) { - LinearProgressIndicator(modifier = Modifier.fillMaxWidth()) + } } } } diff --git a/app/src/main/java/com/jerboa/ui/components/inbox/Inbox.kt b/app/src/main/java/com/jerboa/ui/components/inbox/Inbox.kt index e9431ac67..a21188b5e 100644 --- a/app/src/main/java/com/jerboa/ui/components/inbox/Inbox.kt +++ b/app/src/main/java/com/jerboa/ui/components/inbox/Inbox.kt @@ -4,7 +4,6 @@ package com.jerboa.ui.components.inbox import androidx.compose.foundation.layout.Column import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.ArrowBack import androidx.compose.material.icons.outlined.DoneAll import androidx.compose.material.icons.outlined.FilterList import androidx.compose.material3.ExperimentalMaterial3Api @@ -24,6 +23,7 @@ import androidx.navigation.NavController import androidx.navigation.compose.rememberNavController import com.jerboa.R import com.jerboa.UnreadOrAll +import com.jerboa.ui.components.common.DefaultBackButton import com.jerboa.ui.components.common.UnreadOrAllOptionsDialog @Composable @@ -56,14 +56,7 @@ fun InboxHeader( selectedUnreadOrAll = selectedUnreadOrAll, ) }, - navigationIcon = { - IconButton(onClick = { navController.popBackStack() }) { - Icon( - Icons.Outlined.ArrowBack, - contentDescription = stringResource(R.string.inbox_back), - ) - } - }, + navigationIcon = { DefaultBackButton(navController) }, actions = { IconButton(onClick = { showUnreadOrAllOptions = !showUnreadOrAllOptions diff --git a/app/src/main/java/com/jerboa/ui/components/inbox/InboxActivity.kt b/app/src/main/java/com/jerboa/ui/components/inbox/InboxActivity.kt index 4b058950f..5dba4f220 100644 --- a/app/src/main/java/com/jerboa/ui/components/inbox/InboxActivity.kt +++ b/app/src/main/java/com/jerboa/ui/components/inbox/InboxActivity.kt @@ -23,12 +23,10 @@ import androidx.navigation.NavController import com.jerboa.* import com.jerboa.db.Account import com.jerboa.db.AccountViewModel -import com.jerboa.db.AppSettingsViewModel import com.jerboa.ui.components.comment.mentionnode.CommentMentionNode import com.jerboa.ui.components.comment.reply.CommentReplyViewModel import com.jerboa.ui.components.comment.reply.ReplyItem import com.jerboa.ui.components.comment.replynode.CommentReplyNode -import com.jerboa.ui.components.common.BottomAppBarAll import com.jerboa.ui.components.common.getCurrentAccount import com.jerboa.ui.components.common.simpleVerticalScrollbar import com.jerboa.ui.components.home.HomeViewModel @@ -41,7 +39,6 @@ import kotlinx.coroutines.launch @Composable fun InboxActivity( navController: NavController, - appSettingsViewModel: AppSettingsViewModel, inboxViewModel: InboxViewModel, homeViewModel: HomeViewModel, accountViewModel: AccountViewModel, @@ -112,33 +109,6 @@ fun InboxActivity( siteViewModel = siteViewModel, ) }, - bottomBar = { - BottomAppBarAll( - showBottomNav = appSettingsViewModel.appSettings.value?.showBottomNav, - screen = "inbox", - unreadCounts = homeViewModel.unreadCountResponse, - onClickProfile = { - account?.id?.also { - navController.navigate(route = "profile/$it") - } - }, - onClickInbox = { - account?.also { - navController.navigate(route = "inbox") - } ?: run { - loginFirstToast(ctx) - } - }, - onClickSaved = { - account?.id?.also { - navController.navigate(route = "profile/$it?saved=${true}") - } ?: run { - loginFirstToast(ctx) - } - }, - navController = navController, - ) - }, ) } diff --git a/app/src/main/java/com/jerboa/ui/components/login/Login.kt b/app/src/main/java/com/jerboa/ui/components/login/Login.kt index 2a3c0f071..596bcff02 100644 --- a/app/src/main/java/com/jerboa/ui/components/login/Login.kt +++ b/app/src/main/java/com/jerboa/ui/components/login/Login.kt @@ -10,7 +10,6 @@ import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.ArrowBack import androidx.compose.material.icons.outlined.Visibility import androidx.compose.material.icons.outlined.VisibilityOff import androidx.compose.material3.* @@ -36,6 +35,7 @@ import com.jerboa.R import com.jerboa.datatypes.api.Login import com.jerboa.db.Account import com.jerboa.onAutofill +import com.jerboa.ui.components.common.DefaultBackButton val BANNED_INSTANCES = listOf("wolfballs.com") @@ -219,19 +219,7 @@ fun LoginHeader( text = stringResource(R.string.login_login), ) }, - navigationIcon = { - IconButton( - enabled = !accounts.isNullOrEmpty(), - onClick = { - navController.popBackStack() - }, - ) { - Icon( - Icons.Outlined.ArrowBack, - contentDescription = stringResource(R.string.login_back), - ) - } - }, + navigationIcon = { DefaultBackButton(navController) }, ) } diff --git a/app/src/main/java/com/jerboa/ui/components/login/LoginViewModel.kt b/app/src/main/java/com/jerboa/ui/components/login/LoginViewModel.kt index f51f61157..7b46aec35 100644 --- a/app/src/main/java/com/jerboa/ui/components/login/LoginViewModel.kt +++ b/app/src/main/java/com/jerboa/ui/components/login/LoginViewModel.kt @@ -20,6 +20,7 @@ import com.jerboa.datatypes.api.Login import com.jerboa.db.Account import com.jerboa.db.AccountViewModel import com.jerboa.fetchInitialData +import com.jerboa.nav.loginSuccessGoToHome import com.jerboa.ui.components.home.HomeViewModel import com.jerboa.ui.components.home.SiteViewModel import kotlinx.coroutines.cancel @@ -126,7 +127,7 @@ class LoginViewModel : ViewModel() { loading = false - navController.navigate(route = "home") + navController.loginSuccessGoToHome() } } } diff --git a/app/src/main/java/com/jerboa/ui/components/person/PersonProfile.kt b/app/src/main/java/com/jerboa/ui/components/person/PersonProfile.kt index ea7962cfd..47b48def1 100644 --- a/app/src/main/java/com/jerboa/ui/components/person/PersonProfile.kt +++ b/app/src/main/java/com/jerboa/ui/components/person/PersonProfile.kt @@ -38,6 +38,7 @@ import com.jerboa.datatypes.SortType import com.jerboa.datatypes.samplePersonView import com.jerboa.getLocalizedSortingTypeName import com.jerboa.personNameShown +import com.jerboa.ui.components.common.DefaultBackButton import com.jerboa.ui.components.common.DotSpacer import com.jerboa.ui.components.common.IconAndTextDrawerItem import com.jerboa.ui.components.common.ImageViewerDialog @@ -215,14 +216,7 @@ fun PersonProfileHeader( selectedSortType = selectedSortType, ) }, - navigationIcon = { - IconButton(onClick = { navController.popBackStack() }) { - Icon( - Icons.Outlined.ArrowBack, - contentDescription = stringResource(R.string.person_profile_back), - ) - } - }, + navigationIcon = { DefaultBackButton(navController) }, actions = { IconButton(onClick = { showSortOptions = !showSortOptions diff --git a/app/src/main/java/com/jerboa/ui/components/person/PersonProfileActivity.kt b/app/src/main/java/com/jerboa/ui/components/person/PersonProfileActivity.kt index 06abb0a27..f765c3802 100644 --- a/app/src/main/java/com/jerboa/ui/components/person/PersonProfileActivity.kt +++ b/app/src/main/java/com/jerboa/ui/components/person/PersonProfileActivity.kt @@ -33,19 +33,16 @@ import com.jerboa.db.AccountViewModel import com.jerboa.db.AppSettingsViewModel import com.jerboa.getLocalizedStringForUserTab import com.jerboa.isScrolledToEnd -import com.jerboa.loginFirstToast import com.jerboa.pagerTabIndicatorOffset2 import com.jerboa.scrollToTop import com.jerboa.ui.components.comment.CommentNodes import com.jerboa.ui.components.comment.edit.CommentEditViewModel import com.jerboa.ui.components.comment.reply.CommentReplyViewModel import com.jerboa.ui.components.comment.reply.ReplyItem -import com.jerboa.ui.components.common.BottomAppBarAll import com.jerboa.ui.components.common.getCurrentAccount import com.jerboa.ui.components.common.getPostViewMode import com.jerboa.ui.components.common.simpleVerticalScrollbar import com.jerboa.ui.components.community.CommunityLink -import com.jerboa.ui.components.home.HomeViewModel import com.jerboa.ui.components.home.SiteViewModel import com.jerboa.ui.components.post.PostListings import com.jerboa.ui.components.post.edit.PostEditViewModel @@ -60,7 +57,6 @@ fun PersonProfileActivity( navController: NavController, personProfileViewModel: PersonProfileViewModel, accountViewModel: AccountViewModel, - homeViewModel: HomeViewModel, commentEditViewModel: CommentEditViewModel, commentReplyViewModel: CommentReplyViewModel, postEditViewModel: PostEditViewModel, @@ -74,7 +70,6 @@ fun PersonProfileActivity( val postListState = rememberLazyListState() val ctx = LocalContext.current val account = getCurrentAccount(accountViewModel) - val bottomAppBarScreen = if (savedMode) { "saved" } else { "profile" } val snackbarHostState = remember { SnackbarHostState() } val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(rememberTopAppBarState()) @@ -152,33 +147,6 @@ fun PersonProfileActivity( showAvatar = siteViewModel.siteRes?.my_user?.local_user_view?.local_user?.show_avatars ?: true, ) }, - bottomBar = { - BottomAppBarAll( - showBottomNav = appSettingsViewModel.appSettings.value?.showBottomNav, - screen = bottomAppBarScreen, - unreadCounts = homeViewModel.unreadCountResponse, - onClickProfile = { - account?.id?.also { - navController.navigate(route = "profile/$it") - } - }, - onClickInbox = { - account?.also { - navController.navigate(route = "inbox") - } ?: run { - loginFirstToast(ctx) - } - }, - onClickSaved = { - account?.id?.also { - navController.navigate(route = "profile/$it?saved=${true}") - } ?: run { - loginFirstToast(ctx) - } - }, - navController = navController, - ) - }, ) } diff --git a/app/src/main/java/com/jerboa/ui/components/post/PostActivity.kt b/app/src/main/java/com/jerboa/ui/components/post/PostActivity.kt index 3cf6aaa58..8cf7c1501 100644 --- a/app/src/main/java/com/jerboa/ui/components/post/PostActivity.kt +++ b/app/src/main/java/com/jerboa/ui/components/post/PostActivity.kt @@ -102,6 +102,7 @@ fun PostActivity( } }, ) + Scaffold( modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { @@ -109,8 +110,7 @@ fun PostActivity( SimpleTopAppBar( stringResource(R.string.post_activity_comments), navController = navController, - scrollBehavior = - scrollBehavior, + scrollBehavior = scrollBehavior, ) if (postViewModel.loading) { LinearProgressIndicator(modifier = Modifier.fillMaxWidth()) diff --git a/app/src/main/java/com/jerboa/ui/components/post/create/CreatePost.kt b/app/src/main/java/com/jerboa/ui/components/post/create/CreatePost.kt index fc2b28f45..e07676b72 100644 --- a/app/src/main/java/com/jerboa/ui/components/post/create/CreatePost.kt +++ b/app/src/main/java/com/jerboa/ui/components/post/create/CreatePost.kt @@ -18,7 +18,6 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Add import androidx.compose.material.icons.outlined.ArrowDropDown -import androidx.compose.material.icons.outlined.Close import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon @@ -42,6 +41,7 @@ import com.jerboa.datatypes.CommunitySafe import com.jerboa.datatypes.sampleCommunitySafe import com.jerboa.db.Account import com.jerboa.ui.components.common.CircularIcon +import com.jerboa.ui.components.common.DefaultBackButton import com.jerboa.ui.components.common.MarkdownTextField import com.jerboa.ui.components.common.PickImage import com.jerboa.ui.theme.ICON_SIZE @@ -83,17 +83,8 @@ fun CreatePostHeader( } }, navigationIcon = { - IconButton( - onClick = { - navController.popBackStack() - }, - ) { - // Todo add are you sure cancel dialog - Icon( - Icons.Outlined.Close, - contentDescription = stringResource(R.string.create_post_close), - ) - } + // Todo add are you sure cancel dialog + DefaultBackButton(navController) }, ) } diff --git a/app/src/main/java/com/jerboa/ui/components/post/create/CreatePostActivity.kt b/app/src/main/java/com/jerboa/ui/components/post/create/CreatePostActivity.kt index 1e8d5414b..1ba4fff5b 100644 --- a/app/src/main/java/com/jerboa/ui/components/post/create/CreatePostActivity.kt +++ b/app/src/main/java/com/jerboa/ui/components/post/create/CreatePostActivity.kt @@ -73,6 +73,7 @@ fun CreatePostActivity( } } } + Surface(color = MaterialTheme.colorScheme.background) { Scaffold( topBar = { diff --git a/app/src/main/java/com/jerboa/ui/components/post/edit/PostEdit.kt b/app/src/main/java/com/jerboa/ui/components/post/edit/PostEdit.kt index 661299e79..383bf5df5 100644 --- a/app/src/main/java/com/jerboa/ui/components/post/edit/PostEdit.kt +++ b/app/src/main/java/com/jerboa/ui/components/post/edit/PostEdit.kt @@ -12,7 +12,6 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Close import androidx.compose.material.icons.outlined.Save import androidx.compose.material3.* import androidx.compose.runtime.Composable @@ -25,6 +24,7 @@ import androidx.navigation.NavController import androidx.navigation.compose.rememberNavController import com.jerboa.R import com.jerboa.db.Account +import com.jerboa.ui.components.common.DefaultBackButton import com.jerboa.ui.components.common.MarkdownTextField import com.jerboa.ui.components.common.PickImage import com.jerboa.ui.theme.MEDIUM_PADDING @@ -63,17 +63,8 @@ fun EditPostHeader( } }, navigationIcon = { - IconButton( - onClick = { - navController.popBackStack() - }, - ) { - // Todo add are you sure cancel dialog - Icon( - Icons.Outlined.Close, - contentDescription = stringResource(R.string.post_edit_close), - ) - } + // Todo add are you sure cancel dialog + DefaultBackButton(navController) }, ) } diff --git a/app/src/main/java/com/jerboa/ui/components/privatemessage/PrivateMessageReply.kt b/app/src/main/java/com/jerboa/ui/components/privatemessage/PrivateMessageReply.kt index ec19d540f..e0a28958b 100644 --- a/app/src/main/java/com/jerboa/ui/components/privatemessage/PrivateMessageReply.kt +++ b/app/src/main/java/com/jerboa/ui/components/privatemessage/PrivateMessageReply.kt @@ -22,6 +22,7 @@ import com.jerboa.R import com.jerboa.datatypes.PrivateMessageView import com.jerboa.datatypes.samplePrivateMessageView import com.jerboa.db.Account +import com.jerboa.ui.components.common.DefaultBackButton import com.jerboa.ui.components.common.MarkdownTextField import com.jerboa.ui.theme.LARGE_PADDING import com.jerboa.ui.theme.MEDIUM_PADDING @@ -55,18 +56,7 @@ fun PrivateMessageReplyHeader( } } }, - navigationIcon = { - IconButton( - onClick = { - navController.popBackStack() - }, - ) { - Icon( - Icons.Outlined.Close, - contentDescription = stringResource(R.string.private_message_reply_back), - ) - } - }, + navigationIcon = { DefaultBackButton(navController) }, ) } diff --git a/app/src/main/java/com/jerboa/ui/components/report/CreateReport.kt b/app/src/main/java/com/jerboa/ui/components/report/CreateReport.kt index ef2ddbf6a..ba4a4bcd1 100644 --- a/app/src/main/java/com/jerboa/ui/components/report/CreateReport.kt +++ b/app/src/main/java/com/jerboa/ui/components/report/CreateReport.kt @@ -20,6 +20,7 @@ import androidx.navigation.NavController import androidx.navigation.compose.rememberNavController import com.jerboa.R import com.jerboa.db.Account +import com.jerboa.ui.components.common.DefaultBackButton import com.jerboa.ui.components.common.MarkdownTextField @Composable @@ -51,18 +52,7 @@ fun CreateReportHeader( } } }, - navigationIcon = { - IconButton( - onClick = { - navController.popBackStack() - }, - ) { - Icon( - Icons.Outlined.Close, - contentDescription = stringResource(R.string.create_report_back), - ) - } - }, + navigationIcon = { DefaultBackButton(navController) }, ) } diff --git a/app/src/main/java/com/jerboa/ui/components/settings/account/AccountSettingsActivity.kt b/app/src/main/java/com/jerboa/ui/components/settings/account/AccountSettingsActivity.kt index 0ef47db25..0edf68890 100644 --- a/app/src/main/java/com/jerboa/ui/components/settings/account/AccountSettingsActivity.kt +++ b/app/src/main/java/com/jerboa/ui/components/settings/account/AccountSettingsActivity.kt @@ -34,7 +34,10 @@ fun AccountSettingsActivity( Scaffold( snackbarHost = { SnackbarHost(snackbarHostState) }, topBar = { - SimpleTopAppBar(text = stringResource(R.string.account_settings_activity_account_settings), navController = navController) + SimpleTopAppBar( + text = stringResource(R.string.account_settings_activity_account_settings), + navController = navController, + ) }, content = { padding -> account.also {