package dev.inmo.micro_utils.repos.keyvalue import android.content.Context import android.content.SharedPreferences import androidx.core.content.edit import dev.inmo.micro_utils.pagination.Pagination import dev.inmo.micro_utils.pagination.PaginationResult import dev.inmo.micro_utils.pagination.utils.paginate import dev.inmo.micro_utils.pagination.utils.reverse import dev.inmo.micro_utils.repos.StandardKeyValueRepo import kotlinx.coroutines.flow.* private val cache = HashMap>() fun Context.keyValueStore( name: String = "default", cacheValues: Boolean = false ): StandardKeyValueRepo { return cache.getOrPut(name) { KeyValueStore(this, name, cacheValues) } as KeyValueStore } class KeyValueStore internal constructor ( c: Context, preferencesName: String, useCache: Boolean = false ) : SharedPreferences.OnSharedPreferenceChangeListener, StandardKeyValueRepo { private val sharedPreferences = c.getSharedPreferences(preferencesName, Context.MODE_PRIVATE) private val cachedData = if (useCache) { mutableMapOf() } else { null } private val onNewValueChannel = MutableSharedFlow>() private val _onValueRemovedFlow = MutableSharedFlow() override val onNewValue: Flow> = onNewValueChannel.asSharedFlow() override val onValueRemoved: Flow = _onValueRemovedFlow.asSharedFlow() init { cachedData ?.let { for ((key, value) in sharedPreferences.all) { if (value != null) { cachedData[key] = value } } sharedPreferences.registerOnSharedPreferenceChangeListener(this) } } override fun onSharedPreferenceChanged(sp: SharedPreferences, key: String) { val value = sp.all[key] cachedData ?: return if (value != null) { cachedData[key] = value } else { cachedData.remove(key) } } override suspend fun get(k: String): T? { return (cachedData ?. get(k) ?: sharedPreferences.all[k]) as? T } override suspend fun values(pagination: Pagination, reversed: Boolean): PaginationResult { val resultPagination = if (reversed) pagination.reverse(count()) else pagination return sharedPreferences.all.values.paginate( resultPagination ).let { PaginationResult( it.page, it.pagesNumber, it.results.map { it as T }.let { if (reversed) it.reversed() else it }, it.size ) } } override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult { val resultPagination = if (reversed) pagination.reverse(count()) else pagination return sharedPreferences.all.keys.paginate( resultPagination ).let { PaginationResult( it.page, it.pagesNumber, it.results.let { if (reversed) it.reversed() else it }, it.size ) } } override suspend fun keys(v: T, pagination: Pagination, reversed: Boolean): PaginationResult { val resultPagination = if (reversed) pagination.reverse(count()) else pagination return sharedPreferences.all.mapNotNull { (k, value) -> if (value == v) k else null }.paginate( resultPagination ).let { PaginationResult( it.page, it.pagesNumber, it.results.let { if (reversed) it.reversed() else it }, it.size ) } } override suspend fun contains(key: String): Boolean = sharedPreferences.contains(key) override suspend fun count(): Long = sharedPreferences.all.size.toLong() override suspend fun set(toSet: Map) { sharedPreferences.edit { for ((k, v) in toSet) { when(v) { is Int -> putInt(k, v) is Long -> putLong(k, v) is Float -> putFloat(k, v) is String -> putString(k, v) is Boolean -> putBoolean(k, v) is Set<*> -> putStringSet(k, v.map { (it as? String) ?: it.toString() }.toSet()) else -> error( "Currently supported only primitive types and set for SharedPreferences KeyValue repos" ) } } } for ((k, v) in toSet) { onNewValueChannel.emit(k to v) } } override suspend fun unset(toUnset: List) { sharedPreferences.edit { for (item in toUnset) { remove(item) } } for (it in toUnset) { _onValueRemovedFlow.emit(it) } } override suspend fun unsetWithValues(toUnset: List) { val keysToRemove = sharedPreferences.all.mapNotNull { if (it.value in toUnset) it.key else null } sharedPreferences.edit { keysToRemove.map { remove(it) } } keysToRemove.forEach { _onValueRemovedFlow.emit(it) } } }