Skip to content

Commit 3a22ec0

Browse files
authored
Merge pull request #209 from rosuH/dev
Support tile mode && UI improvements
2 parents c85aefd + 401496c commit 3a22ec0

34 files changed

Lines changed: 652 additions & 103 deletions

.github/workflows/pr_pre_check.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: PR Basic Check
22

33
on:
44
pull_request:
5-
types: [opened, reopened]
5+
types: [opened,reopened,synchronize]
66
branches:
77
- master
88
jobs:
@@ -27,4 +27,4 @@ jobs:
2727
gradle-
2828
2929
- name: Build apk with Gradle
30-
run: bash ./gradlew assembleRelease
30+
run: bash ./gradlew assembleRelease --scan --debug

PrivacyPolicy.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ an integral part of the Privacy Policy Network Service Use Agreement. You do not
1010
with the personal information we request, but in many cases, if you choose not to do so, we will not
1111
be able to provide you with our products or services or respond to any questions you may have. If
1212
you have any questions about the data processing practices described in this Privacy Policy, please
13-
contact us at [rosuh@qq.com](mailto:hi@rosuh.me) so that we can address your specific needs. We are
13+
contact us at [hi@rosuh.me](mailto:hi@rosuh.me) so that we can address your specific needs. We are
1414
happy to receive your feedback.
1515

1616
<h3> I. How we collect information</h3>
@@ -38,7 +38,7 @@ Any changes, by your consent, you agree to be bound by this policy as amended
3838
The constraints.
3939
<h3>Vii. How to contact us</h3>
4040
If you have any questions, comments or suggestions regarding this Privacy Policy, you may
41-
Contact us by email: rosuh@qq.com If you are not satisfied with our response, especially with our personal information
41+
Contact us by email: hi@rosuh.me If you are not satisfied with our response, especially with our personal information
4242
You may also be able to address your legal rights by filing a complaint with us.
4343
Seeking resolution by filing a lawsuit in the people's court of competent jurisdiction
4444
Program.

PrivacyPolicy_zh-CN.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
隐私政策尊重并保护所有使用隐私政策网络服务用户的个人隐私权。 为了给您提供更准确、更有个性化的服务,我们的隐私政策涵盖我们收集,使用,披露,传输和存储您的信息的方式。
44
但隐私政策将以高度的勤勉、审慎义务对待这些信息。隐私政策会不时更新本隐私权政策。 您在同意隐私政策络服务使用协议之时,即视为您已经同意本隐私权政策全部内容。
55
本隐私权政策属于隐私政策络服务使用协议不可分割的一部分。 你不是一定要提供我们要求的个人信息,但在许多情况下,如果你选择不提供,我们将无法为你提供我们的产品或服务,也无法回应你遇到的任何问题。
6-
如果您对本隐私政策中描述的数据处理实践有任何疑问,请通过[rosuh@qq.com](mailto:hi@rosuh.me)联系我们,以便我们处理您的特殊需求。我们很高兴收到您的反馈。
6+
如果您对本隐私政策中描述的数据处理实践有任何疑问,请通过[hi@rosuh.me](mailto:hi@rosuh.me)联系我们,以便我们处理您的特殊需求。我们很高兴收到您的反馈。
77

88
<h3> 一、我们如何收集信息</h3>
99
我们压根就不主动收集信息。即便没有你的信息,我们照样运行得很好。当然,如果崩溃了,你可以选择高抬贵手发送带有设备型号和系统版本信息的邮件给我们,方便我们定位问题。
@@ -30,7 +30,7 @@
3030
的约束。
3131
<h3>七、如何联系我们</h3>
3232
如果你对本隐私政策有任何疑问、意见或建议,可以
33-
通过邮件联系我们: rosuh@qq.com 如果你对我们的回复不满意,特别是我们的个人信息
33+
通过邮件联系我们: hi@rosuh.me 如果你对我们的回复不满意,特别是我们的个人信息
3434
处理行为损害了你的合法权益,你还可以通过向我们
3535
所在地有管辖权的人民法院提起诉讼的方式寻求解决
3636
方案。

app/build.gradle.kts

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ android {
1515
applicationId = "me.rosuh.easywatermark"
1616
minSdk = (Apps.minSdk)
1717
targetSdk = (Apps.targetSdk)
18-
versionCode = 20802
19-
versionName = "2.8.2"
18+
versionCode = 20803
19+
versionName = "2.8.3"
2020
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
2121
setProperty("archivesBaseName", "$applicationId-v$versionName($versionCode)")
2222
}
@@ -76,34 +76,34 @@ dependencies {
7676
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
7777
implementation(project(mapOf("path" to ":cmonet")))
7878

79-
val roomVersion = "2.4.2"
79+
val roomVersion = "2.4.3"
8080

8181
implementation("androidx.room:room-runtime:$roomVersion")
8282
implementation("androidx.room:room-ktx:$roomVersion")
8383
kapt("androidx.room:room-compiler:$roomVersion")
8484

8585
implementation("androidx.datastore:datastore-preferences:1.0.0")
8686

87-
implementation("com.google.dagger:hilt-android:2.42")
88-
kapt("com.google.dagger:hilt-compiler:2.42")
87+
implementation("com.google.dagger:hilt-android:2.43.2")
88+
kapt("com.google.dagger:hilt-compiler:2.43.2")
8989

9090
implementation("androidx.asynclayoutinflater:asynclayoutinflater:1.0.0")
9191

92-
implementation("com.github.bumptech.glide:glide:4.13.1")
93-
kapt("com.github.bumptech.glide:compiler:4.13.1")
92+
implementation("com.github.bumptech.glide:glide:4.13.2")
93+
kapt("com.github.bumptech.glide:compiler:4.13.2")
9494

9595
implementation("id.zelory:compressor:3.0.1")
9696

97-
implementation("org.jetbrains.kotlin:kotlin-stdlib:1.7.0")
98-
implementation("androidx.appcompat:appcompat:1.4.2")
99-
implementation("com.google.android.material:material:1.7.0-alpha02")
100-
implementation("androidx.fragment:fragment-ktx:1.5.0")
101-
implementation("androidx.activity:activity-ktx:1.5.0")
102-
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.5.0")
103-
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.1")
104-
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1")
105-
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.5.0")
106-
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.0")
97+
implementation("org.jetbrains.kotlin:kotlin-stdlib:1.7.10")
98+
implementation("androidx.appcompat:appcompat:1.5.0")
99+
implementation("com.google.android.material:material:1.8.0-alpha01")
100+
implementation("androidx.fragment:fragment-ktx:1.5.2")
101+
implementation("androidx.activity:activity-ktx:1.5.1")
102+
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.5.1")
103+
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4")
104+
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
105+
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.5.1")
106+
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1")
107107
implementation("com.github.skydoves:colorpickerview:2.2.3")
108108
implementation("androidx.viewpager2:viewpager2:1.0.0")
109109
implementation("androidx.recyclerview:recyclerview:1.2.1")

app/src/main/java/me/rosuh/easywatermark/data/model/FuncTitleModel.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,9 @@ data class FuncTitleModel(
2323
object TextSize : FuncType() {
2424
val tag = "TextSize"
2525
}
26+
27+
object TileMode : FuncType() {
28+
val tag = "TileMode"
29+
}
2630
}
2731
}

app/src/main/java/me/rosuh/easywatermark/data/model/ImageInfo.kt

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package me.rosuh.easywatermark.data.model
22

3+
import android.graphics.Shader
34
import android.net.Uri
5+
import androidx.annotation.FloatRange
46

57
data class ImageInfo(
68
val uri: Uri,
@@ -12,7 +14,45 @@ data class ImageInfo(
1214
var result: Result<*>? = null,
1315
var jobState: JobState = JobState.Ready,
1416
var isInDelModel: Boolean = false,
17+
val tileMode: Int = Shader.TileMode.REPEAT.ordinal,
18+
@FloatRange(from = 0.0, to = 1.0) val offsetX: Float = 0.5f,
19+
@FloatRange(from = 0.0, to = 1.0) val offsetY: Float = 0.5f,
1520
) {
1621
val shareUri: Uri?
1722
get() = result?.data as? Uri?
23+
24+
fun obtainTileMode(): Shader.TileMode {
25+
return when (tileMode) {
26+
Shader.TileMode.CLAMP.ordinal -> Shader.TileMode.CLAMP
27+
Shader.TileMode.MIRROR.ordinal -> Shader.TileMode.MIRROR
28+
Shader.TileMode.REPEAT.ordinal -> Shader.TileMode.REPEAT
29+
else -> Shader.TileMode.DECAL
30+
}
31+
}
32+
33+
fun isSameItem(other: ImageInfo): Boolean {
34+
return uri == other.uri
35+
&& result == other.result
36+
&& jobState == other.jobState
37+
&& isInDelModel == other.isInDelModel
38+
}
39+
40+
companion object {
41+
fun empty(): ImageInfo {
42+
return ImageInfo(
43+
Uri.EMPTY,
44+
1,
45+
1,
46+
1,
47+
1f,
48+
1f,
49+
null,
50+
JobState.Ready,
51+
false,
52+
Shader.TileMode.REPEAT.ordinal,
53+
0.5f,
54+
0.5f
55+
)
56+
}
57+
}
1858
}

app/src/main/java/me/rosuh/easywatermark/data/model/WaterMark.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package me.rosuh.easywatermark.data.model
22

3+
import android.graphics.Shader
34
import android.net.Uri
45
import androidx.annotation.Keep
56
import me.rosuh.easywatermark.data.repo.WaterMarkRepository

app/src/main/java/me/rosuh/easywatermark/data/repo/WaterMarkRepository.kt

Lines changed: 70 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
package me.rosuh.easywatermark.data.repo
22

33
import android.graphics.Color
4+
import android.graphics.Shader
45
import android.net.Uri
6+
import android.util.Log
7+
import androidx.collection.ArrayMap
58
import androidx.datastore.core.DataStore
69
import androidx.datastore.preferences.core.*
7-
import androidx.lifecycle.asLiveData
8-
import kotlinx.coroutines.flow.Flow
9-
import kotlinx.coroutines.flow.MutableStateFlow
10-
import kotlinx.coroutines.flow.catch
11-
import kotlinx.coroutines.flow.map
10+
import kotlinx.coroutines.Dispatchers
11+
import kotlinx.coroutines.flow.*
12+
import kotlinx.coroutines.withContext
1213
import me.rosuh.easywatermark.MyApp
1314
import me.rosuh.easywatermark.R
1415
import me.rosuh.easywatermark.data.model.ImageInfo
@@ -21,6 +22,8 @@ import me.rosuh.easywatermark.data.repo.WaterMarkRepository.PreferenceKeys.KEY_E
2122
import me.rosuh.easywatermark.data.repo.WaterMarkRepository.PreferenceKeys.KEY_HORIZON_GAP
2223
import me.rosuh.easywatermark.data.repo.WaterMarkRepository.PreferenceKeys.KEY_ICON_URI
2324
import me.rosuh.easywatermark.data.repo.WaterMarkRepository.PreferenceKeys.KEY_MODE
25+
import me.rosuh.easywatermark.data.repo.WaterMarkRepository.PreferenceKeys.KEY_OFFSET_X
26+
import me.rosuh.easywatermark.data.repo.WaterMarkRepository.PreferenceKeys.KEY_OFFSET_Y
2427
import me.rosuh.easywatermark.data.repo.WaterMarkRepository.PreferenceKeys.KEY_TEXT
2528
import me.rosuh.easywatermark.data.repo.WaterMarkRepository.PreferenceKeys.KEY_TEXT_COLOR
2629
import me.rosuh.easywatermark.data.repo.WaterMarkRepository.PreferenceKeys.KEY_TEXT_SIZE
@@ -31,6 +34,7 @@ import java.io.IOException
3134
import javax.inject.Inject
3235
import javax.inject.Named
3336
import javax.inject.Singleton
37+
import kotlin.collections.ArrayList
3438

3539
@Singleton
3640
class WaterMarkRepository @Inject constructor(
@@ -48,10 +52,18 @@ class WaterMarkRepository @Inject constructor(
4852
val KEY_VERTICAL_GAP = intPreferencesKey(SP_KEY_VERTICAL_GAP)
4953
val KEY_DEGREE = floatPreferencesKey(SP_KEY_DEGREE)
5054
val KEY_ICON_URI = stringPreferencesKey(SP_KEY_ICON_URI)
55+
val KEY_URI = stringPreferencesKey(SP_KEY_URI)
5156
val KEY_MODE = intPreferencesKey(SP_KEY_WATERMARK_MODE)
5257
val KEY_ENABLE_BOUNDS = booleanPreferencesKey(SP_KEY_ENABLE_BOUNDS)
58+
val KEY_TILE_MODE = intPreferencesKey(SP_KEY_TILE_MODEL)
59+
val KEY_OFFSET_X = floatPreferencesKey(SP_KEY_OFFSET_X)
60+
val KEY_OFFSET_Y = floatPreferencesKey(SP_KEY_OFFSET_Y)
5361
}
5462

63+
private val _selectedImage = MutableStateFlow(ImageInfo.empty())
64+
65+
val selectedImage: StateFlow<ImageInfo> = _selectedImage
66+
5567
val waterMark: Flow<WaterMark> = dataStore.data
5668
.catch { exception ->
5769
if (exception is IOException) {
@@ -78,16 +90,19 @@ class WaterMarkRepository @Inject constructor(
7890
)
7991
}
8092

81-
private val imageListFlow: MutableStateFlow<List<ImageInfo>> =
82-
MutableStateFlow(emptyList())
93+
private val _imageMapFlow: MutableStateFlow<List<ImageInfo>> = MutableStateFlow(emptyList())
94+
private val imageInfoMap: MutableMap<Uri, Int> = ArrayMap(_imageMapFlow.value.size)
8395

84-
val uriLivedData = imageListFlow.asLiveData()
96+
val imageInfoMapFlow = _imageMapFlow
8597

8698
val imageInfoList: List<ImageInfo>
87-
get() = uriLivedData.value ?: emptyList()
99+
get() = imageInfoMapFlow.value
88100

89101
suspend fun updateImageList(imageList: List<ImageInfo>) {
90-
imageListFlow.emit(imageList)
102+
val map = imageList.mapIndexed { index, imageInfo -> imageInfo.uri to index }.toMap()
103+
imageInfoMap.clear()
104+
imageInfoMap.putAll(map)
105+
_imageMapFlow.emit(imageList)
91106
}
92107

93108
suspend fun updateText(text: String) {
@@ -138,6 +153,40 @@ class WaterMarkRepository @Inject constructor(
138153
}
139154
}
140155

156+
suspend fun updateTileMode(imageInfo: ImageInfo, mode: Shader.TileMode): ImageInfo {
157+
if (imageInfo.tileMode == mode.ordinal) {
158+
Log.i("WaterMarkRepository", "updateTileMode: same mode")
159+
return imageInfo
160+
}
161+
val index = imageInfoMap[imageInfo.uri] ?: kotlin.run {
162+
Log.e("WaterMarkRepository", "updateTileMode: imageInfo not found, uri = ${imageInfo.uri}")
163+
return imageInfo
164+
}
165+
166+
val info = imageInfo.copy(tileMode = mode.ordinal)
167+
val list = ArrayList(imageInfoList)
168+
list[index] = info
169+
imageInfoMap[info.uri] = index
170+
_imageMapFlow.emit(list)
171+
_selectedImage.emit(info)
172+
return info
173+
}
174+
175+
suspend fun updateOffset(imageInfo: ImageInfo) {
176+
if (imageInfo == selectedImage.value) {
177+
return
178+
}
179+
val index = imageInfoMap[selectedImage.value.uri] ?: kotlin.run {
180+
Log.e("WaterMarkRepository", "updateOffset: imageInfo not found, uri = ${selectedImage.value.uri}")
181+
return
182+
}
183+
val list = ArrayList(imageInfoList)
184+
list[index] = imageInfo
185+
imageInfoMap[imageInfo.uri] = index
186+
_imageMapFlow.emit(list)
187+
_selectedImage.emit(imageInfo)
188+
}
189+
141190
suspend fun resetModeToText() {
142191
dataStore.edit { it[KEY_MODE] = MarkMode.Text.value }
143192
}
@@ -150,6 +199,11 @@ class WaterMarkRepository @Inject constructor(
150199
updateImageList(emptyList())
151200
}
152201

202+
suspend fun select(uri: Uri) = withContext(Dispatchers.Default) {
203+
val info = imageInfoList.find { it.uri == uri } ?: ImageInfo(uri)
204+
_selectedImage.emit(info)
205+
}
206+
153207
sealed class MarkMode(val value: Int) {
154208
object Text : MarkMode(0)
155209

@@ -171,9 +225,15 @@ class WaterMarkRepository @Inject constructor(
171225
const val SP_KEY_CHANGE_LOG = "${SP_NAME}_key_change_log"
172226
const val SP_KEY_ENABLE_BOUNDS = "${SP_NAME}_key_enable_bounds"
173227
const val SP_KEY_ICON_URI = "${SP_NAME}_key_icon_uri"
228+
const val SP_KEY_URI = "${SP_NAME}_key_uri"
174229
const val SP_KEY_WATERMARK_MODE = "${SP_NAME}_key_watermark_mode"
175230
const val SP_KEY_IMAGE_ROTATION = "${SP_NAME}_key_watermark_mode"
231+
const val SP_KEY_TILE_MODEL = "${SP_NAME}_key_tile_model"
232+
const val SP_KEY_OFFSET_X = "${SP_NAME}_key_offset_x"
233+
const val SP_KEY_OFFSET_Y = "${SP_NAME}_key_offset_y"
176234
const val MAX_TEXT_SIZE = 100f
235+
const val MIN_TEXT_SIZE = 1f
236+
const val DEFAULT_TEXT_SIZE = 14f
177237
const val MAX_DEGREE = 360f
178238
const val MAX_HORIZON_GAP = 500
179239
const val MAX_VERTICAL_GAP = 500

0 commit comments

Comments
 (0)