mirror of
https://github.com/InsanusMokrassar/MicroUtils.git
synced 2025-09-18 14:59:24 +00:00
Compare commits
43 Commits
Author | SHA1 | Date | |
---|---|---|---|
6a89ffaa8a | |||
d5a8d0f4d4 | |||
9739bd871e | |||
632d2545d4 | |||
ecfa273a81 | |||
a36828116e | |||
14aa9ca26c | |||
069e51f2ff | |||
a15cbdfb1a | |||
4af8114eda | |||
90dc84e900 | |||
67c595b440 | |||
830b7aee56 | |||
1890608cb3 | |||
bd396959a9 | |||
d5fe19f0a5 | |||
46bfb09415 | |||
a60cb596d1 | |||
6f9d5e2d5f | |||
80bc226ee1 | |||
12e37184e1 | |||
25e9345d02 | |||
ccc4d030c3 | |||
90c0817b6d | |||
527f7bbafe | |||
765a32729f | |||
de783f77a2 | |||
de4c8d104c | |||
88c8c28f45 | |||
57b36826d1 | |||
f81a2f309b | |||
5fc760f4a5 | |||
091cb38339 | |||
3ae9b3e576 | |||
b03b4cbeec | |||
f2c1b3c76a | |||
f3bec34882 | |||
d6aa9fe9c2 | |||
2d5304a770 | |||
88f2c16c82 | |||
490c318d1c | |||
8beaf61a08 | |||
8b61c984eb |
59
CHANGELOG.md
59
CHANGELOG.md
@@ -1,5 +1,64 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.4.15
|
||||||
|
|
||||||
|
* `Coroutines`:
|
||||||
|
* `safely`:
|
||||||
|
* `SafelyExceptionHandlerKey` has been deprecated
|
||||||
|
* `SafelyExceptionHandler` has been deprecated
|
||||||
|
* `ContextSafelyExceptionHandlerKey` has been added
|
||||||
|
* `ContextSafelyExceptionHandler` has been added
|
||||||
|
* `safelyWithContextExceptionHandler` has been added
|
||||||
|
|
||||||
|
## 0.4.14
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `Kotlin`: `1.4.20` -> `1.4.21`
|
||||||
|
* `Ktor`: `1.4.3` -> `1.5.0`
|
||||||
|
* `Klock`: `2.0.1` -> `2.0.2`
|
||||||
|
* `Coroutines`:
|
||||||
|
* Add global variable `defaultSafelyExceptionHandler`
|
||||||
|
* Add `SafelyExceptionHandlerKey` and `SafelyExceptionHandler` classes to be able to overwrite
|
||||||
|
`defaultSafelyExceptionHandler` using context of coroutine
|
||||||
|
|
||||||
|
## 0.4.13
|
||||||
|
|
||||||
|
* `Common`
|
||||||
|
* `Android`
|
||||||
|
* Add expand/collapse functionality for horizontal expand/collapse
|
||||||
|
|
||||||
|
## 0.4.12
|
||||||
|
|
||||||
|
* `Coroutines`
|
||||||
|
* `JVM`
|
||||||
|
* Update `launchSynchronously` signature
|
||||||
|
* `Selector`
|
||||||
|
* Project created
|
||||||
|
|
||||||
|
## 0.4.11
|
||||||
|
|
||||||
|
* `Common`
|
||||||
|
* Add `clamp` function
|
||||||
|
|
||||||
|
## 0.4.10
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `Klock`: `2.0.0` -> `2.0.1`
|
||||||
|
* `Repo`
|
||||||
|
* Repo `WriteStandardKeyValueRepo` got new method `unsetWithValues`
|
||||||
|
|
||||||
|
## 0.4.9
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `Ktor`: `1.4.2` -> `1.4.3`
|
||||||
|
* `Coroutines`:
|
||||||
|
* `launchSynchronously` has been added in JVM
|
||||||
|
* `Repo`
|
||||||
|
* `Common`
|
||||||
|
* In repos different usages of `BroadcastChannel`s has been replaced with `MutableSharedFlow`
|
||||||
|
* `Exposed`
|
||||||
|
* `asObject` open fun has been added in CRUD realization
|
||||||
|
|
||||||
## 0.4.8
|
## 0.4.8
|
||||||
|
|
||||||
* `Versions`:
|
* `Versions`:
|
||||||
|
35
README.md
35
README.md
@@ -1 +1,36 @@
|
|||||||
# MicroUtils
|
# MicroUtils
|
||||||
|
|
||||||
|
This is a library with collection of tools for working in Kotlin environment. First of all, this library collection is oriented to use next technologies:
|
||||||
|
|
||||||
|
* [`Kotlin Coroutines`](https://github.com/Kotlin/kotlinx.coroutines)
|
||||||
|
* [`Kotlin Serialization`](https://github.com/Kotlin/kotlinx.serialization)
|
||||||
|
* [`Kotlin Exposed`](https://github.com/JetBrains/Exposed)
|
||||||
|
* [`Ktor`](https://ktor.io)
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary> <b>Android environment</b> </summary>
|
||||||
|
|
||||||
|
You always can look at the <a href="https://github.com/InsanusMokrassar/MicroUtils/blob/master/gradle.properties#L24-L34">properties file</a> to get information about current project dependencies, compile and build tools for `Android` target.
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## Projects
|
||||||
|
|
||||||
|
* `common` contains common tools for platform which usually are absent out-of-the-box when you starting project
|
||||||
|
* `selector` contains tools to use `Selector` interface with things like `RecyclerView` in android or other selection needs
|
||||||
|
* `coroutines` is a module for `Kotlin Coroutines` with different things like subscribing on flows (`onEach` + `launchIn` shortcut :) )
|
||||||
|
* `ktor` is a set of modules for `client`s and `server`s
|
||||||
|
* `mime_types` is NOT lightweight set of `MimeType`s with a lot of different objected and serializable (with `Kotlin Serialization`) mime types
|
||||||
|
* `pagination` is a complex of modules (explanation in [Complex modules structure](#complex-modules-structure) section) for lightweight pagination
|
||||||
|
* `repos` is a complex of modules (explanation in [Complex modules structure](#complex-modules-structure) section) for `KeyValue`/`OneToMany`/`CRUD` repos created to be able to exclude some heavy dependencies when you need some simple and lightweight typical repositories
|
||||||
|
|
||||||
|
## Complex modules structure
|
||||||
|
|
||||||
|
Most of complex modules are built with next hierarchy:
|
||||||
|
|
||||||
|
* `common` submodule for `API` things which are common for all platforms
|
||||||
|
* `exposed` submodule contains realizations for exposed tables
|
||||||
|
* `ktor` submodule is usually unavailable directly, because it contains its own submodules for clients and servers
|
||||||
|
* `common` part contains routes which are common for clients and servers
|
||||||
|
* `client` submodule contains clients which are usually using `UnifiedRequester` to make requests using routes from `ktor/common` module and some internal logic of requests
|
||||||
|
* `server` submodule (in most cases `JVM`-only) contains some extensions for `Route` instances which usually will give opportunity to proxy internet requests from `ktor/client` realization to some proxy object
|
||||||
|
1
_config.yml
Normal file
1
_config.yml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
theme: jekyll-theme-cayman
|
@@ -0,0 +1,10 @@
|
|||||||
|
package dev.inmo.micro_utils.common
|
||||||
|
|
||||||
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
|
inline fun <T : Comparable<T>> T.clamp(min: T, max: T): T {
|
||||||
|
return when {
|
||||||
|
this < min -> min
|
||||||
|
this > max -> max
|
||||||
|
else -> this
|
||||||
|
}
|
||||||
|
}
|
@@ -5,23 +5,44 @@ import android.view.ViewGroup
|
|||||||
import android.view.animation.Animation
|
import android.view.animation.Animation
|
||||||
import android.view.animation.Transformation
|
import android.view.animation.Transformation
|
||||||
|
|
||||||
@PreviewFeature
|
private fun View.performExpand(
|
||||||
fun View.expand(
|
|
||||||
duration: Long = 500,
|
duration: Long = 500,
|
||||||
targetWidth: Int = ViewGroup.LayoutParams.MATCH_PARENT,
|
targetWidth: Int = ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
targetHeight: Int = ViewGroup.LayoutParams.WRAP_CONTENT
|
targetHeight: Int = ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||||
|
onMeasured: View.() -> Unit,
|
||||||
|
onPerformAnimation: View.(interpolatedTime: Float, t: Transformation?) -> Unit
|
||||||
) {
|
) {
|
||||||
measure(targetWidth, targetHeight)
|
measure(targetWidth, targetHeight)
|
||||||
val measuredHeight: Int = measuredHeight
|
onMeasured()
|
||||||
layoutParams.height = 0
|
show()
|
||||||
visibility = View.VISIBLE
|
|
||||||
val a: Animation = object : Animation() {
|
val a: Animation = object : Animation() {
|
||||||
override fun applyTransformation(interpolatedTime: Float, t: Transformation?) {
|
override fun applyTransformation(interpolatedTime: Float, t: Transformation?) {
|
||||||
super.applyTransformation(interpolatedTime, t)
|
super.applyTransformation(interpolatedTime, t)
|
||||||
layoutParams.height = if (interpolatedTime == 1f) targetHeight else (measuredHeight * interpolatedTime).toInt()
|
onPerformAnimation(interpolatedTime, t)
|
||||||
requestLayout()
|
requestLayout()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun willChangeBounds(): Boolean = true
|
||||||
|
}
|
||||||
|
|
||||||
|
a.duration = duration
|
||||||
|
startAnimation(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun View.performCollapse(
|
||||||
|
duration: Long = 500,
|
||||||
|
onPerformAnimation: View.(interpolatedTime: Float, t: Transformation?) -> Unit
|
||||||
|
) {
|
||||||
|
val a: Animation = object : Animation() {
|
||||||
|
override fun applyTransformation(interpolatedTime: Float, t: Transformation?) {
|
||||||
|
if (interpolatedTime == 1f) {
|
||||||
|
gone()
|
||||||
|
} else {
|
||||||
|
onPerformAnimation(interpolatedTime, t)
|
||||||
|
requestLayout()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun willChangeBounds(): Boolean {
|
override fun willChangeBounds(): Boolean {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -32,27 +53,58 @@ fun View.expand(
|
|||||||
startAnimation(a)
|
startAnimation(a)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PreviewFeature
|
||||||
|
fun View.expand(
|
||||||
|
duration: Long = 500,
|
||||||
|
targetWidth: Int = ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
targetHeight: Int = ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
|
) {
|
||||||
|
var measuredHeight = 0
|
||||||
|
performExpand(
|
||||||
|
duration,
|
||||||
|
targetWidth,
|
||||||
|
targetHeight,
|
||||||
|
{
|
||||||
|
measuredHeight = this.measuredHeight
|
||||||
|
}
|
||||||
|
) { interpolatedTime, _ ->
|
||||||
|
layoutParams.height = if (interpolatedTime == 1f) targetHeight else (measuredHeight * interpolatedTime).toInt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreviewFeature
|
||||||
|
fun View.expandHorizontally(
|
||||||
|
duration: Long = 500,
|
||||||
|
targetWidth: Int = ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
targetHeight: Int = ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
|
) {
|
||||||
|
var measuredWidth = 0
|
||||||
|
performExpand(
|
||||||
|
duration,
|
||||||
|
targetWidth,
|
||||||
|
targetHeight,
|
||||||
|
{
|
||||||
|
measuredWidth = this.measuredWidth
|
||||||
|
}
|
||||||
|
) { interpolatedTime, _ ->
|
||||||
|
layoutParams.width = if (interpolatedTime == 1f) targetWidth else (measuredWidth * interpolatedTime).toInt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@PreviewFeature
|
@PreviewFeature
|
||||||
fun View.collapse(duration: Long = 500) {
|
fun View.collapse(duration: Long = 500) {
|
||||||
val initialHeight: Int = measuredHeight
|
val initialHeight: Int = measuredHeight
|
||||||
val a: Animation = object : Animation() {
|
performCollapse(duration) { interpolatedTime, _ ->
|
||||||
override fun applyTransformation(interpolatedTime: Float, t: Transformation?) {
|
layoutParams.height = initialHeight - (initialHeight * interpolatedTime).toInt()
|
||||||
if (interpolatedTime == 1f) {
|
|
||||||
visibility = View.GONE
|
|
||||||
} else {
|
|
||||||
layoutParams.height = initialHeight - (initialHeight * interpolatedTime).toInt()
|
|
||||||
requestLayout()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun willChangeBounds(): Boolean {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
a.duration = duration
|
@PreviewFeature
|
||||||
|
fun View.collapseHorizontally(duration: Long = 500) {
|
||||||
startAnimation(a)
|
val initialWidth: Int = measuredWidth
|
||||||
|
performCollapse(duration) { interpolatedTime, _ ->
|
||||||
|
layoutParams.width = initialWidth - (initialWidth * interpolatedTime).toInt()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PreviewFeature
|
@PreviewFeature
|
||||||
@@ -74,3 +126,15 @@ fun View.toggleExpandState(duration: Long = 500): Boolean = if (isCollapsed) {
|
|||||||
collapse(duration)
|
collapse(duration)
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true in case of expanding
|
||||||
|
*/
|
||||||
|
@PreviewFeature
|
||||||
|
fun View.toggleExpandHorizontallyState(duration: Long = 500): Boolean = if (isCollapsed) {
|
||||||
|
expandHorizontally(duration)
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
collapseHorizontally(duration)
|
||||||
|
false
|
||||||
|
}
|
||||||
|
@@ -19,7 +19,7 @@ fun <T> CoroutineScope.actor(
|
|||||||
|
|
||||||
inline fun <T> CoroutineScope.safeActor(
|
inline fun <T> CoroutineScope.safeActor(
|
||||||
channelCapacity: Int = Channel.UNLIMITED,
|
channelCapacity: Int = Channel.UNLIMITED,
|
||||||
noinline onException: ExceptionHandler<Unit> = {},
|
noinline onException: ExceptionHandler<Unit> = defaultSafelyExceptionHandler,
|
||||||
crossinline block: suspend (T) -> Unit
|
crossinline block: suspend (T) -> Unit
|
||||||
): Channel<T> = actor(
|
): Channel<T> = actor(
|
||||||
channelCapacity
|
channelCapacity
|
||||||
|
@@ -16,7 +16,7 @@ inline fun <T> Flow<T>.subscribe(scope: CoroutineScope, noinline block: suspend
|
|||||||
*/
|
*/
|
||||||
inline fun <T> Flow<T>.subscribeSafely(
|
inline fun <T> Flow<T>.subscribeSafely(
|
||||||
scope: CoroutineScope,
|
scope: CoroutineScope,
|
||||||
noinline onException: ExceptionHandler<Unit> = { throw it },
|
noinline onException: ExceptionHandler<Unit> = defaultSafelyExceptionHandler,
|
||||||
noinline block: suspend (T) -> Unit
|
noinline block: suspend (T) -> Unit
|
||||||
) = subscribe(scope) {
|
) = subscribe(scope) {
|
||||||
safely(onException) {
|
safely(onException) {
|
||||||
|
@@ -1,23 +1,123 @@
|
|||||||
package dev.inmo.micro_utils.coroutines
|
package dev.inmo.micro_utils.coroutines
|
||||||
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.supervisorScope
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
import kotlin.coroutines.coroutineContext
|
||||||
|
|
||||||
typealias ExceptionHandler<T> = suspend (Throwable) -> T
|
typealias ExceptionHandler<T> = suspend (Throwable) -> T
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This instance will be used in all calls of [safely] where exception handler has not been passed
|
||||||
|
*/
|
||||||
|
var defaultSafelyExceptionHandler: ExceptionHandler<Nothing> = { throw it }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key for [SafelyExceptionHandler] which can be used in [CoroutineContext.get] to get current default
|
||||||
|
* [SafelyExceptionHandler]
|
||||||
|
*/
|
||||||
|
@Deprecated("This method will be useless in future major update", ReplaceWith("ContextSafelyExceptionHandlerKey", "dev.inmo.micro_utils.coroutines.ContextSafelyExceptionHandler"))
|
||||||
|
class SafelyExceptionHandlerKey<T> : CoroutineContext.Key<SafelyExceptionHandler<T>>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shortcut for creating instance of [SafelyExceptionHandlerKey]
|
||||||
|
*/
|
||||||
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
|
@Deprecated("This method will be useless in future major update", ReplaceWith("ContextSafelyExceptionHandlerKey", "dev.inmo.micro_utils.coroutines.ContextSafelyExceptionHandler"))
|
||||||
|
inline fun <T> safelyExceptionHandlerKey() = SafelyExceptionHandlerKey<T>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper for [ExceptionHandler] which can be used in [CoroutineContext] to set local (for [CoroutineContext]) default
|
||||||
|
* [ExceptionHandler]. To get it use [CoroutineContext.get] with key [SafelyExceptionHandlerKey]
|
||||||
|
*
|
||||||
|
* @see SafelyExceptionHandlerKey
|
||||||
|
* @see ExceptionHandler
|
||||||
|
*/
|
||||||
|
@Deprecated("This method will be useless in future major update", ReplaceWith("ContextSafelyExceptionHandler", "dev.inmo.micro_utils.coroutines.ContextSafelyExceptionHandler"))
|
||||||
|
class SafelyExceptionHandler<T>(
|
||||||
|
val handler: ExceptionHandler<T>
|
||||||
|
) : CoroutineContext.Element {
|
||||||
|
override val key: CoroutineContext.Key<*> = safelyExceptionHandlerKey<T>()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This key can (and will) be used to get [ContextSafelyExceptionHandler] from [coroutineContext] of suspend functions
|
||||||
|
* and in [ContextSafelyExceptionHandler] for defining of its [CoroutineContext.Element.key]
|
||||||
|
*
|
||||||
|
* @see safelyWithContextExceptionHandler
|
||||||
|
* @see ContextSafelyExceptionHandler
|
||||||
|
*/
|
||||||
|
object ContextSafelyExceptionHandlerKey : CoroutineContext.Key<ContextSafelyExceptionHandler>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [ExceptionHandler] wrapper which was created to make possible to use [handler] across all coroutines calls
|
||||||
|
*
|
||||||
|
* @see safelyWithContextExceptionHandler
|
||||||
|
* @see ContextSafelyExceptionHandlerKey
|
||||||
|
*/
|
||||||
|
class ContextSafelyExceptionHandler(
|
||||||
|
val handler: ExceptionHandler<Unit>
|
||||||
|
) : CoroutineContext.Element {
|
||||||
|
override val key: CoroutineContext.Key<*>
|
||||||
|
get() = ContextSafelyExceptionHandlerKey
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return [ContextSafelyExceptionHandler] from [coroutineContext] by key [ContextSafelyExceptionHandlerKey] if
|
||||||
|
* exists
|
||||||
|
*
|
||||||
|
* @see ContextSafelyExceptionHandler
|
||||||
|
* @see ContextSafelyExceptionHandlerKey
|
||||||
|
*/
|
||||||
|
suspend inline fun contextSafelyExceptionHandler() = coroutineContext[ContextSafelyExceptionHandlerKey]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method will set new [coroutineContext] with [ContextSafelyExceptionHandler]. In case if [coroutineContext]
|
||||||
|
* already contains [ContextSafelyExceptionHandler], [ContextSafelyExceptionHandler.handler] will be used BEFORE
|
||||||
|
* [contextExceptionHandler] in case of exception.
|
||||||
|
*
|
||||||
|
* After all, will be called [withContext] method with created [ContextSafelyExceptionHandler] and block which will call
|
||||||
|
* [safely] method with [safelyExceptionHandler] as onException parameter and [block] as execution block
|
||||||
|
*/
|
||||||
|
suspend fun <T> safelyWithContextExceptionHandler(
|
||||||
|
contextExceptionHandler: ExceptionHandler<Unit>,
|
||||||
|
safelyExceptionHandler: ExceptionHandler<T> = defaultSafelyExceptionHandler,
|
||||||
|
block: suspend CoroutineScope.() -> T
|
||||||
|
): T {
|
||||||
|
val contextSafelyExceptionHandler = contextSafelyExceptionHandler() ?.handler ?.let { oldHandler ->
|
||||||
|
ContextSafelyExceptionHandler {
|
||||||
|
oldHandler(it)
|
||||||
|
contextExceptionHandler(it)
|
||||||
|
}
|
||||||
|
} ?: ContextSafelyExceptionHandler(contextExceptionHandler)
|
||||||
|
return withContext(contextSafelyExceptionHandler) {
|
||||||
|
safely(safelyExceptionHandler, block)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* It will run [block] inside of [supervisorScope] to avoid problems with catching of exceptions
|
* It will run [block] inside of [supervisorScope] to avoid problems with catching of exceptions
|
||||||
*
|
*
|
||||||
|
* Priorities of [ExceptionHandler]s:
|
||||||
|
*
|
||||||
|
* * [onException] In case if custom (will be used anyway if not [defaultSafelyExceptionHandler])
|
||||||
|
* * [CoroutineContext.get] with [SafelyExceptionHandlerKey] as key
|
||||||
|
* * [defaultSafelyExceptionHandler]
|
||||||
|
*
|
||||||
* @param [onException] Will be called when happen exception inside of [block]. By default will throw exception - this
|
* @param [onException] Will be called when happen exception inside of [block]. By default will throw exception - this
|
||||||
* exception will be available for catching
|
* exception will be available for catching
|
||||||
|
*
|
||||||
|
* @see defaultSafelyExceptionHandler
|
||||||
|
* @see safelyWithoutExceptions
|
||||||
|
* @see safelyWithContextExceptionHandler
|
||||||
*/
|
*/
|
||||||
suspend inline fun <T> safely(
|
suspend inline fun <T> safely(
|
||||||
noinline onException: ExceptionHandler<T> = { throw it },
|
noinline onException: ExceptionHandler<T> = defaultSafelyExceptionHandler,
|
||||||
noinline block: suspend CoroutineScope.() -> T
|
noinline block: suspend CoroutineScope.() -> T
|
||||||
): T {
|
): T {
|
||||||
return try {
|
return try {
|
||||||
supervisorScope(block)
|
supervisorScope(block)
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
|
coroutineContext[ContextSafelyExceptionHandlerKey] ?.handler ?.invoke(e)
|
||||||
onException(e)
|
onException(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,30 @@
|
|||||||
|
package dev.inmo.micro_utils.coroutines
|
||||||
|
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
|
||||||
|
fun <T> CoroutineScope.launchSynchronously(block: suspend CoroutineScope.() -> T): T {
|
||||||
|
var throwable: Throwable? = null
|
||||||
|
var result: T? = null
|
||||||
|
val objectToSynchronize = java.lang.Object()
|
||||||
|
val launchCallback = {
|
||||||
|
launch {
|
||||||
|
safely(
|
||||||
|
{
|
||||||
|
throwable = it
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
result = block()
|
||||||
|
}
|
||||||
|
synchronized(objectToSynchronize) {
|
||||||
|
objectToSynchronize.notifyAll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
synchronized(objectToSynchronize) {
|
||||||
|
launchCallback()
|
||||||
|
objectToSynchronize.wait()
|
||||||
|
}
|
||||||
|
throw throwable ?: return result!!
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> launchSynchronously(block: suspend CoroutineScope.() -> T): T = CoroutineScope(Dispatchers.Default).launchSynchronously(block)
|
@@ -0,0 +1,38 @@
|
|||||||
|
package dev.inmo.micro_utils.coroutines
|
||||||
|
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
import kotlin.test.Test
|
||||||
|
|
||||||
|
class HandleSafelyCoroutineContextTest {
|
||||||
|
@Test
|
||||||
|
fun testHandleSafelyCoroutineContext() {
|
||||||
|
val scope = CoroutineScope(Dispatchers.Default)
|
||||||
|
var contextHandlerHappen = false
|
||||||
|
var localHandlerHappen = false
|
||||||
|
var defaultHandlerHappen = false
|
||||||
|
defaultSafelyExceptionHandler = {
|
||||||
|
defaultHandlerHappen = true
|
||||||
|
throw it
|
||||||
|
}
|
||||||
|
val contextHandler: ExceptionHandler<Unit> = {
|
||||||
|
contextHandlerHappen = true
|
||||||
|
}
|
||||||
|
val checkJob = scope.launch {
|
||||||
|
safelyWithContextExceptionHandler(contextHandler) {
|
||||||
|
safely(
|
||||||
|
{
|
||||||
|
localHandlerHappen = true
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
error("That must happen :)")
|
||||||
|
}
|
||||||
|
println(coroutineContext)
|
||||||
|
error("That must happen too:)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
launchSynchronously { checkJob.join() }
|
||||||
|
assert(contextHandlerHappen)
|
||||||
|
assert(localHandlerHappen)
|
||||||
|
assert(defaultHandlerHappen)
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,13 @@
|
|||||||
|
package dev.inmo.micro_utils.coroutines
|
||||||
|
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
class LaunchSynchronouslyTest {
|
||||||
|
@Test
|
||||||
|
fun testRunInCoroutine() {
|
||||||
|
(0 .. 10000).forEach {
|
||||||
|
assertEquals(it, launchSynchronously { it })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,3 +1,5 @@
|
|||||||
|
apply plugin: 'com.getkeepsafe.dexcount'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion "$android_compileSdkVersion".toInteger()
|
compileSdkVersion "$android_compileSdkVersion".toInteger()
|
||||||
buildToolsVersion "$android_buildToolsVersion"
|
buildToolsVersion "$android_buildToolsVersion"
|
||||||
|
@@ -6,14 +6,14 @@ kotlin.incremental.js=true
|
|||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
android.enableJetifier=true
|
android.enableJetifier=true
|
||||||
|
|
||||||
kotlin_version=1.4.20
|
kotlin_version=1.4.21
|
||||||
kotlin_coroutines_version=1.4.2
|
kotlin_coroutines_version=1.4.2
|
||||||
kotlin_serialisation_core_version=1.0.1
|
kotlin_serialisation_core_version=1.0.1
|
||||||
kotlin_exposed_version=0.28.1
|
kotlin_exposed_version=0.28.1
|
||||||
|
|
||||||
ktor_version=1.4.2
|
ktor_version=1.5.0
|
||||||
|
|
||||||
klockVersion=2.0.0
|
klockVersion=2.0.2
|
||||||
|
|
||||||
github_release_plugin_version=2.2.12
|
github_release_plugin_version=2.2.12
|
||||||
|
|
||||||
@@ -28,17 +28,17 @@ appcompat_version=1.2.0
|
|||||||
android_minSdkVersion=19
|
android_minSdkVersion=19
|
||||||
android_compileSdkVersion=30
|
android_compileSdkVersion=30
|
||||||
android_buildToolsVersion=30.0.2
|
android_buildToolsVersion=30.0.2
|
||||||
dexcount_version=2.0.0-RC1
|
dexcount_version=2.0.0
|
||||||
junit_version=4.12
|
junit_version=4.12
|
||||||
test_ext_junit_version=1.1.2
|
test_ext_junit_version=1.1.2
|
||||||
espresso_core=3.3.0
|
espresso_core=3.3.0
|
||||||
|
|
||||||
# Dokka
|
# Dokka
|
||||||
|
|
||||||
dokka_version=1.4.10.2
|
dokka_version=1.4.20
|
||||||
|
|
||||||
# Project data
|
# Project data
|
||||||
|
|
||||||
group=dev.inmo
|
group=dev.inmo
|
||||||
version=0.4.8
|
version=0.4.15
|
||||||
android_code_version=12
|
android_code_version=19
|
||||||
|
@@ -25,7 +25,7 @@ inline fun <T> HttpClient.createStandardWebsocketFlow(
|
|||||||
val producerScope = this@channelFlow
|
val producerScope = this@channelFlow
|
||||||
do {
|
do {
|
||||||
val reconnect = try {
|
val reconnect = try {
|
||||||
safely ({ throw it }) {
|
safely {
|
||||||
ws(correctedUrl) {
|
ws(correctedUrl) {
|
||||||
for (received in incoming) {
|
for (received in incoming) {
|
||||||
when (received) {
|
when (received) {
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
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 ReadStandardKeyValueRepo<Key, Value> : Repo {
|
interface ReadStandardKeyValueRepo<Key, Value> : Repo {
|
||||||
@@ -20,6 +19,7 @@ interface WriteStandardKeyValueRepo<Key, Value> : Repo {
|
|||||||
|
|
||||||
suspend fun set(toSet: Map<Key, Value>)
|
suspend fun set(toSet: Map<Key, Value>)
|
||||||
suspend fun unset(toUnset: List<Key>)
|
suspend fun unset(toUnset: List<Key>)
|
||||||
|
suspend fun unsetWithValues(toUnset: List<Value>)
|
||||||
}
|
}
|
||||||
typealias WriteKeyValueRepo<Key,Value> = WriteStandardKeyValueRepo<Key, Value>
|
typealias WriteKeyValueRepo<Key,Value> = WriteStandardKeyValueRepo<Key, Value>
|
||||||
|
|
||||||
@@ -35,5 +35,17 @@ suspend inline fun <Key, Value> WriteStandardKeyValueRepo<Key, Value>.unset(
|
|||||||
vararg k: Key
|
vararg k: Key
|
||||||
) = unset(k.toList())
|
) = unset(k.toList())
|
||||||
|
|
||||||
interface StandardKeyValueRepo<Key, Value> : ReadStandardKeyValueRepo<Key, Value>, WriteStandardKeyValueRepo<Key, Value>
|
suspend inline fun <Key, Value> WriteStandardKeyValueRepo<Key, Value>.unsetWithValues(
|
||||||
|
vararg v: Value
|
||||||
|
) = unsetWithValues(v.toList())
|
||||||
|
|
||||||
|
interface StandardKeyValueRepo<Key, Value> : ReadStandardKeyValueRepo<Key, Value>, WriteStandardKeyValueRepo<Key, Value> {
|
||||||
|
override suspend fun unsetWithValues(toUnset: List<Value>) = toUnset.forEach { v ->
|
||||||
|
doWithPagination {
|
||||||
|
keys(v, it).also {
|
||||||
|
unset(it.results)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
typealias KeyValueRepo<Key,Value> = StandardKeyValueRepo<Key, Value>
|
typealias KeyValueRepo<Key,Value> = StandardKeyValueRepo<Key, Value>
|
||||||
|
@@ -105,6 +105,10 @@ open class MapperWriteStandardKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
|||||||
k.toOutKey()
|
k.toOutKey()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
override suspend fun unsetWithValues(toUnset: List<FromValue>) = to.unsetWithValues(
|
||||||
|
toUnset.map { it.toOutValue() }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("NOTHING_TO_INLINE")
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
|
@@ -160,6 +160,18 @@ class FileWriteStandardKeyValueRepo(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun unsetWithValues(toUnset: List<File>) {
|
||||||
|
val keys = toUnset.mapNotNull { v ->
|
||||||
|
val key = v.absolutePath.removePrefix(folder.absolutePath)
|
||||||
|
if (key != v.absolutePath) {
|
||||||
|
key
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unset(keys)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Warning("Files watching will not correctly works on Android Platform with version of API lower than API 26")
|
@Warning("Files watching will not correctly works on Android Platform with version of API lower than API 26")
|
||||||
|
@@ -10,21 +10,6 @@ import kotlin.coroutines.Continuation
|
|||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
import kotlin.coroutines.resumeWithException
|
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(
|
class StandardSQLHelper(
|
||||||
context: Context,
|
context: Context,
|
||||||
name: String,
|
name: String,
|
||||||
|
@@ -3,20 +3,18 @@ package dev.inmo.micro_utils.repos.crud
|
|||||||
import android.content.ContentValues
|
import android.content.ContentValues
|
||||||
import dev.inmo.micro_utils.common.mapNotNullA
|
import dev.inmo.micro_utils.common.mapNotNullA
|
||||||
import dev.inmo.micro_utils.repos.*
|
import dev.inmo.micro_utils.repos.*
|
||||||
import kotlinx.coroutines.channels.BroadcastChannel
|
import kotlinx.coroutines.flow.*
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.asFlow
|
|
||||||
|
|
||||||
abstract class AbstractMutableAndroidCRUDRepo<ObjectType, IdType, InputValueType>(
|
abstract class AbstractMutableAndroidCRUDRepo<ObjectType, IdType, InputValueType>(
|
||||||
helper: StandardSQLHelper
|
helper: StandardSQLHelper
|
||||||
) : WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>,
|
) : WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>,
|
||||||
AbstractAndroidCRUDRepo<ObjectType, IdType>(helper) {
|
AbstractAndroidCRUDRepo<ObjectType, IdType>(helper) {
|
||||||
protected val newObjectsChannel = BroadcastChannel<ObjectType>(64)
|
protected val newObjectsChannel = MutableSharedFlow<ObjectType>(64)
|
||||||
protected val updateObjectsChannel = BroadcastChannel<ObjectType>(64)
|
protected val updateObjectsChannel = MutableSharedFlow<ObjectType>(64)
|
||||||
protected val deleteObjectsIdsChannel = BroadcastChannel<IdType>(64)
|
protected val deleteObjectsIdsChannel = MutableSharedFlow<IdType>(64)
|
||||||
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()
|
||||||
|
|
||||||
protected abstract suspend fun InputValueType.asContentValues(id: IdType? = null): ContentValues
|
protected abstract suspend fun InputValueType.asContentValues(id: IdType? = null): ContentValues
|
||||||
|
|
||||||
@@ -42,7 +40,7 @@ abstract class AbstractMutableAndroidCRUDRepo<ObjectType, IdType, InputValueType
|
|||||||
}
|
}
|
||||||
}.also {
|
}.also {
|
||||||
it.forEach {
|
it.forEach {
|
||||||
newObjectsChannel.send(it)
|
newObjectsChannel.emit(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -59,7 +57,7 @@ abstract class AbstractMutableAndroidCRUDRepo<ObjectType, IdType, InputValueType
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
deleted.forEach {
|
deleted.forEach {
|
||||||
deleteObjectsIdsChannel.send(it)
|
deleteObjectsIdsChannel.emit(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,7 +74,7 @@ abstract class AbstractMutableAndroidCRUDRepo<ObjectType, IdType, InputValueType
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return getById(id) ?.also {
|
return getById(id) ?.also {
|
||||||
updateObjectsChannel.send(it)
|
updateObjectsChannel.emit(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,7 +93,7 @@ abstract class AbstractMutableAndroidCRUDRepo<ObjectType, IdType, InputValueType
|
|||||||
getById(it.first)
|
getById(it.first)
|
||||||
}.also {
|
}.also {
|
||||||
it.forEach {
|
it.forEach {
|
||||||
updateObjectsChannel.send(it)
|
updateObjectsChannel.emit(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -8,10 +8,7 @@ import dev.inmo.micro_utils.pagination.PaginationResult
|
|||||||
import dev.inmo.micro_utils.pagination.utils.paginate
|
import dev.inmo.micro_utils.pagination.utils.paginate
|
||||||
import dev.inmo.micro_utils.pagination.utils.reverse
|
import dev.inmo.micro_utils.pagination.utils.reverse
|
||||||
import dev.inmo.micro_utils.repos.StandardKeyValueRepo
|
import dev.inmo.micro_utils.repos.StandardKeyValueRepo
|
||||||
import kotlinx.coroutines.channels.BroadcastChannel
|
import kotlinx.coroutines.flow.*
|
||||||
import kotlinx.coroutines.channels.Channel
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.asFlow
|
|
||||||
|
|
||||||
private val cache = HashMap<String, KeyValueStore<*>>()
|
private val cache = HashMap<String, KeyValueStore<*>>()
|
||||||
|
|
||||||
@@ -37,11 +34,11 @@ class KeyValueStore<T : Any> internal constructor (
|
|||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
private val onNewValueChannel = BroadcastChannel<Pair<String, T>>(Channel.BUFFERED)
|
private val onNewValueChannel = MutableSharedFlow<Pair<String, T>>()
|
||||||
private val onValueRemovedChannel = BroadcastChannel<String>(Channel.BUFFERED)
|
private val _onValueRemovedFlow = MutableSharedFlow<String>()
|
||||||
|
|
||||||
override val onNewValue: Flow<Pair<String, T>> = onNewValueChannel.asFlow()
|
override val onNewValue: Flow<Pair<String, T>> = onNewValueChannel.asSharedFlow()
|
||||||
override val onValueRemoved: Flow<String> = onValueRemovedChannel.asFlow()
|
override val onValueRemoved: Flow<String> = _onValueRemovedFlow.asSharedFlow()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
cachedData ?.let {
|
cachedData ?.let {
|
||||||
@@ -131,7 +128,7 @@ class KeyValueStore<T : Any> internal constructor (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
toSet.forEach { (k, v) ->
|
toSet.forEach { (k, v) ->
|
||||||
onNewValueChannel.send(k to v)
|
onNewValueChannel.emit(k to v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,6 +136,18 @@ class KeyValueStore<T : Any> internal constructor (
|
|||||||
sharedPreferences.edit {
|
sharedPreferences.edit {
|
||||||
toUnset.forEach { remove(it) }
|
toUnset.forEach { remove(it) }
|
||||||
}
|
}
|
||||||
toUnset.forEach { onValueRemovedChannel.send(it) }
|
toUnset.forEach { _onValueRemovedFlow.emit(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun unsetWithValues(toUnset: List<T>) {
|
||||||
|
val keysToRemove = sharedPreferences.all.mapNotNull { if (it.value in toUnset) it.key else null }
|
||||||
|
sharedPreferences.edit {
|
||||||
|
keysToRemove.map {
|
||||||
|
remove(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
keysToRemove.forEach {
|
||||||
|
_onValueRemovedFlow.emit(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -32,11 +32,11 @@ class AndroidSQLStandardVersionsRepoProxy(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getTableVersion(table: String): Int? = database.writableTransaction {
|
override suspend fun getTableVersion(tableName: String): Int? = database.writableTransaction {
|
||||||
select(
|
select(
|
||||||
tableName,
|
this@AndroidSQLStandardVersionsRepoProxy.tableName,
|
||||||
selection = "$tableNameColumnName=?",
|
selection = "$tableNameColumnName=?",
|
||||||
selectionArgs = arrayOf(table),
|
selectionArgs = arrayOf(tableName),
|
||||||
limit = limitClause(1)
|
limit = limitClause(1)
|
||||||
).use {
|
).use {
|
||||||
if (it.moveToFirst()) {
|
if (it.moveToFirst()) {
|
||||||
@@ -47,16 +47,16 @@ class AndroidSQLStandardVersionsRepoProxy(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun updateTableVersion(table: String, version: Int) {
|
override suspend fun updateTableVersion(tableName: String, version: Int) {
|
||||||
database.writableTransaction {
|
database.writableTransaction {
|
||||||
val updated = update(
|
val updated = update(
|
||||||
tableName,
|
this@AndroidSQLStandardVersionsRepoProxy.tableName,
|
||||||
contentValuesOf(tableVersionColumnName to version),
|
contentValuesOf(tableVersionColumnName to version),
|
||||||
"$tableNameColumnName=?",
|
"$tableNameColumnName=?",
|
||||||
arrayOf(table)
|
arrayOf(tableName)
|
||||||
) > 0
|
) > 0
|
||||||
if (!updated) {
|
if (!updated) {
|
||||||
insert(tableName, null, contentValuesOf(tableNameColumnName to table, tableVersionColumnName to version))
|
insert(this@AndroidSQLStandardVersionsRepoProxy.tableName, null, contentValuesOf(tableNameColumnName to tableName, tableVersionColumnName to version))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,7 +2,6 @@ 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.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
import org.jetbrains.exposed.sql.*
|
import org.jetbrains.exposed.sql.*
|
||||||
import org.jetbrains.exposed.sql.statements.InsertStatement
|
import org.jetbrains.exposed.sql.statements.InsertStatement
|
||||||
@@ -25,8 +24,10 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
|
|||||||
override val updatedObjectsFlow: Flow<ObjectType> = updateObjectsChannel.asSharedFlow()
|
override val updatedObjectsFlow: Flow<ObjectType> = updateObjectsChannel.asSharedFlow()
|
||||||
override val deletedObjectsIdsFlow: Flow<IdType> = deleteObjectsIdsChannel.asSharedFlow()
|
override val deletedObjectsIdsFlow: Flow<IdType> = deleteObjectsIdsChannel.asSharedFlow()
|
||||||
|
|
||||||
|
@Deprecated("Will be removed in near major update. Override open fun with the same name instead")
|
||||||
abstract val InsertStatement<Number>.asObject: ObjectType
|
abstract val InsertStatement<Number>.asObject: ObjectType
|
||||||
abstract val selectByIds: SqlExpressionBuilder.(List<out IdType>) -> Op<Boolean>
|
protected open fun InsertStatement<Number>.asObject(value: InputValueType): ObjectType = asObject
|
||||||
|
abstract val selectByIds: SqlExpressionBuilder.(List<IdType>) -> Op<Boolean>
|
||||||
|
|
||||||
protected abstract fun insert(value: InputValueType, it: InsertStatement<Number>)
|
protected abstract fun insert(value: InputValueType, it: InsertStatement<Number>)
|
||||||
protected abstract fun update(id: IdType, value: InputValueType, it: UpdateStatement)
|
protected abstract fun update(id: IdType, value: InputValueType, it: UpdateStatement)
|
||||||
@@ -34,7 +35,7 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
|
|||||||
protected open suspend fun onBeforeCreate(value: List<InputValueType>) {}
|
protected open suspend fun onBeforeCreate(value: List<InputValueType>) {}
|
||||||
private fun createWithoutNotification(value: InputValueType): ObjectType {
|
private fun createWithoutNotification(value: InputValueType): ObjectType {
|
||||||
return transaction(database) {
|
return transaction(database) {
|
||||||
insert { insert(value, it) }.asObject
|
insert { insert(value, it) }.asObject(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,10 +43,8 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
|
|||||||
onBeforeCreate(values)
|
onBeforeCreate(values)
|
||||||
return transaction(db = database) {
|
return transaction(db = database) {
|
||||||
values.map { value -> createWithoutNotification(value) }
|
values.map { value -> createWithoutNotification(value) }
|
||||||
}.also {
|
}.onEach {
|
||||||
it.forEach {
|
newObjectsChannel.emit(it)
|
||||||
newObjectsChannel.emit(it)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,13 +82,9 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
|
|||||||
return (
|
return (
|
||||||
transaction(db = database) {
|
transaction(db = database) {
|
||||||
values.map { (id, value) -> updateWithoutNotification(id, value) }
|
values.map { (id, value) -> updateWithoutNotification(id, value) }
|
||||||
}.filter {
|
}.filterNotNull()
|
||||||
it != null
|
).onEach {
|
||||||
} as List<ObjectType>
|
updateObjectsChannel.emit(it)
|
||||||
).also {
|
|
||||||
it.forEach {
|
|
||||||
updateObjectsChannel.emit(it)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
protected open suspend fun onBeforeDelete(ids: List<IdType>) {}
|
protected open suspend fun onBeforeDelete(ids: List<IdType>) {}
|
||||||
|
@@ -60,4 +60,16 @@ open class ExposedKeyValueRepo<Key, Value>(
|
|||||||
_onValueRemoved.emit(it)
|
_onValueRemoved.emit(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun unsetWithValues(toUnset: List<Value>) {
|
||||||
|
transaction(database) {
|
||||||
|
toUnset.flatMap {
|
||||||
|
val keys = select { valueColumn.eq(it) }.mapNotNull { it[keyColumn] }
|
||||||
|
deleteWhere { keyColumn.inList(keys) }
|
||||||
|
keys
|
||||||
|
}
|
||||||
|
}.distinct().forEach {
|
||||||
|
_onValueRemoved.emit(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -76,6 +76,15 @@ class WriteMapKeyValueRepo<Key, Value>(
|
|||||||
map.remove(k) ?.also { _ -> _onValueRemoved.emit(k) }
|
map.remove(k) ?.also { _ -> _onValueRemoved.emit(k) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun unsetWithValues(toUnset: List<Value>) {
|
||||||
|
map.forEach {
|
||||||
|
if (it.value in toUnset) {
|
||||||
|
map.remove(it.key)
|
||||||
|
_onValueRemoved.emit(it.key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MapKeyValueRepo<Key, Value>(
|
class MapKeyValueRepo<Key, Value>(
|
||||||
|
@@ -7,6 +7,7 @@ import dev.inmo.micro_utils.repos.*
|
|||||||
import io.ktor.client.HttpClient
|
import io.ktor.client.HttpClient
|
||||||
import kotlinx.serialization.*
|
import kotlinx.serialization.*
|
||||||
|
|
||||||
|
@Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE")
|
||||||
class KtorStandartKeyValueRepo<K, V> (
|
class KtorStandartKeyValueRepo<K, V> (
|
||||||
baseUrl: String,
|
baseUrl: String,
|
||||||
baseSubpart: String,
|
baseSubpart: String,
|
||||||
|
@@ -17,6 +17,7 @@ class KtorWriteStandardKeyValueRepo<K, V> (
|
|||||||
) : WriteStandardKeyValueRepo<K, V> {
|
) : WriteStandardKeyValueRepo<K, V> {
|
||||||
private val keyValueMapSerializer = MapSerializer(keySerializer, valueSerializer)
|
private val keyValueMapSerializer = MapSerializer(keySerializer, valueSerializer)
|
||||||
private val keysListSerializer = ListSerializer(keySerializer)
|
private val keysListSerializer = ListSerializer(keySerializer)
|
||||||
|
private val valuesListSerializer = ListSerializer(valueSerializer)
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
baseUrl: String,
|
baseUrl: String,
|
||||||
@@ -55,4 +56,13 @@ class KtorWriteStandardKeyValueRepo<K, V> (
|
|||||||
BodyPair(keysListSerializer, toUnset),
|
BodyPair(keysListSerializer, toUnset),
|
||||||
Unit.serializer()
|
Unit.serializer()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
override suspend fun unsetWithValues(toUnset: List<V>) = unifiedRequester.unipost(
|
||||||
|
buildStandardUrl(
|
||||||
|
baseUrl,
|
||||||
|
unsetWithValuesRoute,
|
||||||
|
),
|
||||||
|
BodyPair(valuesListSerializer, toUnset),
|
||||||
|
Unit.serializer()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
@@ -10,3 +10,4 @@ const val onNewValueRoute = "onNewValue"
|
|||||||
const val onValueRemovedRoute = "onValueRemoved"
|
const val onValueRemovedRoute = "onValueRemoved"
|
||||||
const val setRoute = "set"
|
const val setRoute = "set"
|
||||||
const val unsetRoute = "unset"
|
const val unsetRoute = "unset"
|
||||||
|
const val unsetWithValuesRoute = "unsetWithValues"
|
@@ -19,6 +19,7 @@ fun <K, V> Route.configureWriteStandardKeyValueRepoRoutes (
|
|||||||
) {
|
) {
|
||||||
val keyValueMapSerializer = MapSerializer(keySerializer, valueSerializer)
|
val keyValueMapSerializer = MapSerializer(keySerializer, valueSerializer)
|
||||||
val keysListSerializer = ListSerializer(keySerializer)
|
val keysListSerializer = ListSerializer(keySerializer)
|
||||||
|
val valuesListSerializer = ListSerializer(valueSerializer)
|
||||||
unifiedRouter.apply {
|
unifiedRouter.apply {
|
||||||
includeWebsocketHandling(
|
includeWebsocketHandling(
|
||||||
onNewValueRoute,
|
onNewValueRoute,
|
||||||
@@ -50,6 +51,14 @@ fun <K, V> Route.configureWriteStandardKeyValueRepoRoutes (
|
|||||||
unianswer(Unit.serializer(), originalRepo.unset(toUnset))
|
unianswer(Unit.serializer(), originalRepo.unset(toUnset))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
post(unsetWithValuesRoute) {
|
||||||
|
unifiedRouter.apply {
|
||||||
|
val toUnset = uniload(valuesListSerializer)
|
||||||
|
|
||||||
|
unianswer(Unit.serializer(), originalRepo.unsetWithValues(toUnset))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <K, V> Route.configureWriteStandartKeyValueRepoRoutes (
|
fun <K, V> Route.configureWriteStandartKeyValueRepoRoutes (
|
||||||
|
17
selector/common/build.gradle
Normal file
17
selector/common/build.gradle
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
plugins {
|
||||||
|
id "org.jetbrains.kotlin.multiplatform"
|
||||||
|
id "org.jetbrains.kotlin.plugin.serialization"
|
||||||
|
id "com.android.library"
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: "$mppProjectWithSerializationPresetPath"
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
sourceSets {
|
||||||
|
commonMain {
|
||||||
|
dependencies {
|
||||||
|
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,168 @@
|
|||||||
|
package dev.inmo.micro_utils.selector
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.*
|
||||||
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unified interface which can be used in any system which require some selection functionality
|
||||||
|
*/
|
||||||
|
interface Selector<T> {
|
||||||
|
val selectedItems: List<T>
|
||||||
|
val itemSelected: SharedFlow<T>
|
||||||
|
val itemUnselected: SharedFlow<T>
|
||||||
|
|
||||||
|
suspend fun toggleSelection(element: T)
|
||||||
|
suspend fun forceSelect(element: T)
|
||||||
|
suspend fun forceDeselect(element: T)
|
||||||
|
suspend fun clearSelection()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
|
inline operator fun <T> Selector<T>.contains(element: T) = selectedItems.contains(element)
|
||||||
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
|
inline fun <T> Selector<T>.nothingSelected(): Boolean = selectedItems.isEmpty()
|
||||||
|
suspend inline fun <T> Selector<T>.toggleSelection(elements: List<T>) = elements.forEach { toggleSelection(it) }
|
||||||
|
suspend inline fun <T> Selector<T>.forceSelect(elements: List<T>) = elements.forEach { forceSelect(it) }
|
||||||
|
suspend inline fun <T> Selector<T>.forceDeselect(elements: List<T>) = elements.forEach { forceDeselect(it) }
|
||||||
|
suspend inline fun <T> Selector<T>.toggleSelection(firstElement: T, vararg elements: T) = toggleSelection(listOf(firstElement) + elements.toList())
|
||||||
|
suspend inline fun <T> Selector<T>.forceSelect(firstElement: T, vararg elements: T) = forceSelect(listOf(firstElement) + elements.toList())
|
||||||
|
suspend inline fun <T> Selector<T>.forceDeselect(firstElement: T, vararg elements: T) = forceDeselect(listOf(firstElement) + elements.toList())
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Realization of [Selector] with one or without selected element. This realization will always have empty
|
||||||
|
* [selectedItems] when nothing selected and one element in [selectedItems] when something selected. Contains
|
||||||
|
* [selectedItem] value for simple access to currently selected item.
|
||||||
|
*
|
||||||
|
* On calling of [toggleSelection] previous selection will be erased and [itemUnselected] will emit this element.
|
||||||
|
*
|
||||||
|
* @param safeChanges Set to false to disable using of [mutex] for synchronizing changes on [toggleSelection]
|
||||||
|
*/
|
||||||
|
class SingleSelector<T>(
|
||||||
|
selectedItem: T? = null,
|
||||||
|
safeChanges: Boolean = true
|
||||||
|
) : Selector<T> {
|
||||||
|
var selectedItem: T? = selectedItem
|
||||||
|
private set
|
||||||
|
override val selectedItems: List<T>
|
||||||
|
get() = selectedItem ?.let { listOf(it) } ?: emptyList()
|
||||||
|
|
||||||
|
private val _itemSelected = MutableSharedFlow<T>()
|
||||||
|
override val itemSelected: SharedFlow<T> = _itemSelected.asSharedFlow()
|
||||||
|
private val _itemUnselected = MutableSharedFlow<T>()
|
||||||
|
override val itemUnselected: SharedFlow<T> = _itemUnselected.asSharedFlow()
|
||||||
|
|
||||||
|
private val mutex = if (safeChanges) {
|
||||||
|
Mutex()
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun forceDeselect(element: T) {
|
||||||
|
mutex ?.lock()
|
||||||
|
if (selectedItem == element) {
|
||||||
|
selectedItem = null
|
||||||
|
_itemUnselected.emit(element)
|
||||||
|
}
|
||||||
|
mutex ?.unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun forceSelect(element: T) {
|
||||||
|
mutex ?.lock()
|
||||||
|
if (selectedItem != element) {
|
||||||
|
selectedItem = element
|
||||||
|
_itemSelected.emit(element)
|
||||||
|
}
|
||||||
|
mutex ?.unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun toggleSelection(element: T) {
|
||||||
|
mutex ?.lock()
|
||||||
|
if (selectedItem == element) {
|
||||||
|
selectedItem = null
|
||||||
|
_itemUnselected.emit(element)
|
||||||
|
} else {
|
||||||
|
val previouslySelected = selectedItem
|
||||||
|
selectedItem = null
|
||||||
|
if (previouslySelected != null) {
|
||||||
|
_itemUnselected.emit(previouslySelected)
|
||||||
|
}
|
||||||
|
selectedItem = element
|
||||||
|
_itemSelected.emit(element)
|
||||||
|
}
|
||||||
|
mutex ?.unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun clearSelection() {
|
||||||
|
selectedItem ?.let { forceDeselect(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Realization of [Selector] with multiple selected elements. On calling of [toggleSelection] this realization will select passed element OR deselect it if it is already in
|
||||||
|
* [selectedItems]
|
||||||
|
*
|
||||||
|
* @param safeChanges Set to false to disable using of [mutex] for synchronizing changes on [toggleSelection]
|
||||||
|
*/
|
||||||
|
class MultipleSelector<T>(
|
||||||
|
selectedItems: List<T> = emptyList(),
|
||||||
|
safeChanges: Boolean = true
|
||||||
|
) : Selector<T> {
|
||||||
|
private val _selectedItems: MutableList<T> = selectedItems.toMutableList()
|
||||||
|
override val selectedItems: List<T> = _selectedItems
|
||||||
|
|
||||||
|
private val _itemSelected = MutableSharedFlow<T>()
|
||||||
|
override val itemSelected: SharedFlow<T> = _itemSelected.asSharedFlow()
|
||||||
|
private val _itemUnselected = MutableSharedFlow<T>()
|
||||||
|
override val itemUnselected: SharedFlow<T> = _itemUnselected.asSharedFlow()
|
||||||
|
|
||||||
|
private val mutex = if (safeChanges) {
|
||||||
|
Mutex()
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun forceDeselect(element: T) {
|
||||||
|
mutex ?.lock()
|
||||||
|
if (_selectedItems.remove(element)) {
|
||||||
|
_itemUnselected.emit(element)
|
||||||
|
}
|
||||||
|
mutex ?.unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun forceSelect(element: T) {
|
||||||
|
mutex ?.lock()
|
||||||
|
if (element !in _selectedItems && _selectedItems.add(element)) {
|
||||||
|
_itemSelected.emit(element)
|
||||||
|
}
|
||||||
|
mutex ?.unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun toggleSelection(element: T) {
|
||||||
|
mutex ?.lock()
|
||||||
|
if (_selectedItems.remove(element)) {
|
||||||
|
_itemUnselected.emit(element)
|
||||||
|
} else {
|
||||||
|
_selectedItems.add(element)
|
||||||
|
_itemSelected.emit(element)
|
||||||
|
}
|
||||||
|
mutex ?.unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun clearSelection() {
|
||||||
|
mutex ?.lock()
|
||||||
|
val preSelectedItems = _selectedItems.toList()
|
||||||
|
_selectedItems.clear()
|
||||||
|
preSelectedItems.forEach { _itemUnselected.emit(it) }
|
||||||
|
mutex ?.unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("FunctionName", "NOTHING_TO_INLINE")
|
||||||
|
inline fun <T> Selector(
|
||||||
|
multiple: Boolean,
|
||||||
|
safeChanges: Boolean = true
|
||||||
|
): Selector<T> = if (multiple) {
|
||||||
|
MultipleSelector(safeChanges = safeChanges)
|
||||||
|
} else {
|
||||||
|
SingleSelector(safeChanges = safeChanges)
|
||||||
|
}
|
@@ -0,0 +1,17 @@
|
|||||||
|
package dev.inmo.micro_utils.selector
|
||||||
|
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.flow.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Returned [SharedFlow] will emit true when [element] has been selected in [this] [Selector] and will emit
|
||||||
|
* false when this [element] was deselected
|
||||||
|
*
|
||||||
|
* @see [Selector]
|
||||||
|
* @see [Selector.itemSelected]
|
||||||
|
* @see [Selector.itemUnselected]
|
||||||
|
*/
|
||||||
|
fun <T> Selector<T>.itemSelectionFlow(element: T, scope: CoroutineScope): SharedFlow<Boolean> = MutableSharedFlow<Boolean>().apply {
|
||||||
|
itemSelected.onEach { if (it == element) emit(true) }.launchIn(scope)
|
||||||
|
itemUnselected.onEach { if (it == element) emit(false) }.launchIn(scope)
|
||||||
|
}.asSharedFlow()
|
1
selector/common/src/main/AndroidManifest.xml
Normal file
1
selector/common/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<manifest package="dev.inmo.micro_utils.selector"/>
|
@@ -2,6 +2,7 @@ rootProject.name='micro_utils'
|
|||||||
|
|
||||||
String[] includes = [
|
String[] includes = [
|
||||||
":common",
|
":common",
|
||||||
|
":selector:common",
|
||||||
":pagination:common",
|
":pagination:common",
|
||||||
":pagination:exposed",
|
":pagination:exposed",
|
||||||
":pagination:ktor:common",
|
":pagination:ktor:common",
|
||||||
|
Reference in New Issue
Block a user