Skip to content

Commit

Permalink
[feature|optimize|fix] New MVI: AddScreen, SearchConfigScreen; Suppor…
Browse files Browse the repository at this point in the history
…t for sticker MD5 in search domain; Fix known bugs
  • Loading branch information
SkyD666 committed Dec 7, 2023
1 parent b843d41 commit e293dd9
Show file tree
Hide file tree
Showing 20 changed files with 650 additions and 395 deletions.
4 changes: 2 additions & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ android {
applicationId "com.skyd.rays"
minSdk 24
targetSdk 34
versionCode 50
versionName "2.0-alpha04"
versionCode 51
versionName "2.0-alpha05"
flavorDimensions = ["versionName"]

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/java/com/skyd/rays/config/SearchConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.skyd.rays.config

import com.skyd.rays.R
import com.skyd.rays.model.bean.STICKER_TABLE_NAME
import com.skyd.rays.model.bean.StickerBean.Companion.STICKER_MD5_COLUMN
import com.skyd.rays.model.bean.StickerBean.Companion.TITLE_COLUMN
import com.skyd.rays.model.bean.StickerBean.Companion.UUID_COLUMN
import com.skyd.rays.model.bean.TAG_TABLE_NAME
Expand All @@ -11,6 +12,7 @@ val allSearchDomain: HashMap<Pair<String, Int>, List<Pair<String, Int>>> = hashM
(STICKER_TABLE_NAME to R.string.db_sticker_table) to listOf(
UUID_COLUMN to R.string.db_sticker_table_uuid,
TITLE_COLUMN to R.string.db_sticker_table_title,
STICKER_MD5_COLUMN to R.string.db_sticker_table_md5,
),
(TAG_TABLE_NAME to R.string.db_tag_table) to listOf(
TAG_COLUMN to R.string.db_tag_table_tag,
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/java/com/skyd/rays/ext/FlowExt.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ fun <T> concat(flow1: Flow<T>, flow2: Flow<T>): Flow<T> = flow {

fun <T> Flow<T>.startWith(item: T): Flow<T> = concat(flowOf(item), this)

fun <T> Flow<T>.endWith(item: T): Flow<T> = concat(this, flowOf(item))


/**
* Projects each source value to a [Flow] which is merged in the output [Flow] only if the previous projected [Flow] has completed.
Expand Down
61 changes: 17 additions & 44 deletions app/src/main/java/com/skyd/rays/model/respository/AddRepository.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import com.google.mlkit.vision.text.japanese.JapaneseTextRecognizerOptions
import com.google.mlkit.vision.text.korean.KoreanTextRecognizerOptions
import com.google.mlkit.vision.text.latin.TextRecognizerOptions
import com.skyd.rays.appContext
import com.skyd.rays.base.BaseData
import com.skyd.rays.base.BaseRepository
import com.skyd.rays.config.CLASSIFICATION_MODEL_DIR_FILE
import com.skyd.rays.config.STICKER_DIR
Expand All @@ -28,7 +27,6 @@ import com.skyd.rays.model.preference.ai.TextRecognizeThresholdPreference
import com.skyd.rays.util.image.ImageFormatChecker
import com.skyd.rays.util.image.format.ImageFormat
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.zip
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.serialization.json.Json
Expand All @@ -48,8 +46,8 @@ class AddRepository @Inject constructor(
suspend fun requestAddStickerWithTags(
stickerWithTags: StickerWithTags,
uri: Uri
): Flow<BaseData<String>> {
return flow {
): Flow<Any> {
return flowOnIo {
appContext.contentResolver.openInputStream(uri)?.use {
check(ImageFormatChecker.check(it) != ImageFormat.UNDEFINED) {
"Unsupported image format"
Expand All @@ -59,55 +57,38 @@ class AddRepository @Inject constructor(
val tempFile = File(STICKER_DIR, "${Random.nextLong()}")
uri.copyTo(tempFile)
val stickerMd5 = tempFile.md5() ?: error("can not calc sticker's md5!")
val containsByMd5 = stickerDao.containsByMd5(stickerMd5)
if (containsByMd5 != null &&
val uuidGotByMd5 = stickerDao.containsByMd5(stickerMd5)
if (uuidGotByMd5 != null &&
stickerDao.containsByUuid(stickerWithTags.sticker.uuid) == 0
) {
tempFile.deleteRecursively()
emitBaseData(BaseData<String>().apply {
code = -2
msg = "Duplicate sticker!"
data = containsByMd5
})
emit(stickerDao.getStickerWithTags(uuidGotByMd5)!!)
} else {
stickerWithTags.sticker.stickerMd5 = stickerMd5
val uuid = stickerDao.addStickerWithTags(stickerWithTags)
if (!tempFile.renameTo(File(STICKER_DIR, uuid))) {
tempFile.deleteRecursively()
}
emitBaseData(BaseData<String>().apply {
code = 0
data = uuid
})
emit(uuid)
}
}
}

suspend fun requestGetStickerWithTags(stickerUuid: String): Flow<BaseData<StickerWithTags>> {
return flow {
suspend fun requestGetStickerWithTags(stickerUuid: String): Flow<StickerWithTags?> {
return flowOnIo {
val stickerWithTags = stickerDao.getStickerWithTags(stickerUuid)
if (stickerWithTags == null) {
emitBaseData(BaseData<StickerWithTags>().apply {
code = -1
msg = "stickerWithTags is null"
})
} else {
emitBaseData(BaseData<StickerWithTags>().apply {
code = 0
data = stickerWithTags
})
}
emit(stickerWithTags)
}
}

suspend fun requestSuggestTags(sticker: Uri): Flow<BaseData<Set<String>>> {
suspend fun requestSuggestTags(sticker: Uri): Flow<Set<String>> {
val image: InputImage
return try {
image = InputImage.fromFilePath(appContext, sticker)
val dataStore = appContext.dataStore
val textRecognizeThreshold = dataStore.getOrDefault(TextRecognizeThresholdPreference)

flow {
flowOnIo {
emit(suspendCancellableCoroutine { cont ->
TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS)
.process(image)
Expand All @@ -118,7 +99,7 @@ class AddRepository @Inject constructor(
}
.addOnFailureListener { cont.resumeWithException(it) }
})
}.zip(flow {
}.zip(flowOnIo {
emit(suspendCancellableCoroutine { cont ->
TextRecognition.getClient(ChineseTextRecognizerOptions.Builder().build())
.process(image)
Expand All @@ -131,7 +112,7 @@ class AddRepository @Inject constructor(
})
}) { other, chinese ->
chinese + other
}.zip(flow {
}.zip(flowOnIo {
emit(suspendCancellableCoroutine { cont ->
TextRecognition.getClient(JapaneseTextRecognizerOptions.Builder().build())
.process(image)
Expand All @@ -144,7 +125,7 @@ class AddRepository @Inject constructor(
})
}) { other, japanese ->
other + japanese
}.zip(flow {
}.zip(flowOnIo {
emit(suspendCancellableCoroutine { cont ->
TextRecognition.getClient(KoreanTextRecognizerOptions.Builder().build())
.process(image)
Expand All @@ -157,7 +138,7 @@ class AddRepository @Inject constructor(
})
}) { other, korean ->
other + korean
}.zip(flow {
}.zip(flowOnIo {
emit(suspendCancellableCoroutine { cont ->
val model = dataStore.getOrDefault(StickerClassificationModelPreference)
val classificationThreshold =
Expand Down Expand Up @@ -190,19 +171,11 @@ class AddRepository @Inject constructor(
}
})
}) { other, classification ->
checkBaseData(BaseData<Set<String>>().apply {
code = 0
data = (classification + other).toSet()
})
(classification + other).toSet()
}
} catch (e: IOException) {
e.printStackTrace()
flow {
emitBaseData(BaseData<Set<String>>().apply {
code = -1
msg = e.message
})
}
throw e
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,42 +1,30 @@
package com.skyd.rays.model.respository

import com.skyd.rays.base.BaseData
import com.skyd.rays.base.BaseRepository
import com.skyd.rays.model.db.dao.SearchDomainDao
import com.skyd.rays.model.bean.SearchDomainBean
import com.skyd.rays.model.db.dao.SearchDomainDao
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import javax.inject.Inject

class SearchConfigRepository @Inject constructor(
private val searchDomainDao: SearchDomainDao
) : BaseRepository() {
suspend fun requestGetSearchDomain(): Flow<BaseData<Map<String, Boolean>>> {
return flow {
suspend fun requestGetSearchDomain(): Flow<Map<String, Boolean>> {
return flowOnIo {
val map = mutableMapOf<String, Boolean>()
searchDomainDao.getAllSearchDomain().forEach {
map["${it.tableName}/${it.columnName}"] = it.search
}
emitBaseData(BaseData<Map<String, Boolean>>().apply {
code = 0
data = map
})
emit(map)
}
}

suspend fun requestSetSearchDomain(
searchDomainBean: SearchDomainBean
): Flow<BaseData<Map<String, Boolean>>> {
return flow {
): Flow<SearchDomainBean> {
return flowOnIo {
searchDomainDao.setSearchDomain(searchDomainBean)
val map = mutableMapOf<String, Boolean>()
searchDomainDao.getAllSearchDomain().forEach {
map["${it.tableName}/${it.columnName}"] = it.search
}
emitBaseData(BaseData<Map<String, Boolean>>().apply {
code = 0
data = map
})
emit(searchDomainBean)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,13 @@ import dagger.hilt.android.EntryPointAccessors
import dagger.hilt.components.SingletonComponent
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapConcat
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.flow.takeWhile
import javax.inject.Inject

class SearchRepository @Inject constructor(
Expand All @@ -58,11 +59,23 @@ class SearchRepository @Inject constructor(
)
}
.distinctUntilChanged()
.flatMapConcat {
stickerDao.getStickerWithTagsList(genSql(it.first.orEmpty()))
.take(1)
.map { list -> sortSearchResultList(list) }
.flowOn(Dispatchers.IO)
.flatMapConcat { triple ->
combine(
stickerDao.getStickerWithTagsList(genSql(triple.first.orEmpty())),
appContext.dataStore.data,
) { list, ds ->
list to ds
}.takeWhile {
triple == Triple(
it.second[QueryPreference.key] ?: QueryPreference.default,
it.second[SearchResultSortPreference.key]
?: SearchResultSortPreference.default,
it.second[SearchResultReversePreference.key]
?: SearchResultReversePreference.default,
)
}.map { pair ->
sortSearchResultList(pair.first)
}.flowOn(Dispatchers.IO)
}
.flowOn(Dispatchers.IO)
}
Expand Down
36 changes: 11 additions & 25 deletions app/src/main/java/com/skyd/rays/ui/activity/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ import android.view.WindowManager
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.scaleIn
Expand Down Expand Up @@ -152,35 +151,22 @@ class MainActivity : AppCompatActivity() {
navController = navController,
startDestination = MAIN_SCREEN_ROUTE,
enterTransition = {
fadeIn(
animationSpec = spring(stiffness = Spring.StiffnessMedium)
) + scaleIn(
animationSpec = spring(stiffness = Spring.StiffnessMedium),
initialScale = 0.96f
)
},
exitTransition = {
fadeOut(
animationSpec = spring(stiffness = Spring.StiffnessMedium)
) + scaleOut(
animationSpec = spring(stiffness = Spring.StiffnessMedium),
targetScale = 0.96f
fadeIn(animationSpec = tween(220, delayMillis = 30)) + scaleIn(
animationSpec = tween(220, delayMillis = 30),
initialScale = 0.92f,
)
},
exitTransition = { fadeOut(animationSpec = tween(90)) },
popEnterTransition = {
fadeIn(
animationSpec = spring(stiffness = Spring.StiffnessMedium)
) + scaleIn(
animationSpec = spring(stiffness = Spring.StiffnessMedium),
initialScale = 0.96f
fadeIn(animationSpec = tween(220)) + scaleIn(
animationSpec = tween(220),
initialScale = 0.92f,
)
},
popExitTransition = {
fadeOut(
animationSpec = spring(stiffness = Spring.StiffnessMedium)
) + scaleOut(
animationSpec = spring(stiffness = Spring.StiffnessMedium),
targetScale = 0.96f
fadeOut(animationSpec = tween(220)) + scaleOut(
animationSpec = tween(220),
targetScale = 0.92f,
)
},
) {
Expand Down
27 changes: 9 additions & 18 deletions app/src/main/java/com/skyd/rays/ui/screen/add/AddEvent.kt
Original file line number Diff line number Diff line change
@@ -1,25 +1,16 @@
package com.skyd.rays.ui.screen.add

import com.skyd.rays.base.IUiEvent
import com.skyd.rays.base.mvi.MviSingleEvent
import com.skyd.rays.model.bean.StickerWithTags

class AddEvent(
val getStickersWithTagsUiEvent: GetStickersWithTagsUiEvent? = null,
val addStickersResultUiEvent: AddStickersResultUiEvent? = null,
val recognizeTextUiEvent: RecognizeTextUiEvent? = null,
) : IUiEvent
sealed interface AddEvent : MviSingleEvent {
sealed interface AddStickersResultEvent : AddEvent {
data class Duplicate(val stickerWithTags: StickerWithTags) : AddStickersResultEvent
data class Success(val stickerUuid: String) : AddStickersResultEvent
data class Failed(val msg: String) : AddStickersResultEvent
}

sealed class GetStickersWithTagsUiEvent {
class Success(val stickerWithTags: StickerWithTags) : GetStickersWithTagsUiEvent()
data object Init : GetStickersWithTagsUiEvent()
data object Failed : GetStickersWithTagsUiEvent()
}
data object CurrentStickerChanged : AddEvent

sealed class AddStickersResultUiEvent {
class Duplicate(val stickerUuid: String) : AddStickersResultUiEvent()
class Success(val stickerUuid: String) : AddStickersResultUiEvent()
}

sealed class RecognizeTextUiEvent {
class Success(val texts: Set<String>) : RecognizeTextUiEvent()
data object GetStickersWithTagsStateChanged : AddEvent
}
Loading

0 comments on commit e293dd9

Please sign in to comment.