diff --git a/app/src/main/java/com/aiosman/ravenow/ui/Navi.kt b/app/src/main/java/com/aiosman/ravenow/ui/Navi.kt index e8ff497..de851fd 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/Navi.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/Navi.kt @@ -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, diff --git a/app/src/main/java/com/aiosman/ravenow/ui/agent/AddAgent.kt b/app/src/main/java/com/aiosman/ravenow/ui/agent/AddAgent.kt index 4ae22db..7dd7ca1 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/agent/AddAgent.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/agent/AddAgent.kt @@ -65,6 +65,8 @@ fun AddAgentScreen() { var agnetDescError by remember { mutableStateOf(null) } var errorMessage by remember { mutableStateOf(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 ) { diff --git a/app/src/main/java/com/aiosman/ravenow/ui/agent/AgentImageCropScreen.kt b/app/src/main/java/com/aiosman/ravenow/ui/agent/AgentImageCropScreen.kt new file mode 100644 index 0000000..93e8a7a --- /dev/null +++ b/app/src/main/java/com/aiosman/ravenow/ui/agent/AgentImageCropScreen.kt @@ -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(null) } + var croppedBitmap by remember { mutableStateOf(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 + } +} diff --git a/app/src/main/java/com/aiosman/ravenow/ui/crop/ImageCropScreen.kt b/app/src/main/java/com/aiosman/ravenow/ui/crop/ImageCropScreen.kt index d54fb11..96ee22a 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/crop/ImageCropScreen.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/crop/ImageCropScreen.kt @@ -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() } } } diff --git a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/ai/Agent.kt b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/ai/Agent.kt index ee3d33f..34f037f 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/ai/Agent.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/ai/Agent.kt @@ -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 )