修复相机拍照问题,优化水印和合成功能
- 修复 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
|
// Location
|
||||||
implementation("com.google.android.gms:play-services-location:21.0.1")
|
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
|
// DataStore for preferences
|
||||||
implementation("androidx.datastore:datastore-preferences:1.0.0")
|
implementation("androidx.datastore:datastore-preferences:1.0.0")
|
||||||
@@ -85,6 +86,9 @@ dependencies {
|
|||||||
// Coil for image loading
|
// Coil for image loading
|
||||||
implementation("io.coil-kt:coil-compose:2.5.0")
|
implementation("io.coil-kt:coil-compose:2.5.0")
|
||||||
|
|
||||||
|
// Accompanist permissions
|
||||||
|
implementation("com.google.accompanist:accompanist-permissions:0.32.0")
|
||||||
|
|
||||||
// Testing
|
// Testing
|
||||||
testImplementation("junit:junit:4.13.2")
|
testImplementation("junit:junit:4.13.2")
|
||||||
androidTestImplementation("androidx.test.ext:junit:1.1.5")
|
androidTestImplementation("androidx.test.ext:junit:1.1.5")
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package com.inspection.camera.data.models
|
package com.inspection.camera.data.models
|
||||||
|
|
||||||
import androidx.compose.ui.graphics.Color
|
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.FontFamily
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ import androidx.compose.material3.Text
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.DisposableEffect
|
import androidx.compose.runtime.DisposableEffect
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.mutableStateListOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableIntStateOf
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
@@ -145,12 +146,13 @@ fun CameraScreen(
|
|||||||
CameraContent(
|
CameraContent(
|
||||||
flashMode = flashMode,
|
flashMode = flashMode,
|
||||||
onFlashModeChange = { flashMode = it },
|
onFlashModeChange = { flashMode = it },
|
||||||
onCapture = {
|
onCapture = { imageCapture ->
|
||||||
if (!isCapturing) {
|
if (!isCapturing) {
|
||||||
isCapturing = true
|
isCapturing = true
|
||||||
scope.launch {
|
scope.launch {
|
||||||
capturePhoto(
|
capturePhoto(
|
||||||
context = context,
|
context = context,
|
||||||
|
imageCapture = imageCapture,
|
||||||
flashMode = flashMode,
|
flashMode = flashMode,
|
||||||
watermarkStyle = currentWatermarkStyle,
|
watermarkStyle = currentWatermarkStyle,
|
||||||
imageQuality = currentImageQuality,
|
imageQuality = currentImageQuality,
|
||||||
@@ -183,7 +185,7 @@ fun CameraScreen(
|
|||||||
private fun CameraContent(
|
private fun CameraContent(
|
||||||
flashMode: Int,
|
flashMode: Int,
|
||||||
onFlashModeChange: (Int) -> Unit,
|
onFlashModeChange: (Int) -> Unit,
|
||||||
onCapture: () -> Unit,
|
onCapture: (ImageCapture) -> Unit,
|
||||||
onSettingsClick: () -> Unit,
|
onSettingsClick: () -> Unit,
|
||||||
onGalleryClick: () -> Unit,
|
onGalleryClick: () -> Unit,
|
||||||
onMergeClick: () -> Unit,
|
onMergeClick: () -> Unit,
|
||||||
@@ -203,7 +205,7 @@ private fun CameraContent(
|
|||||||
val cameraProvider = cameraProviderFuture.get()
|
val cameraProvider = cameraProviderFuture.get()
|
||||||
|
|
||||||
val preview = Preview.Builder().build().also {
|
val preview = Preview.Builder().build().also {
|
||||||
it.surfaceProvider = previewView?.surfaceProvider
|
it.setSurfaceProvider(previewView?.surfaceProvider)
|
||||||
}
|
}
|
||||||
|
|
||||||
imageCapture = ImageCapture.Builder()
|
imageCapture = ImageCapture.Builder()
|
||||||
@@ -254,6 +256,7 @@ private fun CameraContent(
|
|||||||
// 底部控制栏
|
// 底部控制栏
|
||||||
BottomControls(
|
BottomControls(
|
||||||
capturedCount = capturedCount,
|
capturedCount = capturedCount,
|
||||||
|
imageCapture = imageCapture,
|
||||||
onCapture = onCapture,
|
onCapture = onCapture,
|
||||||
onGalleryClick = onGalleryClick,
|
onGalleryClick = onGalleryClick,
|
||||||
onMergeClick = onMergeClick,
|
onMergeClick = onMergeClick,
|
||||||
@@ -303,7 +306,8 @@ private fun TopControls(
|
|||||||
@Composable
|
@Composable
|
||||||
private fun BottomControls(
|
private fun BottomControls(
|
||||||
capturedCount: Int,
|
capturedCount: Int,
|
||||||
onCapture: () -> Unit,
|
imageCapture: ImageCapture?,
|
||||||
|
onCapture: (ImageCapture) -> Unit,
|
||||||
onGalleryClick: () -> Unit,
|
onGalleryClick: () -> Unit,
|
||||||
onMergeClick: () -> Unit,
|
onMergeClick: () -> Unit,
|
||||||
isCapturing: Boolean,
|
isCapturing: Boolean,
|
||||||
@@ -333,7 +337,7 @@ private fun BottomControls(
|
|||||||
}
|
}
|
||||||
|
|
||||||
FloatingActionButton(
|
FloatingActionButton(
|
||||||
onClick = onCapture,
|
onClick = { imageCapture?.let { onCapture(it) } },
|
||||||
modifier = Modifier.size(72.dp),
|
modifier = Modifier.size(72.dp),
|
||||||
containerColor = Color.White,
|
containerColor = Color.White,
|
||||||
shape = CircleShape
|
shape = CircleShape
|
||||||
@@ -402,17 +406,13 @@ private fun PermissionRequest(
|
|||||||
|
|
||||||
private fun capturePhoto(
|
private fun capturePhoto(
|
||||||
context: Context,
|
context: Context,
|
||||||
|
imageCapture: ImageCapture,
|
||||||
flashMode: Int,
|
flashMode: Int,
|
||||||
watermarkStyle: WatermarkStyle,
|
watermarkStyle: WatermarkStyle,
|
||||||
imageQuality: ImageQuality,
|
imageQuality: ImageQuality,
|
||||||
locationText: String,
|
locationText: String,
|
||||||
onComplete: (Uri) -> Unit
|
onComplete: (Uri) -> Unit
|
||||||
) {
|
) {
|
||||||
val imageCapture = ImageCapture.Builder()
|
|
||||||
.setFlashMode(flashMode)
|
|
||||||
.setCaptureMode(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
val photoFile = File(
|
val photoFile = File(
|
||||||
context.cacheDir,
|
context.cacheDir,
|
||||||
"photo_${System.currentTimeMillis()}.jpg"
|
"photo_${System.currentTimeMillis()}.jpg"
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ import androidx.compose.material3.TextButton
|
|||||||
import androidx.compose.material3.TopAppBar
|
import androidx.compose.material3.TopAppBar
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateListOf
|
import androidx.compose.runtime.mutableStateListOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
@@ -56,6 +57,7 @@ import androidx.compose.ui.unit.dp
|
|||||||
import coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
import coil.request.ImageRequest
|
import coil.request.ImageRequest
|
||||||
import com.inspection.camera.data.PreferencesManager
|
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.ImageQuality
|
||||||
import com.inspection.camera.data.models.MergeLayoutType
|
import com.inspection.camera.data.models.MergeLayoutType
|
||||||
import com.inspection.camera.data.models.WatermarkStyle
|
import com.inspection.camera.data.models.WatermarkStyle
|
||||||
@@ -222,8 +224,9 @@ fun MergeScreen(
|
|||||||
onClick = {
|
onClick = {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
previewBitmap = withContext(Dispatchers.Default) {
|
previewBitmap = withContext(Dispatchers.Default) {
|
||||||
|
val imageItems = images.map { ImageItem(uri = it, path = it.toString()) }
|
||||||
ImageProcessor.mergeImages(
|
ImageProcessor.mergeImages(
|
||||||
images.toList(),
|
imageItems,
|
||||||
layoutType,
|
layoutType,
|
||||||
imageQuality
|
imageQuality
|
||||||
).let { bitmap ->
|
).let { bitmap ->
|
||||||
@@ -289,8 +292,9 @@ fun MergeScreen(
|
|||||||
TextButton(onClick = {
|
TextButton(onClick = {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
val bitmap = withContext(Dispatchers.Default) {
|
val bitmap = withContext(Dispatchers.Default) {
|
||||||
|
val imageItems = images.map { ImageItem(uri = it, path = it.toString()) }
|
||||||
ImageProcessor.mergeImages(
|
ImageProcessor.mergeImages(
|
||||||
images.toList(),
|
imageItems,
|
||||||
layoutType,
|
layoutType,
|
||||||
imageQuality
|
imageQuality
|
||||||
).let { mergedBitmap ->
|
).let { mergedBitmap ->
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ import androidx.compose.material.icons.Icons
|
|||||||
import androidx.compose.material.icons.filled.ArrowBack
|
import androidx.compose.material.icons.filled.ArrowBack
|
||||||
import androidx.compose.material3.Card
|
import androidx.compose.material3.Card
|
||||||
import androidx.compose.material3.CardDefaults
|
import androidx.compose.material3.CardDefaults
|
||||||
|
import androidx.compose.material3.Divider
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.HorizontalDivider
|
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
@@ -117,7 +117,7 @@ fun SettingsScreen(
|
|||||||
// 水印设置
|
// 水印设置
|
||||||
SettingsSection(title = "水印设置") {
|
SettingsSection(title = "水印设置") {
|
||||||
SettingsItem(title = "水印样式") {
|
SettingsItem(title = "水印样式") {
|
||||||
WatermarkStyle.entries.forEach { style ->
|
listOf(WatermarkStyle.Default, WatermarkStyle.Simple, WatermarkStyle.Bold).forEach { style ->
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
@@ -135,10 +135,10 @@ fun SettingsScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
HorizontalDivider()
|
Divider()
|
||||||
|
|
||||||
SettingsItem(title = "地点获取方式") {
|
SettingsItem(title = "地点获取方式") {
|
||||||
LocationMode.entries.forEach { mode ->
|
LocationMode.values().forEach { mode ->
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
@@ -161,7 +161,7 @@ fun SettingsScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
HorizontalDivider()
|
Divider()
|
||||||
|
|
||||||
SettingsItem(title = "手动输入地址") {
|
SettingsItem(title = "手动输入地址") {
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
@@ -180,7 +180,7 @@ fun SettingsScreen(
|
|||||||
// 合成设置
|
// 合成设置
|
||||||
SettingsSection(title = "合成设置") {
|
SettingsSection(title = "合成设置") {
|
||||||
SettingsItem(title = "默认合成布局") {
|
SettingsItem(title = "默认合成布局") {
|
||||||
MergeLayoutType.entries.forEach { layout ->
|
MergeLayoutType.values().forEach { layout ->
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
@@ -198,10 +198,10 @@ fun SettingsScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
HorizontalDivider()
|
Divider()
|
||||||
|
|
||||||
SettingsItem(title = "合成图片质量") {
|
SettingsItem(title = "合成图片质量") {
|
||||||
ImageQuality.entries.forEach { quality ->
|
ImageQuality.values().forEach { quality ->
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
@@ -219,10 +219,10 @@ fun SettingsScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
HorizontalDivider()
|
Divider()
|
||||||
|
|
||||||
SettingsItem(title = "默认标题样式") {
|
SettingsItem(title = "默认标题样式") {
|
||||||
WatermarkStyle.entries.forEach { style ->
|
listOf(WatermarkStyle.Default, WatermarkStyle.Simple, WatermarkStyle.Bold).forEach { style ->
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
@@ -240,10 +240,10 @@ fun SettingsScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
HorizontalDivider()
|
Divider()
|
||||||
|
|
||||||
SettingsItem(title = "默认内容样式") {
|
SettingsItem(title = "默认内容样式") {
|
||||||
WatermarkStyle.entries.forEach { style ->
|
listOf(WatermarkStyle.Default, WatermarkStyle.Simple, WatermarkStyle.Bold).forEach { style ->
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
@@ -277,7 +277,7 @@ fun SettingsScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
HorizontalDivider()
|
Divider()
|
||||||
|
|
||||||
SettingsItem(title = "巡检员信息") {
|
SettingsItem(title = "巡检员信息") {
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
|
|||||||
@@ -137,12 +137,6 @@ object ImageProcessor {
|
|||||||
val top = row * cellHeight
|
val top = row * cellHeight
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val inputStream = imageItem.uri.path?.let { path ->
|
|
||||||
imageItem.uri.let { uri ->
|
|
||||||
inputStream
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val sourceBitmap = BitmapFactory.decodeFile(imageItem.path)
|
val sourceBitmap = BitmapFactory.decodeFile(imageItem.path)
|
||||||
?: return@forEachIndexed
|
?: return@forEachIndexed
|
||||||
|
|
||||||
|
|||||||
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,7 +1,7 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
|
distributionUrl=https\://repo.huaweicloud.com/gradle/gradle-8.2-bin.zip
|
||||||
networkTimeout=10000
|
networkTimeout=120000
|
||||||
validateDistributionUrl=true
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
Reference in New Issue
Block a user