修复相机拍照问题,优化水印和合成功能
- 修复 ImageCapture 未绑定相机导致的拍照失败问题 - 添加标题/内容文字样式配置支持 - 使用用户配置的水印样式和图片质量 - 修复 Compose API 兼容性问题 (HorizontalDivider, entries) - 修复 Kotlin 版本兼容性问题 - 添加必要的依赖 (accompanist-permissions, coroutines-play-services)
This commit is contained in:
3
.idea/.gitignore
generated
vendored
Normal file
3
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
6
.idea/AndroidProjectSystem.xml
generated
Normal file
6
.idea/AndroidProjectSystem.xml
generated
Normal 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
12
.idea/gradle.xml
generated
Normal 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
8
.idea/markdown.xml
generated
Normal 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
10
.idea/migrations.xml
generated
Normal 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
10
.idea/misc.xml
generated
Normal 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
17
.idea/runConfigurations.xml
generated
Normal 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
6
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 ->
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user