diff --git a/app/src/main/java/com/aiosman/ravenow/ui/account/AccountSetting.kt b/app/src/main/java/com/aiosman/ravenow/ui/account/AccountSetting.kt index f6baafc..2b527ad 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/account/AccountSetting.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/account/AccountSetting.kt @@ -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) + modifier = Modifier + .fillMaxWidth() + .background( + color = appColors.background, + shape = RoundedCornerShape(AccountSettingConstants.CARD_CORNER_RADIUS.dp) + ) ) { - Box( - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 8.dp) - ) { - NavItem( - iconRes = R.mipmap.rider_pro_change_password, - label = stringResource(R.string.change_password), - modifier = Modifier.noRippleClickable { - navController.navigate(NavigationRoute.ChangePasswordScreen.route) - } - ) - } - - // 分割线 - Box( - modifier = Modifier - .fillMaxWidth() - .height(1.dp) - .background(appColors.divider) + SecurityOptionItem( + iconRes = R.mipmap.icons_padlock, + label = stringResource(R.string.change_password), + onClick = { navController.navigate(NavigationRoute.ChangePasswordScreen.route) } ) - Box( - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 8.dp) - ) { - NavItem( - iconRes = R.drawable.rider_pro_moment_delete, - label = stringResource(R.string.remove_account), - modifier = Modifier.noRippleClickable { - navController.navigate(NavigationRoute.RemoveAccountScreen.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: 导航到屏蔽用户页面 + } + ) + + SecurityOptionItem( + iconRes = R.mipmap.icons_remove, + label = stringResource(R.string.remove_account), + onClick = { navController.navigate(NavigationRoute.RemoveAccountScreen.route) }, + applyColorFilter = false ) } - } } \ No newline at end of file diff --git a/app/src/main/java/com/aiosman/ravenow/ui/account/edit2.kt b/app/src/main/java/com/aiosman/ravenow/ui/account/edit2.kt index 1e28a55..c6d2179 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/account/edit2.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/account/edit2.kt @@ -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,192 +145,28 @@ 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() - Box( - modifier = Modifier.padding(horizontal = 0.dp, vertical = 16.dp) - ) { - NoticeScreenHeader( - title = stringResource(R.string.edit_profile), - moreIcon = false - ) { - - Icon( - modifier = Modifier - .size(24.dp) - .debouncedClickable( - enabled = validate() && !model.isUpdating, - debounceTime = 1000L - ) { - if (validate() && !model.isUpdating) { - model.viewModelScope.launch { - model.isUpdating = true - model.updateUserProfile(context) - model.viewModelScope.launch(Dispatchers.Main) { - debouncedNavigation { - navController.navigateUp() - } - model.isUpdating = false - } - } - } - }, - 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)) - ) { - 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) - ) - } - } - - 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), + modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center ) { androidx.compose.material3.CircularProgressIndicator( @@ -327,24 +174,460 @@ fun AccountEditScreen2(onUpdateBanner: ((Uri, File, Context) -> Unit)? = null,) ) } } + model.profile != null -> { + Column( + modifier = Modifier.fillMaxSize() + ) { + // 顶部背景区域(圆角在底部,覆盖状态栏) + val banner = model.profile?.banner + val statusBarPadding = WindowInsets.systemBars.asPaddingValues().calculateTopPadding() + Box( + modifier = Modifier + .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 + ) { + if (validate() && !model.isUpdating) { + model.viewModelScope.launch { + model.isUpdating = true + model.updateUserProfile(context) + model.viewModelScope.launch(Dispatchers.Main) { + debouncedNavigation { + navController.navigateUp() + } + model.isUpdating = false + } + } + } + }, + contentAlignment = Alignment.Center + ) { + Text( + text = "保存", + fontSize = 17.sp, + fontWeight = FontWeight.Normal, + color = Color.White + ) + } + } + } + } 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) + ) + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/aiosman/ravenow/ui/composables/TextInputField.kt b/app/src/main/java/com/aiosman/ravenow/ui/composables/TextInputField.kt index 3bcb007..3b4df82 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/composables/TextInputField.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/composables/TextInputField.kt @@ -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,69 +56,96 @@ 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){ - BasicTextField( - value = text, - onValueChange = onValueChange, - modifier = Modifier - .weight(1f) - .onFocusChanged { focusState -> - isFocused = focusState.isFocused - }, - textStyle = TextStyle( - fontSize = 16.sp, - fontWeight = FontWeight.W500, - color = AppColors.text - ), - keyboardOptions = KeyboardOptions( - keyboardType = if (password) KeyboardType.Password else KeyboardType.Text - ), - visualTransformation = if (showPassword) VisualTransformation.None else PasswordVisualTransformation(), - singleLine = true, - enabled = enabled, - cursorBrush = SolidColor(AppColors.text), - ) + 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 + .fillMaxWidth() + .onFocusChanged { focusState -> + isFocused = focusState.isFocused + }, + textStyle = TextStyle( + fontSize = 16.sp, + fontWeight = FontWeight.W400, + color = AppColors.text + ), + keyboardOptions = KeyboardOptions( + keyboardType = if (password) KeyboardType.Password else KeyboardType.Email + ), + visualTransformation = if (showPassword) VisualTransformation.None else PasswordVisualTransformation(), + singleLine = true, + enabled = enabled, + cursorBrush = SolidColor(AppColors.text), + ) + if (text.isEmpty() && hint != null) { + Text( + text = hint, + color = HintTextColor, + fontSize = 16.sp, + fontWeight = FontWeight.W400 + ) + } + } if (password) { Image( - painter = painterResource(id = R.drawable.rider_pro_eye), + painter = painterResource( + id = if (showPassword) { + R.drawable.rider_pro_eye + } else { + R.mipmap.icon_eyes_closed_light + } + ), contentDescription = "Password", modifier = Modifier - .size(18.dp) + .size(24.dp) .noRippleClickable { showPassword = !showPassword }, - colorFilter = ColorFilter.tint(AppColors.text) + colorFilter = ColorFilter.tint(PasswordIconColor) ) } } - - if (text.isEmpty()) { - hint?.let { - Text(it, modifier = Modifier.padding(start = 5.dp), color = AppColors.inputHint, fontWeight = FontWeight.W600) - } - } } Spacer(modifier = Modifier.height(8.dp)) Row( diff --git a/app/src/main/java/com/aiosman/ravenow/ui/group/GroupChatInfoViewModel.kt b/app/src/main/java/com/aiosman/ravenow/ui/group/GroupChatInfoViewModel.kt index 2dd62b2..a989194 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/group/GroupChatInfoViewModel.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/group/GroupChatInfoViewModel.kt @@ -9,9 +9,9 @@ import androidx.lifecycle.viewModelScope import com.aiosman.ravenow.AppStore import com.aiosman.ravenow.ChatState import com.aiosman.ravenow.data.api.ApiClient -import com.aiosman.ravenow.data.api.CreatePromptRuleRequestBody -import com.aiosman.ravenow.data.api.PromptRule -import com.aiosman.ravenow.data.api.PromptRuleQuota +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 @@ -33,8 +33,8 @@ class GroupChatInfoViewModel( val notificationStrategy get() = chatNotification?.strategy ?: "default" // 记忆管理相关状态 - var memoryQuota by mutableStateOf(null) - var memoryList by mutableStateOf>(emptyList()) + var memoryQuota by mutableStateOf(null) + var memoryList by mutableStateOf>(emptyList()) var isLoadingMemory by mutableStateOf(false) var memoryError by mutableStateOf(null) var promptOpenId by mutableStateOf(null) @@ -137,12 +137,12 @@ class GroupChatInfoViewModel( } // 创建智能体规则(群记忆) - val requestBody = CreatePromptRuleRequestBody( + val requestBody = CreateAgentRuleRequestBody( rule = memoryText, openId = openId ) - val response = ApiClient.api.createPromptRule(requestBody) + val response = ApiClient.api.createAgentRule(requestBody) if (response.isSuccessful) { addMemorySuccess = true @@ -184,14 +184,14 @@ class GroupChatInfoViewModel( ?: throw Exception("无法获取智能体信息") promptOpenId = fetchedOpenId - val quotaResponse = ApiClient.api.getPromptRuleQuota(fetchedOpenId) + val quotaResponse = ApiClient.api.getAgentRuleQuota(fetchedOpenId) if (quotaResponse.isSuccessful) { memoryQuota = quotaResponse.body()?.data } else { throw Exception("获取配额信息失败: ${quotaResponse.code()}") } } else { - val quotaResponse = ApiClient.api.getPromptRuleQuota(targetOpenId) + val quotaResponse = ApiClient.api.getAgentRuleQuota(targetOpenId) if (quotaResponse.isSuccessful) { memoryQuota = quotaResponse.body()?.data } else { @@ -226,14 +226,14 @@ class GroupChatInfoViewModel( ?: throw Exception("无法获取智能体信息") promptOpenId = fetchedOpenId - val listResponse = ApiClient.api.getPromptRuleList(fetchedOpenId, page = page, pageSize = pageSize) + val listResponse = ApiClient.api.getAgentRuleList(fetchedOpenId, page = page, pageSize = pageSize) if (listResponse.isSuccessful) { memoryList = listResponse.body()?.data?.list ?: emptyList() } else { throw Exception("获取记忆列表失败: ${listResponse.code()}") } } else { - val listResponse = ApiClient.api.getPromptRuleList(targetOpenId, page = page, pageSize = pageSize) + val listResponse = ApiClient.api.getAgentRuleList(targetOpenId, page = page, pageSize = pageSize) if (listResponse.isSuccessful) { memoryList = listResponse.body()?.data?.list ?: emptyList() } else { @@ -258,7 +258,7 @@ class GroupChatInfoViewModel( isLoadingMemory = true memoryError = null - val response = ApiClient.api.deletePromptRule(ruleId) + val response = ApiClient.api.deleteAgentRule(ruleId) if (response.isSuccessful) { // 刷新记忆列表和配额 promptOpenId?.let { openId -> @@ -292,13 +292,13 @@ class GroupChatInfoViewModel( val openId = targetOpenId ?: promptOpenId ?: throw Exception("无法获取智能体ID") - val requestBody = com.aiosman.ravenow.data.api.UpdatePromptRuleRequestBody( + val requestBody = com.aiosman.ravenow.data.api.UpdateAgentRuleRequestBody( id = ruleId, rule = newRuleText, openId = openId ) - val response = ApiClient.api.updatePromptRule(requestBody) + val response = ApiClient.api.updateAgentRule(requestBody) if (response.isSuccessful) { // 刷新记忆列表和配额 loadMemoryQuota(openId) diff --git a/app/src/main/java/com/aiosman/ravenow/ui/group/GroupMemoryManageScreen.kt b/app/src/main/java/com/aiosman/ravenow/ui/group/GroupMemoryManageScreen.kt index 47af95c..3c19423 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/group/GroupMemoryManageScreen.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/group/GroupMemoryManageScreen.kt @@ -257,7 +257,7 @@ fun GroupMemoryManageContent( @OptIn(ExperimentalMaterial3Api::class) @Composable fun EditGroupMemoryDialog( - memory: com.aiosman.ravenow.data.api.PromptRule, + memory: com.aiosman.ravenow.data.api.AgentRule, viewModel: GroupChatInfoViewModel, onDismiss: () -> Unit, onUpdateMemory: (String) -> Unit @@ -403,7 +403,7 @@ fun EditGroupMemoryDialog( */ @Composable fun MemoryItem( - memory: com.aiosman.ravenow.data.api.PromptRule, + memory: com.aiosman.ravenow.data.api.AgentRule, isEditing: Boolean = false, onEdit: () -> Unit = {}, onCancel: () -> Unit = {}, diff --git a/app/src/main/java/com/aiosman/ravenow/ui/index/Index.kt b/app/src/main/java/com/aiosman/ravenow/ui/index/Index.kt index bbf07a2..6f3b64a 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/index/Index.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/index/Index.kt @@ -1,6 +1,12 @@ package com.aiosman.ravenow.ui.index import androidx.compose.animation.animateColorAsState +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.FastOutLinearInEasing +import androidx.compose.animation.core.LinearOutSlowInEasing +import androidx.compose.animation.core.updateTransition +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.FastOutSlowInEasing import androidx.compose.animation.core.tween import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.Image @@ -11,15 +17,21 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.asPaddingValues +import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.navigationBars +import androidx.compose.foundation.layout.requiredHeight import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.requiredWidth import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState @@ -42,8 +54,10 @@ import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha @@ -59,7 +73,11 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.compose.ui.zIndex import com.aiosman.ravenow.AppState +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale import com.aiosman.ravenow.AppStore import com.aiosman.ravenow.GuestLoginCheckOut import com.aiosman.ravenow.GuestLoginCheckOutScene @@ -123,151 +141,16 @@ fun IndexScreen() { gesturesEnabled = drawerState.isOpen, drawerContent = { CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Ltr) { - Column( - modifier = Modifier - .requiredWidth(250.dp) - .fillMaxHeight() - .background( - AppColors.background - ) - ) { - Spacer(modifier = Modifier.height(88.dp)) - NavItem( - iconRes = R.drawable.rave_now_nav_account, - label = stringResource(R.string.account_and_security), - modifier = Modifier.noRippleClickable { - coroutineScope.launch { - drawerState.close() - navController.navigate(NavigationRoute.AccountSetting.route) - } - + SideMenuContent( + onClose = { + coroutineScope.launch { + drawerState.close() } - ) - Spacer(modifier = Modifier.height(16.dp)) - NavItem( - iconRes = R.drawable.rider_pro_favourited, - label = stringResource(R.string.favourites), - modifier = Modifier.noRippleClickable { - coroutineScope.launch { - drawerState.close() - navController.navigate(NavigationRoute.FavouriteList.route) - } - - } - ) - - NavItem( - iconRes = R.drawable.rave_now_nav_night, - label = stringResource(R.string.dark_mode), - rightContent = { - Switch( - checked = AppState.darkMode, - onCheckedChange = { - AppState.switchTheme() - }, - - colors = SwitchDefaults.colors( - checkedThumbColor = Color.White, - checkedTrackColor = AppColors.main, - uncheckedThumbColor = Color.White, - uncheckedTrackColor = AppColors.main.copy(alpha = 0.5f), - uncheckedBorderColor = Color.Transparent - ), - modifier = Modifier.scale(0.8f) - ) - } - ) - // divider - Box( - modifier = Modifier - .fillMaxWidth() - .padding(top = 16.dp, bottom = 16.dp, start = 16.dp, end = 16.dp) - ) { - Box( - modifier = Modifier - .fillMaxWidth() - .height(1.dp) - .background(AppColors.divider) - ) - } - NavItem( - iconRes = R.drawable.rave_now_nav_about, - label = stringResource(R.string.blocked), - modifier = Modifier.noRippleClickable { - coroutineScope.launch { - drawerState.close() - navController.navigate(NavigationRoute.AboutScreen.route) - } - } - ) - NavItem( - iconRes = R.drawable.rave_now_nav_about, - label = stringResource(R.string.feedback), - modifier = Modifier.noRippleClickable { - coroutineScope.launch { - drawerState.close() - navController.navigate(NavigationRoute.AboutScreen.route) - } - } - ) - NavItem( - iconRes = R.drawable.rave_now_nav_about, - label = stringResource(R.string.about_rave_now), - modifier = Modifier.noRippleClickable { - coroutineScope.launch { - drawerState.close() - navController.navigate(NavigationRoute.AboutScreen.route) - } - } - ) - // divider - Box( - modifier = Modifier - .fillMaxWidth() - .padding(top = 16.dp, bottom = 16.dp, start = 16.dp, end = 16.dp) - ) { - Box( - modifier = Modifier - .fillMaxWidth() - .height(1.dp) - .background(AppColors.divider) - ) - } - -// NavItem( -// iconRes = R.drawable.rave_now_nav_switch, -// label = "Switch Account" -// ) -// Spacer(modifier = Modifier.height(16.dp)) - NavItem( - iconRes = R.drawable.rave_now_nav_logout, - label = stringResource(R.string.logout), - modifier = Modifier.noRippleClickable { - coroutineScope.launch { - drawerState.close() - // 只有非游客用户才需要取消注册推送设备 - if (!AppStore.isGuest) { - Messaging.unregisterDevice(context) - } - AppStore.apply { - token = null - rememberMe = false - isGuest = false // 清除游客状态 - saveData() - } - // 删除推送渠道 - - navController.navigate(NavigationRoute.Login.route) { - popUpTo(NavigationRoute.Login.route) { - inclusive = true - } - } - AppState.ReloadAppState(context) - } - } - ) - - } + }, + navController = navController, + context = context, + isDrawerOpen = drawerState.isOpen + ) } } ) { @@ -447,7 +330,6 @@ fun IndexScreen() { ) } } - } @Composable @@ -623,4 +505,344 @@ fun NavItem( } } +} + +@Composable +fun SideMenuContent( + onClose: () -> Unit, + navController: androidx.navigation.NavController, + context: android.content.Context, + isDrawerOpen: Boolean +) { + val appColors = LocalAppTheme.current + val coroutineScope = rememberCoroutineScope() + var messageNotificationEnabled by remember { mutableStateOf(true) } + var darkModeEnabled by remember { mutableStateOf(AppState.darkMode) } + + // 菜单背景色 #FAF9FB + val menuBackgroundColor = Color(0xFFFAF9FB) + // 遮罩颜色 黑色透明度0.6 + val overlayColor = Color.Black.copy(alpha = 0.6f) + // 卡片背景色 白色 + val cardBackgroundColor = Color.White + // 跟随系统文字颜色 #979499 + val followSystemTextColor = Color(0xFF979499) + // 开关开启颜色 #7C45ED + val switchActiveColor = Color(0xFF7C45ED) + + Box( + modifier = Modifier + .fillMaxSize() + ) { + // 左侧半透明遮罩(平滑淡入淡出) + val overlayTransition = updateTransition(targetState = isDrawerOpen, label = "overlay") + val overlayAlpha by overlayTransition.animateFloat( + transitionSpec = { + if (targetState) { + tween(durationMillis = 400, easing = LinearOutSlowInEasing) + } else { + tween(durationMillis = 300, easing = FastOutLinearInEasing) + } + }, + label = "overlayAlpha" + ) { open -> if (open) 0.6f else 0f } + Box( + modifier = Modifier + .fillMaxSize() + .background(Color.Black.copy(alpha = overlayAlpha)) + ) + + // 右侧菜单面板 + Box( + modifier = Modifier + .requiredWidth(302.dp) + .requiredHeight(874.dp) + .align(Alignment.CenterEnd) + .background(menuBackgroundColor) + ) { + // 顶部状态栏间距 + val statusBarHeight = WindowInsets.systemBars.asPaddingValues().calculateTopPadding() + + // 扫一扫功能入口 - 右边距离右边66pt + Row( + modifier = Modifier + .align(Alignment.TopEnd) + .offset(x = (-112).dp, y = 88.dp) + .noRippleClickable { + // TODO: 实现扫一扫功能 + }, + horizontalArrangement = Arrangement.spacedBy(16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + // 扫一扫图标(使用现有图标或占位) + Image( + painter = painterResource(id = R.mipmap.sao), + contentDescription = null, + modifier = Modifier.size(24.dp), + colorFilter = ColorFilter.tint(Color.Black) + ) + } +// 绝对定位的"扫一扫"文字:上方71.5dp,右侧66dp + Text( + text = stringResource(R.string.scan_qr), + fontSize = 14.sp, + color = Color.Black, + modifier = Modifier + .align(Alignment.TopEnd) + .offset(x = (-66).dp, y = 91.5.dp) + ) +// QR码图标 - 右边距离右边112dp,上边距离上边68pt + Image( + painter = painterResource(id = R.mipmap.qr_code_icon), + contentDescription = null, + modifier = Modifier + .size(24.dp) + .align(Alignment.TopEnd) + .offset(x = (-26).dp, y = 88.dp) + .noRippleClickable { + // TODO: 实现QR码功能 + }, + colorFilter = ColorFilter.tint(Color.Black) + ) + + // 菜单选项卡片组 - 第一组卡片上方距离上方108pt(绝对定位) + Column( + modifier = Modifier + .fillMaxWidth() + .offset(y = 128.dp) // 直接距离顶部128dp(整体下移20dp) + .padding(horizontal = 16.dp), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + // 第一组卡片:编辑资料、账号安全、收藏 + MenuCard( + backgroundColor = cardBackgroundColor, + width = 270.dp, + height = 164.dp, + items = listOf( + MenuItem( + icon = R.mipmap.icons_edited_data, + label = stringResource(R.string.edit_profile_info), + onClick = { + coroutineScope.launch { + onClose() + navController.navigate(NavigationRoute.AccountEdit.route) + } + } + ), + MenuItem( + icon = R.mipmap.icons_account_and_security, + label = stringResource(R.string.account_and_security), + onClick = { + coroutineScope.launch { + onClose() + navController.navigate(NavigationRoute.AccountSetting.route) + } + } + ), + MenuItem( + icon = R.mipmap.collect, + label = stringResource(R.string.favourites), + onClick = { + coroutineScope.launch { + onClose() + navController.navigate(NavigationRoute.FavouriteList.route) + } + } + ) + ) + ) + + // 第二组卡片:暗色模式、消息通知 + MenuCard( + backgroundColor = cardBackgroundColor, + width = 270.dp, + height = 112.dp, // 根据设计图,第二组卡片高度为112dp + items = listOf( + MenuItem( + icon = R.mipmap.icons_dark_mode, + label = stringResource(R.string.dark_mode), + rightContent = { + Switch( + checked = darkModeEnabled, + onCheckedChange = { + darkModeEnabled = it + AppState.darkMode = it + AppState.appTheme = if (it) { + com.aiosman.ravenow.DarkThemeColors() + } else { + com.aiosman.ravenow.LightThemeColors() + } + AppStore.saveDarkMode(it) + }, + colors = SwitchDefaults.colors( + checkedThumbColor = Color.White, + checkedTrackColor = switchActiveColor, + uncheckedThumbColor = Color.White, + uncheckedTrackColor = switchActiveColor.copy(alpha = 0.5f), + uncheckedBorderColor = Color.Transparent + ), + modifier = Modifier.size(width = 64.dp, height = 28.dp) + ) + } + ), + MenuItem( + icon = R.mipmap.icons_bell, + label = stringResource(R.string.message_notification), + rightContent = { + Switch( + checked = messageNotificationEnabled, + onCheckedChange = { messageNotificationEnabled = it }, + colors = SwitchDefaults.colors( + checkedThumbColor = Color.White, + checkedTrackColor = switchActiveColor, + uncheckedThumbColor = Color.White, + uncheckedTrackColor = switchActiveColor.copy(alpha = 0.5f), + uncheckedBorderColor = Color.Transparent + ), + modifier = Modifier.size(width = 64.dp, height = 28.dp) + ) + } + ) + ) + ) + + // 第三组卡片:关于派派、反馈、退出登录 + MenuCard( + backgroundColor = cardBackgroundColor, + width = 270.dp, + height = 164.dp, + items = listOf( + MenuItem( + icon = R.mipmap.icons_about, + label = stringResource(R.string.about_paipai), + onClick = { + coroutineScope.launch { + onClose() + navController.navigate(NavigationRoute.AboutScreen.route) + } + } + ), + MenuItem( + icon = R.mipmap.feedback_icon, + label = stringResource(R.string.feedback), + onClick = { + coroutineScope.launch { + onClose() + navController.navigate(NavigationRoute.AboutScreen.route) + } + } + ), + MenuItem( + icon = R.mipmap.log_out_icon, + label = stringResource(R.string.logout_confirm), + onClick = { + coroutineScope.launch { + onClose() + // 只有非游客用户才需要取消注册推送设备 + if (!AppStore.isGuest) { + Messaging.unregisterDevice(context) + } + AppStore.apply { + token = null + rememberMe = false + isGuest = false + saveData() + } + navController.navigate(NavigationRoute.Login.route) { + popUpTo(NavigationRoute.Login.route) { + inclusive = true + } + } + AppState.ReloadAppState(context) + } + }, + showRightArrow = false + ) + ) + ) + } + } + } +} + +data class MenuItem( + val icon: Int, + val label: String, + val onClick: (() -> Unit)? = null, + val rightContent: @Composable (() -> Unit)? = null, + val showRightArrow: Boolean = true +) + +@Composable +fun MenuCard( + backgroundColor: Color, + items: List, + 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)) + ) + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/profile/ProfileV3.kt b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/profile/ProfileV3.kt index 0b2ef83..31a4d70 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/profile/ProfileV3.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/profile/ProfileV3.kt @@ -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,41 +371,34 @@ 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 { - Box( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp) - ) { - if (isSelf) { - SelfProfileAction( - onEditProfile = { - navController.navigate(NavigationRoute.AccountEdit.route) + if (!isSelf && it.id != AppState.UserId) { + Box( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp) + ) { + OtherProfileAction( + it, + onFollow = { + onFollowClick() }, - onPremiumClick = { - navController.navigate(NavigationRoute.VipSelPage.route) + onChat = { + onChatClick() } ) - } else { - if (it.id != AppState.UserId) { - OtherProfileAction( - it, - onFollow = { - onFollowClick() - }, - onChat = { - onChatClick() - } - ) - } } } } @@ -445,39 +452,50 @@ 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 -> - UserAgentsList( - agents = agents, - onAgentClick = onAgentClick, - onAvatarClick = { agent -> - // 导航到智能体个人主页,需要通过openId获取用户ID - scope.launch { - try { - val userService = com.aiosman.ravenow.data.UserServiceImpl() - val profile = userService.getUserProfileByOpenId(agent.openId) - navController.navigate( - NavigationRoute.AccountProfile.route - .replace("{id}", profile.id.toString()) - .replace("{isAiAccount}", "true") - ) - } catch (e: Exception) { - // 处理错误 + 0 -> GalleryGrid(moments = moments) + 1 -> { + if (!isAiAccount) { + UserAgentsList( + agents = agents, + onAgentClick = onAgentClick, + onAvatarClick = { agent -> + // 导航到智能体个人主页,需要通过openId获取用户ID + scope.launch { + try { + val userService = com.aiosman.ravenow.data.UserServiceImpl() + val profile = userService.getUserProfileByOpenId(agent.openId) + navController.navigate( + NavigationRoute.AccountProfile.route + .replace("{id}", profile.id.toString()) + .replace("{isAiAccount}", "true") + ) + } catch (e: Exception) { + // 处理错误 + } } - } - }, - modifier = Modifier.fillMaxSize() - ) + }, + modifier = Modifier.fillMaxSize() + ) + } else { + GroupChatPlaceholder() + } + } + 2 -> { + if (!isAiAccount) { + GroupChatPlaceholder() + } + } } } } + + // 底部间距,增加滚动距离 + Spacer(modifier = Modifier.height(100.dp)) } // 顶部导航栏 @@ -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,107 +596,264 @@ 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) + .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(top = 16.dp, bottom = 16.dp, end = 16.dp) // 增加上下内边距 + .align(Alignment.TopEnd) + .padding(top = statusBarHeight), // 从状态栏下方开始 + horizontalArrangement = Arrangement.End, + verticalAlignment = Alignment.CenterVertically ) { - StatusBarSpacer() + // 左侧:互动数据卡片 Row( modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp, vertical = 8.dp) + .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 ) { - if (!isMain) { - Image( - painter = painterResource(id = R.drawable.rider_pro_back_icon), - contentDescription = "Back", - modifier = Modifier - .noRippleClickable { - navController.navigateUp() - } - .size(24.dp), - colorFilter = ColorFilter.tint(appColors.text) - ) - Spacer(modifier = Modifier.width(8.dp)) - CustomAsyncImage( - LocalContext.current, - profile?.avatar, - modifier = Modifier - .size(32.dp) - .clip(CircleShape), - contentDescription = "", - contentScale = ContentScale.Crop - ) - Spacer(modifier = Modifier.width(16.dp)) - Text( - 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) - ) - } - } - } - 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 - ) - } + Image( + painter = painterResource(id = R.drawable.rider_pro_back_icon), + contentDescription = "Back", + modifier = Modifier + .noRippleClickable { + navController.navigateUp() + } + .size(24.dp), + colorFilter = ColorFilter.tint(Color.White) + ) + Spacer(modifier = Modifier.width(8.dp)) + CustomAsyncImage( + LocalContext.current, + profile?.avatar, + modifier = Modifier + .size(32.dp) + .clip(CircleShape), + contentDescription = "", + contentScale = ContentScale.Crop + ) + Spacer(modifier = Modifier.width(16.dp)) + Text( + text = profile?.nickName ?: "", + fontSize = 16.sp, + fontWeight = FontWeight.W600, + color = Color.White + ) } } } } +// 分享图标(向上箭头) +@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 + ) + } +} + /** * Agent菜单弹窗 */ diff --git a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/profile/composable/GroupChatEmptyContent.kt b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/profile/composable/GroupChatEmptyContent.kt new file mode 100644 index 0000000..2c5fa48 --- /dev/null +++ b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/profile/composable/GroupChatEmptyContent.kt @@ -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 + ) +} + diff --git a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/profile/composable/UserAgentsList.kt b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/profile/composable/UserAgentsList.kt index 30884d2..d91db42 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/profile/composable/UserAgentsList.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/profile/composable/UserAgentsList.kt @@ -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 - LazyColumn( - modifier = modifier.fillMaxSize(), - verticalArrangement = Arrangement.spacedBy(8.dp) - ) { - if (agents.isEmpty()) { - item { - EmptyAgentsView() - } - } else { + if (agents.isEmpty()) { + // 使用带分段控制器的空状态布局 + AgentEmptyContentWithSegments() + } else { + LazyColumn( + modifier = modifier.fillMaxSize(), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { items(agents) { agent -> UserAgentCard( agent = agent, @@ -76,11 +76,11 @@ fun UserAgentsList( onAvatarClick = onAvatarClick ) } - } - - // 底部间距 - item { - Spacer(modifier = Modifier.height(120.dp)) + + // 底部间距 + item { + Spacer(modifier = Modifier.height(120.dp)) + } } } } @@ -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 diff --git a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/profile/composable/UserItem.kt b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/profile/composable/UserItem.kt index ec7b99a..75a3583 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/profile/composable/UserItem.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/profile/composable/UserItem.kt @@ -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()){ + + // 中间:昵称、简介、创建者信息 + Column( + modifier = Modifier + .fillMaxWidth() + ) { + // 昵称 Text( - text = accountProfileEntity.bio, - fontSize = 14.sp, - color = AppColors.secondaryText, - maxLines = 1, - overflow = TextOverflow.Ellipsis + text = accountProfileEntity.nickName, + fontWeight = FontWeight.Bold, + fontSize = 22.sp, + letterSpacing = (-0.3).sp, + color = Color(0xFF000000) ) - }else{ + + 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 = "No bio here.", - fontSize = 14.sp, - color = AppColors.secondaryText, - maxLines = 1, - overflow = TextOverflow.Ellipsis + 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) + ) } diff --git a/app/src/main/java/com/aiosman/ravenow/ui/login/emailsignup.kt b/app/src/main/java/com/aiosman/ravenow/ui/login/emailsignup.kt index e0c6450..6ea5a26 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/login/emailsignup.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/login/emailsignup.kt @@ -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(null) } var confirmPasswordError by remember { mutableStateOf(null) } var termsError by remember { mutableStateOf(false) } - var promotionsError by remember { mutableStateOf(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 } + termsError = 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) + ) + }, + customBackgroundColor = LightGrayBackground, + customCornerRadius = 16f ) - Spacer(modifier = Modifier.padding(4.dp)) + + // 密码输入框 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) + ) + }, + customBackgroundColor = LightGrayBackground, + customCornerRadius = 16f ) - Spacer(modifier = Modifier.padding(4.dp)) + + // 确认密码输入框 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) + ) + }, + customBackgroundColor = LightGrayBackground, + customCornerRadius = 16f ) - Spacer(modifier = Modifier.height(8.dp)) + + 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() } } } - } - } } \ No newline at end of file diff --git a/app/src/main/res/drawable/icons_circle_camera.png b/app/src/main/res/drawable/icons_circle_camera.png new file mode 100644 index 0000000..1604630 Binary files /dev/null and b/app/src/main/res/drawable/icons_circle_camera.png differ diff --git a/app/src/main/res/mipmap-hdpi/bi.png b/app/src/main/res/mipmap-hdpi/bi.png new file mode 100644 index 0000000..a9ac2f7 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/bi.png differ diff --git a/app/src/main/res/mipmap-hdpi/collect.png b/app/src/main/res/mipmap-hdpi/collect.png new file mode 100644 index 0000000..980e928 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/collect.png differ diff --git a/app/src/main/res/mipmap-hdpi/feedback_icon.png b/app/src/main/res/mipmap-hdpi/feedback_icon.png new file mode 100644 index 0000000..cf730e3 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/feedback_icon.png differ diff --git a/app/src/main/res/mipmap-hdpi/fengm.png b/app/src/main/res/mipmap-hdpi/fengm.png new file mode 100644 index 0000000..a9ac2f7 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/fengm.png differ diff --git a/app/src/main/res/mipmap-hdpi/icons_about.png b/app/src/main/res/mipmap-hdpi/icons_about.png new file mode 100644 index 0000000..d7a63ce Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/icons_about.png differ diff --git a/app/src/main/res/mipmap-hdpi/icons_account_and_security.png b/app/src/main/res/mipmap-hdpi/icons_account_and_security.png new file mode 100644 index 0000000..1594cc3 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/icons_account_and_security.png differ diff --git a/app/src/main/res/mipmap-hdpi/icons_bell.png b/app/src/main/res/mipmap-hdpi/icons_bell.png new file mode 100644 index 0000000..28193ca Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/icons_bell.png differ diff --git a/app/src/main/res/mipmap-hdpi/icons_block.png b/app/src/main/res/mipmap-hdpi/icons_block.png new file mode 100644 index 0000000..a044809 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/icons_block.png differ diff --git a/app/src/main/res/mipmap-hdpi/icons_dark_mode.png b/app/src/main/res/mipmap-hdpi/icons_dark_mode.png new file mode 100644 index 0000000..2ab67d2 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/icons_dark_mode.png differ diff --git a/app/src/main/res/mipmap-hdpi/icons_edited_data.png b/app/src/main/res/mipmap-hdpi/icons_edited_data.png new file mode 100644 index 0000000..37dd5bd Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/icons_edited_data.png differ diff --git a/app/src/main/res/mipmap-hdpi/icons_padlock.png b/app/src/main/res/mipmap-hdpi/icons_padlock.png new file mode 100644 index 0000000..9b66dec Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/icons_padlock.png differ diff --git a/app/src/main/res/mipmap-hdpi/icons_remove.png b/app/src/main/res/mipmap-hdpi/icons_remove.png new file mode 100644 index 0000000..b597232 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/icons_remove.png differ diff --git a/app/src/main/res/mipmap-hdpi/l_empty_img.png b/app/src/main/res/mipmap-hdpi/l_empty_img.png new file mode 100644 index 0000000..eca7860 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/l_empty_img.png differ diff --git a/app/src/main/res/mipmap-hdpi/log_out_icon.png b/app/src/main/res/mipmap-hdpi/log_out_icon.png new file mode 100644 index 0000000..a0f253b Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/log_out_icon.png differ diff --git a/app/src/main/res/mipmap-hdpi/menu_ico.png b/app/src/main/res/mipmap-hdpi/menu_ico.png new file mode 100644 index 0000000..2156726 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/menu_ico.png differ diff --git a/app/src/main/res/mipmap-hdpi/menu_icon.png b/app/src/main/res/mipmap-hdpi/menu_icon.png new file mode 100644 index 0000000..ca7055e Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/menu_icon.png differ diff --git a/app/src/main/res/mipmap-hdpi/naoz.png b/app/src/main/res/mipmap-hdpi/naoz.png new file mode 100644 index 0000000..0e45348 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/naoz.png differ diff --git a/app/src/main/res/mipmap-hdpi/paip_coin_img.png b/app/src/main/res/mipmap-hdpi/paip_coin_img.png new file mode 100644 index 0000000..ed87fc8 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/paip_coin_img.png differ diff --git a/app/src/main/res/mipmap-hdpi/qr_code_icon.png b/app/src/main/res/mipmap-hdpi/qr_code_icon.png new file mode 100644 index 0000000..337b664 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/qr_code_icon.png differ diff --git a/app/src/main/res/mipmap-hdpi/sao.png b/app/src/main/res/mipmap-hdpi/sao.png new file mode 100644 index 0000000..e24ca83 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/sao.png differ diff --git a/app/src/main/res/mipmap-hdpi/xingzuo.png b/app/src/main/res/mipmap-hdpi/xingzuo.png new file mode 100644 index 0000000..cea7570 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/xingzuo.png differ diff --git a/app/src/main/res/mipmap-mdpi/bi.png b/app/src/main/res/mipmap-mdpi/bi.png new file mode 100644 index 0000000..de27731 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/bi.png differ diff --git a/app/src/main/res/mipmap-mdpi/collect.png b/app/src/main/res/mipmap-mdpi/collect.png new file mode 100644 index 0000000..cf8944e Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/collect.png differ diff --git a/app/src/main/res/mipmap-mdpi/feedback_icon.png b/app/src/main/res/mipmap-mdpi/feedback_icon.png new file mode 100644 index 0000000..b24206d Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/feedback_icon.png differ diff --git a/app/src/main/res/mipmap-mdpi/fengm.png b/app/src/main/res/mipmap-mdpi/fengm.png new file mode 100644 index 0000000..de27731 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/fengm.png differ diff --git a/app/src/main/res/mipmap-mdpi/icon_email_light.png b/app/src/main/res/mipmap-mdpi/icon_email_light.png new file mode 100644 index 0000000..1a3b78e Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/icon_email_light.png differ diff --git a/app/src/main/res/mipmap-mdpi/icon_eyes_closed_light.png b/app/src/main/res/mipmap-mdpi/icon_eyes_closed_light.png new file mode 100644 index 0000000..0a985c3 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/icon_eyes_closed_light.png differ diff --git a/app/src/main/res/mipmap-mdpi/icon_lock_light.png b/app/src/main/res/mipmap-mdpi/icon_lock_light.png new file mode 100644 index 0000000..ebd5db1 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/icon_lock_light.png differ diff --git a/app/src/main/res/mipmap-mdpi/icons_about.png b/app/src/main/res/mipmap-mdpi/icons_about.png new file mode 100644 index 0000000..82958f6 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/icons_about.png differ diff --git a/app/src/main/res/mipmap-mdpi/icons_account_and_security.png b/app/src/main/res/mipmap-mdpi/icons_account_and_security.png new file mode 100644 index 0000000..eaea4ad Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/icons_account_and_security.png differ diff --git a/app/src/main/res/mipmap-mdpi/icons_bell.png b/app/src/main/res/mipmap-mdpi/icons_bell.png new file mode 100644 index 0000000..77f33cf Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/icons_bell.png differ diff --git a/app/src/main/res/mipmap-mdpi/icons_block.png b/app/src/main/res/mipmap-mdpi/icons_block.png new file mode 100644 index 0000000..d9b9e33 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/icons_block.png differ diff --git a/app/src/main/res/mipmap-mdpi/icons_dark_mode.png b/app/src/main/res/mipmap-mdpi/icons_dark_mode.png new file mode 100644 index 0000000..7913789 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/icons_dark_mode.png differ diff --git a/app/src/main/res/mipmap-mdpi/icons_edited_data.png b/app/src/main/res/mipmap-mdpi/icons_edited_data.png new file mode 100644 index 0000000..9729324 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/icons_edited_data.png differ diff --git a/app/src/main/res/mipmap-mdpi/icons_padlock.png b/app/src/main/res/mipmap-mdpi/icons_padlock.png new file mode 100644 index 0000000..e5852c6 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/icons_padlock.png differ diff --git a/app/src/main/res/mipmap-mdpi/icons_remove.png b/app/src/main/res/mipmap-mdpi/icons_remove.png new file mode 100644 index 0000000..523091b Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/icons_remove.png differ diff --git a/app/src/main/res/mipmap-mdpi/l_empty_img.png b/app/src/main/res/mipmap-mdpi/l_empty_img.png new file mode 100644 index 0000000..f0eb680 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/l_empty_img.png differ diff --git a/app/src/main/res/mipmap-mdpi/log_out_icon.png b/app/src/main/res/mipmap-mdpi/log_out_icon.png new file mode 100644 index 0000000..e10ff62 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/log_out_icon.png differ diff --git a/app/src/main/res/mipmap-mdpi/menu_ico.png b/app/src/main/res/mipmap-mdpi/menu_ico.png new file mode 100644 index 0000000..d018831 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/menu_ico.png differ diff --git a/app/src/main/res/mipmap-mdpi/menu_icon.png b/app/src/main/res/mipmap-mdpi/menu_icon.png new file mode 100644 index 0000000..5cc29fe Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/menu_icon.png differ diff --git a/app/src/main/res/mipmap-mdpi/naoz.png b/app/src/main/res/mipmap-mdpi/naoz.png new file mode 100644 index 0000000..61c2529 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/naoz.png differ diff --git a/app/src/main/res/mipmap-mdpi/paip_coin_img.png b/app/src/main/res/mipmap-mdpi/paip_coin_img.png new file mode 100644 index 0000000..285be2a Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/paip_coin_img.png differ diff --git a/app/src/main/res/mipmap-mdpi/qr_code_icon.png b/app/src/main/res/mipmap-mdpi/qr_code_icon.png new file mode 100644 index 0000000..cc65ba3 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/qr_code_icon.png differ diff --git a/app/src/main/res/mipmap-mdpi/sao.png b/app/src/main/res/mipmap-mdpi/sao.png new file mode 100644 index 0000000..e032865 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/sao.png differ diff --git a/app/src/main/res/mipmap-mdpi/xingzuo.png b/app/src/main/res/mipmap-mdpi/xingzuo.png new file mode 100644 index 0000000..bcc1e12 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/xingzuo.png differ diff --git a/app/src/main/res/mipmap-xhdpi/bi.png b/app/src/main/res/mipmap-xhdpi/bi.png new file mode 100644 index 0000000..a010e6c Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/bi.png differ diff --git a/app/src/main/res/mipmap-xhdpi/collect.png b/app/src/main/res/mipmap-xhdpi/collect.png new file mode 100644 index 0000000..446cfdd Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/collect.png differ diff --git a/app/src/main/res/mipmap-xhdpi/feedback_icon.png b/app/src/main/res/mipmap-xhdpi/feedback_icon.png new file mode 100644 index 0000000..245f6e2 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/feedback_icon.png differ diff --git a/app/src/main/res/mipmap-xhdpi/fengm.png b/app/src/main/res/mipmap-xhdpi/fengm.png new file mode 100644 index 0000000..a010e6c Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/fengm.png differ diff --git a/app/src/main/res/mipmap-xhdpi/icon_email_light.png b/app/src/main/res/mipmap-xhdpi/icon_email_light.png new file mode 100644 index 0000000..54148ed Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/icon_email_light.png differ diff --git a/app/src/main/res/mipmap-xhdpi/icon_eyes_closed_light.png b/app/src/main/res/mipmap-xhdpi/icon_eyes_closed_light.png new file mode 100644 index 0000000..eff9952 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/icon_eyes_closed_light.png differ diff --git a/app/src/main/res/mipmap-xhdpi/icon_lock_light.png b/app/src/main/res/mipmap-xhdpi/icon_lock_light.png new file mode 100644 index 0000000..c36298a Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/icon_lock_light.png differ diff --git a/app/src/main/res/mipmap-xhdpi/icons_about.png b/app/src/main/res/mipmap-xhdpi/icons_about.png new file mode 100644 index 0000000..3daad99 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/icons_about.png differ diff --git a/app/src/main/res/mipmap-xhdpi/icons_account_and_security.png b/app/src/main/res/mipmap-xhdpi/icons_account_and_security.png new file mode 100644 index 0000000..b5e2bc4 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/icons_account_and_security.png differ diff --git a/app/src/main/res/mipmap-xhdpi/icons_bell.png b/app/src/main/res/mipmap-xhdpi/icons_bell.png new file mode 100644 index 0000000..c4111ad Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/icons_bell.png differ diff --git a/app/src/main/res/mipmap-xhdpi/icons_block.png b/app/src/main/res/mipmap-xhdpi/icons_block.png new file mode 100644 index 0000000..317e8d0 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/icons_block.png differ diff --git a/app/src/main/res/mipmap-xhdpi/icons_dark_mode.png b/app/src/main/res/mipmap-xhdpi/icons_dark_mode.png new file mode 100644 index 0000000..6ede1dc Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/icons_dark_mode.png differ diff --git a/app/src/main/res/mipmap-xhdpi/icons_edited_data.png b/app/src/main/res/mipmap-xhdpi/icons_edited_data.png new file mode 100644 index 0000000..4138ab7 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/icons_edited_data.png differ diff --git a/app/src/main/res/mipmap-xhdpi/icons_padlock.png b/app/src/main/res/mipmap-xhdpi/icons_padlock.png new file mode 100644 index 0000000..454c390 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/icons_padlock.png differ diff --git a/app/src/main/res/mipmap-xhdpi/icons_remove.png b/app/src/main/res/mipmap-xhdpi/icons_remove.png new file mode 100644 index 0000000..a33ec95 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/icons_remove.png differ diff --git a/app/src/main/res/mipmap-xhdpi/l_empty_img.png b/app/src/main/res/mipmap-xhdpi/l_empty_img.png new file mode 100644 index 0000000..55906f2 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/l_empty_img.png differ diff --git a/app/src/main/res/mipmap-xhdpi/log_out_icon.png b/app/src/main/res/mipmap-xhdpi/log_out_icon.png new file mode 100644 index 0000000..e32a698 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/log_out_icon.png differ diff --git a/app/src/main/res/mipmap-xhdpi/menu_ico.png b/app/src/main/res/mipmap-xhdpi/menu_ico.png new file mode 100644 index 0000000..8fa72d1 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/menu_ico.png differ diff --git a/app/src/main/res/mipmap-xhdpi/menu_icon.png b/app/src/main/res/mipmap-xhdpi/menu_icon.png new file mode 100644 index 0000000..dfb8818 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/menu_icon.png differ diff --git a/app/src/main/res/mipmap-xhdpi/naoz.png b/app/src/main/res/mipmap-xhdpi/naoz.png new file mode 100644 index 0000000..817c287 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/naoz.png differ diff --git a/app/src/main/res/mipmap-xhdpi/paip_coin_img.png b/app/src/main/res/mipmap-xhdpi/paip_coin_img.png new file mode 100644 index 0000000..050108b Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/paip_coin_img.png differ diff --git a/app/src/main/res/mipmap-xhdpi/qr_code_icon.png b/app/src/main/res/mipmap-xhdpi/qr_code_icon.png new file mode 100644 index 0000000..d5c139b Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/qr_code_icon.png differ diff --git a/app/src/main/res/mipmap-xhdpi/sao.png b/app/src/main/res/mipmap-xhdpi/sao.png new file mode 100644 index 0000000..1d199d0 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/sao.png differ diff --git a/app/src/main/res/mipmap-xhdpi/xingzuo.png b/app/src/main/res/mipmap-xhdpi/xingzuo.png new file mode 100644 index 0000000..83fbb1a Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/xingzuo.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/bi.png b/app/src/main/res/mipmap-xxhdpi/bi.png new file mode 100644 index 0000000..1232d0e Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/bi.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/collect.png b/app/src/main/res/mipmap-xxhdpi/collect.png new file mode 100644 index 0000000..2bdb011 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/collect.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/feedback_icon.png b/app/src/main/res/mipmap-xxhdpi/feedback_icon.png new file mode 100644 index 0000000..a39fb0f Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/feedback_icon.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/fengm.png b/app/src/main/res/mipmap-xxhdpi/fengm.png new file mode 100644 index 0000000..1232d0e Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/fengm.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/icon_email_light.png b/app/src/main/res/mipmap-xxhdpi/icon_email_light.png new file mode 100644 index 0000000..27551c0 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/icon_email_light.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/icon_eyes_closed_light.png b/app/src/main/res/mipmap-xxhdpi/icon_eyes_closed_light.png new file mode 100644 index 0000000..3f18bea Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/icon_eyes_closed_light.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/icon_lock_light.png b/app/src/main/res/mipmap-xxhdpi/icon_lock_light.png new file mode 100644 index 0000000..1355549 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/icon_lock_light.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/icons_about.png b/app/src/main/res/mipmap-xxhdpi/icons_about.png new file mode 100644 index 0000000..0049cd3 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/icons_about.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/icons_account_and_security.png b/app/src/main/res/mipmap-xxhdpi/icons_account_and_security.png new file mode 100644 index 0000000..33c0f44 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/icons_account_and_security.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/icons_bell.png b/app/src/main/res/mipmap-xxhdpi/icons_bell.png new file mode 100644 index 0000000..80aaf28 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/icons_bell.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/icons_block.png b/app/src/main/res/mipmap-xxhdpi/icons_block.png new file mode 100644 index 0000000..955b413 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/icons_block.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/icons_dark_mode.png b/app/src/main/res/mipmap-xxhdpi/icons_dark_mode.png new file mode 100644 index 0000000..0b77746 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/icons_dark_mode.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/icons_edited_data.png b/app/src/main/res/mipmap-xxhdpi/icons_edited_data.png new file mode 100644 index 0000000..77cf523 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/icons_edited_data.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/icons_padlock.png b/app/src/main/res/mipmap-xxhdpi/icons_padlock.png new file mode 100644 index 0000000..9da8a62 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/icons_padlock.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/icons_remove.png b/app/src/main/res/mipmap-xxhdpi/icons_remove.png new file mode 100644 index 0000000..73a03ce Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/icons_remove.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/l_empty_img.png b/app/src/main/res/mipmap-xxhdpi/l_empty_img.png new file mode 100644 index 0000000..65d8bc3 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/l_empty_img.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/log_out_icon.png b/app/src/main/res/mipmap-xxhdpi/log_out_icon.png new file mode 100644 index 0000000..df69674 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/log_out_icon.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/menu_ico.png b/app/src/main/res/mipmap-xxhdpi/menu_ico.png new file mode 100644 index 0000000..3aaf9bc Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/menu_ico.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/menu_icon.png b/app/src/main/res/mipmap-xxhdpi/menu_icon.png new file mode 100644 index 0000000..6eff62c Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/menu_icon.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/naoz.png b/app/src/main/res/mipmap-xxhdpi/naoz.png new file mode 100644 index 0000000..1555215 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/naoz.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/paip_coin_img.png b/app/src/main/res/mipmap-xxhdpi/paip_coin_img.png new file mode 100644 index 0000000..f7a9942 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/paip_coin_img.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/qr_code_icon.png b/app/src/main/res/mipmap-xxhdpi/qr_code_icon.png new file mode 100644 index 0000000..a72f4b1 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/qr_code_icon.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/sao.png b/app/src/main/res/mipmap-xxhdpi/sao.png new file mode 100644 index 0000000..a3f4c77 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/sao.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/xingzuo.png b/app/src/main/res/mipmap-xxhdpi/xingzuo.png new file mode 100644 index 0000000..ff8f71f Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/xingzuo.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/bi.png b/app/src/main/res/mipmap-xxxhdpi/bi.png new file mode 100644 index 0000000..028e23b Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/bi.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/collect.png b/app/src/main/res/mipmap-xxxhdpi/collect.png new file mode 100644 index 0000000..5be481e Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/collect.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/feedback_icon.png b/app/src/main/res/mipmap-xxxhdpi/feedback_icon.png new file mode 100644 index 0000000..bb4212b Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/feedback_icon.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/fengm.png b/app/src/main/res/mipmap-xxxhdpi/fengm.png new file mode 100644 index 0000000..028e23b Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/fengm.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/icons_about.png b/app/src/main/res/mipmap-xxxhdpi/icons_about.png new file mode 100644 index 0000000..fe99270 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/icons_about.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/icons_account_and_security.png b/app/src/main/res/mipmap-xxxhdpi/icons_account_and_security.png new file mode 100644 index 0000000..0f78135 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/icons_account_and_security.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/icons_bell.png b/app/src/main/res/mipmap-xxxhdpi/icons_bell.png new file mode 100644 index 0000000..9196797 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/icons_bell.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/icons_block.png b/app/src/main/res/mipmap-xxxhdpi/icons_block.png new file mode 100644 index 0000000..e13dcb3 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/icons_block.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/icons_dark_mode.png b/app/src/main/res/mipmap-xxxhdpi/icons_dark_mode.png new file mode 100644 index 0000000..bd67d43 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/icons_dark_mode.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/icons_edited_data.png b/app/src/main/res/mipmap-xxxhdpi/icons_edited_data.png new file mode 100644 index 0000000..0c9e9b6 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/icons_edited_data.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/icons_padlock.png b/app/src/main/res/mipmap-xxxhdpi/icons_padlock.png new file mode 100644 index 0000000..492b16a Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/icons_padlock.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/icons_remove.png b/app/src/main/res/mipmap-xxxhdpi/icons_remove.png new file mode 100644 index 0000000..da1adc6 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/icons_remove.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/l_empty_img.png b/app/src/main/res/mipmap-xxxhdpi/l_empty_img.png new file mode 100644 index 0000000..412d54d Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/l_empty_img.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/log_out_icon.png b/app/src/main/res/mipmap-xxxhdpi/log_out_icon.png new file mode 100644 index 0000000..2bdb56f Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/log_out_icon.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/menu_ico.png b/app/src/main/res/mipmap-xxxhdpi/menu_ico.png new file mode 100644 index 0000000..fb3b25a Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/menu_ico.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/menu_icon.png b/app/src/main/res/mipmap-xxxhdpi/menu_icon.png new file mode 100644 index 0000000..a1670ef Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/menu_icon.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/naoz.png b/app/src/main/res/mipmap-xxxhdpi/naoz.png new file mode 100644 index 0000000..ce622c7 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/naoz.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/paip_coin_img.png b/app/src/main/res/mipmap-xxxhdpi/paip_coin_img.png new file mode 100644 index 0000000..d85c046 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/paip_coin_img.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/qr_code_icon.png b/app/src/main/res/mipmap-xxxhdpi/qr_code_icon.png new file mode 100644 index 0000000..e390d33 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/qr_code_icon.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/sao.png b/app/src/main/res/mipmap-xxxhdpi/sao.png new file mode 100644 index 0000000..576f340 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/sao.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/xingzuo.png b/app/src/main/res/mipmap-xxxhdpi/xingzuo.png new file mode 100644 index 0000000..b9e0b15 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/xingzuo.png differ diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 9c5838d..2cd8e9d 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -23,10 +23,11 @@ ログイン レッツ・レヴ・ナウ または - ログイン状態を保持する + ログインを記憶 パスワードをお忘れですか? パスワード メールアドレス + パスワードの確認 メールアドレスは必須です パスワードは必須です メールアドレスを入力してください @@ -91,7 +92,7 @@ メールの送信に失敗しました。ネットワーク接続を確認するか、後でもう一度お試しください。 %1d秒前 %1d分前 - 同意する + パイパイに同意する Rave Nowのプライバシーポリシー ギャラリー チャット @@ -114,9 +115,10 @@ この投稿を報告する理由は? 閉じる ブロック済み + ブロック済みユーザー フィードバック Rave Nowについて - アカウントとセキュリティ + アカウントセキュリティ アカウントを削除 本当にアカウントを削除しますか?この操作は元に戻せません。 確認のためパスワードを入力してください @@ -248,6 +250,20 @@ 「適用」を選択してこのテーマを使用 「キャンセル」をタップして他のテーマをプレビュー + + MBTIタイプ + 星座 + 保存 + MBTIを選択 + 星座を選択 + + + さっと動かす + データの編集 + パイパイについて + フォローアップシステム + メッセージ通知 + ログアウト グループチャット情報 メンバーを追加 diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index e4eecf9..13398da 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -22,10 +22,11 @@ 登录 确认 其他账号登录 - 记住我 + 记住登录 忘记密码 密码 邮箱 + 确认密码 邮箱是必填项 密码是必填项 输入邮箱 @@ -75,7 +76,7 @@ 下载 原始图片 收藏 - 暗黑模式 + 暗色模式 明亮模式 发现新版本 立即更新 @@ -92,7 +93,7 @@ 邮件发送失败,请检查您的网络连接或稍后重试。 %1d秒前 %1d分钟前 - 同意 + 我同意派派的 用户协议 图片 私信 @@ -116,8 +117,9 @@ 关闭 关于Rave Now 已拉黑 + 被屏蔽的用户 反馈 - 账户与安全 + 账号安全 删除账户 注销账号为不可逆的操作,请确认 输入密码以确认 @@ -291,4 +293,12 @@ 保存 选择 MBTI 选择星座 + + + 扫一扫 + 编辑资料 + 关于派派 + 跟随系统 + 消息通知 + 退出登录 \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5d29b9e..f2bcb18 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -22,10 +22,11 @@ Log in Let\'s Rave Now or - Remember me. + Remember login Forgot password? What\'s your password What\'s your email + Confirm password Email is required Password is required Enter your email @@ -90,7 +91,7 @@ Failed to send email. Please check your network connection or try again later. %1d seconds ago %1d minutes ago - I agree to the + I agree to Paipai\'s Rave Now’s Privacy Policy Gallery CHAT @@ -113,9 +114,10 @@ Reason for reporting this post? Close Blocked + Blocked Users Feedback About Rave Now - Account and security + Account Security Remove Account Are you sure you want to remove your account? This action cannot be undone. Please enter your password to confirm @@ -285,4 +287,13 @@ Save Choose MBTI Choose Zodiac + + + Scan QR + Edit Profile + About Paipai + Follow System + Message Notification + Logout + \ No newline at end of file