mirror of
https://github.com/InsanusMokrassar/MicroUtils.git
synced 2025-09-18 06:49:20 +00:00
Compare commits
106 Commits
Author | SHA1 | Date | |
---|---|---|---|
71ff0232aa | |||
63921cd984 | |||
051e03bed3 | |||
a051394f4f | |||
b872babe45 | |||
5a9cabc4bd | |||
3ba630684a | |||
498cd12f94 | |||
062848f2e4 | |||
d4b4547718 | |||
22cd440dd7 | |||
6fc64526d4 | |||
08075dfafe | |||
efcb25622e | |||
5ebf29d1fb | |||
b7d0ce3c97 | |||
e20929aec4 | |||
f74167cc65 | |||
c55cd5342e | |||
237d89d611 | |||
aff9b52f1c | |||
d8cc6ed06e | |||
dffafa54c3 | |||
776a3af497 | |||
5b3d9e8d64 | |||
90b7d74a0c | |||
5b596c76e0 | |||
c59e601e2e | |||
5ce71ee6f6 | |||
97dadf517a | |||
a739e874bf | |||
c73cb14615 | |||
d9464f7b90 | |||
aeee41680e | |||
c914f8c44a | |||
abfda3627c | |||
5b5bfa02db | |||
0fdb072385 | |||
7b4c9d59b0 | |||
bd5923716f | |||
64cc42a23b | |||
325f178763 | |||
fc8d0e52ef | |||
e58348907e | |||
eaba9173ae | |||
2f42b30f87 | |||
35913b95be | |||
8023fa1b76 | |||
4cbe2d1d61 | |||
740036e8d9 | |||
273a7aa9a4 | |||
dccb479c3c | |||
6f5c6e5ebe | |||
8495cc6263 | |||
d242d8dcf4 | |||
864d576e70 | |||
f0127b018e | |||
5eb48a58bf | |||
b690f68c7f | |||
7f19b83828 | |||
98a1ec82db | |||
63313ff964 | |||
210e32bed4 | |||
5b325a8ff9 | |||
4582e0c817 | |||
e2d1c5d6a1 | |||
4019ad7d31 | |||
e7df21e91a | |||
b2e30c9f6d | |||
0b27b5cc06 | |||
347e9c32fe | |||
cc18f58e4c | |||
dbd2e963a1 | |||
6928ca5329 | |||
9ece160aa8 | |||
6115c1bcac | |||
e026e94cbf | |||
56cdd8d6af | |||
899e6760e1 | |||
864d0ffcc6 | |||
c6f417f8c8 | |||
9091fa5bd8 | |||
418af7874d | |||
4972cc9daa | |||
ff905e1491 | |||
64b0184a17 | |||
97cbe44fb5 | |||
452d8778c5 | |||
02ad1a748e | |||
c5a32e2b1b | |||
d34c5d845a | |||
82b39baada | |||
712ed06b69 | |||
3365c63e07 | |||
702300f761 | |||
efcaa08b70 | |||
7f7369b3a8 | |||
45a9a95888 | |||
90c1731bd1 | |||
893fd1ac07 | |||
9cac05f98b | |||
3babc94842 | |||
87230d010c | |||
f8a8808508 | |||
f44174b5b3 | |||
f6eb339cd0 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -10,3 +10,6 @@ build/
|
|||||||
out/
|
out/
|
||||||
|
|
||||||
secret.gradle
|
secret.gradle
|
||||||
|
local.properties
|
||||||
|
|
||||||
|
publishing.sh
|
||||||
|
15
.travis.yml
15
.travis.yml
@@ -1,10 +1,23 @@
|
|||||||
language: java
|
language: android
|
||||||
install: true
|
install: true
|
||||||
|
|
||||||
os: linux
|
os: linux
|
||||||
dist: trusty
|
dist: trusty
|
||||||
jdk: oraclejdk8
|
jdk: oraclejdk8
|
||||||
|
|
||||||
|
android:
|
||||||
|
components:
|
||||||
|
- tools
|
||||||
|
- platform-tools
|
||||||
|
- build-tools-30.0.2
|
||||||
|
- android-30
|
||||||
|
- add-on
|
||||||
|
- extra
|
||||||
|
|
||||||
|
before_script:
|
||||||
|
- yes | /usr/local/android-sdk/tools/bin/sdkmanager "build-tools;30.0.2"
|
||||||
|
- yes | /usr/local/android-sdk/tools/bin/sdkmanager "platforms;android-30"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
include:
|
include:
|
||||||
- stage: build
|
- stage: build
|
||||||
|
198
CHANGELOG.md
198
CHANGELOG.md
@@ -1,10 +1,197 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## 0.1.0
|
## 0.4.1
|
||||||
|
|
||||||
Inited :)
|
* `Repos`:
|
||||||
|
* Fixed error in `ExposedKeyValueRepo` related to negative size of shared flow
|
||||||
|
* Fixed error in `ExposedKeyValueRepo` related to avoiding of table initiation
|
||||||
|
|
||||||
### 0.1.1
|
## 0.4.0
|
||||||
|
|
||||||
|
* `Repos`:
|
||||||
|
* `ReadOneToManyKeyValueRepo` got `keys` method with value parameter
|
||||||
|
* All implementations inside of this library has been updated
|
||||||
|
* `ReadStandardKeyValueRepo` got `keys` method with value parameter
|
||||||
|
* All implementations inside of this library has been updated
|
||||||
|
* New extensions `withMapper`
|
||||||
|
|
||||||
|
## 0.3.3
|
||||||
|
|
||||||
|
* `Coroutines`:
|
||||||
|
* New extension `Flow<T>#plus`
|
||||||
|
|
||||||
|
## 0.3.2
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `Coroutines`: `1.4.1` -> `1.4.2`
|
||||||
|
* `Repos`:
|
||||||
|
* `Common`:
|
||||||
|
* New inline function `mapper` for simplier creating of `MapperRepo` objects
|
||||||
|
* Extensions `withMapper` for keyvalue repos and onetomany repos
|
||||||
|
|
||||||
|
## 0.3.1
|
||||||
|
|
||||||
|
**ANDROID PACKAGES**
|
||||||
|
|
||||||
|
* `Android`:
|
||||||
|
* `RecyclerView`:
|
||||||
|
* Library has been created
|
||||||
|
* `Common`
|
||||||
|
* Now available package `dev.inmo:micro_utils.common-android`
|
||||||
|
* `Coroutines`
|
||||||
|
* Now available package `dev.inmo:micro_utils.coroutines-android`
|
||||||
|
* `Ktor`
|
||||||
|
* `Common`
|
||||||
|
* Now available package `dev.inmo:micro_utils.ktor.common-android`
|
||||||
|
* `Client`
|
||||||
|
* Now available package `dev.inmo:micro_utils.ktor.client-android`
|
||||||
|
* `MimeTypes`
|
||||||
|
* Now available package `dev.inmo:micro_utils.mime_types-android`
|
||||||
|
* `Pagination`
|
||||||
|
* `Common`
|
||||||
|
* Now available package `dev.inmo:micro_utils.pagination.common-android`
|
||||||
|
* `Ktor`
|
||||||
|
* `Common`
|
||||||
|
* Now available package `dev.inmo:micro_utils.pagination.ktor.common-android`
|
||||||
|
* `Repos`
|
||||||
|
* `Common`
|
||||||
|
* Now available package `dev.inmo:micro_utils.repos.common-android`
|
||||||
|
* Now it is possible to use default realizations of repos abstractions natively on android
|
||||||
|
* `Inmemory`
|
||||||
|
* Now available package `dev.inmo:micro_utils.repos.inmemory-android`
|
||||||
|
* `Ktor`
|
||||||
|
* `Common`
|
||||||
|
* Now available package `dev.inmo:micro_utils.repos.ktor.common-android`
|
||||||
|
* `Common`
|
||||||
|
* Now available package `dev.inmo:micro_utils.repos.ktor.client-android`
|
||||||
|
|
||||||
|
## 0.3.0
|
||||||
|
|
||||||
|
All deprecations has been removed
|
||||||
|
|
||||||
|
* `Repos`:
|
||||||
|
* `Common`:
|
||||||
|
* `KeyValue` and `OneToMany` repos lost their deprecated methods
|
||||||
|
* `OneToMany` write repos got additional extensions for mutation of repo
|
||||||
|
* `KeyValue` write repos got additional extensions for mutation of repo
|
||||||
|
* New interface `MapperRepo` and new classes which are using this:
|
||||||
|
* `KeyValue`
|
||||||
|
* `MapperReadStandardKeyValueRepo`
|
||||||
|
* `MapperWriteStandardKeyValueRepo`
|
||||||
|
* `MapperStandardKeyValueRepo`
|
||||||
|
* `OneToMany`
|
||||||
|
* `MapperReadOneToManyKeyValueRepo`
|
||||||
|
* `MapperWriteOneToManyKeyValueRepo`
|
||||||
|
* `MapperOneToManyKeyValueRepo`
|
||||||
|
* `Exposed`:
|
||||||
|
* CRUD realizations replaced their channels to shared flows
|
||||||
|
|
||||||
|
## 0.2.7
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `Coroutines`: `1.4.0` -> `1.4.1`
|
||||||
|
* `Repos`:
|
||||||
|
* `WriteStandardKeyValueRepo` got new methods `set` and `unset` with collections
|
||||||
|
* All standard realizations of repos got collections methods realizations
|
||||||
|
* All old usages of `BroadcastFlow` and `BroadcastChannel` has been replaced with `MutableSharedFlow`
|
||||||
|
* `Ktor`:
|
||||||
|
* `Server`:
|
||||||
|
* Fixed incorrect answer for `keyvalue`
|
||||||
|
|
||||||
|
## 0.2.6
|
||||||
|
|
||||||
|
* `Pagination`
|
||||||
|
* Fixes in function `List#paginate`
|
||||||
|
* Extension property `Pagination#lastIndexExclusive`
|
||||||
|
|
||||||
|
## 0.2.5
|
||||||
|
|
||||||
|
* `Coroutines`
|
||||||
|
* Function `safelyWithoutExceptions`
|
||||||
|
* Extension `CoroutineScope#safeActor`
|
||||||
|
|
||||||
|
## 0.2.4
|
||||||
|
|
||||||
|
* `Versions`
|
||||||
|
* `Serialization`: `1.0.0` -> `1.0.1`
|
||||||
|
* `Common`
|
||||||
|
* Full rework of `DiffUtils`
|
||||||
|
* Data class `Diff` has been added
|
||||||
|
* Extension `Iterable#calculateDiff` has been added
|
||||||
|
* Extension `Iterable#calculateStrictDiff` as replacement for `Iterable#calculateDiff` with
|
||||||
|
`strictComparison` mode enabled
|
||||||
|
* Functions `Diff` (as analog of `Iterable#calculateDiff`) and `StrictDiff` (as analog of
|
||||||
|
`Iterable#calculateStrictDiff`)
|
||||||
|
* `Coroutines`
|
||||||
|
* `BroadcastFlow` now is deprecated
|
||||||
|
* `BroadcastStateFlow` now is deprecated
|
||||||
|
* New extensions for `Flow`s:
|
||||||
|
* `Flow#subscribe`
|
||||||
|
* `Flow#subscribeSafely`
|
||||||
|
* `Flow#subscribeSafelyWithoutExceptions`
|
||||||
|
|
||||||
|
## 0.2.3
|
||||||
|
|
||||||
|
* `Versions`
|
||||||
|
* `Coroutines`: `1.3.9` -> `1.4.0`
|
||||||
|
* `Exposed`: `0.27.1` -> `0.28.1`
|
||||||
|
* `Common`
|
||||||
|
* `K/JS`
|
||||||
|
* Add several extensions for `Element` objects to detect that object is on screen viewport
|
||||||
|
* Add several extensions for `Element` objects to detect object visibility
|
||||||
|
* `Coroutines`
|
||||||
|
* `BroadcastStateFlow` now use different strategy for getting of state and implements `replayCache`
|
||||||
|
|
||||||
|
## 0.2.2
|
||||||
|
|
||||||
|
* `Repos`
|
||||||
|
* `Common`
|
||||||
|
* Several new methods `ReadOneToManyKeyValueRepo#getAll`
|
||||||
|
* Several new method `WriteOneToManyKeyValueRepo#add` and several extensions
|
||||||
|
* Several new method `WriteOneToManyKeyValueRepo#remove` and several extensions
|
||||||
|
|
||||||
|
## 0.2.1
|
||||||
|
|
||||||
|
* `Pagination`
|
||||||
|
* `Common`:
|
||||||
|
* Extension `Pagination#reverse` has been added
|
||||||
|
* Factory `PaginationByIndexes`
|
||||||
|
* Shortcut `calculatePagesNumber` with reversed parameters
|
||||||
|
* Value `emptyPagination` for empty `SimplePagination` cases
|
||||||
|
|
||||||
|
## 0.2.0
|
||||||
|
|
||||||
|
* `Repos`
|
||||||
|
* `Exposed`
|
||||||
|
* Now this project depend on `micro_utils.coroutines`
|
||||||
|
* Typealias `ColumnAllocator` has been replaced to root exposed package
|
||||||
|
* Interface `ExposedRepo` has been added
|
||||||
|
* `ExposedCRUDRepo` now extends `ExposedRepo` instead of simple `Repo`
|
||||||
|
* New extension `initTable` for classes which are `Table` and `ExposedRepo` at the same time
|
||||||
|
* `KeyValue`:
|
||||||
|
* `tableName` parameter
|
||||||
|
* Class `AbstractExposedReadKeyValueRepo`
|
||||||
|
* Renamed to `ExposedReadKeyValueRepo`
|
||||||
|
* Changed incoming types to `ColumnAllocator`
|
||||||
|
* `open` instead of `abstract`
|
||||||
|
* Implements `ExposedRepo`
|
||||||
|
* Class `AbstractExposedKeyValueRepo`
|
||||||
|
* Renamed to `ExposedKeyValueRepo`
|
||||||
|
* Changed incoming types to `ColumnAllocator`
|
||||||
|
* `open` instead of `abstract`
|
||||||
|
* `OneToMany`:
|
||||||
|
* `tableName` parameter
|
||||||
|
* Class `AbstractExposedReadOneToManyKeyValueRepo`
|
||||||
|
* Renamed to `ExposedReadOneToManyKeyValueRepo`
|
||||||
|
* Changed incoming arguments order
|
||||||
|
* Implements `ExposedRepo`
|
||||||
|
* Class `AbstractExposedOneToManyKeyValueRepo`
|
||||||
|
* Renamed to `ExposedKeyValueRepo`
|
||||||
|
* Changed incoming arguments order
|
||||||
|
* `open` instead of `abstract`
|
||||||
|
* Release for every `Flow` in parent interfaces
|
||||||
|
|
||||||
|
## 0.1.1
|
||||||
|
|
||||||
* `Versions`:
|
* `Versions`:
|
||||||
* `kotlinx.serialization`: `1.0.0-RC2` -> `1.0.0`
|
* `kotlinx.serialization`: `1.0.0-RC2` -> `1.0.0`
|
||||||
@@ -39,3 +226,8 @@ Inited :)
|
|||||||
* `MapCRUDRepo` class as implementation of `StandardCRUDRepo` on top of `MutableMap` has been added
|
* `MapCRUDRepo` class as implementation of `StandardCRUDRepo` on top of `MutableMap` has been added
|
||||||
* `MapKeyValueRepo` class as implementation of `StandardKeyValueRepo` on top of `MutableMap` has been added
|
* `MapKeyValueRepo` class as implementation of `StandardKeyValueRepo` on top of `MutableMap` has been added
|
||||||
* `MapOneToManyKeyValueRepo` class as implementation of `OneToManyKeyValueRepo` on top of `MutableMap` has been added
|
* `MapOneToManyKeyValueRepo` class as implementation of `OneToManyKeyValueRepo` on top of `MutableMap` has been added
|
||||||
|
|
||||||
|
|
||||||
|
## 0.1.0
|
||||||
|
|
||||||
|
Inited :)
|
23
android/recyclerview/build.gradle
Normal file
23
android/recyclerview/build.gradle
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
plugins {
|
||||||
|
id "org.jetbrains.kotlin.multiplatform"
|
||||||
|
id "org.jetbrains.kotlin.plugin.serialization"
|
||||||
|
id "com.android.library"
|
||||||
|
id "kotlin-android-extensions"
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: "$mppAndroidProjectPresetPath"
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
sourceSets {
|
||||||
|
commonMain {
|
||||||
|
dependencies {
|
||||||
|
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
androidMain {
|
||||||
|
dependencies {
|
||||||
|
api "androidx.recyclerview:recyclerview:$androidx_recycler_version"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1
android/recyclerview/src/main/AndroidManifest.xml
Normal file
1
android/recyclerview/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<manifest package="dev.inmo.micro_utils.android.recyclerview"/>
|
@@ -0,0 +1,22 @@
|
|||||||
|
package dev.inmo.micro_utils.android.recyclerview
|
||||||
|
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
|
||||||
|
abstract class AbstractStandardViewHolder<T>(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
viewId: Int,
|
||||||
|
onViewInflated: ((View) -> Unit)? = null
|
||||||
|
) : AbstractViewHolder<T>(
|
||||||
|
inflater.inflate(viewId, container, false).also {
|
||||||
|
onViewInflated ?.invoke(it)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
constructor(
|
||||||
|
container: ViewGroup,
|
||||||
|
viewId: Int,
|
||||||
|
onViewInflated: ((View) -> Unit)? = null
|
||||||
|
) : this(LayoutInflater.from(container.context), container, viewId, onViewInflated)
|
||||||
|
}
|
@@ -0,0 +1,10 @@
|
|||||||
|
package dev.inmo.micro_utils.android.recyclerview
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
|
||||||
|
abstract class AbstractViewHolder<in T>(
|
||||||
|
view: View
|
||||||
|
) : RecyclerView.ViewHolder(view) {
|
||||||
|
abstract fun onBind(item: T)
|
||||||
|
}
|
@@ -0,0 +1,9 @@
|
|||||||
|
package dev.inmo.micro_utils.android.recyclerview
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import androidx.recyclerview.widget.DividerItemDecoration
|
||||||
|
|
||||||
|
val Context.recyclerViewItemsDecoration
|
||||||
|
get() = DividerItemDecoration(this, LinearLayout.VERTICAL)
|
||||||
|
|
@@ -0,0 +1,47 @@
|
|||||||
|
package dev.inmo.micro_utils.android.recyclerview
|
||||||
|
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.flow.*
|
||||||
|
|
||||||
|
fun RecyclerView.lastVisibleItemFlow(
|
||||||
|
completingScope: CoroutineScope
|
||||||
|
): Flow<Int> {
|
||||||
|
val lastVisibleElementFun: () -> Int = (layoutManager as? LinearLayoutManager) ?.let { it::findLastVisibleItemPosition } ?: error("Currently supported only linear layout manager")
|
||||||
|
val lastVisibleFlow = MutableStateFlow(lastVisibleElementFun())
|
||||||
|
addOnScrollListener(
|
||||||
|
object : RecyclerView.OnScrollListener() {
|
||||||
|
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||||
|
super.onScrolled(recyclerView, dx, dy)
|
||||||
|
lastVisibleFlow.value = lastVisibleElementFun()
|
||||||
|
}
|
||||||
|
}.also { scrollListener ->
|
||||||
|
lastVisibleFlow.onCompletion {
|
||||||
|
removeOnScrollListener(scrollListener)
|
||||||
|
}.launchIn(completingScope)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return lastVisibleFlow.asStateFlow()
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun Flow<Int>.mapLeftItems(
|
||||||
|
crossinline countGetter: () -> Int
|
||||||
|
): Flow<Int> = map { countGetter() - it }
|
||||||
|
|
||||||
|
inline fun Flow<Int>.mapRequireFilling(
|
||||||
|
minimalLeftItems: Int,
|
||||||
|
crossinline countGetter: () -> Int
|
||||||
|
): Flow<Int> = mapLeftItems(countGetter).mapNotNull {
|
||||||
|
if (it < minimalLeftItems) {
|
||||||
|
it
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun RecyclerView.mapRequireFilling(
|
||||||
|
minimalLeftItems: Int,
|
||||||
|
completingScope: CoroutineScope,
|
||||||
|
crossinline countGetter: () -> Int
|
||||||
|
): Flow<Int> = lastVisibleItemFlow(completingScope).mapRequireFilling(minimalLeftItems, countGetter)
|
@@ -0,0 +1,68 @@
|
|||||||
|
package dev.inmo.micro_utils.android.recyclerview
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
|
||||||
|
|
||||||
|
abstract class RecyclerViewAdapter<T>(
|
||||||
|
val data: List<T>
|
||||||
|
): RecyclerView.Adapter<AbstractViewHolder<T>>() {
|
||||||
|
var emptyView: View? = null
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
checkEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
registerAdapterDataObserver(
|
||||||
|
object : RecyclerView.AdapterDataObserver() {
|
||||||
|
override fun onItemRangeChanged(positionStart: Int, itemCount: Int) {
|
||||||
|
super.onItemRangeChanged(positionStart, itemCount)
|
||||||
|
checkEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemRangeChanged(positionStart: Int, itemCount: Int, payload: Any?) {
|
||||||
|
super.onItemRangeChanged(positionStart, itemCount, payload)
|
||||||
|
checkEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onChanged() {
|
||||||
|
super.onChanged()
|
||||||
|
checkEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
|
||||||
|
super.onItemRangeRemoved(positionStart, itemCount)
|
||||||
|
checkEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) {
|
||||||
|
super.onItemRangeMoved(fromPosition, toPosition, itemCount)
|
||||||
|
checkEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
|
||||||
|
super.onItemRangeInserted(positionStart, itemCount)
|
||||||
|
checkEmpty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
checkEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount(): Int = data.size
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: AbstractViewHolder<T>, position: Int) {
|
||||||
|
holder.onBind(data[position])
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkEmpty() {
|
||||||
|
emptyView ?. let {
|
||||||
|
if (data.isEmpty()) {
|
||||||
|
it.visibility = View.VISIBLE
|
||||||
|
} else {
|
||||||
|
it.visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
19
build.gradle
19
build.gradle
@@ -1,24 +1,31 @@
|
|||||||
buildscript {
|
buildscript {
|
||||||
repositories {
|
repositories {
|
||||||
mavenLocal()
|
|
||||||
jcenter()
|
jcenter()
|
||||||
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
mavenLocal()
|
||||||
maven { url "https://plugins.gradle.org/m2/" }
|
maven { url "https://plugins.gradle.org/m2/" }
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
classpath 'com.android.tools.build:gradle:4.0.0'
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
|
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
|
||||||
classpath "com.jfrog.bintray.gradle:gradle-bintray-plugin:$gradle_bintray_plugin_version"
|
classpath "com.jfrog.bintray.gradle:gradle-bintray-plugin:$gradle_bintray_plugin_version"
|
||||||
|
classpath "com.getkeepsafe.dexcount:dexcount-gradle-plugin:$dexcount_version"
|
||||||
classpath "com.github.breadmoirai:github-release:$github_release_plugin_version"
|
classpath "com.github.breadmoirai:github-release:$github_release_plugin_version"
|
||||||
|
classpath "org.jetbrains.dokka:dokka-gradle-plugin:$dokka_version"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
allprojects {
|
||||||
mavenLocal()
|
repositories {
|
||||||
jcenter()
|
mavenLocal()
|
||||||
mavenCentral()
|
jcenter()
|
||||||
maven { url "https://kotlin.bintray.com/kotlinx" }
|
mavenCentral()
|
||||||
|
google()
|
||||||
|
maven { url "https://kotlin.bintray.com/kotlinx" }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "./extensions.gradle"
|
apply from: "./extensions.gradle"
|
||||||
|
@@ -12,4 +12,7 @@ function assert_success() {
|
|||||||
export RELEASE_MODE=true
|
export RELEASE_MODE=true
|
||||||
project="$1"
|
project="$1"
|
||||||
|
|
||||||
assert_success ./gradlew clean "$project:clean" "$project:build" "$project:publishToMavenLocal" "$project:bintrayUpload"
|
assert_success ./gradlew clean
|
||||||
|
assert_success ./gradlew "$project:build"
|
||||||
|
assert_success ./gradlew "$project:publishToMavenLocal"
|
||||||
|
assert_success ./gradlew "$project:bintrayUpload"
|
||||||
|
@@ -1,24 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
function parse() {
|
|
||||||
version=$1
|
|
||||||
|
|
||||||
while IFS= read -r line && [ -z "`echo $line | grep -e "^#\+ $version"`" ]
|
|
||||||
do
|
|
||||||
: # do nothing
|
|
||||||
done
|
|
||||||
|
|
||||||
while IFS= read -r line && [ -z "`echo $line | grep -e "^#\+"`" ]
|
|
||||||
do
|
|
||||||
echo "$line"
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
version=$1
|
|
||||||
file=$2
|
|
||||||
|
|
||||||
if [ -n "$file" ]; then
|
|
||||||
parse $version < "$file"
|
|
||||||
else
|
|
||||||
parse $version
|
|
||||||
fi
|
|
24
changelog_parser.sh
Executable file
24
changelog_parser.sh
Executable file
@@ -0,0 +1,24 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
function parse() {
|
||||||
|
version="$1"
|
||||||
|
|
||||||
|
while IFS= read -r line && [ -z "`echo "$line" | grep -e "^#\+ $version"`" ]
|
||||||
|
do
|
||||||
|
: # do nothing
|
||||||
|
done
|
||||||
|
|
||||||
|
while IFS= read -r line && [ -z "`echo "$line" | grep -e "^#\+"`" ]
|
||||||
|
do
|
||||||
|
echo "$line"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
version="$1"
|
||||||
|
file="$2"
|
||||||
|
|
||||||
|
if [ -n "$file" ]; then
|
||||||
|
parse "$version" < "$file"
|
||||||
|
else
|
||||||
|
parse "$version"
|
||||||
|
fi
|
@@ -1,6 +1,8 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id "org.jetbrains.kotlin.multiplatform"
|
id "org.jetbrains.kotlin.multiplatform"
|
||||||
id "org.jetbrains.kotlin.plugin.serialization"
|
id "org.jetbrains.kotlin.plugin.serialization"
|
||||||
|
id "com.android.library"
|
||||||
|
id "kotlin-android-extensions"
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$mppProjectWithSerializationPresetPath"
|
apply from: "$mppProjectWithSerializationPresetPath"
|
||||||
|
@@ -1,10 +1,151 @@
|
|||||||
|
@file:Suppress("NOTHING_TO_INLINE")
|
||||||
|
|
||||||
package dev.inmo.micro_utils.common
|
package dev.inmo.micro_utils.common
|
||||||
|
|
||||||
fun <T> Iterable<T>.syncWith(
|
private inline fun <T> getObject(
|
||||||
other: Iterable<T>,
|
additional: MutableList<T>,
|
||||||
removed: (List<T>) -> Unit = {},
|
iterator: Iterator<T>
|
||||||
added: (List<T>) -> Unit = {}
|
): T? = when {
|
||||||
) {
|
additional.isNotEmpty() -> additional.removeFirst()
|
||||||
removed(filter { it !in other })
|
iterator.hasNext() -> iterator.next()
|
||||||
added(other.filter { it !in this })
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Diff object which contains information about differences between two [Iterable]s
|
||||||
|
*
|
||||||
|
* @see calculateDiff
|
||||||
|
*/
|
||||||
|
data class Diff<T> internal constructor(
|
||||||
|
val removed: List<IndexedValue<T>>,
|
||||||
|
/**
|
||||||
|
* Old-New values pairs
|
||||||
|
*/
|
||||||
|
val replaced: List<Pair<IndexedValue<T>, IndexedValue<T>>>,
|
||||||
|
val added: List<IndexedValue<T>>
|
||||||
|
)
|
||||||
|
|
||||||
|
private inline fun <T> performChanges(
|
||||||
|
potentialChanges: MutableList<Pair<IndexedValue<T>?, IndexedValue<T>?>>,
|
||||||
|
additionalsInOld: MutableList<T>,
|
||||||
|
additionalsInNew: MutableList<T>,
|
||||||
|
changedList: MutableList<Pair<IndexedValue<T>, IndexedValue<T>>>,
|
||||||
|
removedList: MutableList<IndexedValue<T>>,
|
||||||
|
addedList: MutableList<IndexedValue<T>>,
|
||||||
|
strictComparison: Boolean
|
||||||
|
) {
|
||||||
|
var i = -1
|
||||||
|
val (oldObject, newObject) = potentialChanges.lastOrNull() ?: return
|
||||||
|
for ((old, new) in potentialChanges.take(potentialChanges.size - 1)) {
|
||||||
|
i++
|
||||||
|
val oldOneEqualToNewObject = old ?.value === newObject ?.value || (old ?.value == newObject ?.value && !strictComparison)
|
||||||
|
val newOneEqualToOldObject = new ?.value === oldObject ?.value || (new ?.value == oldObject ?.value && !strictComparison)
|
||||||
|
if (oldOneEqualToNewObject || newOneEqualToOldObject) {
|
||||||
|
changedList.addAll(
|
||||||
|
potentialChanges.take(i).mapNotNull {
|
||||||
|
if (it.first != null && it.second != null) it as Pair<IndexedValue<T>, IndexedValue<T>> else null
|
||||||
|
}
|
||||||
|
)
|
||||||
|
val newPotentials = potentialChanges.drop(i).take(potentialChanges.size - i)
|
||||||
|
when {
|
||||||
|
oldOneEqualToNewObject -> {
|
||||||
|
newPotentials.first().second ?.let { addedList.add(it) }
|
||||||
|
newPotentials.drop(1).take(newPotentials.size - 2).forEach { (oldOne, newOne) ->
|
||||||
|
addedList.add(newOne!!)
|
||||||
|
oldOne ?.let { additionalsInOld.add(oldOne.value) }
|
||||||
|
}
|
||||||
|
if (newPotentials.size > 1) {
|
||||||
|
newPotentials.last().first ?.value ?.let { additionalsInOld.add(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newOneEqualToOldObject -> {
|
||||||
|
newPotentials.first().first ?.let { removedList.add(it) }
|
||||||
|
newPotentials.drop(1).take(newPotentials.size - 2).forEach { (oldOne, newOne) ->
|
||||||
|
removedList.add(oldOne!!)
|
||||||
|
newOne ?.let { additionalsInNew.add(newOne.value) }
|
||||||
|
}
|
||||||
|
if (newPotentials.size > 1) {
|
||||||
|
newPotentials.last().second ?.value ?.let { additionalsInNew.add(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
potentialChanges.clear()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (potentialChanges.isNotEmpty() && potentialChanges.last().let { it.first == null && it.second == null }) {
|
||||||
|
potentialChanges.dropLast(1).forEach { (old, new) ->
|
||||||
|
when {
|
||||||
|
old != null && new != null -> changedList.add(old to new)
|
||||||
|
old != null -> removedList.add(old)
|
||||||
|
new != null -> addedList.add(new)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculating [Diff] object
|
||||||
|
*
|
||||||
|
* @param strictComparison If this parameter set to true, objects which are not equal by links will be used as different
|
||||||
|
* objects. For example, in case of two "Example" string they will be equal by value, but CAN be different by links
|
||||||
|
*/
|
||||||
|
fun <T> Iterable<T>.calculateDiff(
|
||||||
|
other: Iterable<T>,
|
||||||
|
strictComparison: Boolean = false
|
||||||
|
): Diff<T> {
|
||||||
|
var i = -1
|
||||||
|
var j = -1
|
||||||
|
|
||||||
|
val additionalInOld = mutableListOf<T>()
|
||||||
|
val additionalInNew = mutableListOf<T>()
|
||||||
|
|
||||||
|
val oldIterator = iterator()
|
||||||
|
val newIterator = other.iterator()
|
||||||
|
|
||||||
|
val potentiallyChangedObjects = mutableListOf<Pair<IndexedValue<T>?, IndexedValue<T>?>>()
|
||||||
|
val changedObjects = mutableListOf<Pair<IndexedValue<T>, IndexedValue<T>>>()
|
||||||
|
val addedObjects = mutableListOf<IndexedValue<T>>()
|
||||||
|
val removedObjects = mutableListOf<IndexedValue<T>>()
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
i++
|
||||||
|
j++
|
||||||
|
|
||||||
|
val oldObject = getObject(additionalInOld, oldIterator)
|
||||||
|
val newObject = getObject(additionalInNew, newIterator)
|
||||||
|
|
||||||
|
if (oldObject == null && newObject == null) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
when {
|
||||||
|
oldObject === newObject || (oldObject == newObject && !strictComparison) -> {
|
||||||
|
changedObjects.addAll(potentiallyChangedObjects.map { it as Pair<IndexedValue<T>, IndexedValue<T>> })
|
||||||
|
potentiallyChangedObjects.clear()
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
potentiallyChangedObjects.add(oldObject ?.let { IndexedValue(i, oldObject) } to newObject ?.let { IndexedValue(j, newObject) })
|
||||||
|
val previousOldsAdditionsSize = additionalInOld.size
|
||||||
|
val previousNewsAdditionsSize = additionalInNew.size
|
||||||
|
performChanges(potentiallyChangedObjects, additionalInOld, additionalInNew, changedObjects, removedObjects, addedObjects, strictComparison)
|
||||||
|
i -= (additionalInOld.size - previousOldsAdditionsSize)
|
||||||
|
j -= (additionalInNew.size - previousNewsAdditionsSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
potentiallyChangedObjects.add(null to null)
|
||||||
|
performChanges(potentiallyChangedObjects, additionalInOld, additionalInNew, changedObjects, removedObjects, addedObjects, strictComparison)
|
||||||
|
|
||||||
|
return Diff(removedObjects.toList(), changedObjects.toList(), addedObjects.toList())
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <T> Diff(old: Iterable<T>, new: Iterable<T>) = old.calculateDiff(new)
|
||||||
|
inline fun <T> StrictDiff(old: Iterable<T>, new: Iterable<T>) = old.calculateDiff(new, true)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method call [calculateDiff] with strict mode enabled
|
||||||
|
*/
|
||||||
|
inline fun <T> Iterable<T>.calculateStrictDiff(
|
||||||
|
other: Iterable<T>
|
||||||
|
) = calculateDiff(other, strictComparison = true)
|
||||||
|
@@ -0,0 +1,76 @@
|
|||||||
|
package dev.inmo.micro_utils.common
|
||||||
|
|
||||||
|
import kotlin.math.floor
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
class DiffUtilsTests {
|
||||||
|
@Test
|
||||||
|
fun testThatSimpleRemoveWorks() {
|
||||||
|
val oldList = (0 until 10).toList()
|
||||||
|
val withIndex = oldList.withIndex()
|
||||||
|
|
||||||
|
for (count in 1 .. (floor(oldList.size.toFloat() / 2).toInt())) {
|
||||||
|
for ((i, v) in withIndex) {
|
||||||
|
if (i + count > oldList.lastIndex) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
val removedSublist = oldList.subList(i, i + count)
|
||||||
|
oldList.calculateDiff(oldList - removedSublist).apply {
|
||||||
|
assertEquals(
|
||||||
|
removedSublist.mapIndexed { j, o -> IndexedValue(i + j, o) },
|
||||||
|
removed
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testThatSimpleAddWorks() {
|
||||||
|
val oldList = (0 until 10).map { it.toString() }
|
||||||
|
val withIndex = oldList.withIndex()
|
||||||
|
|
||||||
|
for (count in 1 .. (floor(oldList.size.toFloat() / 2).toInt())) {
|
||||||
|
for ((i, v) in withIndex) {
|
||||||
|
if (i + count > oldList.lastIndex) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
val addedSublist = oldList.subList(i, i + count).map { "added$it" }
|
||||||
|
val mutable = oldList.toMutableList()
|
||||||
|
mutable.addAll(i, addedSublist)
|
||||||
|
oldList.calculateDiff(mutable).apply {
|
||||||
|
assertEquals(
|
||||||
|
addedSublist.mapIndexed { j, o -> IndexedValue(i + j, o) },
|
||||||
|
added
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testThatSimpleChangesWorks() {
|
||||||
|
val oldList = (0 until 10).map { it.toString() }
|
||||||
|
val withIndex = oldList.withIndex()
|
||||||
|
|
||||||
|
for (step in 0 until oldList.size) {
|
||||||
|
for ((i, v) in withIndex) {
|
||||||
|
val mutable = oldList.toMutableList()
|
||||||
|
val changes = (
|
||||||
|
if (step == 0) i until oldList.size else (i until oldList.size step step)
|
||||||
|
).map { index ->
|
||||||
|
IndexedValue(index, mutable[index]) to IndexedValue(index, "changed$index").also {
|
||||||
|
mutable[index] = it.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
oldList.calculateDiff(mutable).apply {
|
||||||
|
assertEquals(
|
||||||
|
changes,
|
||||||
|
replaced
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,43 @@
|
|||||||
|
package dev.inmo.micro_utils.common
|
||||||
|
|
||||||
|
import kotlinx.browser.window
|
||||||
|
import org.w3c.dom.DOMRect
|
||||||
|
import org.w3c.dom.Element
|
||||||
|
|
||||||
|
val DOMRect.isOnScreenByLeftEdge: Boolean
|
||||||
|
get() = left >= 0 && left <= window.innerWidth
|
||||||
|
inline val Element.isOnScreenByLeftEdge
|
||||||
|
get() = getBoundingClientRect().isOnScreenByLeftEdge
|
||||||
|
|
||||||
|
val DOMRect.isOnScreenByRightEdge: Boolean
|
||||||
|
get() = right >= 0 && right <= window.innerWidth
|
||||||
|
inline val Element.isOnScreenByRightEdge
|
||||||
|
get() = getBoundingClientRect().isOnScreenByRightEdge
|
||||||
|
|
||||||
|
internal val DOMRect.isOnScreenHorizontally: Boolean
|
||||||
|
get() = isOnScreenByLeftEdge || isOnScreenByRightEdge
|
||||||
|
|
||||||
|
|
||||||
|
val DOMRect.isOnScreenByTopEdge: Boolean
|
||||||
|
get() = top >= 0 && top <= window.innerHeight
|
||||||
|
inline val Element.isOnScreenByTopEdge
|
||||||
|
get() = getBoundingClientRect().isOnScreenByTopEdge
|
||||||
|
|
||||||
|
val DOMRect.isOnScreenByBottomEdge: Boolean
|
||||||
|
get() = bottom >= 0 && bottom <= window.innerHeight
|
||||||
|
inline val Element.isOnScreenByBottomEdge
|
||||||
|
get() = getBoundingClientRect().isOnScreenByBottomEdge
|
||||||
|
|
||||||
|
internal val DOMRect.isOnScreenVertically: Boolean
|
||||||
|
get() = isOnScreenByLeftEdge || isOnScreenByRightEdge
|
||||||
|
|
||||||
|
|
||||||
|
val DOMRect.isOnScreenFully: Boolean
|
||||||
|
get() = isOnScreenByLeftEdge && isOnScreenByTopEdge && isOnScreenByRightEdge && isOnScreenByBottomEdge
|
||||||
|
val Element.isOnScreenFully: Boolean
|
||||||
|
get() = getBoundingClientRect().isOnScreenFully
|
||||||
|
|
||||||
|
val DOMRect.isOnScreen: Boolean
|
||||||
|
get() = isOnScreenFully || (isOnScreenHorizontally && isOnScreenVertically)
|
||||||
|
inline val Element.isOnScreen: Boolean
|
||||||
|
get() = getBoundingClientRect().isOnScreen
|
@@ -0,0 +1,48 @@
|
|||||||
|
package dev.inmo.micro_utils.common
|
||||||
|
|
||||||
|
import kotlinx.browser.window
|
||||||
|
import org.w3c.dom.Element
|
||||||
|
import org.w3c.dom.css.CSSStyleDeclaration
|
||||||
|
|
||||||
|
sealed class Visibility
|
||||||
|
object Visible : Visibility()
|
||||||
|
object Invisible : Visibility()
|
||||||
|
object Gone : Visibility()
|
||||||
|
|
||||||
|
var CSSStyleDeclaration.visibilityState: Visibility
|
||||||
|
get() = when {
|
||||||
|
display == "none" -> Gone
|
||||||
|
visibility == "hidden" -> Invisible
|
||||||
|
else -> Visible
|
||||||
|
}
|
||||||
|
set(value) {
|
||||||
|
when (value) {
|
||||||
|
Visible -> {
|
||||||
|
if (display == "none") {
|
||||||
|
display = "initial"
|
||||||
|
}
|
||||||
|
visibility = "visible"
|
||||||
|
}
|
||||||
|
Invisible -> {
|
||||||
|
if (display == "none") {
|
||||||
|
display = "initial"
|
||||||
|
}
|
||||||
|
visibility = "hidden"
|
||||||
|
}
|
||||||
|
Gone -> {
|
||||||
|
display = "none"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inline var Element.visibilityState: Visibility
|
||||||
|
get() = window.getComputedStyle(this).visibilityState
|
||||||
|
set(value) {
|
||||||
|
window.getComputedStyle(this).visibilityState = value
|
||||||
|
}
|
||||||
|
|
||||||
|
inline val Element.isVisible: Boolean
|
||||||
|
get() = visibilityState == Visible
|
||||||
|
inline val Element.isInvisible: Boolean
|
||||||
|
get() = visibilityState == Invisible
|
||||||
|
inline val Element.isGone: Boolean
|
||||||
|
get() = visibilityState == Gone
|
1
common/src/main/AndroidManifest.xml
Normal file
1
common/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<manifest package="dev.inmo.micro_utils.common"/>
|
@@ -0,0 +1,7 @@
|
|||||||
|
package dev.inmo.micro_utils.common
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST", "SimplifiableCall")
|
||||||
|
inline fun <T, R> Iterable<T>.mapNotNullA(transform: (T) -> R?): List<R> = map(transform).filter { it != null } as List<R>
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST", "SimplifiableCall")
|
||||||
|
inline fun <T, R> Array<T>.mapNotNullA(mapper: (T) -> R?): List<R> = map(mapper).filter { it != null } as List<R>
|
@@ -1,6 +1,8 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id "org.jetbrains.kotlin.multiplatform"
|
id "org.jetbrains.kotlin.multiplatform"
|
||||||
id "org.jetbrains.kotlin.plugin.serialization"
|
id "org.jetbrains.kotlin.plugin.serialization"
|
||||||
|
id "com.android.library"
|
||||||
|
id "kotlin-android-extensions"
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$mppProjectWithSerializationPresetPath"
|
apply from: "$mppProjectWithSerializationPresetPath"
|
||||||
@@ -12,5 +14,10 @@ kotlin {
|
|||||||
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version"
|
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
androidMain {
|
||||||
|
dependencies {
|
||||||
|
api "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -17,3 +17,15 @@ fun <T> CoroutineScope.actor(
|
|||||||
return channel
|
return channel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline fun <T> CoroutineScope.safeActor(
|
||||||
|
channelCapacity: Int = Channel.UNLIMITED,
|
||||||
|
noinline onException: ExceptionHandler<Unit> = {},
|
||||||
|
crossinline block: suspend (T) -> Unit
|
||||||
|
): Channel<T> = actor(
|
||||||
|
channelCapacity
|
||||||
|
) {
|
||||||
|
safely(onException) {
|
||||||
|
block(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -1,21 +0,0 @@
|
|||||||
package dev.inmo.micro_utils.coroutines
|
|
||||||
|
|
||||||
import kotlinx.coroutines.channels.*
|
|
||||||
import kotlinx.coroutines.flow.*
|
|
||||||
|
|
||||||
@Suppress("FunctionName")
|
|
||||||
fun <T> BroadcastFlow(
|
|
||||||
internalChannelSize: Int = Channel.BUFFERED
|
|
||||||
): BroadcastFlow<T> {
|
|
||||||
val channel = BroadcastChannel<T>(internalChannelSize)
|
|
||||||
|
|
||||||
return BroadcastFlow(
|
|
||||||
channel,
|
|
||||||
channel.asFlow()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
class BroadcastFlow<T> internal constructor(
|
|
||||||
private val channel: BroadcastChannel<T>,
|
|
||||||
private val flow: Flow<T>
|
|
||||||
): Flow<T> by flow, SendChannel<T> by channel
|
|
@@ -1,33 +0,0 @@
|
|||||||
package dev.inmo.micro_utils.coroutines
|
|
||||||
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.channels.BroadcastChannel
|
|
||||||
import kotlinx.coroutines.channels.Channel
|
|
||||||
import kotlinx.coroutines.flow.*
|
|
||||||
|
|
||||||
class BroadcastStateFlow<T> internal constructor(
|
|
||||||
parentFlow: Flow<T>,
|
|
||||||
private val stateGetter: () -> T
|
|
||||||
) : StateFlow<T>, Flow<T> by parentFlow {
|
|
||||||
override val value: T
|
|
||||||
get() = stateGetter()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <T> BroadcastChannel<T>.asStateFlow(value: T, scope: CoroutineScope): StateFlow<T> = asFlow().let {
|
|
||||||
var state: T = value
|
|
||||||
it.onEach { state = it }.launchIn(scope)
|
|
||||||
BroadcastStateFlow(it) {
|
|
||||||
state
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <T> BroadcastChannel<T?>.asStateFlow(scope: CoroutineScope): StateFlow<T?> = asStateFlow(null, scope)
|
|
||||||
|
|
||||||
fun <T> broadcastStateFlow(initial: T, scope: CoroutineScope, channelSize: Int = Channel.BUFFERED) = BroadcastChannel<T>(
|
|
||||||
channelSize
|
|
||||||
).let {
|
|
||||||
it to it.asStateFlow(initial, scope)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <T> broadcastStateFlow(scope: CoroutineScope, channelSize: Int = Channel.BUFFERED) = broadcastStateFlow<T?>(null, scope, channelSize)
|
|
||||||
|
|
@@ -0,0 +1,37 @@
|
|||||||
|
@file:Suppress("NOTHING_TO_INLINE")
|
||||||
|
|
||||||
|
package dev.inmo.micro_utils.coroutines
|
||||||
|
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.flow.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shortcut for chain if [Flow.onEach] and [Flow.launchIn]
|
||||||
|
*/
|
||||||
|
inline fun <T> Flow<T>.subscribe(scope: CoroutineScope, noinline block: suspend (T) -> Unit) = onEach(block).launchIn(scope)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use [subscribe], but all [block]s will be called inside of [safely] function.
|
||||||
|
* Use [onException] to set up your reaction for [Throwable]s
|
||||||
|
*/
|
||||||
|
inline fun <T> Flow<T>.subscribeSafely(
|
||||||
|
scope: CoroutineScope,
|
||||||
|
noinline onException: ExceptionHandler<Unit> = { throw it },
|
||||||
|
noinline block: suspend (T) -> Unit
|
||||||
|
) = subscribe(scope) {
|
||||||
|
safely(onException) {
|
||||||
|
block(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use [subscribeSafelyWithoutExceptions], but all exceptions inside of [safely] will be skipped
|
||||||
|
*/
|
||||||
|
inline fun <T> Flow<T>.subscribeSafelyWithoutExceptions(
|
||||||
|
scope: CoroutineScope,
|
||||||
|
noinline block: suspend (T) -> Unit
|
||||||
|
) = subscribeSafely(
|
||||||
|
scope,
|
||||||
|
{},
|
||||||
|
block
|
||||||
|
)
|
@@ -0,0 +1,8 @@
|
|||||||
|
@file:Suppress("NOTHING_TO_INLINE")
|
||||||
|
|
||||||
|
package dev.inmo.micro_utils.coroutines
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.merge
|
||||||
|
|
||||||
|
inline operator fun <T> Flow<T>.plus(other: Flow<T>) = merge(this, other)
|
@@ -21,3 +21,10 @@ suspend inline fun <T> safely(
|
|||||||
onException(e)
|
onException(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shortcut for [safely] without exception handler (instead of this you will receive null as a result)
|
||||||
|
*/
|
||||||
|
suspend inline fun <T> safelyWithoutExceptions(
|
||||||
|
noinline block: suspend CoroutineScope.() -> T
|
||||||
|
): T? = safely({ null }, block)
|
||||||
|
1
coroutines/src/main/AndroidManifest.xml
Normal file
1
coroutines/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<manifest package="dev.inmo.micro_utils.coroutines"/>
|
@@ -0,0 +1,10 @@
|
|||||||
|
package dev.inmo.micro_utils.coroutines
|
||||||
|
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
suspend inline fun <T> doInUI(noinline block: suspend CoroutineScope.() -> T) = withContext(
|
||||||
|
Dispatchers.Main,
|
||||||
|
block
|
||||||
|
)
|
38
defaultAndroidSettings
Normal file
38
defaultAndroidSettings
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
android {
|
||||||
|
compileSdkVersion "$android_compileSdkVersion".toInteger()
|
||||||
|
buildToolsVersion "$android_buildToolsVersion"
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
minSdkVersion "$android_minSdkVersion".toInteger()
|
||||||
|
targetSdkVersion "$android_compileSdkVersion".toInteger()
|
||||||
|
versionCode "${android_code_version}".toInteger()
|
||||||
|
versionName "$version"
|
||||||
|
}
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
minifyEnabled false
|
||||||
|
}
|
||||||
|
debug {
|
||||||
|
debuggable true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
packagingOptions {
|
||||||
|
exclude 'META-INF/kotlinx-serialization-runtime.kotlin_module'
|
||||||
|
exclude 'META-INF/kotlinx-serialization-cbor.kotlin_module'
|
||||||
|
exclude 'META-INF/kotlinx-serialization-properties.kotlin_module'
|
||||||
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
main.java.srcDirs += 'src/main/kotlin'
|
||||||
|
}
|
||||||
|
}
|
135
dokka/build.gradle
Normal file
135
dokka/build.gradle
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
plugins {
|
||||||
|
id "org.jetbrains.kotlin.multiplatform"
|
||||||
|
id "org.jetbrains.kotlin.plugin.serialization"
|
||||||
|
id "com.android.library"
|
||||||
|
id "kotlin-android-extensions"
|
||||||
|
id "org.jetbrains.dokka"
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenLocal()
|
||||||
|
jcenter()
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
jvm()
|
||||||
|
js(BOTH) {
|
||||||
|
browser()
|
||||||
|
nodejs()
|
||||||
|
}
|
||||||
|
android {}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
commonMain {
|
||||||
|
dependencies {
|
||||||
|
implementation kotlin('stdlib')
|
||||||
|
|
||||||
|
project.parent.subprojects.forEach {
|
||||||
|
if (
|
||||||
|
it != project
|
||||||
|
&& it.hasProperty("kotlin")
|
||||||
|
&& it.kotlin.sourceSets.any { it.name.contains("commonMain") }
|
||||||
|
&& it.kotlin.sourceSets.any { it.name.contains("jsMain") }
|
||||||
|
&& it.kotlin.sourceSets.any { it.name.contains("jvmMain") }
|
||||||
|
&& it.kotlin.sourceSets.any { it.name.contains("androidMain") }
|
||||||
|
) {
|
||||||
|
api it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jsMain {
|
||||||
|
dependencies {
|
||||||
|
implementation kotlin('stdlib')
|
||||||
|
|
||||||
|
project.parent.subprojects.forEach {
|
||||||
|
if (
|
||||||
|
it != project
|
||||||
|
&& it.hasProperty("kotlin")
|
||||||
|
&& it.kotlin.sourceSets.any { it.name.contains("commonMain") }
|
||||||
|
&& it.kotlin.sourceSets.any { it.name.contains("jsMain") }
|
||||||
|
) {
|
||||||
|
api it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jvmMain {
|
||||||
|
dependencies {
|
||||||
|
implementation kotlin('stdlib')
|
||||||
|
|
||||||
|
project.parent.subprojects.forEach {
|
||||||
|
if (
|
||||||
|
it != project
|
||||||
|
&& it.hasProperty("kotlin")
|
||||||
|
&& it.kotlin.sourceSets.any { it.name.contains("commonMain") }
|
||||||
|
&& it.kotlin.sourceSets.any { it.name.contains("jvmMain") }
|
||||||
|
) {
|
||||||
|
api it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
androidMain {
|
||||||
|
dependencies {
|
||||||
|
implementation kotlin('stdlib')
|
||||||
|
|
||||||
|
project.parent.subprojects.forEach {
|
||||||
|
if (
|
||||||
|
it != project
|
||||||
|
&& it.hasProperty("kotlin")
|
||||||
|
&& it.kotlin.sourceSets.any { it.name.contains("commonMain") }
|
||||||
|
&& it.kotlin.sourceSets.any { it.name.contains("androidMain") }
|
||||||
|
) {
|
||||||
|
api it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<SourceDirectorySet> findSourcesWithName(String... approximateNames) {
|
||||||
|
return parent.subprojects
|
||||||
|
.findAll { it != project && it.hasProperty("kotlin") }
|
||||||
|
.collectMany { it.kotlin.sourceSets }
|
||||||
|
.findAll { sourceSet ->
|
||||||
|
approximateNames.any { nameToFilter ->
|
||||||
|
sourceSet.name.contains(nameToFilter)
|
||||||
|
}
|
||||||
|
}.collect { it.kotlin }
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.dokkaHtml {
|
||||||
|
dokkaSourceSets {
|
||||||
|
configureEach {
|
||||||
|
skipDeprecated.set(true)
|
||||||
|
|
||||||
|
sourceLink {
|
||||||
|
localDirectory.set(file("./"))
|
||||||
|
remoteUrl.set(new URL("https://github.com/InsanusMokrassar/MicroUtils/blob/master/"))
|
||||||
|
remoteLineSuffix.set("#L")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
named("commonMain") {
|
||||||
|
sourceRoots.setFrom(findSourcesWithName("commonMain"))
|
||||||
|
}
|
||||||
|
|
||||||
|
named("jsMain") {
|
||||||
|
sourceRoots.setFrom(findSourcesWithName("jsMain", "commonMain"))
|
||||||
|
}
|
||||||
|
|
||||||
|
named("jvmMain") {
|
||||||
|
sourceRoots.setFrom(findSourcesWithName("jvmMain", "commonMain"))
|
||||||
|
}
|
||||||
|
|
||||||
|
named("androidMain") {
|
||||||
|
sourceRoots.setFrom(findSourcesWithName("androidMain", "commonMain"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: "$defaultAndroidSettingsPresetPath"
|
1
dokka/src/main/AndroidManifest.xml
Normal file
1
dokka/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<manifest package="dev.inmo.dokka"/>
|
@@ -10,17 +10,21 @@ allprojects {
|
|||||||
}
|
}
|
||||||
|
|
||||||
internalProject = { name ->
|
internalProject = { name ->
|
||||||
if (releaseMode) {
|
// if (releaseMode) {
|
||||||
"$group:$name:$version"
|
// "$group:$name:$version"
|
||||||
} else {
|
// } else {
|
||||||
projectByName("$name")
|
// projectByName("$name")
|
||||||
}
|
// }
|
||||||
|
projectByName("$name")
|
||||||
}
|
}
|
||||||
|
|
||||||
releaseMode = (project.hasProperty('RELEASE_MODE') && project.property('RELEASE_MODE') == "true") || System.getenv('RELEASE_MODE') == "true"
|
releaseMode = (project.hasProperty('RELEASE_MODE') && project.property('RELEASE_MODE') == "true") || System.getenv('RELEASE_MODE') == "true"
|
||||||
|
|
||||||
mppProjectWithSerializationPresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerialization"
|
mppProjectWithSerializationPresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerialization"
|
||||||
mppJavaProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJavaProject"
|
mppJavaProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJavaProject"
|
||||||
|
mppAndroidProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppAndroidProject"
|
||||||
|
|
||||||
|
defaultAndroidSettingsPresetPath = "${rootProject.projectDir.absolutePath}/defaultAndroidSettings"
|
||||||
|
|
||||||
publishGradlePath = "${rootProject.projectDir.absolutePath}/publish.gradle"
|
publishGradlePath = "${rootProject.projectDir.absolutePath}/publish.gradle"
|
||||||
publishMavenPath = "${rootProject.projectDir.absolutePath}/maven.publish.gradle"
|
publishMavenPath = "${rootProject.projectDir.absolutePath}/maven.publish.gradle"
|
||||||
|
@@ -1,8 +1,11 @@
|
|||||||
private String getCurrentVersionChangelog() {
|
private String getCurrentVersionChangelog() {
|
||||||
OutputStream changelogDataOS = new ByteArrayOutputStream()
|
OutputStream changelogDataOS = new ByteArrayOutputStream()
|
||||||
|
exec {
|
||||||
|
commandLine 'chmod', "+x", './changelog_parser.sh'
|
||||||
|
}
|
||||||
exec {
|
exec {
|
||||||
standardOutput = changelogDataOS
|
standardOutput = changelogDataOS
|
||||||
commandLine './changelog_info_retriever', "$library_version", 'CHANGELOG.md'
|
commandLine './changelog_parser.sh', "${project.version}", 'CHANGELOG.md'
|
||||||
}
|
}
|
||||||
|
|
||||||
return changelogDataOS.toString().trim()
|
return changelogDataOS.toString().trim()
|
||||||
@@ -18,9 +21,9 @@ if (new File(projectDir, "secret.gradle").exists()) {
|
|||||||
owner "InsanusMokrassar"
|
owner "InsanusMokrassar"
|
||||||
repo "MicroUtils"
|
repo "MicroUtils"
|
||||||
|
|
||||||
tagName "$library_version"
|
tagName "${project.version}"
|
||||||
releaseName "$library_version"
|
releaseName "${project.version}"
|
||||||
targetCommitish "$library_version"
|
targetCommitish "${project.version}"
|
||||||
|
|
||||||
body getCurrentVersionChangelog()
|
body getCurrentVersionChangelog()
|
||||||
}
|
}
|
||||||
|
@@ -3,13 +3,16 @@ org.gradle.parallel=true
|
|||||||
kotlin.js.generate.externals=true
|
kotlin.js.generate.externals=true
|
||||||
kotlin.incremental=true
|
kotlin.incremental=true
|
||||||
kotlin.incremental.js=true
|
kotlin.incremental.js=true
|
||||||
|
android.useAndroidX=true
|
||||||
|
android.enableJetifier=true
|
||||||
|
org.gradle.jvmargs=-Xmx3072m
|
||||||
|
|
||||||
kotlin_version=1.4.10
|
kotlin_version=1.4.10
|
||||||
kotlin_coroutines_version=1.3.9
|
kotlin_coroutines_version=1.4.1
|
||||||
kotlin_serialisation_core_version=1.0.0
|
kotlin_serialisation_core_version=1.0.1
|
||||||
kotlin_exposed_version=0.27.1
|
kotlin_exposed_version=0.28.1
|
||||||
|
|
||||||
ktor_version=1.4.1
|
ktor_version=1.4.2
|
||||||
|
|
||||||
klockVersion=1.12.1
|
klockVersion=1.12.1
|
||||||
|
|
||||||
@@ -18,5 +21,25 @@ github_release_plugin_version=2.2.12
|
|||||||
|
|
||||||
uuidVersion=0.2.2
|
uuidVersion=0.2.2
|
||||||
|
|
||||||
|
# ANDROID
|
||||||
|
|
||||||
|
core_ktx_version=1.3.2
|
||||||
|
androidx_recycler_version=1.1.0
|
||||||
|
|
||||||
|
android_minSdkVersion=19
|
||||||
|
android_compileSdkVersion=30
|
||||||
|
android_buildToolsVersion=30.0.2
|
||||||
|
dexcount_version=2.0.0-RC1
|
||||||
|
junit_version=4.12
|
||||||
|
test_ext_junit_version=1.1.2
|
||||||
|
espresso_core=3.3.0
|
||||||
|
|
||||||
|
# Dokka
|
||||||
|
|
||||||
|
dokka_version=1.4.0
|
||||||
|
|
||||||
|
# Project data
|
||||||
|
|
||||||
group=dev.inmo
|
group=dev.inmo
|
||||||
version=0.1.1
|
version=0.4.1
|
||||||
|
android_code_version=5
|
||||||
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,5 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id "org.jetbrains.kotlin.multiplatform"
|
id "org.jetbrains.kotlin.multiplatform"
|
||||||
id "org.jetbrains.kotlin.plugin.serialization"
|
id "org.jetbrains.kotlin.plugin.serialization"
|
||||||
|
id "com.android.library"
|
||||||
|
id "kotlin-android-extensions"
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$mppProjectWithSerializationPresetPath"
|
apply from: "$mppProjectWithSerializationPresetPath"
|
||||||
|
@@ -4,7 +4,8 @@ import dev.inmo.micro_utils.coroutines.safely
|
|||||||
import dev.inmo.micro_utils.ktor.common.*
|
import dev.inmo.micro_utils.ktor.common.*
|
||||||
import io.ktor.client.HttpClient
|
import io.ktor.client.HttpClient
|
||||||
import io.ktor.client.features.websocket.ws
|
import io.ktor.client.features.websocket.ws
|
||||||
import io.ktor.http.cio.websocket.*
|
import io.ktor.http.cio.websocket.Frame
|
||||||
|
import io.ktor.http.cio.websocket.readBytes
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.channelFlow
|
import kotlinx.coroutines.flow.channelFlow
|
||||||
import kotlinx.serialization.DeserializationStrategy
|
import kotlinx.serialization.DeserializationStrategy
|
||||||
|
@@ -4,7 +4,8 @@ import dev.inmo.micro_utils.ktor.common.*
|
|||||||
import io.ktor.client.HttpClient
|
import io.ktor.client.HttpClient
|
||||||
import io.ktor.client.request.get
|
import io.ktor.client.request.get
|
||||||
import io.ktor.client.request.post
|
import io.ktor.client.request.post
|
||||||
import kotlinx.serialization.*
|
import kotlinx.serialization.DeserializationStrategy
|
||||||
|
import kotlinx.serialization.SerializationStrategy
|
||||||
|
|
||||||
typealias BodyPair<T> = Pair<SerializationStrategy<T>, T>
|
typealias BodyPair<T> = Pair<SerializationStrategy<T>, T>
|
||||||
|
|
||||||
|
1
ktor/client/src/main/AndroidManifest.xml
Normal file
1
ktor/client/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<manifest package="dev.inmo.micro_utils.ktor.client"/>
|
@@ -1,6 +1,8 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id "org.jetbrains.kotlin.multiplatform"
|
id "org.jetbrains.kotlin.multiplatform"
|
||||||
id "org.jetbrains.kotlin.plugin.serialization"
|
id "org.jetbrains.kotlin.plugin.serialization"
|
||||||
|
id "com.android.library"
|
||||||
|
id "kotlin-android-extensions"
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$mppProjectWithSerializationPresetPath"
|
apply from: "$mppProjectWithSerializationPresetPath"
|
||||||
|
1
ktor/common/src/main/AndroidManifest.xml
Normal file
1
ktor/common/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<manifest package="dev.inmo.micro_utils.ktor.common"/>
|
@@ -22,13 +22,11 @@ fun <T> Route.includeWebsocketHandling(
|
|||||||
converter: (T) -> StandardKtorSerialInputData
|
converter: (T) -> StandardKtorSerialInputData
|
||||||
) {
|
) {
|
||||||
webSocket(suburl) {
|
webSocket(suburl) {
|
||||||
// println("connected")
|
|
||||||
safely {
|
safely {
|
||||||
flow.collect {
|
flow.collect {
|
||||||
send(converter(it))
|
send(converter(it))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// println("disconnected")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -7,8 +7,8 @@ import io.ktor.http.HttpStatusCode
|
|||||||
import io.ktor.request.receive
|
import io.ktor.request.receive
|
||||||
import io.ktor.response.respond
|
import io.ktor.response.respond
|
||||||
import io.ktor.response.respondBytes
|
import io.ktor.response.respondBytes
|
||||||
import io.ktor.util.toByteArray
|
import kotlinx.serialization.DeserializationStrategy
|
||||||
import kotlinx.serialization.*
|
import kotlinx.serialization.SerializationStrategy
|
||||||
|
|
||||||
suspend fun <T> ApplicationCall.unianswer(
|
suspend fun <T> ApplicationCall.unianswer(
|
||||||
answerSerializer: SerializationStrategy<T>,
|
answerSerializer: SerializationStrategy<T>,
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id "org.jetbrains.kotlin.multiplatform"
|
id "org.jetbrains.kotlin.multiplatform"
|
||||||
id "org.jetbrains.kotlin.plugin.serialization"
|
id "org.jetbrains.kotlin.plugin.serialization"
|
||||||
|
id "com.android.library"
|
||||||
|
id "kotlin-android-extensions"
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$mppProjectWithSerializationPresetPath"
|
apply from: "$mppProjectWithSerializationPresetPath"
|
||||||
|
1
mime_types/src/main/AndroidManifest.xml
Normal file
1
mime_types/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<manifest package="dev.inmo.micro_utils.mime_types"/>
|
26
mppAndroidProject
Normal file
26
mppAndroidProject
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
project.version = "$version"
|
||||||
|
project.group = "$group"
|
||||||
|
|
||||||
|
apply from: "$publishGradlePath"
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
android {
|
||||||
|
publishAllLibraryVariants()
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
commonMain {
|
||||||
|
dependencies {
|
||||||
|
implementation kotlin('stdlib')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
commonTest {
|
||||||
|
dependencies {
|
||||||
|
implementation kotlin('test-common')
|
||||||
|
implementation kotlin('test-annotations-common')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: "$defaultAndroidSettingsPresetPath"
|
@@ -3,13 +3,6 @@ project.group = "$group"
|
|||||||
|
|
||||||
apply from: "$publishGradlePath"
|
apply from: "$publishGradlePath"
|
||||||
|
|
||||||
repositories {
|
|
||||||
jcenter()
|
|
||||||
mavenCentral()
|
|
||||||
maven { url "https://kotlin.bintray.com/kotlinx" }
|
|
||||||
mavenLocal()
|
|
||||||
}
|
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
jvm()
|
jvm()
|
||||||
|
|
||||||
|
@@ -3,19 +3,15 @@ project.group = "$group"
|
|||||||
|
|
||||||
apply from: "$publishGradlePath"
|
apply from: "$publishGradlePath"
|
||||||
|
|
||||||
repositories {
|
|
||||||
jcenter()
|
|
||||||
mavenCentral()
|
|
||||||
maven { url "https://kotlin.bintray.com/kotlinx" }
|
|
||||||
mavenLocal()
|
|
||||||
}
|
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
jvm()
|
jvm()
|
||||||
js (BOTH) {
|
js (BOTH) {
|
||||||
browser()
|
browser()
|
||||||
nodejs()
|
nodejs()
|
||||||
}
|
}
|
||||||
|
android {
|
||||||
|
publishAllLibraryVariants()
|
||||||
|
}
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
commonMain {
|
commonMain {
|
||||||
@@ -41,5 +37,14 @@ kotlin {
|
|||||||
implementation kotlin('test-junit')
|
implementation kotlin('test-junit')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
androidTest {
|
||||||
|
dependencies {
|
||||||
|
implementation kotlin('test-junit')
|
||||||
|
implementation "androidx.test.ext:junit:$test_ext_junit_version"
|
||||||
|
implementation "androidx.test.espresso:espresso-core:$espresso_core"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
apply from: "$defaultAndroidSettingsPresetPath"
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id "org.jetbrains.kotlin.multiplatform"
|
id "org.jetbrains.kotlin.multiplatform"
|
||||||
id "org.jetbrains.kotlin.plugin.serialization"
|
id "org.jetbrains.kotlin.plugin.serialization"
|
||||||
|
id "com.android.library"
|
||||||
|
id "kotlin-android-extensions"
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$mppProjectWithSerializationPresetPath"
|
apply from: "$mppProjectWithSerializationPresetPath"
|
||||||
|
@@ -28,6 +28,15 @@ interface Pagination {
|
|||||||
val Pagination.firstIndex: Int
|
val Pagination.firstIndex: Int
|
||||||
get() = page * size
|
get() = page * size
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Last number in index of objects. In fact, one [Pagination] object represent data in next range:
|
||||||
|
*
|
||||||
|
* [[firstIndex], [lastIndex]]; That means, that for [Pagination] with [Pagination.size] == 10 and [Pagination.page] == 1
|
||||||
|
* you will retrieve [Pagination.firstIndex] == 10 and [Pagination.lastIndex] == 19. Here [Pagination.lastIndexExclusive] == 20
|
||||||
|
*/
|
||||||
|
val Pagination.lastIndexExclusive: Int
|
||||||
|
get() = firstIndex + size
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Last number in index of objects. In fact, one [Pagination] object represent data in next range:
|
* Last number in index of objects. In fact, one [Pagination] object represent data in next range:
|
||||||
*
|
*
|
||||||
@@ -35,7 +44,7 @@ val Pagination.firstIndex: Int
|
|||||||
* you will retrieve [Pagination.firstIndex] == 10 and [Pagination.lastIndex] == 19.
|
* you will retrieve [Pagination.firstIndex] == 10 and [Pagination.lastIndex] == 19.
|
||||||
*/
|
*/
|
||||||
val Pagination.lastIndex: Int
|
val Pagination.lastIndex: Int
|
||||||
get() = firstIndex + size - 1
|
get() = lastIndexExclusive - 1
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculates pages count for given [datasetSize]
|
* Calculates pages count for given [datasetSize]
|
||||||
@@ -43,6 +52,11 @@ val Pagination.lastIndex: Int
|
|||||||
fun calculatePagesNumber(datasetSize: Long, pageSize: Int): Int {
|
fun calculatePagesNumber(datasetSize: Long, pageSize: Int): Int {
|
||||||
return ceil(datasetSize.toDouble() / pageSize).toInt()
|
return ceil(datasetSize.toDouble() / pageSize).toInt()
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Calculates pages count for given [datasetSize]. As a fact, it is shortcut for [calculatePagesNumber]
|
||||||
|
* @return calculated page number which can be correctly used in [PaginationResult] as [PaginationResult.page] value
|
||||||
|
*/
|
||||||
|
fun calculatePagesNumber(pageSize: Int, datasetSize: Long): Int = calculatePagesNumber(datasetSize, pageSize)
|
||||||
/**
|
/**
|
||||||
* Calculates pages count for given [datasetSize]
|
* Calculates pages count for given [datasetSize]
|
||||||
*/
|
*/
|
||||||
|
@@ -14,6 +14,8 @@ inline fun FirstPagePagination(size: Int = defaultMediumPageSize) =
|
|||||||
size = size
|
size = size
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val emptyPagination = Pagination(0, 0)
|
||||||
|
|
||||||
@Suppress("NOTHING_TO_INLINE")
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
inline fun Pagination.nextPage() =
|
inline fun Pagination.nextPage() =
|
||||||
SimplePagination(
|
SimplePagination(
|
||||||
@@ -27,7 +29,21 @@ data class SimplePagination(
|
|||||||
override val size: Int
|
override val size: Int
|
||||||
) : Pagination
|
) : Pagination
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory for [SimplePagination]
|
||||||
|
*/
|
||||||
fun Pagination(
|
fun Pagination(
|
||||||
page: Int,
|
page: Int,
|
||||||
size: Int
|
size: Int
|
||||||
) = SimplePagination(page, size)
|
) = SimplePagination(page, size)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param firstIndex Inclusive first index of pagination
|
||||||
|
* @param lastIndex INCLUSIVE last index of pagination (last index of object covered by result [SimplePagination])
|
||||||
|
*/
|
||||||
|
fun PaginationByIndexes(
|
||||||
|
firstIndex: Int,
|
||||||
|
lastIndex: Int
|
||||||
|
) = maxOf(0, (lastIndex - firstIndex + 1)).let { size ->
|
||||||
|
Pagination(calculatePage(firstIndex, size), size)
|
||||||
|
}
|
||||||
|
@@ -21,7 +21,12 @@ fun <T> Iterable<T>.paginate(with: Pagination): PaginationResult<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun <T> List<T>.paginate(with: Pagination): PaginationResult<T> {
|
fun <T> List<T>.paginate(with: Pagination): PaginationResult<T> {
|
||||||
return subList(with.firstIndex, with.lastIndex + 1).createPaginationResult(
|
val firstIndex = maxOf(with.firstIndex, 0)
|
||||||
|
val lastIndex = minOf(with.lastIndexExclusive, size)
|
||||||
|
if (firstIndex > lastIndex) {
|
||||||
|
return emptyPaginationResult()
|
||||||
|
}
|
||||||
|
return subList(firstIndex, lastIndex).createPaginationResult(
|
||||||
with,
|
with,
|
||||||
size.toLong()
|
size.toLong()
|
||||||
)
|
)
|
||||||
|
@@ -0,0 +1,28 @@
|
|||||||
|
package dev.inmo.micro_utils.pagination.utils
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.pagination.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Example:
|
||||||
|
*
|
||||||
|
* * `|__f__l_______________________|` will be transformed to `|_______________________f__l__|`
|
||||||
|
* * `|__f__l_|` will be transformed to `|__f__l_|`
|
||||||
|
*
|
||||||
|
* @return Reversed version of this [Pagination]
|
||||||
|
*/
|
||||||
|
fun Pagination.reverse(datasetSize: Long): SimplePagination {
|
||||||
|
val pagesNumber = calculatePagesNumber(size, datasetSize)
|
||||||
|
val newPage = pagesNumber - page - 1
|
||||||
|
return when {
|
||||||
|
page < 0 || page >= pagesNumber -> emptyPagination
|
||||||
|
else -> Pagination(
|
||||||
|
newPage,
|
||||||
|
size
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shortcut for [reverse]
|
||||||
|
*/
|
||||||
|
fun Pagination.reverse(objectsCount: Int) = reverse(objectsCount.toLong())
|
@@ -0,0 +1,24 @@
|
|||||||
|
package dev.inmo.micro_utils.pagination.utils
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.pagination.*
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
class PaginationReversingTests {
|
||||||
|
@Test
|
||||||
|
fun testThatCommonCaseWorksOk() {
|
||||||
|
val pageSize = 3
|
||||||
|
val collectionSize = 9
|
||||||
|
|
||||||
|
assertEquals(Pagination(-1, pageSize).reverse(collectionSize), Pagination(0, 0))
|
||||||
|
|
||||||
|
val middleFirstIndex = collectionSize / 2 - pageSize / 2
|
||||||
|
val middleLastIndex = middleFirstIndex + pageSize - 1
|
||||||
|
assertEquals(
|
||||||
|
PaginationByIndexes(middleFirstIndex, middleLastIndex).reverse(collectionSize),
|
||||||
|
PaginationByIndexes(middleFirstIndex, middleLastIndex)
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(Pagination(calculatePagesNumber(collectionSize, pageSize), pageSize).reverse(collectionSize), Pagination(0, 0))
|
||||||
|
}
|
||||||
|
}
|
1
pagination/common/src/main/AndroidManifest.xml
Normal file
1
pagination/common/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<manifest package="dev.inmo.micro_utils.pagination.common"/>
|
@@ -1,6 +1,8 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id "org.jetbrains.kotlin.multiplatform"
|
id "org.jetbrains.kotlin.multiplatform"
|
||||||
id "org.jetbrains.kotlin.plugin.serialization"
|
id "org.jetbrains.kotlin.plugin.serialization"
|
||||||
|
id "com.android.library"
|
||||||
|
id "kotlin-android-extensions"
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$mppProjectWithSerializationPresetPath"
|
apply from: "$mppProjectWithSerializationPresetPath"
|
||||||
|
1
pagination/ktor/common/src/main/AndroidManifest.xml
Normal file
1
pagination/ktor/common/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<manifest package="dev.inmo.micro_utils.pagination.ktor.common"/>
|
@@ -13,7 +13,17 @@ bintray {
|
|||||||
if (directorySubname == "kotlinMultiplatform") {
|
if (directorySubname == "kotlinMultiplatform") {
|
||||||
it.setPath("${project.name}/${project.version}/${project.name}-${project.version}.module")
|
it.setPath("${project.name}/${project.version}/${project.name}-${project.version}.module")
|
||||||
} else {
|
} else {
|
||||||
it.setPath("${project.name}-${directorySubname}/${project.version}/${project.name}-${directorySubname}-${project.version}.module")
|
switch (directorySubname) {
|
||||||
|
case "androidRelease":
|
||||||
|
it.setPath("${project.name}-android/${project.version}/${project.name}-android-${project.version}.module")
|
||||||
|
break
|
||||||
|
case "androidDebug":
|
||||||
|
it.setPath("${project.name}-android-debug/${project.version}/${project.name}-android-debug-${project.version}.module")
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
it.setPath("${project.name}-${directorySubname}/${project.version}/${project.name}-${directorySubname}-${project.version}.module")
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (directorySubname == "kotlinMultiplatform" && it.getName() == "pom-default.xml") {
|
if (directorySubname == "kotlinMultiplatform" && it.getName() == "pom-default.xml") {
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id "org.jetbrains.kotlin.multiplatform"
|
id "org.jetbrains.kotlin.multiplatform"
|
||||||
id "org.jetbrains.kotlin.plugin.serialization"
|
id "org.jetbrains.kotlin.plugin.serialization"
|
||||||
|
id "com.android.library"
|
||||||
|
id "kotlin-android-extensions"
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$mppProjectWithSerializationPresetPath"
|
apply from: "$mppProjectWithSerializationPresetPath"
|
||||||
@@ -15,5 +17,13 @@ kotlin {
|
|||||||
api "com.benasher44:uuid:$uuidVersion"
|
api "com.benasher44:uuid:$uuidVersion"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
androidMain {
|
||||||
|
dependencies {
|
||||||
|
api "androidx.core:core-ktx:$core_ktx_version"
|
||||||
|
api internalProject("micro_utils.common")
|
||||||
|
api internalProject("micro_utils.coroutines")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -0,0 +1,21 @@
|
|||||||
|
package dev.inmo.micro_utils.repos
|
||||||
|
|
||||||
|
interface MapperRepo<FromKey, FromValue, ToKey, ToValue> {
|
||||||
|
suspend fun FromKey.toOutKey() = this as ToKey
|
||||||
|
suspend fun FromValue.toOutValue() = this as ToValue
|
||||||
|
|
||||||
|
suspend fun ToKey.toInnerKey() = this as FromKey
|
||||||
|
suspend fun ToValue.toInnerValue() = this as FromValue
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> mapper(
|
||||||
|
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
|
||||||
|
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
|
||||||
|
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
|
||||||
|
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
|
||||||
|
) = object : MapperRepo<FromKey, FromValue, ToKey, ToValue> {
|
||||||
|
override suspend fun FromKey.toOutKey(): ToKey = keyFromToTo()
|
||||||
|
override suspend fun FromValue.toOutValue(): ToValue = valueFromToTo()
|
||||||
|
override suspend fun ToKey.toInnerKey(): FromKey = keyToToFrom()
|
||||||
|
override suspend fun ToValue.toInnerValue(): FromValue = valueToToFrom()
|
||||||
|
}
|
@@ -1,30 +1,86 @@
|
|||||||
package dev.inmo.micro_utils.repos
|
package dev.inmo.micro_utils.repos
|
||||||
|
|
||||||
import dev.inmo.micro_utils.pagination.Pagination
|
import dev.inmo.micro_utils.pagination.*
|
||||||
import dev.inmo.micro_utils.pagination.PaginationResult
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
interface ReadOneToManyKeyValueRepo<Key, Value> : Repo {
|
interface ReadOneToManyKeyValueRepo<Key, Value> : Repo {
|
||||||
suspend fun get(k: Key, pagination: Pagination, reversed: Boolean = false): PaginationResult<Value>
|
suspend fun get(k: Key, pagination: Pagination, reversed: Boolean = false): PaginationResult<Value>
|
||||||
suspend fun keys(pagination: Pagination, reversed: Boolean = false): PaginationResult<Key>
|
suspend fun keys(pagination: Pagination, reversed: Boolean = false): PaginationResult<Key>
|
||||||
|
suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean = false): PaginationResult<Key>
|
||||||
suspend fun contains(k: Key): Boolean
|
suspend fun contains(k: Key): Boolean
|
||||||
suspend fun contains(k: Key, v: Value): Boolean
|
suspend fun contains(k: Key, v: Value): Boolean
|
||||||
suspend fun count(k: Key): Long
|
suspend fun count(k: Key): Long
|
||||||
suspend fun count(): Long
|
suspend fun count(): Long
|
||||||
|
|
||||||
|
suspend fun getAll(k: Key, reversed: Boolean = false): List<Value> = mutableListOf<Value>().also { list ->
|
||||||
|
doWithPagination {
|
||||||
|
get(k, it).also {
|
||||||
|
list.addAll(it.results)
|
||||||
|
}.nextPageIfNotEmpty()
|
||||||
|
}
|
||||||
|
if (reversed) {
|
||||||
|
list.reverse()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WARNING!!! THIS METHOD PROBABLY IS NOT EFFICIENT, USE WITH CAUTION
|
||||||
|
*/
|
||||||
|
suspend fun getAll(reverseLists: Boolean = false): Map<Key, List<Value>> = mutableMapOf<Key, List<Value>>().also { map ->
|
||||||
|
doWithPagination {
|
||||||
|
keys(it).also { paginationResult ->
|
||||||
|
paginationResult.results.forEach { k ->
|
||||||
|
map[k] = getAll(k, reverseLists)
|
||||||
|
}
|
||||||
|
}.nextPageIfNotEmpty()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@Deprecated("Renamed", ReplaceWith("ReadOneToManyKeyValueRepo", "dev.inmo.micro_utils.repos.ReadOneToManyKeyValueRepo"))
|
|
||||||
typealias OneToManyReadKeyValueRepo<Key, Value> = ReadOneToManyKeyValueRepo<Key, Value>
|
|
||||||
|
|
||||||
interface WriteOneToManyKeyValueRepo<Key, Value> : Repo {
|
interface WriteOneToManyKeyValueRepo<Key, Value> : Repo {
|
||||||
val onNewValue: Flow<Pair<Key, Value>>
|
val onNewValue: Flow<Pair<Key, Value>>
|
||||||
val onValueRemoved: Flow<Pair<Key, Value>>
|
val onValueRemoved: Flow<Pair<Key, Value>>
|
||||||
val onDataCleared: Flow<Key>
|
val onDataCleared: Flow<Key>
|
||||||
|
|
||||||
suspend fun add(k: Key, v: Value)
|
suspend fun add(toAdd: Map<Key, List<Value>>)
|
||||||
suspend fun remove(k: Key, v: Value)
|
|
||||||
|
suspend fun remove(toRemove: Map<Key, List<Value>>)
|
||||||
|
|
||||||
suspend fun clear(k: Key)
|
suspend fun clear(k: Key)
|
||||||
}
|
}
|
||||||
@Deprecated("Renamed", ReplaceWith("WriteOneToManyKeyValueRepo", "dev.inmo.micro_utils.repos.WriteOneToManyKeyValueRepo"))
|
|
||||||
typealias OneToManyWriteKeyValueRepo<Key, Value> = WriteOneToManyKeyValueRepo<Key, Value>
|
suspend inline fun <Key, Value, REPO : WriteOneToManyKeyValueRepo<Key, Value>> REPO.add(
|
||||||
|
keysAndValues: List<Pair<Key, List<Value>>>
|
||||||
|
) = add(keysAndValues.toMap())
|
||||||
|
|
||||||
|
suspend inline fun <Key, Value, REPO : WriteOneToManyKeyValueRepo<Key, Value>> REPO.add(
|
||||||
|
vararg keysAndValues: Pair<Key, List<Value>>
|
||||||
|
) = add(keysAndValues.toMap())
|
||||||
|
|
||||||
|
suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.add(
|
||||||
|
k: Key, v: List<Value>
|
||||||
|
) = add(mapOf(k to v))
|
||||||
|
|
||||||
|
suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.add(
|
||||||
|
k: Key, vararg v: Value
|
||||||
|
) = add(k, v.toList())
|
||||||
|
|
||||||
interface OneToManyKeyValueRepo<Key, Value> : ReadOneToManyKeyValueRepo<Key, Value>, WriteOneToManyKeyValueRepo<Key, Value>
|
interface OneToManyKeyValueRepo<Key, Value> : ReadOneToManyKeyValueRepo<Key, Value>, WriteOneToManyKeyValueRepo<Key, Value>
|
||||||
|
|
||||||
|
suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.remove(
|
||||||
|
keysAndValues: List<Pair<Key, List<Value>>>
|
||||||
|
) = remove(keysAndValues.toMap())
|
||||||
|
|
||||||
|
suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.remove(
|
||||||
|
vararg keysAndValues: Pair<Key, List<Value>>
|
||||||
|
) = remove(keysAndValues.toMap())
|
||||||
|
|
||||||
|
suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.remove(
|
||||||
|
k: Key,
|
||||||
|
v: List<Value>
|
||||||
|
) = remove(mapOf(k to v))
|
||||||
|
|
||||||
|
suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.remove(
|
||||||
|
k: Key,
|
||||||
|
vararg v: Value
|
||||||
|
) = remove(k, v.toList())
|
||||||
|
@@ -8,6 +8,7 @@ interface ReadStandardKeyValueRepo<Key, Value> : Repo {
|
|||||||
suspend fun get(k: Key): Value?
|
suspend fun get(k: Key): Value?
|
||||||
suspend fun values(pagination: Pagination, reversed: Boolean = false): PaginationResult<Value>
|
suspend fun values(pagination: Pagination, reversed: Boolean = false): PaginationResult<Value>
|
||||||
suspend fun keys(pagination: Pagination, reversed: Boolean = false): PaginationResult<Key>
|
suspend fun keys(pagination: Pagination, reversed: Boolean = false): PaginationResult<Key>
|
||||||
|
suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean = false): PaginationResult<Key>
|
||||||
suspend fun contains(key: Key): Boolean
|
suspend fun contains(key: Key): Boolean
|
||||||
suspend fun count(): Long
|
suspend fun count(): Long
|
||||||
}
|
}
|
||||||
@@ -16,8 +17,20 @@ interface WriteStandardKeyValueRepo<Key, Value> : Repo {
|
|||||||
val onNewValue: Flow<Pair<Key, Value>>
|
val onNewValue: Flow<Pair<Key, Value>>
|
||||||
val onValueRemoved: Flow<Key>
|
val onValueRemoved: Flow<Key>
|
||||||
|
|
||||||
suspend fun set(k: Key, v: Value)
|
suspend fun set(toSet: Map<Key, Value>)
|
||||||
suspend fun unset(k: Key)
|
suspend fun unset(toUnset: List<Key>)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend inline fun <Key, Value> WriteStandardKeyValueRepo<Key, Value>.set(
|
||||||
|
vararg toSet: Pair<Key, Value>
|
||||||
|
) = set(toSet.toMap())
|
||||||
|
|
||||||
|
suspend inline fun <Key, Value> WriteStandardKeyValueRepo<Key, Value>.set(
|
||||||
|
k: Key, v: Value
|
||||||
|
) = set(k to v)
|
||||||
|
|
||||||
|
suspend inline fun <Key, Value> WriteStandardKeyValueRepo<Key, Value>.unset(
|
||||||
|
vararg k: Key
|
||||||
|
) = unset(k.toList())
|
||||||
|
|
||||||
interface StandardKeyValueRepo<Key, Value> : ReadStandardKeyValueRepo<Key, Value>, WriteStandardKeyValueRepo<Key, Value>
|
interface StandardKeyValueRepo<Key, Value> : ReadStandardKeyValueRepo<Key, Value>, WriteStandardKeyValueRepo<Key, Value>
|
@@ -0,0 +1,146 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.mappers
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.pagination.Pagination
|
||||||
|
import dev.inmo.micro_utils.pagination.PaginationResult
|
||||||
|
import dev.inmo.micro_utils.repos.*
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
|
||||||
|
open class MapperReadStandardKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||||
|
private val to: ReadStandardKeyValueRepo<ToKey, ToValue>,
|
||||||
|
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||||
|
) : ReadStandardKeyValueRepo<FromKey, FromValue>, MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper {
|
||||||
|
override suspend fun get(k: FromKey): FromValue? = to.get(
|
||||||
|
k.toOutKey()
|
||||||
|
) ?.toInnerValue()
|
||||||
|
|
||||||
|
override suspend fun values(
|
||||||
|
pagination: Pagination,
|
||||||
|
reversed: Boolean
|
||||||
|
): PaginationResult<FromValue> = to.values(
|
||||||
|
pagination,
|
||||||
|
reversed
|
||||||
|
).let {
|
||||||
|
PaginationResult(
|
||||||
|
it.page,
|
||||||
|
it.pagesNumber,
|
||||||
|
it.results.map { it.toInnerValue() },
|
||||||
|
it.size
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun keys(
|
||||||
|
pagination: Pagination,
|
||||||
|
reversed: Boolean
|
||||||
|
): PaginationResult<FromKey> = to.keys(
|
||||||
|
pagination,
|
||||||
|
reversed
|
||||||
|
).let {
|
||||||
|
PaginationResult(
|
||||||
|
it.page,
|
||||||
|
it.pagesNumber,
|
||||||
|
it.results.map { it.toInnerKey() },
|
||||||
|
it.size
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun keys(
|
||||||
|
v: FromValue,
|
||||||
|
pagination: Pagination,
|
||||||
|
reversed: Boolean
|
||||||
|
): PaginationResult<FromKey> = to.keys(
|
||||||
|
v.toOutValue(),
|
||||||
|
pagination,
|
||||||
|
reversed
|
||||||
|
).let {
|
||||||
|
PaginationResult(
|
||||||
|
it.page,
|
||||||
|
it.pagesNumber,
|
||||||
|
it.results.map { it.toInnerKey() },
|
||||||
|
it.size
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun contains(key: FromKey): Boolean = to.contains(
|
||||||
|
key.toOutKey()
|
||||||
|
)
|
||||||
|
|
||||||
|
override suspend fun count(): Long = to.count()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
|
inline fun <FromKey, FromValue, ToKey, ToValue> ReadStandardKeyValueRepo<ToKey, ToValue>.withMapper(
|
||||||
|
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||||
|
): ReadStandardKeyValueRepo<FromKey, FromValue> = MapperReadStandardKeyValueRepo(this, mapper)
|
||||||
|
|
||||||
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
|
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> ReadStandardKeyValueRepo<ToKey, ToValue>.withMapper(
|
||||||
|
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
|
||||||
|
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
|
||||||
|
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
|
||||||
|
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
|
||||||
|
): ReadStandardKeyValueRepo<FromKey, FromValue> = withMapper(
|
||||||
|
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
|
||||||
|
)
|
||||||
|
|
||||||
|
open class MapperWriteStandardKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||||
|
private val to: WriteStandardKeyValueRepo<ToKey, ToValue>,
|
||||||
|
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||||
|
) : WriteStandardKeyValueRepo<FromKey, FromValue>, MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper {
|
||||||
|
override val onNewValue: Flow<Pair<FromKey, FromValue>> = to.onNewValue.map { (k, v) ->
|
||||||
|
k.toInnerKey() to v.toInnerValue()
|
||||||
|
}
|
||||||
|
override val onValueRemoved: Flow<FromKey> = to.onValueRemoved.map { k ->
|
||||||
|
k.toInnerKey()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun set(toSet: Map<FromKey, FromValue>) = to.set(
|
||||||
|
toSet.map { (k, v) ->
|
||||||
|
k.toOutKey() to v.toOutValue()
|
||||||
|
}.toMap()
|
||||||
|
)
|
||||||
|
|
||||||
|
override suspend fun unset(toUnset: List<FromKey>) = to.unset(
|
||||||
|
toUnset.map { k ->
|
||||||
|
k.toOutKey()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
|
inline fun <FromKey, FromValue, ToKey, ToValue> WriteStandardKeyValueRepo<ToKey, ToValue>.withMapper(
|
||||||
|
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||||
|
): WriteStandardKeyValueRepo<FromKey, FromValue> = MapperWriteStandardKeyValueRepo(this, mapper)
|
||||||
|
|
||||||
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
|
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> WriteStandardKeyValueRepo<ToKey, ToValue>.withMapper(
|
||||||
|
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
|
||||||
|
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
|
||||||
|
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
|
||||||
|
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
|
||||||
|
): WriteStandardKeyValueRepo<FromKey, FromValue> = withMapper(
|
||||||
|
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
|
||||||
|
)
|
||||||
|
|
||||||
|
open class MapperStandardKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||||
|
private val to: StandardKeyValueRepo<ToKey, ToValue>,
|
||||||
|
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||||
|
) : StandardKeyValueRepo<FromKey, FromValue>,
|
||||||
|
MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper,
|
||||||
|
ReadStandardKeyValueRepo<FromKey, FromValue> by MapperReadStandardKeyValueRepo(to, mapper),
|
||||||
|
WriteStandardKeyValueRepo<FromKey, FromValue> by MapperWriteStandardKeyValueRepo(to, mapper)
|
||||||
|
|
||||||
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
|
inline fun <FromKey, FromValue, ToKey, ToValue> StandardKeyValueRepo<ToKey, ToValue>.withMapper(
|
||||||
|
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||||
|
): StandardKeyValueRepo<FromKey, FromValue> = MapperStandardKeyValueRepo(this, mapper)
|
||||||
|
|
||||||
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
|
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> StandardKeyValueRepo<ToKey, ToValue>.withMapper(
|
||||||
|
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
|
||||||
|
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
|
||||||
|
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
|
||||||
|
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
|
||||||
|
): StandardKeyValueRepo<FromKey, FromValue> = withMapper(
|
||||||
|
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
|
||||||
|
)
|
@@ -0,0 +1,149 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.mappers
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.pagination.Pagination
|
||||||
|
import dev.inmo.micro_utils.pagination.PaginationResult
|
||||||
|
import dev.inmo.micro_utils.repos.*
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
|
||||||
|
open class MapperReadOneToManyKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||||
|
private val to: ReadOneToManyKeyValueRepo<ToKey, ToValue>,
|
||||||
|
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||||
|
) : ReadOneToManyKeyValueRepo<FromKey, FromValue>, MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper {
|
||||||
|
override suspend fun get(
|
||||||
|
k: FromKey,
|
||||||
|
pagination: Pagination,
|
||||||
|
reversed: Boolean
|
||||||
|
): PaginationResult<FromValue> = to.get(
|
||||||
|
k.toOutKey(),
|
||||||
|
pagination,
|
||||||
|
reversed
|
||||||
|
).let {
|
||||||
|
PaginationResult(
|
||||||
|
it.page,
|
||||||
|
it.pagesNumber,
|
||||||
|
it.results.map { it.toInnerValue() },
|
||||||
|
it.size
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun keys(
|
||||||
|
pagination: Pagination,
|
||||||
|
reversed: Boolean
|
||||||
|
): PaginationResult<FromKey> = to.keys(
|
||||||
|
pagination,
|
||||||
|
reversed
|
||||||
|
).let {
|
||||||
|
PaginationResult(
|
||||||
|
it.page,
|
||||||
|
it.pagesNumber,
|
||||||
|
it.results.map { it.toInnerKey() },
|
||||||
|
it.size
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun keys(
|
||||||
|
v: FromValue,
|
||||||
|
pagination: Pagination,
|
||||||
|
reversed: Boolean
|
||||||
|
): PaginationResult<FromKey> = to.keys(
|
||||||
|
v.toOutValue(),
|
||||||
|
pagination,
|
||||||
|
reversed
|
||||||
|
).let {
|
||||||
|
PaginationResult(
|
||||||
|
it.page,
|
||||||
|
it.pagesNumber,
|
||||||
|
it.results.map { it.toInnerKey() },
|
||||||
|
it.size
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun contains(k: FromKey): Boolean = to.contains(k.toOutKey())
|
||||||
|
override suspend fun contains(k: FromKey, v: FromValue): Boolean = to.contains(k.toOutKey(), v.toOutValue())
|
||||||
|
|
||||||
|
override suspend fun count(): Long = to.count()
|
||||||
|
override suspend fun count(k: FromKey): Long = to.count(k.toOutKey())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
|
inline fun <FromKey, FromValue, ToKey, ToValue> ReadOneToManyKeyValueRepo<ToKey, ToValue>.withMapper(
|
||||||
|
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||||
|
): ReadOneToManyKeyValueRepo<FromKey, FromValue> = MapperReadOneToManyKeyValueRepo(this, mapper)
|
||||||
|
|
||||||
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
|
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> ReadOneToManyKeyValueRepo<ToKey, ToValue>.withMapper(
|
||||||
|
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
|
||||||
|
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
|
||||||
|
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
|
||||||
|
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
|
||||||
|
): ReadOneToManyKeyValueRepo<FromKey, FromValue> = withMapper(
|
||||||
|
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
|
||||||
|
)
|
||||||
|
|
||||||
|
open class MapperWriteOneToManyKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||||
|
private val to: WriteOneToManyKeyValueRepo<ToKey, ToValue>,
|
||||||
|
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||||
|
) : WriteOneToManyKeyValueRepo<FromKey, FromValue>, MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper {
|
||||||
|
override val onNewValue: Flow<Pair<FromKey, FromValue>> = to.onNewValue.map { (k, v) ->
|
||||||
|
k.toInnerKey() to v.toInnerValue()
|
||||||
|
}
|
||||||
|
override val onValueRemoved: Flow<Pair<FromKey, FromValue>> = to.onValueRemoved.map { (k, v) ->
|
||||||
|
k.toInnerKey() to v.toInnerValue()
|
||||||
|
}
|
||||||
|
override val onDataCleared: Flow<FromKey> = to.onDataCleared.map { k ->
|
||||||
|
k.toInnerKey()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun add(toAdd: Map<FromKey, List<FromValue>>) = to.add(
|
||||||
|
toAdd.map { (k, v) ->
|
||||||
|
k.toOutKey() to v.map { it.toOutValue() }
|
||||||
|
}.toMap()
|
||||||
|
)
|
||||||
|
|
||||||
|
override suspend fun remove(toRemove: Map<FromKey, List<FromValue>>) = to.remove(
|
||||||
|
toRemove.map { (k, v) ->
|
||||||
|
k.toOutKey() to v.map { it.toOutValue() }
|
||||||
|
}.toMap()
|
||||||
|
)
|
||||||
|
|
||||||
|
override suspend fun clear(k: FromKey) = to.clear(k.toOutKey())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
|
inline fun <FromKey, FromValue, ToKey, ToValue> WriteOneToManyKeyValueRepo<ToKey, ToValue>.withMapper(
|
||||||
|
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||||
|
): WriteOneToManyKeyValueRepo<FromKey, FromValue> = MapperWriteOneToManyKeyValueRepo(this, mapper)
|
||||||
|
|
||||||
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
|
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> WriteOneToManyKeyValueRepo<ToKey, ToValue>.withMapper(
|
||||||
|
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
|
||||||
|
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
|
||||||
|
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
|
||||||
|
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
|
||||||
|
): WriteOneToManyKeyValueRepo<FromKey, FromValue> = withMapper(
|
||||||
|
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
|
||||||
|
)
|
||||||
|
|
||||||
|
open class MapperOneToManyKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||||
|
private val to: OneToManyKeyValueRepo<ToKey, ToValue>,
|
||||||
|
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||||
|
) : OneToManyKeyValueRepo<FromKey, FromValue>,
|
||||||
|
MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper,
|
||||||
|
ReadOneToManyKeyValueRepo<FromKey, FromValue> by MapperReadOneToManyKeyValueRepo(to, mapper),
|
||||||
|
WriteOneToManyKeyValueRepo<FromKey, FromValue> by MapperWriteOneToManyKeyValueRepo(to, mapper)
|
||||||
|
|
||||||
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
|
inline fun <FromKey, FromValue, ToKey, ToValue> OneToManyKeyValueRepo<ToKey, ToValue>.withMapper(
|
||||||
|
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||||
|
): OneToManyKeyValueRepo<FromKey, FromValue> = MapperOneToManyKeyValueRepo(this, mapper)
|
||||||
|
|
||||||
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
|
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> OneToManyKeyValueRepo<ToKey, ToValue>.withMapper(
|
||||||
|
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
|
||||||
|
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
|
||||||
|
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
|
||||||
|
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
|
||||||
|
): OneToManyKeyValueRepo<FromKey, FromValue> = withMapper(
|
||||||
|
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
|
||||||
|
)
|
@@ -1,7 +1,7 @@
|
|||||||
package dev.inmo.micro_utils.repos.pagination
|
package dev.inmo.micro_utils.repos.pagination
|
||||||
|
|
||||||
import dev.inmo.micro_utils.pagination.*
|
import dev.inmo.micro_utils.pagination.*
|
||||||
import dev.inmo.micro_utils.repos.*
|
import dev.inmo.micro_utils.repos.ReadStandardKeyValueRepo
|
||||||
|
|
||||||
suspend inline fun <Key, Value, REPO : ReadStandardKeyValueRepo<Key, Value>> REPO.doForAll(
|
suspend inline fun <Key, Value, REPO : ReadStandardKeyValueRepo<Key, Value>> REPO.doForAll(
|
||||||
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
|
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
package dev.inmo.micro_utils.repos.pagination
|
package dev.inmo.micro_utils.repos.pagination
|
||||||
|
|
||||||
import dev.inmo.micro_utils.pagination.*
|
import dev.inmo.micro_utils.pagination.*
|
||||||
import dev.inmo.micro_utils.repos.*
|
import dev.inmo.micro_utils.repos.ReadOneToManyKeyValueRepo
|
||||||
|
|
||||||
suspend inline fun <Key, Value, REPO : ReadOneToManyKeyValueRepo<Key, Value>> REPO.doForAll(
|
suspend inline fun <Key, Value, REPO : ReadOneToManyKeyValueRepo<Key, Value>> REPO.doForAll(
|
||||||
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
|
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
|
||||||
|
1
repos/common/src/main/AndroidManifest.xml
Normal file
1
repos/common/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<manifest package="dev.inmo.micro_utils.repos.common"/>
|
@@ -0,0 +1,46 @@
|
|||||||
|
package dev.inmo.micro_utils.repos
|
||||||
|
|
||||||
|
sealed class ColumnType(
|
||||||
|
typeName: String,
|
||||||
|
nullable: Boolean
|
||||||
|
) {
|
||||||
|
open val asType: String = "$typeName${if (!nullable) " not null" else ""}"
|
||||||
|
sealed class Text(
|
||||||
|
nullable: Boolean
|
||||||
|
) : ColumnType("text", nullable) {
|
||||||
|
object NULLABLE : Text(true)
|
||||||
|
object NOT_NULLABLE : Text(false)
|
||||||
|
}
|
||||||
|
sealed class Numeric(
|
||||||
|
typeName: String,
|
||||||
|
autoincrement: Boolean = false,
|
||||||
|
primaryKey: Boolean = false,
|
||||||
|
nullable: Boolean = false
|
||||||
|
) : ColumnType(
|
||||||
|
typeName,
|
||||||
|
nullable
|
||||||
|
) {
|
||||||
|
override val asType: String = "${super.asType}${if (primaryKey) " primary key" else ""}${if (autoincrement) " autoincrement" else ""}"
|
||||||
|
|
||||||
|
class INTEGER(
|
||||||
|
autoincrement: Boolean = false,
|
||||||
|
primaryKey: Boolean = false,
|
||||||
|
nullable: Boolean = false
|
||||||
|
) : Numeric(
|
||||||
|
"integer",
|
||||||
|
autoincrement,
|
||||||
|
primaryKey,
|
||||||
|
nullable
|
||||||
|
)
|
||||||
|
class DOUBLE(autoincrement: Boolean = false, primaryKey: Boolean = false, nullable: Boolean = false) : Numeric(
|
||||||
|
"double",
|
||||||
|
autoincrement,
|
||||||
|
primaryKey,
|
||||||
|
nullable
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return asType
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,8 @@
|
|||||||
|
package dev.inmo.micro_utils.repos
|
||||||
|
|
||||||
|
import androidx.core.content.contentValuesOf
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST", "SimplifiableCall")
|
||||||
|
fun contentValuesOfNotNull(vararg pairs: Pair<String, Any?>?) = contentValuesOf(
|
||||||
|
*(pairs.filter { it != null } as List<Pair<String, Any?>>).toTypedArray()
|
||||||
|
)
|
@@ -0,0 +1,21 @@
|
|||||||
|
package dev.inmo.micro_utils.repos
|
||||||
|
|
||||||
|
import android.database.Cursor
|
||||||
|
|
||||||
|
inline fun <T> Cursor.map(
|
||||||
|
block: (Cursor) -> T
|
||||||
|
): List<T> {
|
||||||
|
val result = mutableListOf<T>()
|
||||||
|
if (moveToFirst()) {
|
||||||
|
do {
|
||||||
|
result.add(block(this))
|
||||||
|
} while (moveToNext())
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Cursor.firstOrNull(): Cursor? = if (moveToFirst()) {
|
||||||
|
this
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
@@ -0,0 +1,6 @@
|
|||||||
|
package dev.inmo.micro_utils.repos
|
||||||
|
|
||||||
|
import kotlinx.coroutines.newSingleThreadContext
|
||||||
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
|
val DatabaseCoroutineContext: CoroutineContext = newSingleThreadContext("db-context")
|
@@ -0,0 +1,64 @@
|
|||||||
|
package dev.inmo.micro_utils.repos
|
||||||
|
|
||||||
|
import android.database.Cursor
|
||||||
|
import android.database.sqlite.SQLiteDatabase
|
||||||
|
|
||||||
|
fun createTableQuery(
|
||||||
|
tableName: String,
|
||||||
|
vararg columnsToTypes: Pair<String, ColumnType>
|
||||||
|
) = "create table $tableName (${columnsToTypes.joinToString(", ") { "${it.first} ${it.second}" }});"
|
||||||
|
|
||||||
|
fun SQLiteDatabase.createTable(
|
||||||
|
tableName: String,
|
||||||
|
vararg columnsToTypes: Pair<String, ColumnType>,
|
||||||
|
onInit: (SQLiteDatabase.() -> Unit)? = null
|
||||||
|
): Boolean {
|
||||||
|
val existing = rawQuery("SELECT name FROM sqlite_master WHERE type='table' AND name='$tableName'", null).use {
|
||||||
|
it.count > 0
|
||||||
|
}
|
||||||
|
return if (existing) {
|
||||||
|
false
|
||||||
|
// TODO:: add upgrade opportunity
|
||||||
|
} else {
|
||||||
|
execSQL(createTableQuery(tableName, *columnsToTypes))
|
||||||
|
onInit ?.invoke(this)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Cursor.getString(columnName: String) = getString(
|
||||||
|
getColumnIndex(columnName)
|
||||||
|
)
|
||||||
|
|
||||||
|
fun Cursor.getLong(columnName: String) = getLong(
|
||||||
|
getColumnIndex(columnName)
|
||||||
|
)
|
||||||
|
|
||||||
|
fun Cursor.getInt(columnName: String) = getInt(
|
||||||
|
getColumnIndex(columnName)
|
||||||
|
)
|
||||||
|
|
||||||
|
fun Cursor.getDouble(columnName: String) = getDouble(
|
||||||
|
getColumnIndex(columnName)
|
||||||
|
)
|
||||||
|
|
||||||
|
fun SQLiteDatabase.select(
|
||||||
|
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(
|
||||||
|
table, columns, selection, selectionArgs, groupBy, having, orderBy, limit
|
||||||
|
)
|
||||||
|
|
||||||
|
fun makePlaceholders(count: Int): String {
|
||||||
|
return (0 until count).joinToString { "?" }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun makeStringPlaceholders(count: Int): String {
|
||||||
|
return (0 until count).joinToString { "\"?\"" }
|
||||||
|
}
|
@@ -0,0 +1,8 @@
|
|||||||
|
package dev.inmo.micro_utils.repos
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.pagination.Pagination
|
||||||
|
import dev.inmo.micro_utils.pagination.firstIndex
|
||||||
|
|
||||||
|
fun limitClause(size: Long, since: Long? = null) = "${since ?.let { "$it, " } ?: ""}$size"
|
||||||
|
fun limitClause(size: Int, since: Int? = null) = limitClause(size.toLong(), since ?.toLong())
|
||||||
|
fun Pagination.limitClause() = limitClause(size, firstIndex)
|
@@ -0,0 +1,45 @@
|
|||||||
|
package dev.inmo.micro_utils.repos
|
||||||
|
|
||||||
|
import android.database.sqlite.SQLiteDatabase
|
||||||
|
import dev.inmo.micro_utils.coroutines.safely
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
suspend fun <T> SQLiteDatabase.transaction(block: suspend SQLiteDatabase.() -> T): T {
|
||||||
|
return withContext(DatabaseCoroutineContext) {
|
||||||
|
when {
|
||||||
|
inTransaction() -> {
|
||||||
|
block()
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
beginTransaction()
|
||||||
|
safely(
|
||||||
|
{
|
||||||
|
endTransaction()
|
||||||
|
throw it
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
block().also {
|
||||||
|
setTransactionSuccessful()
|
||||||
|
endTransaction()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <T> SQLiteDatabase.inlineTransaction(block: SQLiteDatabase.() -> T): T {
|
||||||
|
return when {
|
||||||
|
inTransaction() -> block()
|
||||||
|
else -> {
|
||||||
|
beginTransaction()
|
||||||
|
try {
|
||||||
|
block().also {
|
||||||
|
setTransactionSuccessful()
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
endTransaction()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,7 @@
|
|||||||
|
package dev.inmo.micro_utils.repos
|
||||||
|
|
||||||
|
val internalId = "_id"
|
||||||
|
val internalIdType = ColumnType.Numeric.INTEGER(
|
||||||
|
autoincrement = true,
|
||||||
|
primaryKey = true
|
||||||
|
)
|
@@ -0,0 +1,51 @@
|
|||||||
|
package dev.inmo.micro_utils.repos
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.database.DatabaseErrorHandler
|
||||||
|
import android.database.sqlite.SQLiteDatabase
|
||||||
|
import android.database.sqlite.SQLiteOpenHelper
|
||||||
|
import dev.inmo.micro_utils.coroutines.safely
|
||||||
|
import kotlin.coroutines.Continuation
|
||||||
|
import kotlin.coroutines.resume
|
||||||
|
import kotlin.coroutines.resumeWithException
|
||||||
|
|
||||||
|
private data class CallbackContinuationPair<T> (
|
||||||
|
val callback: suspend SQLiteDatabase.() -> T,
|
||||||
|
val continuation: Continuation<T>
|
||||||
|
) {
|
||||||
|
suspend fun SQLiteDatabase.execute() {
|
||||||
|
safely(
|
||||||
|
{
|
||||||
|
continuation.resumeWithException(it)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
continuation.resume(callback())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class StandardSQLHelper(
|
||||||
|
context: Context,
|
||||||
|
name: String,
|
||||||
|
factory: SQLiteDatabase.CursorFactory? = null,
|
||||||
|
version: Int = 1,
|
||||||
|
errorHandler: DatabaseErrorHandler? = null
|
||||||
|
) {
|
||||||
|
val sqlOpenHelper = object : SQLiteOpenHelper(context, name, factory, version, errorHandler) {
|
||||||
|
override fun onCreate(db: SQLiteDatabase?) {}
|
||||||
|
|
||||||
|
override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun <T> writableTransaction(block: suspend SQLiteDatabase.() -> T): T = sqlOpenHelper.writableTransaction(block)
|
||||||
|
|
||||||
|
suspend fun <T> readableTransaction(block: suspend SQLiteDatabase.() -> T): T = sqlOpenHelper.readableTransaction(block)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun <T> SQLiteOpenHelper.writableTransaction(block: suspend SQLiteDatabase.() -> T): T {
|
||||||
|
return writableDatabase.transaction(block)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun <T> SQLiteOpenHelper.readableTransaction(block: suspend SQLiteDatabase.() -> T): T {
|
||||||
|
return readableDatabase.transaction(block)
|
||||||
|
}
|
@@ -0,0 +1,67 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.crud
|
||||||
|
|
||||||
|
import android.database.Cursor
|
||||||
|
import android.database.sqlite.SQLiteDatabase
|
||||||
|
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.repos.*
|
||||||
|
|
||||||
|
val <T> T.asId: String
|
||||||
|
get() = (this as? String) ?: this!!.toString()
|
||||||
|
|
||||||
|
abstract class AbstractAndroidCRUDRepo<ObjectType, IdType>(
|
||||||
|
protected val helper: StandardSQLHelper
|
||||||
|
) : ReadStandardCRUDRepo<ObjectType, IdType> {
|
||||||
|
protected abstract val tableName: String
|
||||||
|
protected abstract val idColumnName: String
|
||||||
|
protected abstract suspend fun Cursor.toObject(): ObjectType
|
||||||
|
protected fun SQLiteDatabase.count(): Long = select(tableName).use {
|
||||||
|
it.count
|
||||||
|
}.toLong()
|
||||||
|
|
||||||
|
override suspend fun contains(id: IdType): Boolean = helper.readableTransaction {
|
||||||
|
select(
|
||||||
|
tableName,
|
||||||
|
null,
|
||||||
|
"$idColumnName=?",
|
||||||
|
arrayOf(id.asId)
|
||||||
|
).use {
|
||||||
|
it.count > 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getById(id: IdType): ObjectType? = helper.readableTransaction {
|
||||||
|
select(
|
||||||
|
tableName,
|
||||||
|
selection = "$idColumnName=?",
|
||||||
|
selectionArgs = arrayOf(id.asId),
|
||||||
|
limit = limitClause(1)
|
||||||
|
).use { c ->
|
||||||
|
if (c.moveToFirst()) {
|
||||||
|
c.toObject()
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getByPagination(pagination: Pagination): PaginationResult<ObjectType> {
|
||||||
|
return helper.readableTransaction {
|
||||||
|
select(
|
||||||
|
tableName,
|
||||||
|
limit = pagination.limitClause()
|
||||||
|
).use {
|
||||||
|
if (it.moveToFirst()) {
|
||||||
|
val resultList = mutableListOf(it.toObject())
|
||||||
|
while (it.moveToNext()) {
|
||||||
|
resultList.add(it.toObject())
|
||||||
|
}
|
||||||
|
resultList.createPaginationResult(pagination, count())
|
||||||
|
} else {
|
||||||
|
emptyList<ObjectType>().createPaginationResult(pagination, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,104 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.crud
|
||||||
|
|
||||||
|
import android.content.ContentValues
|
||||||
|
import dev.inmo.micro_utils.common.mapNotNullA
|
||||||
|
import dev.inmo.micro_utils.repos.*
|
||||||
|
import kotlinx.coroutines.channels.BroadcastChannel
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.asFlow
|
||||||
|
|
||||||
|
abstract class AbstractMutableAndroidCRUDRepo<ObjectType, IdType, InputValueType>(
|
||||||
|
helper: StandardSQLHelper
|
||||||
|
) : WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>,
|
||||||
|
AbstractAndroidCRUDRepo<ObjectType, IdType>(helper) {
|
||||||
|
protected val newObjectsChannel = BroadcastChannel<ObjectType>(64)
|
||||||
|
protected val updateObjectsChannel = BroadcastChannel<ObjectType>(64)
|
||||||
|
protected val deleteObjectsIdsChannel = BroadcastChannel<IdType>(64)
|
||||||
|
override val newObjectsFlow: Flow<ObjectType> = newObjectsChannel.asFlow()
|
||||||
|
override val updatedObjectsFlow: Flow<ObjectType> = updateObjectsChannel.asFlow()
|
||||||
|
override val deletedObjectsIdsFlow: Flow<IdType> = deleteObjectsIdsChannel.asFlow()
|
||||||
|
|
||||||
|
protected abstract suspend fun InputValueType.asContentValues(id: IdType? = null): ContentValues
|
||||||
|
|
||||||
|
override suspend fun create(values: List<InputValueType>): List<ObjectType> {
|
||||||
|
val indexes = helper.writableTransaction {
|
||||||
|
values.map {
|
||||||
|
insert(tableName, null, it.asContentValues())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return helper.readableTransaction {
|
||||||
|
indexes.mapNotNullA { i ->
|
||||||
|
select(
|
||||||
|
tableName,
|
||||||
|
selection = "$internalId=?",
|
||||||
|
selectionArgs = arrayOf(i.toString())
|
||||||
|
).use { c ->
|
||||||
|
if (c.moveToFirst()) {
|
||||||
|
c.toObject()
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.also {
|
||||||
|
it.forEach {
|
||||||
|
newObjectsChannel.send(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun deleteById(ids: List<IdType>) {
|
||||||
|
val deleted = mutableListOf<IdType>()
|
||||||
|
helper.writableTransaction {
|
||||||
|
ids.forEach { id ->
|
||||||
|
delete(tableName, "$idColumnName=?", arrayOf(id.asId)).also {
|
||||||
|
if (it > 0) {
|
||||||
|
deleted.add(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
deleted.forEach {
|
||||||
|
deleteObjectsIdsChannel.send(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun update(id: IdType, value: InputValueType): ObjectType? {
|
||||||
|
val asContentValues = value.asContentValues(id)
|
||||||
|
if (asContentValues.keySet().isNotEmpty()) {
|
||||||
|
helper.writableTransaction {
|
||||||
|
update(
|
||||||
|
tableName,
|
||||||
|
asContentValues,
|
||||||
|
"$idColumnName=?",
|
||||||
|
arrayOf(id.asId)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return getById(id) ?.also {
|
||||||
|
updateObjectsChannel.send(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun update(values: List<UpdatedValuePair<IdType, InputValueType>>): List<ObjectType> {
|
||||||
|
helper.writableTransaction {
|
||||||
|
values.forEach { (id, value) ->
|
||||||
|
update(
|
||||||
|
tableName,
|
||||||
|
value.asContentValues(id),
|
||||||
|
"$idColumnName=?",
|
||||||
|
arrayOf(id.asId)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return values.mapNotNullA {
|
||||||
|
getById(it.first)
|
||||||
|
}.also {
|
||||||
|
it.forEach {
|
||||||
|
updateObjectsChannel.send(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun count(): Long = helper.readableTransaction { select(tableName).use { it.count.toLong() } }
|
||||||
|
}
|
@@ -0,0 +1,144 @@
|
|||||||
|
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.channels.BroadcastChannel
|
||||||
|
import kotlinx.coroutines.channels.Channel
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.asFlow
|
||||||
|
|
||||||
|
private val cache = HashMap<String, KeyValueStore<*>>()
|
||||||
|
|
||||||
|
fun <T : Any> Context.keyValueStore(
|
||||||
|
name: String = "default",
|
||||||
|
cacheValues: Boolean = false
|
||||||
|
): StandardKeyValueRepo<String, T> {
|
||||||
|
return cache.getOrPut(name) {
|
||||||
|
KeyValueStore<T>(this, name, cacheValues)
|
||||||
|
} as KeyValueStore<T>
|
||||||
|
}
|
||||||
|
|
||||||
|
class KeyValueStore<T : Any> internal constructor (
|
||||||
|
c: Context,
|
||||||
|
preferencesName: String,
|
||||||
|
useCache: Boolean = false
|
||||||
|
) : SharedPreferences.OnSharedPreferenceChangeListener, StandardKeyValueRepo<String, T> {
|
||||||
|
private val sharedPreferences = c.getSharedPreferences(preferencesName, Context.MODE_PRIVATE)
|
||||||
|
|
||||||
|
private val cachedData = if (useCache) {
|
||||||
|
mutableMapOf<String, Any>()
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
private val onNewValueChannel = BroadcastChannel<Pair<String, T>>(Channel.BUFFERED)
|
||||||
|
private val onValueRemovedChannel = BroadcastChannel<String>(Channel.BUFFERED)
|
||||||
|
|
||||||
|
override val onNewValue: Flow<Pair<String, T>> = onNewValueChannel.asFlow()
|
||||||
|
override val onValueRemoved: Flow<String> = onValueRemovedChannel.asFlow()
|
||||||
|
|
||||||
|
init {
|
||||||
|
cachedData ?.let {
|
||||||
|
sharedPreferences.all.forEach {
|
||||||
|
if (it.value != null) {
|
||||||
|
cachedData[it.key] = it.value as Any
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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<T> {
|
||||||
|
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<String> {
|
||||||
|
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<String> {
|
||||||
|
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<String, T>) {
|
||||||
|
sharedPreferences.edit {
|
||||||
|
toSet.forEach { (k, v) ->
|
||||||
|
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"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
toSet.forEach { (k, v) ->
|
||||||
|
onNewValueChannel.send(k to v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun unset(toUnset: List<String>) {
|
||||||
|
sharedPreferences.edit {
|
||||||
|
toUnset.forEach { remove(it) }
|
||||||
|
}
|
||||||
|
toUnset.forEach { onValueRemovedChannel.send(it) }
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,214 @@
|
|||||||
|
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.utils.reverse
|
||||||
|
import dev.inmo.micro_utils.repos.*
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
import kotlinx.coroutines.flow.asSharedFlow
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import kotlinx.serialization.KSerializer
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
|
||||||
|
private val internalSerialFormat = Json {
|
||||||
|
ignoreUnknownKeys = true
|
||||||
|
}
|
||||||
|
|
||||||
|
class OneToManyAndroidRepo<Key, Value>(
|
||||||
|
private val tableName: String,
|
||||||
|
private val keySerializer: KSerializer<Key>,
|
||||||
|
private val valueSerializer: KSerializer<Value>,
|
||||||
|
private val helper: SQLiteOpenHelper
|
||||||
|
) : OneToManyKeyValueRepo<Key, Value> {
|
||||||
|
private val _onNewValue: MutableSharedFlow<Pair<Key, Value>> = MutableSharedFlow()
|
||||||
|
override val onNewValue: Flow<Pair<Key, Value>> = _onNewValue.asSharedFlow()
|
||||||
|
private val _onValueRemoved: MutableSharedFlow<Pair<Key, Value>> = MutableSharedFlow()
|
||||||
|
override val onValueRemoved: Flow<Pair<Key, Value>> = _onValueRemoved.asSharedFlow()
|
||||||
|
private val _onDataCleared = MutableSharedFlow<Key>()
|
||||||
|
override val onDataCleared: Flow<Key> = _onDataCleared.asSharedFlow()
|
||||||
|
|
||||||
|
private val idColumnName = "id"
|
||||||
|
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)
|
||||||
|
|
||||||
|
init {
|
||||||
|
runBlocking(DatabaseCoroutineContext) {
|
||||||
|
helper.writableTransaction {
|
||||||
|
createTable(
|
||||||
|
tableName,
|
||||||
|
internalId to internalIdType,
|
||||||
|
idColumnName to ColumnType.Text.NOT_NULLABLE,
|
||||||
|
valueColumnName to ColumnType.Text.NULLABLE
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun add(toAdd: Map<Key, List<Value>>) {
|
||||||
|
val added = mutableListOf<Pair<Key, Value>>()
|
||||||
|
helper.writableTransaction {
|
||||||
|
toAdd.forEach { (k, values) ->
|
||||||
|
values.forEach { v ->
|
||||||
|
insert(
|
||||||
|
tableName,
|
||||||
|
null,
|
||||||
|
contentValuesOf(
|
||||||
|
idColumnName to k.asId(),
|
||||||
|
valueColumnName to v.asValue()
|
||||||
|
)
|
||||||
|
).also {
|
||||||
|
if (it != -1L) {
|
||||||
|
added.add(k to v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
added.forEach { _onNewValue.emit(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun clear(k: Key) {
|
||||||
|
helper.writableTransaction {
|
||||||
|
delete(tableName, "$idColumnName=?", arrayOf(k.asId()))
|
||||||
|
}.also {
|
||||||
|
if (it > 0) {
|
||||||
|
_onDataCleared.emit(k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun contains(k: Key): Boolean = helper.readableTransaction {
|
||||||
|
select(tableName, selection = "$idColumnName=?", selectionArgs = arrayOf(k.asId()), limit = FirstPagePagination(1).limitClause()).use {
|
||||||
|
it.count > 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun contains(k: Key, v: Value): Boolean = helper.readableTransaction {
|
||||||
|
select(
|
||||||
|
tableName,
|
||||||
|
selection = "$idColumnName=? AND $valueColumnName=?",
|
||||||
|
selectionArgs = arrayOf(k.asId(), v.asValue()),
|
||||||
|
limit = FirstPagePagination(1).limitClause()
|
||||||
|
).use {
|
||||||
|
it.count > 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun count(): Long =helper.readableTransaction {
|
||||||
|
select(
|
||||||
|
tableName
|
||||||
|
).use {
|
||||||
|
it.count
|
||||||
|
}
|
||||||
|
}.toLong()
|
||||||
|
|
||||||
|
override suspend fun count(k: Key): Long = helper.readableTransaction {
|
||||||
|
select(tableName, selection = "$idColumnName=?", selectionArgs = arrayOf(k.asId()), limit = FirstPagePagination(1).limitClause()).use {
|
||||||
|
it.count
|
||||||
|
}
|
||||||
|
}.toLong()
|
||||||
|
|
||||||
|
override suspend fun get(
|
||||||
|
k: Key,
|
||||||
|
pagination: Pagination,
|
||||||
|
reversed: Boolean
|
||||||
|
): PaginationResult<Value> = count(k).let { count ->
|
||||||
|
val resultPagination = pagination.let { if (reversed) pagination.reverse(count) else pagination }
|
||||||
|
helper.readableTransaction {
|
||||||
|
select(
|
||||||
|
tableName,
|
||||||
|
selection = "$idColumnName=?",
|
||||||
|
selectionArgs = arrayOf(k.asId()),
|
||||||
|
limit = resultPagination.limitClause()
|
||||||
|
).use { c ->
|
||||||
|
mutableListOf<Value>().also {
|
||||||
|
if (c.moveToFirst()) {
|
||||||
|
do {
|
||||||
|
it.add(c.getString(valueColumnName).asValue())
|
||||||
|
} while (c.moveToNext())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.createPaginationResult(
|
||||||
|
pagination,
|
||||||
|
count
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun keys(
|
||||||
|
pagination: Pagination,
|
||||||
|
reversed: Boolean
|
||||||
|
): PaginationResult<Key> = count().let { count ->
|
||||||
|
val resultPagination = pagination.let { if (reversed) pagination.reverse(count) else pagination }
|
||||||
|
helper.readableTransaction {
|
||||||
|
select(
|
||||||
|
tableName,
|
||||||
|
limit = resultPagination.limitClause()
|
||||||
|
).use { c ->
|
||||||
|
mutableListOf<Key>().also {
|
||||||
|
if (c.moveToFirst()) {
|
||||||
|
do {
|
||||||
|
it.add(c.getString(idColumnName).asKey())
|
||||||
|
} while (c.moveToNext())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.createPaginationResult(
|
||||||
|
pagination,
|
||||||
|
count
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun keys(
|
||||||
|
v: Value,
|
||||||
|
pagination: Pagination,
|
||||||
|
reversed: Boolean
|
||||||
|
): PaginationResult<Key> = count().let { count ->
|
||||||
|
val resultPagination = pagination.let { if (reversed) pagination.reverse(count) else pagination }
|
||||||
|
helper.readableTransaction {
|
||||||
|
select(
|
||||||
|
tableName,
|
||||||
|
selection = "$valueColumnName=?",
|
||||||
|
selectionArgs = arrayOf(v.asValue()),
|
||||||
|
limit = resultPagination.limitClause()
|
||||||
|
).use { c ->
|
||||||
|
mutableListOf<Key>().also {
|
||||||
|
if (c.moveToFirst()) {
|
||||||
|
do {
|
||||||
|
it.add(c.getString(idColumnName).asKey())
|
||||||
|
} while (c.moveToNext())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.createPaginationResult(
|
||||||
|
pagination,
|
||||||
|
count
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun remove(toRemove: Map<Key, List<Value>>) {
|
||||||
|
helper.writableTransaction {
|
||||||
|
toRemove.flatMap { (k, vs) ->
|
||||||
|
vs.mapNotNullA { v ->
|
||||||
|
if (delete(tableName, "$idColumnName=? AND $valueColumnName=?", arrayOf(k.asId(), v.asValue())) > 0) {
|
||||||
|
k to v
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.forEach { (k, v) ->
|
||||||
|
_onValueRemoved.emit(k to v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -9,6 +9,7 @@ kotlin {
|
|||||||
sourceSets {
|
sourceSets {
|
||||||
commonMain {
|
commonMain {
|
||||||
dependencies {
|
dependencies {
|
||||||
|
api internalProject("micro_utils.coroutines")
|
||||||
api internalProject("micro_utils.repos.common")
|
api internalProject("micro_utils.repos.common")
|
||||||
api internalProject("micro_utils.pagination.exposed")
|
api internalProject("micro_utils.pagination.exposed")
|
||||||
}
|
}
|
||||||
|
@@ -1,9 +1,7 @@
|
|||||||
package dev.inmo.micro_utils.repos.exposed
|
package dev.inmo.micro_utils.repos.exposed
|
||||||
|
|
||||||
import kotlinx.coroutines.channels.Channel
|
|
||||||
|
|
||||||
abstract class AbstractExposedCRUDRepo<ObjectType, IdType, InputValueType>(
|
abstract class AbstractExposedCRUDRepo<ObjectType, IdType, InputValueType>(
|
||||||
flowsChannelsSize: Int = Channel.BUFFERED,
|
flowsChannelsSize: Int = 0,
|
||||||
tableName: String = ""
|
tableName: String = ""
|
||||||
) :
|
) :
|
||||||
AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
|
AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
|
||||||
|
@@ -3,28 +3,27 @@ package dev.inmo.micro_utils.repos.exposed
|
|||||||
import dev.inmo.micro_utils.repos.UpdatedValuePair
|
import dev.inmo.micro_utils.repos.UpdatedValuePair
|
||||||
import dev.inmo.micro_utils.repos.WriteStandardCRUDRepo
|
import dev.inmo.micro_utils.repos.WriteStandardCRUDRepo
|
||||||
import kotlinx.coroutines.channels.BroadcastChannel
|
import kotlinx.coroutines.channels.BroadcastChannel
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.*
|
||||||
import kotlinx.coroutines.flow.asFlow
|
|
||||||
import org.jetbrains.exposed.sql.*
|
import org.jetbrains.exposed.sql.*
|
||||||
import org.jetbrains.exposed.sql.statements.InsertStatement
|
import org.jetbrains.exposed.sql.statements.InsertStatement
|
||||||
import org.jetbrains.exposed.sql.statements.UpdateStatement
|
import org.jetbrains.exposed.sql.statements.UpdateStatement
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
|
|
||||||
abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
|
abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
|
||||||
flowsChannelsSize: Int = 64,
|
flowsChannelsSize: Int = 0,
|
||||||
tableName: String = ""
|
tableName: String = ""
|
||||||
) :
|
) :
|
||||||
AbstractExposedReadCRUDRepo<ObjectType, IdType>(tableName),
|
AbstractExposedReadCRUDRepo<ObjectType, IdType>(tableName),
|
||||||
ExposedCRUDRepo<ObjectType, IdType>,
|
ExposedCRUDRepo<ObjectType, IdType>,
|
||||||
WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>
|
WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>
|
||||||
{
|
{
|
||||||
protected val newObjectsChannel = BroadcastChannel<ObjectType>(flowsChannelsSize)
|
protected val newObjectsChannel = MutableSharedFlow<ObjectType>(flowsChannelsSize)
|
||||||
protected val updateObjectsChannel = BroadcastChannel<ObjectType>(flowsChannelsSize)
|
protected val updateObjectsChannel = MutableSharedFlow<ObjectType>(flowsChannelsSize)
|
||||||
protected val deleteObjectsIdsChannel = BroadcastChannel<IdType>(flowsChannelsSize)
|
protected val deleteObjectsIdsChannel = MutableSharedFlow<IdType>(flowsChannelsSize)
|
||||||
|
|
||||||
override val newObjectsFlow: Flow<ObjectType> = newObjectsChannel.asFlow()
|
override val newObjectsFlow: Flow<ObjectType> = newObjectsChannel.asSharedFlow()
|
||||||
override val updatedObjectsFlow: Flow<ObjectType> = updateObjectsChannel.asFlow()
|
override val updatedObjectsFlow: Flow<ObjectType> = updateObjectsChannel.asSharedFlow()
|
||||||
override val deletedObjectsIdsFlow: Flow<IdType> = deleteObjectsIdsChannel.asFlow()
|
override val deletedObjectsIdsFlow: Flow<IdType> = deleteObjectsIdsChannel.asSharedFlow()
|
||||||
|
|
||||||
abstract val InsertStatement<Number>.asObject: ObjectType
|
abstract val InsertStatement<Number>.asObject: ObjectType
|
||||||
abstract val selectByIds: SqlExpressionBuilder.(List<out IdType>) -> Op<Boolean>
|
abstract val selectByIds: SqlExpressionBuilder.(List<out IdType>) -> Op<Boolean>
|
||||||
@@ -45,7 +44,7 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
|
|||||||
values.map { value -> createWithoutNotification(value) }
|
values.map { value -> createWithoutNotification(value) }
|
||||||
}.also {
|
}.also {
|
||||||
it.forEach {
|
it.forEach {
|
||||||
newObjectsChannel.send(it)
|
newObjectsChannel.emit(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -75,7 +74,7 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
|
|||||||
onBeforeUpdate(listOf(id to value))
|
onBeforeUpdate(listOf(id to value))
|
||||||
return updateWithoutNotification(id, value).also {
|
return updateWithoutNotification(id, value).also {
|
||||||
if (it != null) {
|
if (it != null) {
|
||||||
updateObjectsChannel.send(it)
|
updateObjectsChannel.emit(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -89,7 +88,7 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
|
|||||||
} as List<ObjectType>
|
} as List<ObjectType>
|
||||||
).also {
|
).also {
|
||||||
it.forEach {
|
it.forEach {
|
||||||
updateObjectsChannel.send(it)
|
updateObjectsChannel.emit(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,6 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.exposed
|
||||||
|
|
||||||
|
import org.jetbrains.exposed.sql.Column
|
||||||
|
import org.jetbrains.exposed.sql.Table
|
||||||
|
|
||||||
|
typealias ColumnAllocator<T> = Table.() -> Column<T>
|
@@ -1,11 +1,8 @@
|
|||||||
package dev.inmo.micro_utils.repos.exposed
|
package dev.inmo.micro_utils.repos.exposed
|
||||||
|
|
||||||
import dev.inmo.micro_utils.repos.Repo
|
|
||||||
import org.jetbrains.exposed.sql.*
|
import org.jetbrains.exposed.sql.*
|
||||||
|
|
||||||
interface ExposedCRUDRepo<ObjectType, IdType> : Repo {
|
interface ExposedCRUDRepo<ObjectType, IdType> : ExposedRepo {
|
||||||
val database: Database
|
|
||||||
|
|
||||||
val ResultRow.asObject: ObjectType
|
val ResultRow.asObject: ObjectType
|
||||||
val selectById: SqlExpressionBuilder.(IdType) -> Op<Boolean>
|
val selectById: SqlExpressionBuilder.(IdType) -> Op<Boolean>
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,8 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.exposed
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.repos.Repo
|
||||||
|
import org.jetbrains.exposed.sql.Database
|
||||||
|
|
||||||
|
interface ExposedRepo : Repo {
|
||||||
|
val database: Database
|
||||||
|
}
|
@@ -6,3 +6,5 @@ import org.jetbrains.exposed.sql.transactions.transaction
|
|||||||
fun Table.initTable(database: Database) {
|
fun Table.initTable(database: Database) {
|
||||||
transaction(database) { SchemaUtils.createMissingTablesAndColumns(this@initTable) }
|
transaction(database) { SchemaUtils.createMissingTablesAndColumns(this@initTable) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun <T> T.initTable() where T: ExposedRepo, T: Table = initTable(database)
|
||||||
|
@@ -1,48 +0,0 @@
|
|||||||
package dev.inmo.micro_utils.repos.exposed.keyvalue
|
|
||||||
|
|
||||||
import dev.inmo.micro_utils.repos.StandardKeyValueRepo
|
|
||||||
import kotlinx.coroutines.channels.BroadcastChannel
|
|
||||||
import kotlinx.coroutines.channels.Channel
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.asFlow
|
|
||||||
import org.jetbrains.exposed.sql.*
|
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
|
||||||
|
|
||||||
abstract class AbstractExposedKeyValueRepo<Key, Value>(
|
|
||||||
database: Database,
|
|
||||||
keyColumn: Column<Key>,
|
|
||||||
valueColumn: Column<Value>
|
|
||||||
) : StandardKeyValueRepo<Key, Value>, AbstractExposedReadKeyValueRepo<Key, Value>(
|
|
||||||
database,
|
|
||||||
keyColumn,
|
|
||||||
valueColumn
|
|
||||||
) {
|
|
||||||
private val onNewValueChannel = BroadcastChannel<Pair<Key, Value>>(Channel.BUFFERED)
|
|
||||||
private val onValueRemovedChannel = BroadcastChannel<Key>(Channel.BUFFERED)
|
|
||||||
|
|
||||||
override val onNewValue: Flow<Pair<Key, Value>> = onNewValueChannel.asFlow()
|
|
||||||
override val onValueRemoved: Flow<Key> = onValueRemovedChannel.asFlow()
|
|
||||||
|
|
||||||
override suspend fun set(k: Key, v: Value) {
|
|
||||||
transaction(database) {
|
|
||||||
if (select { keyColumn.eq(k) }.limit(1).any()) {
|
|
||||||
update({ keyColumn.eq(k) }) {
|
|
||||||
it[valueColumn] = v
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
insert {
|
|
||||||
it[keyColumn] = k
|
|
||||||
it[valueColumn] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onNewValueChannel.send(k to v)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun unset(k: Key) {
|
|
||||||
transaction(database) {
|
|
||||||
deleteWhere { keyColumn.eq(k) }
|
|
||||||
}
|
|
||||||
onValueRemovedChannel.send(k)
|
|
||||||
}
|
|
||||||
}
|
|
@@ -0,0 +1,63 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.exposed.keyvalue
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.repos.StandardKeyValueRepo
|
||||||
|
import dev.inmo.micro_utils.repos.exposed.ColumnAllocator
|
||||||
|
import dev.inmo.micro_utils.repos.exposed.initTable
|
||||||
|
import kotlinx.coroutines.channels.Channel
|
||||||
|
import kotlinx.coroutines.flow.*
|
||||||
|
import org.jetbrains.exposed.sql.*
|
||||||
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
|
|
||||||
|
open class ExposedKeyValueRepo<Key, Value>(
|
||||||
|
database: Database,
|
||||||
|
keyColumnAllocator: ColumnAllocator<Key>,
|
||||||
|
valueColumnAllocator: ColumnAllocator<Value>,
|
||||||
|
tableName: String? = null
|
||||||
|
) : StandardKeyValueRepo<Key, Value>, ExposedReadKeyValueRepo<Key, Value>(
|
||||||
|
database,
|
||||||
|
keyColumnAllocator,
|
||||||
|
valueColumnAllocator,
|
||||||
|
tableName
|
||||||
|
) {
|
||||||
|
private val _onNewValue = MutableSharedFlow<Pair<Key, Value>>()
|
||||||
|
private val _onValueRemoved = MutableSharedFlow<Key>()
|
||||||
|
|
||||||
|
override val onNewValue: Flow<Pair<Key, Value>> = _onNewValue.asSharedFlow()
|
||||||
|
override val onValueRemoved: Flow<Key> = _onValueRemoved.asSharedFlow()
|
||||||
|
|
||||||
|
override suspend fun set(toSet: Map<Key, Value>) {
|
||||||
|
transaction(database) {
|
||||||
|
toSet.mapNotNull { (k, v) ->
|
||||||
|
if (update({ keyColumn.eq(k) }) { it[valueColumn] = v } > 0) {
|
||||||
|
k to v
|
||||||
|
} else {
|
||||||
|
val inserted = insert {
|
||||||
|
it[keyColumn] = k
|
||||||
|
it[valueColumn] = v
|
||||||
|
}.getOrNull(keyColumn) != null
|
||||||
|
if (inserted) {
|
||||||
|
k to v
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.forEach {
|
||||||
|
_onNewValue.emit(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun unset(toUnset: List<Key>) {
|
||||||
|
transaction(database) {
|
||||||
|
toUnset.mapNotNull {
|
||||||
|
if (deleteWhere { keyColumn.eq(it) } > 0) {
|
||||||
|
it
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.forEach {
|
||||||
|
_onValueRemoved.emit(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -2,16 +2,22 @@ package dev.inmo.micro_utils.repos.exposed.keyvalue
|
|||||||
|
|
||||||
import dev.inmo.micro_utils.pagination.*
|
import dev.inmo.micro_utils.pagination.*
|
||||||
import dev.inmo.micro_utils.repos.ReadStandardKeyValueRepo
|
import dev.inmo.micro_utils.repos.ReadStandardKeyValueRepo
|
||||||
|
import dev.inmo.micro_utils.repos.exposed.*
|
||||||
import org.jetbrains.exposed.sql.*
|
import org.jetbrains.exposed.sql.*
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
|
|
||||||
abstract class AbstractExposedReadKeyValueRepo<Key, Value>(
|
open class ExposedReadKeyValueRepo<Key, Value>(
|
||||||
protected val database: Database,
|
override val database: Database,
|
||||||
protected val keyColumn: Column<Key>,
|
keyColumnAllocator: ColumnAllocator<Key>,
|
||||||
protected val valueColumn: Column<Value>
|
valueColumnAllocator: ColumnAllocator<Value>,
|
||||||
) : ReadStandardKeyValueRepo<Key, Value>, Table() {
|
tableName: String? = null
|
||||||
|
) : ReadStandardKeyValueRepo<Key, Value>, ExposedRepo, Table(tableName ?: "") {
|
||||||
|
protected val keyColumn: Column<Key> = keyColumnAllocator()
|
||||||
|
protected val valueColumn: Column<Value> = valueColumnAllocator()
|
||||||
override val primaryKey: PrimaryKey = PrimaryKey(keyColumn, valueColumn)
|
override val primaryKey: PrimaryKey = PrimaryKey(keyColumn, valueColumn)
|
||||||
|
|
||||||
|
init { initTable() }
|
||||||
|
|
||||||
override suspend fun get(k: Key): Value? = transaction(database) {
|
override suspend fun get(k: Key): Value? = transaction(database) {
|
||||||
select { keyColumn.eq(k) }.limit(1).firstOrNull() ?.getOrNull(valueColumn)
|
select { keyColumn.eq(k) }.limit(1).firstOrNull() ?.getOrNull(valueColumn)
|
||||||
}
|
}
|
||||||
@@ -28,6 +34,16 @@ abstract class AbstractExposedReadKeyValueRepo<Key, Value>(
|
|||||||
}
|
}
|
||||||
}.createPaginationResult(pagination, count())
|
}.createPaginationResult(pagination, count())
|
||||||
|
|
||||||
|
override suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean): PaginationResult<Key> = transaction(database) {
|
||||||
|
select { valueColumn.eq(v) }.let {
|
||||||
|
it.count() to it.paginate(pagination, keyColumn to if (reversed) SortOrder.DESC else SortOrder.ASC).map {
|
||||||
|
it[keyColumn]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.let { (count, list) ->
|
||||||
|
list.createPaginationResult(pagination, count)
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun values(pagination: Pagination, reversed: Boolean): PaginationResult<Value> = transaction(database) {
|
override suspend fun values(pagination: Pagination, reversed: Boolean): PaginationResult<Value> = transaction(database) {
|
||||||
selectAll().paginate(pagination, keyColumn to if (reversed) SortOrder.DESC else SortOrder.ASC).map {
|
selectAll().paginate(pagination, keyColumn to if (reversed) SortOrder.DESC else SortOrder.ASC).map {
|
||||||
it[valueColumn]
|
it[valueColumn]
|
@@ -1,35 +0,0 @@
|
|||||||
package dev.inmo.micro_utils.repos.exposed.onetomany
|
|
||||||
|
|
||||||
import dev.inmo.micro_utils.repos.OneToManyKeyValueRepo
|
|
||||||
import org.jetbrains.exposed.sql.*
|
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
|
||||||
|
|
||||||
abstract class AbstractExposedOneToManyKeyValueRepo<Key, Value>(
|
|
||||||
keyColumnAllocator: ColumnAllocator<Key>,
|
|
||||||
valueColumnAllocator: ColumnAllocator<Value>,
|
|
||||||
database: Database
|
|
||||||
) : OneToManyKeyValueRepo<Key, Value>, AbstractExposedReadOneToManyKeyValueRepo<Key, Value>(
|
|
||||||
keyColumnAllocator,
|
|
||||||
valueColumnAllocator,
|
|
||||||
database
|
|
||||||
) {
|
|
||||||
override suspend fun add(k: Key, v: Value) {
|
|
||||||
transaction(database) {
|
|
||||||
insert {
|
|
||||||
it[keyColumn] = k
|
|
||||||
it[valueColumn] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun remove(k: Key, v: Value) {
|
|
||||||
transaction(database) { deleteWhere { keyColumn.eq(k).and(valueColumn.eq(v)) } }
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun clear(k: Key) {
|
|
||||||
transaction(database) { deleteWhere { keyColumn.eq(k) } }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Deprecated("Renamed", ReplaceWith("AbstractExposedOneToManyKeyValueRepo", "dev.inmo.micro_utils.repos.exposed.onetomany.AbstractExposedOneToManyKeyValueRepo"))
|
|
||||||
typealias AbstractOneToManyExposedKeyValueRepo<Key, Value> = AbstractExposedOneToManyKeyValueRepo<Key, Value>
|
|
@@ -0,0 +1,64 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.exposed.onetomany
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.repos.OneToManyKeyValueRepo
|
||||||
|
import dev.inmo.micro_utils.repos.exposed.ColumnAllocator
|
||||||
|
import kotlinx.coroutines.flow.*
|
||||||
|
import org.jetbrains.exposed.sql.*
|
||||||
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
|
|
||||||
|
open class ExposedOneToManyKeyValueRepo<Key, Value>(
|
||||||
|
database: Database,
|
||||||
|
keyColumnAllocator: ColumnAllocator<Key>,
|
||||||
|
valueColumnAllocator: ColumnAllocator<Value>,
|
||||||
|
tableName: String? = null
|
||||||
|
) : OneToManyKeyValueRepo<Key, Value>, ExposedReadOneToManyKeyValueRepo<Key, Value>(
|
||||||
|
database,
|
||||||
|
keyColumnAllocator,
|
||||||
|
valueColumnAllocator,
|
||||||
|
tableName
|
||||||
|
) {
|
||||||
|
protected val _onNewValue: MutableSharedFlow<Pair<Key, Value>> = MutableSharedFlow()
|
||||||
|
override val onNewValue: Flow<Pair<Key, Value>>
|
||||||
|
get() = _onNewValue
|
||||||
|
protected val _onValueRemoved: MutableSharedFlow<Pair<Key, Value>> = MutableSharedFlow()
|
||||||
|
override val onValueRemoved: Flow<Pair<Key, Value>>
|
||||||
|
get() = _onValueRemoved
|
||||||
|
protected val _onDataCleared: MutableSharedFlow<Key> = MutableSharedFlow()
|
||||||
|
override val onDataCleared: Flow<Key>
|
||||||
|
get() = _onDataCleared
|
||||||
|
|
||||||
|
override suspend fun add(toAdd: Map<Key, List<Value>>) {
|
||||||
|
transaction(database) {
|
||||||
|
toAdd.keys.flatMap { k ->
|
||||||
|
toAdd[k] ?.mapNotNull { v ->
|
||||||
|
insertIgnore {
|
||||||
|
it[keyColumn] = k
|
||||||
|
it[valueColumn] = v
|
||||||
|
}.getOrNull(keyColumn) ?.let { k to v }
|
||||||
|
} ?: emptyList()
|
||||||
|
}
|
||||||
|
}.forEach { _onNewValue.emit(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun remove(toRemove: Map<Key, List<Value>>) {
|
||||||
|
transaction(database) {
|
||||||
|
toRemove.keys.flatMap { k ->
|
||||||
|
toRemove[k] ?.mapNotNull { v ->
|
||||||
|
if (deleteIgnoreWhere { keyColumn.eq(k).and(valueColumn.eq(v)) } > 0 ) {
|
||||||
|
k to v
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
} ?: emptyList()
|
||||||
|
}
|
||||||
|
}.forEach {
|
||||||
|
_onValueRemoved.emit(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun clear(k: Key) {
|
||||||
|
transaction(database) {
|
||||||
|
deleteWhere { keyColumn.eq(k) }
|
||||||
|
}.also { _onDataCleared.emit(k) }
|
||||||
|
}
|
||||||
|
}
|
@@ -2,19 +2,21 @@ package dev.inmo.micro_utils.repos.exposed.onetomany
|
|||||||
|
|
||||||
import dev.inmo.micro_utils.pagination.*
|
import dev.inmo.micro_utils.pagination.*
|
||||||
import dev.inmo.micro_utils.repos.ReadOneToManyKeyValueRepo
|
import dev.inmo.micro_utils.repos.ReadOneToManyKeyValueRepo
|
||||||
|
import dev.inmo.micro_utils.repos.exposed.*
|
||||||
import org.jetbrains.exposed.sql.*
|
import org.jetbrains.exposed.sql.*
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
|
|
||||||
typealias ColumnAllocator<T> = Table.() -> Column<T>
|
open class ExposedReadOneToManyKeyValueRepo<Key, Value>(
|
||||||
|
override val database: Database,
|
||||||
abstract class AbstractExposedReadOneToManyKeyValueRepo<Key, Value>(
|
|
||||||
keyColumnAllocator: ColumnAllocator<Key>,
|
keyColumnAllocator: ColumnAllocator<Key>,
|
||||||
valueColumnAllocator: ColumnAllocator<Value>,
|
valueColumnAllocator: ColumnAllocator<Value>,
|
||||||
protected val database: Database
|
tableName: String? = null
|
||||||
) : ReadOneToManyKeyValueRepo<Key, Value>, Table() {
|
) : ReadOneToManyKeyValueRepo<Key, Value>, ExposedRepo, Table(tableName ?: "") {
|
||||||
protected val keyColumn: Column<Key> = keyColumnAllocator()
|
protected val keyColumn: Column<Key> = keyColumnAllocator()
|
||||||
protected val valueColumn: Column<Value> = valueColumnAllocator()
|
protected val valueColumn: Column<Value> = valueColumnAllocator()
|
||||||
|
|
||||||
|
init { initTable() }
|
||||||
|
|
||||||
override suspend fun count(k: Key): Long = transaction(database) { select { keyColumn.eq(k) }.count() }
|
override suspend fun count(k: Key): Long = transaction(database) { select { keyColumn.eq(k) }.count() }
|
||||||
|
|
||||||
override suspend fun count(): Long = transaction(database) { selectAll().count() }
|
override suspend fun count(): Long = transaction(database) { selectAll().count() }
|
||||||
@@ -37,6 +39,21 @@ abstract class AbstractExposedReadOneToManyKeyValueRepo<Key, Value>(
|
|||||||
count()
|
count()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
override suspend fun keys(
|
||||||
|
v: Value,
|
||||||
|
pagination: Pagination,
|
||||||
|
reversed: Boolean
|
||||||
|
): PaginationResult<Key> = transaction(database) {
|
||||||
|
select { valueColumn.eq(v) }.let {
|
||||||
|
it.count() to it.paginate(pagination, keyColumn, reversed).map { it[keyColumn] }
|
||||||
|
}
|
||||||
|
}.let { (count, list) ->
|
||||||
|
list.createPaginationResult(
|
||||||
|
pagination,
|
||||||
|
count
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun contains(k: Key): Boolean = transaction(database) {
|
override suspend fun contains(k: Key): Boolean = transaction(database) {
|
||||||
select { keyColumn.eq(k) }.limit(1).any()
|
select { keyColumn.eq(k) }.limit(1).any()
|
||||||
}
|
}
|
||||||
@@ -45,6 +62,3 @@ abstract class AbstractExposedReadOneToManyKeyValueRepo<Key, Value>(
|
|||||||
select { keyColumn.eq(k).and(valueColumn.eq(v)) }.limit(1).any()
|
select { keyColumn.eq(k).and(valueColumn.eq(v)) }.limit(1).any()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated("Renamed", ReplaceWith("AbstractExposedReadOneToManyKeyValueRepo", "dev.inmo.micro_utils.repos.exposed.onetomany.AbstractExposedReadOneToManyKeyValueRepo"))
|
|
||||||
typealias AbstractOneToManyExposedReadKeyValueRepo<Key, Value> = AbstractExposedReadOneToManyKeyValueRepo<Key, Value>
|
|
@@ -1,6 +1,8 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id "org.jetbrains.kotlin.multiplatform"
|
id "org.jetbrains.kotlin.multiplatform"
|
||||||
id "org.jetbrains.kotlin.plugin.serialization"
|
id "org.jetbrains.kotlin.plugin.serialization"
|
||||||
|
id "com.android.library"
|
||||||
|
id "kotlin-android-extensions"
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$mppProjectWithSerializationPresetPath"
|
apply from: "$mppProjectWithSerializationPresetPath"
|
||||||
|
@@ -1,8 +1,7 @@
|
|||||||
package dev.inmo.micro_utils.repos
|
package dev.inmo.micro_utils.repos
|
||||||
|
|
||||||
import dev.inmo.micro_utils.coroutines.BroadcastFlow
|
|
||||||
import dev.inmo.micro_utils.pagination.*
|
import dev.inmo.micro_utils.pagination.*
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.*
|
||||||
|
|
||||||
class ReadMapCRUDRepo<ObjectType, IdType>(
|
class ReadMapCRUDRepo<ObjectType, IdType>(
|
||||||
private val map: Map<IdType, ObjectType> = emptyMap()
|
private val map: Map<IdType, ObjectType> = emptyMap()
|
||||||
@@ -26,15 +25,12 @@ class ReadMapCRUDRepo<ObjectType, IdType>(
|
|||||||
abstract class WriteMapCRUDRepo<ObjectType, IdType, InputValueType>(
|
abstract class WriteMapCRUDRepo<ObjectType, IdType, InputValueType>(
|
||||||
private val map: MutableMap<IdType, ObjectType> = mutableMapOf()
|
private val map: MutableMap<IdType, ObjectType> = mutableMapOf()
|
||||||
) : WriteStandardCRUDRepo<ObjectType, IdType, InputValueType> {
|
) : WriteStandardCRUDRepo<ObjectType, IdType, InputValueType> {
|
||||||
private val _newObjectsFlow: BroadcastFlow<ObjectType> = BroadcastFlow()
|
private val _newObjectsFlow: MutableSharedFlow<ObjectType> = MutableSharedFlow()
|
||||||
override val newObjectsFlow: Flow<ObjectType>
|
override val newObjectsFlow: Flow<ObjectType> = _newObjectsFlow.asSharedFlow()
|
||||||
get() = _newObjectsFlow
|
private val _updatedObjectsFlow: MutableSharedFlow<ObjectType> = MutableSharedFlow()
|
||||||
private val _updatedObjectsFlow: BroadcastFlow<ObjectType> = BroadcastFlow()
|
override val updatedObjectsFlow: Flow<ObjectType> = _updatedObjectsFlow.asSharedFlow()
|
||||||
override val updatedObjectsFlow: Flow<ObjectType>
|
private val _deletedObjectsIdsFlow: MutableSharedFlow<IdType> = MutableSharedFlow()
|
||||||
get() = _updatedObjectsFlow
|
override val deletedObjectsIdsFlow: Flow<IdType> = _deletedObjectsIdsFlow.asSharedFlow()
|
||||||
private val _deletedObjectsIdsFlow: BroadcastFlow<IdType> = BroadcastFlow()
|
|
||||||
override val deletedObjectsIdsFlow: Flow<IdType>
|
|
||||||
get() = _deletedObjectsIdsFlow
|
|
||||||
|
|
||||||
protected abstract suspend fun updateObject(newValue: InputValueType, id: IdType, old: ObjectType): ObjectType
|
protected abstract suspend fun updateObject(newValue: InputValueType, id: IdType, old: ObjectType): ObjectType
|
||||||
protected abstract suspend fun createObject(newValue: InputValueType): Pair<IdType, ObjectType>
|
protected abstract suspend fun createObject(newValue: InputValueType): Pair<IdType, ObjectType>
|
||||||
@@ -44,7 +40,7 @@ abstract class WriteMapCRUDRepo<ObjectType, IdType, InputValueType>(
|
|||||||
val (id, newObject) = createObject(it)
|
val (id, newObject) = createObject(it)
|
||||||
map[id] = newObject
|
map[id] = newObject
|
||||||
newObject.also { _ ->
|
newObject.also { _ ->
|
||||||
_newObjectsFlow.send(newObject)
|
_newObjectsFlow.emit(newObject)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -54,7 +50,7 @@ abstract class WriteMapCRUDRepo<ObjectType, IdType, InputValueType>(
|
|||||||
|
|
||||||
return newValue.also {
|
return newValue.also {
|
||||||
map[id] = it
|
map[id] = it
|
||||||
_updatedObjectsFlow.send(it)
|
_updatedObjectsFlow.emit(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,7 +60,7 @@ abstract class WriteMapCRUDRepo<ObjectType, IdType, InputValueType>(
|
|||||||
|
|
||||||
override suspend fun deleteById(ids: List<IdType>) {
|
override suspend fun deleteById(ids: List<IdType>) {
|
||||||
ids.forEach {
|
ids.forEach {
|
||||||
map.remove(it) ?.also { _ -> _deletedObjectsIdsFlow.send(it) }
|
map.remove(it) ?.also { _ -> _deletedObjectsIdsFlow.emit(it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,8 +1,11 @@
|
|||||||
package dev.inmo.micro_utils.repos
|
package dev.inmo.micro_utils.repos
|
||||||
|
|
||||||
import dev.inmo.micro_utils.coroutines.BroadcastFlow
|
import dev.inmo.micro_utils.pagination.Pagination
|
||||||
import dev.inmo.micro_utils.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 kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
|
||||||
class ReadMapKeyValueRepo<Key, Value>(
|
class ReadMapKeyValueRepo<Key, Value>(
|
||||||
private val map: Map<Key, Value> = emptyMap()
|
private val map: Map<Key, Value> = emptyMap()
|
||||||
@@ -13,31 +16,39 @@ class ReadMapKeyValueRepo<Key, Value>(
|
|||||||
pagination: Pagination,
|
pagination: Pagination,
|
||||||
reversed: Boolean
|
reversed: Boolean
|
||||||
): PaginationResult<Value> {
|
): PaginationResult<Value> {
|
||||||
val firstIndex: Int = if (reversed) {
|
val values = map.values
|
||||||
val size = map.size
|
val actualPagination = if (reversed) pagination.reverse(values.size) else pagination
|
||||||
(size - pagination.lastIndex).let { if (it < 0) 0 else it }
|
return values.paginate(actualPagination).let {
|
||||||
} else {
|
if (reversed) {
|
||||||
pagination.firstIndex
|
it.copy(results = it.results.reversed())
|
||||||
|
} else {
|
||||||
|
it
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return map.keys.drop(firstIndex).take(pagination.size).mapNotNull { map[it] }.createPaginationResult(
|
|
||||||
firstIndex,
|
|
||||||
count()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult<Key> {
|
override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult<Key> {
|
||||||
val firstIndex: Int = if (reversed) {
|
val keys = map.keys
|
||||||
val size = map.size
|
val actualPagination = if (reversed) pagination.reverse(keys.size) else pagination
|
||||||
(size - pagination.lastIndex).let { if (it < 0) 0 else it }
|
return keys.paginate(actualPagination).let {
|
||||||
} else {
|
if (reversed) {
|
||||||
pagination.firstIndex
|
it.copy(results = it.results.reversed())
|
||||||
|
} else {
|
||||||
|
it
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return map.keys.drop(firstIndex).take(pagination.size).createPaginationResult(
|
override suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean): PaginationResult<Key> {
|
||||||
firstIndex,
|
val keys: List<Key> = map.mapNotNull { (k, value) -> if (v == value) k else null }
|
||||||
count()
|
val actualPagination = if (reversed) pagination.reverse(keys.size) else pagination
|
||||||
)
|
return keys.paginate(actualPagination).let {
|
||||||
|
if (reversed) {
|
||||||
|
it.copy(results = it.results.reversed())
|
||||||
|
} else {
|
||||||
|
it
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun contains(key: Key): Boolean = map.containsKey(key)
|
override suspend fun contains(key: Key): Boolean = map.containsKey(key)
|
||||||
@@ -48,20 +59,22 @@ class ReadMapKeyValueRepo<Key, Value>(
|
|||||||
class WriteMapKeyValueRepo<Key, Value>(
|
class WriteMapKeyValueRepo<Key, Value>(
|
||||||
private val map: MutableMap<Key, Value> = mutableMapOf()
|
private val map: MutableMap<Key, Value> = mutableMapOf()
|
||||||
) : WriteStandardKeyValueRepo<Key, Value> {
|
) : WriteStandardKeyValueRepo<Key, Value> {
|
||||||
private val _onNewValue: BroadcastFlow<Pair<Key, Value>> = BroadcastFlow()
|
private val _onNewValue: MutableSharedFlow<Pair<Key, Value>> = MutableSharedFlow()
|
||||||
override val onNewValue: Flow<Pair<Key, Value>>
|
override val onNewValue: Flow<Pair<Key, Value>>
|
||||||
get() = _onNewValue
|
get() = _onNewValue
|
||||||
private val _onValueRemoved: BroadcastFlow<Key> = BroadcastFlow()
|
private val _onValueRemoved: MutableSharedFlow<Key> = MutableSharedFlow()
|
||||||
override val onValueRemoved: Flow<Key>
|
override val onValueRemoved: Flow<Key>
|
||||||
get() = _onValueRemoved
|
get() = _onValueRemoved
|
||||||
|
|
||||||
override suspend fun set(k: Key, v: Value) {
|
override suspend fun set(toSet: Map<Key, Value>) {
|
||||||
map[k] = v
|
map.putAll(toSet)
|
||||||
_onNewValue.send(k to v)
|
toSet.forEach { (k, v) -> _onNewValue.emit(k to v) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun unset(k: Key) {
|
override suspend fun unset(toUnset: List<Key>) {
|
||||||
map.remove(k) ?.also { _onValueRemoved.send(k) }
|
toUnset.forEach { k ->
|
||||||
|
map.remove(k) ?.also { _ -> _onValueRemoved.emit(k) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
package dev.inmo.micro_utils.repos
|
package dev.inmo.micro_utils.repos
|
||||||
|
|
||||||
import dev.inmo.micro_utils.coroutines.BroadcastFlow
|
|
||||||
import dev.inmo.micro_utils.pagination.*
|
import dev.inmo.micro_utils.pagination.*
|
||||||
import dev.inmo.micro_utils.pagination.utils.paginate
|
import dev.inmo.micro_utils.pagination.utils.paginate
|
||||||
import kotlinx.coroutines.flow.Flow
|
import dev.inmo.micro_utils.pagination.utils.reverse
|
||||||
|
import kotlinx.coroutines.flow.*
|
||||||
|
|
||||||
class MapReadOneToManyKeyValueRepo<Key, Value>(
|
class MapReadOneToManyKeyValueRepo<Key, Value>(
|
||||||
private val map: Map<Key, List<Value>> = emptyMap()
|
private val map: Map<Key, List<Value>> = emptyMap()
|
||||||
@@ -13,8 +13,7 @@ class MapReadOneToManyKeyValueRepo<Key, Value>(
|
|||||||
|
|
||||||
return list.paginate(
|
return list.paginate(
|
||||||
if (reversed) {
|
if (reversed) {
|
||||||
val firstIndex = (map.size - pagination.lastIndex).let { if (it < 0) 0 else it }
|
pagination.reverse(list.size)
|
||||||
SimplePagination(firstIndex, pagination.size)
|
|
||||||
} else {
|
} else {
|
||||||
pagination
|
pagination
|
||||||
}
|
}
|
||||||
@@ -22,17 +21,27 @@ class MapReadOneToManyKeyValueRepo<Key, Value>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult<Key> {
|
override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult<Key> {
|
||||||
val firstIndex: Int = if (reversed) {
|
val keys = map.keys
|
||||||
val size = map.size
|
val actualPagination = if (reversed) pagination.reverse(keys.size) else pagination
|
||||||
(size - pagination.lastIndex).let { if (it < 0) 0 else it }
|
return keys.paginate(actualPagination).let {
|
||||||
} else {
|
if (reversed) {
|
||||||
pagination.firstIndex
|
it.copy(results = it.results.reversed())
|
||||||
|
} else {
|
||||||
|
it
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return map.keys.drop(firstIndex).take(pagination.size).createPaginationResult(
|
override suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean): PaginationResult<Key> {
|
||||||
firstIndex,
|
val keys = map.keys.filter { map[it] ?.contains(v) == true }
|
||||||
count()
|
val actualPagination = if (reversed) pagination.reverse(keys.size) else pagination
|
||||||
)
|
return keys.paginate(actualPagination).let {
|
||||||
|
if (reversed) {
|
||||||
|
it.copy(results = it.results.reversed())
|
||||||
|
} else {
|
||||||
|
it
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun contains(k: Key): Boolean = map.containsKey(k)
|
override suspend fun contains(k: Key): Boolean = map.containsKey(k)
|
||||||
@@ -47,27 +56,35 @@ class MapReadOneToManyKeyValueRepo<Key, Value>(
|
|||||||
class MapWriteOneToManyKeyValueRepo<Key, Value>(
|
class MapWriteOneToManyKeyValueRepo<Key, Value>(
|
||||||
private val map: MutableMap<Key, MutableList<Value>> = mutableMapOf()
|
private val map: MutableMap<Key, MutableList<Value>> = mutableMapOf()
|
||||||
) : WriteOneToManyKeyValueRepo<Key, Value> {
|
) : WriteOneToManyKeyValueRepo<Key, Value> {
|
||||||
private val _onNewValue: BroadcastFlow<Pair<Key, Value>> = BroadcastFlow()
|
private val _onNewValue: MutableSharedFlow<Pair<Key, Value>> = MutableSharedFlow()
|
||||||
override val onNewValue: Flow<Pair<Key, Value>>
|
override val onNewValue: Flow<Pair<Key, Value>> = _onNewValue.asSharedFlow()
|
||||||
get() = _onNewValue
|
private val _onValueRemoved: MutableSharedFlow<Pair<Key, Value>> = MutableSharedFlow()
|
||||||
private val _onValueRemoved: BroadcastFlow<Pair<Key, Value>> = BroadcastFlow()
|
override val onValueRemoved: Flow<Pair<Key, Value>> = _onValueRemoved.asSharedFlow()
|
||||||
override val onValueRemoved: Flow<Pair<Key, Value>>
|
private val _onDataCleared: MutableSharedFlow<Key> = MutableSharedFlow()
|
||||||
get() = _onValueRemoved
|
override val onDataCleared: Flow<Key> = _onDataCleared.asSharedFlow()
|
||||||
private val _onDataCleared: BroadcastFlow<Key> = BroadcastFlow()
|
|
||||||
override val onDataCleared: Flow<Key>
|
|
||||||
get() = _onDataCleared
|
|
||||||
|
|
||||||
override suspend fun add(k: Key, v: Value) {
|
override suspend fun add(toAdd: Map<Key, List<Value>>) {
|
||||||
map.getOrPut(k) { mutableListOf() }.add(v)
|
toAdd.keys.forEach { k ->
|
||||||
_onNewValue.send(k to v)
|
if (map.getOrPut(k) { mutableListOf() }.addAll(toAdd[k] ?: return@forEach)) {
|
||||||
|
toAdd[k] ?.forEach { v ->
|
||||||
|
_onNewValue.emit(k to v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun remove(k: Key, v: Value) {
|
override suspend fun remove(toRemove: Map<Key, List<Value>>) {
|
||||||
map[k] ?.remove(v) ?.also { _onValueRemoved.send(k to v) }
|
toRemove.keys.forEach { k ->
|
||||||
|
if (map[k] ?.removeAll(toRemove[k] ?: return@forEach) == true) {
|
||||||
|
toRemove[k] ?.forEach { v ->
|
||||||
|
_onValueRemoved.emit(k to v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun clear(k: Key) {
|
override suspend fun clear(k: Key) {
|
||||||
map.remove(k) ?.also { _onDataCleared.send(k) }
|
map.remove(k) ?.also { _onDataCleared.emit(k) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
1
repos/inmemory/src/main/AndroidManifest.xml
Normal file
1
repos/inmemory/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<manifest package="dev.inmo.micro_utils.repos.inmemory"/>
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user