增加智能体头像裁剪页面
- 新增 `AgentImageCropScreen.kt` 用于智能体头像的裁剪和预览。 - 调整导航逻辑,添加智能体时跳转到新的 `AgentImageCrop` 路由。 - 原有的 `ImageCropScreen` 专门用于处理个人资料头像的裁剪。 - `AddAgentViewModel` 中移除 `isFromAddAgent` 标志,通过不同的导航路由区分头像裁剪来源。
This commit is contained in:
@@ -35,6 +35,7 @@ import com.aiosman.ravenow.ui.account.AccountSetting
|
||||
import com.aiosman.ravenow.ui.account.RemoveAccountScreen
|
||||
import com.aiosman.ravenow.ui.account.ResetPasswordScreen
|
||||
import com.aiosman.ravenow.ui.agent.AddAgentScreen
|
||||
import com.aiosman.ravenow.ui.agent.AgentImageCropScreen
|
||||
import com.aiosman.ravenow.ui.group.CreateGroupChatScreen
|
||||
import com.aiosman.ravenow.ui.chat.ChatAiScreen
|
||||
import com.aiosman.ravenow.ui.chat.ChatScreen
|
||||
@@ -103,6 +104,7 @@ sealed class NavigationRoute(
|
||||
data object ChatGroup : NavigationRoute("ChatGroup/{id}/{name}/{avatar}")
|
||||
data object CommentNoticeScreen : NavigationRoute("CommentNoticeScreen")
|
||||
data object ImageCrop : NavigationRoute("ImageCrop")
|
||||
data object AgentImageCrop : NavigationRoute("AgentImageCrop")
|
||||
data object AccountSetting : NavigationRoute("AccountSetting")
|
||||
data object AboutScreen : NavigationRoute("AboutScreen")
|
||||
data object AddAgent : NavigationRoute("AddAgent")
|
||||
@@ -449,6 +451,13 @@ fun NavigationController(
|
||||
ImageCropScreen()
|
||||
}
|
||||
}
|
||||
composable(route = NavigationRoute.AgentImageCrop.route) {
|
||||
CompositionLocalProvider(
|
||||
LocalAnimatedContentScope provides this,
|
||||
) {
|
||||
AgentImageCropScreen()
|
||||
}
|
||||
}
|
||||
composable(route = NavigationRoute.AccountSetting.route) {
|
||||
CompositionLocalProvider(
|
||||
LocalAnimatedContentScope provides this,
|
||||
|
||||
@@ -65,6 +65,8 @@ fun AddAgentScreen() {
|
||||
var agnetDescError by remember { mutableStateOf<String?>(null) }
|
||||
var errorMessage by remember { mutableStateOf<String?>(null) }
|
||||
|
||||
|
||||
|
||||
fun onNameChange(value: String) {
|
||||
model.name = value.trim()
|
||||
agnetNameError = when {
|
||||
@@ -147,8 +149,8 @@ fun AddAgentScreen() {
|
||||
.background(appColors.main)
|
||||
.align(Alignment.BottomEnd)
|
||||
.noRippleClickable {
|
||||
model.isFromAddAgent = true
|
||||
navController.navigate(NavigationRoute.ImageCrop.route)
|
||||
println("AddAgent: Navigating to AgentImageCrop")
|
||||
navController.navigate(NavigationRoute.AgentImageCrop.route)
|
||||
},
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
|
||||
@@ -0,0 +1,222 @@
|
||||
package com.aiosman.ravenow.ui.agent
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.net.Uri
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
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.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Check
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
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.setValue
|
||||
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.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.layout.onGloballyPositioned
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.aiosman.ravenow.LocalNavController
|
||||
import com.aiosman.ravenow.R
|
||||
import com.aiosman.ravenow.ui.composables.CustomAsyncImage
|
||||
import com.aiosman.ravenow.ui.composables.StatusBarSpacer
|
||||
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||
import com.image.cropview.CropType
|
||||
import com.image.cropview.EdgeType
|
||||
import com.image.cropview.ImageCrop
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.InputStream
|
||||
|
||||
/**
|
||||
* 专门用于智能体头像裁剪的页面
|
||||
*/
|
||||
@Composable
|
||||
fun AgentImageCropScreen() {
|
||||
var imageCrop by remember { mutableStateOf<ImageCrop?>(null) }
|
||||
var croppedBitmap by remember { mutableStateOf<Bitmap?>(null) }
|
||||
val context = LocalContext.current
|
||||
val configuration = LocalConfiguration.current
|
||||
var imageWidthInDp by remember { mutableStateOf(0) }
|
||||
var imageHeightInDp by remember { mutableStateOf(0) }
|
||||
val density = LocalDensity.current
|
||||
val navController = LocalNavController.current
|
||||
|
||||
val imagePickLauncher = rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.GetContent()
|
||||
) { uri: Uri? ->
|
||||
uri?.let {
|
||||
val bitmap = uriToBitmap(context = context, uri = it)
|
||||
if (bitmap != null) {
|
||||
val aspectRatio = bitmap.height.toFloat() / bitmap.width.toFloat()
|
||||
imageHeightInDp = (imageWidthInDp.toFloat() * aspectRatio).toInt()
|
||||
imageCrop = ImageCrop(bitmap)
|
||||
}
|
||||
}
|
||||
if (uri == null) {
|
||||
navController.popBackStack()
|
||||
}
|
||||
}
|
||||
|
||||
val systemUiController = rememberSystemUiController()
|
||||
LaunchedEffect(Unit) {
|
||||
systemUiController.setStatusBarColor(darkIcons = false, color = Color.Black)
|
||||
imagePickLauncher.launch("image/*")
|
||||
}
|
||||
|
||||
DisposableEffect(Unit) {
|
||||
onDispose {
|
||||
imageCrop = null
|
||||
systemUiController.setStatusBarColor(darkIcons = true, color = Color.White)
|
||||
}
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier.background(Color.Black).fillMaxSize()
|
||||
) {
|
||||
StatusBarSpacer()
|
||||
|
||||
// 头部工具栏
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 8.dp, horizontal = 16.dp)
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(R.drawable.rider_pro_back_icon),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.clickable {
|
||||
navController.popBackStack()
|
||||
},
|
||||
colorFilter = ColorFilter.tint(Color.White)
|
||||
)
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
|
||||
// 确认按钮
|
||||
Icon(
|
||||
Icons.Default.Check,
|
||||
contentDescription = null,
|
||||
tint = if (croppedBitmap != null) Color.Green else Color.White,
|
||||
modifier = Modifier.clickable {
|
||||
if (croppedBitmap != null) {
|
||||
// 如果已经有裁剪结果,直接返回
|
||||
println("AgentImageCrop: Using existing cropped bitmap")
|
||||
AddAgentViewModel.croppedBitmap = croppedBitmap
|
||||
AddAgentViewModel.viewModelScope.launch {
|
||||
AddAgentViewModel.updateAgentAvatar(context)
|
||||
navController.popBackStack()
|
||||
}
|
||||
} else {
|
||||
// 进行裁剪
|
||||
imageCrop?.let {
|
||||
val bitmap = it.onCrop()
|
||||
croppedBitmap = bitmap
|
||||
println("AgentImageCrop: Cropped bitmap created: ${bitmap != null}")
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// 裁剪预览区域
|
||||
Box(
|
||||
modifier = Modifier.fillMaxWidth().padding(24.dp)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(imageHeightInDp.dp)
|
||||
.onGloballyPositioned { coordinates ->
|
||||
with(density) {
|
||||
imageWidthInDp = coordinates.size.width.toDp().value.toInt()
|
||||
}
|
||||
}
|
||||
) {
|
||||
imageCrop?.ImageCropView(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
guideLineColor = Color.White,
|
||||
guideLineWidth = 2.dp,
|
||||
edgeCircleSize = 5.dp,
|
||||
cropType = CropType.SQUARE,
|
||||
edgeType = EdgeType.CIRCULAR
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 裁剪结果预览
|
||||
croppedBitmap?.let { bitmap ->
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(
|
||||
Color.Black.copy(alpha = 0.8f),
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
)
|
||||
.padding(16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(
|
||||
text = "✅ 裁剪完成",
|
||||
color = Color.Green,
|
||||
fontSize = 16.sp
|
||||
)
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
CustomAsyncImage(
|
||||
context,
|
||||
bitmap,
|
||||
modifier = Modifier
|
||||
.size(100.dp)
|
||||
.clip(CircleShape)
|
||||
.background(Color.Gray.copy(alpha = 0.3f), CircleShape),
|
||||
contentDescription = "智能体头像预览",
|
||||
contentScale = ContentScale.Crop
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text(
|
||||
text = "点击 ✓ 确认使用此头像",
|
||||
color = Color.White.copy(alpha = 0.8f),
|
||||
fontSize = 12.sp
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun uriToBitmap(context: Context, uri: Uri): Bitmap? {
|
||||
return try {
|
||||
val inputStream: InputStream? = context.contentResolver.openInputStream(uri)
|
||||
BitmapFactory.decodeStream(inputStream)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
null
|
||||
}
|
||||
}
|
||||
@@ -114,23 +114,13 @@ fun ImageCropScreen() {
|
||||
imageCrop?.let {
|
||||
val bitmap = it.onCrop()
|
||||
println("ImageCrop: Cropped bitmap created: ${bitmap != null}")
|
||||
if (AddAgentViewModel.isFromAddAgent) {
|
||||
println("ImageCrop: Setting bitmap to AddAgentViewModel")
|
||||
// 如果是从AddAgent页面跳转过来的
|
||||
AddAgentViewModel.croppedBitmap = bitmap
|
||||
AddAgentViewModel.viewModelScope.launch {
|
||||
AddAgentViewModel.updateAgentAvatar(context)
|
||||
AddAgentViewModel.isFromAddAgent = false
|
||||
navController.popBackStack()
|
||||
}
|
||||
} else {
|
||||
println("ImageCrop: Setting bitmap to AccountEditViewModel")
|
||||
// 默认处理AccountEdit
|
||||
AccountEditViewModel.croppedBitmap = bitmap
|
||||
AccountEditViewModel.viewModelScope.launch {
|
||||
AccountEditViewModel.updateUserProfile(context)
|
||||
navController.popBackStack()
|
||||
}
|
||||
|
||||
// 专门处理个人资料头像
|
||||
println("ImageCrop: Setting bitmap to AccountEditViewModel for user profile")
|
||||
AccountEditViewModel.croppedBitmap = bitmap
|
||||
AccountEditViewModel.viewModelScope.launch {
|
||||
AccountEditViewModel.updateUserProfile(context)
|
||||
navController.popBackStack()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,8 +137,7 @@ fun Agent() {
|
||||
.size(36.dp)
|
||||
.noRippleClickable {
|
||||
if (DebounceUtils.simpleDebounceClick(lastClickTime, 500L) {
|
||||
// 设置标志,表示新增智能体后不需要刷新
|
||||
com.aiosman.ravenow.ui.agent.AddAgentViewModel.isFromAddAgent = true
|
||||
// 导航到添加智能体页面
|
||||
navController.navigate(
|
||||
NavigationRoute.AddAgent.route
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user