feat: add airtest scripts for camera and gallery testing
This commit is contained in:
@@ -76,6 +76,7 @@ import com.inspection.camera.util.PermissionManager
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
import java.io.File
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
@@ -158,17 +159,17 @@ fun CameraScreen(
|
||||
}
|
||||
}
|
||||
|
||||
// 获取位置
|
||||
// 获取位置(10秒超时)
|
||||
LaunchedEffect(permissionsState.allPermissionsGranted, hasLocationPermission) {
|
||||
isLocationLoading = true
|
||||
if (permissionsState.allPermissionsGranted && hasLocationPermission) {
|
||||
try {
|
||||
Log.d("CameraScreen", "Getting location...")
|
||||
locationText = locationHelper.getLocationInfo()
|
||||
Log.d("CameraScreen", "Location result: $locationText")
|
||||
if (locationText.isEmpty()) {
|
||||
locationText = "定位失败"
|
||||
Log.d("CameraScreen", "Getting location with 10s timeout...")
|
||||
val result = withTimeoutOrNull(10000) {
|
||||
locationHelper.getLocationInfo()
|
||||
}
|
||||
locationText = result ?: "定位失败"
|
||||
Log.d("CameraScreen", "Location result: $locationText")
|
||||
} catch (e: Exception) {
|
||||
Log.e("CameraScreen", "Location error", e)
|
||||
locationText = "定位失败"
|
||||
@@ -200,6 +201,9 @@ fun CameraScreen(
|
||||
onComplete = { uri ->
|
||||
capturedImages.add(uri)
|
||||
isCapturing = false
|
||||
},
|
||||
onError = {
|
||||
isCapturing = false
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -299,7 +303,7 @@ private fun CameraContent(
|
||||
|
||||
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
|
||||
|
||||
try {
|
||||
try {
|
||||
cameraProvider.unbindAll()
|
||||
cameraProvider.bindToLifecycle(
|
||||
lifecycleOwner,
|
||||
@@ -307,8 +311,10 @@ private fun CameraContent(
|
||||
preview,
|
||||
imageCapture
|
||||
)
|
||||
Log.d("CameraScreen", "Camera initialized successfully")
|
||||
} catch (e: Exception) {
|
||||
Log.e("CameraScreen", "Camera binding failed", e)
|
||||
imageCapture = null
|
||||
}
|
||||
}, ContextCompat.getMainExecutor(context))
|
||||
|
||||
@@ -462,24 +468,31 @@ private suspend fun getValidLocationTextForPhoto(
|
||||
manualAddress: String,
|
||||
locationHelper: LocationHelper
|
||||
): String {
|
||||
Log.d("CameraScreen", "getValidLocationTextForPhoto called, currentLocationText=$currentLocationText")
|
||||
|
||||
// 检查当前定位文本是否有效
|
||||
val invalidTexts = listOf("正在定位...", "定位失败", "请授予定位权限")
|
||||
val isInvalid = currentLocationText.isBlank() || invalidTexts.contains(currentLocationText)
|
||||
|
||||
if (!isInvalid) {
|
||||
Log.d("CameraScreen", "Using current location text: $currentLocationText")
|
||||
return currentLocationText
|
||||
}
|
||||
|
||||
// 使用手动地址
|
||||
if (manualAddress.isNotBlank()) {
|
||||
Log.d("CameraScreen", "Using manual address: $manualAddress")
|
||||
return manualAddress
|
||||
}
|
||||
|
||||
// 尝试快速获取当前位置(使用缓存)
|
||||
Log.d("CameraScreen", "Requesting new location...")
|
||||
val location = locationHelper.getCurrentLocation()
|
||||
return location?.let {
|
||||
val result = location?.let {
|
||||
"${"%.4f".format(it.latitude)}, ${"%.4f".format(it.longitude)}"
|
||||
} ?: "未知地点"
|
||||
Log.d("CameraScreen", "Got location result: $result")
|
||||
return result
|
||||
}
|
||||
|
||||
private fun capturePhoto(
|
||||
@@ -489,8 +502,10 @@ private fun capturePhoto(
|
||||
watermarkStyle: WatermarkStyle,
|
||||
imageQuality: ImageQuality,
|
||||
locationText: String,
|
||||
onComplete: (Uri) -> Unit
|
||||
onComplete: (Uri) -> Unit,
|
||||
onError: () -> Unit = {}
|
||||
) {
|
||||
Log.d("CameraScreen", "capturePhoto called, locationText=$locationText")
|
||||
val photoFile = File(
|
||||
context.cacheDir,
|
||||
"photo_${System.currentTimeMillis()}.jpg"
|
||||
@@ -504,9 +519,17 @@ private fun capturePhoto(
|
||||
executor,
|
||||
object : ImageCapture.OnImageSavedCallback {
|
||||
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
|
||||
Log.d("CameraScreen", "Photo saved, adding watermark with locationText=$locationText")
|
||||
val bitmap = BitmapFactory.decodeFile(photoFile.absolutePath)
|
||||
if (bitmap != null) {
|
||||
if (bitmap == null) {
|
||||
Log.e("CameraScreen", "Failed to decode photo file: ${photoFile.absolutePath}")
|
||||
onError()
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
val timeText = ImageProcessor.getCurrentTimeText()
|
||||
Log.d("CameraScreen", "Adding watermark: timeText=$timeText, locationText=$locationText")
|
||||
val watermarkedBitmap = ImageProcessor.addWatermark(
|
||||
bitmap,
|
||||
timeText,
|
||||
@@ -521,12 +544,22 @@ private fun capturePhoto(
|
||||
bitmap.recycle()
|
||||
watermarkedBitmap.recycle()
|
||||
|
||||
uri?.let { onComplete(it) }
|
||||
if (uri != null) {
|
||||
onComplete(uri)
|
||||
} else {
|
||||
Log.e("CameraScreen", "Failed to save image to gallery")
|
||||
onError()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("CameraScreen", "Error processing image", e)
|
||||
bitmap.recycle()
|
||||
onError()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onError(exception: ImageCaptureException) {
|
||||
Log.e("CameraScreen", "Photo capture failed", exception)
|
||||
onError()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -69,6 +69,7 @@ fun GalleryScreen(
|
||||
var selectedImages by remember { mutableStateOf<Set<Uri>>(emptySet()) }
|
||||
var showDeleteDialog by remember { mutableStateOf(false) }
|
||||
var isSelectionMode by remember { mutableStateOf(false) }
|
||||
var selectedImageUri by remember { mutableStateOf<Uri?>(null) }
|
||||
|
||||
// 加载图片
|
||||
LaunchedEffect(Unit) {
|
||||
@@ -149,6 +150,9 @@ fun GalleryScreen(
|
||||
} else {
|
||||
selectedImages + uri
|
||||
}
|
||||
} else {
|
||||
// 非选择模式下,点击打开大图查看器
|
||||
selectedImageUri = uri
|
||||
}
|
||||
},
|
||||
onLongClick = {
|
||||
@@ -189,6 +193,32 @@ fun GalleryScreen(
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// 大图查看对话框
|
||||
if (selectedImageUri != null) {
|
||||
AlertDialog(
|
||||
onDismissRequest = { selectedImageUri = null },
|
||||
title = { Text("图片预览") },
|
||||
text = {
|
||||
AsyncImage(
|
||||
model = ImageRequest.Builder(LocalContext.current)
|
||||
.data(selectedImageUri)
|
||||
.crossfade(true)
|
||||
.build(),
|
||||
contentDescription = "大图预览",
|
||||
contentScale = ContentScale.Fit,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.aspectRatio(1f)
|
||||
)
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = { selectedImageUri = null }) {
|
||||
Text("关闭")
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
||||
@@ -33,6 +33,44 @@ object ImageProcessor {
|
||||
private val dateFormat = SimpleDateFormat("yyyy年-MM月-dd日 HH:mm:ss", Locale.getDefault())
|
||||
private val fileNameFormat = SimpleDateFormat("yyyyMMddHHmm", Locale.getDefault())
|
||||
|
||||
/**
|
||||
* 尝试加载图片 Bitmap
|
||||
*/
|
||||
private fun tryLoadBitmap(context: Context, imageItem: ImageItem): Bitmap? {
|
||||
android.util.Log.d("ImageProcessor", "Loading image, URI: ${imageItem.uri}, path: ${imageItem.path}")
|
||||
|
||||
// 优先尝试 URI 加载
|
||||
try {
|
||||
val uri = imageItem.uri
|
||||
android.util.Log.d("ImageProcessor", "Trying to open URI: $uri")
|
||||
context.contentResolver.openInputStream(uri)?.use { inputStream ->
|
||||
val bitmap = BitmapFactory.decodeStream(inputStream)
|
||||
android.util.Log.d("ImageProcessor", "URI loaded bitmap: ${bitmap != null}")
|
||||
return bitmap
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e("ImageProcessor", "URI load failed: ${e.message}")
|
||||
}
|
||||
|
||||
// 尝试从文件路径加载
|
||||
if (imageItem.path.isNotEmpty()) {
|
||||
try {
|
||||
val file = java.io.File(imageItem.path)
|
||||
android.util.Log.d("ImageProcessor", "Trying file: ${file.absolutePath}, exists: ${file.exists()}")
|
||||
if (file.exists()) {
|
||||
val bitmap = BitmapFactory.decodeFile(imageItem.path)
|
||||
android.util.Log.d("ImageProcessor", "File loaded bitmap: ${bitmap != null}")
|
||||
return bitmap
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e("ImageProcessor", "File load failed: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
android.util.Log.w("ImageProcessor", "Failed to load image")
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前时间戳文本
|
||||
*/
|
||||
@@ -101,9 +139,10 @@ object ImageProcessor {
|
||||
}
|
||||
|
||||
/**
|
||||
* 合成多张图片
|
||||
* 合成多张图片(支持 URI)
|
||||
*/
|
||||
fun mergeImages(
|
||||
context: Context,
|
||||
images: List<ImageItem>,
|
||||
layoutType: MergeLayoutType,
|
||||
quality: ImageQuality
|
||||
@@ -114,7 +153,6 @@ object ImageProcessor {
|
||||
|
||||
val cols = layoutType.cols
|
||||
val rows = layoutType.rows
|
||||
val imageCount = images.size.coerceAtMost(rows * cols)
|
||||
|
||||
val outputWidth = 1920
|
||||
val outputHeight = 1080
|
||||
@@ -137,10 +175,10 @@ object ImageProcessor {
|
||||
val top = row * cellHeight
|
||||
|
||||
try {
|
||||
val sourceBitmap = BitmapFactory.decodeFile(imageItem.path)
|
||||
?: return@forEachIndexed
|
||||
val sourceBitmap = tryLoadBitmap(context, imageItem)
|
||||
|
||||
sourceBitmap ?: return@forEachIndexed
|
||||
|
||||
// 缩放并居中裁剪
|
||||
val scaledBitmap = scaleAndCropBitmap(sourceBitmap, cellWidth, cellHeight)
|
||||
val dstRect = Rect(left, top, left + cellWidth, top + cellHeight)
|
||||
canvas.drawBitmap(scaledBitmap, null, dstRect, paint)
|
||||
@@ -150,7 +188,6 @@ object ImageProcessor {
|
||||
}
|
||||
sourceBitmap.recycle()
|
||||
} catch (e: Exception) {
|
||||
// 加载失败绘制占位
|
||||
val placeholderPaint = Paint().apply {
|
||||
color = Color.LTGRAY
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user