Merge branch 'main' into zhong_1
@@ -1,117 +1,177 @@
|
||||
package com.aiosman.ravenow.ui.account
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.BasicTextField
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.rememberModalBottomSheetState
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.runtime.Composable
|
||||
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.platform.LocalContext
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.aiosman.ravenow.AppState
|
||||
import com.aiosman.ravenow.AppStore
|
||||
import com.aiosman.ravenow.LocalAppTheme
|
||||
import com.aiosman.ravenow.LocalNavController
|
||||
import com.aiosman.ravenow.Messaging
|
||||
import com.aiosman.ravenow.R
|
||||
import com.aiosman.ravenow.data.AccountServiceImpl
|
||||
import com.aiosman.ravenow.ui.NavigationRoute
|
||||
import com.aiosman.ravenow.ui.comment.NoticeScreenHeader
|
||||
import com.aiosman.ravenow.ui.composables.ActionButton
|
||||
import com.aiosman.ravenow.ui.composables.StatusBarSpacer
|
||||
import com.aiosman.ravenow.ui.index.NavItem
|
||||
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
private object AccountSettingConstants {
|
||||
const val BACK_BUTTON_SIZE = 36
|
||||
const val BACK_BUTTON_ICON_SIZE = 24
|
||||
const val BACK_BUTTON_START_PADDING = 19
|
||||
const val OPTION_ITEM_HEIGHT = 56
|
||||
const val OPTION_ITEM_ICON_SIZE = 24
|
||||
const val OPTION_ITEM_HORIZONTAL_PADDING = 16
|
||||
const val OPTION_ITEM_ICON_TEXT_SPACING = 12
|
||||
const val OPTION_ITEM_TEXT_SIZE = 17
|
||||
const val HEADER_VERTICAL_PADDING = 16
|
||||
const val TITLE_OFFSET_X = 19
|
||||
const val CARD_CORNER_RADIUS = 16
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CircularBackButton(
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val appColors = LocalAppTheme.current
|
||||
Box(
|
||||
modifier = modifier
|
||||
.size(AccountSettingConstants.BACK_BUTTON_SIZE.dp)
|
||||
.background(
|
||||
color = appColors.secondaryBackground,
|
||||
shape = CircleShape
|
||||
)
|
||||
.noRippleClickable { onClick() },
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.rider_pro_back_icon),
|
||||
contentDescription = "返回",
|
||||
modifier = Modifier.size(AccountSettingConstants.BACK_BUTTON_ICON_SIZE.dp),
|
||||
colorFilter = ColorFilter.tint(appColors.text)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SecurityOptionItem(
|
||||
iconRes: Int,
|
||||
label: String,
|
||||
onClick: () -> Unit,
|
||||
applyColorFilter: Boolean = true
|
||||
) {
|
||||
val appColors = LocalAppTheme.current
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(AccountSettingConstants.OPTION_ITEM_HEIGHT.dp)
|
||||
.padding(horizontal = AccountSettingConstants.OPTION_ITEM_HORIZONTAL_PADDING.dp)
|
||||
.noRippleClickable { onClick() },
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = iconRes),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(AccountSettingConstants.OPTION_ITEM_ICON_SIZE.dp),
|
||||
colorFilter = if (applyColorFilter) ColorFilter.tint(appColors.text) else null
|
||||
)
|
||||
Text(
|
||||
text = label,
|
||||
modifier = Modifier
|
||||
.padding(start = AccountSettingConstants.OPTION_ITEM_ICON_TEXT_SPACING.dp)
|
||||
.weight(1f),
|
||||
color = appColors.text,
|
||||
fontSize = AccountSettingConstants.OPTION_ITEM_TEXT_SIZE.sp,
|
||||
fontWeight = FontWeight.Medium
|
||||
)
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.rave_now_nav_right),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(AccountSettingConstants.OPTION_ITEM_ICON_SIZE.dp),
|
||||
colorFilter = ColorFilter.tint(appColors.secondaryText)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun AccountSetting() {
|
||||
val appColors = LocalAppTheme.current
|
||||
val navController = LocalNavController.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val context = LocalContext.current
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(appColors.background),
|
||||
) {
|
||||
StatusBarSpacer()
|
||||
|
||||
// 顶部标题栏
|
||||
Box(
|
||||
modifier = Modifier.padding(horizontal = 24.dp, vertical = 16.dp)
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = AccountSettingConstants.HEADER_VERTICAL_PADDING.dp)
|
||||
) {
|
||||
NoticeScreenHeader(
|
||||
title = stringResource(R.string.account_and_security),
|
||||
moreIcon = false
|
||||
CircularBackButton(
|
||||
onClick = { navController.navigateUp() },
|
||||
modifier = Modifier.padding(start = AccountSettingConstants.BACK_BUTTON_START_PADDING.dp)
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.account_and_security),
|
||||
fontWeight = FontWeight.W800,
|
||||
fontSize = AccountSettingConstants.OPTION_ITEM_TEXT_SIZE.sp,
|
||||
color = appColors.text,
|
||||
modifier = Modifier
|
||||
.align(Alignment.Center)
|
||||
.offset(x = AccountSettingConstants.TITLE_OFFSET_X.dp)
|
||||
)
|
||||
}
|
||||
|
||||
// 安全选项卡片
|
||||
Column(
|
||||
modifier = Modifier.padding(start = 24.dp)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 8.dp)
|
||||
.background(
|
||||
color = appColors.background,
|
||||
shape = RoundedCornerShape(AccountSettingConstants.CARD_CORNER_RADIUS.dp)
|
||||
)
|
||||
) {
|
||||
NavItem(
|
||||
iconRes = R.mipmap.rider_pro_change_password,
|
||||
SecurityOptionItem(
|
||||
iconRes = R.mipmap.icons_padlock,
|
||||
label = stringResource(R.string.change_password),
|
||||
modifier = Modifier.noRippleClickable {
|
||||
navController.navigate(NavigationRoute.ChangePasswordScreen.route)
|
||||
}
|
||||
onClick = { navController.navigate(NavigationRoute.ChangePasswordScreen.route) }
|
||||
)
|
||||
}
|
||||
|
||||
// 分割线
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(1.dp)
|
||||
.background(appColors.divider)
|
||||
SecurityOptionItem(
|
||||
iconRes = R.mipmap.icons_block,
|
||||
label = stringResource(R.string.blocked_users),
|
||||
onClick = {
|
||||
// TODO: 导航到屏蔽用户页面
|
||||
}
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 8.dp)
|
||||
) {
|
||||
NavItem(
|
||||
iconRes = R.drawable.rider_pro_moment_delete,
|
||||
|
||||
SecurityOptionItem(
|
||||
iconRes = R.mipmap.icons_remove,
|
||||
label = stringResource(R.string.remove_account),
|
||||
modifier = Modifier.noRippleClickable {
|
||||
navController.navigate(NavigationRoute.RemoveAccountScreen.route)
|
||||
}
|
||||
onClick = { navController.navigate(NavigationRoute.RemoveAccountScreen.route) },
|
||||
applyColorFilter = false
|
||||
)
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(1.dp)
|
||||
.background(appColors.divider)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -4,19 +4,26 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.ArrowForward
|
||||
import androidx.compose.material.icons.filled.Check
|
||||
import androidx.compose.material.icons.filled.Edit as EditIcon
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
@@ -28,10 +35,14 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.aiosman.ravenow.AppState
|
||||
import com.aiosman.ravenow.AppStore
|
||||
@@ -39,26 +50,26 @@ import com.aiosman.ravenow.LocalAppTheme
|
||||
import com.aiosman.ravenow.LocalNavController
|
||||
import com.aiosman.ravenow.R
|
||||
import com.aiosman.ravenow.ui.NavigationRoute
|
||||
import com.aiosman.ravenow.ui.comment.NoticeScreenHeader
|
||||
import com.aiosman.ravenow.ui.composables.CustomAsyncImage
|
||||
import com.aiosman.ravenow.ui.composables.StatusBarMaskLayout
|
||||
import com.aiosman.ravenow.ui.composables.StatusBarSpacer
|
||||
import com.aiosman.ravenow.ui.composables.form.FormTextInput
|
||||
import com.aiosman.ravenow.ui.composables.debouncedClickable
|
||||
import com.aiosman.ravenow.ui.composables.rememberDebouncedNavigation
|
||||
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import android.util.Log
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.asPaddingValues
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.systemBars
|
||||
import androidx.compose.foundation.text.BasicTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.draw.shadow
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import com.aiosman.ravenow.ConstVars
|
||||
import com.aiosman.ravenow.ui.composables.pickupAndCompressLauncher
|
||||
import java.io.File
|
||||
@@ -134,29 +145,308 @@ fun AccountEditScreen2(onUpdateBanner: ((Uri, File, Context) -> Unit)? = null,)
|
||||
// 确保显示的是当前登录用户的信息,而不是之前用户的缓存数据
|
||||
model.reloadProfile()
|
||||
}
|
||||
|
||||
// 设置状态栏为透明,使用浅色图标(因为顶部背景是深色图片)
|
||||
val systemUiController = rememberSystemUiController()
|
||||
LaunchedEffect(Unit) {
|
||||
systemUiController.setStatusBarColor(Color.Transparent, darkIcons = false)
|
||||
}
|
||||
|
||||
StatusBarMaskLayout(
|
||||
modifier = Modifier.background(color = appColors.background).padding(horizontal = 16.dp),
|
||||
darkIcons = !AppState.darkMode,
|
||||
maskBoxBackgroundColor = appColors.background
|
||||
modifier = Modifier.background(Color(0xFFFAF9FB)),
|
||||
darkIcons = false, // 浅色图标(白色),因为顶部背景是深色
|
||||
maskBoxBackgroundColor = Color.Transparent
|
||||
) {
|
||||
Column(
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(color = appColors.background),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
.background(Color(0xFFFAF9FB))
|
||||
) {
|
||||
//StatusBarSpacer()
|
||||
when {
|
||||
model.isLoading -> {
|
||||
// 加载中状态
|
||||
Box(
|
||||
modifier = Modifier.padding(horizontal = 0.dp, vertical = 16.dp)
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
NoticeScreenHeader(
|
||||
title = stringResource(R.string.edit_profile),
|
||||
moreIcon = false
|
||||
androidx.compose.material3.CircularProgressIndicator(
|
||||
color = appColors.main
|
||||
)
|
||||
}
|
||||
}
|
||||
model.profile != null -> {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
|
||||
Icon(
|
||||
// 顶部背景区域(圆角在底部,覆盖状态栏)
|
||||
val banner = model.profile?.banner
|
||||
val statusBarPadding = WindowInsets.systemBars.asPaddingValues().calculateTopPadding()
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(24.dp)
|
||||
.fillMaxWidth()
|
||||
.offset(y = -statusBarPadding)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.width(402.dp)
|
||||
.height(206.dp)
|
||||
.align(Alignment.TopCenter)
|
||||
.clip(RoundedCornerShape(bottomStart = 32.dp, bottomEnd = 32.dp))
|
||||
) {
|
||||
if (banner != null) {
|
||||
CustomAsyncImage(
|
||||
context = context,
|
||||
imageUrl = banner,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentDescription = "Banner",
|
||||
contentScale = ContentScale.Crop
|
||||
)
|
||||
} else {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(Color.Gray.copy(alpha = 0.2f))
|
||||
)
|
||||
}
|
||||
|
||||
// 更换封面按钮(位于壁纸右下方)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomEnd)
|
||||
.padding(end = 16.dp, bottom = 20.dp)
|
||||
.clip(RoundedCornerShape(12.dp))
|
||||
.background(Color(0x5C7D7C80)) // RGB(125, 120, 128, 0.36)
|
||||
.padding(horizontal = 8.dp, vertical = 4.dp)
|
||||
.noRippleClickable {
|
||||
Intent(Intent.ACTION_PICK).apply {
|
||||
type = "image/*"
|
||||
pickBannerImageLauncher.launch(this)
|
||||
}
|
||||
}
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
// 更换封面图标
|
||||
Icon(
|
||||
painter = painterResource(
|
||||
id = if (AppState.darkMode) {
|
||||
// TODO: 添加更换封面暗色模式图标
|
||||
R.mipmap.frame_4 // 临时占位,需替换为实际图标
|
||||
} else {
|
||||
// TODO: 添加更换封面亮色模式图标
|
||||
R.mipmap.fengm // 临时占位,需替换为实际图标
|
||||
}
|
||||
),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(16.dp),
|
||||
tint = Color.White
|
||||
)
|
||||
Text(
|
||||
text = "更换封面",
|
||||
fontSize = 12.sp,
|
||||
color = Color.White
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 状态栏区域(时间、信号、电池)
|
||||
// 这里使用系统状态栏,不单独实现
|
||||
|
||||
// 导航栏区域
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp)
|
||||
) {
|
||||
Spacer(modifier = Modifier.height(statusBarPadding + 12.dp))
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 12.dp)
|
||||
) {
|
||||
// 返回按钮
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(44.dp)
|
||||
.clip(CircleShape)
|
||||
.background(Color.White.copy(alpha = 0.3f))
|
||||
.noRippleClickable {
|
||||
navController.navigateUp()
|
||||
}
|
||||
.align(Alignment.CenterStart),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.rider_pro_back_icon),
|
||||
contentDescription = "Back",
|
||||
modifier = Modifier.size(24.dp),
|
||||
colorFilter = ColorFilter.tint(Color.White)
|
||||
)
|
||||
}
|
||||
|
||||
// 标题
|
||||
Text(
|
||||
text = "编辑资料",
|
||||
fontSize = 17.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = Color.White,
|
||||
modifier = Modifier.align(Alignment.Center)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 用户头像区域(距离顶部-50dp,包含状态栏高度,左右居中)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.offset(y = (-50).dp - statusBarPadding),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
val it = model.profile!!
|
||||
Box(
|
||||
modifier = Modifier.size(96.dp),
|
||||
contentAlignment = Alignment.BottomEnd
|
||||
) {
|
||||
// 头像
|
||||
CustomAsyncImage(
|
||||
context,
|
||||
model.croppedBitmap ?: it.avatar,
|
||||
modifier = Modifier
|
||||
.size(96.dp)
|
||||
.clip(CircleShape)
|
||||
.border(2.4.dp, Color(0xFFFAF9FB), CircleShape),
|
||||
contentDescription = "",
|
||||
contentScale = ContentScale.Crop
|
||||
)
|
||||
|
||||
// 编辑头像按钮
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(28.dp)
|
||||
.clip(CircleShape)
|
||||
.background(Color(0xFF110C13))
|
||||
.border(2.dp, Color.White, CircleShape)
|
||||
.debouncedClickable(debounceTime = 800L) {
|
||||
debouncedNavigation {
|
||||
navController.navigate(NavigationRoute.ImageCrop.route)
|
||||
}
|
||||
},
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(
|
||||
id = if (AppState.darkMode) {
|
||||
// TODO: 添加编辑头像暗色模式图标
|
||||
R.mipmap.frame_4 // 临时占位,需替换为实际图标
|
||||
} else {
|
||||
// TODO: 添加编辑头像亮色模式图标
|
||||
R.mipmap.bi // 临时占位,需替换为实际图标
|
||||
}
|
||||
),
|
||||
contentDescription = "Edit Avatar",
|
||||
modifier = Modifier.size(16.dp),
|
||||
tint = Color.White
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(1f)
|
||||
.padding(horizontal = 16.dp)
|
||||
.offset(y = (-74).dp)//
|
||||
) {
|
||||
// 昵称输入框
|
||||
ProfileInfoCard(
|
||||
label = "昵称",
|
||||
value = model.name,
|
||||
placeholder = "Value",
|
||||
onValueChange = { onNicknameChange(it) },
|
||||
isMultiline = false
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
// 个人简介输入框
|
||||
ProfileInfoCard(
|
||||
label = "个人简介",
|
||||
value = model.bio,
|
||||
placeholder = "Welcome to my fantiac word i will show you something about magic",
|
||||
onValueChange = { onBioChange(it) },
|
||||
isMultiline = true
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
// MBTI 类型和星座
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(16.dp))
|
||||
.background(Color.White)
|
||||
) {
|
||||
// MBTI 类型
|
||||
ProfileSelectItem(
|
||||
label = "MBTI 类型",
|
||||
value = model.mbti ?: "ENFP",
|
||||
iconColor = Color(0xFF7C45ED),
|
||||
iconResDark = null, // TODO: 添加MBTI暗色模式图标
|
||||
iconResLight = null, // TODO: 添加MBTI亮色模式图标
|
||||
onClick = {
|
||||
debouncedNavigation {
|
||||
navController.navigate(NavigationRoute.MbtiSelect.route)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// 分隔线
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(0.3.dp)
|
||||
.background(Color(0x41413C43).copy(alpha = 0.2f))
|
||||
.padding(horizontal = 16.dp)
|
||||
)
|
||||
|
||||
// 星座(使用当前图标)
|
||||
ProfileSelectItem(
|
||||
label = "星座",
|
||||
value = model.zodiac ?: "白羊座",
|
||||
iconColor = Color(0xFFFFCC00),
|
||||
iconResDark = R.mipmap.frame_4, // 星座暗色模式图标
|
||||
iconResLight = R.mipmap.xingzuo, // 星座亮色模式图标
|
||||
onClick = {
|
||||
debouncedNavigation {
|
||||
navController.navigate(NavigationRoute.ZodiacSelect.route)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
|
||||
// 保存按钮
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(50.dp)
|
||||
.clip(RoundedCornerShape(1000.dp))
|
||||
.background(
|
||||
brush = Brush.horizontalGradient(
|
||||
colors = listOf(
|
||||
Color(0xFF7C45ED), // RGB(124, 69, 237) - 左侧
|
||||
Color(0xFF7C57EE), // RGB(124, 87, 238) - 中间
|
||||
Color(0xFF7BD8F8) // RGB(123, 216, 248) - 右侧
|
||||
)
|
||||
)
|
||||
)
|
||||
.debouncedClickable(
|
||||
enabled = validate() && !model.isUpdating,
|
||||
debounceTime = 1000L
|
||||
@@ -174,177 +464,170 @@ fun AccountEditScreen2(onUpdateBanner: ((Uri, File, Context) -> Unit)? = null,)
|
||||
}
|
||||
}
|
||||
},
|
||||
imageVector = Icons.Default.Check,
|
||||
contentDescription = "保存",
|
||||
tint = if (validate() && !model.isUpdating) appColors.text else appColors.nonActiveText
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 添加横幅图片区域
|
||||
val banner = model.profile?.banner
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(300.dp)
|
||||
.clip(RoundedCornerShape(12.dp))
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
if (banner != null) {
|
||||
CustomAsyncImage(
|
||||
context = LocalContext.current,
|
||||
imageUrl = banner,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentDescription = "Banner",
|
||||
contentScale = ContentScale.Crop
|
||||
)
|
||||
} else {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(Color.Gray.copy(alpha = 0.1f))
|
||||
)
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.width(120.dp)
|
||||
.height(42.dp)
|
||||
.align(Alignment.BottomEnd)
|
||||
.padding(end = 12.dp, bottom = 12.dp)
|
||||
.background(
|
||||
color = Color.Black.copy(alpha = 0.4f),
|
||||
shape = RoundedCornerShape(9.dp)
|
||||
)
|
||||
.noRippleClickable {
|
||||
Intent(Intent.ACTION_PICK).apply {
|
||||
type = "image/*"
|
||||
pickBannerImageLauncher.launch(this)
|
||||
}
|
||||
}
|
||||
){
|
||||
Text(
|
||||
text = "change",
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.W600,
|
||||
color = Color.White,
|
||||
modifier = Modifier.align(Alignment.Center)
|
||||
text = "保存",
|
||||
fontSize = 17.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = Color.White
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
|
||||
// 显示内容或加载状态
|
||||
Log.d("AccountEditScreen2", "UI状态 - profile: ${model.profile?.nickName}, isLoading: ${model.isLoading}")
|
||||
when {
|
||||
model.profile != null -> {
|
||||
Log.d("AccountEditScreen2", "显示用户资料内容")
|
||||
// 有数据时显示内容
|
||||
val it = model.profile!!
|
||||
Box(
|
||||
modifier = Modifier.size(88.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
CustomAsyncImage(
|
||||
context,
|
||||
model.croppedBitmap ?: it.avatar,
|
||||
modifier = Modifier
|
||||
.size(88.dp)
|
||||
.clip(
|
||||
RoundedCornerShape(88.dp)
|
||||
),
|
||||
contentDescription = "",
|
||||
contentScale = ContentScale.Crop
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(32.dp)
|
||||
.clip(CircleShape)
|
||||
.background(
|
||||
brush = Brush.linearGradient(
|
||||
colors = listOf(
|
||||
Color(0xFF7c45ed),
|
||||
Color(0x997c68ef),
|
||||
Color(0xFF7bd8f8)
|
||||
)
|
||||
),
|
||||
)
|
||||
.align(Alignment.BottomEnd)
|
||||
.debouncedClickable(
|
||||
debounceTime = 800L
|
||||
) {
|
||||
debouncedNavigation {
|
||||
navController.navigate(NavigationRoute.ImageCrop.route)
|
||||
}
|
||||
},
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.Add,
|
||||
contentDescription = "Add",
|
||||
tint = Color.White,
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.height(18.dp))
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(horizontal = 16.dp)
|
||||
) {
|
||||
FormTextInput(
|
||||
value = model.name,
|
||||
label = stringResource(R.string.nickname),
|
||||
hint = "Input nickname",
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
error = usernameError
|
||||
) { value ->
|
||||
onNicknameChange(value)
|
||||
}
|
||||
FormTextInput(
|
||||
value = model.bio,
|
||||
label = stringResource(R.string.bio),
|
||||
hint = "Input bio",
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
error = bioError
|
||||
) { value ->
|
||||
onBioChange(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
model.isLoading -> {
|
||||
Log.d("AccountEditScreen2", "显示加载指示器")
|
||||
// 加载中状态
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(16.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
androidx.compose.material3.CircularProgressIndicator(
|
||||
color = appColors.main
|
||||
)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
Log.d("AccountEditScreen2", "显示错误信息 - 没有数据且不在加载中")
|
||||
// 没有数据且不在加载中,显示错误信息
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(16.dp),
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
androidx.compose.material3.Text(
|
||||
Text(
|
||||
text = "加载用户资料失败,请重试",
|
||||
color = appColors.text
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 信息输入卡片组件
|
||||
*/
|
||||
@Composable
|
||||
fun ProfileInfoCard(
|
||||
label: String,
|
||||
value: String,
|
||||
placeholder: String,
|
||||
onValueChange: (String) -> Unit,
|
||||
isMultiline: Boolean = false
|
||||
) {
|
||||
val appColors = LocalAppTheme.current
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(if (isMultiline) 66.dp else 56.dp) // 昵称框高度56dp,个人简介66dp
|
||||
.clip(RoundedCornerShape(16.dp))
|
||||
.background(Color.White),
|
||||
contentAlignment = if (isMultiline) Alignment.TopStart else Alignment.CenterStart
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp)
|
||||
.padding(vertical = if (isMultiline) 11.dp else 0.dp),
|
||||
verticalAlignment = if (isMultiline) Alignment.Top else Alignment.CenterVertically
|
||||
) {
|
||||
// 标签
|
||||
Text(
|
||||
text = label,
|
||||
fontSize = 17.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = Color.Black,
|
||||
modifier = Modifier.width(100.dp)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
|
||||
// 输入框
|
||||
Box(
|
||||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
if (value.isEmpty()) {
|
||||
Text(
|
||||
text = placeholder,
|
||||
fontSize = if (isMultiline) 15.sp else 17.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = Color(0x993C3C43),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
|
||||
BasicTextField(
|
||||
value = value,
|
||||
onValueChange = onValueChange,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
textStyle = androidx.compose.ui.text.TextStyle(
|
||||
fontSize = if (isMultiline) 15.sp else 17.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = Color.Black
|
||||
),
|
||||
cursorBrush = SolidColor(Color.Black),
|
||||
maxLines = if (isMultiline) Int.MAX_VALUE else 1,
|
||||
singleLine = !isMultiline
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择项组件(MBTI、星座)
|
||||
*/
|
||||
@Composable
|
||||
fun ProfileSelectItem(
|
||||
label: String,
|
||||
value: String,
|
||||
iconColor: Color,
|
||||
onClick: () -> Unit,
|
||||
iconResDark: Int? = null,
|
||||
iconResLight: Int? = null
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(56.dp)
|
||||
.clickable(onClick = onClick)
|
||||
.padding(horizontal = 16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
// 自定义图标
|
||||
Icon(
|
||||
painter = painterResource(
|
||||
id = if (AppState.darkMode) {
|
||||
iconResDark ?: R.mipmap.frame_4 // 使用传入的暗色模式图标,或默认占位
|
||||
} else {
|
||||
iconResLight ?: R.mipmap.naoz // 使用传入的亮色模式图标,或默认占位
|
||||
}
|
||||
),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(24.dp),
|
||||
tint = iconColor
|
||||
)
|
||||
|
||||
Text(
|
||||
text = label,
|
||||
fontSize = 17.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = Color.Black
|
||||
)
|
||||
}
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
Text(
|
||||
text = value,
|
||||
fontSize = 17.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = Color(0x993C3C43)
|
||||
)
|
||||
|
||||
Icon(
|
||||
imageVector = Icons.Default.ArrowForward,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(8.dp),
|
||||
tint = Color(0x4D3C3C43)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,6 +43,10 @@ import com.aiosman.ravenow.LocalAppTheme
|
||||
import com.aiosman.ravenow.R
|
||||
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
||||
|
||||
private val LabelTextColor = Color(red = 60f / 255f, green = 60f / 255f, blue = 67f / 255f, alpha = 0.6f)
|
||||
private val HintTextColor = Color(red = 60f / 255f, green = 60f / 255f, blue = 67f / 255f, alpha = 0.3f)
|
||||
private val PasswordIconColor = Color(red = 17f / 255f, green = 12f / 255f, blue = 19f / 255f)
|
||||
|
||||
@Composable
|
||||
fun TextInputField(
|
||||
modifier: Modifier = Modifier,
|
||||
@@ -52,67 +56,94 @@ fun TextInputField(
|
||||
label: String? = null,
|
||||
hint: String? = null,
|
||||
error: String? = null,
|
||||
enabled: Boolean = true
|
||||
enabled: Boolean = true,
|
||||
leadingIcon: @Composable (() -> Unit)? = null,
|
||||
customBackgroundColor: Color? = null,
|
||||
customCornerRadius: Float = 24f
|
||||
) {
|
||||
val AppColors = LocalAppTheme.current
|
||||
var showPassword by remember { mutableStateOf(!password) }
|
||||
var isFocused by remember { mutableStateOf(false) }
|
||||
val backgroundColor = customBackgroundColor ?: AppColors.inputBackground
|
||||
Column(modifier = modifier) {
|
||||
label?.let {
|
||||
Text(it, color = AppColors.secondaryText)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Text(
|
||||
text = it,
|
||||
color = LabelTextColor,
|
||||
fontSize = 13.sp,
|
||||
modifier = Modifier.padding(start = 8.dp, top = 8.dp, bottom = 8.dp)
|
||||
)
|
||||
}
|
||||
Box(
|
||||
contentAlignment = Alignment.CenterStart,
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(24.dp))
|
||||
.background(AppColors.inputBackground)
|
||||
.clip(RoundedCornerShape(customCornerRadius.dp))
|
||||
.background(backgroundColor)
|
||||
.border(
|
||||
width = 2.dp,
|
||||
color = if (error == null) Color.Transparent else AppColors.error,
|
||||
shape = RoundedCornerShape(24.dp)
|
||||
shape = RoundedCornerShape(customCornerRadius.dp)
|
||||
)
|
||||
.padding(horizontal = 16.dp, vertical = 16.dp)
|
||||
) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically){
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
){
|
||||
leadingIcon?.let {
|
||||
Box(modifier = Modifier.size(24.dp)) {
|
||||
it()
|
||||
}
|
||||
Spacer(modifier = Modifier.size(12.dp))
|
||||
}
|
||||
Box(modifier = Modifier.weight(1f)) {
|
||||
BasicTextField(
|
||||
value = text,
|
||||
onValueChange = onValueChange,
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.fillMaxWidth()
|
||||
.onFocusChanged { focusState ->
|
||||
isFocused = focusState.isFocused
|
||||
},
|
||||
textStyle = TextStyle(
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.W500,
|
||||
fontWeight = FontWeight.W400,
|
||||
color = AppColors.text
|
||||
),
|
||||
keyboardOptions = KeyboardOptions(
|
||||
keyboardType = if (password) KeyboardType.Password else KeyboardType.Text
|
||||
keyboardType = if (password) KeyboardType.Password else KeyboardType.Email
|
||||
),
|
||||
visualTransformation = if (showPassword) VisualTransformation.None else PasswordVisualTransformation(),
|
||||
singleLine = true,
|
||||
enabled = enabled,
|
||||
cursorBrush = SolidColor(AppColors.text),
|
||||
)
|
||||
if (password) {
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.rider_pro_eye),
|
||||
contentDescription = "Password",
|
||||
modifier = Modifier
|
||||
.size(18.dp)
|
||||
.noRippleClickable {
|
||||
showPassword = !showPassword
|
||||
},
|
||||
colorFilter = ColorFilter.tint(AppColors.text)
|
||||
if (text.isEmpty() && hint != null) {
|
||||
Text(
|
||||
text = hint,
|
||||
color = HintTextColor,
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.W400
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (text.isEmpty()) {
|
||||
hint?.let {
|
||||
Text(it, modifier = Modifier.padding(start = 5.dp), color = AppColors.inputHint, fontWeight = FontWeight.W600)
|
||||
if (password) {
|
||||
Image(
|
||||
painter = painterResource(
|
||||
id = if (showPassword) {
|
||||
R.drawable.rider_pro_eye
|
||||
} else {
|
||||
R.mipmap.icon_eyes_closed_light
|
||||
}
|
||||
),
|
||||
contentDescription = "Password",
|
||||
modifier = Modifier
|
||||
.size(24.dp)
|
||||
.noRippleClickable {
|
||||
showPassword = !showPassword
|
||||
},
|
||||
colorFilter = ColorFilter.tint(PasswordIconColor)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,9 @@ import com.aiosman.ravenow.data.api.AgentRule
|
||||
import com.aiosman.ravenow.data.api.AgentRuleQuota
|
||||
import com.aiosman.ravenow.data.api.CreateAgentRuleRequestBody
|
||||
import com.aiosman.ravenow.data.api.UpdateAgentRuleRequestBody
|
||||
import com.aiosman.ravenow.data.api.CreateAgentRuleRequestBody
|
||||
import com.aiosman.ravenow.data.api.AgentRule
|
||||
import com.aiosman.ravenow.data.api.AgentRuleQuota
|
||||
import com.aiosman.ravenow.data.parseErrorResponse
|
||||
import com.aiosman.ravenow.entity.ChatNotification
|
||||
import com.aiosman.ravenow.entity.GroupInfo
|
||||
|
||||
@@ -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,152 +141,17 @@ 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 {
|
||||
SideMenuContent(
|
||||
onClose = {
|
||||
coroutineScope.launch {
|
||||
drawerState.close()
|
||||
navController.navigate(NavigationRoute.AccountSetting.route)
|
||||
}
|
||||
|
||||
}
|
||||
)
|
||||
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)
|
||||
navController = navController,
|
||||
context = context,
|
||||
isDrawerOpen = drawerState.isOpen
|
||||
)
|
||||
}
|
||||
)
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
) {
|
||||
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Ltr) {
|
||||
@@ -447,7 +330,6 @@ fun IndexScreen() {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -624,3 +506,343 @@ 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))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import android.util.Log
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
@@ -86,8 +87,8 @@ import com.aiosman.ravenow.ui.composables.toolbar.rememberCollapsingToolbarScaff
|
||||
import com.aiosman.ravenow.ui.index.IndexViewModel
|
||||
import com.aiosman.ravenow.ui.index.tabs.profile.composable.GalleryGrid
|
||||
import com.aiosman.ravenow.ui.post.MenuActionItem
|
||||
import com.aiosman.ravenow.ui.index.tabs.profile.composable.GroupChatEmptyContent
|
||||
import com.aiosman.ravenow.ui.index.tabs.profile.composable.OtherProfileAction
|
||||
import com.aiosman.ravenow.ui.index.tabs.profile.composable.SelfProfileAction
|
||||
import com.aiosman.ravenow.ui.index.tabs.profile.composable.UserAgentsList
|
||||
import com.aiosman.ravenow.ui.index.tabs.profile.composable.UserAgentsRow
|
||||
import com.aiosman.ravenow.ui.index.tabs.profile.composable.UserContentPageIndicator
|
||||
@@ -100,6 +101,13 @@ import kotlinx.coroutines.launch
|
||||
import java.io.File
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import java.text.NumberFormat
|
||||
import java.util.Locale
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@@ -121,7 +129,8 @@ fun ProfileV3(
|
||||
postCount: Long? = null, // 新增参数用于传递帖子总数
|
||||
) {
|
||||
val model = MyProfileViewModel
|
||||
val pagerState = rememberPagerState(pageCount = { if (isAiAccount) 1 else 2 })
|
||||
// Tabs: 动态、(可选)智能体、群聊
|
||||
val pagerState = rememberPagerState(pageCount = { if (isAiAccount) 2 else 3 })
|
||||
val enabled by remember { mutableStateOf(true) }
|
||||
val statusBarPaddingValues = WindowInsets.systemBars.asPaddingValues()
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
@@ -134,7 +143,8 @@ fun ProfileV3(
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val navController = LocalNavController.current
|
||||
val bannerHeight = 400
|
||||
val bannerWidth = 402
|
||||
val bannerHeight = 206
|
||||
val pickBannerImageLauncher = pickupAndCompressLauncher(
|
||||
context,
|
||||
scope,
|
||||
@@ -156,12 +166,13 @@ fun ProfileV3(
|
||||
val gridState = rememberLazyGridState()
|
||||
val scrollState = rememberScrollState()
|
||||
|
||||
val toolbarAlpha by remember {
|
||||
// 计算导航栏背景透明度,根据滚动位置从0到1
|
||||
val toolbarBackgroundAlpha by remember {
|
||||
derivedStateOf {
|
||||
if (!isSelf) {
|
||||
1f
|
||||
} else {
|
||||
val maxScroll = 500f // 最大滚动距离,可调整
|
||||
val maxScroll = 600f // 增加最大滚动距离,让渐变更平缓
|
||||
val progress = (scrollState.value.coerceAtMost(maxScroll.toInt()) / maxScroll).coerceIn(0f, 1f)
|
||||
progress
|
||||
}
|
||||
@@ -308,12 +319,19 @@ fun ProfileV3(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(bannerHeight.dp)
|
||||
.background(AppColors.profileBackground)
|
||||
.background(AppColors.profileBackground),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(bannerHeight.dp - 24.dp)
|
||||
.width(bannerWidth.dp)
|
||||
.height(bannerHeight.dp)
|
||||
.clip(
|
||||
RoundedCornerShape(
|
||||
bottomStart = 32.dp,
|
||||
bottomEnd = 32.dp
|
||||
)
|
||||
)
|
||||
.let {
|
||||
if (isSelf && isMain) {
|
||||
it.noRippleClickable {
|
||||
@@ -326,13 +344,6 @@ fun ProfileV3(
|
||||
it
|
||||
}
|
||||
}
|
||||
.shadow(
|
||||
elevation = 6.dp,
|
||||
shape = RoundedCornerShape(
|
||||
bottomStart = 32.dp,
|
||||
bottomEnd = 32.dp
|
||||
),
|
||||
)
|
||||
) {
|
||||
CustomAsyncImage(
|
||||
LocalContext.current,
|
||||
@@ -347,6 +358,9 @@ fun ProfileV3(
|
||||
Spacer(modifier = Modifier.height(100.dp))
|
||||
}
|
||||
|
||||
// 壁纸下方间距
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
// 用户信息
|
||||
Box(
|
||||
modifier = Modifier
|
||||
@@ -357,31 +371,25 @@ fun ProfileV3(
|
||||
profile?.let {
|
||||
UserItem(
|
||||
accountProfileEntity = it,
|
||||
postCount = postCount ?: if (isSelf) MyProfileViewModel.momentLoader.total else moments.size.toLong()
|
||||
postCount = postCount ?: if (isSelf) MyProfileViewModel.momentLoader.total else moments.size.toLong(),
|
||||
isSelf = isSelf,
|
||||
onEditClick = {
|
||||
navController.navigate(NavigationRoute.AccountEdit.route)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
|
||||
// 操作按钮
|
||||
// 操作按钮(仅其他用户显示)
|
||||
profile?.let {
|
||||
if (!isSelf && it.id != AppState.UserId) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp)
|
||||
) {
|
||||
if (isSelf) {
|
||||
SelfProfileAction(
|
||||
onEditProfile = {
|
||||
navController.navigate(NavigationRoute.AccountEdit.route)
|
||||
},
|
||||
onPremiumClick = {
|
||||
navController.navigate(NavigationRoute.VipSelPage.route)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
if (it.id != AppState.UserId) {
|
||||
OtherProfileAction(
|
||||
it,
|
||||
onFollow = {
|
||||
@@ -394,7 +402,6 @@ fun ProfileV3(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 用户智能体行
|
||||
if (!isAiAccount) {
|
||||
@@ -445,15 +452,14 @@ fun ProfileV3(
|
||||
pagerState = pagerState,
|
||||
showAgentTab = !isAiAccount
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
HorizontalPager(
|
||||
state = pagerState,
|
||||
modifier = Modifier.height(500.dp) // 固定滚动高度
|
||||
) { idx ->
|
||||
when (idx) {
|
||||
0 ->
|
||||
GalleryGrid(moments = moments)
|
||||
1 ->
|
||||
0 -> GalleryGrid(moments = moments)
|
||||
1 -> {
|
||||
if (!isAiAccount) {
|
||||
UserAgentsList(
|
||||
agents = agents,
|
||||
onAgentClick = onAgentClick,
|
||||
@@ -475,10 +481,22 @@ fun ProfileV3(
|
||||
},
|
||||
modifier = Modifier.fillMaxSize()
|
||||
)
|
||||
} else {
|
||||
GroupChatPlaceholder()
|
||||
}
|
||||
}
|
||||
2 -> {
|
||||
if (!isAiAccount) {
|
||||
GroupChatPlaceholder()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 底部间距,增加滚动距离
|
||||
Spacer(modifier = Modifier.height(100.dp))
|
||||
}
|
||||
|
||||
// 顶部导航栏
|
||||
TopNavigationBar(
|
||||
@@ -486,9 +504,13 @@ fun ProfileV3(
|
||||
isSelf = isSelf,
|
||||
profile = profile,
|
||||
navController = navController,
|
||||
alpha = toolbarAlpha,
|
||||
backgroundAlpha = toolbarBackgroundAlpha,
|
||||
interactionCount = moments.sumOf { it.likeCount }, // 计算总点赞数作为互动数据
|
||||
onMenuClick = {
|
||||
showOtherUserMenu = true
|
||||
},
|
||||
onShareClick = {
|
||||
// TODO: 实现分享功能
|
||||
}
|
||||
)
|
||||
|
||||
@@ -562,6 +584,11 @@ fun ProfileV3(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun GroupChatPlaceholder() {
|
||||
GroupChatEmptyContent()
|
||||
}
|
||||
|
||||
//顶部导航栏组件
|
||||
@Composable
|
||||
fun TopNavigationBar(
|
||||
@@ -569,30 +596,161 @@ fun TopNavigationBar(
|
||||
isSelf: Boolean,
|
||||
profile: AccountProfileEntity?,
|
||||
navController: androidx.navigation.NavController,
|
||||
alpha: Float,
|
||||
onMenuClick: () -> Unit = {}
|
||||
backgroundAlpha: Float,
|
||||
interactionCount: Int = 0,
|
||||
onMenuClick: () -> Unit = {},
|
||||
onShareClick: () -> Unit = {}
|
||||
) {
|
||||
val appColors = LocalAppTheme.current
|
||||
val numberFormat = remember { NumberFormat.getNumberInstance(Locale.getDefault()) }
|
||||
|
||||
// 根据背景透明度决定图标颜色:透明度为1时变黑,否则为白色
|
||||
val iconColor = if (backgroundAlpha >= 1f) Color.Black else Color.White
|
||||
val cardBorderColor = if (backgroundAlpha >= 1f) Color.Black else Color.White
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.graphicsLayer { this.alpha = alpha }
|
||||
) {
|
||||
Column(
|
||||
val statusBarPadding = WindowInsets.systemBars.asPaddingValues()
|
||||
val statusBarHeight = statusBarPadding.calculateTopPadding()
|
||||
val navigationBarHeight = 56.dp // 增加导航栏高度,包括图标和额外空间
|
||||
|
||||
// 导航栏背景层,包括状态栏区域,根据滚动位置逐渐变白
|
||||
val totalHeight = statusBarHeight + navigationBarHeight
|
||||
val density = LocalDensity.current
|
||||
val totalHeightPx = with(density) { totalHeight.toPx() }
|
||||
|
||||
// 根据滚动位置计算基础颜色,从深色平滑过渡到白色,透明度从初始值逐渐减到0
|
||||
val baseColor = remember(backgroundAlpha) {
|
||||
val smoothProgress = backgroundAlpha.coerceIn(0f, 1f)
|
||||
|
||||
// 初始状态:半透明深色,让白色图标清晰可见
|
||||
val initialDarkAlpha = 0.12f
|
||||
|
||||
// 使用平滑的插值函数,让整个过渡更自然
|
||||
val easedProgress = smoothProgress * smoothProgress * (3f - 2f * smoothProgress) // smoothstep
|
||||
|
||||
// 颜色值:从黑色(0)平滑过渡到白色(1)
|
||||
val colorValue = easedProgress
|
||||
|
||||
// 透明度:从初始值(0.12f)逐渐减少到0
|
||||
// 当smoothProgress从0到1时,alpha从initialDarkAlpha减少到0
|
||||
val alpha = initialDarkAlpha * (1f - easedProgress)
|
||||
|
||||
Color(
|
||||
red = colorValue,
|
||||
green = colorValue,
|
||||
blue = colorValue,
|
||||
alpha = alpha.coerceIn(0f, initialDarkAlpha)
|
||||
)
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(appColors.profileBackground)
|
||||
) {
|
||||
StatusBarSpacer()
|
||||
.height(totalHeight) // 状态栏高度 + 导航栏高度
|
||||
.align(Alignment.TopCenter)
|
||||
.background(
|
||||
brush = Brush.verticalGradient(
|
||||
colors = listOf(
|
||||
baseColor, // 顶部保持基础颜色
|
||||
baseColor, // 中间保持基础颜色
|
||||
baseColor.copy(alpha = baseColor.alpha * 0.5f), // 底部过渡,逐渐变透明
|
||||
Color.Transparent // 最底部完全透明
|
||||
),
|
||||
startY = 0f,
|
||||
endY = totalHeightPx
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
// 功能按钮区域,图标和文字根据背景透明度改变颜色
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||
.noRippleClickable {
|
||||
},
|
||||
.padding(top = 16.dp, bottom = 16.dp, end = 16.dp) // 增加上下内边距
|
||||
.align(Alignment.TopEnd)
|
||||
.padding(top = statusBarHeight), // 从状态栏下方开始
|
||||
horizontalArrangement = Arrangement.End,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
// 左侧:互动数据卡片
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.height(24.dp)
|
||||
.background(
|
||||
color = Color.White.copy(alpha = 0.52f),
|
||||
shape = RoundedCornerShape(16.dp)
|
||||
)
|
||||
.border(
|
||||
width = 0.5.dp,
|
||||
color = cardBorderColor, // 根据背景透明度改变边框颜色
|
||||
shape = RoundedCornerShape(16.dp)
|
||||
)
|
||||
.padding(horizontal = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Center
|
||||
) {
|
||||
// 互动图标
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.paip_coin_img),
|
||||
contentDescription = "互动",
|
||||
modifier = Modifier.size(24.dp),
|
||||
)
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
Text(
|
||||
text = numberFormat.format(interactionCount),
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.W500,
|
||||
color = Color.Black, // 文字始终为黑色
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
|
||||
// 中间:分享图标
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.menu_icon),
|
||||
contentDescription = "分享",
|
||||
modifier = Modifier
|
||||
.size(24.dp)
|
||||
.noRippleClickable {
|
||||
onShareClick()
|
||||
},
|
||||
colorFilter = ColorFilter.tint(iconColor) // 根据背景透明度改变颜色
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
|
||||
// 右侧:菜单图标
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.menu_ico),
|
||||
contentDescription = "菜单",
|
||||
modifier = Modifier
|
||||
.size(24.dp)
|
||||
.noRippleClickable {
|
||||
if (isSelf && isMain) {
|
||||
IndexViewModel.openDrawer = true
|
||||
} else {
|
||||
onMenuClick()
|
||||
}
|
||||
},
|
||||
colorFilter = ColorFilter.tint(iconColor) // 根据背景透明度改变颜色
|
||||
)
|
||||
}
|
||||
|
||||
// 如果不是主页面,显示返回按钮和用户信息
|
||||
if (!isMain) {
|
||||
val statusBarPadding = WindowInsets.systemBars.asPaddingValues()
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.align(Alignment.TopStart)
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||
.padding(top = statusBarPadding.calculateTopPadding()),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.rider_pro_back_icon),
|
||||
contentDescription = "Back",
|
||||
@@ -601,7 +759,7 @@ fun TopNavigationBar(
|
||||
navController.navigateUp()
|
||||
}
|
||||
.size(24.dp),
|
||||
colorFilter = ColorFilter.tint(appColors.text)
|
||||
colorFilter = ColorFilter.tint(Color.White)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
CustomAsyncImage(
|
||||
@@ -618,55 +776,81 @@ fun TopNavigationBar(
|
||||
text = profile?.nickName ?: "",
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.W600,
|
||||
color = appColors.text
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
if (isSelf && isMain) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(24.dp)
|
||||
.padding(16.dp)
|
||||
)
|
||||
} else if (!isSelf) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.noRippleClickable {
|
||||
onMenuClick()
|
||||
}
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.rider_pro_more_horizon),
|
||||
contentDescription = "菜单",
|
||||
tint = appColors.text,
|
||||
modifier = Modifier.size(24.dp)
|
||||
color = Color.White
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
}
|
||||
if (isSelf && isMain) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.align(Alignment.TopEnd)
|
||||
.padding(top = 32.dp, end = 16.dp)
|
||||
.noRippleClickable {
|
||||
IndexViewModel.openDrawer = true
|
||||
}
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier.padding(16.dp)
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.rider_pro_more_horizon),
|
||||
contentDescription = "",
|
||||
tint = appColors.text
|
||||
}
|
||||
|
||||
// 分享图标(向上箭头)
|
||||
@Composable
|
||||
fun ShareIcon(
|
||||
color: Color,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Canvas(modifier = modifier) {
|
||||
val strokeWidth = 2.dp.toPx()
|
||||
val centerX = size.width / 2
|
||||
val centerY = size.height / 2
|
||||
|
||||
// 绘制向上的箭头
|
||||
// 底部横线
|
||||
drawLine(
|
||||
color = color,
|
||||
start = Offset(centerX - 9.dp.toPx(), centerY + 6.dp.toPx()),
|
||||
end = Offset(centerX + 9.dp.toPx(), centerY + 6.dp.toPx()),
|
||||
strokeWidth = strokeWidth
|
||||
)
|
||||
// 顶部横线
|
||||
drawLine(
|
||||
color = color,
|
||||
start = Offset(centerX - 5.dp.toPx(), centerY - 6.5.dp.toPx()),
|
||||
end = Offset(centerX + 5.dp.toPx(), centerY - 6.5.dp.toPx()),
|
||||
strokeWidth = strokeWidth
|
||||
)
|
||||
// 中间竖线
|
||||
drawLine(
|
||||
color = color,
|
||||
start = Offset(centerX, centerY - 3.dp.toPx()),
|
||||
end = Offset(centerX, centerY + 6.dp.toPx()),
|
||||
strokeWidth = strokeWidth
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 菜单图标(三条横线)
|
||||
@Composable
|
||||
fun MenuIcon(
|
||||
color: Color,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Canvas(modifier = modifier) {
|
||||
val strokeWidth = 2.dp.toPx()
|
||||
val centerX = size.width / 2
|
||||
val centerY = size.height / 2
|
||||
val lineLength = 16.dp.toPx()
|
||||
val spacing = 6.dp.toPx()
|
||||
|
||||
// 绘制三条横线
|
||||
drawLine(
|
||||
color = color,
|
||||
start = Offset(centerX - lineLength / 2, centerY - spacing),
|
||||
end = Offset(centerX + lineLength / 2, centerY - spacing),
|
||||
strokeWidth = strokeWidth
|
||||
)
|
||||
drawLine(
|
||||
color = color,
|
||||
start = Offset(centerX - lineLength / 2, centerY),
|
||||
end = Offset(centerX + lineLength / 2, centerY),
|
||||
strokeWidth = strokeWidth
|
||||
)
|
||||
drawLine(
|
||||
color = color,
|
||||
start = Offset(centerX - lineLength / 2, centerY + spacing),
|
||||
end = Offset(centerX + lineLength / 2, centerY + spacing),
|
||||
strokeWidth = strokeWidth
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
package com.aiosman.ravenow.ui.index.tabs.profile.composable
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Divider
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.aiosman.ravenow.R
|
||||
|
||||
@Composable
|
||||
fun GroupChatEmptyContent() {
|
||||
var selectedSegment by remember { mutableStateOf(0) } // 0: 全部, 1: 公开, 2: 私有
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(horizontal = 16.dp)
|
||||
) {
|
||||
// 分割线(紧贴上方栏)
|
||||
Divider(
|
||||
color = Color(0xFFF0F0F0), // 更浅的灰色
|
||||
thickness = 1.dp,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
// 分段控制器
|
||||
SegmentedControl(
|
||||
selectedIndex = selectedSegment,
|
||||
onSegmentSelected = { selectedSegment = it },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
// 空状态内容(居中)
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
// 空状态插图
|
||||
EmptyStateIllustration()
|
||||
|
||||
Spacer(modifier = Modifier.height(9.dp))
|
||||
|
||||
// 空状态文本
|
||||
Text(
|
||||
text = "空空如也~",
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = Color(0xFF000000)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SegmentedControl(
|
||||
selectedIndex: Int,
|
||||
onSegmentSelected: (Int) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.height(32.dp),
|
||||
horizontalArrangement = Arrangement.Start,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
// 全部
|
||||
SegmentButton(
|
||||
text = "全部",
|
||||
isSelected = selectedIndex == 0,
|
||||
onClick = { onSegmentSelected(0) },
|
||||
width = 54.dp
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
|
||||
// 公开
|
||||
SegmentButton(
|
||||
text = "公开",
|
||||
isSelected = selectedIndex == 1,
|
||||
onClick = { onSegmentSelected(1) },
|
||||
width = 59.dp
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
|
||||
// 私有
|
||||
SegmentButton(
|
||||
text = "私有",
|
||||
isSelected = selectedIndex == 2,
|
||||
onClick = { onSegmentSelected(2) },
|
||||
width = 54.dp
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SegmentButton(
|
||||
text: String,
|
||||
isSelected: Boolean,
|
||||
onClick: () -> Unit,
|
||||
width: androidx.compose.ui.unit.Dp
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.width(width)
|
||||
.height(32.dp)
|
||||
.background(
|
||||
color = if (isSelected) {
|
||||
Color(0xFF110C13) // RGB(17, 12, 19)
|
||||
} else {
|
||||
Color(0x147C7480) // RGB(124, 116, 128, alpha 0.08)
|
||||
},
|
||||
shape = RoundedCornerShape(1000.dp)
|
||||
)
|
||||
.clickable(onClick = onClick),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
text = text,
|
||||
fontSize = 13.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = if (isSelected) Color(0xFFFFFFFF) else Color(0xFF000000)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun EmptyStateIllustration() {
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.l_empty_img),
|
||||
contentDescription = "空状态",
|
||||
modifier = Modifier
|
||||
.width(181.dp)
|
||||
.height(153.dp),
|
||||
contentScale = ContentScale.Fit
|
||||
)
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Divider
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
@@ -60,15 +61,14 @@ fun UserAgentsList(
|
||||
) {
|
||||
val AppColors = LocalAppTheme.current
|
||||
|
||||
if (agents.isEmpty()) {
|
||||
// 使用带分段控制器的空状态布局
|
||||
AgentEmptyContentWithSegments()
|
||||
} else {
|
||||
LazyColumn(
|
||||
modifier = modifier.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
if (agents.isEmpty()) {
|
||||
item {
|
||||
EmptyAgentsView()
|
||||
}
|
||||
} else {
|
||||
items(agents) { agent ->
|
||||
UserAgentCard(
|
||||
agent = agent,
|
||||
@@ -76,13 +76,13 @@ fun UserAgentsList(
|
||||
onAvatarClick = onAvatarClick
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 底部间距
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(120.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -198,6 +198,178 @@ fun UserAgentCard(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AgentEmptyContentWithSegments() {
|
||||
var selectedSegment by remember { mutableStateOf(0) } // 0: 全部, 1: 公开, 2: 私有
|
||||
val AppColors = LocalAppTheme.current
|
||||
val isNetworkAvailable = NetworkUtils.isNetworkAvailable(LocalContext.current)
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(horizontal = 16.dp)
|
||||
) {
|
||||
// 分割线(紧贴上方栏)
|
||||
Divider(
|
||||
color = Color(0xFFF0F0F0), // 更浅的灰色
|
||||
thickness = 1.dp,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
// 分段控制器
|
||||
AgentSegmentedControl(
|
||||
selectedIndex = selectedSegment,
|
||||
onSegmentSelected = { selectedSegment = it },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
// 空状态内容(使用智能体原本的图标和文字)
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
if (isNetworkAvailable) {
|
||||
Image(
|
||||
painter = painterResource(
|
||||
id = if(AppState.darkMode) R.mipmap.ai_dark
|
||||
else R.mipmap.ai),
|
||||
contentDescription = "暂无Agent",
|
||||
modifier = Modifier
|
||||
.size(width = 181.dp, height = 153.dp)
|
||||
.align(Alignment.CenterHorizontally),
|
||||
)
|
||||
|
||||
// 根据是否为深色模式调整间距
|
||||
Spacer(modifier = Modifier.height(if(AppState.darkMode) 9.dp else 24.dp))
|
||||
|
||||
Text(
|
||||
text = "专属AI等你召唤",
|
||||
fontSize = 16.sp,
|
||||
color = AppColors.text,
|
||||
fontWeight = FontWeight.W600
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
Text(
|
||||
text = "AI将成为你的伙伴,而不是工具",
|
||||
fontSize = 14.sp,
|
||||
color = AppColors.secondaryText,
|
||||
fontWeight = FontWeight.W400
|
||||
)
|
||||
} else {
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.invalid_name_10),
|
||||
contentDescription = "network error",
|
||||
modifier = Modifier.size(181.dp),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.friend_chat_no_network_title),
|
||||
fontSize = 16.sp,
|
||||
color = AppColors.text,
|
||||
fontWeight = FontWeight.W600
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.friend_chat_no_network_subtitle),
|
||||
fontSize = 14.sp,
|
||||
color = AppColors.secondaryText,
|
||||
fontWeight = FontWeight.W400
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
ReloadButton(
|
||||
onClick = {
|
||||
MyProfileViewModel.ResetModel()
|
||||
MyProfileViewModel.loadProfile(pullRefresh = true)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AgentSegmentedControl(
|
||||
selectedIndex: Int,
|
||||
onSegmentSelected: (Int) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.height(32.dp),
|
||||
horizontalArrangement = Arrangement.Start,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
// 全部
|
||||
AgentSegmentButton(
|
||||
text = "全部",
|
||||
isSelected = selectedIndex == 0,
|
||||
onClick = { onSegmentSelected(0) },
|
||||
width = 54.dp
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
|
||||
// 公开
|
||||
AgentSegmentButton(
|
||||
text = "公开",
|
||||
isSelected = selectedIndex == 1,
|
||||
onClick = { onSegmentSelected(1) },
|
||||
width = 59.dp
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
|
||||
// 私有
|
||||
AgentSegmentButton(
|
||||
text = "私有",
|
||||
isSelected = selectedIndex == 2,
|
||||
onClick = { onSegmentSelected(2) },
|
||||
width = 54.dp
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AgentSegmentButton(
|
||||
text: String,
|
||||
isSelected: Boolean,
|
||||
onClick: () -> Unit,
|
||||
width: androidx.compose.ui.unit.Dp
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.width(width)
|
||||
.height(32.dp)
|
||||
.background(
|
||||
color = if (isSelected) {
|
||||
Color(0xFF110C13) // RGB(17, 12, 19)
|
||||
} else {
|
||||
Color(0x147C7480) // RGB(124, 116, 128, alpha 0.08)
|
||||
},
|
||||
shape = RoundedCornerShape(1000.dp)
|
||||
)
|
||||
.clickable(onClick = onClick),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
text = text,
|
||||
fontSize = 13.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = if (isSelected) Color(0xFFFFFFFF) else Color(0xFF000000)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun EmptyAgentsView() {
|
||||
val AppColors = LocalAppTheme.current
|
||||
|
||||
@@ -1,26 +1,38 @@
|
||||
package com.aiosman.ravenow.ui.index.tabs.profile.composable
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.aiosman.ravenow.AppStore
|
||||
import com.aiosman.ravenow.LocalAppTheme
|
||||
import com.aiosman.ravenow.LocalNavController
|
||||
import com.aiosman.ravenow.R
|
||||
@@ -29,23 +41,52 @@ import com.aiosman.ravenow.ui.NavigationRoute
|
||||
import com.aiosman.ravenow.ui.composables.CustomAsyncImage
|
||||
import com.aiosman.ravenow.ui.composables.rememberDebouncer
|
||||
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
||||
import java.text.NumberFormat
|
||||
import java.util.Locale
|
||||
|
||||
@Composable
|
||||
fun UserItem(
|
||||
accountProfileEntity: AccountProfileEntity,
|
||||
postCount: Long = 0
|
||||
postCount: Long = 0,
|
||||
isSelf: Boolean = false,
|
||||
onEditClick: () -> Unit = {}
|
||||
) {
|
||||
val navController = LocalNavController.current
|
||||
val AppColors = LocalAppTheme.current
|
||||
val followerDebouncer = rememberDebouncer()
|
||||
val followingDebouncer = rememberDebouncer()
|
||||
|
||||
// 获取 MBTI 和星座信息
|
||||
val mbti = remember(accountProfileEntity.id) {
|
||||
AppStore.getUserMbti(accountProfileEntity.id)
|
||||
}
|
||||
val zodiac = remember(accountProfileEntity.id) {
|
||||
AppStore.getUserZodiac(accountProfileEntity.id)
|
||||
}
|
||||
|
||||
// 格式化粉丝数
|
||||
val numberFormat = remember { NumberFormat.getNumberInstance(Locale.getDefault()) }
|
||||
val formattedFollowerCount = remember(accountProfileEntity.followerCount) {
|
||||
if (accountProfileEntity.followerCount >= 10000) {
|
||||
val wan = accountProfileEntity.followerCount / 10000.0
|
||||
if (wan >= 100) {
|
||||
"${wan.toInt()}万"
|
||||
} else {
|
||||
String.format("%.1f万", wan)
|
||||
}
|
||||
} else {
|
||||
numberFormat.format(accountProfileEntity.followerCount)
|
||||
}
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
// 顶部:头像和统计数据
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
// 头像
|
||||
CustomAsyncImage(
|
||||
@@ -53,39 +94,47 @@ fun UserItem(
|
||||
accountProfileEntity.avatar,
|
||||
modifier = Modifier
|
||||
.clip(CircleShape)
|
||||
.size(48.dp),
|
||||
.size(96.dp),
|
||||
contentDescription = "",
|
||||
contentScale = ContentScale.Crop
|
||||
)
|
||||
Spacer(modifier = Modifier.width(32.dp))
|
||||
//个人统计
|
||||
|
||||
// 统计数据
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.weight(1f)
|
||||
modifier = Modifier.weight(1f),
|
||||
horizontalArrangement = Arrangement.spacedBy(0.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
// 帖子数
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.width(80.dp)
|
||||
.height(40.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.weight(1f)
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
Text(
|
||||
text = postCount.toString(),
|
||||
fontWeight = FontWeight.W600,
|
||||
fontSize = 16.sp,
|
||||
color = AppColors.text
|
||||
fontWeight = FontWeight.Medium,
|
||||
fontSize = 15.sp,
|
||||
color = Color(0xFF000000),
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Spacer(modifier = Modifier.height(2.dp))
|
||||
Text(
|
||||
text = "帖子",
|
||||
color = AppColors.text
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 11.sp,
|
||||
color = Color(0xFF000000),
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
|
||||
// 粉丝数
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.width(80.dp)
|
||||
.height(40.dp)
|
||||
.noRippleClickable {
|
||||
followerDebouncer {
|
||||
navController.navigate(
|
||||
@@ -95,26 +144,33 @@ fun UserItem(
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
Text(
|
||||
text = accountProfileEntity.followerCount.toString(),
|
||||
fontWeight = FontWeight.W600,
|
||||
fontSize = 16.sp,
|
||||
color = AppColors.text
|
||||
text = formattedFollowerCount,
|
||||
fontWeight = FontWeight.Medium,
|
||||
fontSize = 15.sp,
|
||||
color = Color(0xFF000000),
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Spacer(modifier = Modifier.height(2.dp))
|
||||
Text(
|
||||
text = "粉丝",
|
||||
color = AppColors.text
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 11.sp,
|
||||
color = Color(0xFF000000),
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
|
||||
// 关注数
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.width(80.dp)
|
||||
.height(40.dp)
|
||||
.offset(x = 6.dp)
|
||||
.noRippleClickable {
|
||||
followingDebouncer {
|
||||
navController.navigate(
|
||||
@@ -124,49 +180,161 @@ fun UserItem(
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
Text(
|
||||
text = accountProfileEntity.followingCount.toString(),
|
||||
fontWeight = FontWeight.W600,
|
||||
fontSize = 16.sp,
|
||||
color = AppColors.text
|
||||
fontWeight = FontWeight.Medium,
|
||||
fontSize = 15.sp,
|
||||
color = Color(0xFF000000),
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Spacer(modifier = Modifier.height(2.dp))
|
||||
Text(
|
||||
text = "关注",
|
||||
color = AppColors.text
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 11.sp,
|
||||
color = Color(0xFF000000),
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
// 昵称
|
||||
Text(
|
||||
text = accountProfileEntity.nickName,
|
||||
fontWeight = FontWeight.W600,
|
||||
fontSize = 16.sp,
|
||||
color = AppColors.text
|
||||
)
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
// 个人简介
|
||||
if (accountProfileEntity.bio.isNotEmpty()){
|
||||
Text(
|
||||
text = accountProfileEntity.bio,
|
||||
fontSize = 14.sp,
|
||||
color = AppColors.secondaryText,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}else{
|
||||
Text(
|
||||
text = "No bio here.",
|
||||
fontSize = 14.sp,
|
||||
color = AppColors.secondaryText,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
|
||||
// 中间:昵称、简介、创建者信息
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
// 昵称
|
||||
Text(
|
||||
text = accountProfileEntity.nickName,
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 22.sp,
|
||||
letterSpacing = (-0.3).sp,
|
||||
color = Color(0xFF000000)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
|
||||
// 个人简介
|
||||
if (accountProfileEntity.bio.isNotEmpty()) {
|
||||
Text(
|
||||
text = accountProfileEntity.bio,
|
||||
fontSize = 13.sp,
|
||||
color = Color(0x993C3C43), // 60/255, 60/255, 67/255, alpha 0.6
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
} else {
|
||||
Text(
|
||||
text = "Welcome to my fantiac word i will show you something about magic",
|
||||
fontSize = 13.sp,
|
||||
color = Color(0x993C3C43),
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
|
||||
// 创建者信息(如果是 AI 账户,可以显示创建者)
|
||||
// 注意:当前 AccountProfileEntity 没有创建者字段,这里暂时留空
|
||||
// 如果需要显示,需要从其他地方获取创建者信息
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
|
||||
// 底部:标签按钮
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
// MBTI 标签
|
||||
if (!mbti.isNullOrEmpty()) {
|
||||
ProfileTag(
|
||||
text = mbti,
|
||||
backgroundColor = Color(0x33FF8D28), // 255/255, 141/255, 40/255, alpha 0.2
|
||||
textColor = Color(0xFF000000)
|
||||
)
|
||||
}
|
||||
|
||||
// 星座标签
|
||||
if (!zodiac.isNullOrEmpty()) {
|
||||
ProfileTag(
|
||||
text = zodiac,
|
||||
backgroundColor = Color(0x33FFCC00), // 255/255, 204/255, 0/255, alpha 0.2
|
||||
textColor = Color(0xFF000000)
|
||||
)
|
||||
}
|
||||
|
||||
// 编辑标签(仅自己可见)
|
||||
if (isSelf) {
|
||||
ProfileTag(
|
||||
text = "编辑",
|
||||
backgroundColor = Color(0x14947A80), // 124/255, 116/255, 128/255, alpha 0.08
|
||||
textColor = Color(0xFF9284BD), // 146/255, 132/255, 189/255
|
||||
leadingIcon = {
|
||||
EditIcon(
|
||||
color = Color(0xFF9284BD),
|
||||
modifier = Modifier.size(16.dp)
|
||||
)
|
||||
},
|
||||
onClick = onEditClick
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ProfileTag(
|
||||
text: String,
|
||||
backgroundColor: Color,
|
||||
textColor: Color,
|
||||
leadingIcon: (@Composable () -> Unit)? = null,
|
||||
onClick: (() -> Unit)? = null
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.height(25.dp)
|
||||
.clip(RoundedCornerShape(12.dp))
|
||||
.background(backgroundColor)
|
||||
.then(
|
||||
if (onClick != null) {
|
||||
Modifier.clickable(onClick = onClick)
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
)
|
||||
.padding(horizontal = 8.dp, vertical = 4.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
leadingIcon?.invoke()
|
||||
Text(
|
||||
text = text,
|
||||
fontSize = 12.sp,
|
||||
color = textColor
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun EditIcon(
|
||||
color: Color,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.bi),
|
||||
contentDescription = "编辑",
|
||||
modifier = modifier,
|
||||
colorFilter = ColorFilter.tint(color)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
package com.aiosman.ravenow.ui.login
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@@ -19,9 +23,13 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.aiosman.ravenow.AppState
|
||||
import com.aiosman.ravenow.AppStore
|
||||
import com.aiosman.ravenow.LocalAppTheme
|
||||
@@ -33,25 +41,28 @@ import com.aiosman.ravenow.data.ServiceException
|
||||
import com.aiosman.ravenow.data.AccountServiceImpl
|
||||
import com.aiosman.ravenow.data.api.getErrorMessageCode
|
||||
import com.aiosman.ravenow.ui.NavigationRoute
|
||||
import com.aiosman.ravenow.ui.comment.NoticeScreenHeader
|
||||
import com.aiosman.ravenow.ui.composables.ActionButton
|
||||
import com.aiosman.ravenow.ui.composables.CheckboxWithLabel
|
||||
import com.aiosman.ravenow.ui.composables.PolicyCheckbox
|
||||
import com.aiosman.ravenow.ui.composables.StatusBarSpacer
|
||||
import com.aiosman.ravenow.ui.composables.TextInputField
|
||||
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
||||
import com.aiosman.ravenow.utils.PasswordValidator
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
private val LightGrayBackground = Color(red = 250f / 255f, green = 249f / 255f, blue = 251f / 255f)
|
||||
private val IconGray = Color(red = 151f / 255f, green = 148f / 255f, blue = 153f / 255f)
|
||||
private val PurpleButton = Color(0xFF7C45ED)
|
||||
|
||||
@Composable
|
||||
fun EmailSignupScreen() {
|
||||
var appColor = LocalAppTheme.current
|
||||
val appColor = LocalAppTheme.current
|
||||
var email by remember { mutableStateOf("") }
|
||||
var password by remember { mutableStateOf("") }
|
||||
var confirmPassword by remember { mutableStateOf("") }
|
||||
var rememberMe by remember { mutableStateOf(false) }
|
||||
var acceptTerms by remember { mutableStateOf(false) }
|
||||
var acceptPromotions by remember { mutableStateOf(false) }
|
||||
val scope = rememberCoroutineScope()
|
||||
val navController = LocalNavController.current
|
||||
val context = LocalContext.current
|
||||
@@ -60,7 +71,6 @@ fun EmailSignupScreen() {
|
||||
var passwordError by remember { mutableStateOf<String?>(null) }
|
||||
var confirmPasswordError by remember { mutableStateOf<String?>(null) }
|
||||
var termsError by remember { mutableStateOf<Boolean>(false) }
|
||||
var promotionsError by remember { mutableStateOf<Boolean>(false) }
|
||||
fun validateForm(): Boolean {
|
||||
emailError = when {
|
||||
// 非空
|
||||
@@ -88,22 +98,8 @@ fun EmailSignupScreen() {
|
||||
}
|
||||
termsError = true
|
||||
return false
|
||||
} else {
|
||||
}
|
||||
termsError = false
|
||||
}
|
||||
if (!acceptPromotions) {
|
||||
scope.launch(Dispatchers.Main) {
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(R.string.error_not_accept_recive_notice),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
promotionsError = true
|
||||
return false
|
||||
} else {
|
||||
promotionsError = false
|
||||
}
|
||||
|
||||
return emailError == null && passwordError == null && confirmPasswordError == null
|
||||
}
|
||||
@@ -158,63 +154,127 @@ fun EmailSignupScreen() {
|
||||
}
|
||||
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(appColor.background)
|
||||
) {
|
||||
StatusBarSpacer()
|
||||
Box(
|
||||
// 顶部导航栏:返回箭头 + "注册"标题,左对齐
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 16.dp, start = 16.dp, end = 16.dp)
|
||||
.padding(top = 15.dp, start = 16.dp, bottom = 15.dp, end = 16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
NoticeScreenHeader(stringResource(R.string.sign_up_upper), moreIcon = false)
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.rider_pro_back_icon),
|
||||
contentDescription = "Back",
|
||||
modifier = Modifier
|
||||
.size(24.dp)
|
||||
.noRippleClickable {
|
||||
navController.navigateUp()
|
||||
},
|
||||
colorFilter = ColorFilter.tint(Color.Black)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text(
|
||||
text = stringResource(R.string.sign_up_upper),
|
||||
fontSize = 20.sp,
|
||||
fontWeight = FontWeight.W600,
|
||||
color = Color.Black
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.padding(32.dp))
|
||||
|
||||
// 输入区域
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(1f)
|
||||
.padding(horizontal = 24.dp)
|
||||
.padding(horizontal = 0.dp)
|
||||
) {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
// 邮箱输入框
|
||||
TextInputField(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(),
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 24.dp),
|
||||
text = email,
|
||||
onValueChange = {
|
||||
email = it
|
||||
},
|
||||
hint = stringResource(R.string.text_hint_email),
|
||||
error = emailError
|
||||
label = stringResource(R.string.login_email_label),
|
||||
hint = "输入电子邮件",
|
||||
error = emailError,
|
||||
leadingIcon = {
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.icon_email_light),
|
||||
contentDescription = "Email",
|
||||
modifier = Modifier.size(24.dp),
|
||||
colorFilter = ColorFilter.tint(IconGray)
|
||||
)
|
||||
Spacer(modifier = Modifier.padding(4.dp))
|
||||
},
|
||||
customBackgroundColor = LightGrayBackground,
|
||||
customCornerRadius = 16f
|
||||
)
|
||||
|
||||
// 密码输入框
|
||||
TextInputField(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(),
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 24.dp),
|
||||
text = password,
|
||||
onValueChange = {
|
||||
password = it
|
||||
},
|
||||
password = true,
|
||||
label = stringResource(R.string.text_hint_password).replace("输入", ""),
|
||||
hint = stringResource(R.string.text_hint_password),
|
||||
error = passwordError
|
||||
error = passwordError,
|
||||
leadingIcon = {
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.icon_lock_light),
|
||||
contentDescription = "Lock",
|
||||
modifier = Modifier.size(24.dp),
|
||||
colorFilter = ColorFilter.tint(IconGray)
|
||||
)
|
||||
Spacer(modifier = Modifier.padding(4.dp))
|
||||
},
|
||||
customBackgroundColor = LightGrayBackground,
|
||||
customCornerRadius = 16f
|
||||
)
|
||||
|
||||
// 确认密码输入框
|
||||
TextInputField(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(),
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 24.dp),
|
||||
text = confirmPassword,
|
||||
onValueChange = {
|
||||
confirmPassword = it
|
||||
},
|
||||
password = true,
|
||||
hint = stringResource(R.string.text_hint_confirm_password),
|
||||
error = confirmPasswordError
|
||||
label = stringResource(R.string.confirm_password_label),
|
||||
hint = stringResource(R.string.text_hint_password),
|
||||
error = confirmPasswordError,
|
||||
leadingIcon = {
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.icon_lock_light),
|
||||
contentDescription = "Lock",
|
||||
modifier = Modifier.size(24.dp),
|
||||
colorFilter = ColorFilter.tint(IconGray)
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
},
|
||||
customBackgroundColor = LightGrayBackground,
|
||||
customCornerRadius = 16f
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
// 功能选项区域
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 24.dp),
|
||||
horizontalAlignment = Alignment.Start,
|
||||
) {
|
||||
CheckboxWithLabel(
|
||||
@@ -236,42 +296,31 @@ fun EmailSignupScreen() {
|
||||
termsError = false
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
CheckboxWithLabel(
|
||||
checked = acceptPromotions,
|
||||
checkSize = 16,
|
||||
fontSize = 12,
|
||||
label = stringResource(R.string.agree_promotion),
|
||||
error = promotionsError
|
||||
) {
|
||||
acceptPromotions = it
|
||||
// 当用户勾选时,立即清除错误状态
|
||||
if (it) {
|
||||
promotionsError = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
Spacer(modifier = Modifier.height(76.dp))
|
||||
|
||||
// 底部注册按钮
|
||||
Box(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 24.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
ActionButton(
|
||||
modifier = Modifier
|
||||
.width(345.dp),
|
||||
text = stringResource(R.string.lets_ride_upper),
|
||||
backgroundColor = Color(0xffda3832),
|
||||
color = Color.White
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = stringResource(R.string.sign_up_upper),
|
||||
backgroundColor = PurpleButton,
|
||||
color = Color.White,
|
||||
fontSize = 17.sp,
|
||||
fontWeight = FontWeight.W600
|
||||
) {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
registerUser()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
BIN
app/src/main/res/drawable/icons_circle_camera.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
app/src/main/res/mipmap-hdpi/bi.png
Normal file
|
After Width: | Height: | Size: 331 B |
BIN
app/src/main/res/mipmap-hdpi/collect.png
Normal file
|
After Width: | Height: | Size: 438 B |
BIN
app/src/main/res/mipmap-hdpi/feedback_icon.png
Normal file
|
After Width: | Height: | Size: 434 B |
BIN
app/src/main/res/mipmap-hdpi/fengm.png
Normal file
|
After Width: | Height: | Size: 331 B |
BIN
app/src/main/res/mipmap-hdpi/icons_about.png
Normal file
|
After Width: | Height: | Size: 465 B |
BIN
app/src/main/res/mipmap-hdpi/icons_account_and_security.png
Normal file
|
After Width: | Height: | Size: 390 B |
BIN
app/src/main/res/mipmap-hdpi/icons_bell.png
Normal file
|
After Width: | Height: | Size: 425 B |
BIN
app/src/main/res/mipmap-hdpi/icons_block.png
Normal file
|
After Width: | Height: | Size: 476 B |
BIN
app/src/main/res/mipmap-hdpi/icons_dark_mode.png
Normal file
|
After Width: | Height: | Size: 381 B |
BIN
app/src/main/res/mipmap-hdpi/icons_edited_data.png
Normal file
|
After Width: | Height: | Size: 428 B |
BIN
app/src/main/res/mipmap-hdpi/icons_padlock.png
Normal file
|
After Width: | Height: | Size: 391 B |
BIN
app/src/main/res/mipmap-hdpi/icons_remove.png
Normal file
|
After Width: | Height: | Size: 396 B |
BIN
app/src/main/res/mipmap-hdpi/l_empty_img.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
app/src/main/res/mipmap-hdpi/log_out_icon.png
Normal file
|
After Width: | Height: | Size: 402 B |
BIN
app/src/main/res/mipmap-hdpi/menu_ico.png
Normal file
|
After Width: | Height: | Size: 156 B |
BIN
app/src/main/res/mipmap-hdpi/menu_icon.png
Normal file
|
After Width: | Height: | Size: 283 B |
BIN
app/src/main/res/mipmap-hdpi/naoz.png
Normal file
|
After Width: | Height: | Size: 551 B |
BIN
app/src/main/res/mipmap-hdpi/paip_coin_img.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
app/src/main/res/mipmap-hdpi/qr_code_icon.png
Normal file
|
After Width: | Height: | Size: 490 B |
BIN
app/src/main/res/mipmap-hdpi/sao.png
Normal file
|
After Width: | Height: | Size: 367 B |
BIN
app/src/main/res/mipmap-hdpi/xingzuo.png
Normal file
|
After Width: | Height: | Size: 593 B |
BIN
app/src/main/res/mipmap-mdpi/bi.png
Normal file
|
After Width: | Height: | Size: 283 B |
BIN
app/src/main/res/mipmap-mdpi/collect.png
Normal file
|
After Width: | Height: | Size: 348 B |
BIN
app/src/main/res/mipmap-mdpi/feedback_icon.png
Normal file
|
After Width: | Height: | Size: 326 B |
BIN
app/src/main/res/mipmap-mdpi/fengm.png
Normal file
|
After Width: | Height: | Size: 283 B |
BIN
app/src/main/res/mipmap-mdpi/icon_email_light.png
Normal file
|
After Width: | Height: | Size: 354 B |
BIN
app/src/main/res/mipmap-mdpi/icon_eyes_closed_light.png
Normal file
|
After Width: | Height: | Size: 268 B |
BIN
app/src/main/res/mipmap-mdpi/icon_lock_light.png
Normal file
|
After Width: | Height: | Size: 319 B |
BIN
app/src/main/res/mipmap-mdpi/icons_about.png
Normal file
|
After Width: | Height: | Size: 347 B |
BIN
app/src/main/res/mipmap-mdpi/icons_account_and_security.png
Normal file
|
After Width: | Height: | Size: 325 B |
BIN
app/src/main/res/mipmap-mdpi/icons_bell.png
Normal file
|
After Width: | Height: | Size: 328 B |
BIN
app/src/main/res/mipmap-mdpi/icons_block.png
Normal file
|
After Width: | Height: | Size: 404 B |
BIN
app/src/main/res/mipmap-mdpi/icons_dark_mode.png
Normal file
|
After Width: | Height: | Size: 329 B |
BIN
app/src/main/res/mipmap-mdpi/icons_edited_data.png
Normal file
|
After Width: | Height: | Size: 361 B |
BIN
app/src/main/res/mipmap-mdpi/icons_padlock.png
Normal file
|
After Width: | Height: | Size: 323 B |
BIN
app/src/main/res/mipmap-mdpi/icons_remove.png
Normal file
|
After Width: | Height: | Size: 289 B |
BIN
app/src/main/res/mipmap-mdpi/l_empty_img.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
app/src/main/res/mipmap-mdpi/log_out_icon.png
Normal file
|
After Width: | Height: | Size: 301 B |
BIN
app/src/main/res/mipmap-mdpi/menu_ico.png
Normal file
|
After Width: | Height: | Size: 143 B |
BIN
app/src/main/res/mipmap-mdpi/menu_icon.png
Normal file
|
After Width: | Height: | Size: 229 B |
BIN
app/src/main/res/mipmap-mdpi/naoz.png
Normal file
|
After Width: | Height: | Size: 419 B |
BIN
app/src/main/res/mipmap-mdpi/paip_coin_img.png
Normal file
|
After Width: | Height: | Size: 873 B |
BIN
app/src/main/res/mipmap-mdpi/qr_code_icon.png
Normal file
|
After Width: | Height: | Size: 390 B |
BIN
app/src/main/res/mipmap-mdpi/sao.png
Normal file
|
After Width: | Height: | Size: 290 B |
BIN
app/src/main/res/mipmap-mdpi/xingzuo.png
Normal file
|
After Width: | Height: | Size: 473 B |
BIN
app/src/main/res/mipmap-xhdpi/bi.png
Normal file
|
After Width: | Height: | Size: 415 B |
BIN
app/src/main/res/mipmap-xhdpi/collect.png
Normal file
|
After Width: | Height: | Size: 548 B |
BIN
app/src/main/res/mipmap-xhdpi/feedback_icon.png
Normal file
|
After Width: | Height: | Size: 516 B |
BIN
app/src/main/res/mipmap-xhdpi/fengm.png
Normal file
|
After Width: | Height: | Size: 415 B |
BIN
app/src/main/res/mipmap-xhdpi/icon_email_light.png
Normal file
|
After Width: | Height: | Size: 563 B |
BIN
app/src/main/res/mipmap-xhdpi/icon_eyes_closed_light.png
Normal file
|
After Width: | Height: | Size: 393 B |
BIN
app/src/main/res/mipmap-xhdpi/icon_lock_light.png
Normal file
|
After Width: | Height: | Size: 456 B |
BIN
app/src/main/res/mipmap-xhdpi/icons_about.png
Normal file
|
After Width: | Height: | Size: 552 B |
BIN
app/src/main/res/mipmap-xhdpi/icons_account_and_security.png
Normal file
|
After Width: | Height: | Size: 479 B |
BIN
app/src/main/res/mipmap-xhdpi/icons_bell.png
Normal file
|
After Width: | Height: | Size: 523 B |
BIN
app/src/main/res/mipmap-xhdpi/icons_block.png
Normal file
|
After Width: | Height: | Size: 588 B |
BIN
app/src/main/res/mipmap-xhdpi/icons_dark_mode.png
Normal file
|
After Width: | Height: | Size: 455 B |
BIN
app/src/main/res/mipmap-xhdpi/icons_edited_data.png
Normal file
|
After Width: | Height: | Size: 544 B |
BIN
app/src/main/res/mipmap-xhdpi/icons_padlock.png
Normal file
|
After Width: | Height: | Size: 473 B |
BIN
app/src/main/res/mipmap-xhdpi/icons_remove.png
Normal file
|
After Width: | Height: | Size: 457 B |
BIN
app/src/main/res/mipmap-xhdpi/l_empty_img.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
app/src/main/res/mipmap-xhdpi/log_out_icon.png
Normal file
|
After Width: | Height: | Size: 497 B |
BIN
app/src/main/res/mipmap-xhdpi/menu_ico.png
Normal file
|
After Width: | Height: | Size: 167 B |
BIN
app/src/main/res/mipmap-xhdpi/menu_icon.png
Normal file
|
After Width: | Height: | Size: 328 B |
BIN
app/src/main/res/mipmap-xhdpi/naoz.png
Normal file
|
After Width: | Height: | Size: 718 B |
BIN
app/src/main/res/mipmap-xhdpi/paip_coin_img.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
app/src/main/res/mipmap-xhdpi/qr_code_icon.png
Normal file
|
After Width: | Height: | Size: 595 B |
BIN
app/src/main/res/mipmap-xhdpi/sao.png
Normal file
|
After Width: | Height: | Size: 421 B |
BIN
app/src/main/res/mipmap-xhdpi/xingzuo.png
Normal file
|
After Width: | Height: | Size: 705 B |
BIN
app/src/main/res/mipmap-xxhdpi/bi.png
Normal file
|
After Width: | Height: | Size: 550 B |
BIN
app/src/main/res/mipmap-xxhdpi/collect.png
Normal file
|
After Width: | Height: | Size: 779 B |
BIN
app/src/main/res/mipmap-xxhdpi/feedback_icon.png
Normal file
|
After Width: | Height: | Size: 685 B |
BIN
app/src/main/res/mipmap-xxhdpi/fengm.png
Normal file
|
After Width: | Height: | Size: 550 B |
BIN
app/src/main/res/mipmap-xxhdpi/icon_email_light.png
Normal file
|
After Width: | Height: | Size: 729 B |
BIN
app/src/main/res/mipmap-xxhdpi/icon_eyes_closed_light.png
Normal file
|
After Width: | Height: | Size: 524 B |
BIN
app/src/main/res/mipmap-xxhdpi/icon_lock_light.png
Normal file
|
After Width: | Height: | Size: 643 B |
BIN
app/src/main/res/mipmap-xxhdpi/icons_about.png
Normal file
|
After Width: | Height: | Size: 787 B |
BIN
app/src/main/res/mipmap-xxhdpi/icons_account_and_security.png
Normal file
|
After Width: | Height: | Size: 656 B |
BIN
app/src/main/res/mipmap-xxhdpi/icons_bell.png
Normal file
|
After Width: | Height: | Size: 687 B |
BIN
app/src/main/res/mipmap-xxhdpi/icons_block.png
Normal file
|
After Width: | Height: | Size: 779 B |
BIN
app/src/main/res/mipmap-xxhdpi/icons_dark_mode.png
Normal file
|
After Width: | Height: | Size: 613 B |
BIN
app/src/main/res/mipmap-xxhdpi/icons_edited_data.png
Normal file
|
After Width: | Height: | Size: 746 B |
BIN
app/src/main/res/mipmap-xxhdpi/icons_padlock.png
Normal file
|
After Width: | Height: | Size: 660 B |
BIN
app/src/main/res/mipmap-xxhdpi/icons_remove.png
Normal file
|
After Width: | Height: | Size: 648 B |
BIN
app/src/main/res/mipmap-xxhdpi/l_empty_img.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/log_out_icon.png
Normal file
|
After Width: | Height: | Size: 652 B |
BIN
app/src/main/res/mipmap-xxhdpi/menu_ico.png
Normal file
|
After Width: | Height: | Size: 171 B |
BIN
app/src/main/res/mipmap-xxhdpi/menu_icon.png
Normal file
|
After Width: | Height: | Size: 418 B |
BIN
app/src/main/res/mipmap-xxhdpi/naoz.png
Normal file
|
After Width: | Height: | Size: 980 B |