From ad54a902619feb2cf676b0419f30c106841754fb Mon Sep 17 00:00:00 2001 From: Diego Beraldin Date: Thu, 19 Oct 2023 19:14:42 +0200 Subject: [PATCH] fix: back to top and top bar colors --- .../communitydetail/CommunityDetailScreen.kt | 10 ++- .../components/FloatingActionButtonMenu.kt | 65 +++++++++-------- .../commonui/postdetail/PostDetailScreen.kt | 10 ++- .../commonui/saveditems/SavedItemsScreen.kt | 72 +++++++++++++++++-- .../commonui/userdetail/UserDetailScreen.kt | 10 ++- .../feature/home/postlist/PostListScreen.kt | 6 +- .../feature/home/postlist/PostsTopBar.kt | 4 -- .../inbox/mentions/InboxMentionsScreen.kt | 12 +++- .../inbox/messages/InboxMessagesScreen.kt | 12 +++- .../inbox/replies/InboxRepliesScreen.kt | 12 +++- .../profile/logged/ProfileLoggedScreen.kt | 20 +++++- .../feature/search/main/ExploreScreen.kt | 6 +- .../feature/search/main/ExploreTopBar.kt | 4 -- .../ManageSubscriptionsScreen.kt | 62 +++++++++++++++- .../detail/MultiCommunityScreen.kt | 65 ++++++++++++++++- .../feature/settings/main/SettingsScreen.kt | 21 ++++-- 16 files changed, 325 insertions(+), 66 deletions(-) diff --git a/core-commonui/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/commonui/communitydetail/CommunityDetailScreen.kt b/core-commonui/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/commonui/communitydetail/CommunityDetailScreen.kt index 1d8e96a12..bec041ec9 100644 --- a/core-commonui/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/commonui/communitydetail/CommunityDetailScreen.kt +++ b/core-commonui/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/commonui/communitydetail/CommunityDetailScreen.kt @@ -23,8 +23,8 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.ArrowCircleDown import androidx.compose.material.icons.filled.ArrowCircleUp -import androidx.compose.material.icons.filled.ArrowUpward import androidx.compose.material.icons.filled.Create +import androidx.compose.material.icons.filled.ExpandLess import androidx.compose.material.icons.outlined.AddCircleOutline import androidx.compose.material.icons.outlined.CheckCircle import androidx.compose.material.icons.outlined.Pending @@ -40,6 +40,7 @@ import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.rememberTopAppBarState import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect @@ -117,7 +118,8 @@ class CommunityDetailScreen( val navigator = remember { getNavigationCoordinator().getRootNavigator() } val bottomSheetNavigator = LocalBottomSheetNavigator.current val isOnOtherInstance = otherInstance.isNotEmpty() - val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior() + val topAppBarState = rememberTopAppBarState() + val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(topAppBarState) val isFabVisible = remember { mutableStateOf(true) } val fabNestedScrollConnection = remember { object : NestedScrollConnection { @@ -234,11 +236,13 @@ class CommunityDetailScreen( FloatingActionButtonMenu( items = buildList { this += FloatingActionButtonMenuItem( - icon = Icons.Default.ArrowUpward, + icon = Icons.Default.ExpandLess, text = stringResource(MR.strings.action_back_to_top), onSelected = { scope.launch { lazyListState.scrollToItem(0) + topAppBarState.heightOffset = 0f + topAppBarState.contentOffset = 0f } }, ) diff --git a/core-commonui/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/commonui/components/FloatingActionButtonMenu.kt b/core-commonui/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/commonui/components/FloatingActionButtonMenu.kt index cede0e248..ed1dac08e 100644 --- a/core-commonui/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/commonui/components/FloatingActionButtonMenu.kt +++ b/core-commonui/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/commonui/components/FloatingActionButtonMenu.kt @@ -3,11 +3,10 @@ package com.github.diegoberaldin.raccoonforlemmy.core.commonui.components import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.FastOutSlowInEasing import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.animateIntAsState import androidx.compose.animation.core.tween -import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut -import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -51,43 +50,59 @@ fun FloatingActionButtonMenu( var fabExpanded by remember { mutableStateOf(false) } val fabRotation by animateFloatAsState(if (fabExpanded) 45f else 0f) val enterTransition = remember { - expandVertically( - expandFrom = Alignment.Bottom, - animationSpec = tween(150, easing = FastOutSlowInEasing) - ) + fadeIn( + fadeIn( initialAlpha = 0.3f, animationSpec = tween(150, easing = FastOutSlowInEasing) ) } val exitTransition = remember { - shrinkVertically( - shrinkTowards = Alignment.Bottom, - animationSpec = tween(150, easing = FastOutSlowInEasing) - ) + fadeOut( + fadeOut( animationSpec = tween(150, easing = FastOutSlowInEasing) ) } + val numberOfItems by animateIntAsState( + targetValue = if (fabExpanded) items.size else 0, + animationSpec = tween(250 * items.size) + ) + val indices: List = if (numberOfItems == 0) { + emptyList() + } else { + buildList { + for (i in 0 until numberOfItems) { + add(items.size - i - 1) + } + } + } Column( horizontalAlignment = Alignment.End, ) { - AnimatedVisibility( - visible = fabExpanded, - enter = enterTransition, - exit = exitTransition + Column( + verticalArrangement = Arrangement.spacedBy(Spacing.xs), + horizontalAlignment = Alignment.End, ) { - Column( - verticalArrangement = Arrangement.spacedBy(Spacing.xs) - ) { - Spacer(modifier = Modifier.height(Spacing.m)) - for (item in items) { + Spacer(modifier = Modifier.height(Spacing.m)) + items.forEachIndexed { idx, item -> + AnimatedVisibility( + visible = idx in indices, + enter = enterTransition, + exit = exitTransition + ) { Row( modifier = Modifier.onClick { fabExpanded = false item.onSelected?.invoke() - }, + }.padding(end = 16.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(Spacing.xxs) ) { + Text( + modifier = Modifier.background( + color = MaterialTheme.colorScheme.background, + shape = RoundedCornerShape(CornerSize.s), + ).padding(vertical = Spacing.xs, horizontal = Spacing.s), + text = item.text, + style = MaterialTheme.typography.bodyMedium, + ) Icon( modifier = Modifier .size(26.dp) @@ -98,18 +113,10 @@ fun FloatingActionButtonMenu( imageVector = item.icon, contentDescription = null, ) - Text( - modifier = Modifier.background( - color = MaterialTheme.colorScheme.background, - shape = RoundedCornerShape(CornerSize.s), - ).padding(vertical = Spacing.xs, horizontal = Spacing.s), - text = item.text, - style = MaterialTheme.typography.bodyMedium, - ) } } - Spacer(modifier = Modifier.height(Spacing.xxs)) } + Spacer(modifier = Modifier.height(Spacing.xxs)) } FloatingActionButton( shape = CircleShape, diff --git a/core-commonui/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/commonui/postdetail/PostDetailScreen.kt b/core-commonui/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/commonui/postdetail/PostDetailScreen.kt index f25924ad1..be78b2ee8 100644 --- a/core-commonui/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/commonui/postdetail/PostDetailScreen.kt +++ b/core-commonui/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/commonui/postdetail/PostDetailScreen.kt @@ -28,7 +28,7 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.ArrowCircleDown import androidx.compose.material.icons.filled.ArrowCircleUp -import androidx.compose.material.icons.filled.ArrowUpward +import androidx.compose.material.icons.filled.ExpandLess import androidx.compose.material.icons.filled.Reply import androidx.compose.material.pullrefresh.PullRefreshIndicator import androidx.compose.material.pullrefresh.pullRefresh @@ -42,6 +42,7 @@ import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.rememberTopAppBarState import androidx.compose.material3.surfaceColorAtElevation import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider @@ -128,7 +129,8 @@ class PostDetailScreen( val isOnOtherInstance = otherInstance.isNotEmpty() val navigator = remember { getNavigationCoordinator().getRootNavigator() } val bottomSheetNavigator = LocalBottomSheetNavigator.current - val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior() + val topAppBarState = rememberTopAppBarState() + val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(topAppBarState) val isFabVisible = remember { mutableStateOf(true) } val fabNestedScrollConnection = remember { object : NestedScrollConnection { @@ -229,11 +231,13 @@ class PostDetailScreen( FloatingActionButtonMenu( items = buildList { this += FloatingActionButtonMenuItem( - icon = Icons.Default.ArrowUpward, + icon = Icons.Default.ExpandLess, text = stringResource(MR.strings.action_back_to_top), onSelected = { scope.launch { lazyListState.scrollToItem(0) + topAppBarState.heightOffset = 0f + topAppBarState.contentOffset = 0f } }, ) diff --git a/core-commonui/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/commonui/saveditems/SavedItemsScreen.kt b/core-commonui/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/commonui/saveditems/SavedItemsScreen.kt index 1ad92062b..68da57ad4 100644 --- a/core-commonui/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/commonui/saveditems/SavedItemsScreen.kt +++ b/core-commonui/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/commonui/saveditems/SavedItemsScreen.kt @@ -1,5 +1,8 @@ package com.github.diegoberaldin.raccoonforlemmy.core.commonui.saveditems +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -11,9 +14,11 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material.icons.filled.ExpandLess import androidx.compose.material.pullrefresh.PullRefreshIndicator import androidx.compose.material.pullrefresh.pullRefresh import androidx.compose.material.pullrefresh.rememberPullRefreshState @@ -25,13 +30,19 @@ import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.rememberTopAppBarState import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.input.nestedscroll.NestedScrollConnection +import androidx.compose.ui.input.nestedscroll.NestedScrollSource import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.unit.dp import cafe.adriel.voyager.core.model.rememberScreenModel @@ -42,6 +53,8 @@ import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.Spacing import com.github.diegoberaldin.raccoonforlemmy.core.architecture.bindToLifecycle import com.github.diegoberaldin.raccoonforlemmy.core.commonui.communitydetail.CommunityDetailScreen import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.CommentCard +import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.FloatingActionButtonMenu +import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.FloatingActionButtonMenuItem import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.PostCard import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.SectionSelector import com.github.diegoberaldin.raccoonforlemmy.core.commonui.createcomment.CreateCommentScreen @@ -59,6 +72,7 @@ import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.SortType import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.toIcon import com.github.diegoberaldin.raccoonforlemmy.resources.MR import dev.icerock.moko.resources.compose.stringResource +import kotlinx.coroutines.launch class SavedItemsScreen : Screen { @@ -70,8 +84,25 @@ class SavedItemsScreen : Screen { val uiState by model.uiState.collectAsState() val navigator = remember { getNavigationCoordinator().getRootNavigator() } val bottomSheetNavigator = LocalBottomSheetNavigator.current - val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior() + val topAppBarState = rememberTopAppBarState() + val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(topAppBarState) val notificationCenter = remember { getNotificationCenter() } + val isFabVisible = remember { mutableStateOf(true) } + val lazyListState = rememberLazyListState() + val scope = rememberCoroutineScope() + val fabNestedScrollConnection = remember { + object : NestedScrollConnection { + override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { + if (available.y < -1) { + isFabVisible.value = false + } + if (available.y > 1) { + isFabVisible.value = true + } + return Offset.Zero + } + } + } Scaffold( topBar = { @@ -116,12 +147,37 @@ class SavedItemsScreen : Screen { ) }, ) - } + }, + floatingActionButton = { + AnimatedVisibility( + visible = isFabVisible.value, + enter = slideInVertically( + initialOffsetY = { it * 2 }, + ), + exit = slideOutVertically( + targetOffsetY = { it * 2 }, + ), + ) { + FloatingActionButtonMenu( + items = buildList { + this += FloatingActionButtonMenuItem( + icon = Icons.Default.ExpandLess, + text = stringResource(MR.strings.action_back_to_top), + onSelected = { + scope.launch { + lazyListState.scrollToItem(0) + topAppBarState.heightOffset = 0f + topAppBarState.contentOffset = 0f + } + }, + ) + } + ) + } + }, ) { paddingValues -> Column( - modifier = Modifier - .padding(paddingValues) - .nestedScroll(scrollBehavior.nestedScrollConnection), + modifier = Modifier.padding(paddingValues), verticalArrangement = Arrangement.spacedBy(Spacing.s), ) { SectionSelector( @@ -146,9 +202,13 @@ class SavedItemsScreen : Screen { model.reduce(SavedItemsMviModel.Intent.Refresh) }) Box( - modifier = Modifier.fillMaxWidth().pullRefresh(pullRefreshState), + modifier = Modifier.fillMaxWidth() + .nestedScroll(scrollBehavior.nestedScrollConnection) + .pullRefresh(pullRefreshState) + .nestedScroll(fabNestedScrollConnection), ) { LazyColumn( + state = lazyListState, modifier = Modifier.padding(horizontal = Spacing.xxxs), ) { if (uiState.section == SavedItemsSection.Posts) { diff --git a/core-commonui/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/commonui/userdetail/UserDetailScreen.kt b/core-commonui/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/commonui/userdetail/UserDetailScreen.kt index c97ea0f24..24b83fdd9 100644 --- a/core-commonui/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/commonui/userdetail/UserDetailScreen.kt +++ b/core-commonui/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/commonui/userdetail/UserDetailScreen.kt @@ -24,8 +24,8 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.ArrowCircleDown import androidx.compose.material.icons.filled.ArrowCircleUp -import androidx.compose.material.icons.filled.ArrowUpward import androidx.compose.material.icons.filled.Chat +import androidx.compose.material.icons.filled.ExpandLess import androidx.compose.material.pullrefresh.PullRefreshIndicator import androidx.compose.material.pullrefresh.pullRefresh import androidx.compose.material.pullrefresh.rememberPullRefreshState @@ -39,6 +39,7 @@ import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.rememberTopAppBarState import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect @@ -115,7 +116,8 @@ class UserDetailScreen( val isOnOtherInstance = otherInstance.isNotEmpty() val bottomSheetNavigator = LocalBottomSheetNavigator.current val navigator = remember { getNavigationCoordinator().getRootNavigator() } - val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior() + val topAppBarState = rememberTopAppBarState() + val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(topAppBarState) val notificationCenter = remember { getNotificationCenter() } val isFabVisible = remember { mutableStateOf(true) } val fabNestedScrollConnection = remember { @@ -214,11 +216,13 @@ class UserDetailScreen( FloatingActionButtonMenu( items = buildList { this += FloatingActionButtonMenuItem( - icon = Icons.Default.ArrowUpward, + icon = Icons.Default.ExpandLess, text = stringResource(MR.strings.action_back_to_top), onSelected = { scope.launch { lazyListState.scrollToItem(0) + topAppBarState.heightOffset = 0f + topAppBarState.contentOffset = 0f } }, ) diff --git a/feature-home/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/home/postlist/PostListScreen.kt b/feature-home/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/home/postlist/PostListScreen.kt index 78aa58ea0..b1f522fef 100644 --- a/feature-home/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/home/postlist/PostListScreen.kt +++ b/feature-home/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/home/postlist/PostListScreen.kt @@ -26,6 +26,7 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.rememberTopAppBarState import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.DisposableEffect @@ -84,7 +85,8 @@ class PostListScreen : Screen { model.bindToLifecycle(key) val uiState by model.uiState.collectAsState() val bottomSheetNavigator = LocalBottomSheetNavigator.current - val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior() + val topAppBarState = rememberTopAppBarState() + val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(topAppBarState) val navigationCoordinator = remember { getNavigationCoordinator() } val navigator = remember { navigationCoordinator.getRootNavigator() } val notificationCenter = remember { getNotificationCenter() } @@ -99,6 +101,8 @@ class PostListScreen : Screen { navigationCoordinator.onDoubleTabSelection.onEach { tab -> if (tab == HomeTab) { lazyListState.scrollToItem(0) + topAppBarState.heightOffset = 0f + topAppBarState.contentOffset = 0f } }.launchIn(this) } diff --git a/feature-home/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/home/postlist/PostsTopBar.kt b/feature-home/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/home/postlist/PostsTopBar.kt index bd4e171a9..e8e7e4f39 100644 --- a/feature-home/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/home/postlist/PostsTopBar.kt +++ b/feature-home/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/home/postlist/PostsTopBar.kt @@ -13,7 +13,6 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar -import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -41,9 +40,6 @@ internal fun PostsTopBar( ) { TopAppBar( scrollBehavior = scrollBehavior, - colors = TopAppBarDefaults.topAppBarColors( - scrolledContainerColor = MaterialTheme.colorScheme.background, - ), navigationIcon = { when { onHamburgerTapped != null -> { diff --git a/feature-inbox/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/inbox/mentions/InboxMentionsScreen.kt b/feature-inbox/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/inbox/mentions/InboxMentionsScreen.kt index b0b1d3cc1..b0306bdea 100644 --- a/feature-inbox/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/inbox/mentions/InboxMentionsScreen.kt +++ b/feature-inbox/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/inbox/mentions/InboxMentionsScreen.kt @@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.DismissDirection import androidx.compose.material.DismissValue import androidx.compose.material.ExperimentalMaterialApi @@ -49,6 +50,7 @@ import com.github.diegoberaldin.raccoonforlemmy.core.commonui.di.getNavigationCo import com.github.diegoberaldin.raccoonforlemmy.core.commonui.postdetail.PostDetailScreen import com.github.diegoberaldin.raccoonforlemmy.core.commonui.userdetail.UserDetailScreen import com.github.diegoberaldin.raccoonforlemmy.feature.inbox.di.getInboxMentionsViewModel +import com.github.diegoberaldin.raccoonforlemmy.feature.inbox.ui.InboxTab import com.github.diegoberaldin.raccoonforlemmy.resources.MR import dev.icerock.moko.resources.compose.stringResource import kotlinx.coroutines.flow.launchIn @@ -69,7 +71,14 @@ class InboxMentionsScreen : Tab { val uiState by model.uiState.collectAsState() val navigationCoordinator = remember { getNavigationCoordinator() } val navigator = remember { navigationCoordinator.getRootNavigator() } - + val lazyListState = rememberLazyListState() + LaunchedEffect(navigationCoordinator) { + navigationCoordinator.onDoubleTabSelection.onEach { + if (it == InboxTab) { + lazyListState.scrollToItem(0) + } + }.launchIn(this) + } LaunchedEffect(model) { model.effects.onEach { effect -> when (effect) { @@ -88,6 +97,7 @@ class InboxMentionsScreen : Tab { ) { LazyColumn( modifier = Modifier.fillMaxSize(), + state = lazyListState, verticalArrangement = Arrangement.spacedBy(Spacing.xs), ) { if (uiState.mentions.isEmpty() && uiState.initial) { diff --git a/feature-inbox/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/inbox/messages/InboxMessagesScreen.kt b/feature-inbox/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/inbox/messages/InboxMessagesScreen.kt index 9cfcdfa38..d214916ca 100644 --- a/feature-inbox/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/inbox/messages/InboxMessagesScreen.kt +++ b/feature-inbox/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/inbox/messages/InboxMessagesScreen.kt @@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.Text import androidx.compose.material.pullrefresh.PullRefreshIndicator @@ -39,6 +40,7 @@ import com.github.diegoberaldin.raccoonforlemmy.core.commonui.chat.InboxChatScre import com.github.diegoberaldin.raccoonforlemmy.core.commonui.di.getNavigationCoordinator import com.github.diegoberaldin.raccoonforlemmy.core.commonui.userdetail.UserDetailScreen import com.github.diegoberaldin.raccoonforlemmy.feature.inbox.di.getInboxMessagesViewModel +import com.github.diegoberaldin.raccoonforlemmy.feature.inbox.ui.InboxTab import com.github.diegoberaldin.raccoonforlemmy.resources.MR import dev.icerock.moko.resources.compose.stringResource import kotlinx.coroutines.flow.launchIn @@ -59,7 +61,14 @@ class InboxMessagesScreen : Tab { val uiState by model.uiState.collectAsState() val navigationCoordinator = remember { getNavigationCoordinator() } val navigator = remember { navigationCoordinator.getRootNavigator() } - + val lazyListState = rememberLazyListState() + LaunchedEffect(navigationCoordinator) { + navigationCoordinator.onDoubleTabSelection.onEach { + if (it == InboxTab) { + lazyListState.scrollToItem(0) + } + }.launchIn(this) + } LaunchedEffect(model) { model.effects.onEach { effect -> when (effect) { @@ -78,6 +87,7 @@ class InboxMessagesScreen : Tab { ) { LazyColumn( modifier = Modifier.fillMaxSize(), + state = lazyListState, verticalArrangement = Arrangement.spacedBy(Spacing.xs), ) { if (uiState.chats.isEmpty() && uiState.initial) { diff --git a/feature-inbox/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/inbox/replies/InboxRepliesScreen.kt b/feature-inbox/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/inbox/replies/InboxRepliesScreen.kt index 99d1b63fa..65d1117fc 100644 --- a/feature-inbox/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/inbox/replies/InboxRepliesScreen.kt +++ b/feature-inbox/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/inbox/replies/InboxRepliesScreen.kt @@ -11,6 +11,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.DismissDirection import androidx.compose.material.DismissValue @@ -55,6 +56,7 @@ import com.github.diegoberaldin.raccoonforlemmy.core.commonui.di.getNavigationCo import com.github.diegoberaldin.raccoonforlemmy.core.commonui.postdetail.PostDetailScreen import com.github.diegoberaldin.raccoonforlemmy.core.commonui.userdetail.UserDetailScreen import com.github.diegoberaldin.raccoonforlemmy.feature.inbox.di.getInboxRepliesViewModel +import com.github.diegoberaldin.raccoonforlemmy.feature.inbox.ui.InboxTab import com.github.diegoberaldin.raccoonforlemmy.resources.MR import dev.icerock.moko.resources.compose.stringResource import kotlinx.coroutines.flow.launchIn @@ -74,7 +76,14 @@ class InboxRepliesScreen : Tab { val uiState by model.uiState.collectAsState() val navigationCoordinator = remember { getNavigationCoordinator() } val navigator = remember { navigationCoordinator.getRootNavigator() } - + val lazyListState = rememberLazyListState() + LaunchedEffect(navigationCoordinator) { + navigationCoordinator.onDoubleTabSelection.onEach { + if (it == InboxTab) { + lazyListState.scrollToItem(0) + } + }.launchIn(this) + } LaunchedEffect(model) { model.effects.onEach { effect -> when (effect) { @@ -93,6 +102,7 @@ class InboxRepliesScreen : Tab { ) { LazyColumn( modifier = Modifier.fillMaxSize(), + state = lazyListState, verticalArrangement = Arrangement.spacedBy(Spacing.xs), ) { if (uiState.replies.isEmpty() && uiState.initial) { diff --git a/feature-profile/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/profile/logged/ProfileLoggedScreen.kt b/feature-profile/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/profile/logged/ProfileLoggedScreen.kt index b4e85ff6b..cee91ee12 100644 --- a/feature-profile/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/profile/logged/ProfileLoggedScreen.kt +++ b/feature-profile/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/profile/logged/ProfileLoggedScreen.kt @@ -12,6 +12,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.pullrefresh.PullRefreshIndicator import androidx.compose.material.pullrefresh.pullRefresh @@ -21,6 +22,7 @@ import androidx.compose.material3.Divider import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember @@ -51,8 +53,11 @@ import com.github.diegoberaldin.raccoonforlemmy.core.notifications.di.getNotific import com.github.diegoberaldin.raccoonforlemmy.core.utils.onClick import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.PostModel import com.github.diegoberaldin.raccoonforlemmy.feature.profile.di.getProfileLoggedViewModel +import com.github.diegoberaldin.raccoonforlemmy.feature.profile.ui.ProfileTab import com.github.diegoberaldin.raccoonforlemmy.resources.MR import dev.icerock.moko.resources.compose.stringResource +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach internal object ProfileLoggedScreen : Tab { @@ -74,8 +79,17 @@ internal object ProfileLoggedScreen : Tab { val uiState by model.uiState.collectAsState() val user = uiState.user val notificationCenter = remember { getNotificationCenter() } - val navigator = remember { getNavigationCoordinator().getRootNavigator() } + val navigationCoordinator = remember { getNavigationCoordinator() } + val navigator = remember { navigationCoordinator.getRootNavigator() } val bottomSheetNavigator = LocalBottomSheetNavigator.current + val lazyListState = rememberLazyListState() + LaunchedEffect(navigator) { + navigationCoordinator.onDoubleTabSelection.onEach { tab -> + if (tab == ProfileTab) { + lazyListState.scrollToItem(0) + } + }.launchIn(this) + } DisposableEffect(key) { onDispose { notificationCenter.removeObserver(key) @@ -89,7 +103,9 @@ internal object ProfileLoggedScreen : Tab { Box( modifier = Modifier.pullRefresh(pullRefreshState), ) { - LazyColumn { + LazyColumn( + state = lazyListState, + ) { item { Column( horizontalAlignment = Alignment.CenterHorizontally, diff --git a/feature-search/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/search/main/ExploreScreen.kt b/feature-search/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/search/main/ExploreScreen.kt index 75e6efd10..4b7587bd2 100644 --- a/feature-search/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/search/main/ExploreScreen.kt +++ b/feature-search/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/search/main/ExploreScreen.kt @@ -31,6 +31,7 @@ import androidx.compose.material3.Tab import androidx.compose.material3.Text import androidx.compose.material3.TextField import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.rememberTopAppBarState import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.DisposableEffect @@ -101,7 +102,8 @@ class ExploreScreen : Screen { val navigationCoordinator = remember { getNavigationCoordinator() } val navigator = remember { navigationCoordinator.getRootNavigator() } val bottomSheetNavigator = LocalBottomSheetNavigator.current - val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior() + val topAppBarState = rememberTopAppBarState() + val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(topAppBarState) val notificationCenter = remember { getNotificationCenter() } val drawerCoordinator = remember { getDrawerCoordinator() } val scope = rememberCoroutineScope() @@ -124,6 +126,8 @@ class ExploreScreen : Screen { navigationCoordinator.onDoubleTabSelection.onEach { tab -> if (tab == ExploreTab) { lazyListState.scrollToItem(0) + topAppBarState.heightOffset = 0f + topAppBarState.contentOffset = 0f } }.launchIn(this) } diff --git a/feature-search/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/search/main/ExploreTopBar.kt b/feature-search/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/search/main/ExploreTopBar.kt index ea1a65111..8e9979c47 100644 --- a/feature-search/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/search/main/ExploreTopBar.kt +++ b/feature-search/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/search/main/ExploreTopBar.kt @@ -13,7 +13,6 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar -import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -40,9 +39,6 @@ internal fun ExploreTopBar( ) { TopAppBar( scrollBehavior = scrollBehavior, - colors = TopAppBarDefaults.topAppBarColors( - scrolledContainerColor = MaterialTheme.colorScheme.background, - ), navigationIcon = { when { onHamburgerTapped != null -> { diff --git a/feature-search/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/search/managesubscriptions/ManageSubscriptionsScreen.kt b/feature-search/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/search/managesubscriptions/ManageSubscriptionsScreen.kt index c5b09833d..9aafc42a7 100644 --- a/feature-search/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/search/managesubscriptions/ManageSubscriptionsScreen.kt +++ b/feature-search/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/search/managesubscriptions/ManageSubscriptionsScreen.kt @@ -1,5 +1,8 @@ package com.github.diegoberaldin.raccoonforlemmy.feature.search.managesubscriptions +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement @@ -11,6 +14,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.DismissDirection import androidx.compose.material.DismissValue import androidx.compose.material.ExperimentalMaterialApi @@ -20,6 +24,7 @@ import androidx.compose.material.icons.filled.AddCircle import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.Delete import androidx.compose.material.icons.filled.Edit +import androidx.compose.material.icons.filled.ExpandLess import androidx.compose.material.icons.filled.Unsubscribe import androidx.compose.material.pullrefresh.PullRefreshIndicator import androidx.compose.material.pullrefresh.pullRefresh @@ -30,14 +35,20 @@ import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.rememberTopAppBarState import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.input.nestedscroll.NestedScrollConnection +import androidx.compose.ui.input.nestedscroll.NestedScrollSource import androidx.compose.ui.input.nestedscroll.nestedScroll import cafe.adriel.voyager.core.model.rememberScreenModel import cafe.adriel.voyager.core.screen.Screen @@ -45,6 +56,8 @@ import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.Spacing import com.github.diegoberaldin.raccoonforlemmy.core.architecture.bindToLifecycle import com.github.diegoberaldin.raccoonforlemmy.core.commonui.communitydetail.CommunityDetailScreen import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.CommunityItem +import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.FloatingActionButtonMenu +import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.FloatingActionButtonMenuItem import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.MultiCommunityItem import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.SwipeableCard import com.github.diegoberaldin.raccoonforlemmy.core.commonui.di.getNavigationCoordinator @@ -54,6 +67,7 @@ import com.github.diegoberaldin.raccoonforlemmy.feature.search.multicommunity.de import com.github.diegoberaldin.raccoonforlemmy.feature.search.multicommunity.editor.MultiCommunityEditorScreen import com.github.diegoberaldin.raccoonforlemmy.resources.MR import dev.icerock.moko.resources.compose.stringResource +import kotlinx.coroutines.launch class ManageSubscriptionsScreen : Screen { @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class) @@ -63,7 +77,24 @@ class ManageSubscriptionsScreen : Screen { model.bindToLifecycle(key) val uiState by model.uiState.collectAsState() val navigator = remember { getNavigationCoordinator().getRootNavigator() } - val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior() + val topAppBarState = rememberTopAppBarState() + val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(topAppBarState) + val isFabVisible = remember { mutableStateOf(true) } + val lazyListState = rememberLazyListState() + val scope = rememberCoroutineScope() + val fabNestedScrollConnection = remember { + object : NestedScrollConnection { + override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { + if (available.y < -1) { + isFabVisible.value = false + } + if (available.y > 1) { + isFabVisible.value = true + } + return Offset.Zero + } + } + } Scaffold( topBar = { @@ -88,6 +119,33 @@ class ManageSubscriptionsScreen : Screen { }, ) }, + floatingActionButton = { + AnimatedVisibility( + visible = isFabVisible.value, + enter = slideInVertically( + initialOffsetY = { it * 2 }, + ), + exit = slideOutVertically( + targetOffsetY = { it * 2 }, + ), + ) { + FloatingActionButtonMenu( + items = buildList { + this += FloatingActionButtonMenuItem( + icon = Icons.Default.ExpandLess, + text = stringResource(MR.strings.action_back_to_top), + onSelected = { + scope.launch { + lazyListState.scrollToItem(0) + topAppBarState.heightOffset = 0f + topAppBarState.contentOffset = 0f + } + }, + ) + } + ) + } + }, ) { paddingValues -> val pullRefreshState = rememberPullRefreshState(uiState.refreshing, { model.reduce(ManageSubscriptionsMviModel.Intent.Refresh) @@ -95,10 +153,12 @@ class ManageSubscriptionsScreen : Screen { Box( modifier = Modifier.padding(paddingValues) .nestedScroll(scrollBehavior.nestedScrollConnection) + .nestedScroll(fabNestedScrollConnection) .pullRefresh(pullRefreshState), ) { LazyColumn( modifier = Modifier.fillMaxSize(), + state = lazyListState, verticalArrangement = Arrangement.spacedBy(Spacing.xxs), ) { item { diff --git a/feature-search/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/search/multicommunity/detail/MultiCommunityScreen.kt b/feature-search/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/search/multicommunity/detail/MultiCommunityScreen.kt index 06038f14e..050213f09 100644 --- a/feature-search/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/search/multicommunity/detail/MultiCommunityScreen.kt +++ b/feature-search/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/search/multicommunity/detail/MultiCommunityScreen.kt @@ -1,5 +1,8 @@ package com.github.diegoberaldin.raccoonforlemmy.feature.search.multicommunity.detail +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row @@ -11,6 +14,7 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.DismissDirection import androidx.compose.material.DismissValue import androidx.compose.material.ExperimentalMaterialApi @@ -19,6 +23,7 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.ArrowCircleDown import androidx.compose.material.icons.filled.ArrowCircleUp +import androidx.compose.material.icons.filled.ExpandLess import androidx.compose.material.pullrefresh.PullRefreshIndicator import androidx.compose.material.pullrefresh.pullRefresh import androidx.compose.material.pullrefresh.rememberPullRefreshState @@ -30,16 +35,22 @@ import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.rememberTopAppBarState import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.input.nestedscroll.NestedScrollConnection +import androidx.compose.ui.input.nestedscroll.NestedScrollSource import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.text.style.TextOverflow @@ -53,6 +64,8 @@ import com.github.diegoberaldin.raccoonforlemmy.core.appearance.di.getThemeRepos import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.Spacing import com.github.diegoberaldin.raccoonforlemmy.core.architecture.bindToLifecycle import com.github.diegoberaldin.raccoonforlemmy.core.commonui.communitydetail.CommunityDetailScreen +import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.FloatingActionButtonMenu +import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.FloatingActionButtonMenuItem import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.PostCard import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.PostCardPlaceholder import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.SwipeableCard @@ -71,6 +84,7 @@ import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.toIcon import com.github.diegoberaldin.raccoonforlemmy.feature.search.di.getMultiCommunityViewModel import com.github.diegoberaldin.raccoonforlemmy.resources.MR import dev.icerock.moko.resources.compose.stringResource +import kotlinx.coroutines.launch class MultiCommunityScreen( private val community: MultiCommunityModel, @@ -83,10 +97,27 @@ class MultiCommunityScreen( model.bindToLifecycle(key) val uiState by model.uiState.collectAsState() val bottomSheetNavigator = LocalBottomSheetNavigator.current - val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior() + val topAppBarState = rememberTopAppBarState() + val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(topAppBarState) val bottomNavCoordinator = remember { getNavigationCoordinator() } val navigator = remember { bottomNavCoordinator.getRootNavigator() } val notificationCenter = remember { getNotificationCenter() } + val lazyListState = rememberLazyListState() + val scope = rememberCoroutineScope() + val isFabVisible = remember { mutableStateOf(true) } + val fabNestedScrollConnection = remember { + object : NestedScrollConnection { + override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { + if (available.y < -1) { + isFabVisible.value = false + } + if (available.y > 1) { + isFabVisible.value = true + } + return Offset.Zero + } + } + } DisposableEffect(key) { onDispose { notificationCenter.removeObserver(key) @@ -164,6 +195,33 @@ class MultiCommunityScreen( } ) }, + floatingActionButton = { + AnimatedVisibility( + visible = isFabVisible.value, + enter = slideInVertically( + initialOffsetY = { it * 2 }, + ), + exit = slideOutVertically( + targetOffsetY = { it * 2 }, + ), + ) { + FloatingActionButtonMenu( + items = buildList { + this += FloatingActionButtonMenuItem( + icon = Icons.Default.ExpandLess, + text = stringResource(MR.strings.action_back_to_top), + onSelected = { + scope.launch { + lazyListState.scrollToItem(0) + topAppBarState.heightOffset = 0f + topAppBarState.contentOffset = 0f + } + }, + ) + } + ) + } + } ) { padding -> val pullRefreshState = rememberPullRefreshState(uiState.refreshing, { model.reduce(MultiCommunityMviModel.Intent.Refresh) @@ -178,9 +236,12 @@ class MultiCommunityScreen( it.nestedScroll(connection) } else it } + .nestedScroll(fabNestedScrollConnection) .pullRefresh(pullRefreshState), ) { - LazyColumn { + LazyColumn( + state = lazyListState, + ) { if (uiState.posts.isEmpty() && uiState.loading) { items(5) { PostCardPlaceholder( diff --git a/feature-settings/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/settings/main/SettingsScreen.kt b/feature-settings/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/settings/main/SettingsScreen.kt index 002205849..aa7d9b04c 100644 --- a/feature-settings/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/settings/main/SettingsScreen.kt +++ b/feature-settings/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/settings/main/SettingsScreen.kt @@ -23,6 +23,7 @@ import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.rememberTopAppBarState import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect @@ -49,6 +50,7 @@ import com.github.diegoberaldin.raccoonforlemmy.core.appearance.di.getThemeRepos import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.Spacing import com.github.diegoberaldin.raccoonforlemmy.core.architecture.bindToLifecycle import com.github.diegoberaldin.raccoonforlemmy.core.commonui.di.getDrawerCoordinator +import com.github.diegoberaldin.raccoonforlemmy.core.commonui.di.getNavigationCoordinator import com.github.diegoberaldin.raccoonforlemmy.core.commonui.modals.ColorBottomSheet import com.github.diegoberaldin.raccoonforlemmy.core.commonui.modals.FontFamilyBottomSheet import com.github.diegoberaldin.raccoonforlemmy.core.commonui.modals.FontScaleBottomSheet @@ -90,10 +92,23 @@ class SettingsScreen : Screen { model.bindToLifecycle(SettingsTab.key) val uiState by model.uiState.collectAsState() val bottomSheetNavigator = LocalBottomSheetNavigator.current - val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior() + val topAppBarState = rememberTopAppBarState() + val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(topAppBarState) val notificationCenter = remember { getNotificationCenter() } val drawerCoordinator = remember { getDrawerCoordinator() } val scope = rememberCoroutineScope() + val navigationCoordinator = remember { getNavigationCoordinator() } + val navigator = remember { navigationCoordinator.getRootNavigator() } + val scrollState = rememberScrollState() + LaunchedEffect(navigator) { + navigationCoordinator.onDoubleTabSelection.onEach { tab -> + if (tab == SettingsTab) { + scrollState.scrollTo(0) + topAppBarState.heightOffset = 0f + topAppBarState.contentOffset = 0f + } + }.launchIn(this) + } DisposableEffect(key) { onDispose { notificationCenter.removeObserver(key) @@ -153,9 +168,7 @@ class SettingsScreen : Screen { Column( modifier = Modifier .fillMaxSize() - .verticalScroll( - rememberScrollState() - ), + .verticalScroll(scrollState), verticalArrangement = Arrangement.spacedBy(Spacing.xs), ) { SettingsHeader(