Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,8 @@ dependencies {
// Fuzzy Search
implementation(libs.fuzzywuzzy.kotlin)

implementation(libs.androidx.palette.ktx)

// Aire
implementation(libs.aire)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"formatVersion": 1,
"database": {
"version": 13,
"identityHash": "26ff9ec9cc612eb24d80263f7bb14a7f",
"identityHash": "f4c23754c71fe941151d4de6df0d4f74",
"entities": [
{
"tableName": "pinned_table",
Expand Down Expand Up @@ -350,6 +350,179 @@
]
}
},
{
"tableName": "hue_indexed_media",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `label` TEXT NOT NULL, `uri` TEXT NOT NULL, `path` TEXT NOT NULL, `relativePath` TEXT NOT NULL, `albumID` INTEGER NOT NULL, `albumLabel` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, `expiryTimestamp` INTEGER, `takenTimestamp` INTEGER, `fullDate` TEXT NOT NULL, `mimeType` TEXT NOT NULL, `favorite` INTEGER NOT NULL, `trashed` INTEGER NOT NULL, `size` INTEGER NOT NULL, `duration` TEXT, `L1` REAL NOT NULL, `a1` REAL NOT NULL, `b1` REAL NOT NULL, `L2` REAL NOT NULL, `a2` REAL NOT NULL, `b2` REAL NOT NULL, `morton1` INTEGER NOT NULL, `morton2` INTEGER NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "label",
"columnName": "label",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "uri",
"columnName": "uri",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "path",
"columnName": "path",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "relativePath",
"columnName": "relativePath",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "albumID",
"columnName": "albumID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "albumLabel",
"columnName": "albumLabel",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "timestamp",
"columnName": "timestamp",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "expiryTimestamp",
"columnName": "expiryTimestamp",
"affinity": "INTEGER"
},
{
"fieldPath": "takenTimestamp",
"columnName": "takenTimestamp",
"affinity": "INTEGER"
},
{
"fieldPath": "fullDate",
"columnName": "fullDate",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "mimeType",
"columnName": "mimeType",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "favorite",
"columnName": "favorite",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "trashed",
"columnName": "trashed",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "size",
"columnName": "size",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "duration",
"columnName": "duration",
"affinity": "TEXT"
},
{
"fieldPath": "L1",
"columnName": "L1",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "a1",
"columnName": "a1",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "b1",
"columnName": "b1",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "L2",
"columnName": "L2",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "a2",
"columnName": "a2",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "b2",
"columnName": "b2",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "morton1",
"columnName": "morton1",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "morton2",
"columnName": "morton2",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_hue_indexed_media_morton1",
"unique": false,
"columnNames": [
"morton1"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_hue_indexed_media_morton1` ON `${TABLE_NAME}` (`morton1`)"
},
{
"name": "index_hue_indexed_media_morton2",
"unique": false,
"columnNames": [
"morton2"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_hue_indexed_media_morton2` ON `${TABLE_NAME}` (`morton2`)"
}
]
},
{
"tableName": "encrypted_media",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `label` TEXT NOT NULL, `uuid` TEXT NOT NULL, `path` TEXT NOT NULL, `relativePath` TEXT NOT NULL, `albumID` INTEGER NOT NULL, `albumLabel` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, `expiryTimestamp` INTEGER, `takenTimestamp` INTEGER, `fullDate` TEXT NOT NULL, `mimeType` TEXT NOT NULL, `favorite` INTEGER NOT NULL, `trashed` INTEGER NOT NULL, `size` INTEGER NOT NULL, `duration` TEXT, PRIMARY KEY(`id`))",
Expand Down Expand Up @@ -732,7 +905,7 @@
],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '26ff9ec9cc612eb24d80263f7bb14a7f')"
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'f4c23754c71fe941151d4de6df0d4f74')"
]
}
}
9 changes: 9 additions & 0 deletions app/src/main/kotlin/com/dot/gallery/core/MediaDistributor.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.dot.gallery.core

import androidx.compose.ui.graphics.Color
import com.dot.gallery.feature_node.domain.model.AlbumState
import com.dot.gallery.feature_node.domain.model.IgnoredAlbum
import com.dot.gallery.feature_node.domain.model.ImageEmbedding
Expand All @@ -10,6 +11,7 @@ import com.dot.gallery.feature_node.domain.model.PinnedAlbum
import com.dot.gallery.feature_node.domain.model.TimelineSettings
import com.dot.gallery.feature_node.domain.model.Vault
import com.dot.gallery.feature_node.domain.model.VaultState
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
Expand Down Expand Up @@ -60,5 +62,12 @@ interface MediaDistributor {
*/
val imageEmbeddingsFlow: StateFlow<List<ImageEmbedding>>

/**
* Hue Search
*/
fun hueSearchMediaFlow(
hueFlow: Flow<Color>,
debounceMillis: Long = 50L
): StateFlow<MediaState<Media.HueIndexedMedia>>

}
43 changes: 43 additions & 0 deletions app/src/main/kotlin/com/dot/gallery/core/MediaDistributorImpl.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
package com.dot.gallery.core

import androidx.compose.runtime.compositionLocalOf
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.util.fastDistinctBy
import androidx.compose.ui.util.fastFilter
import androidx.core.graphics.ColorUtils
import androidx.work.WorkInfo
import androidx.work.WorkManager
import com.dot.gallery.core.Settings.Misc.DEFAULT_DATE_FORMAT
Expand All @@ -24,19 +29,24 @@ import com.dot.gallery.feature_node.domain.util.MediaOrder
import com.dot.gallery.feature_node.domain.util.OrderType
import com.dot.gallery.feature_node.domain.util.mapPinned
import com.dot.gallery.feature_node.domain.util.removeBlacklisted
import com.dot.gallery.feature_node.presentation.huesearch.HueIndexHelper
import com.dot.gallery.feature_node.presentation.util.mapMediaToItem
import com.dot.gallery.feature_node.presentation.util.mediaFlow
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.shareIn
Expand Down Expand Up @@ -285,5 +295,38 @@ class MediaDistributorImpl @Inject constructor(
initialValue = emptyList()
)

/**
* Hue Search
*/
@OptIn(FlowPreview::class)
override fun hueSearchMediaFlow(
hueFlow: Flow<Color>,
debounceMillis: Long
): StateFlow<MediaState<Media.HueIndexedMedia>> =
combine(
hueFlow.debounce(debounceMillis),
repository.getHueIndexedImagesFlow(),
dateFormatsFlow
) { hue, indexedImages, (defaultDateFormat, extendedDateFormat, weeklyDateFormat) ->
val lab = DoubleArray(3).also { ColorUtils.colorToLAB(hue.toArgb(), it) }
val (gx, gy, gz) = HueIndexHelper.quantizeLab(lab[0], lab[1], lab[2])

val neighborhood = HueIndexHelper.neighbors(gx, gy, gz, 4)
val data = indexedImages.run {
fastFilter { it.morton1 in neighborhood }
.plus(fastFilter { it.morton2 in neighborhood })
.fastDistinctBy { it.id }
}
mapMediaToItem(
data = data,
error = "",
albumId = -1L,
defaultDateFormat = defaultDateFormat,
extendedDateFormat = extendedDateFormat,
weeklyDateFormat = weeklyDateFormat
)
}
.flowOn(Dispatchers.Default)
.stateIn(appScope, sharingMethod, MediaState())

}
3 changes: 3 additions & 0 deletions app/src/main/kotlin/com/dot/gallery/core/MediaHandler.kt
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ interface MediaHandler {

fun getClassifiedMediaThumbnailByCategory(category: String): Flow<Media.ClassifiedMedia?>

fun getHueIndexedMediaCount(): Flow<Int>
suspend fun deleteHueIndexData()

suspend fun deleteAlbumThumbnail(albumId: Long)
suspend fun updateAlbumThumbnail(albumId: Long, newThumbnail: Uri)
fun hasAlbumThumbnail(albumId: Long): Flow<Boolean>
Expand Down
7 changes: 7 additions & 0 deletions app/src/main/kotlin/com/dot/gallery/core/MediaHandlerImpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,13 @@ class MediaHandlerImpl @Inject constructor(
override fun getClassifiedMediaThumbnailByCategory(category: String): Flow<Media.ClassifiedMedia?> =
repository.getClassifiedMediaThumbnailByCategory(category)

override fun getHueIndexedMediaCount(): Flow<Int> =
repository.getHueIndexedImageCount()

override suspend fun deleteHueIndexData() {
repository.deleteHueIndexData()
}

override suspend fun deleteAlbumThumbnail(albumId: Long) =
repository.deleteAlbumThumbnail(albumId)

Expand Down
Loading