增加智能体头像裁剪页面
- 新增 `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.RemoveAccountScreen
|
||||||
import com.aiosman.ravenow.ui.account.ResetPasswordScreen
|
import com.aiosman.ravenow.ui.account.ResetPasswordScreen
|
||||||
import com.aiosman.ravenow.ui.agent.AddAgentScreen
|
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.group.CreateGroupChatScreen
|
||||||
import com.aiosman.ravenow.ui.chat.ChatAiScreen
|
import com.aiosman.ravenow.ui.chat.ChatAiScreen
|
||||||
import com.aiosman.ravenow.ui.chat.ChatScreen
|
import com.aiosman.ravenow.ui.chat.ChatScreen
|
||||||
@@ -103,6 +104,7 @@ sealed class NavigationRoute(
|
|||||||
data object ChatGroup : NavigationRoute("ChatGroup/{id}/{name}/{avatar}")
|
data object ChatGroup : NavigationRoute("ChatGroup/{id}/{name}/{avatar}")
|
||||||
data object CommentNoticeScreen : NavigationRoute("CommentNoticeScreen")
|
data object CommentNoticeScreen : NavigationRoute("CommentNoticeScreen")
|
||||||
data object ImageCrop : NavigationRoute("ImageCrop")
|
data object ImageCrop : NavigationRoute("ImageCrop")
|
||||||
|
data object AgentImageCrop : NavigationRoute("AgentImageCrop")
|
||||||
data object AccountSetting : NavigationRoute("AccountSetting")
|
data object AccountSetting : NavigationRoute("AccountSetting")
|
||||||
data object AboutScreen : NavigationRoute("AboutScreen")
|
data object AboutScreen : NavigationRoute("AboutScreen")
|
||||||
data object AddAgent : NavigationRoute("AddAgent")
|
data object AddAgent : NavigationRoute("AddAgent")
|
||||||
@@ -449,6 +451,13 @@ fun NavigationController(
|
|||||||
ImageCropScreen()
|
ImageCropScreen()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
composable(route = NavigationRoute.AgentImageCrop.route) {
|
||||||
|
CompositionLocalProvider(
|
||||||
|
LocalAnimatedContentScope provides this,
|
||||||
|
) {
|
||||||
|
AgentImageCropScreen()
|
||||||
|
}
|
||||||
|
}
|
||||||
composable(route = NavigationRoute.AccountSetting.route) {
|
composable(route = NavigationRoute.AccountSetting.route) {
|
||||||
CompositionLocalProvider(
|
CompositionLocalProvider(
|
||||||
LocalAnimatedContentScope provides this,
|
LocalAnimatedContentScope provides this,
|
||||||
|
|||||||
@@ -65,6 +65,8 @@ fun AddAgentScreen() {
|
|||||||
var agnetDescError by remember { mutableStateOf<String?>(null) }
|
var agnetDescError by remember { mutableStateOf<String?>(null) }
|
||||||
var errorMessage by remember { mutableStateOf<String?>(null) }
|
var errorMessage by remember { mutableStateOf<String?>(null) }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
fun onNameChange(value: String) {
|
fun onNameChange(value: String) {
|
||||||
model.name = value.trim()
|
model.name = value.trim()
|
||||||
agnetNameError = when {
|
agnetNameError = when {
|
||||||
@@ -147,8 +149,8 @@ fun AddAgentScreen() {
|
|||||||
.background(appColors.main)
|
.background(appColors.main)
|
||||||
.align(Alignment.BottomEnd)
|
.align(Alignment.BottomEnd)
|
||||||
.noRippleClickable {
|
.noRippleClickable {
|
||||||
model.isFromAddAgent = true
|
println("AddAgent: Navigating to AgentImageCrop")
|
||||||
navController.navigate(NavigationRoute.ImageCrop.route)
|
navController.navigate(NavigationRoute.AgentImageCrop.route)
|
||||||
},
|
},
|
||||||
contentAlignment = Alignment.Center
|
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,18 +114,9 @@ fun ImageCropScreen() {
|
|||||||
imageCrop?.let {
|
imageCrop?.let {
|
||||||
val bitmap = it.onCrop()
|
val bitmap = it.onCrop()
|
||||||
println("ImageCrop: Cropped bitmap created: ${bitmap != null}")
|
println("ImageCrop: Cropped bitmap created: ${bitmap != null}")
|
||||||
if (AddAgentViewModel.isFromAddAgent) {
|
|
||||||
println("ImageCrop: Setting bitmap to AddAgentViewModel")
|
// 专门处理个人资料头像
|
||||||
// 如果是从AddAgent页面跳转过来的
|
println("ImageCrop: Setting bitmap to AccountEditViewModel for user profile")
|
||||||
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.croppedBitmap = bitmap
|
||||||
AccountEditViewModel.viewModelScope.launch {
|
AccountEditViewModel.viewModelScope.launch {
|
||||||
AccountEditViewModel.updateUserProfile(context)
|
AccountEditViewModel.updateUserProfile(context)
|
||||||
@@ -133,7 +124,6 @@ fun ImageCropScreen() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Box(
|
Box(
|
||||||
|
|||||||
@@ -137,8 +137,7 @@ fun Agent() {
|
|||||||
.size(36.dp)
|
.size(36.dp)
|
||||||
.noRippleClickable {
|
.noRippleClickable {
|
||||||
if (DebounceUtils.simpleDebounceClick(lastClickTime, 500L) {
|
if (DebounceUtils.simpleDebounceClick(lastClickTime, 500L) {
|
||||||
// 设置标志,表示新增智能体后不需要刷新
|
// 导航到添加智能体页面
|
||||||
com.aiosman.ravenow.ui.agent.AddAgentViewModel.isFromAddAgent = true
|
|
||||||
navController.navigate(
|
navController.navigate(
|
||||||
NavigationRoute.AddAgent.route
|
NavigationRoute.AddAgent.route
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user