feat: profile side menu (#1081)

This commit is contained in:
Diego Beraldin 2024-07-05 20:31:12 +02:00 committed by GitHub
parent daf82cee84
commit 99a4e1594c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 215 additions and 86 deletions

View File

@ -73,11 +73,12 @@ fun DraggableSideMenu(
}
LaunchedEffect(draggableState) {
snapshotFlow { draggableState.currentValue }.onEach { value: SlideAnchorPosition ->
if (value == SlideAnchorPosition.Closed) {
onDismiss?.invoke()
}
}.launchIn(this)
snapshotFlow { draggableState.currentValue }
.onEach { value: SlideAnchorPosition ->
if (value == SlideAnchorPosition.Closed) {
onDismiss?.invoke()
}
}.launchIn(this)
}
Box(
@ -90,17 +91,12 @@ fun DraggableSideMenu(
x = draggableState.requireOffset().roundToInt(),
y = 0,
)
}
.anchoredDraggable(
}.anchoredDraggable(
state = draggableState,
orientation = Orientation.Horizontal,
)
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp))
).background(MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp))
.padding(
top = Spacing.xxl,
bottom = Spacing.m,
end = Spacing.s,
start = Spacing.s,
),
) {
content()

View File

@ -2,6 +2,8 @@ package com.github.diegoberaldin.raccoonforlemmy.feature.profile.di
import com.github.diegoberaldin.raccoonforlemmy.feature.profile.main.ProfileMainMviModel
import com.github.diegoberaldin.raccoonforlemmy.feature.profile.main.ProfileMainViewModel
import com.github.diegoberaldin.raccoonforlemmy.feature.profile.menu.ProfileSideMenuMviModel
import com.github.diegoberaldin.raccoonforlemmy.feature.profile.menu.ProfileSideMenuViewModel
import com.github.diegoberaldin.raccoonforlemmy.feature.profile.notlogged.ProfileNotLoggedMviModel
import com.github.diegoberaldin.raccoonforlemmy.feature.profile.notlogged.ProfileNotLoggedViewModel
import com.github.diegoberaldin.raccoonforlemmy.unit.login.di.loginModule
@ -27,4 +29,9 @@ val profileTabModule =
identityRepository = get(),
)
}
factory<ProfileSideMenuMviModel> {
ProfileSideMenuViewModel(
lemmyValueCache = get(),
)
}
}

View File

@ -5,8 +5,7 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.Logout
import androidx.compose.material.icons.filled.ManageAccounts
import androidx.compose.material.icons.automirrored.filled.MenuOpen
import androidx.compose.material.icons.filled.Menu
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
@ -51,6 +50,7 @@ import com.github.diegoberaldin.raccoonforlemmy.core.notifications.di.getNotific
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.di.getSettingsRepository
import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.onClick
import com.github.diegoberaldin.raccoonforlemmy.core.utils.toLocalPixel
import com.github.diegoberaldin.raccoonforlemmy.feature.profile.menu.ProfileSideMenuScreen
import com.github.diegoberaldin.raccoonforlemmy.feature.profile.notlogged.ProfileNotLoggedScreen
import com.github.diegoberaldin.raccoonforlemmy.unit.drafts.DraftsScreen
import com.github.diegoberaldin.raccoonforlemmy.unit.filteredcontents.FilteredContentsScreen
@ -91,7 +91,8 @@ internal object ProfileMainScreen : Tab {
var logoutConfirmDialogOpen by remember { mutableStateOf(false) }
LaunchedEffect(notificationCenter) {
notificationCenter.subscribe(NotificationCenterEvent.ModeratorZoneActionSelected::class)
notificationCenter
.subscribe(NotificationCenterEvent.ModeratorZoneActionSelected::class)
.onEach {
val action = it.value.toModeratorZoneAction()
when (action) {
@ -110,41 +111,45 @@ internal object ProfileMainScreen : Tab {
}
}.launchIn(this)
notificationCenter.subscribe(NotificationCenterEvent.ProfileSideMenuAction::class).onEach { evt ->
navigationCoordinator.closeSideMenu()
notificationCenter
.subscribe(NotificationCenterEvent.ProfileSideMenuAction::class)
.onEach { evt ->
navigationCoordinator.closeSideMenu()
when (evt) {
NotificationCenterEvent.ProfileSideMenuAction.ManageAccounts -> {
navigationCoordinator.showBottomSheet(ManageAccountsScreen())
}
when (evt) {
NotificationCenterEvent.ProfileSideMenuAction.ManageAccounts -> {
navigationCoordinator.showBottomSheet(ManageAccountsScreen())
}
NotificationCenterEvent.ProfileSideMenuAction.ManageSubscriptions -> {
navigationCoordinator.pushScreen(ManageSubscriptionsScreen())
}
NotificationCenterEvent.ProfileSideMenuAction.ManageSubscriptions -> {
navigationCoordinator.pushScreen(ManageSubscriptionsScreen())
}
NotificationCenterEvent.ProfileSideMenuAction.Bookmarks -> {
val screen = FilteredContentsScreen(type = FilteredContentsType.Bookmarks.toInt())
navigationCoordinator.pushScreen(screen)
}
NotificationCenterEvent.ProfileSideMenuAction.Bookmarks -> {
val screen =
FilteredContentsScreen(type = FilteredContentsType.Bookmarks.toInt())
navigationCoordinator.pushScreen(screen)
}
NotificationCenterEvent.ProfileSideMenuAction.Drafts -> {
navigationCoordinator.pushScreen(DraftsScreen())
}
NotificationCenterEvent.ProfileSideMenuAction.Drafts -> {
navigationCoordinator.pushScreen(DraftsScreen())
}
NotificationCenterEvent.ProfileSideMenuAction.Votes -> {
val screen = FilteredContentsScreen(type = FilteredContentsType.Votes.toInt())
navigationCoordinator.pushScreen(screen)
}
NotificationCenterEvent.ProfileSideMenuAction.Votes -> {
val screen =
FilteredContentsScreen(type = FilteredContentsType.Votes.toInt())
navigationCoordinator.pushScreen(screen)
}
NotificationCenterEvent.ProfileSideMenuAction.ModeratorZone -> {
navigationCoordinator.showBottomSheet(ModeratorZoneBottomSheet())
}
NotificationCenterEvent.ProfileSideMenuAction.ModeratorZone -> {
navigationCoordinator.showBottomSheet(ModeratorZoneBottomSheet())
}
NotificationCenterEvent.ProfileSideMenuAction.Logout -> {
logoutConfirmDialogOpen = true
NotificationCenterEvent.ProfileSideMenuAction.Logout -> {
logoutConfirmDialogOpen = true
}
}
}
}.launchIn(this)
}.launchIn(this)
}
Scaffold(
@ -152,9 +157,10 @@ internal object ProfileMainScreen : Tab {
topBar = {
val maxTopInset = Dimensions.maxTopBarInset.toLocalPixel()
var topInset by remember { mutableStateOf(maxTopInset) }
snapshotFlow { topAppBarState.collapsedFraction }.onEach {
topInset = maxTopInset * (1 - it)
}.launchIn(scope)
snapshotFlow { topAppBarState.collapsedFraction }
.onEach {
topInset = maxTopInset * (1 - it)
}.launchIn(scope)
TopAppBar(
windowInsets =
@ -194,26 +200,15 @@ internal object ProfileMainScreen : Tab {
.padding(horizontal = Spacing.xs)
.onClick(
onClick = {
notificationCenter.send(NotificationCenterEvent.ProfileSideMenuAction.ManageAccounts)
navigationCoordinator.openSideMenu(
ProfileSideMenuScreen(),
)
},
),
imageVector = Icons.Default.ManageAccounts,
imageVector = Icons.AutoMirrored.Default.MenuOpen,
contentDescription = null,
tint = MaterialTheme.colorScheme.onBackground,
)
Icon(
modifier =
Modifier
.padding(horizontal = Spacing.xs)
.onClick(
onClick = {
logoutConfirmDialogOpen = true
},
),
imageVector = Icons.AutoMirrored.Default.Logout,
contentDescription = null,
tint = MaterialTheme.colorScheme.primary,
)
}
},
)
@ -224,8 +219,7 @@ internal object ProfileMainScreen : Tab {
Modifier
.padding(
top = padding.calculateTopPadding(),
)
.nestedScroll(fabNestedScrollConnection)
).nestedScroll(fabNestedScrollConnection)
.then(
if (settings.hideNavigationBarWhileScrolling) {
Modifier.nestedScroll(scrollBehavior.nestedScrollConnection)
@ -250,7 +244,9 @@ internal object ProfileMainScreen : Tab {
CurrentScreen()
val navigator = LocalTabNavigator.current
LaunchedEffect(model) {
model.uiState.map { s -> s.logged }.distinctUntilChanged()
model.uiState
.map { s -> s.logged }
.distinctUntilChanged()
.onEach { logged ->
val index =
when (logged) {

View File

@ -1,15 +1,20 @@
package com.github.diegoberaldin.raccoonforlemmy.unit.myaccount
package com.github.diegoberaldin.raccoonforlemmy.feature.profile.menu
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.Logout
import androidx.compose.material.icons.filled.Book
import androidx.compose.material.icons.filled.Bookmark
import androidx.compose.material.icons.filled.Drafts
import androidx.compose.material.icons.filled.ManageAccounts
import androidx.compose.material.icons.filled.Shield
import androidx.compose.material.icons.filled.ThumbsUpDown
import androidx.compose.material3.HorizontalDivider
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.Spacing
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.lemmyui.SettingsRow
import com.github.diegoberaldin.raccoonforlemmy.core.l10n.messages.LocalStrings
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenterEvent
@ -17,13 +22,15 @@ import com.github.diegoberaldin.raccoonforlemmy.core.notifications.di.getNotific
import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.rememberCallback
@Composable
internal fun ProfileActionMenu(
internal fun ProfileMenuContent(
modifier: Modifier = Modifier,
isModerator: Boolean = false,
) {
val notificationCenter = remember { getNotificationCenter() }
Column(
modifier = modifier,
verticalArrangement = Arrangement.spacedBy(Spacing.m),
) {
SettingsRow(
title = LocalStrings.current.navigationDrawerTitleSubscriptions,
@ -68,5 +75,25 @@ internal fun ProfileActionMenu(
},
)
}
SettingsRow(
title = LocalStrings.current.manageAccountsTitle,
icon = Icons.Default.ManageAccounts,
onTap =
rememberCallback {
notificationCenter.send(NotificationCenterEvent.ProfileSideMenuAction.ManageAccounts)
},
)
HorizontalDivider()
SettingsRow(
title = LocalStrings.current.actionLogout,
icon = Icons.AutoMirrored.Default.Logout,
onTap =
rememberCallback {
notificationCenter.send(NotificationCenterEvent.ProfileSideMenuAction.Logout)
},
)
}
}

View File

@ -0,0 +1,16 @@
package com.github.diegoberaldin.raccoonforlemmy.feature.profile.menu
import cafe.adriel.voyager.core.model.ScreenModel
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.MviModel
interface ProfileSideMenuMviModel :
MviModel<ProfileSideMenuMviModel.Intent, ProfileSideMenuMviModel.State, ProfileSideMenuMviModel.Effect>,
ScreenModel {
sealed interface Intent
data class State(
val isModerator: Boolean = false,
)
sealed interface Effect
}

View File

@ -0,0 +1,75 @@
package com.github.diegoberaldin.raccoonforlemmy.feature.profile.menu
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.surfaceColorAtElevation
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.koin.getScreenModel
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.Spacing
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.di.getNavigationCoordinator
import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.onClick
import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.rememberCallback
class ProfileSideMenuScreen : Screen {
@OptIn(ExperimentalMaterial3Api::class)
@Composable
override fun Content() {
val model = getScreenModel<ProfileSideMenuMviModel>()
val navigationCoordinator = remember { getNavigationCoordinator() }
val uiState by model.uiState.collectAsState()
Scaffold(
contentColor = MaterialTheme.colorScheme.onBackground,
containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp),
topBar = {
TopAppBar(
colors =
TopAppBarDefaults.topAppBarColors().copy(
containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp),
),
title = {},
actions = {
Icon(
modifier =
Modifier.padding(horizontal = Spacing.xs).onClick(
onClick =
rememberCallback {
navigationCoordinator.closeSideMenu()
},
),
imageVector = Icons.Default.Close,
contentDescription = null,
tint = MaterialTheme.colorScheme.onBackground,
)
},
)
},
) { padding ->
ProfileMenuContent(
modifier =
Modifier
.fillMaxSize()
.padding(
top = padding.calculateTopPadding(),
start = Spacing.m,
end = Spacing.m,
),
isModerator = uiState.isModerator,
)
}
}
}

View File

@ -0,0 +1,28 @@
package com.github.diegoberaldin.raccoonforlemmy.feature.profile.menu
import cafe.adriel.voyager.core.model.screenModelScope
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.DefaultMviModel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.LemmyValueCache
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
class ProfileSideMenuViewModel(
private val lemmyValueCache: LemmyValueCache,
) : DefaultMviModel<ProfileSideMenuMviModel.Intent, ProfileSideMenuMviModel.State, ProfileSideMenuMviModel.Effect>(
ProfileSideMenuMviModel.State(),
),
ProfileSideMenuMviModel {
init {
screenModelScope.launch {
lemmyValueCache.isCurrentUserModerator
.onEach { isModerator ->
updateState {
it.copy(
isModerator = isModerator,
)
}
}.launchIn(this)
}
}
}

View File

@ -81,7 +81,6 @@ class CommunityInfoScreen(
topBar = {
val title = uiState.community.readableName(uiState.preferNicknames)
TopAppBar(
modifier = Modifier.padding(top = Spacing.s),
colors =
TopAppBarDefaults.topAppBarColors().copy(
containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp),
@ -112,12 +111,9 @@ class CommunityInfoScreen(
LazyColumn(
modifier =
Modifier
.padding(
top = padding.calculateTopPadding(),
)
.fillMaxSize()
.padding(
top = Spacing.s,
top = padding.calculateTopPadding(),
start = Spacing.m,
end = Spacing.m,
),
@ -138,7 +134,10 @@ class CommunityInfoScreen(
DetailInfoItem(
modifier = Modifier.fillMaxWidth(),
icon = Icons.Default.Cake,
title = uiState.community.creationDate?.prettifyDate().orEmpty(),
title =
uiState.community.creationDate
?.prettifyDate()
.orEmpty(),
)
DetailInfoItem(
modifier = Modifier.fillMaxWidth(),

View File

@ -167,18 +167,6 @@ object ProfileLoggedScreen : Tab {
)
}
}
item {
ProfileActionMenu(
modifier =
Modifier
.padding(
top = Spacing.xs,
bottom = Spacing.s,
).fillMaxWidth(),
isModerator = uiState.isModerator,
)
HorizontalDivider()
}
item {
SectionSelector(
modifier = Modifier.padding(bottom = Spacing.s),

View File

@ -85,7 +85,6 @@ class UserInfoScreen(
topBar = {
val title = uiState.user.readableName(uiState.preferNicknames)
TopAppBar(
modifier = Modifier.padding(top = Spacing.s),
colors =
TopAppBarDefaults.topAppBarColors().copy(
containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp),
@ -119,8 +118,6 @@ class UserInfoScreen(
.fillMaxSize()
.padding(
top = padding.calculateTopPadding(),
).padding(
top = Spacing.s,
start = Spacing.m,
end = Spacing.m,
),