diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/component/ImageEditorDialog.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/component/ImageEditorDialog.kt index 0bb280cc..d4e4b074 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/component/ImageEditorDialog.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/component/ImageEditorDialog.kt @@ -1,6 +1,7 @@ package com.sukisu.ultra.ui.component import android.net.Uri +import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.background import androidx.compose.foundation.gestures.detectTransformGestures import androidx.compose.foundation.layout.* @@ -8,6 +9,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Check import androidx.compose.material.icons.filled.Close +import androidx.compose.material.icons.filled.Fullscreen import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment @@ -18,6 +20,7 @@ import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog @@ -27,6 +30,12 @@ import coil.request.ImageRequest import com.sukisu.ultra.R import com.sukisu.ultra.ui.util.BackgroundTransformation import com.sukisu.ultra.ui.util.saveTransformedBackground +import kotlinx.coroutines.launch +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.layout.onSizeChanged +import kotlin.math.max @Composable fun ImageEditorDialog( @@ -38,6 +47,48 @@ fun ImageEditorDialog( var offsetX by remember { mutableFloatStateOf(0f) } var offsetY by remember { mutableFloatStateOf(0f) } val context = LocalContext.current + val scope = rememberCoroutineScope() + val density = LocalDensity.current + var lastScale by remember { mutableFloatStateOf(1f) } + var lastOffsetX by remember { mutableFloatStateOf(0f) } + var lastOffsetY by remember { mutableFloatStateOf(0f) } + var imageSize by remember { mutableStateOf(Size.Zero) } + var screenSize by remember { mutableStateOf(Size.Zero) } + val animatedScale by animateFloatAsState( + targetValue = scale, + label = "ScaleAnimation" + ) + val animatedOffsetX by animateFloatAsState( + targetValue = offsetX, + label = "OffsetXAnimation" + ) + val animatedOffsetY by animateFloatAsState( + targetValue = offsetY, + label = "OffsetYAnimation" + ) + val updateTransformation = remember { + { newScale: Float, newOffsetX: Float, newOffsetY: Float -> + val scaleDiff = kotlin.math.abs(newScale - lastScale) + val offsetXDiff = kotlin.math.abs(newOffsetX - lastOffsetX) + val offsetYDiff = kotlin.math.abs(newOffsetY - lastOffsetY) + if (scaleDiff > 0.01f || offsetXDiff > 1f || offsetYDiff > 1f) { + scale = newScale + offsetX = newOffsetX + offsetY = newOffsetY + lastScale = newScale + lastOffsetX = newOffsetX + lastOffsetY = newOffsetY + } + } + } + val scaleToFullScreen = remember { + { + if (imageSize.height > 0 && screenSize.height > 0) { + val newScale = screenSize.height / imageSize.height + updateTransformation(newScale, 0f, 0f) + } + } + } Dialog( onDismissRequest = onDismiss, @@ -51,8 +102,10 @@ fun ImageEditorDialog( modifier = Modifier .fillMaxSize() .background(Color.Black.copy(alpha = 0.9f)) + .onSizeChanged { size -> + screenSize = Size(size.width.toFloat(), size.height.toFloat()) + } ) { - // 主图片区域 AsyncImage( model = ImageRequest.Builder(LocalContext.current) .data(imageUri) @@ -63,24 +116,39 @@ fun ImageEditorDialog( modifier = Modifier .fillMaxSize() .graphicsLayer( - scaleX = scale, - scaleY = scale, - translationX = offsetX, - translationY = offsetY + scaleX = animatedScale, + scaleY = animatedScale, + translationX = animatedOffsetX, + translationY = animatedOffsetY ) .pointerInput(Unit) { detectTransformGestures { _, pan, zoom, _ -> - scale = (scale * zoom).coerceIn(0.5f, 3f) - - // 限制平移范围,防止图片完全移出屏幕 - val maxOffset = size.width * (scale - 1) / 2 - offsetX = (offsetX + pan.x).coerceIn(-maxOffset, maxOffset) - offsetY = (offsetY + pan.y).coerceIn(-maxOffset, maxOffset) + scope.launch { + try { + val newScale = (scale * zoom).coerceIn(0.5f, 3f) + val maxOffsetX = max(0f, size.width * (newScale - 1) / 2) + val maxOffsetY = max(0f, size.height * (newScale - 1) / 2) + val newOffsetX = if (maxOffsetX > 0) { + (offsetX + pan.x).coerceIn(-maxOffsetX, maxOffsetX) + } else { + 0f + } + val newOffsetY = if (maxOffsetY > 0) { + (offsetY + pan.y).coerceIn(-maxOffsetY, maxOffsetY) + } else { + 0f + } + updateTransformation(newScale, newOffsetX, newOffsetY) + } catch (e: Exception) { + updateTransformation(lastScale, lastOffsetX, lastOffsetY) + } + } } } + .onSizeChanged { size -> + imageSize = Size(size.width.toFloat(), size.height.toFloat()) + } ) - - // 顶部工具栏 Row( modifier = Modifier .fillMaxWidth() @@ -100,12 +168,29 @@ fun ImageEditorDialog( tint = Color.White ) } - + IconButton( + onClick = { scaleToFullScreen() }, + modifier = Modifier + .clip(RoundedCornerShape(8.dp)) + .background(Color.Black.copy(alpha = 0.6f)) + ) { + Icon( + imageVector = Icons.Default.Fullscreen, + contentDescription = stringResource(R.string.reprovision), + tint = Color.White + ) + } IconButton( onClick = { - val transformation = BackgroundTransformation(scale, offsetX, offsetY) - val savedUri = context.saveTransformedBackground(imageUri, transformation) - savedUri?.let { onConfirm(it) } + scope.launch { + try { + val transformation = BackgroundTransformation(scale, offsetX, offsetY) + val savedUri = context.saveTransformedBackground(imageUri, transformation) + savedUri?.let { onConfirm(it) } + } catch (e: Exception) { + "" + } + } }, modifier = Modifier .clip(RoundedCornerShape(8.dp)) @@ -119,7 +204,6 @@ fun ImageEditorDialog( } } - // 底部提示 Box( modifier = Modifier .fillMaxWidth() diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/util/BackgroundUtils.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/util/BackgroundUtils.kt index 8bbcd1de..481e6dfa 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/util/BackgroundUtils.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/util/BackgroundUtils.kt @@ -59,17 +59,33 @@ fun Context.applyTransformationToBitmap(bitmap: Bitmap, transformation: Backgrou val matrix = Matrix() - matrix.postScale(transformation.scale, transformation.scale) + // 确保缩放值有效 + val safeScale = maxOf(0.1f, transformation.scale) + matrix.postScale(safeScale, safeScale) // 计算中心点 val centerX = targetWidth / 2f val centerY = targetHeight / 2f - // 缩放围绕中心点 - matrix.postTranslate( - -((bitmap.width * transformation.scale - targetWidth) / 2) + transformation.offsetX, - -((bitmap.height * transformation.scale - targetHeight) / 2) + transformation.offsetY - ) + // 计算偏移量,确保不会出现负最大值的问题 + val widthDiff = (bitmap.width * safeScale - targetWidth) + val heightDiff = (bitmap.height * safeScale - targetHeight) + + // 安全计算偏移量边界 + val maxOffsetX = maxOf(0f, widthDiff / 2) + val maxOffsetY = maxOf(0f, heightDiff / 2) + + // 限制偏移范围 + val safeOffsetX = if (maxOffsetX > 0) + transformation.offsetX.coerceIn(-maxOffsetX, maxOffsetX) else 0f + val safeOffsetY = if (maxOffsetY > 0) + transformation.offsetY.coerceIn(-maxOffsetY, maxOffsetY) else 0f + + // 应用偏移量到矩阵 + val translationX = -widthDiff / 2 + safeOffsetX + val translationY = -heightDiff / 2 + safeOffsetY + + matrix.postTranslate(translationX, translationY) // 将原始位图绘制到新位图上 canvas.drawBitmap(bitmap, matrix, null) @@ -92,7 +108,7 @@ fun Context.saveTransformedBackground(uri: Uri, transformation: BackgroundTransf return Uri.fromFile(file) } catch (e: Exception) { - Log.e("BackgroundUtils", "Failed to save transformed image: ${e.message}") + Log.e("BackgroundUtils", "Failed to save transformed image: ${e.message}", e) return null } } \ No newline at end of file diff --git a/manager/app/src/main/res/values-zh-rCN/strings.xml b/manager/app/src/main/res/values-zh-rCN/strings.xml index 509ad3da..7442e2ae 100644 --- a/manager/app/src/main/res/values-zh-rCN/strings.xml +++ b/manager/app/src/main/res/values-zh-rCN/strings.xml @@ -265,4 +265,5 @@ 调整背景图片 使用双指缩放图片,单指拖动调整位置 无法加载图片 + 重置 diff --git a/manager/app/src/main/res/values/strings.xml b/manager/app/src/main/res/values/strings.xml index 594ce789..894ed0f7 100644 --- a/manager/app/src/main/res/values/strings.xml +++ b/manager/app/src/main/res/values/strings.xml @@ -269,4 +269,5 @@ Adjust background image Use two fingers to zoom the image, and one finger to drag it to adjust the position Could not load image + Reprovision