diff --git a/app/build.gradle.kts b/app/build.gradle.kts index fd5b82b43..2cdec31ac 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -104,6 +104,7 @@ dependencies { implementation("com.google.accompanist:accompanist-pager-indicators:0.30.1") implementation("com.google.accompanist:accompanist-flowlayout:0.30.1") implementation("com.google.accompanist:accompanist-permissions: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 ebf0d5a41..72bc1426b 100644 --- a/app/src/main/java/com/jerboa/MainActivity.kt +++ b/app/src/main/java/com/jerboa/MainActivity.kt @@ -2,8 +2,6 @@ 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 @@ -11,6 +9,9 @@ import android.util.Patterns import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.viewModels +import androidx.compose.animation.ExperimentalAnimationApi +import androidx.compose.animation.slideInHorizontally +import androidx.compose.animation.slideOutHorizontally import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState @@ -18,12 +19,12 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember 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.composable +import com.google.accompanist.navigation.animation.rememberAnimatedNavController import com.jerboa.api.ApiState import com.jerboa.api.MINIMUM_API_VERSION import com.jerboa.datatypes.types.GetCommunity @@ -114,11 +115,10 @@ class MainActivity : ComponentActivity() { AppSettingsViewModelFactory((application as JerboaApplication).appSettingsRepository) } + @OptIn(ExperimentalAnimationApi::class) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) - val accountSync = getCurrentAccountSync(accountViewModel) fetchInitialData(accountSync, siteViewModel, homeViewModel) @@ -129,7 +129,7 @@ class MainActivity : ComponentActivity() { JerboaTheme( appSettings = appSettings, ) { - val navController = rememberNavController() + val navController = rememberAnimatedNavController() val ctx = LocalContext.current val serverVersionOutdatedViewed = remember { mutableStateOf(false) } @@ -154,9 +154,13 @@ class MainActivity : ComponentActivity() { else -> {} } - NavHost( + AnimatedNavHost( navController = navController, startDestination = "home", + enterTransition = { slideInHorizontally { it } }, + exitTransition = { slideOutHorizontally { -it } }, + popEnterTransition = { slideInHorizontally { -it } }, + popExitTransition = { slideOutHorizontally { it } }, ) { composable( route = "login", @@ -175,16 +179,20 @@ class MainActivity : ComponentActivity() { composable( route = "home", ) { - HomeActivity( + BottomNavActivity( navController = navController, homeViewModel = homeViewModel, accountViewModel = accountViewModel, siteViewModel = siteViewModel, postEditViewModel = postEditViewModel, appSettingsViewModel = appSettingsViewModel, - showVotingArrowsInListView = appSettings?.showVotingArrowsInListView ?: true, - useCustomTabs = appSettings?.useCustomTabs ?: true, - usePrivateTabs = appSettings?.usePrivateTabs ?: false, + appSettings = appSettings, + communityListViewModel = communityListViewModel, + inboxViewModel = inboxViewModel, + commentReplyViewModel = commentReplyViewModel, + commentEditViewModel = commentEditViewModel, + personProfileViewModel = personProfileViewModel, + privateMessageReplyViewModel = privateMessageReplyViewModel, ) } composable( @@ -381,8 +389,6 @@ class MainActivity : ComponentActivity() { CommunityListActivity( navController = navController, accountViewModel = accountViewModel, - siteViewModel = siteViewModel, - appSettingsViewModel = appSettingsViewModel, communityListViewModel = communityListViewModel, selectMode = it.arguments?.getBoolean("select")!!, ) @@ -455,7 +461,6 @@ class MainActivity : ComponentActivity() { InboxActivity( navController = navController, - appSettingsViewModel = appSettingsViewModel, inboxViewModel = inboxViewModel, accountViewModel = accountViewModel, commentReplyViewModel = commentReplyViewModel, 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 6ba570e5b..4eb458fe8 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,9 +13,12 @@ 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.KeyboardArrowDown import androidx.compose.material.icons.filled.KeyboardArrowUp +import androidx.compose.material.icons.filled.Person import androidx.compose.material.icons.filled.Search import androidx.compose.material.icons.outlined.* import androidx.compose.material3.* @@ -40,7 +43,6 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.navigation.NavController -import androidx.navigation.compose.rememberNavController import com.google.accompanist.flowlayout.FlowCrossAxisAlignment import com.google.accompanist.flowlayout.FlowMainAxisAlignment import com.google.accompanist.flowlayout.FlowRow @@ -53,6 +55,7 @@ import com.jerboa.loginFirstToast import com.jerboa.scrollToNextParentComment import com.jerboa.scrollToPreviousParentComment import com.jerboa.siFormat +import com.jerboa.ui.components.home.BottomNavTab import com.jerboa.ui.components.person.PersonProfileLink import com.jerboa.ui.theme.* import kotlinx.coroutines.CoroutineScope @@ -84,13 +87,10 @@ fun SimpleTopAppBar( @Composable fun BottomAppBarAll( - navController: NavController = rememberNavController(), - screen: String, - unreadCount: Int, + selectedTab: BottomNavTab, + onSelect: (BottomNavTab) -> Unit, + unreadCounts: Int, showBottomNav: Boolean? = true, - onClickSaved: () -> Unit, - onClickProfile: () -> Unit, - onClickInbox: () -> Unit, ) { if (showBottomNav == true) { // Check for preview mode @@ -108,96 +108,60 @@ fun BottomAppBarAll( } NavigationBar { - NavigationBarItem( - icon = { - Icon( - imageVector = Icons.Filled.Home, - contentDescription = stringResource(R.string.bottomBar_home), - ) - }, - label = { - Text( - text = stringResource(R.string.bottomBar_label_home), - color = MaterialTheme.colorScheme.onSurface, - ) - }, - selected = screen == "home", - onClick = { - navController.navigate("home") - }, - ) - - NavigationBarItem( - icon = { - Icon( - imageVector = Icons.Filled.Search, - contentDescription = stringResource(R.string.bottomBar_search), - ) - }, - label = { - Text( - text = stringResource(R.string.bottomBar_label_search), - color = MaterialTheme.colorScheme.onSurface, - ) - }, - selected = screen == "communityList", - onClick = { - navController.navigate("communityList") - }, - ) - NavigationBarItem( - icon = { - InboxIconAndBadge( - iconBadgeCount = unreadCount, - icon = Icons.Outlined.Email, - contentDescription = stringResource(R.string.bottomBar_inbox), - ) - }, - label = { - Text( - text = stringResource(R.string.bottomBar_label_inbox), - color = MaterialTheme.colorScheme.onSurface, - ) - }, - selected = screen == "inbox", - onClick = { - onClickInbox() - }, - ) - NavigationBarItem( - icon = { - Icon( - imageVector = Icons.Outlined.Bookmarks, - contentDescription = stringResource(R.string.bottomBar_bookmarks), - ) - }, - label = { - Text( - text = stringResource(R.string.bottomBar_label_bookmarks), - color = MaterialTheme.colorScheme.onSurface, - ) - }, - selected = screen == "saved", - onClick = { - onClickSaved() - }, - ) - NavigationBarItem( - icon = { - Icon( - imageVector = Icons.Outlined.Person, - contentDescription = stringResource(R.string.bottomBar_profile), - ) - }, - label = { - Text( - text = stringResource(R.string.bottomBar_label_profile), - color = MaterialTheme.colorScheme.onSurface, - ) - }, - selected = screen == "profile", - onClick = onClickProfile, - ) + for (tab in BottomNavTab.values()) { + val selected = tab == selectedTab + NavigationBarItem( + icon = { + InboxIconAndBadge( + iconBadgeCount = if (tab == BottomNavTab.Inbox) unreadCounts else null, + icon = if (selected) { + when (tab) { + BottomNavTab.Home -> Icons.Filled.Home + BottomNavTab.Search -> Icons.Filled.Search + BottomNavTab.Inbox -> Icons.Filled.Email + BottomNavTab.Saved -> Icons.Filled.Bookmarks + BottomNavTab.Profile -> Icons.Filled.Person + } + } else { + when (tab) { + BottomNavTab.Home -> Icons.Outlined.Home + BottomNavTab.Search -> Icons.Outlined.Search + BottomNavTab.Inbox -> Icons.Outlined.Email + BottomNavTab.Saved -> Icons.Outlined.Bookmarks + BottomNavTab.Profile -> Icons.Outlined.Person + } + }, + contentDescription = stringResource( + when (tab) { + BottomNavTab.Home -> R.string.bottomBar_home + BottomNavTab.Search -> R.string.bottomBar_search + BottomNavTab.Inbox -> R.string.bottomBar_inbox + BottomNavTab.Saved -> R.string.bottomBar_bookmarks + BottomNavTab.Profile -> R.string.bottomBar_profile + }, + ), + ) + }, + label = { + Text( + text = stringResource( + when (tab) { + BottomNavTab.Home -> R.string.bottomBar_label_home + BottomNavTab.Search -> R.string.bottomBar_label_search + BottomNavTab.Inbox -> R.string.bottomBar_label_inbox + BottomNavTab.Saved -> R.string.bottomBar_label_bookmarks + BottomNavTab.Profile -> R.string.bottomBar_label_profile + }, + ), + color = MaterialTheme.colorScheme.onSurface, + ) + }, + selected = selected, + onClick = { + onSelect(tab) + }, + ) + } } } } @@ -206,11 +170,9 @@ fun BottomAppBarAll( @Composable fun BottomAppBarAllPreview() { BottomAppBarAll( - onClickInbox = {}, - onClickProfile = {}, - onClickSaved = {}, - unreadCount = 0, - screen = "home", + selectedTab = BottomNavTab.Home, + onSelect = {}, + unreadCounts = 30, showBottomNav = true, ) } 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..9d758ab5d --- /dev/null +++ b/app/src/main/java/com/jerboa/ui/components/common/DefaultBackButton.kt @@ -0,0 +1,25 @@ +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.remember +import androidx.compose.ui.res.stringResource +import androidx.navigation.NavController +import com.jerboa.R + +@Composable +fun DefaultBackButton(navController: NavController) { + val canPop = remember { navController.previousBackStackEntry != null } + + if (canPop) { + IconButton(onClick = navController::navigateUp) { + Icon( + Icons.Outlined.ArrowBack, + contentDescription = stringResource(R.string.community_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 21f8e9a76..8e860d018 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.types.CommunityView import com.jerboa.datatypes.types.SortType import com.jerboa.datatypes.types.SubscribedType import com.jerboa.getLocalizedSortingTypeName +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 @@ -180,14 +181,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 8d021ebce..dd67793d3 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 @@ -41,12 +41,10 @@ import com.jerboa.datatypes.types.SavePost import com.jerboa.datatypes.types.SubscribedType import com.jerboa.db.AccountViewModel import com.jerboa.db.AppSettingsViewModel -import com.jerboa.loginFirstToast import com.jerboa.newVote import com.jerboa.scrollToTop import com.jerboa.ui.components.common.ApiEmptyText import com.jerboa.ui.components.common.ApiErrorText -import com.jerboa.ui.components.common.BottomAppBarAll import com.jerboa.ui.components.common.LoadingBar import com.jerboa.ui.components.common.getCurrentAccount import com.jerboa.ui.components.common.getPostViewMode @@ -349,34 +347,5 @@ fun CommunityActivity( else -> {} } }, - bottomBar = { - BottomAppBarAll( - showBottomNav = appSettingsViewModel.appSettings.value?.showBottomNav, - screen = "communityList", - unreadCount = siteViewModel.getUnreadCountTotal(), - 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 cfa5ee37f..e0d191f84 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 @@ -26,6 +26,7 @@ import androidx.navigation.compose.rememberNavController import com.jerboa.R import com.jerboa.datatypes.sampleCommunityView import com.jerboa.datatypes.types.* +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 @@ -55,18 +56,7 @@ fun CommunityListHeader( ) } }, - navigationIcon = { - IconButton( - onClick = { - navController.popBackStack() - }, - ) { - Icon( - Icons.Outlined.ArrowBack, - 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 7ef4dddaa..3c81ac3d2 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 @@ -13,7 +13,6 @@ 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.platform.LocalContext import androidx.navigation.NavController import com.jerboa.DEBOUNCE_DELAY import com.jerboa.api.ApiState @@ -21,14 +20,10 @@ import com.jerboa.datatypes.types.Search import com.jerboa.datatypes.types.SearchType import com.jerboa.datatypes.types.SortType import com.jerboa.db.AccountViewModel -import com.jerboa.db.AppSettingsViewModel -import com.jerboa.loginFirstToast import com.jerboa.ui.components.common.ApiEmptyText import com.jerboa.ui.components.common.ApiErrorText -import com.jerboa.ui.components.common.BottomAppBarAll import com.jerboa.ui.components.common.LoadingBar import com.jerboa.ui.components.common.getCurrentAccount -import com.jerboa.ui.components.home.SiteViewModel import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -40,8 +35,6 @@ fun CommunityListActivity( navController: NavController, communityListViewModel: CommunityListViewModel, accountViewModel: AccountViewModel, - siteViewModel: SiteViewModel, - appSettingsViewModel: AppSettingsViewModel, selectMode: Boolean = false, ) { Log.d("jerboa", "got to community list activity") @@ -51,7 +44,6 @@ fun CommunityListActivity( var search by rememberSaveable { mutableStateOf("") } val scope = rememberCoroutineScope() - val ctx = LocalContext.current Surface(color = MaterialTheme.colorScheme.background) { Scaffold( @@ -102,35 +94,6 @@ fun CommunityListActivity( } } }, - bottomBar = { - BottomAppBarAll( - showBottomNav = appSettingsViewModel.appSettings.value?.showBottomNav, - screen = "communityList", - unreadCount = siteViewModel.getUnreadCountTotal(), - 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/BottomNavActivity.kt b/app/src/main/java/com/jerboa/ui/components/home/BottomNavActivity.kt new file mode 100644 index 000000000..bce3bcbd6 --- /dev/null +++ b/app/src/main/java/com/jerboa/ui/components/home/BottomNavActivity.kt @@ -0,0 +1,272 @@ +package com.jerboa.ui.components.home + +import androidx.compose.animation.ExperimentalAnimationApi +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.isImeVisible +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.DrawerValue +import androidx.compose.material3.ModalDrawerSheet +import androidx.compose.material3.ModalNavigationDrawer +import androidx.compose.material3.Scaffold +import androidx.compose.material3.rememberDrawerState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.semantics.testTagsAsResourceId +import androidx.compose.ui.unit.dp +import androidx.navigation.NavController +import com.google.accompanist.navigation.animation.AnimatedNavHost +import com.google.accompanist.navigation.animation.composable +import com.google.accompanist.navigation.animation.rememberAnimatedNavController +import com.jerboa.datatypes.types.GetPersonDetails +import com.jerboa.datatypes.types.GetPersonMentions +import com.jerboa.datatypes.types.GetPrivateMessages +import com.jerboa.datatypes.types.GetReplies +import com.jerboa.datatypes.types.SortType +import com.jerboa.db.AccountViewModel +import com.jerboa.db.AppSettings +import com.jerboa.db.AppSettingsViewModel +import com.jerboa.loginFirstToast +import com.jerboa.ui.components.comment.edit.CommentEditViewModel +import com.jerboa.ui.components.comment.reply.CommentReplyViewModel +import com.jerboa.ui.components.common.BottomAppBarAll +import com.jerboa.ui.components.common.getCurrentAccount +import com.jerboa.ui.components.community.list.CommunityListActivity +import com.jerboa.ui.components.community.list.CommunityListViewModel +import com.jerboa.ui.components.inbox.InboxActivity +import com.jerboa.ui.components.inbox.InboxViewModel +import com.jerboa.ui.components.person.PersonProfileActivity +import com.jerboa.ui.components.person.PersonProfileViewModel +import com.jerboa.ui.components.post.edit.PostEditViewModel +import com.jerboa.ui.components.privatemessage.PrivateMessageReplyViewModel + +enum class BottomNavTab { + Home, Search, Inbox, Saved, Profile; + + fun needsLogin() = this == Inbox || this == Saved || this == Profile +} + +@OptIn( + ExperimentalAnimationApi::class, + ExperimentalLayoutApi::class, + ExperimentalComposeUiApi::class, +) +@Composable +fun BottomNavActivity( + navController: NavController, + homeViewModel: HomeViewModel, + accountViewModel: AccountViewModel, + siteViewModel: SiteViewModel, + postEditViewModel: PostEditViewModel, + appSettingsViewModel: AppSettingsViewModel, + appSettings: AppSettings?, + communityListViewModel: CommunityListViewModel, + inboxViewModel: InboxViewModel, + commentReplyViewModel: CommentReplyViewModel, + commentEditViewModel: CommentEditViewModel, + personProfileViewModel: PersonProfileViewModel, + privateMessageReplyViewModel: PrivateMessageReplyViewModel, +) { + val account = getCurrentAccount(accountViewModel) + val ctx = LocalContext.current + val scope = rememberCoroutineScope() + + val bottomNavController = rememberAnimatedNavController() + var selectedTab by rememberSaveable { mutableStateOf(BottomNavTab.Home) } + val onSelectTab = { tab: BottomNavTab -> + if (tab.needsLogin() && account == null) { + loginFirstToast(ctx) + } else { + selectedTab = tab + bottomNavController.navigate(tab.name) { + launchSingleTop = true + popUpTo(0) // To make back button close the app. + } + } + } + + val drawerState = rememberDrawerState(DrawerValue.Closed) + + ModalNavigationDrawer( + gesturesEnabled = selectedTab == BottomNavTab.Home, + drawerState = drawerState, + drawerContent = { + ModalDrawerSheet( + content = { + MainDrawer( + siteViewModel = siteViewModel, + navController = navController, + accountViewModel = accountViewModel, + homeViewModel = homeViewModel, + scope = scope, + drawerState = drawerState, + onSelectTab = if (appSettings?.showBottomNav == true) onSelectTab else null, + ) + }, + ) + }, + modifier = Modifier.semantics { testTagsAsResourceId = true }, + content = { + Scaffold( + bottomBar = { + BottomAppBarAll( + showBottomNav = appSettingsViewModel.appSettings.value?.showBottomNav, + selectedTab = selectedTab, + unreadCounts = siteViewModel.getUnreadCountTotal(), + onSelect = onSelectTab, + ) + }, + ) { padding -> + val bottomPadding = + if (selectedTab == BottomNavTab.Search && WindowInsets.isImeVisible) { + 0.dp + } else { + padding.calculateBottomPadding() + } + + AnimatedNavHost( + navController = bottomNavController, + startDestination = BottomNavTab.Home.name, + modifier = Modifier.padding(bottom = bottomPadding), + ) { + composable(route = BottomNavTab.Home.name) { + HomeActivity( + navController = navController, + homeViewModel = homeViewModel, + accountViewModel = accountViewModel, + siteViewModel = siteViewModel, + postEditViewModel = postEditViewModel, + appSettingsViewModel = appSettingsViewModel, + showVotingArrowsInListView = appSettings?.showVotingArrowsInListView + ?: true, + useCustomTabs = appSettings?.useCustomTabs ?: true, + usePrivateTabs = appSettings?.usePrivateTabs ?: false, + drawerState = drawerState, + ) + } + + composable(route = BottomNavTab.Search.name) { + LaunchedEffect(Unit) { + // Whenever navigating here, reset the list with your followed communities + communityListViewModel.setCommunityListFromFollowed(siteViewModel) + } + + CommunityListActivity( + navController = navController, + accountViewModel = accountViewModel, + communityListViewModel = communityListViewModel, + selectMode = it.arguments?.getBoolean("select")!!, + ) + } + + composable(route = BottomNavTab.Inbox.name) { + if (account != null) { + LaunchedEffect(Unit) { + inboxViewModel.resetPage() + inboxViewModel.getReplies( + GetReplies( + auth = account.jwt, + ), + ) + inboxViewModel.getMentions( + GetPersonMentions( + auth = account.jwt, + ), + ) + inboxViewModel.getMessages( + GetPrivateMessages( + auth = account.jwt, + ), + ) + } + } + + InboxActivity( + navController = navController, + inboxViewModel = inboxViewModel, + accountViewModel = accountViewModel, + commentReplyViewModel = commentReplyViewModel, + siteViewModel = siteViewModel, + privateMessageReplyViewModel = privateMessageReplyViewModel, + ) + } + + composable(route = BottomNavTab.Saved.name) { + val savedMode = true + LaunchedEffect(Unit) { + val personId = account?.id!! + + personProfileViewModel.resetPage() + personProfileViewModel.getPersonDetails( + GetPersonDetails( + person_id = personId, + sort = SortType.New, + auth = account.jwt, + saved_only = savedMode, + ), + ) + } + + PersonProfileActivity( + savedMode = savedMode, + navController = navController, + personProfileViewModel = personProfileViewModel, + accountViewModel = accountViewModel, + commentEditViewModel = commentEditViewModel, + commentReplyViewModel = commentReplyViewModel, + postEditViewModel = postEditViewModel, + appSettingsViewModel = appSettingsViewModel, + showVotingArrowsInListView = appSettings?.showVotingArrowsInListView + ?: true, + siteViewModel = siteViewModel, + useCustomTabs = appSettings?.useCustomTabs ?: true, + usePrivateTabs = appSettings?.usePrivateTabs ?: false, + ) + } + + composable(route = BottomNavTab.Profile.name) { + val savedMode = false + LaunchedEffect(Unit) { + val personId = account?.id!! + + personProfileViewModel.resetPage() + personProfileViewModel.getPersonDetails( + GetPersonDetails( + person_id = personId, + sort = SortType.New, + auth = account.jwt, + saved_only = savedMode, + ), + ) + } + + PersonProfileActivity( + savedMode = savedMode, + navController = navController, + personProfileViewModel = personProfileViewModel, + accountViewModel = accountViewModel, + commentEditViewModel = commentEditViewModel, + commentReplyViewModel = commentReplyViewModel, + postEditViewModel = postEditViewModel, + appSettingsViewModel = appSettingsViewModel, + showVotingArrowsInListView = appSettings?.showVotingArrowsInListView + ?: true, + siteViewModel = siteViewModel, + useCustomTabs = appSettings?.useCustomTabs ?: true, + usePrivateTabs = appSettings?.usePrivateTabs ?: false, + ) + } + } + } + }, + ) +} 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 123b941a4..4f6f0ff8f 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 @@ -15,19 +15,15 @@ import androidx.compose.material.pullrefresh.PullRefreshIndicator import androidx.compose.material.pullrefresh.pullRefresh import androidx.compose.material.pullrefresh.rememberPullRefreshState import androidx.compose.material3.DrawerState -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.ModalDrawerSheet -import androidx.compose.material3.ModalNavigationDrawer 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.rememberDrawerState import androidx.compose.material3.rememberTopAppBarState import androidx.compose.runtime.Composable import androidx.compose.runtime.remember @@ -60,7 +56,6 @@ import com.jerboa.newVote import com.jerboa.scrollToTop import com.jerboa.ui.components.common.ApiEmptyText import com.jerboa.ui.components.common.ApiErrorText -import com.jerboa.ui.components.common.BottomAppBarAll import com.jerboa.ui.components.common.LoadingBar import com.jerboa.ui.components.common.getCurrentAccount import com.jerboa.ui.components.common.getPostViewMode @@ -80,116 +75,66 @@ fun HomeActivity( showVotingArrowsInListView: Boolean, useCustomTabs: Boolean, usePrivateTabs: Boolean, + drawerState: DrawerState, ) { 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 ctx = LocalContext.current val account = getCurrentAccount(accountViewModel) - ModalNavigationDrawer( - drawerState = drawerState, - drawerContent = { - ModalDrawerSheet( - content = { - MainDrawer( - siteViewModel = siteViewModel, - navController = navController, - accountViewModel = accountViewModel, - homeViewModel = homeViewModel, - scope = scope, - drawerState = drawerState, - ctx = ctx, - ) - }, + Scaffold( + modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection) + .semantics { testTagsAsResourceId = true }, + snackbarHost = { SnackbarHost(snackbarHostState) }, + topBar = { + MainTopBar( + scope = scope, + postListState = postListState, + drawerState = drawerState, + homeViewModel = homeViewModel, + appSettingsViewModel = appSettingsViewModel, + account = account, + navController = navController, + scrollBehavior = scrollBehavior, ) }, - content = { - Scaffold( - modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection) - .semantics { testTagsAsResourceId = true }, - snackbarHost = { SnackbarHost(snackbarHostState) }, - topBar = { - MainTopBar( - scope = scope, - postListState = postListState, - drawerState = drawerState, - homeViewModel = homeViewModel, - appSettingsViewModel = appSettingsViewModel, - account = account, - 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, - useCustomTabs = useCustomTabs, - usePrivateTabs = usePrivateTabs, - ) - }, - 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), - ) + content = { padding -> + MainPostListingsContent( + padding = padding, + homeViewModel = homeViewModel, + siteViewModel = siteViewModel, + postEditViewModel = postEditViewModel, + appSettingsViewModel = appSettingsViewModel, + account = account, + ctx = ctx, + navController = navController, + postListState = postListState, + showVotingArrowsInListView = showVotingArrowsInListView, + useCustomTabs = useCustomTabs, + usePrivateTabs = usePrivateTabs, + ) + }, + floatingActionButtonPosition = FabPosition.End, + floatingActionButton = { + FloatingActionButton( + onClick = { + account?.also { + navController.navigate("createPost") + } ?: run { + loginFirstToast(ctx) } }, - bottomBar = { - BottomAppBarAll( - showBottomNav = appSettingsViewModel.appSettings.value?.showBottomNav, - screen = "home", - unreadCount = siteViewModel.getUnreadCountTotal(), - 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, - ) - }, - ) + ) { + Icon( + imageVector = Icons.Outlined.Add, + contentDescription = stringResource(R.string.floating_createPost), + ) + } }, - modifier = Modifier.semantics { testTagsAsResourceId = true }, ) } @@ -381,9 +326,11 @@ fun MainDrawer( accountViewModel: AccountViewModel, homeViewModel: HomeViewModel, scope: CoroutineScope, - ctx: Context, drawerState: DrawerState, + onSelectTab: ((BottomNavTab) -> Unit)?, ) { + val ctx = LocalContext.current + val accounts = accountViewModel.allAccounts.value val account = getCurrentAccount(accountViewModel) @@ -443,22 +390,32 @@ fun MainDrawer( closeDrawer(scope, drawerState) }, onClickProfile = { - account?.id?.also { - navController.navigate(route = "profile/$it") - closeDrawer(scope, drawerState) + onSelectTab?.invoke(BottomNavTab.Profile) ?: run { + account?.id?.also { + navController.navigate(route = "profile/$it") + } ?: run { + loginFirstToast(ctx) + } } + closeDrawer(scope, drawerState) }, onClickSaved = { - account?.id?.also { - navController.navigate(route = "profile/$it?saved=${true}") - closeDrawer(scope, drawerState) + onSelectTab?.invoke(BottomNavTab.Saved) ?: run { + account?.id?.also { + navController.navigate(route = "profile/$it?saved=${true}") + } ?: run { + loginFirstToast(ctx) + } } + closeDrawer(scope, drawerState) }, onClickInbox = { - account?.also { - navController.navigate(route = "inbox") - } ?: run { - loginFirstToast(ctx) + onSelectTab?.invoke(BottomNavTab.Inbox) ?: run { + account?.also { + navController.navigate(route = "inbox") + } ?: run { + loginFirstToast(ctx) + } } closeDrawer(scope, drawerState) }, @@ -467,7 +424,9 @@ fun MainDrawer( closeDrawer(scope, drawerState) }, onClickCommunities = { - navController.navigate(route = "communityList") + onSelectTab?.invoke(BottomNavTab.Search) ?: run { + navController.navigate(route = "communityList") + } closeDrawer(scope, drawerState) }, ) 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 10907e20e..ba81ae06f 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 @@ -26,6 +25,7 @@ import androidx.navigation.compose.rememberNavController import com.jerboa.R import com.jerboa.UnreadOrAll import com.jerboa.getLocalizedUnreadOrAllName +import com.jerboa.ui.components.common.DefaultBackButton import com.jerboa.ui.components.common.UnreadOrAllOptionsDialog @Composable @@ -58,14 +58,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 c47648386..f135be3d0 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 @@ -37,14 +37,12 @@ import com.jerboa.datatypes.types.MarkPrivateMessageAsRead import com.jerboa.datatypes.types.SaveComment 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.ApiEmptyText import com.jerboa.ui.components.common.ApiErrorText -import com.jerboa.ui.components.common.BottomAppBarAll import com.jerboa.ui.components.common.LoadingBar import com.jerboa.ui.components.common.getCurrentAccount import com.jerboa.ui.components.common.simpleVerticalScrollbar @@ -58,7 +56,6 @@ import kotlinx.coroutines.launch @Composable fun InboxActivity( navController: NavController, - appSettingsViewModel: AppSettingsViewModel, inboxViewModel: InboxViewModel, siteViewModel: SiteViewModel, accountViewModel: AccountViewModel, @@ -143,33 +140,6 @@ fun InboxActivity( scope = scope, ) }, - bottomBar = { - BottomAppBarAll( - showBottomNav = appSettingsViewModel.appSettings.value?.showBottomNav, - screen = "inbox", - unreadCount = siteViewModel.getUnreadCountTotal(), - 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/LoginViewModel.kt b/app/src/main/java/com/jerboa/ui/components/login/LoginViewModel.kt index 4a3c311bf..eaa7176e3 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 @@ -143,7 +143,9 @@ class LoginViewModel : ViewModel() { loading = false - navController.navigate(route = "home") + navController.navigate(route = "home") { + popUpTo(0) + } } else -> {} 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 f3156d899..e73b25781 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 @@ -36,6 +36,7 @@ import com.jerboa.datatypes.types.PersonView import com.jerboa.datatypes.types.SortType 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 @@ -214,14 +215,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 a2d783b80..75f89296d 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 @@ -40,7 +40,6 @@ 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.newVote import com.jerboa.pagerTabIndicatorOffset2 import com.jerboa.scrollToTop @@ -50,7 +49,6 @@ import com.jerboa.ui.components.comment.reply.CommentReplyViewModel import com.jerboa.ui.components.comment.reply.ReplyItem import com.jerboa.ui.components.common.ApiEmptyText import com.jerboa.ui.components.common.ApiErrorText -import com.jerboa.ui.components.common.BottomAppBarAll import com.jerboa.ui.components.common.LoadingBar import com.jerboa.ui.components.common.getCurrentAccount import com.jerboa.ui.components.common.getPostViewMode @@ -85,11 +83,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()) personProfileViewModel.updateSavedOnly(savedMode) @@ -178,33 +171,6 @@ fun PersonProfileActivity( usePrivateTabs = usePrivateTabs, ) }, - bottomBar = { - BottomAppBarAll( - showBottomNav = appSettingsViewModel.appSettings.value?.showBottomNav, - screen = bottomAppBarScreen, - unreadCount = siteViewModel.getUnreadCountTotal(), - 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, - ) - }, ) }