mirror of
https://github.com/InsanusMokrassar/MicroUtils.git
synced 2025-09-17 14:29:24 +00:00
Compare commits
24 Commits
Author | SHA1 | Date | |
---|---|---|---|
1c86f3f4bf | |||
6d999be590 | |||
e715772dbf | |||
63eb7b7ea8 | |||
b07683b815 | |||
96e97d1691 | |||
261d8827e3 | |||
c3156f2e41 | |||
8c08801460 | |||
aaf1299da7 | |||
a411355b4f | |||
eba41066b4 | |||
f295dff8a2 | |||
a16815143c | |||
6ff3f6ae42 | |||
84071881af | |||
7cccf7e56e | |||
2516d5e381 | |||
cdec8bac75 | |||
fa30aae194 | |||
eb959a3135 | |||
24033e0cac | |||
71f9a505e0 | |||
979b8f017b |
12
.github/workflows/build.yml
vendored
Normal file
12
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
name: Regular build
|
||||
on: [push]
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 1.8
|
||||
- name: Build
|
||||
run: ./gradlew build
|
57
CHANGELOG.md
57
CHANGELOG.md
@@ -1,5 +1,62 @@
|
||||
# Changelog
|
||||
|
||||
## 0.5.12
|
||||
|
||||
* `Common`:
|
||||
* `Android`
|
||||
* Extension `View#changeVisibility` has been fixed
|
||||
* `Android`
|
||||
* `RecyclerView`
|
||||
* Default adapter got `dataCountFlow` property
|
||||
* New subtype of adapter based on `StateFlow`: `StateFlowBasedRecyclerViewAdapter`
|
||||
|
||||
## 0.5.11
|
||||
|
||||
* `Repos`:
|
||||
* `Common`:
|
||||
* Fixes in `WriteOneToManyRepo#add`
|
||||
* `Exposed`:
|
||||
* Fixes in `ExposedOneToManyKeyValueRepo#add`
|
||||
|
||||
## 0.5.10
|
||||
|
||||
* `Versions`
|
||||
* `Core KTX`: `1.3.2` -> `1.5.0`
|
||||
* `AndroidX Recycler`: `1.2.0` -> `1.2.1`
|
||||
* `AppCompat`: `1.2.0` -> `1.3.0`
|
||||
* `Android`
|
||||
* `RecyclerView`:
|
||||
* `data` of `RecyclerViewAdapter` became an abstract field
|
||||
* New function `RecyclerViewAdapter`
|
||||
* `Common`:
|
||||
* New extension `View#changeVisibility`
|
||||
* `Repos`:
|
||||
* `Common`:
|
||||
* `WriteOneToManyRepo` got new function `clearWithValue`
|
||||
* `Android`:
|
||||
* New extension `SQLiteDatabase#selectDistinct`
|
||||
* Fixes in `OneToManyAndroidRepo`
|
||||
* `Ktor`
|
||||
* `Server`
|
||||
* All elements in configurators became a `fun interface`
|
||||
* `Pagination`
|
||||
* New function `doForAllWithCurrentPaging`
|
||||
|
||||
## 0.5.9
|
||||
|
||||
* `Repos`
|
||||
* `Common`
|
||||
* `OneToManyAndroidRepo` got new primary constructor
|
||||
|
||||
## 0.5.8
|
||||
|
||||
* `Common`:
|
||||
* New extension `Iterable#firstNotNull`
|
||||
* `Coroutines`
|
||||
* New extension `Flow#firstNotNull`
|
||||
* New extensions `CoroutineContext#LinkedSupervisorJob`, `CoroutineScope#LinkedSupervisorJob` and
|
||||
`CoroutineScope#LinkedSupervisorScope`
|
||||
|
||||
## 0.5.7
|
||||
|
||||
* `Pagination`
|
||||
|
@@ -35,9 +35,9 @@ class ActionViewHolder(
|
||||
}
|
||||
|
||||
class ActionsRecyclerViewAdapter(
|
||||
data: List<AlertAction>,
|
||||
override val data: List<AlertAction>,
|
||||
private val dialogInterfaceGetter: () -> DialogInterface
|
||||
) : RecyclerViewAdapter<AlertAction>(data) {
|
||||
) : RecyclerViewAdapter<AlertAction>() {
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AbstractViewHolder<AlertAction> = ActionViewHolder(
|
||||
parent, dialogInterfaceGetter
|
||||
)
|
||||
|
@@ -11,6 +11,7 @@ kotlin {
|
||||
commonMain {
|
||||
dependencies {
|
||||
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version"
|
||||
api project(":micro_utils.common")
|
||||
}
|
||||
}
|
||||
androidMain {
|
||||
|
@@ -1,12 +1,21 @@
|
||||
package dev.inmo.micro_utils.android.recyclerview
|
||||
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
|
||||
abstract class RecyclerViewAdapter<T>(
|
||||
val data: List<T>
|
||||
): RecyclerView.Adapter<AbstractViewHolder<T>>() {
|
||||
abstract class RecyclerViewAdapter<T>: RecyclerView.Adapter<AbstractViewHolder<T>>() {
|
||||
protected abstract val data: List<T>
|
||||
|
||||
private val _dataCountState by lazy {
|
||||
MutableStateFlow<Int>(data.size)
|
||||
}
|
||||
val dataCountState: StateFlow<Int> by lazy {
|
||||
_dataCountState.asStateFlow()
|
||||
}
|
||||
|
||||
var emptyView: View? = null
|
||||
set(value) {
|
||||
field = value
|
||||
@@ -18,31 +27,37 @@ abstract class RecyclerViewAdapter<T>(
|
||||
object : RecyclerView.AdapterDataObserver() {
|
||||
override fun onItemRangeChanged(positionStart: Int, itemCount: Int) {
|
||||
super.onItemRangeChanged(positionStart, itemCount)
|
||||
_dataCountState.value = data.size
|
||||
checkEmpty()
|
||||
}
|
||||
|
||||
override fun onItemRangeChanged(positionStart: Int, itemCount: Int, payload: Any?) {
|
||||
super.onItemRangeChanged(positionStart, itemCount, payload)
|
||||
_dataCountState.value = data.size
|
||||
checkEmpty()
|
||||
}
|
||||
|
||||
override fun onChanged() {
|
||||
super.onChanged()
|
||||
_dataCountState.value = data.size
|
||||
checkEmpty()
|
||||
}
|
||||
|
||||
override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
|
||||
super.onItemRangeRemoved(positionStart, itemCount)
|
||||
_dataCountState.value = data.size
|
||||
checkEmpty()
|
||||
}
|
||||
|
||||
override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) {
|
||||
super.onItemRangeMoved(fromPosition, toPosition, itemCount)
|
||||
_dataCountState.value = data.size
|
||||
checkEmpty()
|
||||
}
|
||||
|
||||
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
|
||||
super.onItemRangeInserted(positionStart, itemCount)
|
||||
_dataCountState.value = data.size
|
||||
checkEmpty()
|
||||
}
|
||||
}
|
||||
@@ -58,7 +73,7 @@ abstract class RecyclerViewAdapter<T>(
|
||||
|
||||
private fun checkEmpty() {
|
||||
emptyView ?. let {
|
||||
if (data.isEmpty()) {
|
||||
if (dataCountState.value == 0) {
|
||||
it.visibility = View.VISIBLE
|
||||
} else {
|
||||
it.visibility = View.GONE
|
||||
@@ -66,3 +81,11 @@ abstract class RecyclerViewAdapter<T>(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> RecyclerViewAdapter(
|
||||
data: List<T>,
|
||||
onCreateViewHolder: (parent: ViewGroup, viewType: Int) -> AbstractViewHolder<T>
|
||||
) = object : RecyclerViewAdapter<T>() {
|
||||
override val data: List<T> = data
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AbstractViewHolder<T> = onCreateViewHolder(parent, viewType)
|
||||
}
|
||||
|
@@ -0,0 +1,50 @@
|
||||
package dev.inmo.micro_utils.android.recyclerview
|
||||
|
||||
import dev.inmo.micro_utils.common.Diff
|
||||
import dev.inmo.micro_utils.common.PreviewFeature
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
@PreviewFeature("This feature in preview state and may contains different bugs. " +
|
||||
"Besides, this feature can be changed in future in non-compatible way")
|
||||
abstract class StateFlowBasedRecyclerViewAdapter<T>(
|
||||
listeningScope: CoroutineScope,
|
||||
dataState: StateFlow<List<T>>
|
||||
) : RecyclerViewAdapter<T>() {
|
||||
override var data: List<T> = emptyList()
|
||||
|
||||
init {
|
||||
dataState.onEach {
|
||||
try {
|
||||
val diffForRemoves = Diff(data, it)
|
||||
val removedIndexes = diffForRemoves.removed.map { it.index }
|
||||
val leftRemove = removedIndexes.toMutableList()
|
||||
data = data.filterIndexed { i, _ ->
|
||||
if (i in leftRemove) {
|
||||
leftRemove.remove(i)
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
removedIndexes.sortedDescending().forEach {
|
||||
notifyItemRemoved(it)
|
||||
}
|
||||
}
|
||||
val diffAddsAndReplaces = Diff(data, it)
|
||||
data = it
|
||||
withContext(Dispatchers.Main) {
|
||||
diffAddsAndReplaces.replaced.forEach { (from, to) ->
|
||||
notifyItemMoved(from.index, to.index)
|
||||
}
|
||||
diffAddsAndReplaces.added.forEach {
|
||||
notifyItemInserted(it.index)
|
||||
}
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
// currently do nothing
|
||||
}
|
||||
}.launchIn(listeningScope)
|
||||
}
|
||||
}
|
@@ -16,7 +16,7 @@ package dev.inmo.micro_utils.common
|
||||
AnnotationTarget.TYPEALIAS,
|
||||
AnnotationTarget.TYPE_PARAMETER
|
||||
)
|
||||
annotation class PreviewFeature
|
||||
annotation class PreviewFeature(val message: String = "It is possible, that behaviour of this thing will be changed or removed in future releases")
|
||||
|
||||
@RequiresOptIn(
|
||||
"This thing is marked as warned. See message of warn to get more info",
|
||||
|
@@ -0,0 +1,3 @@
|
||||
package dev.inmo.micro_utils.common
|
||||
|
||||
fun <T> Iterable<T?>.firstNotNull() = first { it != null }!!
|
@@ -33,3 +33,15 @@ fun View.toggleVisibility(goneOnHide: Boolean = true) {
|
||||
show()
|
||||
}
|
||||
}
|
||||
|
||||
fun View.changeVisibility(show: Boolean = !isShown, goneOnHide: Boolean = true) {
|
||||
if (show) {
|
||||
show()
|
||||
} else {
|
||||
if (goneOnHide) {
|
||||
gone()
|
||||
} else {
|
||||
hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,6 @@
|
||||
package dev.inmo.micro_utils.coroutines
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.first
|
||||
|
||||
suspend fun <T> Flow<T?>.firstNotNull() = first { it != null }!!
|
@@ -0,0 +1,17 @@
|
||||
package dev.inmo.micro_utils.coroutines
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
fun CoroutineContext.LinkedSupervisorJob(
|
||||
additionalContext: CoroutineContext? = null
|
||||
) = SupervisorJob(job).let { if (additionalContext != null) it + additionalContext else it }
|
||||
fun CoroutineScope.LinkedSupervisorJob(
|
||||
additionalContext: CoroutineContext? = null
|
||||
) = coroutineContext.LinkedSupervisorJob(additionalContext)
|
||||
|
||||
fun CoroutineScope.LinkedSupervisorScope(
|
||||
additionalContext: CoroutineContext? = null
|
||||
) = CoroutineScope(
|
||||
coroutineContext + LinkedSupervisorJob(additionalContext)
|
||||
)
|
@@ -22,9 +22,9 @@ uuidVersion=0.3.0
|
||||
|
||||
# ANDROID
|
||||
|
||||
core_ktx_version=1.3.2
|
||||
androidx_recycler_version=1.2.0
|
||||
appcompat_version=1.2.0
|
||||
core_ktx_version=1.5.0
|
||||
androidx_recycler_version=1.2.1
|
||||
appcompat_version=1.3.0
|
||||
|
||||
android_minSdkVersion=19
|
||||
android_compileSdkVersion=30
|
||||
@@ -45,5 +45,5 @@ dokka_version=1.4.32
|
||||
# Project data
|
||||
|
||||
group=dev.inmo
|
||||
version=0.5.7
|
||||
android_code_version=48
|
||||
version=0.5.12
|
||||
android_code_version=53
|
||||
|
@@ -8,7 +8,7 @@ import kotlinx.serialization.Contextual
|
||||
data class ApplicationCachingHeadersConfigurator(
|
||||
private val elements: List<@Contextual Element>
|
||||
) : KtorApplicationConfigurator {
|
||||
interface Element { operator fun CachingHeaders.Configuration.invoke() }
|
||||
fun interface Element { operator fun CachingHeaders.Configuration.invoke() }
|
||||
|
||||
override fun Application.configure() {
|
||||
install(CachingHeaders) {
|
||||
|
@@ -10,17 +10,18 @@ import kotlinx.serialization.Serializable
|
||||
class ApplicationRoutingConfigurator(
|
||||
private val elements: List<@Contextual Element>
|
||||
) : KtorApplicationConfigurator {
|
||||
interface Element { operator fun Route.invoke() }
|
||||
fun interface Element { operator fun Route.invoke() }
|
||||
private val rootInstaller = Element {
|
||||
elements.forEach {
|
||||
it.apply { invoke() }
|
||||
}
|
||||
}
|
||||
|
||||
override fun Application.configure() {
|
||||
try {
|
||||
feature(Routing)
|
||||
} catch (e: IllegalStateException) {
|
||||
install(Routing) {
|
||||
elements.forEach {
|
||||
it.apply { invoke() }
|
||||
}
|
||||
}
|
||||
featureOrNull(Routing) ?.apply {
|
||||
rootInstaller.apply { invoke() }
|
||||
} ?: install(Routing) {
|
||||
rootInstaller.apply { invoke() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -8,7 +8,7 @@ import kotlinx.serialization.Contextual
|
||||
class ApplicationSessionsConfigurator(
|
||||
private val elements: List<@Contextual Element>
|
||||
) : KtorApplicationConfigurator {
|
||||
interface Element { operator fun Sessions.Configuration.invoke() }
|
||||
fun interface Element { operator fun Sessions.Configuration.invoke() }
|
||||
|
||||
override fun Application.configure() {
|
||||
install(Sessions) {
|
||||
|
@@ -8,7 +8,7 @@ import kotlinx.serialization.Contextual
|
||||
class StatusPagesConfigurator(
|
||||
private val elements: List<@Contextual Element>
|
||||
) : KtorApplicationConfigurator {
|
||||
interface Element { operator fun StatusPages.Configuration.invoke() }
|
||||
fun interface Element { operator fun StatusPages.Configuration.invoke() }
|
||||
|
||||
override fun Application.configure() {
|
||||
install(StatusPages) {
|
||||
|
@@ -33,3 +33,8 @@ suspend fun <T> doAllWithCurrentPaging(
|
||||
block
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun <T> doForAllWithCurrentPaging(
|
||||
initialPagination: Pagination = FirstPagePagination(),
|
||||
block: suspend (Pagination) -> PaginationResult<T>
|
||||
) = doAllWithCurrentPaging(initialPagination, block)
|
||||
|
@@ -1,6 +1,7 @@
|
||||
package dev.inmo.micro_utils.repos
|
||||
|
||||
import dev.inmo.micro_utils.pagination.*
|
||||
import dev.inmo.micro_utils.pagination.utils.doForAllWithCurrentPaging
|
||||
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@@ -47,6 +48,7 @@ interface WriteOneToManyKeyValueRepo<Key, Value> : Repo {
|
||||
suspend fun remove(toRemove: Map<Key, List<Value>>)
|
||||
|
||||
suspend fun clear(k: Key)
|
||||
suspend fun clearWithValue(v: Value)
|
||||
|
||||
suspend fun set(toSet: Map<Key, List<Value>>) {
|
||||
toSet.keys.forEach { key -> clear(key) }
|
||||
@@ -87,7 +89,19 @@ suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.set(
|
||||
k: Key, vararg v: Value
|
||||
) = set(k, v.toList())
|
||||
|
||||
interface OneToManyKeyValueRepo<Key, Value> : ReadOneToManyKeyValueRepo<Key, Value>, WriteOneToManyKeyValueRepo<Key, Value>
|
||||
interface OneToManyKeyValueRepo<Key, Value> : ReadOneToManyKeyValueRepo<Key, Value>, WriteOneToManyKeyValueRepo<Key, Value> {
|
||||
override suspend fun clearWithValue(v: Value) {
|
||||
doWithPagination {
|
||||
val keysResult = keys(v, it)
|
||||
|
||||
if (keysResult.results.isNotEmpty()) {
|
||||
remove(keysResult.results.map { it to listOf(v) })
|
||||
}
|
||||
|
||||
keysResult.currentPageIfNotEmpty()
|
||||
}
|
||||
}
|
||||
}
|
||||
typealias KeyValuesRepo<Key,Value> = OneToManyKeyValueRepo<Key, Value>
|
||||
|
||||
suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.remove(
|
||||
|
@@ -114,6 +114,7 @@ open class MapperWriteOneToManyKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
}
|
||||
|
||||
override suspend fun clear(k: FromKey) = to.clear(k.toOutKey())
|
||||
override suspend fun clearWithValue(v: FromValue) = to.clearWithValue(v.toOutValue())
|
||||
}
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
|
@@ -74,6 +74,19 @@ fun SQLiteDatabase.select(
|
||||
table, columns, selection, selectionArgs, groupBy, having, orderBy, limit
|
||||
)
|
||||
|
||||
fun SQLiteDatabase.selectDistinct(
|
||||
table: String,
|
||||
columns: Array<String>? = null,
|
||||
selection: String? = null,
|
||||
selectionArgs: Array<String>? = null,
|
||||
groupBy: String? = null,
|
||||
having: String? = null,
|
||||
orderBy: String? = null,
|
||||
limit: String? = null
|
||||
) = query(
|
||||
true, table, columns, selection, selectionArgs, groupBy, having, orderBy, limit
|
||||
)
|
||||
|
||||
fun makePlaceholders(count: Int): String {
|
||||
return (0 until count).joinToString { "?" }
|
||||
}
|
||||
|
@@ -3,10 +3,7 @@ package dev.inmo.micro_utils.repos.onetomany
|
||||
import android.database.sqlite.SQLiteOpenHelper
|
||||
import androidx.core.content.contentValuesOf
|
||||
import dev.inmo.micro_utils.common.mapNotNullA
|
||||
import dev.inmo.micro_utils.pagination.FirstPagePagination
|
||||
import dev.inmo.micro_utils.pagination.Pagination
|
||||
import dev.inmo.micro_utils.pagination.PaginationResult
|
||||
import dev.inmo.micro_utils.pagination.createPaginationResult
|
||||
import dev.inmo.micro_utils.pagination.*
|
||||
import dev.inmo.micro_utils.pagination.utils.reverse
|
||||
import dev.inmo.micro_utils.repos.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
@@ -20,10 +17,14 @@ private val internalSerialFormat = Json {
|
||||
ignoreUnknownKeys = true
|
||||
}
|
||||
|
||||
typealias KeyValuesAndroidRepo<Key, Value> = OneToManyAndroidRepo<Key, Value>
|
||||
|
||||
class OneToManyAndroidRepo<Key, Value>(
|
||||
private val tableName: String,
|
||||
private val keySerializer: KSerializer<Key>,
|
||||
private val valueSerializer: KSerializer<Value>,
|
||||
private val keyAsString: Key.() -> String,
|
||||
private val valueAsString: Value.() -> String,
|
||||
private val keyFromString: String.() -> Key,
|
||||
private val valueFromString: String.() -> Value,
|
||||
private val helper: SQLiteOpenHelper
|
||||
) : OneToManyKeyValueRepo<Key, Value> {
|
||||
private val _onNewValue: MutableSharedFlow<Pair<Key, Value>> = MutableSharedFlow()
|
||||
@@ -34,12 +35,9 @@ class OneToManyAndroidRepo<Key, Value>(
|
||||
override val onDataCleared: Flow<Key> = _onDataCleared.asSharedFlow()
|
||||
|
||||
private val idColumnName = "id"
|
||||
private val idColumnArray = arrayOf(idColumnName)
|
||||
private val valueColumnName = "value"
|
||||
|
||||
private fun Key.asId() = internalSerialFormat.encodeToString(keySerializer, this)
|
||||
private fun Value.asValue() = internalSerialFormat.encodeToString(valueSerializer, this)
|
||||
private fun String.asValue(): Value = internalSerialFormat.decodeFromString(valueSerializer, this)
|
||||
private fun String.asKey(): Key = internalSerialFormat.decodeFromString(keySerializer, this)
|
||||
private val valueColumnArray = arrayOf(valueColumnName)
|
||||
|
||||
init {
|
||||
helper.blockingWritableTransaction {
|
||||
@@ -57,12 +55,23 @@ class OneToManyAndroidRepo<Key, Value>(
|
||||
helper.blockingWritableTransaction {
|
||||
for ((k, values) in toAdd) {
|
||||
values.forEach { v ->
|
||||
val kAsString = k.keyAsString()
|
||||
val vAsString = v.valueAsString()
|
||||
val isThere = select(tableName,
|
||||
null,
|
||||
"$idColumnName=? AND $valueColumnName=?",
|
||||
arrayOf(kAsString, vAsString),
|
||||
limit = limitClause(1)
|
||||
).use { it.moveToFirst() }
|
||||
if (isThere) {
|
||||
return@forEach
|
||||
}
|
||||
insert(
|
||||
tableName,
|
||||
null,
|
||||
contentValuesOf(
|
||||
idColumnName to k.asId(),
|
||||
valueColumnName to v.asValue()
|
||||
idColumnName to k.keyAsString(),
|
||||
valueColumnName to v.valueAsString()
|
||||
)
|
||||
).also {
|
||||
if (it != -1L) {
|
||||
@@ -77,7 +86,7 @@ class OneToManyAndroidRepo<Key, Value>(
|
||||
|
||||
override suspend fun clear(k: Key) {
|
||||
helper.blockingWritableTransaction {
|
||||
delete(tableName, "$idColumnName=?", arrayOf(k.asId()))
|
||||
delete(tableName, "$idColumnName=?", arrayOf(k.keyAsString()))
|
||||
}.also {
|
||||
if (it > 0) {
|
||||
_onDataCleared.emit(k)
|
||||
@@ -88,7 +97,7 @@ class OneToManyAndroidRepo<Key, Value>(
|
||||
override suspend fun set(toSet: Map<Key, List<Value>>) {
|
||||
val (clearedKeys, inserted) = helper.blockingWritableTransaction {
|
||||
toSet.mapNotNull { (k, _) ->
|
||||
if (delete(tableName, "$idColumnName=?", arrayOf(k.asId())) > 0) {
|
||||
if (delete(tableName, "$idColumnName=?", arrayOf(k.keyAsString())) > 0) {
|
||||
k
|
||||
} else {
|
||||
null
|
||||
@@ -98,7 +107,7 @@ class OneToManyAndroidRepo<Key, Value>(
|
||||
insert(
|
||||
tableName,
|
||||
null,
|
||||
contentValuesOf(idColumnName to k.asId(), valueColumnName to v.asValue())
|
||||
contentValuesOf(idColumnName to k.keyAsString(), valueColumnName to v.valueAsString())
|
||||
)
|
||||
k to v
|
||||
}
|
||||
@@ -109,7 +118,7 @@ class OneToManyAndroidRepo<Key, Value>(
|
||||
}
|
||||
|
||||
override suspend fun contains(k: Key): Boolean = helper.blockingReadableTransaction {
|
||||
select(tableName, selection = "$idColumnName=?", selectionArgs = arrayOf(k.asId()), limit = FirstPagePagination(1).limitClause()).use {
|
||||
select(tableName, selection = "$idColumnName=?", selectionArgs = arrayOf(k.keyAsString()), limit = firstPageWithOneElementPagination.limitClause()).use {
|
||||
it.count > 0
|
||||
}
|
||||
}
|
||||
@@ -118,14 +127,14 @@ class OneToManyAndroidRepo<Key, Value>(
|
||||
select(
|
||||
tableName,
|
||||
selection = "$idColumnName=? AND $valueColumnName=?",
|
||||
selectionArgs = arrayOf(k.asId(), v.asValue()),
|
||||
selectionArgs = arrayOf(k.keyAsString(), v.valueAsString()),
|
||||
limit = FirstPagePagination(1).limitClause()
|
||||
).use {
|
||||
it.count > 0
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun count(): Long =helper.blockingReadableTransaction {
|
||||
override suspend fun count(): Long = helper.blockingReadableTransaction {
|
||||
select(
|
||||
tableName
|
||||
).use {
|
||||
@@ -134,7 +143,7 @@ class OneToManyAndroidRepo<Key, Value>(
|
||||
}.toLong()
|
||||
|
||||
override suspend fun count(k: Key): Long = helper.blockingReadableTransaction {
|
||||
select(tableName, selection = "$idColumnName=?", selectionArgs = arrayOf(k.asId()), limit = FirstPagePagination(1).limitClause()).use {
|
||||
selectDistinct(tableName, columns = valueColumnArray, selection = "$idColumnName=?", selectionArgs = arrayOf(k.keyAsString()), limit = FirstPagePagination(1).limitClause()).use {
|
||||
it.count
|
||||
}
|
||||
}.toLong()
|
||||
@@ -144,18 +153,25 @@ class OneToManyAndroidRepo<Key, Value>(
|
||||
pagination: Pagination,
|
||||
reversed: Boolean
|
||||
): PaginationResult<Value> = count(k).let { count ->
|
||||
if (pagination.firstIndex >= count) {
|
||||
return@let emptyList<Value>().createPaginationResult(
|
||||
pagination,
|
||||
count
|
||||
)
|
||||
}
|
||||
val resultPagination = pagination.let { if (reversed) pagination.reverse(count) else pagination }
|
||||
helper.blockingReadableTransaction {
|
||||
select(
|
||||
tableName,
|
||||
valueColumnArray,
|
||||
selection = "$idColumnName=?",
|
||||
selectionArgs = arrayOf(k.asId()),
|
||||
selectionArgs = arrayOf(k.keyAsString()),
|
||||
limit = resultPagination.limitClause()
|
||||
).use { c ->
|
||||
mutableListOf<Value>().also {
|
||||
if (c.moveToFirst()) {
|
||||
do {
|
||||
it.add(c.getString(valueColumnName).asValue())
|
||||
it.add(c.getString(valueColumnName).valueFromString())
|
||||
} while (c.moveToNext())
|
||||
}
|
||||
}
|
||||
@@ -170,16 +186,23 @@ class OneToManyAndroidRepo<Key, Value>(
|
||||
pagination: Pagination,
|
||||
reversed: Boolean
|
||||
): PaginationResult<Key> = count().let { count ->
|
||||
if (pagination.firstIndex >= count) {
|
||||
return@let emptyList<Key>().createPaginationResult(
|
||||
pagination,
|
||||
count
|
||||
)
|
||||
}
|
||||
val resultPagination = pagination.let { if (reversed) pagination.reverse(count) else pagination }
|
||||
helper.blockingReadableTransaction {
|
||||
select(
|
||||
selectDistinct(
|
||||
tableName,
|
||||
idColumnArray,
|
||||
limit = resultPagination.limitClause()
|
||||
).use { c ->
|
||||
mutableListOf<Key>().also {
|
||||
if (c.moveToFirst()) {
|
||||
do {
|
||||
it.add(c.getString(idColumnName).asKey())
|
||||
it.add(c.getString(idColumnName).keyFromString())
|
||||
} while (c.moveToNext())
|
||||
}
|
||||
}
|
||||
@@ -197,16 +220,17 @@ class OneToManyAndroidRepo<Key, Value>(
|
||||
): PaginationResult<Key> = count().let { count ->
|
||||
val resultPagination = pagination.let { if (reversed) pagination.reverse(count) else pagination }
|
||||
helper.blockingReadableTransaction {
|
||||
select(
|
||||
selectDistinct(
|
||||
tableName,
|
||||
idColumnArray,
|
||||
selection = "$valueColumnName=?",
|
||||
selectionArgs = arrayOf(v.asValue()),
|
||||
selectionArgs = arrayOf(v.valueAsString()),
|
||||
limit = resultPagination.limitClause()
|
||||
).use { c ->
|
||||
mutableListOf<Key>().also {
|
||||
if (c.moveToFirst()) {
|
||||
do {
|
||||
it.add(c.getString(idColumnName).asKey())
|
||||
it.add(c.getString(idColumnName).keyFromString())
|
||||
} while (c.moveToNext())
|
||||
}
|
||||
}
|
||||
@@ -221,7 +245,7 @@ class OneToManyAndroidRepo<Key, Value>(
|
||||
helper.blockingWritableTransaction {
|
||||
toRemove.flatMap { (k, vs) ->
|
||||
vs.mapNotNullA { v ->
|
||||
if (delete(tableName, "$idColumnName=? AND $valueColumnName=?", arrayOf(k.asId(), v.asValue())) > 0) {
|
||||
if (delete(tableName, "$idColumnName=? AND $valueColumnName=?", arrayOf(k.keyAsString(), v.valueAsString())) > 0) {
|
||||
k to v
|
||||
} else {
|
||||
null
|
||||
@@ -233,3 +257,24 @@ class OneToManyAndroidRepo<Key, Value>(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun <Key, Value> OneToManyAndroidRepo(
|
||||
tableName: String,
|
||||
keySerializer: KSerializer<Key>,
|
||||
valueSerializer: KSerializer<Value>,
|
||||
helper: SQLiteOpenHelper
|
||||
) = OneToManyAndroidRepo(
|
||||
tableName,
|
||||
{ internalSerialFormat.encodeToString(keySerializer, this) },
|
||||
{ internalSerialFormat.encodeToString(valueSerializer, this) },
|
||||
{ internalSerialFormat.decodeFromString(keySerializer, this) },
|
||||
{ internalSerialFormat.decodeFromString(valueSerializer, this) },
|
||||
helper
|
||||
)
|
||||
|
||||
fun <Key, Value> KeyValuesAndroidRepo(
|
||||
tableName: String,
|
||||
keySerializer: KSerializer<Key>,
|
||||
valueSerializer: KSerializer<Value>,
|
||||
helper: SQLiteOpenHelper
|
||||
) = OneToManyAndroidRepo(tableName, keySerializer, valueSerializer, helper)
|
||||
|
@@ -31,6 +31,9 @@ open class ExposedOneToManyKeyValueRepo<Key, Value>(
|
||||
transaction(database) {
|
||||
toAdd.keys.flatMap { k ->
|
||||
toAdd[k] ?.mapNotNull { v ->
|
||||
if (select { keyColumn.eq(k).and(valueColumn.eq(v)) }.limit(1).count() > 0) {
|
||||
return@mapNotNull null
|
||||
}
|
||||
insertIgnore {
|
||||
it[keyColumn] = k
|
||||
it[valueColumn] = v
|
||||
|
@@ -86,6 +86,12 @@ class MapWriteOneToManyKeyValueRepo<Key, Value>(
|
||||
override suspend fun clear(k: Key) {
|
||||
map.remove(k) ?.also { _onDataCleared.emit(k) }
|
||||
}
|
||||
|
||||
override suspend fun clearWithValue(v: Value) {
|
||||
map.forEach { (k, values) ->
|
||||
if (values.remove(v)) _onValueRemoved.emit(k to v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MapOneToManyKeyValueRepo<Key, Value>(
|
||||
|
@@ -67,6 +67,15 @@ class KtorWriteOneToManyKeyValueRepo<Key, Value> (
|
||||
Unit.serializer(),
|
||||
)
|
||||
|
||||
override suspend fun clearWithValue(v: Value) = unifiedRequester.unipost(
|
||||
buildStandardUrl(
|
||||
baseUrl,
|
||||
clearWithValueRoute,
|
||||
),
|
||||
BodyPair(valueSerializer, v),
|
||||
Unit.serializer(),
|
||||
)
|
||||
|
||||
override suspend fun set(toSet: Map<Key, List<Value>>) = unifiedRequester.unipost(
|
||||
buildStandardUrl(
|
||||
baseUrl,
|
||||
@@ -75,4 +84,4 @@ class KtorWriteOneToManyKeyValueRepo<Key, Value> (
|
||||
BodyPair(keyValueMapSerializer, toSet),
|
||||
Unit.serializer(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@@ -14,4 +14,5 @@ const val onDataClearedRoute = "onDataCleared"
|
||||
const val addRoute = "add"
|
||||
const val removeRoute = "remove"
|
||||
const val clearRoute = "clear"
|
||||
const val setRoute = "set"
|
||||
const val clearWithValueRoute = "clearWithValue"
|
||||
const val setRoute = "set"
|
||||
|
@@ -72,6 +72,17 @@ fun <Key, Value> Route.configureOneToManyWriteKeyValueRepoRoutes(
|
||||
}
|
||||
}
|
||||
|
||||
post(clearWithValueRoute) {
|
||||
unifiedRouter.apply {
|
||||
val v = uniload(valueSerializer)
|
||||
|
||||
unianswer(
|
||||
Unit.serializer(),
|
||||
originalRepo.clearWithValue(v),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
post(setRoute) {
|
||||
unifiedRouter.apply {
|
||||
val obj = uniload(keyValueMapSerializer)
|
||||
|
Reference in New Issue
Block a user