Skip to content

Commit

Permalink
[feature|optimize|build] Support searching for similar images; optimi…
Browse files Browse the repository at this point in the history
…ze image listing page with Paging; use TOML to manage dependencies
  • Loading branch information
SkyD666 committed Jan 14, 2025
1 parent 763d3d2 commit ed9e7fc
Show file tree
Hide file tree
Showing 40 changed files with 1,105 additions and 340 deletions.
113 changes: 58 additions & 55 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ import com.android.build.api.variant.FilterConfiguration
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("org.jetbrains.kotlin.plugin.compose")
id("kotlinx-serialization")
id("kotlin-parcelize")
id("kotlin-android")
id("com.google.devtools.ksp")
id("com.google.dagger.hilt.android")
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.parcelize)
alias(libs.plugins.kotlin.compose)
alias(libs.plugins.kotlin.serialization)
alias(libs.plugins.hilt)
alias(libs.plugins.ksp)
alias(libs.plugins.objectbox)
}

apply(from = "../secret.gradle.kts")
Expand All @@ -23,7 +23,7 @@ android {
minSdk = 24
targetSdk = 35
versionCode = 67
versionName = "2.3-rc02"
versionName = "2.3-rc04"

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
Expand Down Expand Up @@ -146,55 +146,58 @@ tasks.withType(KotlinCompile::class.java).configureEach {
}

dependencies {
implementation("androidx.core:core-ktx:1.15.0")
implementation("androidx.compose.ui:ui:1.7.5")
implementation("androidx.compose.material3:material3:1.3.1")
implementation("androidx.compose.material3:material3-window-size-class:1.3.1")
implementation("androidx.compose.material:material:1.7.5")
implementation("androidx.compose.material:material-icons-extended:1.7.5")
implementation("androidx.compose.ui:ui-tooling-preview:1.7.5")
implementation("com.google.android.material:material:1.12.0")
implementation("androidx.lifecycle:lifecycle-runtime-compose:2.8.7")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.7")
implementation("androidx.activity:activity-compose:1.9.3")
implementation("androidx.palette:palette-ktx:1.0.0")
implementation("com.google.dagger:hilt-android:2.53")
ksp("com.google.dagger:hilt-android-compiler:2.53")
implementation("androidx.hilt:hilt-navigation-compose:1.2.0")
implementation("androidx.navigation:navigation-compose:2.8.4")
implementation("androidx.security:security-crypto:1.1.0-alpha06")
implementation("com.google.accompanist:accompanist-drawablepainter:0.36.0")
implementation("io.coil-kt.coil3:coil-compose:3.0.4")
implementation("io.coil-kt.coil3:coil-gif:3.0.4")
implementation("io.coil-kt.coil3:coil-svg:3.0.4")
implementation("androidx.profileinstaller:profileinstaller:1.4.1")
implementation("androidx.core:core-splashscreen:1.0.1")
implementation("androidx.room:room-runtime:2.6.1")
implementation("androidx.room:room-ktx:2.6.1")
ksp("androidx.room:room-compiler:2.6.1")
implementation("com.github.thegrizzlylabs:sardine-android:0.8")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")
implementation("com.materialkolor:material-kolor:2.0.0")
implementation("androidx.datastore:datastore-preferences:1.1.1")
implementation("com.airbnb.android:lottie-compose:6.6.1")

implementation("com.squareup.retrofit2:retrofit:2.11.0")
implementation("com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0")
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.compose.ui)
implementation(libs.androidx.compose.material3)
implementation(libs.androidx.compose.window.size)
implementation(libs.androidx.compose.material)
implementation(libs.androidx.compose.icons)
implementation(libs.androidx.ui.tooling.preview)
implementation(libs.material)
implementation(libs.androidx.lifecycle.runtime.compose)
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.palette.ktx)
implementation(libs.hilt.android)
ksp(libs.hilt.android.compiler)
implementation(libs.androidx.hilt.navigation.compose)
implementation(libs.androidx.navigation.compose)
implementation(libs.androidx.security.crypto)
implementation(libs.accompanist.drawablepainter)
implementation(libs.coil.compose)
implementation(libs.coil.gif)
implementation(libs.coil.svg)
implementation(libs.androidx.profileinstaller)
implementation(libs.androidx.core.splashscreen)
implementation(libs.androidx.room.runtime)
implementation(libs.androidx.room.ktx)
implementation(libs.androidx.room.paging)
ksp(libs.androidx.room.compiler)
implementation(libs.androidx.paging.compose)
implementation(libs.sardine.android)
implementation(libs.kotlinx.serialization.json)
implementation(libs.material.kolor)
implementation(libs.androidx.datastore.preferences)
implementation(libs.lottie.compose)

implementation(libs.retrofit2)
implementation(libs.retrofit2.kotlinx.serialization.converter)

// Google ML Kit
implementation("com.google.mlkit:text-recognition:16.0.1")
implementation("com.google.mlkit:text-recognition-chinese:16.0.1")
implementation("com.google.mlkit:text-recognition-japanese:16.0.1")
implementation("com.google.mlkit:text-recognition-korean:16.0.1")
implementation("com.google.mlkit:image-labeling-custom:17.0.3")
implementation("com.google.mlkit:segmentation-selfie:16.0.0-beta6")

implementation(libs.text.recognition)
implementation(libs.text.recognition.chinese)
implementation(libs.text.recognition.japanese)
implementation(libs.text.recognition.korean)
implementation(libs.image.labeling.custom)
implementation(libs.segmentation.selfie)
implementation(libs.tasks.vision)
// TF Lite
implementation("org.tensorflow:tensorflow-lite:2.16.1")
implementation("org.tensorflow:tensorflow-lite-support:0.4.4")
implementation(libs.ai.edge.litert)
implementation(libs.ai.edge.litert.support)

implementation(libs.apng)

implementation("com.github.penfeizhou.android.animation:apng:3.0.2")
debugImplementation(libs.androidx.compose.ui.ui.tooling3)
debugImplementation(libs.androidx.compose.ui.ui.test.manifest)

debugImplementation("androidx.compose.ui:ui-tooling:1.7.5")
debugImplementation("androidx.compose.ui:ui-test-manifest:1.7.5")
testImplementation(libs.junit)
}
7 changes: 7 additions & 0 deletions app/src/main/java/com/skyd/rays/di/DatabaseModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,24 @@ import com.skyd.rays.model.db.dao.UriStringSharePackageDao
import com.skyd.rays.model.db.dao.cache.StickerShareTimeDao
import com.skyd.rays.model.db.dao.sticker.MimeTypeDao
import com.skyd.rays.model.db.dao.sticker.StickerDao
import com.skyd.rays.model.db.objectbox.ObjectBox
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import io.objectbox.BoxStore
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
object DatabaseModule {

@Provides
@Singleton
fun provideObjectBoxStore(@ApplicationContext context: Context): BoxStore =
ObjectBox.getInstance(context)

@Provides
@Singleton
fun provideAppDatabase(@ApplicationContext context: Context): AppDatabase =
Expand Down
17 changes: 17 additions & 0 deletions app/src/main/java/com/skyd/rays/di/PagingModule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.skyd.rays.di

import androidx.paging.PagingConfig
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
object PagingModule {
@Provides
@Singleton
fun providePagingConfig(): PagingConfig =
PagingConfig(pageSize = 20, enablePlaceholders = false)
}
10 changes: 10 additions & 0 deletions app/src/main/java/com/skyd/rays/ext/PaddingValuesExt.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,13 @@ operator fun PaddingValues.plus(other: PaddingValues): PaddingValues = PaddingVa
end = calculateEndPadding(LocalLayoutDirection.current) +
other.calculateEndPadding(LocalLayoutDirection.current)
)

@Composable
operator fun PaddingValues.minus(other: PaddingValues): PaddingValues = PaddingValues(
top = calculateTopPadding() - other.calculateTopPadding(),
bottom = calculateBottomPadding() - other.calculateBottomPadding(),
start = calculateStartPadding(LocalLayoutDirection.current) -
other.calculateStartPadding(LocalLayoutDirection.current),
end = calculateEndPadding(LocalLayoutDirection.current) -
other.calculateEndPadding(LocalLayoutDirection.current)
)
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ sealed interface HandleImportedStickerStrategy : Parcelable {
return false
}
moveFile(stickerFile)
stickerDao.innerAddSticker(importedStickerWithTags.sticker)
stickerDao.addSticker(importedStickerWithTags.sticker)
importedStickerWithTags.tags.forEach {
it.stickerUuid = stickerUuid
}
Expand All @@ -74,7 +74,7 @@ sealed interface HandleImportedStickerStrategy : Parcelable {
checkStickerWithTagsFormat(importedStickerWithTags)
val stickerUuid = importedStickerWithTags.sticker.uuid
moveFile(stickerFile)
stickerDao.innerAddSticker(importedStickerWithTags.sticker)
stickerDao.addSticker(importedStickerWithTags.sticker)
importedStickerWithTags.tags.forEach {
it.stickerUuid = stickerUuid
}
Expand Down
40 changes: 38 additions & 2 deletions app/src/main/java/com/skyd/rays/model/db/dao/sticker/StickerDao.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package com.skyd.rays.model.db.dao.sticker

import androidx.paging.PagingSource
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.MapColumn
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.RawQuery
import androidx.room.RewriteQueriesToDropUnusedColumns
import androidx.room.Transaction
import androidx.sqlite.db.SupportSQLiteQuery
import com.skyd.rays.appContext
Expand All @@ -27,11 +29,15 @@ import com.skyd.rays.model.bean.StickerWithTagsAndFile
import com.skyd.rays.model.bean.TagBean
import com.skyd.rays.model.db.dao.TagDao
import com.skyd.rays.model.db.dao.cache.StickerShareTimeDao
import com.skyd.rays.model.db.objectbox.entity.StickerEmbedding
import com.skyd.rays.model.db.objectbox.entity.StickerEmbedding_
import com.skyd.rays.model.preference.CurrentStickerUuidPreference
import dagger.hilt.EntryPoint
import dagger.hilt.InstallIn
import dagger.hilt.android.EntryPointAccessors
import dagger.hilt.components.SingletonComponent
import io.objectbox.BoxStore
import io.objectbox.query.QueryBuilder
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
Expand All @@ -45,12 +51,25 @@ interface StickerDao {
interface StickerDaoEntryPoint {
val tagDao: TagDao
val stickerShareTimeDao: StickerShareTimeDao
val boxStore: BoxStore
}

@Transaction
@RawQuery(observedEntities = [StickerBean::class, TagBean::class])
fun getStickerWithTagsList(sql: SupportSQLiteQuery): Flow<List<StickerWithTags>>

@Transaction
@RawQuery(observedEntities = [StickerBean::class, TagBean::class])
fun getStickerWithTagsPaging(sql: SupportSQLiteQuery): PagingSource<Int, StickerWithTags>

@Transaction
@RawQuery(observedEntities = [StickerBean::class, TagBean::class])
fun getStickerUuidList(sql: SupportSQLiteQuery): List<String>

@Transaction
@Query("SELECT $UUID_COLUMN FROM $STICKER_TABLE_NAME")
fun getAllStickerUuidList(): List<String>

@Transaction
@Query("SELECT * FROM $STICKER_TABLE_NAME")
fun getAllStickerWithTagsList(): List<StickerWithTags>
Expand Down Expand Up @@ -119,6 +138,7 @@ interface StickerDao {
fun getMostSharedStickersList(count: Int): Flow<List<StickerWithTags>>

@Transaction
@RewriteQueriesToDropUnusedColumns
@Query(
"""
SELECT * FROM $STICKER_TABLE_NAME LEFT JOIN (
Expand Down Expand Up @@ -191,7 +211,7 @@ interface StickerDao {
stickerUuid = UUID.randomUUID().toString()
stickerWithTags.sticker.uuid = stickerUuid
}
innerAddSticker(stickerWithTags.sticker)
addSticker(stickerWithTags.sticker)
stickerWithTags.tags.forEach {
it.stickerUuid = stickerUuid
}
Expand All @@ -202,9 +222,25 @@ interface StickerDao {
return stickerUuid
}

fun addSticker(stickerBean: StickerBean) {
EntryPointAccessors.fromApplication(appContext, StickerDaoEntryPoint::class.java)
.boxStore.boxFor(StickerEmbedding::class.java).apply {
val oldEmbedding = query().equal(
StickerEmbedding_.uuid,
stickerBean.uuid,
QueryBuilder.StringOrder.CASE_SENSITIVE
).build().findUnique()
if (oldEmbedding != null) {
remove(oldEmbedding)
}
}
_innerAddSticker(stickerBean)
}

@Suppress("FunctionName")
@Transaction
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun innerAddSticker(stickerBean: StickerBean)
fun _innerAddSticker(stickerBean: StickerBean)

@Transaction
fun deleteStickerWithTags(stickerUuids: List<String>): Int {
Expand Down
19 changes: 19 additions & 0 deletions app/src/main/java/com/skyd/rays/model/db/objectbox/ObjectBox.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.skyd.rays.model.db.objectbox

import android.content.Context
import com.skyd.rays.model.db.objectbox.entity.MyObjectBox
import io.objectbox.BoxStore

object ObjectBox {
@Volatile
private var instance: BoxStore? = null

fun getInstance(context: Context): BoxStore {
return instance ?: synchronized(this) {
instance ?: MyObjectBox.builder()
.androidContext(context)
.build()
.apply { instance = this }
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.skyd.rays.model.db.objectbox.entity

import io.objectbox.annotation.Entity
import io.objectbox.annotation.HnswIndex
import io.objectbox.annotation.Id
import io.objectbox.annotation.Index

@Entity
data class StickerEmbedding(
@Id var id: Long = 0,
@Index var uuid: String,
@HnswIndex(dimensions = 1024)
var embedding: FloatArray? = null,
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false

other as StickerEmbedding

if (uuid != other.uuid) return false
if (embedding != null) {
if (other.embedding == null) return false
if (!embedding.contentEquals(other.embedding)) return false
} else if (other.embedding != null) return false

return true
}

override fun hashCode(): Int {
var result = uuid.hashCode()
result = 31 * result + (embedding?.contentHashCode() ?: 0)
return result
}
}
Loading

0 comments on commit ed9e7fc

Please sign in to comment.