fix: back to top and top bar colors

This commit is contained in:
Diego Beraldin 2023-10-19 19:14:42 +02:00
parent 3826653010
commit ad54a90261
16 changed files with 325 additions and 66 deletions

View File

@ -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
}
},
)

View File

@ -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<Int> = 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,

View File

@ -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
}
},
)

View File

@ -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) {

View File

@ -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
}
},
)

View File

@ -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)
}

View File

@ -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 -> {

View File

@ -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) {

View File

@ -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) {

View File

@ -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) {

View File

@ -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,

View File

@ -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)
}

View File

@ -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 -> {

View File

@ -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 {

View File

@ -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(

View File

@ -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(