我的(侧边栏)ui调整
This commit is contained in:
@@ -1,6 +1,12 @@
|
||||
package com.aiosman.ravenow.ui.index
|
||||
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.animation.core.FastOutLinearInEasing
|
||||
import androidx.compose.animation.core.LinearOutSlowInEasing
|
||||
import androidx.compose.animation.core.updateTransition
|
||||
import androidx.compose.animation.core.animateFloat
|
||||
import androidx.compose.animation.core.FastOutSlowInEasing
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.Image
|
||||
@@ -11,15 +17,21 @@ import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.asPaddingValues
|
||||
import androidx.compose.foundation.layout.systemBars
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.navigationBars
|
||||
import androidx.compose.foundation.layout.requiredHeight
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.requiredWidth
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.pager.HorizontalPager
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
@@ -42,8 +54,10 @@ import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
@@ -59,7 +73,11 @@ import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.LayoutDirection
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.zIndex
|
||||
import com.aiosman.ravenow.AppState
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
import com.aiosman.ravenow.AppStore
|
||||
import com.aiosman.ravenow.GuestLoginCheckOut
|
||||
import com.aiosman.ravenow.GuestLoginCheckOutScene
|
||||
@@ -123,151 +141,16 @@ fun IndexScreen() {
|
||||
gesturesEnabled = drawerState.isOpen,
|
||||
drawerContent = {
|
||||
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Ltr) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.requiredWidth(250.dp)
|
||||
.fillMaxHeight()
|
||||
.background(
|
||||
AppColors.background
|
||||
)
|
||||
) {
|
||||
Spacer(modifier = Modifier.height(88.dp))
|
||||
NavItem(
|
||||
iconRes = R.drawable.rave_now_nav_account,
|
||||
label = stringResource(R.string.account_and_security),
|
||||
modifier = Modifier.noRippleClickable {
|
||||
coroutineScope.launch {
|
||||
drawerState.close()
|
||||
navController.navigate(NavigationRoute.AccountSetting.route)
|
||||
}
|
||||
|
||||
SideMenuContent(
|
||||
onClose = {
|
||||
coroutineScope.launch {
|
||||
drawerState.close()
|
||||
}
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
NavItem(
|
||||
iconRes = R.drawable.rider_pro_favourited,
|
||||
label = stringResource(R.string.favourites),
|
||||
modifier = Modifier.noRippleClickable {
|
||||
coroutineScope.launch {
|
||||
drawerState.close()
|
||||
navController.navigate(NavigationRoute.FavouriteList.route)
|
||||
}
|
||||
|
||||
}
|
||||
)
|
||||
|
||||
NavItem(
|
||||
iconRes = R.drawable.rave_now_nav_night,
|
||||
label = stringResource(R.string.dark_mode),
|
||||
rightContent = {
|
||||
Switch(
|
||||
checked = AppState.darkMode,
|
||||
onCheckedChange = {
|
||||
AppState.switchTheme()
|
||||
},
|
||||
|
||||
colors = SwitchDefaults.colors(
|
||||
checkedThumbColor = Color.White,
|
||||
checkedTrackColor = AppColors.main,
|
||||
uncheckedThumbColor = Color.White,
|
||||
uncheckedTrackColor = AppColors.main.copy(alpha = 0.5f),
|
||||
uncheckedBorderColor = Color.Transparent
|
||||
),
|
||||
modifier = Modifier.scale(0.8f)
|
||||
)
|
||||
}
|
||||
)
|
||||
// divider
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 16.dp, bottom = 16.dp, start = 16.dp, end = 16.dp)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(1.dp)
|
||||
.background(AppColors.divider)
|
||||
)
|
||||
}
|
||||
NavItem(
|
||||
iconRes = R.drawable.rave_now_nav_about,
|
||||
label = stringResource(R.string.blocked),
|
||||
modifier = Modifier.noRippleClickable {
|
||||
coroutineScope.launch {
|
||||
drawerState.close()
|
||||
navController.navigate(NavigationRoute.AboutScreen.route)
|
||||
}
|
||||
}
|
||||
)
|
||||
NavItem(
|
||||
iconRes = R.drawable.rave_now_nav_about,
|
||||
label = stringResource(R.string.feedback),
|
||||
modifier = Modifier.noRippleClickable {
|
||||
coroutineScope.launch {
|
||||
drawerState.close()
|
||||
navController.navigate(NavigationRoute.AboutScreen.route)
|
||||
}
|
||||
}
|
||||
)
|
||||
NavItem(
|
||||
iconRes = R.drawable.rave_now_nav_about,
|
||||
label = stringResource(R.string.about_rave_now),
|
||||
modifier = Modifier.noRippleClickable {
|
||||
coroutineScope.launch {
|
||||
drawerState.close()
|
||||
navController.navigate(NavigationRoute.AboutScreen.route)
|
||||
}
|
||||
}
|
||||
)
|
||||
// divider
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 16.dp, bottom = 16.dp, start = 16.dp, end = 16.dp)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(1.dp)
|
||||
.background(AppColors.divider)
|
||||
)
|
||||
}
|
||||
|
||||
// NavItem(
|
||||
// iconRes = R.drawable.rave_now_nav_switch,
|
||||
// label = "Switch Account"
|
||||
// )
|
||||
// Spacer(modifier = Modifier.height(16.dp))
|
||||
NavItem(
|
||||
iconRes = R.drawable.rave_now_nav_logout,
|
||||
label = stringResource(R.string.logout),
|
||||
modifier = Modifier.noRippleClickable {
|
||||
coroutineScope.launch {
|
||||
drawerState.close()
|
||||
// 只有非游客用户才需要取消注册推送设备
|
||||
if (!AppStore.isGuest) {
|
||||
Messaging.unregisterDevice(context)
|
||||
}
|
||||
AppStore.apply {
|
||||
token = null
|
||||
rememberMe = false
|
||||
isGuest = false // 清除游客状态
|
||||
saveData()
|
||||
}
|
||||
// 删除推送渠道
|
||||
|
||||
navController.navigate(NavigationRoute.Login.route) {
|
||||
popUpTo(NavigationRoute.Login.route) {
|
||||
inclusive = true
|
||||
}
|
||||
}
|
||||
AppState.ReloadAppState(context)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
}
|
||||
},
|
||||
navController = navController,
|
||||
context = context,
|
||||
isDrawerOpen = drawerState.isOpen
|
||||
)
|
||||
}
|
||||
}
|
||||
) {
|
||||
@@ -447,7 +330,6 @@ fun IndexScreen() {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -623,4 +505,344 @@ fun NavItem(
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SideMenuContent(
|
||||
onClose: () -> Unit,
|
||||
navController: androidx.navigation.NavController,
|
||||
context: android.content.Context,
|
||||
isDrawerOpen: Boolean
|
||||
) {
|
||||
val appColors = LocalAppTheme.current
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
var messageNotificationEnabled by remember { mutableStateOf(true) }
|
||||
var darkModeEnabled by remember { mutableStateOf(AppState.darkMode) }
|
||||
|
||||
// 菜单背景色 #FAF9FB
|
||||
val menuBackgroundColor = Color(0xFFFAF9FB)
|
||||
// 遮罩颜色 黑色透明度0.6
|
||||
val overlayColor = Color.Black.copy(alpha = 0.6f)
|
||||
// 卡片背景色 白色
|
||||
val cardBackgroundColor = Color.White
|
||||
// 跟随系统文字颜色 #979499
|
||||
val followSystemTextColor = Color(0xFF979499)
|
||||
// 开关开启颜色 #7C45ED
|
||||
val switchActiveColor = Color(0xFF7C45ED)
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
) {
|
||||
// 左侧半透明遮罩(平滑淡入淡出)
|
||||
val overlayTransition = updateTransition(targetState = isDrawerOpen, label = "overlay")
|
||||
val overlayAlpha by overlayTransition.animateFloat(
|
||||
transitionSpec = {
|
||||
if (targetState) {
|
||||
tween(durationMillis = 400, easing = LinearOutSlowInEasing)
|
||||
} else {
|
||||
tween(durationMillis = 300, easing = FastOutLinearInEasing)
|
||||
}
|
||||
},
|
||||
label = "overlayAlpha"
|
||||
) { open -> if (open) 0.6f else 0f }
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(Color.Black.copy(alpha = overlayAlpha))
|
||||
)
|
||||
|
||||
// 右侧菜单面板
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.requiredWidth(302.dp)
|
||||
.requiredHeight(874.dp)
|
||||
.align(Alignment.CenterEnd)
|
||||
.background(menuBackgroundColor)
|
||||
) {
|
||||
// 顶部状态栏间距
|
||||
val statusBarHeight = WindowInsets.systemBars.asPaddingValues().calculateTopPadding()
|
||||
|
||||
// 扫一扫功能入口 - 右边距离右边66pt
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.align(Alignment.TopEnd)
|
||||
.offset(x = (-112).dp, y = 88.dp)
|
||||
.noRippleClickable {
|
||||
// TODO: 实现扫一扫功能
|
||||
},
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
// 扫一扫图标(使用现有图标或占位)
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.sao),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(24.dp),
|
||||
colorFilter = ColorFilter.tint(Color.Black)
|
||||
)
|
||||
}
|
||||
// 绝对定位的"扫一扫"文字:上方71.5dp,右侧66dp
|
||||
Text(
|
||||
text = stringResource(R.string.scan_qr),
|
||||
fontSize = 14.sp,
|
||||
color = Color.Black,
|
||||
modifier = Modifier
|
||||
.align(Alignment.TopEnd)
|
||||
.offset(x = (-66).dp, y = 91.5.dp)
|
||||
)
|
||||
// QR码图标 - 右边距离右边112dp,上边距离上边68pt
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.qr_code_icon),
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.size(24.dp)
|
||||
.align(Alignment.TopEnd)
|
||||
.offset(x = (-26).dp, y = 88.dp)
|
||||
.noRippleClickable {
|
||||
// TODO: 实现QR码功能
|
||||
},
|
||||
colorFilter = ColorFilter.tint(Color.Black)
|
||||
)
|
||||
|
||||
// 菜单选项卡片组 - 第一组卡片上方距离上方108pt(绝对定位)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.offset(y = 128.dp) // 直接距离顶部128dp(整体下移20dp)
|
||||
.padding(horizontal = 16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
// 第一组卡片:编辑资料、账号安全、收藏
|
||||
MenuCard(
|
||||
backgroundColor = cardBackgroundColor,
|
||||
width = 270.dp,
|
||||
height = 164.dp,
|
||||
items = listOf(
|
||||
MenuItem(
|
||||
icon = R.mipmap.icons_edited_data,
|
||||
label = stringResource(R.string.edit_profile_info),
|
||||
onClick = {
|
||||
coroutineScope.launch {
|
||||
onClose()
|
||||
navController.navigate(NavigationRoute.AccountEdit.route)
|
||||
}
|
||||
}
|
||||
),
|
||||
MenuItem(
|
||||
icon = R.mipmap.icons_account_and_security,
|
||||
label = stringResource(R.string.account_and_security),
|
||||
onClick = {
|
||||
coroutineScope.launch {
|
||||
onClose()
|
||||
navController.navigate(NavigationRoute.AccountSetting.route)
|
||||
}
|
||||
}
|
||||
),
|
||||
MenuItem(
|
||||
icon = R.mipmap.collect,
|
||||
label = stringResource(R.string.favourites),
|
||||
onClick = {
|
||||
coroutineScope.launch {
|
||||
onClose()
|
||||
navController.navigate(NavigationRoute.FavouriteList.route)
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
// 第二组卡片:暗色模式、消息通知
|
||||
MenuCard(
|
||||
backgroundColor = cardBackgroundColor,
|
||||
width = 270.dp,
|
||||
height = 112.dp, // 根据设计图,第二组卡片高度为112dp
|
||||
items = listOf(
|
||||
MenuItem(
|
||||
icon = R.mipmap.icons_dark_mode,
|
||||
label = stringResource(R.string.dark_mode),
|
||||
rightContent = {
|
||||
Switch(
|
||||
checked = darkModeEnabled,
|
||||
onCheckedChange = {
|
||||
darkModeEnabled = it
|
||||
AppState.darkMode = it
|
||||
AppState.appTheme = if (it) {
|
||||
com.aiosman.ravenow.DarkThemeColors()
|
||||
} else {
|
||||
com.aiosman.ravenow.LightThemeColors()
|
||||
}
|
||||
AppStore.saveDarkMode(it)
|
||||
},
|
||||
colors = SwitchDefaults.colors(
|
||||
checkedThumbColor = Color.White,
|
||||
checkedTrackColor = switchActiveColor,
|
||||
uncheckedThumbColor = Color.White,
|
||||
uncheckedTrackColor = switchActiveColor.copy(alpha = 0.5f),
|
||||
uncheckedBorderColor = Color.Transparent
|
||||
),
|
||||
modifier = Modifier.size(width = 64.dp, height = 28.dp)
|
||||
)
|
||||
}
|
||||
),
|
||||
MenuItem(
|
||||
icon = R.mipmap.icons_bell,
|
||||
label = stringResource(R.string.message_notification),
|
||||
rightContent = {
|
||||
Switch(
|
||||
checked = messageNotificationEnabled,
|
||||
onCheckedChange = { messageNotificationEnabled = it },
|
||||
colors = SwitchDefaults.colors(
|
||||
checkedThumbColor = Color.White,
|
||||
checkedTrackColor = switchActiveColor,
|
||||
uncheckedThumbColor = Color.White,
|
||||
uncheckedTrackColor = switchActiveColor.copy(alpha = 0.5f),
|
||||
uncheckedBorderColor = Color.Transparent
|
||||
),
|
||||
modifier = Modifier.size(width = 64.dp, height = 28.dp)
|
||||
)
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
// 第三组卡片:关于派派、反馈、退出登录
|
||||
MenuCard(
|
||||
backgroundColor = cardBackgroundColor,
|
||||
width = 270.dp,
|
||||
height = 164.dp,
|
||||
items = listOf(
|
||||
MenuItem(
|
||||
icon = R.mipmap.icons_about,
|
||||
label = stringResource(R.string.about_paipai),
|
||||
onClick = {
|
||||
coroutineScope.launch {
|
||||
onClose()
|
||||
navController.navigate(NavigationRoute.AboutScreen.route)
|
||||
}
|
||||
}
|
||||
),
|
||||
MenuItem(
|
||||
icon = R.mipmap.feedback_icon,
|
||||
label = stringResource(R.string.feedback),
|
||||
onClick = {
|
||||
coroutineScope.launch {
|
||||
onClose()
|
||||
navController.navigate(NavigationRoute.AboutScreen.route)
|
||||
}
|
||||
}
|
||||
),
|
||||
MenuItem(
|
||||
icon = R.mipmap.log_out_icon,
|
||||
label = stringResource(R.string.logout_confirm),
|
||||
onClick = {
|
||||
coroutineScope.launch {
|
||||
onClose()
|
||||
// 只有非游客用户才需要取消注册推送设备
|
||||
if (!AppStore.isGuest) {
|
||||
Messaging.unregisterDevice(context)
|
||||
}
|
||||
AppStore.apply {
|
||||
token = null
|
||||
rememberMe = false
|
||||
isGuest = false
|
||||
saveData()
|
||||
}
|
||||
navController.navigate(NavigationRoute.Login.route) {
|
||||
popUpTo(NavigationRoute.Login.route) {
|
||||
inclusive = true
|
||||
}
|
||||
}
|
||||
AppState.ReloadAppState(context)
|
||||
}
|
||||
},
|
||||
showRightArrow = false
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class MenuItem(
|
||||
val icon: Int,
|
||||
val label: String,
|
||||
val onClick: (() -> Unit)? = null,
|
||||
val rightContent: @Composable (() -> Unit)? = null,
|
||||
val showRightArrow: Boolean = true
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun MenuCard(
|
||||
backgroundColor: Color,
|
||||
items: List<MenuItem>,
|
||||
width: androidx.compose.ui.unit.Dp? = null,
|
||||
height: androidx.compose.ui.unit.Dp? = null
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.then(if (width != null) Modifier.requiredWidth(width) else Modifier.fillMaxWidth())
|
||||
.then(if (height != null) Modifier.requiredHeight(height) else Modifier)
|
||||
.background(backgroundColor, RoundedCornerShape(16.dp))
|
||||
.padding(horizontal = 16.dp),
|
||||
verticalArrangement = if (height != null) Arrangement.SpaceEvenly else Arrangement.spacedBy(8.dp) // 固定高度时均匀分布
|
||||
) {
|
||||
items.forEachIndexed { index, item ->
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.then(if (height != null) Modifier.weight(1f) else Modifier),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
MenuItemRow(item = item, compact = height != null) // 传递compact参数
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MenuItemRow(item: MenuItem, compact: Boolean = false) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.then(
|
||||
if (item.onClick != null) {
|
||||
Modifier.noRippleClickable { item.onClick?.invoke() }
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
)
|
||||
.padding(vertical = if (compact) 4.dp else 8.dp), // 紧凑模式下减少垂直padding
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = item.icon),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(24.dp),
|
||||
colorFilter = ColorFilter.tint(Color.Black)
|
||||
)
|
||||
Text(
|
||||
text = item.label,
|
||||
fontSize = 14.sp,
|
||||
color = Color.Black
|
||||
)
|
||||
}
|
||||
|
||||
if (item.rightContent != null) {
|
||||
item.rightContent?.invoke()
|
||||
} else if (item.showRightArrow) {
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.rave_now_nav_right),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(24.dp),
|
||||
colorFilter = ColorFilter.tint(Color(0xFF111213))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user