Skip to content

Commit

Permalink
[feature|fix|build] DocumentsProvider supports searching and listing …
Browse files Browse the repository at this point in the history
…recent stickers; set reuse bitmap mutable; update dependencies
  • Loading branch information
SkyD666 committed Apr 10, 2024
1 parent 8e20cc5 commit 000d4b5
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 42 deletions.
8 changes: 3 additions & 5 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ android {
minSdk = 24
targetSdk = 34
versionCode = 62
versionName = "2.2-alpha12"
versionName = "2.2-beta01"

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
Expand All @@ -35,8 +35,6 @@ android {

signingConfigs {
create("release") {
// You need to specify either an absolute path or include the
// keystore file in the same directory as the build.gradle file.
@Suppress("UNCHECKED_CAST")
val sign = ((extra["secret"] as Map<*, *>)["sign"] as Map<String, String>)
storeFile = file("../key.jks")
Expand Down Expand Up @@ -200,6 +198,6 @@ dependencies {

implementation("com.github.penfeizhou.android.animation:apng:2.28.0")

debugImplementation("androidx.compose.ui:ui-tooling:1.6.4")
debugImplementation("androidx.compose.ui:ui-test-manifest:1.6.4")
debugImplementation("androidx.compose.ui:ui-tooling:1.6.5")
debugImplementation("androidx.compose.ui:ui-test-manifest:1.6.5")
}
Original file line number Diff line number Diff line change
@@ -1,25 +1,12 @@
package com.skyd.rays.api.strategy

import android.content.Intent
import android.os.Parcelable
import androidx.core.content.FileProvider
import com.skyd.rays.api.ApiStickerWithTags
import com.skyd.rays.appContext
import com.skyd.rays.base.BaseBean
import com.skyd.rays.model.bean.StickerBean
import com.skyd.rays.model.bean.StickerWithTags
import com.skyd.rays.model.bean.TagBean
import com.skyd.rays.model.respository.SearchRepository
import com.skyd.rays.util.stickerUuidToFile
import dagger.hilt.EntryPoint
import dagger.hilt.InstallIn
import dagger.hilt.android.EntryPointAccessors
import dagger.hilt.components.SingletonComponent
import kotlinx.coroutines.flow.first
import kotlinx.parcelize.Parcelize
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json

object SearchStickersStrategy {
suspend fun execute(
Expand All @@ -28,7 +15,7 @@ object SearchStickersStrategy {
requestPackage: String,
): List<ApiStickerWithTags> {
val result = repo
.requestStickerWithTagsList(keyword.orEmpty())
.requestStickerWithTagsListFlow(keyword.orEmpty())
.first()
.map {
val uri = FileProvider.getUriForFile(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ fun <I> ActivityResultLauncher<I>.safeLaunch(
runCatching {
launch(input)
}.onFailure {
// May: No Activity found to handle Intent
// e.g. No Activity found to handle Intent
it.printStackTrace()
onError.invoke(it)
}
Expand All @@ -25,7 +25,7 @@ fun <I> ActivityResultLauncher<I>.safeLaunch(
runCatching {
launch(input, options)
}.onFailure {
// May: No Activity found to handle Intent
// e.g. No Activity found to handle Intent
it.printStackTrace()
onError.invoke(it)
}
Expand Down
16 changes: 16 additions & 0 deletions app/src/main/java/com/skyd/rays/model/db/dao/sticker/StickerDao.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import com.skyd.rays.model.bean.STICKER_TABLE_NAME
import com.skyd.rays.model.bean.StickerBean
import com.skyd.rays.model.bean.StickerBean.Companion.CLICK_COUNT_COLUMN
import com.skyd.rays.model.bean.StickerBean.Companion.CREATE_TIME_COLUMN
import com.skyd.rays.model.bean.StickerBean.Companion.MODIFY_TIME_COLUMN
import com.skyd.rays.model.bean.StickerBean.Companion.SHARE_COUNT_COLUMN
import com.skyd.rays.model.bean.StickerBean.Companion.STICKER_MD5_COLUMN
import com.skyd.rays.model.bean.StickerBean.Companion.UUID_COLUMN
Expand Down Expand Up @@ -62,6 +63,21 @@ interface StickerDao {
@Query("SELECT * FROM $STICKER_TABLE_NAME WHERE $UUID_COLUMN LIKE :stickerUuid")
fun getStickerWithTags(stickerUuid: String): StickerWithTags?

@Transaction
@Query("SELECT * FROM $STICKER_TABLE_NAME ORDER BY $MODIFY_TIME_COLUMN DESC LIMIT :count")
fun getRecentModifiedStickers(count: Int = 15): List<StickerWithTags>

@Transaction
@Query(
"""
SELECT $UUID_COLUMN, $MODIFY_TIME_COLUMN FROM $STICKER_TABLE_NAME
WHERE $UUID_COLUMN IN (:stickerUuids)
"""
)
fun getStickerModified(stickerUuids: List<String>): Map<
@MapColumn(columnName = UUID_COLUMN) String,
@MapColumn(columnName = MODIFY_TIME_COLUMN) Long>

@Transaction
@Query(
"""
Expand Down
104 changes: 89 additions & 15 deletions app/src/main/java/com/skyd/rays/model/provider/StickerProvider.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import com.skyd.rays.ext.toDateTimeString
import com.skyd.rays.model.db.dao.TagDao
import com.skyd.rays.model.db.dao.sticker.MimeTypeDao
import com.skyd.rays.model.db.dao.sticker.StickerDao
import com.skyd.rays.model.respository.SearchRepository
import com.skyd.rays.util.image.ImageFormatChecker
import com.skyd.rays.util.image.format.ImageFormat
import dagger.hilt.EntryPoint
Expand Down Expand Up @@ -61,7 +62,11 @@ class StickerProvider : DocumentsProvider() {
// recently used documents will show up in the "Recents" category.
// FLAG_SUPPORTS_SEARCH allows users to search all documents the application
// shares.
add(Root.COLUMN_FLAGS, Root.FLAG_LOCAL_ONLY or Root.FLAG_SUPPORTS_IS_CHILD)
add(
Root.COLUMN_FLAGS,
Root.FLAG_LOCAL_ONLY or Root.FLAG_SUPPORTS_IS_CHILD or
Root.FLAG_SUPPORTS_RECENTS or Root.FLAG_SUPPORTS_SEARCH
)

add(Root.COLUMN_TITLE, context!!.getString(R.string.app_name))

Expand All @@ -70,7 +75,7 @@ class StickerProvider : DocumentsProvider() {

// The child MIME types are used to filter the roots and only present to the
// user those roots that contain the desired type somewhere in their file hierarchy.
add(Root.COLUMN_MIME_TYPES, MIME_TYPE_DIR)
add(Root.COLUMN_MIME_TYPES, "image/*")
add(Root.COLUMN_ICON, R.mipmap.ic_launcher)
}

Expand All @@ -87,12 +92,14 @@ class StickerProvider : DocumentsProvider() {
override fun queryDocument(documentId: String, projection: Array<out String>?): Cursor {
// Create a cursor with the requested projection, or the default projection.
return MatrixCursor(projection ?: DEFAULT_DOCUMENT_COLUMNS).apply {
val files = arrayOf(File(documentId))
val uuids = listOf(File(documentId).name)
val modifiedMap = getModifiedMap(uuids)
includeFile(
this,
documentId,
getDisplayMap(files),
getMimeTypeMap(files),
path = documentId,
lastModified = modifiedMap[uuids.first()],
displayMap = getDisplayMap(uuids),
mimeTypeMap = getMimeTypeMap(uuids),
)
}
}
Expand All @@ -105,17 +112,29 @@ class StickerProvider : DocumentsProvider() {
return MatrixCursor(projection ?: DEFAULT_DOCUMENT_COLUMNS).apply {
val parent = File(parentDocumentId ?: STICKER_DIR)
val listFiles = parent.listFiles().orEmpty()
val displayMap = getDisplayMap(listFiles)
val mimeTypeMap = getMimeTypeMap(listFiles)
val stickerUuids = listFiles.map { it.name }
val modifiedMap = getModifiedMap(stickerUuids)
val displayMap = getDisplayMap(stickerUuids)
val mimeTypeMap = getMimeTypeMap(stickerUuids)

listFiles.forEach { file ->
includeFile(this, file.path, displayMap, mimeTypeMap)
includeFile(
cursor = this,
path = file.path,
lastModified = modifiedMap[file.name],
displayMap = displayMap,
mimeTypeMap = mimeTypeMap,
)
}
}
}

private fun getDisplayMap(listFiles: Array<out File>): MutableMap<String, String> {
val titles = entryPoint.stickerDao().getStickerTitles(listFiles.map { it.name })
private fun getModifiedMap(stickerUuids: List<String>): MutableMap<String, Long> {
return entryPoint.stickerDao().getStickerModified(stickerUuids).toMutableMap()
}

private fun getDisplayMap(stickerUuids: List<String>): MutableMap<String, String> {
val titles = entryPoint.stickerDao().getStickerTitles(stickerUuids)
.toMutableMap()

entryPoint.tagDao().getTagStringMap(
Expand All @@ -127,14 +146,14 @@ class StickerProvider : DocumentsProvider() {
return titles
}

private fun getMimeTypeMap(listFiles: Array<out File>): MutableMap<String, String> {
return entryPoint.mimeTypeDao().getStickerMimeTypes(listFiles.map { it.name })
.toMutableMap()
private fun getMimeTypeMap(stickerUuids: List<String>): MutableMap<String, String> {
return entryPoint.mimeTypeDao().getStickerMimeTypes(stickerUuids).toMutableMap()
}

private fun includeFile(
cursor: MatrixCursor,
path: String?,
lastModified: Long? = null,
displayMap: Map<String, String>,
mimeTypeMap: MutableMap<String, String>,
) {
Expand Down Expand Up @@ -163,7 +182,7 @@ class StickerProvider : DocumentsProvider() {
row.add(Document.COLUMN_DISPLAY_NAME, file.name)
}
row.add(Document.COLUMN_SIZE, file.length())
row.add(Document.COLUMN_LAST_MODIFIED, file.lastModified())
row.add(Document.COLUMN_LAST_MODIFIED, lastModified ?: file.lastModified())
}

override fun openDocument(
Expand Down Expand Up @@ -232,6 +251,8 @@ class StickerProvider : DocumentsProvider() {
if (reuseBitmap?.get() != null) {
inBitmap = reuseBitmap?.get()
}
// Only mutable bitmap can be reused.
inMutable = true
val bitmap = BitmapFactory.decodeFile(stickerFile.path, this)
bitmap.compress(Bitmap.CompressFormat.PNG, 90, fos)

Expand Down Expand Up @@ -262,9 +283,62 @@ class StickerProvider : DocumentsProvider() {
)
}

override fun queryRecentDocuments(rootId: String?, projection: Array<out String>?): Cursor {
return MatrixCursor(projection ?: DEFAULT_DOCUMENT_COLUMNS).apply {
val stickers = entryPoint.stickerDao().getRecentModifiedStickers()
.associateBy { it.sticker.uuid }
val listFiles = stickers.values
.map { File(STICKER_DIR, it.sticker.uuid) }
.filter { it.exists() }
.toTypedArray()
val stickerUuids = listFiles.map { it.name }
val displayMap = getDisplayMap(stickerUuids)
val mimeTypeMap = getMimeTypeMap(stickerUuids)

listFiles.forEach { file ->
includeFile(
cursor = this,
path = file.path,
lastModified = stickers[file.name]?.sticker?.modifyTime,
displayMap = displayMap,
mimeTypeMap = mimeTypeMap,
)
}
}
}

override fun querySearchDocuments(
rootId: String?,
query: String?,
projection: Array<out String>?
): Cursor {
return MatrixCursor(projection ?: DEFAULT_DOCUMENT_COLUMNS).apply {
val stickers = entryPoint.searchRepo().requestStickerWithTagsList(query.orEmpty())
.associateBy { it.sticker.uuid }
val listFiles = stickers.values
.map { File(STICKER_DIR, it.sticker.uuid) }
.filter { it.exists() }
.toTypedArray()
val stickerUuids = listFiles.map { it.name }
val displayMap = getDisplayMap(stickerUuids)
val mimeTypeMap = getMimeTypeMap(stickerUuids)

listFiles.forEach { file ->
includeFile(
cursor = this,
path = file.path,
lastModified = stickers[file.name]?.sticker?.modifyTime,
displayMap = displayMap,
mimeTypeMap = mimeTypeMap,
)
}
}
}

@EntryPoint
@InstallIn(SingletonComponent::class)
interface StickerProviderEntryPoint {
fun searchRepo(): SearchRepository
fun stickerDao(): StickerDao
fun tagDao(): TagDao
fun mimeTypeDao(): MimeTypeDao
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,24 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapConcat
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.takeWhile
import kotlinx.coroutines.runBlocking
import javax.inject.Inject

class SearchRepository @Inject constructor(
private val stickerDao: StickerDao,
) : BaseRepository() {
fun requestStickerWithTagsList(keyword: String): Flow<List<StickerWithTags>> {
fun requestStickerWithTagsList(keyword: String): List<StickerWithTags> = runBlocking {
stickerDao.getStickerWithTagsList(genSql(k = keyword)).first()
}

fun requestStickerWithTagsListFlow(keyword: String): Flow<List<StickerWithTags>> {
return flow { emit(genSql(keyword)) }
.flowOn(Dispatchers.IO)
.flatMapConcat {
Expand Down Expand Up @@ -268,6 +274,9 @@ class SearchRepository @Inject constructor(
appContext, HomeRepositoryEntryPoint::class.java
).searchDomainDao.getSearchDomain(tableName, columnName)
},
intersectSearchBySpace: Boolean = appContext.dataStore.getOrDefault(
IntersectSearchBySpacePreference
),
): SimpleSQLiteQuery {
val useRegexSearch = appContext.dataStore.getOrDefault(UseRegexSearchPreference)
if (useRegexSearch) {
Expand All @@ -277,9 +286,6 @@ class SearchRepository @Inject constructor(
}
}

// 是否使用多个关键字并集查询
val intersectSearchBySpace =
appContext.dataStore.getOrDefault(IntersectSearchBySpacePreference)
return if (intersectSearchBySpace) {
// 以多个连续的空格/制表符/换行符分割
val keywords = k.trim().split("\\s+".toRegex()).toSet()
Expand Down
4 changes: 2 additions & 2 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
buildscript {
extra.apply {
set("composeVersion", "1.6.0")
set("md3Version", "1.2.0")
set("composeVersion", "1.6.5")
set("md3Version", "1.2.1")
set("accompanistVersion", "0.34.0")
set("mlkitRecognitionVersion", "16.0.0")
set("roomVersion", "2.6.1")
Expand Down

0 comments on commit 000d4b5

Please sign in to comment.