mirror of
				https://github.com/InsanusMokrassar/MicroUtils.git
				synced 2025-10-26 17:50:41 +00:00 
			
		
		
		
	Compare commits
	
		
			48 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| ac587a67e6 | |||
| 59428140a8 | |||
| 60bdb59d71 | |||
| be52871de8 | |||
| b7934cf357 | |||
| dbfbeef90a | |||
| 00943c9cdf | |||
| 8745c6a16a | |||
| 433ba4b58f | |||
| d40376e524 | |||
| a2982f88f5 | |||
| 1642f7abd9 | |||
| a10d2184ff | |||
| 522435f096 | |||
| 79b30290c0 | |||
| f8b8626859 | |||
| b061b85a08 | |||
| 3870db1c88 | |||
| 1be1070eb4 | |||
| 2696e663cf | |||
| 1e1f7df86d | |||
| 1d8ded8fd3 | |||
| 197825123a | |||
| 422b2e6db1 | |||
| 1973e0b5bf | |||
| 8258cf93a9 | |||
| 1d49bd5947 | |||
| 44317d1519 | |||
| 48e08fcc69 | |||
| 1a3ce6e623 | |||
| fb122f3e70 | |||
| 7cca6039cc | |||
| 118e3dba39 | |||
| 87070710fa | |||
| 38499c3d4a | |||
| 5d754d968b | |||
| d543d436bc | |||
| 12b54f99af | |||
| 2a95d7e643 | |||
| e3d3cacfa4 | |||
| 4b13491a0e | |||
| 85d516d1e9 | |||
| ac58b6a7e3 | |||
| 2cc6126765 | |||
| f94b085850 | |||
| c9822a491b | |||
| 23b2d60295 | |||
| f4bc9eed39 | 
							
								
								
									
										79
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										79
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -1,5 +1,84 @@ | |||||||
| # Changelog | # Changelog | ||||||
|  |  | ||||||
|  | ## 0.12.15 | ||||||
|  |  | ||||||
|  | * `Common`: | ||||||
|  |     * `applyDiff` will return `Diff` object since this release | ||||||
|  |     * `Android`: | ||||||
|  |       * New functions/extensions `findViewsByTag` and `findViewsByTagInActivity` | ||||||
|  | * `Coroutines`: | ||||||
|  |     * Add `Flow` extensions `flatMap`, `flatMapNotNull` and `flatten` | ||||||
|  |     * Add `Flow` extensions `takeNotNull` and `filterNotNull` | ||||||
|  |  | ||||||
|  | ## 0.12.14 | ||||||
|  |  | ||||||
|  | * `Versions`: | ||||||
|  |   * `Android CoreKTX`: `1.8.0` -> `1.9.0` | ||||||
|  |   * `Android AppCompat`: `1.4.2` -> `1.5.1` | ||||||
|  |   * Android Compile SDK: 32 -> 33 | ||||||
|  |   * Android Build Tools: 32.0.0 -> 33.0.0 | ||||||
|  | * `Common`: | ||||||
|  |   * `Android`: | ||||||
|  |     * Add `argumentOrNull`/`argumentOrThrow` delegates for fragments | ||||||
|  | * `Coroutines`: | ||||||
|  |   * Rewrite `awaitFirstWithDeferred` onto `CompletableDeferred` instead of coroutines suspending | ||||||
|  |  | ||||||
|  | ## 0.12.13 | ||||||
|  |  | ||||||
|  | * `Coroutines`: | ||||||
|  |   * Add opportunity to use markers in actors (solution of [#160](https://github.com/InsanusMokrassar/MicroUtils/issues/160)) | ||||||
|  | * `Koin`: | ||||||
|  |   * Module inited :) | ||||||
|  | * `Repos`: | ||||||
|  |   * `Android`: | ||||||
|  |     * Add typealias `KeyValueSPRepo` and opportunity to create shared preferences `KeyValue` repo with `KeyValueStore(...)` (fix of [#155](https://github.com/InsanusMokrassar/MicroUtils/issues/155)) | ||||||
|  |  | ||||||
|  | ## 0.12.12 | ||||||
|  |  | ||||||
|  | * `Common`: | ||||||
|  |   * `Compose`: | ||||||
|  |     * `JS`: | ||||||
|  |       * Add `SkeletonAnimation` stylesheet | ||||||
|  |  | ||||||
|  | ## 0.12.11 | ||||||
|  |  | ||||||
|  | * `Repos`: | ||||||
|  |     * `Cache`: | ||||||
|  |         * Override `KeyValue` cache method `values` | ||||||
|  |  | ||||||
|  | ## 0.12.10 | ||||||
|  |  | ||||||
|  | * `Repos`: | ||||||
|  |     * `Cache`: | ||||||
|  |         * Hotfix in key values `get` | ||||||
|  |  | ||||||
|  | ## 0.12.9 | ||||||
|  |  | ||||||
|  | * `Versions`: | ||||||
|  |   * `Klock`: `3.0.0` -> `3.1.0` | ||||||
|  | * `Repos`: | ||||||
|  |     * `Cache`: | ||||||
|  |       * Fixes in key values cache | ||||||
|  |  | ||||||
|  | ## 0.12.8 | ||||||
|  |  | ||||||
|  | * `Versions`: | ||||||
|  |     * `Ktor`: `2.1.0` -> `2.1.1` | ||||||
|  |     * `Compose`: `1.2.0-alpha01-dev764` -> `1.2.0-alpha01-dev774` | ||||||
|  | * `Ktor`: | ||||||
|  |   * `Client`: | ||||||
|  |     * New extension `HttpClient#bodyOrNull` which returns `null` in case when server responded with `No Content` (204) | ||||||
|  |   * `Server`: | ||||||
|  |     * New extension `ApplicationCall#respondOrNoContent` which responds `No Content` (204) when passed data is null | ||||||
|  |  | ||||||
|  | ## 0.12.7 | ||||||
|  |  | ||||||
|  | * `Repos`: | ||||||
|  |   * `Cache`: | ||||||
|  |     * Force `WriteCRUDCacheRepo` to subscribe on new and updated objects of parent repo | ||||||
|  | * `Pagination`: | ||||||
|  |   * New function `changeResultsUnchecked(Pagination)` | ||||||
|  |  | ||||||
| ## 0.12.6 | ## 0.12.6 | ||||||
|  |  | ||||||
| * `MimeeTypes>`: | * `MimeeTypes>`: | ||||||
|   | |||||||
| @@ -16,6 +16,7 @@ kotlin { | |||||||
|         androidMain { |         androidMain { | ||||||
|             dependencies { |             dependencies { | ||||||
|                 api project(":micro_utils.coroutines") |                 api project(":micro_utils.coroutines") | ||||||
|  |                 api libs.android.fragment | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -0,0 +1,43 @@ | |||||||
|  | package dev.inmo.micro_utils.common.compose | ||||||
|  |  | ||||||
|  | import org.jetbrains.compose.web.css.* | ||||||
|  |  | ||||||
|  | object SkeletonAnimation : StyleSheet() { | ||||||
|  |     val skeletonKeyFrames: CSSNamedKeyframes by keyframes { | ||||||
|  |         to { backgroundPosition("-20% 0") } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun CSSBuilder.includeSkeletonStyle( | ||||||
|  |         duration: CSSSizeValue<out CSSUnitTime> = 2.s, | ||||||
|  |         timingFunction: AnimationTimingFunction = AnimationTimingFunction.EaseInOut, | ||||||
|  |         iterationCount: Int? = null, | ||||||
|  |         direction: AnimationDirection = AnimationDirection.Normal, | ||||||
|  |         keyFrames: CSSNamedKeyframes = skeletonKeyFrames, | ||||||
|  |         hideChildren: Boolean = true, | ||||||
|  |         hideText: Boolean = hideChildren | ||||||
|  |     ) { | ||||||
|  |         backgroundImage("linear-gradient(110deg, rgb(236, 236, 236) 40%, rgb(245, 245, 245) 50%, rgb(236, 236, 236) 65%)") | ||||||
|  |         backgroundSize("200% 100%") | ||||||
|  |         backgroundPosition("180% 0") | ||||||
|  |         animation(keyFrames) { | ||||||
|  |             duration(duration) | ||||||
|  |             timingFunction(timingFunction) | ||||||
|  |             iterationCount(iterationCount) | ||||||
|  |             direction(direction) | ||||||
|  |         } | ||||||
|  |         if (hideText) { | ||||||
|  |             property("color", "${Color.transparent} !important") | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (hideChildren) { | ||||||
|  |             child(self, universal) style { | ||||||
|  |                 property("visibility", "hidden") | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     val skeleton by style { | ||||||
|  |         includeSkeletonStyle() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| @@ -14,6 +14,14 @@ private inline fun <T> getObject( | |||||||
| /** | /** | ||||||
|  * Diff object which contains information about differences between two [Iterable]s |  * Diff object which contains information about differences between two [Iterable]s | ||||||
|  * |  * | ||||||
|  |  * See tests for more info | ||||||
|  |  * | ||||||
|  |  * @param removed The objects which has been presented in the old collection but absent in new one. Index here is the index in the old collection | ||||||
|  |  * @param added The object which appear in new collection only. Indexes here show the index in the new collection | ||||||
|  |  * @param replaced Pair of old-new changes. First object has been presented in the old collection on its | ||||||
|  |  * [IndexedValue.index] place, the second one is the object in new collection. Both have indexes due to the fact that in | ||||||
|  |  * case when some value has been replaced after adds or removes in original collection the object index will be changed | ||||||
|  |  * | ||||||
|  * @see calculateDiff |  * @see calculateDiff | ||||||
|  */ |  */ | ||||||
| data class Diff<T> internal constructor( | data class Diff<T> internal constructor( | ||||||
| @@ -165,7 +173,7 @@ inline fun <T> Iterable<T>.calculateStrictDiff( | |||||||
| fun <T> MutableList<T>.applyDiff( | fun <T> MutableList<T>.applyDiff( | ||||||
|     source: Iterable<T>, |     source: Iterable<T>, | ||||||
|     strictComparison: Boolean = false |     strictComparison: Boolean = false | ||||||
| ) = calculateDiff(source, strictComparison).let { | ): Diff<T> = calculateDiff(source, strictComparison).also { | ||||||
|     for (i in it.removed.indices.sortedDescending()) { |     for (i in it.removed.indices.sortedDescending()) { | ||||||
|         removeAt(it.removed[i].index) |         removeAt(it.removed[i].index) | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -0,0 +1,76 @@ | |||||||
|  | package dev.inmo.micro_utils.common | ||||||
|  |  | ||||||
|  | import android.os.Bundle | ||||||
|  | import android.os.Parcelable | ||||||
|  | import androidx.fragment.app.Fragment | ||||||
|  | import java.io.Serializable | ||||||
|  | import kotlin.reflect.KProperty | ||||||
|  |  | ||||||
|  | object ArgumentPropertyNullableDelegate { | ||||||
|  |     operator fun <T: Any> getValue(thisRef: Fragment, property: KProperty<*>): T? { | ||||||
|  |         val arguments = thisRef.arguments ?: return null | ||||||
|  |         val key = property.name | ||||||
|  |         return when (property.getter.returnType.classifier) { | ||||||
|  |             // Scalars | ||||||
|  |             String::class -> arguments.getString(key) | ||||||
|  |             Boolean::class -> arguments.getBoolean(key) | ||||||
|  |             Byte::class -> arguments.getByte(key) | ||||||
|  |             Char::class -> arguments.getChar(key) | ||||||
|  |             Double::class -> arguments.getDouble(key) | ||||||
|  |             Float::class -> arguments.getFloat(key) | ||||||
|  |             Int::class -> arguments.getInt(key) | ||||||
|  |             Long::class -> arguments.getLong(key) | ||||||
|  |             Short::class -> arguments.getShort(key) | ||||||
|  |  | ||||||
|  |             // References | ||||||
|  |             Bundle::class -> arguments.getBundle(key) | ||||||
|  |             CharSequence::class -> arguments.getCharSequence(key) | ||||||
|  |             Parcelable::class -> arguments.getParcelable(key) | ||||||
|  |  | ||||||
|  |             // Scalar arrays | ||||||
|  |             BooleanArray::class -> arguments.getBooleanArray(key) | ||||||
|  |             ByteArray::class -> arguments.getByteArray(key) | ||||||
|  |             CharArray::class -> arguments.getCharArray(key) | ||||||
|  |             DoubleArray::class -> arguments.getDoubleArray(key) | ||||||
|  |             FloatArray::class -> arguments.getFloatArray(key) | ||||||
|  |             IntArray::class -> arguments.getIntArray(key) | ||||||
|  |             LongArray::class -> arguments.getLongArray(key) | ||||||
|  |             ShortArray::class -> arguments.getShortArray(key) | ||||||
|  |             Array::class -> { | ||||||
|  |                 val componentType = property.returnType.classifier ?.javaClass ?.componentType!! | ||||||
|  |                 @Suppress("UNCHECKED_CAST") // Checked by reflection. | ||||||
|  |                 when { | ||||||
|  |                     Parcelable::class.java.isAssignableFrom(componentType) -> { | ||||||
|  |                         arguments.getParcelableArray(key) | ||||||
|  |                     } | ||||||
|  |                     String::class.java.isAssignableFrom(componentType) -> { | ||||||
|  |                         arguments.getStringArray(key) | ||||||
|  |                     } | ||||||
|  |                     CharSequence::class.java.isAssignableFrom(componentType) -> { | ||||||
|  |                         arguments.getCharSequenceArray(key) | ||||||
|  |                     } | ||||||
|  |                     Serializable::class.java.isAssignableFrom(componentType) -> { | ||||||
|  |                         arguments.getSerializable(key) | ||||||
|  |                     } | ||||||
|  |                     else -> { | ||||||
|  |                         val valueType = componentType.canonicalName | ||||||
|  |                         throw IllegalArgumentException( | ||||||
|  |                             "Illegal value array type $valueType for key \"$key\"" | ||||||
|  |                         ) | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             Serializable::class -> arguments.getSerializable(key) | ||||||
|  |             else -> null | ||||||
|  |         } as? T | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | object ArgumentPropertyNonNullableDelegate { | ||||||
|  |     operator fun <T: Any> getValue(thisRef: Fragment, property: KProperty<*>): T { | ||||||
|  |         return ArgumentPropertyNullableDelegate.getValue<T>(thisRef, property)!! | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fun argumentOrNull() = ArgumentPropertyNullableDelegate | ||||||
|  | fun argumentOrThrow() = ArgumentPropertyNonNullableDelegate | ||||||
| @@ -0,0 +1,61 @@ | |||||||
|  | package dev.inmo.micro_utils.common | ||||||
|  |  | ||||||
|  | import android.app.Activity | ||||||
|  | import android.view.View | ||||||
|  | import android.view.ViewGroup | ||||||
|  | import androidx.core.view.children | ||||||
|  | import androidx.fragment.app.Fragment | ||||||
|  |  | ||||||
|  | fun findViewsByTag(viewGroup: ViewGroup, tag: Any?): List<View> { | ||||||
|  |     return viewGroup.children.flatMap { | ||||||
|  |         findViewsByTag(it, tag) | ||||||
|  |     }.toList() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fun findViewsByTag(viewGroup: ViewGroup, key: Int, tag: Any?): List<View> { | ||||||
|  |     return viewGroup.children.flatMap { | ||||||
|  |         findViewsByTag(it, key, tag) | ||||||
|  |     }.toList() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fun findViewsByTag(view: View, tag: Any?): List<View> { | ||||||
|  |     val result = mutableListOf<View>() | ||||||
|  |     if (view.tag == tag) { | ||||||
|  |         result.add(view) | ||||||
|  |     } | ||||||
|  |     if (view is ViewGroup) { | ||||||
|  |         result.addAll(findViewsByTag(view, tag)) | ||||||
|  |     } | ||||||
|  |     return result.toList() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fun findViewsByTag(view: View, key: Int, tag: Any?): List<View> { | ||||||
|  |     val result = mutableListOf<View>() | ||||||
|  |     if (view.getTag(key) == tag) { | ||||||
|  |         result.add(view) | ||||||
|  |     } | ||||||
|  |     if (view is ViewGroup) { | ||||||
|  |         result.addAll(findViewsByTag(view, key, tag)) | ||||||
|  |     } | ||||||
|  |     return result.toList() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fun Activity.findViewsByTag(tag: Any?) = rootView ?.let { | ||||||
|  |     findViewsByTag(it, tag) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fun Activity.findViewsByTag(key: Int, tag: Any?) = rootView ?.let { | ||||||
|  |     findViewsByTag(it, key, tag) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fun Fragment.findViewsByTag(tag: Any?) = view ?.let { | ||||||
|  |     findViewsByTag(it, tag) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fun Fragment.findViewsByTag(key: Int, tag: Any?) = view ?.let { | ||||||
|  |     findViewsByTag(it, key, tag) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fun Fragment.findViewsByTagInActivity(tag: Any?) = activity ?.findViewsByTag(tag) | ||||||
|  |  | ||||||
|  | fun Fragment.findViewsByTagInActivity(key: Int, tag: Any?) = activity ?.findViewsByTag(key, tag) | ||||||
| @@ -0,0 +1,7 @@ | |||||||
|  | package dev.inmo.micro_utils.common | ||||||
|  |  | ||||||
|  | import android.app.Activity | ||||||
|  | import android.view.View | ||||||
|  |  | ||||||
|  | val Activity.rootView: View? | ||||||
|  |     get() = findViewById<View?>(android.R.id.content) ?.rootView ?: window.decorView.findViewById<View?>(android.R.id.content) ?.rootView | ||||||
| @@ -1,19 +1,15 @@ | |||||||
| package dev.inmo.micro_utils.coroutines | package dev.inmo.micro_utils.coroutines | ||||||
|  |  | ||||||
| import kotlinx.coroutines.CoroutineScope | import kotlinx.coroutines.* | ||||||
| import kotlinx.coroutines.channels.Channel | import kotlinx.coroutines.channels.Channel | ||||||
| import kotlinx.coroutines.launch | import kotlinx.coroutines.flow.consumeAsFlow | ||||||
|  |  | ||||||
| fun <T> CoroutineScope.actor( | fun <T> CoroutineScope.actor( | ||||||
|     channelCapacity: Int = Channel.UNLIMITED, |     channelCapacity: Int = Channel.UNLIMITED, | ||||||
|     block: suspend (T) -> Unit |     block: suspend (T) -> Unit | ||||||
| ): Channel<T> { | ): Channel<T> { | ||||||
|     val channel = Channel<T>(channelCapacity) |     val channel = Channel<T>(channelCapacity) | ||||||
|     launch { |     channel.consumeAsFlow().subscribe(this, block) | ||||||
|         for (data in channel) { |  | ||||||
|             block(data) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     return channel |     return channel | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -0,0 +1,30 @@ | |||||||
|  | package dev.inmo.micro_utils.coroutines | ||||||
|  |  | ||||||
|  | import kotlinx.coroutines.* | ||||||
|  | import kotlinx.coroutines.channels.Channel | ||||||
|  | import kotlinx.coroutines.flow.consumeAsFlow | ||||||
|  |  | ||||||
|  | fun <T> CoroutineScope.actorAsync( | ||||||
|  |     channelCapacity: Int = Channel.UNLIMITED, | ||||||
|  |     markerFactory: suspend (T) -> Any? = { null }, | ||||||
|  |     block: suspend (T) -> Unit | ||||||
|  | ): Channel<T> { | ||||||
|  |     val channel = Channel<T>(channelCapacity) | ||||||
|  |     channel.consumeAsFlow().subscribeAsync(this, markerFactory, block) | ||||||
|  |     return channel | ||||||
|  | } | ||||||
|  |  | ||||||
|  | inline fun <T> CoroutineScope.safeActorAsync( | ||||||
|  |     channelCapacity: Int = Channel.UNLIMITED, | ||||||
|  |     noinline onException: ExceptionHandler<Unit> = defaultSafelyExceptionHandler, | ||||||
|  |     noinline markerFactory: suspend (T) -> Any? = { null }, | ||||||
|  |     crossinline block: suspend (T) -> Unit | ||||||
|  | ): Channel<T> = actorAsync( | ||||||
|  |     channelCapacity, | ||||||
|  |     markerFactory | ||||||
|  | ) { | ||||||
|  |     safely(onException) { | ||||||
|  |         block(it) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| @@ -6,23 +6,19 @@ import kotlin.coroutines.* | |||||||
| suspend fun <T> Iterable<Deferred<T>>.awaitFirstWithDeferred( | suspend fun <T> Iterable<Deferred<T>>.awaitFirstWithDeferred( | ||||||
|     scope: CoroutineScope, |     scope: CoroutineScope, | ||||||
|     cancelOnResult: Boolean = true |     cancelOnResult: Boolean = true | ||||||
| ): Pair<Deferred<T>, T> = suspendCoroutine<Pair<Deferred<T>, T>> { continuation -> | ): Pair<Deferred<T>, T> { | ||||||
|     scope.launch(SupervisorJob()) { |     val resultDeferred = CompletableDeferred<Pair<Deferred<T>, T>>() | ||||||
|         val scope = this |     val scope = scope.LinkedSupervisorScope() | ||||||
|         forEach { |     forEach { | ||||||
|             scope.launch { |         scope.launch { | ||||||
|                 continuation.resume(it to it.await()) |             resultDeferred.complete(it to it.await()) | ||||||
|                 scope.cancel() |             scope.cancel() | ||||||
|             } |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| }.also { |     return resultDeferred.await().also { | ||||||
|     if (cancelOnResult) { |         if (cancelOnResult) { | ||||||
|         forEach { |             forEach { | ||||||
|             try { |                 runCatchingSafely { it.cancel() } | ||||||
|                 it.cancel() |  | ||||||
|             } catch (e: IllegalStateException) { |  | ||||||
|                 e.printStackTrace() |  | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -0,0 +1,39 @@ | |||||||
|  | package dev.inmo.micro_utils.coroutines | ||||||
|  |  | ||||||
|  | import kotlinx.coroutines.flow.* | ||||||
|  | import kotlin.js.JsName | ||||||
|  | import kotlin.jvm.JvmName | ||||||
|  |  | ||||||
|  | inline fun <T, R> Flow<Flow<T>>.flatMap( | ||||||
|  |     crossinline mapper: suspend (T) -> R | ||||||
|  | ) = flow { | ||||||
|  |     collect { | ||||||
|  |         it.collect { | ||||||
|  |             emit(mapper(it)) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @JsName("flatMapIterable") | ||||||
|  | @JvmName("flatMapIterable") | ||||||
|  | inline fun <T, R> Flow<Iterable<T>>.flatMap( | ||||||
|  |     crossinline mapper: suspend (T) -> R | ||||||
|  | ) = map { | ||||||
|  |     it.asFlow() | ||||||
|  | }.flatMap(mapper) | ||||||
|  |  | ||||||
|  | inline fun <T, R> Flow<Flow<T>>.flatMapNotNull( | ||||||
|  |     crossinline mapper: suspend (T) -> R | ||||||
|  | ) = flatMap(mapper).takeNotNull() | ||||||
|  |  | ||||||
|  | @JsName("flatMapNotNullIterable") | ||||||
|  | @JvmName("flatMapNotNullIterable") | ||||||
|  | inline fun <T, R> Flow<Iterable<T>>.flatMapNotNull( | ||||||
|  |     crossinline mapper: suspend (T) -> R | ||||||
|  | ) = flatMap(mapper).takeNotNull() | ||||||
|  |  | ||||||
|  | fun <T> Flow<Flow<T>>.flatten() = flatMap { it } | ||||||
|  |  | ||||||
|  | @JsName("flattenIterable") | ||||||
|  | @JvmName("flattenIterable") | ||||||
|  | fun <T> Flow<Iterable<T>>.flatten() = flatMap { it } | ||||||
| @@ -0,0 +1,6 @@ | |||||||
|  | package dev.inmo.micro_utils.coroutines | ||||||
|  |  | ||||||
|  | import kotlinx.coroutines.flow.* | ||||||
|  |  | ||||||
|  | fun <T> Flow<T>.takeNotNull() = mapNotNull { it } | ||||||
|  | fun <T> Flow<T>.filterNotNull() = takeNotNull() | ||||||
| @@ -14,5 +14,5 @@ crypto_js_version=4.1.1 | |||||||
| # Project data | # Project data | ||||||
|  |  | ||||||
| group=dev.inmo | group=dev.inmo | ||||||
| version=0.12.6 | version=0.12.15 | ||||||
| android_code_version=145 | android_code_version=154 | ||||||
|   | |||||||
| @@ -4,33 +4,37 @@ kt = "1.7.10" | |||||||
| kt-serialization = "1.4.0" | kt-serialization = "1.4.0" | ||||||
| kt-coroutines = "1.6.4" | kt-coroutines = "1.6.4" | ||||||
|  |  | ||||||
| jb-compose = "1.2.0-alpha01-dev764" | jb-compose = "1.2.0-alpha01-dev774" | ||||||
| jb-exposed = "0.39.2" | jb-exposed = "0.39.2" | ||||||
| jb-dokka = "1.7.10" | jb-dokka = "1.7.10" | ||||||
|  |  | ||||||
| klock = "3.0.0" | klock = "3.1.0" | ||||||
| uuid = "0.5.0" | uuid = "0.5.0" | ||||||
|  |  | ||||||
| ktor = "2.1.0" | ktor = "2.1.1" | ||||||
|  |  | ||||||
| gh-release = "2.4.1" | gh-release = "2.4.1" | ||||||
|  |  | ||||||
|  | koin = "3.2.0" | ||||||
|  |  | ||||||
| android-gradle = "7.2.2" | android-gradle = "7.2.2" | ||||||
| dexcount = "3.1.0" | dexcount = "3.1.0" | ||||||
|  |  | ||||||
| android-coreKtx = "1.8.0" | android-coreKtx = "1.9.0" | ||||||
| android-recyclerView = "1.2.1" | android-recyclerView = "1.2.1" | ||||||
| android-appCompat = "1.4.2" | android-appCompat = "1.5.1" | ||||||
|  | android-fragment = "1.5.3" | ||||||
| android-espresso = "3.4.0" | android-espresso = "3.4.0" | ||||||
| android-test = "1.1.3" | android-test = "1.1.3" | ||||||
|  |  | ||||||
| android-props-minSdk = "21" | android-props-minSdk = "21" | ||||||
| android-props-compileSdk = "32" | android-props-compileSdk = "33" | ||||||
| android-props-buildTools = "32.0.0" | android-props-buildTools = "33.0.0" | ||||||
|  |  | ||||||
| [libraries] | [libraries] | ||||||
|  |  | ||||||
| kt-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kt" } | kt-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kt" } | ||||||
|  | kt-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kt" } | ||||||
|  |  | ||||||
| kt-serialization = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kt-serialization" } | kt-serialization = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kt-serialization" } | ||||||
| kt-serialization-cbor = { module = "org.jetbrains.kotlinx:kotlinx-serialization-cbor", version.ref = "kt-serialization" } | kt-serialization-cbor = { module = "org.jetbrains.kotlinx:kotlinx-serialization-cbor", version.ref = "kt-serialization" } | ||||||
| @@ -59,6 +63,7 @@ ktor-server-content-negotiation = { module = "io.ktor:ktor-server-content-negoti | |||||||
|  |  | ||||||
| klock = { module = "com.soywiz.korlibs.klock:klock", version.ref = "klock" } | klock = { module = "com.soywiz.korlibs.klock:klock", version.ref = "klock" } | ||||||
| uuid = { module = "com.benasher44:uuid", version.ref = "uuid" } | uuid = { module = "com.benasher44:uuid", version.ref = "uuid" } | ||||||
|  | koin = { module = "io.insert-koin:koin-core", version.ref = "koin" } | ||||||
|  |  | ||||||
|  |  | ||||||
| jb-exposed = { module = "org.jetbrains.exposed:exposed-core", version.ref = "jb-exposed" } | jb-exposed = { module = "org.jetbrains.exposed:exposed-core", version.ref = "jb-exposed" } | ||||||
| @@ -67,6 +72,7 @@ jb-exposed = { module = "org.jetbrains.exposed:exposed-core", version.ref = "jb- | |||||||
| android-coreKtx = { module = "androidx.core:core-ktx", version.ref = "android-coreKtx" } | android-coreKtx = { module = "androidx.core:core-ktx", version.ref = "android-coreKtx" } | ||||||
| android-recyclerView = { module = "androidx.recyclerview:recyclerview", version.ref = "android-recyclerView" } | android-recyclerView = { module = "androidx.recyclerview:recyclerview", version.ref = "android-recyclerView" } | ||||||
| android-appCompat-resources = { module = "androidx.appcompat:appcompat-resources", version.ref = "android-appCompat" } | android-appCompat-resources = { module = "androidx.appcompat:appcompat-resources", version.ref = "android-appCompat" } | ||||||
|  | android-fragment = { module = "androidx.fragment:fragment", version.ref = "android-fragment" } | ||||||
| android-espresso = { module = "androidx.test.espresso:espresso-core", version.ref = "android-espresso" } | android-espresso = { module = "androidx.test.espresso:espresso-core", version.ref = "android-espresso" } | ||||||
| android-test-junit = { module = "androidx.test.ext:junit", version.ref = "android-test" } | android-test-junit = { module = "androidx.test.ext:junit", version.ref = "android-test" } | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										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-7.5-bin.zip | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip | ||||||
| zipStoreBase=GRADLE_USER_HOME | zipStoreBase=GRADLE_USER_HOME | ||||||
| zipStorePath=wrapper/dists | zipStorePath=wrapper/dists | ||||||
|   | |||||||
							
								
								
									
										28
									
								
								koin/build.gradle
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								koin/build.gradle
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | |||||||
|  | plugins { | ||||||
|  |     id "org.jetbrains.kotlin.multiplatform" | ||||||
|  |     id "org.jetbrains.kotlin.plugin.serialization" | ||||||
|  |     id "com.android.library" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | apply from: "$mppProjectWithSerializationPresetPath" | ||||||
|  |  | ||||||
|  | kotlin { | ||||||
|  |     sourceSets { | ||||||
|  |         commonMain { | ||||||
|  |             dependencies { | ||||||
|  |                 api libs.koin | ||||||
|  |                 api libs.uuid | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         jvmMain { | ||||||
|  |             dependencies { | ||||||
|  |                 api libs.kt.reflect | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         androidMain { | ||||||
|  |             dependencies { | ||||||
|  |                 api libs.kt.reflect | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										12
									
								
								koin/src/commonMain/kotlin/FactoryWithRandomQualifier.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								koin/src/commonMain/kotlin/FactoryWithRandomQualifier.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | |||||||
|  | package dev.inmo.micro_utils.koin | ||||||
|  |  | ||||||
|  | import org.koin.core.definition.Definition | ||||||
|  | import org.koin.core.module.Module | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Will be useful in case you need to declare some singles with one type several types, but need to separate them and do | ||||||
|  |  * not care about how :) | ||||||
|  |  */ | ||||||
|  | inline fun <reified T : Any> Module.factoryWithRandomQualifier( | ||||||
|  |     noinline definition: Definition<T> | ||||||
|  | ) = factory(RandomQualifier(), definition) | ||||||
							
								
								
									
										12
									
								
								koin/src/commonMain/kotlin/FactoryWithStringQualifier.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								koin/src/commonMain/kotlin/FactoryWithStringQualifier.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | |||||||
|  | package dev.inmo.micro_utils.koin | ||||||
|  |  | ||||||
|  | import org.koin.core.definition.Definition | ||||||
|  | import org.koin.core.module.Module | ||||||
|  | import org.koin.core.qualifier.StringQualifier | ||||||
|  |  | ||||||
|  | inline fun <reified T : Any> Module.factory( | ||||||
|  |     qualifier: String, | ||||||
|  |     noinline definition: Definition<T> | ||||||
|  | ) = factory(StringQualifier(qualifier), definition) | ||||||
|  |  | ||||||
|  |  | ||||||
							
								
								
									
										8
									
								
								koin/src/commonMain/kotlin/GetAllDistinct.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								koin/src/commonMain/kotlin/GetAllDistinct.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | |||||||
|  | package dev.inmo.micro_utils.koin | ||||||
|  |  | ||||||
|  | import org.koin.core.Koin | ||||||
|  | import org.koin.core.scope.Scope | ||||||
|  |  | ||||||
|  | inline fun <reified T : Any> Scope.getAllDistinct() = getAll<T>().distinct() | ||||||
|  | inline fun <reified T : Any> Koin.getAllDistinct() = getAll<T>().distinct() | ||||||
|  |  | ||||||
							
								
								
									
										8
									
								
								koin/src/commonMain/kotlin/GetAny.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								koin/src/commonMain/kotlin/GetAny.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | |||||||
|  | package dev.inmo.micro_utils.koin | ||||||
|  |  | ||||||
|  | import org.koin.core.Koin | ||||||
|  | import org.koin.core.scope.Scope | ||||||
|  |  | ||||||
|  | inline fun <reified T : Any> Scope.getAny() = getAll<T>().first() | ||||||
|  | inline fun <reified T : Any> Koin.getAny() = getAll<T>().first() | ||||||
|  |  | ||||||
							
								
								
									
										6
									
								
								koin/src/commonMain/kotlin/RandomQualifier.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								koin/src/commonMain/kotlin/RandomQualifier.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | |||||||
|  | package dev.inmo.micro_utils.koin | ||||||
|  |  | ||||||
|  | import com.benasher44.uuid.uuid4 | ||||||
|  | import org.koin.core.qualifier.StringQualifier | ||||||
|  |  | ||||||
|  | fun RandomQualifier(randomFun: () -> String = { uuid4().toString() }) = StringQualifier(randomFun()) | ||||||
							
								
								
									
										13
									
								
								koin/src/commonMain/kotlin/SingleWithRandomQualifier.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								koin/src/commonMain/kotlin/SingleWithRandomQualifier.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | |||||||
|  | package dev.inmo.micro_utils.koin | ||||||
|  |  | ||||||
|  | import org.koin.core.definition.Definition | ||||||
|  | import org.koin.core.module.Module | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Will be useful in case you need to declare some singles with one type several types, but need to separate them and do | ||||||
|  |  * not care about how :) | ||||||
|  |  */ | ||||||
|  | inline fun <reified T : Any> Module.singleWithRandomQualifier( | ||||||
|  |     createdAtStart: Boolean = false, | ||||||
|  |     noinline definition: Definition<T> | ||||||
|  | ) = single(RandomQualifier(), createdAtStart, definition) | ||||||
							
								
								
									
										12
									
								
								koin/src/commonMain/kotlin/SingleWithStringQualifier.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								koin/src/commonMain/kotlin/SingleWithStringQualifier.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | |||||||
|  | package dev.inmo.micro_utils.koin | ||||||
|  |  | ||||||
|  | import org.koin.core.definition.Definition | ||||||
|  | import org.koin.core.module.Module | ||||||
|  | import org.koin.core.qualifier.StringQualifier | ||||||
|  |  | ||||||
|  | inline fun <reified T : Any> Module.single( | ||||||
|  |     qualifier: String, | ||||||
|  |     createdAtStart: Boolean = false, | ||||||
|  |     noinline definition: Definition<T> | ||||||
|  | ) = single(StringQualifier(qualifier), createdAtStart, definition) | ||||||
|  |  | ||||||
							
								
								
									
										27
									
								
								koin/src/jvmMain/kotlin/FactoryWithBinds.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								koin/src/jvmMain/kotlin/FactoryWithBinds.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | |||||||
|  | package dev.inmo.micro_utils.koin | ||||||
|  |  | ||||||
|  | import org.koin.core.definition.Definition | ||||||
|  | import org.koin.core.instance.InstanceFactory | ||||||
|  | import org.koin.core.module.Module | ||||||
|  | import org.koin.core.qualifier.Qualifier | ||||||
|  | import org.koin.dsl.binds | ||||||
|  | import kotlin.reflect.KClass | ||||||
|  | import kotlin.reflect.full.allSuperclasses | ||||||
|  |  | ||||||
|  |  | ||||||
|  | inline fun <reified T : Any> Module.factoryWithBinds( | ||||||
|  |     qualifier: Qualifier? = null, | ||||||
|  |     bindFilter: (KClass<*>) -> Boolean = { true }, | ||||||
|  |     noinline definition: Definition<T> | ||||||
|  | ): Pair<Module, InstanceFactory<*>> { | ||||||
|  |     return factory(qualifier, definition) binds (T::class.allSuperclasses.filter(bindFilter).toTypedArray()) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | inline fun <reified T : Any> Module.factoryWithBinds( | ||||||
|  |     qualifier: String, | ||||||
|  |     bindFilter: (KClass<*>) -> Boolean = { true }, | ||||||
|  |     noinline definition: Definition<T> | ||||||
|  | ): Pair<Module, InstanceFactory<*>> { | ||||||
|  |     return factory(qualifier, definition) binds (T::class.allSuperclasses.filter(bindFilter).toTypedArray()) | ||||||
|  | } | ||||||
|  |  | ||||||
| @@ -0,0 +1,13 @@ | |||||||
|  | package dev.inmo.micro_utils.koin | ||||||
|  |  | ||||||
|  | import org.koin.core.definition.Definition | ||||||
|  | import org.koin.core.instance.InstanceFactory | ||||||
|  | import org.koin.core.module.Module | ||||||
|  | import kotlin.reflect.KClass | ||||||
|  |  | ||||||
|  | inline fun <reified T : Any> Module.factoryWithRandomQualifierAndBinds( | ||||||
|  |     bindFilter: (KClass<*>) -> Boolean = { true }, | ||||||
|  |     noinline definition: Definition<T> | ||||||
|  | ): Pair<Module, InstanceFactory<*>> { | ||||||
|  |     return factoryWithBinds(RandomQualifier(), bindFilter, definition) | ||||||
|  | } | ||||||
							
								
								
									
										30
									
								
								koin/src/jvmMain/kotlin/SignleWithBinds.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								koin/src/jvmMain/kotlin/SignleWithBinds.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | |||||||
|  | package dev.inmo.micro_utils.koin | ||||||
|  |  | ||||||
|  | import org.koin.core.definition.Definition | ||||||
|  | import org.koin.core.instance.InstanceFactory | ||||||
|  | import org.koin.core.module.Module | ||||||
|  | import org.koin.core.qualifier.Qualifier | ||||||
|  | import org.koin.dsl.binds | ||||||
|  | import kotlin.reflect.KClass | ||||||
|  | import kotlin.reflect.full.allSuperclasses | ||||||
|  |  | ||||||
|  |  | ||||||
|  | inline fun <reified T : Any> Module.singleWithBinds( | ||||||
|  |     qualifier: Qualifier? = null, | ||||||
|  |     createdAtStart: Boolean = false, | ||||||
|  |     bindFilter: (KClass<*>) -> Boolean = { true }, | ||||||
|  |     noinline definition: Definition<T> | ||||||
|  | ): Pair<Module, InstanceFactory<*>> { | ||||||
|  |     return single(qualifier, createdAtStart, definition) binds (T::class.allSuperclasses.filter(bindFilter).toTypedArray()) | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | inline fun <reified T : Any> Module.singleWithBinds( | ||||||
|  |     qualifier: String, | ||||||
|  |     createdAtStart: Boolean = false, | ||||||
|  |     bindFilter: (KClass<*>) -> Boolean = { true }, | ||||||
|  |     noinline definition: Definition<T> | ||||||
|  | ): Pair<Module, InstanceFactory<*>> { | ||||||
|  |     return single(qualifier, createdAtStart, definition) binds (T::class.allSuperclasses.filter(bindFilter).toTypedArray()) | ||||||
|  | } | ||||||
|  |  | ||||||
							
								
								
									
										14
									
								
								koin/src/jvmMain/kotlin/SingleWithBindsAndRandomQualifier.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								koin/src/jvmMain/kotlin/SingleWithBindsAndRandomQualifier.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | |||||||
|  | package dev.inmo.micro_utils.koin | ||||||
|  |  | ||||||
|  | import org.koin.core.definition.Definition | ||||||
|  | import org.koin.core.instance.InstanceFactory | ||||||
|  | import org.koin.core.module.Module | ||||||
|  | import kotlin.reflect.KClass | ||||||
|  |  | ||||||
|  | inline fun <reified T : Any> Module.singleWithRandomQualifierAndBinds( | ||||||
|  |     createdAtStart: Boolean = false, | ||||||
|  |     bindFilter: (KClass<*>) -> Boolean = { true }, | ||||||
|  |     noinline definition: Definition<T> | ||||||
|  | ): Pair<Module, InstanceFactory<*>> { | ||||||
|  |     return singleWithBinds(RandomQualifier(), createdAtStart, bindFilter, definition) | ||||||
|  | } | ||||||
							
								
								
									
										1
									
								
								koin/src/main/AndroidManifest.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								koin/src/main/AndroidManifest.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | <manifest package="dev.inmo.micro_utils.koin"/> | ||||||
| @@ -0,0 +1,9 @@ | |||||||
|  | package dev.inmo.micro_utils.ktor.client | ||||||
|  |  | ||||||
|  | import io.ktor.client.call.body | ||||||
|  | import io.ktor.client.statement.HttpResponse | ||||||
|  | import io.ktor.http.HttpStatusCode | ||||||
|  |  | ||||||
|  | suspend inline fun <reified T : Any> HttpResponse.bodyOrNull() = takeIf { | ||||||
|  |     status == HttpStatusCode.OK | ||||||
|  | } ?.body<T>() | ||||||
| @@ -0,0 +1,15 @@ | |||||||
|  | package dev.inmo.micro_utils.ktor.server | ||||||
|  |  | ||||||
|  | import io.ktor.http.HttpStatusCode | ||||||
|  | import io.ktor.server.application.ApplicationCall | ||||||
|  | import io.ktor.server.response.respond | ||||||
|  |  | ||||||
|  | suspend inline fun <reified T : Any> ApplicationCall.respondOrNoContent( | ||||||
|  |     data: T? | ||||||
|  | ) { | ||||||
|  |     if (data == null) { | ||||||
|  |         respond(HttpStatusCode.NoContent) | ||||||
|  |     } else { | ||||||
|  |         respond(data) | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -48,6 +48,14 @@ data class PaginationResult<T>( | |||||||
| } | } | ||||||
|  |  | ||||||
| fun <T> emptyPaginationResult() = PaginationResult<T>(0, 0, emptyList(), 0L) | fun <T> emptyPaginationResult() = PaginationResult<T>(0, 0, emptyList(), 0L) | ||||||
|  | fun <T> emptyPaginationResult( | ||||||
|  |     basePagination: Pagination | ||||||
|  | ) = PaginationResult<T>( | ||||||
|  |     basePagination.page, | ||||||
|  |     basePagination.size, | ||||||
|  |     emptyList(), | ||||||
|  |     0L | ||||||
|  | ) | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * @return New [PaginationResult] with [data] without checking of data sizes equality |  * @return New [PaginationResult] with [data] without checking of data sizes equality | ||||||
|   | |||||||
| @@ -33,6 +33,14 @@ open class WriteCRUDCacheRepo<ObjectType, IdType, InputValueType>( | |||||||
|     override val updatedObjectsFlow: Flow<ObjectType> by parentRepo::updatedObjectsFlow |     override val updatedObjectsFlow: Flow<ObjectType> by parentRepo::updatedObjectsFlow | ||||||
|     override val deletedObjectsIdsFlow: Flow<IdType> by parentRepo::deletedObjectsIdsFlow |     override val deletedObjectsIdsFlow: Flow<IdType> by parentRepo::deletedObjectsIdsFlow | ||||||
|  |  | ||||||
|  |     val createdObjectsFlowJob = parentRepo.newObjectsFlow.onEach { | ||||||
|  |         kvCache.set(idGetter(it), it) | ||||||
|  |     }.launchIn(scope) | ||||||
|  |  | ||||||
|  |     val updatedObjectsFlowJob = parentRepo.updatedObjectsFlow.onEach { | ||||||
|  |         kvCache.set(idGetter(it), it) | ||||||
|  |     }.launchIn(scope) | ||||||
|  |  | ||||||
|     val deletedObjectsFlowJob = parentRepo.deletedObjectsIdsFlow.onEach { |     val deletedObjectsFlowJob = parentRepo.deletedObjectsIdsFlow.onEach { | ||||||
|         kvCache.unset(it) |         kvCache.unset(it) | ||||||
|     }.launchIn(scope) |     }.launchIn(scope) | ||||||
|   | |||||||
| @@ -1,7 +1,6 @@ | |||||||
| package dev.inmo.micro_utils.repos.cache | package dev.inmo.micro_utils.repos.cache | ||||||
|  |  | ||||||
| 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.repos.* | import dev.inmo.micro_utils.repos.* | ||||||
| import dev.inmo.micro_utils.repos.cache.cache.KVCache | import dev.inmo.micro_utils.repos.cache.cache.KVCache | ||||||
| import kotlinx.coroutines.CoroutineScope | import kotlinx.coroutines.CoroutineScope | ||||||
| @@ -14,6 +13,16 @@ open class ReadKeyValueCacheRepo<Key,Value>( | |||||||
| ) : ReadKeyValueRepo<Key,Value> by parentRepo, CacheRepo { | ) : ReadKeyValueRepo<Key,Value> by parentRepo, CacheRepo { | ||||||
|     override suspend fun get(k: Key): Value? = kvCache.get(k) ?: parentRepo.get(k) ?.also { kvCache.set(k, it) } |     override suspend fun get(k: Key): Value? = kvCache.get(k) ?: parentRepo.get(k) ?.also { kvCache.set(k, it) } | ||||||
|     override suspend fun contains(key: Key): Boolean = kvCache.contains(key) || parentRepo.contains(key) |     override suspend fun contains(key: Key): Boolean = kvCache.contains(key) || parentRepo.contains(key) | ||||||
|  |  | ||||||
|  |     override suspend fun values(pagination: Pagination, reversed: Boolean): PaginationResult<Value> { | ||||||
|  |         return keys(pagination, reversed).let { | ||||||
|  |             it.changeResultsUnchecked( | ||||||
|  |                 it.results.mapNotNull { | ||||||
|  |                     get(it) | ||||||
|  |                 } | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| fun <Key, Value> ReadKeyValueRepo<Key, Value>.cached( | fun <Key, Value> ReadKeyValueRepo<Key, Value>.cached( | ||||||
|   | |||||||
| @@ -1,9 +1,7 @@ | |||||||
| package dev.inmo.micro_utils.repos.cache | package dev.inmo.micro_utils.repos.cache | ||||||
|  |  | ||||||
| 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.* | ||||||
| import dev.inmo.micro_utils.pagination.utils.paginate |  | ||||||
| import dev.inmo.micro_utils.pagination.utils.reverse |  | ||||||
| import dev.inmo.micro_utils.repos.* | import dev.inmo.micro_utils.repos.* | ||||||
| import dev.inmo.micro_utils.repos.cache.cache.KVCache | import dev.inmo.micro_utils.repos.cache.cache.KVCache | ||||||
| import kotlinx.coroutines.CoroutineScope | import kotlinx.coroutines.CoroutineScope | ||||||
| @@ -15,18 +13,22 @@ open class ReadKeyValuesCacheRepo<Key,Value>( | |||||||
|     protected open val kvCache: KVCache<Key, List<Value>> |     protected open val kvCache: KVCache<Key, List<Value>> | ||||||
| ) : ReadKeyValuesRepo<Key,Value> by parentRepo, CacheRepo { | ) : ReadKeyValuesRepo<Key,Value> by parentRepo, CacheRepo { | ||||||
|     override suspend fun get(k: Key, pagination: Pagination, reversed: Boolean): PaginationResult<Value> { |     override suspend fun get(k: Key, pagination: Pagination, reversed: Boolean): PaginationResult<Value> { | ||||||
|         return kvCache.get(k) ?.paginate( |         return getAll(k, reversed).paginate( | ||||||
|             pagination.let { if (reversed) it.reverse(count(k)) else it } |             pagination | ||||||
|         ) ?.let { |         ) | ||||||
|             if (reversed) it.copy(results = it.results.reversed()) else it |  | ||||||
|         } ?: parentRepo.get(k, pagination, reversed) |  | ||||||
|     } |     } | ||||||
|     override suspend fun getAll(k: Key, reversed: Boolean): List<Value> { |     override suspend fun getAll(k: Key, reversed: Boolean): List<Value> { | ||||||
|         return kvCache.get(k) ?.let { |         return kvCache.get(k) ?.let { | ||||||
|             if (reversed) it.reversed() else it |             if (reversed) it.reversed() else it | ||||||
|         } ?: parentRepo.getAll(k, reversed) |         } ?: parentRepo.getAll(k, reversed).also { | ||||||
|  |             kvCache.set(k, it) | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|     override suspend fun contains(k: Key, v: Value): Boolean = kvCache.get(k) ?.contains(v) ?: parentRepo.contains(k, v) |     override suspend fun contains(k: Key, v: Value): Boolean = kvCache.get(k) ?.contains(v) ?: (parentRepo.contains(k, v).also { | ||||||
|  |         if (it) { | ||||||
|  |             kvCache.unset(k) // clear as invalid | ||||||
|  |         } | ||||||
|  |     }) | ||||||
|     override suspend fun contains(k: Key): Boolean = kvCache.contains(k) || parentRepo.contains(k) |     override suspend fun contains(k: Key): Boolean = kvCache.contains(k) || parentRepo.contains(k) | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -39,9 +41,21 @@ open class KeyValuesCacheRepo<Key,Value>( | |||||||
|     kvCache: KVCache<Key, List<Value>>, |     kvCache: KVCache<Key, List<Value>>, | ||||||
|     scope: CoroutineScope = CoroutineScope(Dispatchers.Default) |     scope: CoroutineScope = CoroutineScope(Dispatchers.Default) | ||||||
| ) : ReadKeyValuesCacheRepo<Key,Value>(parentRepo, kvCache), KeyValuesRepo<Key,Value>, WriteKeyValuesRepo<Key,Value> by parentRepo, CacheRepo { | ) : ReadKeyValuesCacheRepo<Key,Value>(parentRepo, kvCache), KeyValuesRepo<Key,Value>, WriteKeyValuesRepo<Key,Value> by parentRepo, CacheRepo { | ||||||
|     protected val onNewJob = parentRepo.onNewValue.onEach { kvCache.set(it.first, kvCache.get(it.first) ?.plus(it.second) ?: listOf(it.second)) }.launchIn(scope) |     protected val onNewJob = parentRepo.onNewValue.onEach { (k, v) -> | ||||||
|     protected val onRemoveJob = parentRepo.onValueRemoved.onEach { kvCache.set(it.first, kvCache.get(it.first) ?.minus(it.second) ?: return@onEach) }.launchIn(scope) |         kvCache.set( | ||||||
|     protected val onDataClearedJob = parentRepo.onDataCleared.onEach { kvCache.unset(it) }.launchIn(scope) |             k, | ||||||
|  |             kvCache.get(k) ?.plus(v) ?: return@onEach | ||||||
|  |         ) | ||||||
|  |     }.launchIn(scope) | ||||||
|  |     protected val onRemoveJob = parentRepo.onValueRemoved.onEach { (k, v) -> | ||||||
|  |         kvCache.set( | ||||||
|  |             k, | ||||||
|  |             kvCache.get(k) ?.minus(v) ?: return@onEach | ||||||
|  |         ) | ||||||
|  |     }.launchIn(scope) | ||||||
|  |     protected val onDataClearedJob = parentRepo.onDataCleared.onEach { | ||||||
|  |         kvCache.unset(it) | ||||||
|  |     }.launchIn(scope) | ||||||
| } | } | ||||||
|  |  | ||||||
| fun <Key, Value> KeyValuesRepo<Key, Value>.cached( | fun <Key, Value> KeyValuesRepo<Key, Value>.cached( | ||||||
|   | |||||||
| @@ -25,6 +25,9 @@ open class SimpleKVCache<K, V>( | |||||||
|                         kvParent.unset(it) |                         kvParent.unset(it) | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|  |                 do { | ||||||
|  |                     val removed = cacheQueue.remove(k) | ||||||
|  |                 } while (removed) | ||||||
|                 cacheQueue.addLast(k) |                 cacheQueue.addLast(k) | ||||||
|                 kvParent.set(k, v) |                 kvParent.set(k, v) | ||||||
|             } |             } | ||||||
|   | |||||||
| @@ -17,7 +17,7 @@ fun <T : Any> Context.keyValueStore( | |||||||
| ): KeyValueRepo<String, T> { | ): KeyValueRepo<String, T> { | ||||||
|     @Suppress("UNCHECKED_CAST") |     @Suppress("UNCHECKED_CAST") | ||||||
|     return cache.getOrPut(name) { |     return cache.getOrPut(name) { | ||||||
|         KeyValueStore<T>(this, name, cacheValues) |         KeyValueStore<T>(c = this, preferencesName = name, useCache = cacheValues) | ||||||
|     } as KeyValueStore<T> |     } as KeyValueStore<T> | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -149,6 +149,14 @@ class KeyValueStore<T : Any> internal constructor ( | |||||||
|             _onValueRemovedFlow.emit(it) |             _onValueRemovedFlow.emit(it) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     companion object { | ||||||
|  |         operator fun <T : Any> invoke( | ||||||
|  |             context: Context, | ||||||
|  |             name: String = "default", | ||||||
|  |             cacheValues: Boolean = false | ||||||
|  |         ) = context.keyValueStore<T>(name, cacheValues) | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| inline fun <T : Any> SharedPreferencesKeyValueRepo( | inline fun <T : Any> SharedPreferencesKeyValueRepo( | ||||||
| @@ -156,3 +164,5 @@ inline fun <T : Any> SharedPreferencesKeyValueRepo( | |||||||
|     name: String = "default", |     name: String = "default", | ||||||
|     cacheValues: Boolean = false |     cacheValues: Boolean = false | ||||||
| ) = context.keyValueStore<T>(name, cacheValues) | ) = context.keyValueStore<T>(name, cacheValues) | ||||||
|  |  | ||||||
|  | typealias KeyValueSPRepo<T> = KeyValueStore<T> | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ String[] includes = [ | |||||||
|     ":common:compose", |     ":common:compose", | ||||||
|     ":matrix", |     ":matrix", | ||||||
|     ":crypto", |     ":crypto", | ||||||
|  |     ":koin", | ||||||
|     ":selector:common", |     ":selector:common", | ||||||
|     ":pagination:common", |     ":pagination:common", | ||||||
|     ":pagination:exposed", |     ":pagination:exposed", | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user