修复相机拍照问题,优化水印和合成功能

- 修复 ImageCapture 未绑定相机导致的拍照失败问题
- 添加标题/内容文字样式配置支持
- 使用用户配置的水印样式和图片质量
- 修复 Compose API 兼容性问题 (HorizontalDivider, entries)
- 修复 Kotlin 版本兼容性问题
- 添加必要的依赖 (accompanist-permissions, coroutines-play-services)
This commit is contained in:
2026-02-28 17:12:13 +08:00
parent d8fe374a16
commit e8be0ef93f
15 changed files with 107 additions and 34 deletions

3
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

6
.idea/AndroidProjectSystem.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AndroidProjectSystem">
<option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
</component>
</project>

12
.idea/gradle.xml generated Normal file
View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="CHOOSE_PER_TEST" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
</GradleProjectSettings>
</option>
</component>
</project>

8
.idea/markdown.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MarkdownSettings">
<option name="previewPanelProviderInfo">
<ProviderInfo name="Compose (experimental)" className="com.intellij.markdown.compose.preview.ComposePanelProvider" />
</option>
</component>
</project>

10
.idea/migrations.xml generated Normal file
View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectMigrations">
<option name="MigrateToGradleLocalJavaHome">
<set>
<option value="$PROJECT_DIR$" />
</set>
</option>
</component>
</project>

10
.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>

17
.idea/runConfigurations.xml generated Normal file
View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
<option value="com.intellij.execution.junit.PatternConfigurationProducer" />
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
<option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
</set>
</option>
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

View File

@@ -78,6 +78,7 @@ dependencies {
// Location
implementation("com.google.android.gms:play-services-location:21.0.1")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.7.3")
// DataStore for preferences
implementation("androidx.datastore:datastore-preferences:1.0.0")
@@ -85,6 +86,9 @@ dependencies {
// Coil for image loading
implementation("io.coil-kt:coil-compose:2.5.0")
// Accompanist permissions
implementation("com.google.accompanist:accompanist-permissions:0.32.0")
// Testing
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")

View File

@@ -1,7 +1,6 @@
package com.inspection.camera.data.models
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.alpha
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp

View File

@@ -43,6 +43,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
@@ -145,12 +146,13 @@ fun CameraScreen(
CameraContent(
flashMode = flashMode,
onFlashModeChange = { flashMode = it },
onCapture = {
onCapture = { imageCapture ->
if (!isCapturing) {
isCapturing = true
scope.launch {
capturePhoto(
context = context,
imageCapture = imageCapture,
flashMode = flashMode,
watermarkStyle = currentWatermarkStyle,
imageQuality = currentImageQuality,
@@ -183,7 +185,7 @@ fun CameraScreen(
private fun CameraContent(
flashMode: Int,
onFlashModeChange: (Int) -> Unit,
onCapture: () -> Unit,
onCapture: (ImageCapture) -> Unit,
onSettingsClick: () -> Unit,
onGalleryClick: () -> Unit,
onMergeClick: () -> Unit,
@@ -203,7 +205,7 @@ private fun CameraContent(
val cameraProvider = cameraProviderFuture.get()
val preview = Preview.Builder().build().also {
it.surfaceProvider = previewView?.surfaceProvider
it.setSurfaceProvider(previewView?.surfaceProvider)
}
imageCapture = ImageCapture.Builder()
@@ -254,6 +256,7 @@ private fun CameraContent(
// 底部控制栏
BottomControls(
capturedCount = capturedCount,
imageCapture = imageCapture,
onCapture = onCapture,
onGalleryClick = onGalleryClick,
onMergeClick = onMergeClick,
@@ -303,7 +306,8 @@ private fun TopControls(
@Composable
private fun BottomControls(
capturedCount: Int,
onCapture: () -> Unit,
imageCapture: ImageCapture?,
onCapture: (ImageCapture) -> Unit,
onGalleryClick: () -> Unit,
onMergeClick: () -> Unit,
isCapturing: Boolean,
@@ -333,7 +337,7 @@ private fun BottomControls(
}
FloatingActionButton(
onClick = onCapture,
onClick = { imageCapture?.let { onCapture(it) } },
modifier = Modifier.size(72.dp),
containerColor = Color.White,
shape = CircleShape
@@ -402,17 +406,13 @@ private fun PermissionRequest(
private fun capturePhoto(
context: Context,
imageCapture: ImageCapture,
flashMode: Int,
watermarkStyle: WatermarkStyle,
imageQuality: ImageQuality,
locationText: String,
onComplete: (Uri) -> Unit
) {
val imageCapture = ImageCapture.Builder()
.setFlashMode(flashMode)
.setCaptureMode(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY)
.build()
val photoFile = File(
context.cacheDir,
"photo_${System.currentTimeMillis()}.jpg"

View File

@@ -39,6 +39,7 @@ import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
@@ -56,6 +57,7 @@ import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage
import coil.request.ImageRequest
import com.inspection.camera.data.PreferencesManager
import com.inspection.camera.data.models.ImageItem
import com.inspection.camera.data.models.ImageQuality
import com.inspection.camera.data.models.MergeLayoutType
import com.inspection.camera.data.models.WatermarkStyle
@@ -222,8 +224,9 @@ fun MergeScreen(
onClick = {
scope.launch {
previewBitmap = withContext(Dispatchers.Default) {
val imageItems = images.map { ImageItem(uri = it, path = it.toString()) }
ImageProcessor.mergeImages(
images.toList(),
imageItems,
layoutType,
imageQuality
).let { bitmap ->
@@ -289,8 +292,9 @@ fun MergeScreen(
TextButton(onClick = {
scope.launch {
val bitmap = withContext(Dispatchers.Default) {
val imageItems = images.map { ImageItem(uri = it, path = it.toString()) }
ImageProcessor.mergeImages(
images.toList(),
imageItems,
layoutType,
imageQuality
).let { mergedBitmap ->

View File

@@ -15,8 +15,8 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Divider
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
@@ -117,7 +117,7 @@ fun SettingsScreen(
// 水印设置
SettingsSection(title = "水印设置") {
SettingsItem(title = "水印样式") {
WatermarkStyle.entries.forEach { style ->
listOf(WatermarkStyle.Default, WatermarkStyle.Simple, WatermarkStyle.Bold).forEach { style ->
Row(
modifier = Modifier
.fillMaxWidth()
@@ -135,10 +135,10 @@ fun SettingsScreen(
}
}
HorizontalDivider()
Divider()
SettingsItem(title = "地点获取方式") {
LocationMode.entries.forEach { mode ->
LocationMode.values().forEach { mode ->
Row(
modifier = Modifier
.fillMaxWidth()
@@ -161,7 +161,7 @@ fun SettingsScreen(
}
}
HorizontalDivider()
Divider()
SettingsItem(title = "手动输入地址") {
OutlinedTextField(
@@ -180,7 +180,7 @@ fun SettingsScreen(
// 合成设置
SettingsSection(title = "合成设置") {
SettingsItem(title = "默认合成布局") {
MergeLayoutType.entries.forEach { layout ->
MergeLayoutType.values().forEach { layout ->
Row(
modifier = Modifier
.fillMaxWidth()
@@ -198,10 +198,10 @@ fun SettingsScreen(
}
}
HorizontalDivider()
Divider()
SettingsItem(title = "合成图片质量") {
ImageQuality.entries.forEach { quality ->
ImageQuality.values().forEach { quality ->
Row(
modifier = Modifier
.fillMaxWidth()
@@ -219,10 +219,10 @@ fun SettingsScreen(
}
}
HorizontalDivider()
Divider()
SettingsItem(title = "默认标题样式") {
WatermarkStyle.entries.forEach { style ->
listOf(WatermarkStyle.Default, WatermarkStyle.Simple, WatermarkStyle.Bold).forEach { style ->
Row(
modifier = Modifier
.fillMaxWidth()
@@ -240,10 +240,10 @@ fun SettingsScreen(
}
}
HorizontalDivider()
Divider()
SettingsItem(title = "默认内容样式") {
WatermarkStyle.entries.forEach { style ->
listOf(WatermarkStyle.Default, WatermarkStyle.Simple, WatermarkStyle.Bold).forEach { style ->
Row(
modifier = Modifier
.fillMaxWidth()
@@ -277,7 +277,7 @@ fun SettingsScreen(
)
}
HorizontalDivider()
Divider()
SettingsItem(title = "巡检员信息") {
OutlinedTextField(

View File

@@ -137,12 +137,6 @@ object ImageProcessor {
val top = row * cellHeight
try {
val inputStream = imageItem.uri.path?.let { path ->
imageItem.uri.let { uri ->
inputStream
}
}
val sourceBitmap = BitmapFactory.decodeFile(imageItem.path)
?: return@forEachIndexed

View File

@@ -1,7 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
networkTimeout=10000
distributionUrl=https\://repo.huaweicloud.com/gradle/gradle-8.2-bin.zip
networkTimeout=120000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists