mirror of
https://github.com/InsanusMokrassar/MicroUtils.git
synced 2025-09-17 14:29:24 +00:00
Compare commits
173 Commits
Author | SHA1 | Date | |
---|---|---|---|
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 | |||
e38094df58 | |||
c25e3f5867 | |||
f78e81d175 | |||
3837ae237d | |||
2b6ef8b4ff | |||
6cc0eefb3e | |||
ab11e28bf7 | |||
26d5f5a5f5 | |||
74ae91cba6 | |||
70509c7edd | |||
5a69bd6c63 | |||
091bb1394f | |||
b82c3864a0 | |||
49ee38a936 | |||
c201866c51 | |||
023f841fb5 | |||
76102e9ab3 | |||
b2fc5e2a4d | |||
55aacb8753 | |||
8702846216 | |||
3347a6d189 | |||
47b3b42949 | |||
e985631621 | |||
e15034bfa4 | |||
e5dbcd25dd | |||
3714c02c12 | |||
fa636b4146 | |||
3d825aebc3 | |||
629884a396 | |||
e7a0fa4e8f | |||
1a41f37a9d | |||
07a65e0bb5 | |||
615f7f99c3 | |||
3de5558ed4 | |||
6bbe3a271f | |||
8bee29f683 | |||
c40f0fdcb9 | |||
30d1453a12 | |||
75fa88b00d | |||
0f817ad212 | |||
d7b46ae0d4 | |||
b6be14ecca | |||
33dbfc6f69 | |||
fcacdcd544 | |||
dd2fc5a86f | |||
0f8a6f6bde | |||
1efd94181d | |||
71ff0232aa | |||
63921cd984 | |||
051e03bed3 | |||
a051394f4f | |||
b872babe45 | |||
5a9cabc4bd | |||
3ba630684a | |||
498cd12f94 | |||
062848f2e4 | |||
d4b4547718 | |||
22cd440dd7 | |||
6fc64526d4 | |||
08075dfafe | |||
efcb25622e | |||
5ebf29d1fb | |||
b7d0ce3c97 | |||
e20929aec4 | |||
f74167cc65 | |||
c55cd5342e | |||
237d89d611 | |||
aff9b52f1c | |||
d8cc6ed06e | |||
dffafa54c3 | |||
776a3af497 | |||
5b3d9e8d64 | |||
90b7d74a0c | |||
5b596c76e0 | |||
c59e601e2e | |||
5ce71ee6f6 | |||
97dadf517a | |||
a739e874bf | |||
c73cb14615 | |||
d9464f7b90 | |||
aeee41680e | |||
c914f8c44a | |||
abfda3627c | |||
5b5bfa02db | |||
0fdb072385 | |||
7b4c9d59b0 | |||
bd5923716f | |||
64cc42a23b | |||
325f178763 | |||
fc8d0e52ef | |||
e58348907e | |||
eaba9173ae | |||
2f42b30f87 | |||
35913b95be | |||
8023fa1b76 | |||
4cbe2d1d61 | |||
740036e8d9 | |||
273a7aa9a4 | |||
dccb479c3c | |||
6f5c6e5ebe | |||
8495cc6263 | |||
d242d8dcf4 | |||
864d576e70 | |||
f0127b018e | |||
5eb48a58bf | |||
b690f68c7f | |||
7f19b83828 | |||
98a1ec82db | |||
63313ff964 | |||
210e32bed4 | |||
5b325a8ff9 | |||
4582e0c817 | |||
e2d1c5d6a1 | |||
4019ad7d31 | |||
e7df21e91a | |||
b2e30c9f6d | |||
0b27b5cc06 | |||
347e9c32fe | |||
cc18f58e4c | |||
dbd2e963a1 | |||
6928ca5329 | |||
9ece160aa8 | |||
6115c1bcac | |||
e026e94cbf | |||
56cdd8d6af | |||
899e6760e1 | |||
864d0ffcc6 | |||
c6f417f8c8 | |||
9091fa5bd8 | |||
418af7874d | |||
4972cc9daa | |||
ff905e1491 | |||
64b0184a17 | |||
97cbe44fb5 | |||
452d8778c5 | |||
02ad1a748e | |||
c5a32e2b1b | |||
702300f761 | |||
efcaa08b70 | |||
7f7369b3a8 | |||
45a9a95888 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -10,5 +10,6 @@ build/
|
||||
out/
|
||||
|
||||
secret.gradle
|
||||
local.properties
|
||||
|
||||
publishing.sh
|
||||
|
20
.travis.yml
20
.travis.yml
@@ -1,13 +1,27 @@
|
||||
language: java
|
||||
language: android
|
||||
install: true
|
||||
|
||||
os: linux
|
||||
dist: trusty
|
||||
jdk: oraclejdk8
|
||||
|
||||
android:
|
||||
components:
|
||||
- tools
|
||||
- platform-tools
|
||||
- build-tools-30.0.2
|
||||
- android-30
|
||||
- add-on
|
||||
- extra
|
||||
|
||||
before_script:
|
||||
- yes | /usr/local/android-sdk/tools/bin/sdkmanager "build-tools;30.0.2"
|
||||
- yes | /usr/local/android-sdk/tools/bin/sdkmanager "platforms;android-30"
|
||||
|
||||
jobs:
|
||||
include:
|
||||
- stage: build
|
||||
script: ./gradlew build -s -x jvmTest -x jsIrTest -x jsIrBrowserTest -x jsIrNodeTest -x jsLegacyTest -x jsLegacyBrowserTest -x jsLegacyNodeTest
|
||||
- state: test
|
||||
script: ./gradlew allTests
|
||||
# Tests are temporarily disabled on public travis due to the problems of launching
|
||||
# - state: test
|
||||
# script: ./gradlew allTests
|
||||
|
283
CHANGELOG.md
283
CHANGELOG.md
@@ -1,5 +1,284 @@
|
||||
# Changelog
|
||||
|
||||
## 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
|
||||
|
||||
* `Versions`:
|
||||
* `Coroutines`: `1.4.1` -> `1.4.2`
|
||||
* `UUID`: `0.2.2` -> `0.2.3`
|
||||
* `Pagination`
|
||||
* Add `PaginatedIterable` and `PaginatedIterator`
|
||||
|
||||
## 0.4.7
|
||||
|
||||
* `Ktor`
|
||||
* `Client`
|
||||
* New class `UnifiedRequester`
|
||||
* `Server`
|
||||
* New class `UnifiedRouter`
|
||||
* `Repos`
|
||||
* `Ktor`
|
||||
* `Client`
|
||||
* Rewriting of all clients on new `UnifiedRequester`
|
||||
* `Server`
|
||||
* Rewriting of all clients on new `UnifiedRouter`
|
||||
|
||||
## 0.4.6
|
||||
|
||||
* `Common`
|
||||
* New annotation `Warning` has been added
|
||||
* `Pagination`
|
||||
* `Common`
|
||||
* `Pagination` got new extension: `Pagination#isFirstPage`
|
||||
* `Coroutines`:
|
||||
* New extension `FlowCollector#invoke` has been added
|
||||
* `Repos`
|
||||
* `Common`
|
||||
* `JVM` (and `Android` since `Android API 26`):
|
||||
* `FileStandardKeyValueRepo` has been added
|
||||
* Add several `typealias`es for each type of repos
|
||||
|
||||
## 0.4.5
|
||||
|
||||
* `Android`
|
||||
* `Alerts`
|
||||
* `Common`
|
||||
* Project has been created
|
||||
* `RecyclerView`
|
||||
* Project has been created
|
||||
* `Common`
|
||||
* Annotation `PreviewFeature` has been added
|
||||
* `Android`
|
||||
* Added tools to work with visibility in more comfortable way
|
||||
* Added tools to work with disabled/enabled state in more comfortable way
|
||||
* Added tools to work with expanded/collapsed state in more comfortable way (in preview mode)
|
||||
|
||||
## 0.4.4
|
||||
|
||||
* `Versions`:
|
||||
* `Klock`: `1.12.1` -> `2.0.0`
|
||||
* `Commons`:
|
||||
* Update left items functionality to include work with `GridLayoutManager`
|
||||
* `Repos`:
|
||||
* Add interface `VersionsRepo`
|
||||
* Add default realization of `VersionsRepo` named `StandardVersionsRepo` which use `StandardVersionsRepoProxy`
|
||||
to get access to some end-store
|
||||
* Add default realization of `StandardVersionsRepoProxy` based on `KeyValue` repos
|
||||
* Add realizations of `StandardVersionsRepoProxy` for exposed and android (`SQL` and `SharedPreferences`)
|
||||
* `Commons`:
|
||||
* In Android fully reworked transactions functions
|
||||
* Now `DatabaseCoroutineContext` is a shortcut for `Dispatchers.IO`
|
||||
|
||||
## 0.4.3
|
||||
|
||||
* `Versions`:
|
||||
* `Kotlin`: `1.4.10` -> `1.4.20`
|
||||
* `Common`:
|
||||
* Two new extensions for Android:
|
||||
* `Resources#getSp`
|
||||
* `Resources#getDp`
|
||||
|
||||
## 0.4.2
|
||||
|
||||
* `Repos`:
|
||||
* Add `WriteOneToManyKeyValueRepo#set` function and extensions
|
||||
|
||||
## 0.4.1
|
||||
|
||||
* `Repos`:
|
||||
* Fixed error in `ExposedKeyValueRepo` related to negative size of shared flow
|
||||
* Fixed error in `ExposedKeyValueRepo` related to avoiding of table initiation
|
||||
|
||||
## 0.4.0
|
||||
|
||||
* `Repos`:
|
||||
* `ReadOneToManyKeyValueRepo` got `keys` method with value parameter
|
||||
* All implementations inside of this library has been updated
|
||||
* `ReadStandardKeyValueRepo` got `keys` method with value parameter
|
||||
* All implementations inside of this library has been updated
|
||||
* New extensions `withMapper`
|
||||
|
||||
## 0.3.3
|
||||
|
||||
* `Coroutines`:
|
||||
* New extension `Flow<T>#plus`
|
||||
|
||||
## 0.3.2
|
||||
|
||||
* `Versions`:
|
||||
* `Coroutines`: `1.4.1` -> `1.4.2`
|
||||
* `Repos`:
|
||||
* `Common`:
|
||||
* New inline function `mapper` for simplier creating of `MapperRepo` objects
|
||||
* Extensions `withMapper` for keyvalue repos and onetomany repos
|
||||
|
||||
## 0.3.1
|
||||
|
||||
**ANDROID PACKAGES**
|
||||
|
||||
* `Android`:
|
||||
* `RecyclerView`:
|
||||
* Library has been created
|
||||
* `Common`
|
||||
* Now available package `dev.inmo:micro_utils.common-android`
|
||||
* `Coroutines`
|
||||
* Now available package `dev.inmo:micro_utils.coroutines-android`
|
||||
* `Ktor`
|
||||
* `Common`
|
||||
* Now available package `dev.inmo:micro_utils.ktor.common-android`
|
||||
* `Client`
|
||||
* Now available package `dev.inmo:micro_utils.ktor.client-android`
|
||||
* `MimeTypes`
|
||||
* Now available package `dev.inmo:micro_utils.mime_types-android`
|
||||
* `Pagination`
|
||||
* `Common`
|
||||
* Now available package `dev.inmo:micro_utils.pagination.common-android`
|
||||
* `Ktor`
|
||||
* `Common`
|
||||
* Now available package `dev.inmo:micro_utils.pagination.ktor.common-android`
|
||||
* `Repos`
|
||||
* `Common`
|
||||
* Now available package `dev.inmo:micro_utils.repos.common-android`
|
||||
* Now it is possible to use default realizations of repos abstractions natively on android
|
||||
* `Inmemory`
|
||||
* Now available package `dev.inmo:micro_utils.repos.inmemory-android`
|
||||
* `Ktor`
|
||||
* `Common`
|
||||
* Now available package `dev.inmo:micro_utils.repos.ktor.common-android`
|
||||
* `Common`
|
||||
* Now available package `dev.inmo:micro_utils.repos.ktor.client-android`
|
||||
|
||||
## 0.3.0
|
||||
|
||||
All deprecations has been removed
|
||||
|
||||
* `Repos`:
|
||||
* `Common`:
|
||||
* `KeyValue` and `OneToMany` repos lost their deprecated methods
|
||||
* `OneToMany` write repos got additional extensions for mutation of repo
|
||||
* `KeyValue` write repos got additional extensions for mutation of repo
|
||||
* New interface `MapperRepo` and new classes which are using this:
|
||||
* `KeyValue`
|
||||
* `MapperReadStandardKeyValueRepo`
|
||||
* `MapperWriteStandardKeyValueRepo`
|
||||
* `MapperStandardKeyValueRepo`
|
||||
* `OneToMany`
|
||||
* `MapperReadOneToManyKeyValueRepo`
|
||||
* `MapperWriteOneToManyKeyValueRepo`
|
||||
* `MapperOneToManyKeyValueRepo`
|
||||
* `Exposed`:
|
||||
* CRUD realizations replaced their channels to shared flows
|
||||
|
||||
## 0.2.7
|
||||
|
||||
* `Versions`:
|
||||
* `Coroutines`: `1.4.0` -> `1.4.1`
|
||||
* `Repos`:
|
||||
* `WriteStandardKeyValueRepo` got new methods `set` and `unset` with collections
|
||||
* All standard realizations of repos got collections methods realizations
|
||||
* All old usages of `BroadcastFlow` and `BroadcastChannel` has been replaced with `MutableSharedFlow`
|
||||
* `Ktor`:
|
||||
* `Server`:
|
||||
* Fixed incorrect answer for `keyvalue`
|
||||
|
||||
## 0.2.6
|
||||
|
||||
* `Pagination`
|
||||
* Fixes in function `List#paginate`
|
||||
* Extension property `Pagination#lastIndexExclusive`
|
||||
|
||||
## 0.2.5
|
||||
|
||||
* `Coroutines`
|
||||
* Function `safelyWithoutExceptions`
|
||||
* Extension `CoroutineScope#safeActor`
|
||||
|
||||
## 0.2.4
|
||||
|
||||
* `Versions`
|
||||
* `Serialization`: `1.0.0` -> `1.0.1`
|
||||
* `Common`
|
||||
* Full rework of `DiffUtils`
|
||||
* Data class `Diff` has been added
|
||||
* Extension `Iterable#calculateDiff` has been added
|
||||
* Extension `Iterable#calculateStrictDiff` as replacement for `Iterable#calculateDiff` with
|
||||
`strictComparison` mode enabled
|
||||
* Functions `Diff` (as analog of `Iterable#calculateDiff`) and `StrictDiff` (as analog of
|
||||
`Iterable#calculateStrictDiff`)
|
||||
* `Coroutines`
|
||||
* `BroadcastFlow` now is deprecated
|
||||
* `BroadcastStateFlow` now is deprecated
|
||||
* New extensions for `Flow`s:
|
||||
* `Flow#subscribe`
|
||||
* `Flow#subscribeSafely`
|
||||
* `Flow#subscribeSafelyWithoutExceptions`
|
||||
|
||||
## 0.2.3
|
||||
|
||||
* `Versions`
|
||||
* `Coroutines`: `1.3.9` -> `1.4.0`
|
||||
* `Exposed`: `0.27.1` -> `0.28.1`
|
||||
* `Common`
|
||||
* `K/JS`
|
||||
* Add several extensions for `Element` objects to detect that object is on screen viewport
|
||||
* Add several extensions for `Element` objects to detect object visibility
|
||||
* `Coroutines`
|
||||
* `BroadcastStateFlow` now use different strategy for getting of state and implements `replayCache`
|
||||
|
||||
## 0.2.2
|
||||
|
||||
* `Repos`
|
||||
* `Common`
|
||||
* Several new methods `ReadOneToManyKeyValueRepo#getAll`
|
||||
* Several new method `WriteOneToManyKeyValueRepo#add` and several extensions
|
||||
* Several new method `WriteOneToManyKeyValueRepo#remove` and several extensions
|
||||
|
||||
## 0.2.1
|
||||
|
||||
* `Pagination`
|
||||
* `Common`:
|
||||
* Extension `Pagination#reverse` has been added
|
||||
* Factory `PaginationByIndexes`
|
||||
* Shortcut `calculatePagesNumber` with reversed parameters
|
||||
* Value `emptyPagination` for empty `SimplePagination` cases
|
||||
|
||||
## 0.2.0
|
||||
|
||||
* `Repos`
|
||||
@@ -10,6 +289,7 @@
|
||||
* `ExposedCRUDRepo` now extends `ExposedRepo` instead of simple `Repo`
|
||||
* New extension `initTable` for classes which are `Table` and `ExposedRepo` at the same time
|
||||
* `KeyValue`:
|
||||
* `tableName` parameter
|
||||
* Class `AbstractExposedReadKeyValueRepo`
|
||||
* Renamed to `ExposedReadKeyValueRepo`
|
||||
* Changed incoming types to `ColumnAllocator`
|
||||
@@ -20,6 +300,7 @@
|
||||
* Changed incoming types to `ColumnAllocator`
|
||||
* `open` instead of `abstract`
|
||||
* `OneToMany`:
|
||||
* `tableName` parameter
|
||||
* Class `AbstractExposedReadOneToManyKeyValueRepo`
|
||||
* Renamed to `ExposedReadOneToManyKeyValueRepo`
|
||||
* Changed incoming arguments order
|
||||
@@ -69,4 +350,4 @@
|
||||
|
||||
## 0.1.0
|
||||
|
||||
Inited :)
|
||||
Inited :)
|
||||
|
37
README.md
37
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
|
17
android/alerts/common/build.gradle
Normal file
17
android/alerts/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: "$mppAndroidProjectPresetPath"
|
||||
|
||||
kotlin {
|
||||
sourceSets {
|
||||
androidMain {
|
||||
dependencies {
|
||||
api "androidx.appcompat:appcompat-resources:$appcompat_version"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
1
android/alerts/common/src/main/AndroidManifest.xml
Normal file
1
android/alerts/common/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
||||
<manifest package="dev.inmo.micro_utils.android.alerts.common"/>
|
@@ -0,0 +1,55 @@
|
||||
@file:Suppress("NOTHING_TO_INLINE", "unused")
|
||||
|
||||
package dev.inmo.micro_utils.android.alerts.common
|
||||
|
||||
import android.app.AlertDialog
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
|
||||
typealias AlertDialogCallback = (DialogInterface) -> Unit
|
||||
|
||||
inline fun Context.createAlertDialogTemplate(
|
||||
title: String? = null,
|
||||
positivePair: Pair<String, AlertDialogCallback?>? = null,
|
||||
neutralPair: Pair<String, AlertDialogCallback?>? = null,
|
||||
negativePair: Pair<String, AlertDialogCallback?>? = null
|
||||
): AlertDialog.Builder {
|
||||
val builder = AlertDialog.Builder(this)
|
||||
|
||||
title ?.let {
|
||||
builder.setTitle(title)
|
||||
}
|
||||
|
||||
positivePair ?. let {
|
||||
builder.setPositiveButton(it.first) { di, _ -> it.second ?. invoke(di) }
|
||||
}
|
||||
negativePair ?. let {
|
||||
builder.setNegativeButton(it.first) { di, _ -> it.second ?. invoke(di) }
|
||||
}
|
||||
neutralPair ?. let {
|
||||
builder.setNeutralButton(it.first) { di, _ -> it.second ?. invoke(di) }
|
||||
}
|
||||
|
||||
return builder
|
||||
}
|
||||
|
||||
inline fun Context.createAlertDialogTemplateWithResources(
|
||||
title: Int? = null,
|
||||
positivePair: Pair<Int, AlertDialogCallback?>? = null,
|
||||
neutralPair: Pair<Int, AlertDialogCallback?>? = null,
|
||||
negativePair: Pair<Int, AlertDialogCallback?>? = null
|
||||
): AlertDialog.Builder = createAlertDialogTemplate(
|
||||
title ?.let { getString(it) },
|
||||
positivePair ?.let { getString(it.first) to it.second },
|
||||
neutralPair ?.let { getString(it.first) to it.second },
|
||||
negativePair ?.let { getString(it.first) to it.second }
|
||||
)
|
||||
|
||||
inline fun AlertDialog.setDismissChecker(noinline checker: () -> Boolean) : AlertDialog {
|
||||
setOnDismissListener {
|
||||
if (!checker()) {
|
||||
show()
|
||||
}
|
||||
}
|
||||
return this
|
||||
}
|
@@ -0,0 +1,38 @@
|
||||
@file:Suppress("NOTHING_TO_INLINE", "unused")
|
||||
|
||||
package dev.inmo.micro_utils.android.alerts.common
|
||||
|
||||
import android.app.AlertDialog
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
|
||||
inline fun <T: View> Context.createCustomViewAlertDialog(
|
||||
title: String? = null,
|
||||
positivePair: Pair<String, AlertDialogCallback?>? = null,
|
||||
neutralPair: Pair<String, AlertDialogCallback?>? = null,
|
||||
negativePair: Pair<String, AlertDialogCallback?>? = null,
|
||||
show: Boolean = true,
|
||||
viewCreator: (Context) -> T
|
||||
): AlertDialog = createAlertDialogTemplate(
|
||||
title, positivePair, neutralPair, negativePair
|
||||
).apply {
|
||||
setView(viewCreator(this@createCustomViewAlertDialog))
|
||||
}.create().apply {
|
||||
if (show) show()
|
||||
}
|
||||
|
||||
inline fun <T: View> Context.createCustomViewAlertDialogWithResources(
|
||||
title: Int? = null,
|
||||
positivePair: Pair<Int, AlertDialogCallback?>? = null,
|
||||
neutralPair: Pair<Int, AlertDialogCallback?>? = null,
|
||||
negativePair: Pair<Int, AlertDialogCallback?>? = null,
|
||||
show: Boolean = true,
|
||||
viewCreator: (Context) -> T
|
||||
): AlertDialog = createCustomViewAlertDialog(
|
||||
title ?.let { getString(it) },
|
||||
positivePair ?.let { getString(it.first) to it.second },
|
||||
neutralPair ?.let { getString(it.first) to it.second },
|
||||
negativePair ?.let { getString(it.first) to it.second },
|
||||
show,
|
||||
viewCreator
|
||||
)
|
@@ -0,0 +1,45 @@
|
||||
@file:Suppress("NOTHING_TO_INLINE", "unused")
|
||||
|
||||
package dev.inmo.micro_utils.android.alerts.common
|
||||
|
||||
import android.app.AlertDialog
|
||||
import android.content.Context
|
||||
import androidx.annotation.StringRes
|
||||
|
||||
inline fun Context.createSimpleTextAlertDialog(
|
||||
text: String,
|
||||
title: String? = null,
|
||||
positivePair: Pair<String, AlertDialogCallback?>? = null,
|
||||
neutralPair: Pair<String, AlertDialogCallback?>? = null,
|
||||
negativePair: Pair<String, AlertDialogCallback?>? = null,
|
||||
show: Boolean = true
|
||||
): AlertDialog = createAlertDialogTemplate(
|
||||
title,
|
||||
positivePair,
|
||||
neutralPair,
|
||||
negativePair
|
||||
).apply {
|
||||
setMessage(text)
|
||||
}.create().apply {
|
||||
if (show) {
|
||||
show()
|
||||
}
|
||||
}
|
||||
|
||||
inline fun Context.createSimpleTextAlertDialog(
|
||||
@StringRes
|
||||
text: Int,
|
||||
@StringRes
|
||||
title: Int? = null,
|
||||
positivePair: Pair<Int, AlertDialogCallback?>? = null,
|
||||
neutralPair: Pair<Int, AlertDialogCallback?>? = null,
|
||||
negativePair: Pair<Int, AlertDialogCallback?>? = null,
|
||||
show: Boolean = true
|
||||
): AlertDialog = createSimpleTextAlertDialog(
|
||||
getString(text),
|
||||
title ?.let { getString(it) },
|
||||
positivePair ?.let { getString(it.first) to it.second },
|
||||
neutralPair ?.let { getString(it.first) to it.second },
|
||||
negativePair ?.let { getString(it.first) to it.second },
|
||||
show
|
||||
)
|
18
android/alerts/recyclerview/build.gradle
Normal file
18
android/alerts/recyclerview/build.gradle
Normal file
@@ -0,0 +1,18 @@
|
||||
plugins {
|
||||
id "org.jetbrains.kotlin.multiplatform"
|
||||
id "org.jetbrains.kotlin.plugin.serialization"
|
||||
id "com.android.library"
|
||||
}
|
||||
|
||||
apply from: "$mppAndroidProjectPresetPath"
|
||||
|
||||
kotlin {
|
||||
sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
api internalProject("micro_utils.android.alerts.common")
|
||||
api internalProject("micro_utils.android.recyclerview")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
1
android/alerts/recyclerview/src/main/AndroidManifest.xml
Normal file
1
android/alerts/recyclerview/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
||||
<manifest package="dev.inmo.micro_utils.android.alerts.recyclerview"/>
|
@@ -0,0 +1,65 @@
|
||||
@file:Suppress("unused")
|
||||
|
||||
package dev.inmo.micro_utils.android.alerts.recyclerview
|
||||
|
||||
import android.app.AlertDialog
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import dev.inmo.micro_utils.android.alerts.common.AlertDialogCallback
|
||||
import dev.inmo.micro_utils.android.recyclerview.*
|
||||
|
||||
data class AlertAction(
|
||||
val title: String,
|
||||
val callback: (DialogInterface) -> Unit
|
||||
)
|
||||
|
||||
private class ActionViewHolder(
|
||||
container: ViewGroup, dialogInterfaceGetter: () -> DialogInterface
|
||||
) : AbstractStandardViewHolder<AlertAction>(container, android.R.layout.simple_list_item_1) {
|
||||
private lateinit var action: AlertAction
|
||||
private val textView: TextView
|
||||
get() = itemView.findViewById(android.R.id.text1)
|
||||
|
||||
init {
|
||||
itemView.setOnClickListener {
|
||||
action.callback(dialogInterfaceGetter())
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBind(item: AlertAction) {
|
||||
action = item
|
||||
textView.text = item.title
|
||||
}
|
||||
}
|
||||
|
||||
private class ActionsRecyclerViewAdapter(
|
||||
data: List<AlertAction>,
|
||||
private val dialogInterfaceGetter: () -> DialogInterface
|
||||
) : RecyclerViewAdapter<AlertAction>(data) {
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AbstractViewHolder<AlertAction> = ActionViewHolder(
|
||||
parent, dialogInterfaceGetter
|
||||
)
|
||||
}
|
||||
|
||||
fun Context.createActionsAlertDialog(
|
||||
actions: List<AlertAction>,
|
||||
title: Int? = null,
|
||||
positivePair: Pair<Int, AlertDialogCallback?>? = null,
|
||||
neutralPair: Pair<Int, AlertDialogCallback?>? = null,
|
||||
negativePair: Pair<Int, AlertDialogCallback?>? = null,
|
||||
show: Boolean = true
|
||||
): AlertDialog {
|
||||
lateinit var dialogInterface: DialogInterface
|
||||
|
||||
return createRecyclerViewDialog(
|
||||
title, positivePair, neutralPair, negativePair, show
|
||||
) {
|
||||
ActionsRecyclerViewAdapter(
|
||||
actions
|
||||
) {
|
||||
dialogInterface
|
||||
}
|
||||
}.also { dialogInterface = it }
|
||||
}
|
@@ -0,0 +1,43 @@
|
||||
package dev.inmo.micro_utils.android.alerts.recyclerview
|
||||
|
||||
import android.app.AlertDialog
|
||||
import android.content.Context
|
||||
import android.widget.LinearLayout
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import dev.inmo.micro_utils.android.alerts.common.AlertDialogCallback
|
||||
import dev.inmo.micro_utils.android.alerts.common.createCustomViewAlertDialogWithResources
|
||||
|
||||
fun Context.createRecyclerViewDialog(
|
||||
title: Int? = null,
|
||||
positivePair: Pair<Int, AlertDialogCallback?>? = null,
|
||||
neutralPair: Pair<Int, AlertDialogCallback?>? = null,
|
||||
negativePair: Pair<Int, AlertDialogCallback?>? = null,
|
||||
show: Boolean = true,
|
||||
layoutManager: RecyclerView.LayoutManager = LinearLayoutManager(this),
|
||||
marginOfRecyclerView: Int = 8, // dp
|
||||
recyclerViewSetUp: RecyclerView.() -> Unit = {},
|
||||
adapterFactory: () -> RecyclerView.Adapter<*>
|
||||
): AlertDialog {
|
||||
val recyclerView = RecyclerView(this).apply {
|
||||
layoutParams = LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||
).apply {
|
||||
setMargins(marginOfRecyclerView, marginOfRecyclerView, marginOfRecyclerView, marginOfRecyclerView)
|
||||
}
|
||||
this.layoutManager = layoutManager
|
||||
adapter = adapterFactory()
|
||||
recyclerViewSetUp()
|
||||
}
|
||||
|
||||
return createCustomViewAlertDialogWithResources(
|
||||
title,
|
||||
positivePair,
|
||||
neutralPair,
|
||||
negativePair,
|
||||
show
|
||||
) {
|
||||
recyclerView
|
||||
}
|
||||
}
|
22
android/recyclerview/build.gradle
Normal file
22
android/recyclerview/build.gradle
Normal file
@@ -0,0 +1,22 @@
|
||||
plugins {
|
||||
id "org.jetbrains.kotlin.multiplatform"
|
||||
id "org.jetbrains.kotlin.plugin.serialization"
|
||||
id "com.android.library"
|
||||
}
|
||||
|
||||
apply from: "$mppAndroidProjectPresetPath"
|
||||
|
||||
kotlin {
|
||||
sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version"
|
||||
}
|
||||
}
|
||||
androidMain {
|
||||
dependencies {
|
||||
api "androidx.recyclerview:recyclerview:$androidx_recycler_version"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
1
android/recyclerview/src/main/AndroidManifest.xml
Normal file
1
android/recyclerview/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
||||
<manifest package="dev.inmo.micro_utils.android.recyclerview"/>
|
@@ -0,0 +1,22 @@
|
||||
package dev.inmo.micro_utils.android.recyclerview
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
|
||||
abstract class AbstractStandardViewHolder<T>(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
viewId: Int,
|
||||
onViewInflated: ((View) -> Unit)? = null
|
||||
) : AbstractViewHolder<T>(
|
||||
inflater.inflate(viewId, container, false).also {
|
||||
onViewInflated ?.invoke(it)
|
||||
}
|
||||
) {
|
||||
constructor(
|
||||
container: ViewGroup,
|
||||
viewId: Int,
|
||||
onViewInflated: ((View) -> Unit)? = null
|
||||
) : this(LayoutInflater.from(container.context), container, viewId, onViewInflated)
|
||||
}
|
@@ -0,0 +1,10 @@
|
||||
package dev.inmo.micro_utils.android.recyclerview
|
||||
|
||||
import android.view.View
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
||||
abstract class AbstractViewHolder<in T>(
|
||||
view: View
|
||||
) : RecyclerView.ViewHolder(view) {
|
||||
abstract fun onBind(item: T)
|
||||
}
|
@@ -0,0 +1,9 @@
|
||||
package dev.inmo.micro_utils.android.recyclerview
|
||||
|
||||
import android.content.Context
|
||||
import android.widget.LinearLayout
|
||||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
|
||||
val Context.recyclerViewItemsDecoration
|
||||
get() = DividerItemDecoration(this, LinearLayout.VERTICAL)
|
||||
|
@@ -0,0 +1,53 @@
|
||||
package dev.inmo.micro_utils.android.recyclerview
|
||||
|
||||
import androidx.recyclerview.widget.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
private inline fun RecyclerView.LayoutManager.findLastVisibleItemPositionGetter(): (() -> Int)? = when (this) {
|
||||
is LinearLayoutManager -> ::findLastVisibleItemPosition
|
||||
is GridLayoutManager -> ::findLastVisibleItemPosition
|
||||
else -> null
|
||||
}
|
||||
|
||||
fun RecyclerView.lastVisibleItemFlow(
|
||||
completingScope: CoroutineScope
|
||||
): Flow<Int> {
|
||||
val lastVisibleElementFun: () -> Int = layoutManager ?.findLastVisibleItemPositionGetter() ?: error("Currently supported only linear and grid layout manager")
|
||||
val lastVisibleFlow = MutableStateFlow(lastVisibleElementFun())
|
||||
addOnScrollListener(
|
||||
object : RecyclerView.OnScrollListener() {
|
||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||
super.onScrolled(recyclerView, dx, dy)
|
||||
lastVisibleFlow.value = lastVisibleElementFun()
|
||||
}
|
||||
}.also { scrollListener ->
|
||||
lastVisibleFlow.onCompletion {
|
||||
removeOnScrollListener(scrollListener)
|
||||
}.launchIn(completingScope)
|
||||
}
|
||||
)
|
||||
return lastVisibleFlow.asStateFlow()
|
||||
}
|
||||
|
||||
inline fun Flow<Int>.mapLeftItems(
|
||||
crossinline countGetter: () -> Int
|
||||
): Flow<Int> = map { countGetter() - it }
|
||||
|
||||
inline fun Flow<Int>.mapRequireFilling(
|
||||
minimalLeftItems: Int,
|
||||
crossinline countGetter: () -> Int
|
||||
): Flow<Int> = mapLeftItems(countGetter).mapNotNull {
|
||||
if (it < minimalLeftItems) {
|
||||
it
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
inline fun RecyclerView.mapRequireFilling(
|
||||
minimalLeftItems: Int,
|
||||
completingScope: CoroutineScope,
|
||||
crossinline countGetter: () -> Int
|
||||
): Flow<Int> = lastVisibleItemFlow(completingScope).mapRequireFilling(minimalLeftItems, countGetter)
|
@@ -0,0 +1,68 @@
|
||||
package dev.inmo.micro_utils.android.recyclerview
|
||||
|
||||
import android.view.View
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
||||
|
||||
abstract class RecyclerViewAdapter<T>(
|
||||
val data: List<T>
|
||||
): RecyclerView.Adapter<AbstractViewHolder<T>>() {
|
||||
var emptyView: View? = null
|
||||
set(value) {
|
||||
field = value
|
||||
checkEmpty()
|
||||
}
|
||||
|
||||
init {
|
||||
registerAdapterDataObserver(
|
||||
object : RecyclerView.AdapterDataObserver() {
|
||||
override fun onItemRangeChanged(positionStart: Int, itemCount: Int) {
|
||||
super.onItemRangeChanged(positionStart, itemCount)
|
||||
checkEmpty()
|
||||
}
|
||||
|
||||
override fun onItemRangeChanged(positionStart: Int, itemCount: Int, payload: Any?) {
|
||||
super.onItemRangeChanged(positionStart, itemCount, payload)
|
||||
checkEmpty()
|
||||
}
|
||||
|
||||
override fun onChanged() {
|
||||
super.onChanged()
|
||||
checkEmpty()
|
||||
}
|
||||
|
||||
override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
|
||||
super.onItemRangeRemoved(positionStart, itemCount)
|
||||
checkEmpty()
|
||||
}
|
||||
|
||||
override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) {
|
||||
super.onItemRangeMoved(fromPosition, toPosition, itemCount)
|
||||
checkEmpty()
|
||||
}
|
||||
|
||||
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
|
||||
super.onItemRangeInserted(positionStart, itemCount)
|
||||
checkEmpty()
|
||||
}
|
||||
}
|
||||
)
|
||||
checkEmpty()
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = data.size
|
||||
|
||||
override fun onBindViewHolder(holder: AbstractViewHolder<T>, position: Int) {
|
||||
holder.onBind(data[position])
|
||||
}
|
||||
|
||||
private fun checkEmpty() {
|
||||
emptyView ?. let {
|
||||
if (data.isEmpty()) {
|
||||
it.visibility = View.VISIBLE
|
||||
} else {
|
||||
it.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,16 +1,19 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenLocal()
|
||||
jcenter()
|
||||
google()
|
||||
mavenCentral()
|
||||
mavenLocal()
|
||||
maven { url "https://plugins.gradle.org/m2/" }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:4.0.2'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
|
||||
classpath "com.jfrog.bintray.gradle:gradle-bintray-plugin:$gradle_bintray_plugin_version"
|
||||
classpath "com.getkeepsafe.dexcount:dexcount-gradle-plugin:$dexcount_version"
|
||||
classpath "com.github.breadmoirai:github-release:$github_release_plugin_version"
|
||||
classpath "org.jetbrains.dokka:dokka-gradle-plugin:$dokka_version"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +22,7 @@ allprojects {
|
||||
mavenLocal()
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
google()
|
||||
maven { url "https://kotlin.bintray.com/kotlinx" }
|
||||
}
|
||||
}
|
||||
|
@@ -12,4 +12,7 @@ function assert_success() {
|
||||
export RELEASE_MODE=true
|
||||
project="$1"
|
||||
|
||||
assert_success ./gradlew clean "$project:clean" "$project:build" "$project:publishToMavenLocal" "$project:bintrayUpload"
|
||||
assert_success ./gradlew clean
|
||||
assert_success ./gradlew "$project:build"
|
||||
assert_success ./gradlew "$project:publishToMavenLocal"
|
||||
assert_success ./gradlew "$project:bintrayUpload"
|
||||
|
@@ -1,24 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
function parse() {
|
||||
version=$1
|
||||
|
||||
while IFS= read -r line && [ -z "`echo $line | grep -e "^#\+ $version"`" ]
|
||||
do
|
||||
: # do nothing
|
||||
done
|
||||
|
||||
while IFS= read -r line && [ -z "`echo $line | grep -e "^#\+"`" ]
|
||||
do
|
||||
echo "$line"
|
||||
done
|
||||
}
|
||||
|
||||
version=$1
|
||||
file=$2
|
||||
|
||||
if [ -n "$file" ]; then
|
||||
parse $version < "$file"
|
||||
else
|
||||
parse $version
|
||||
fi
|
24
changelog_parser.sh
Executable file
24
changelog_parser.sh
Executable file
@@ -0,0 +1,24 @@
|
||||
#!/bin/bash
|
||||
|
||||
function parse() {
|
||||
version="$1"
|
||||
|
||||
while IFS= read -r line && [ -z "`echo "$line" | grep -e "^#\+ $version"`" ]
|
||||
do
|
||||
: # do nothing
|
||||
done
|
||||
|
||||
while IFS= read -r line && [ -z "`echo "$line" | grep -e "^#\+"`" ]
|
||||
do
|
||||
echo "$line"
|
||||
done
|
||||
}
|
||||
|
||||
version="$1"
|
||||
file="$2"
|
||||
|
||||
if [ -n "$file" ]; then
|
||||
parse "$version" < "$file"
|
||||
else
|
||||
parse "$version"
|
||||
fi
|
@@ -1,6 +1,7 @@
|
||||
plugins {
|
||||
id "org.jetbrains.kotlin.multiplatform"
|
||||
id "org.jetbrains.kotlin.plugin.serialization"
|
||||
id "com.android.library"
|
||||
}
|
||||
|
||||
apply from: "$mppProjectWithSerializationPresetPath"
|
||||
|
@@ -0,0 +1,37 @@
|
||||
package dev.inmo.micro_utils.common
|
||||
|
||||
@RequiresOptIn(
|
||||
"It is possible, that behaviour of this thing will be changed or removed in future releases",
|
||||
RequiresOptIn.Level.WARNING
|
||||
)
|
||||
@Target(
|
||||
AnnotationTarget.CLASS,
|
||||
AnnotationTarget.CONSTRUCTOR,
|
||||
AnnotationTarget.FIELD,
|
||||
AnnotationTarget.PROPERTY,
|
||||
AnnotationTarget.PROPERTY_GETTER,
|
||||
AnnotationTarget.PROPERTY_SETTER,
|
||||
AnnotationTarget.FUNCTION,
|
||||
AnnotationTarget.TYPE,
|
||||
AnnotationTarget.TYPEALIAS,
|
||||
AnnotationTarget.TYPE_PARAMETER
|
||||
)
|
||||
annotation class PreviewFeature
|
||||
|
||||
@RequiresOptIn(
|
||||
"This thing is marked as warned. See message of warn to get more info",
|
||||
RequiresOptIn.Level.WARNING
|
||||
)
|
||||
@Target(
|
||||
AnnotationTarget.CLASS,
|
||||
AnnotationTarget.CONSTRUCTOR,
|
||||
AnnotationTarget.FIELD,
|
||||
AnnotationTarget.PROPERTY,
|
||||
AnnotationTarget.PROPERTY_GETTER,
|
||||
AnnotationTarget.PROPERTY_SETTER,
|
||||
AnnotationTarget.FUNCTION,
|
||||
AnnotationTarget.TYPE,
|
||||
AnnotationTarget.TYPEALIAS,
|
||||
AnnotationTarget.TYPE_PARAMETER
|
||||
)
|
||||
annotation class Warning(val message: String)
|
@@ -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
|
||||
}
|
||||
}
|
@@ -1,10 +1,151 @@
|
||||
@file:Suppress("NOTHING_TO_INLINE")
|
||||
|
||||
package dev.inmo.micro_utils.common
|
||||
|
||||
fun <T> Iterable<T>.syncWith(
|
||||
other: Iterable<T>,
|
||||
removed: (List<T>) -> Unit = {},
|
||||
added: (List<T>) -> Unit = {}
|
||||
) {
|
||||
removed(filter { it !in other })
|
||||
added(other.filter { it !in this })
|
||||
private inline fun <T> getObject(
|
||||
additional: MutableList<T>,
|
||||
iterator: Iterator<T>
|
||||
): T? = when {
|
||||
additional.isNotEmpty() -> additional.removeFirst()
|
||||
iterator.hasNext() -> iterator.next()
|
||||
else -> null
|
||||
}
|
||||
|
||||
/**
|
||||
* Diff object which contains information about differences between two [Iterable]s
|
||||
*
|
||||
* @see calculateDiff
|
||||
*/
|
||||
data class Diff<T> internal constructor(
|
||||
val removed: List<IndexedValue<T>>,
|
||||
/**
|
||||
* Old-New values pairs
|
||||
*/
|
||||
val replaced: List<Pair<IndexedValue<T>, IndexedValue<T>>>,
|
||||
val added: List<IndexedValue<T>>
|
||||
)
|
||||
|
||||
private inline fun <T> performChanges(
|
||||
potentialChanges: MutableList<Pair<IndexedValue<T>?, IndexedValue<T>?>>,
|
||||
additionalsInOld: MutableList<T>,
|
||||
additionalsInNew: MutableList<T>,
|
||||
changedList: MutableList<Pair<IndexedValue<T>, IndexedValue<T>>>,
|
||||
removedList: MutableList<IndexedValue<T>>,
|
||||
addedList: MutableList<IndexedValue<T>>,
|
||||
strictComparison: Boolean
|
||||
) {
|
||||
var i = -1
|
||||
val (oldObject, newObject) = potentialChanges.lastOrNull() ?: return
|
||||
for ((old, new) in potentialChanges.take(potentialChanges.size - 1)) {
|
||||
i++
|
||||
val oldOneEqualToNewObject = old ?.value === newObject ?.value || (old ?.value == newObject ?.value && !strictComparison)
|
||||
val newOneEqualToOldObject = new ?.value === oldObject ?.value || (new ?.value == oldObject ?.value && !strictComparison)
|
||||
if (oldOneEqualToNewObject || newOneEqualToOldObject) {
|
||||
changedList.addAll(
|
||||
potentialChanges.take(i).mapNotNull {
|
||||
if (it.first != null && it.second != null) it as Pair<IndexedValue<T>, IndexedValue<T>> else null
|
||||
}
|
||||
)
|
||||
val newPotentials = potentialChanges.drop(i).take(potentialChanges.size - i)
|
||||
when {
|
||||
oldOneEqualToNewObject -> {
|
||||
newPotentials.first().second ?.let { addedList.add(it) }
|
||||
newPotentials.drop(1).take(newPotentials.size - 2).forEach { (oldOne, newOne) ->
|
||||
addedList.add(newOne!!)
|
||||
oldOne ?.let { additionalsInOld.add(oldOne.value) }
|
||||
}
|
||||
if (newPotentials.size > 1) {
|
||||
newPotentials.last().first ?.value ?.let { additionalsInOld.add(it) }
|
||||
}
|
||||
}
|
||||
newOneEqualToOldObject -> {
|
||||
newPotentials.first().first ?.let { removedList.add(it) }
|
||||
newPotentials.drop(1).take(newPotentials.size - 2).forEach { (oldOne, newOne) ->
|
||||
removedList.add(oldOne!!)
|
||||
newOne ?.let { additionalsInNew.add(newOne.value) }
|
||||
}
|
||||
if (newPotentials.size > 1) {
|
||||
newPotentials.last().second ?.value ?.let { additionalsInNew.add(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
potentialChanges.clear()
|
||||
return
|
||||
}
|
||||
}
|
||||
if (potentialChanges.isNotEmpty() && potentialChanges.last().let { it.first == null && it.second == null }) {
|
||||
potentialChanges.dropLast(1).forEach { (old, new) ->
|
||||
when {
|
||||
old != null && new != null -> changedList.add(old to new)
|
||||
old != null -> removedList.add(old)
|
||||
new != null -> addedList.add(new)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculating [Diff] object
|
||||
*
|
||||
* @param strictComparison If this parameter set to true, objects which are not equal by links will be used as different
|
||||
* objects. For example, in case of two "Example" string they will be equal by value, but CAN be different by links
|
||||
*/
|
||||
fun <T> Iterable<T>.calculateDiff(
|
||||
other: Iterable<T>,
|
||||
strictComparison: Boolean = false
|
||||
): Diff<T> {
|
||||
var i = -1
|
||||
var j = -1
|
||||
|
||||
val additionalInOld = mutableListOf<T>()
|
||||
val additionalInNew = mutableListOf<T>()
|
||||
|
||||
val oldIterator = iterator()
|
||||
val newIterator = other.iterator()
|
||||
|
||||
val potentiallyChangedObjects = mutableListOf<Pair<IndexedValue<T>?, IndexedValue<T>?>>()
|
||||
val changedObjects = mutableListOf<Pair<IndexedValue<T>, IndexedValue<T>>>()
|
||||
val addedObjects = mutableListOf<IndexedValue<T>>()
|
||||
val removedObjects = mutableListOf<IndexedValue<T>>()
|
||||
|
||||
while (true) {
|
||||
i++
|
||||
j++
|
||||
|
||||
val oldObject = getObject(additionalInOld, oldIterator)
|
||||
val newObject = getObject(additionalInNew, newIterator)
|
||||
|
||||
if (oldObject == null && newObject == null) {
|
||||
break
|
||||
}
|
||||
|
||||
when {
|
||||
oldObject === newObject || (oldObject == newObject && !strictComparison) -> {
|
||||
changedObjects.addAll(potentiallyChangedObjects.map { it as Pair<IndexedValue<T>, IndexedValue<T>> })
|
||||
potentiallyChangedObjects.clear()
|
||||
}
|
||||
else -> {
|
||||
potentiallyChangedObjects.add(oldObject ?.let { IndexedValue(i, oldObject) } to newObject ?.let { IndexedValue(j, newObject) })
|
||||
val previousOldsAdditionsSize = additionalInOld.size
|
||||
val previousNewsAdditionsSize = additionalInNew.size
|
||||
performChanges(potentiallyChangedObjects, additionalInOld, additionalInNew, changedObjects, removedObjects, addedObjects, strictComparison)
|
||||
i -= (additionalInOld.size - previousOldsAdditionsSize)
|
||||
j -= (additionalInNew.size - previousNewsAdditionsSize)
|
||||
}
|
||||
}
|
||||
}
|
||||
potentiallyChangedObjects.add(null to null)
|
||||
performChanges(potentiallyChangedObjects, additionalInOld, additionalInNew, changedObjects, removedObjects, addedObjects, strictComparison)
|
||||
|
||||
return Diff(removedObjects.toList(), changedObjects.toList(), addedObjects.toList())
|
||||
}
|
||||
|
||||
inline fun <T> Diff(old: Iterable<T>, new: Iterable<T>) = old.calculateDiff(new)
|
||||
inline fun <T> StrictDiff(old: Iterable<T>, new: Iterable<T>) = old.calculateDiff(new, true)
|
||||
|
||||
/**
|
||||
* This method call [calculateDiff] with strict mode enabled
|
||||
*/
|
||||
inline fun <T> Iterable<T>.calculateStrictDiff(
|
||||
other: Iterable<T>
|
||||
) = calculateDiff(other, strictComparison = true)
|
||||
|
@@ -0,0 +1,76 @@
|
||||
package dev.inmo.micro_utils.common
|
||||
|
||||
import kotlin.math.floor
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class DiffUtilsTests {
|
||||
@Test
|
||||
fun testThatSimpleRemoveWorks() {
|
||||
val oldList = (0 until 10).toList()
|
||||
val withIndex = oldList.withIndex()
|
||||
|
||||
for (count in 1 .. (floor(oldList.size.toFloat() / 2).toInt())) {
|
||||
for ((i, v) in withIndex) {
|
||||
if (i + count > oldList.lastIndex) {
|
||||
continue
|
||||
}
|
||||
val removedSublist = oldList.subList(i, i + count)
|
||||
oldList.calculateDiff(oldList - removedSublist).apply {
|
||||
assertEquals(
|
||||
removedSublist.mapIndexed { j, o -> IndexedValue(i + j, o) },
|
||||
removed
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testThatSimpleAddWorks() {
|
||||
val oldList = (0 until 10).map { it.toString() }
|
||||
val withIndex = oldList.withIndex()
|
||||
|
||||
for (count in 1 .. (floor(oldList.size.toFloat() / 2).toInt())) {
|
||||
for ((i, v) in withIndex) {
|
||||
if (i + count > oldList.lastIndex) {
|
||||
continue
|
||||
}
|
||||
val addedSublist = oldList.subList(i, i + count).map { "added$it" }
|
||||
val mutable = oldList.toMutableList()
|
||||
mutable.addAll(i, addedSublist)
|
||||
oldList.calculateDiff(mutable).apply {
|
||||
assertEquals(
|
||||
addedSublist.mapIndexed { j, o -> IndexedValue(i + j, o) },
|
||||
added
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testThatSimpleChangesWorks() {
|
||||
val oldList = (0 until 10).map { it.toString() }
|
||||
val withIndex = oldList.withIndex()
|
||||
|
||||
for (step in 0 until oldList.size) {
|
||||
for ((i, v) in withIndex) {
|
||||
val mutable = oldList.toMutableList()
|
||||
val changes = (
|
||||
if (step == 0) i until oldList.size else (i until oldList.size step step)
|
||||
).map { index ->
|
||||
IndexedValue(index, mutable[index]) to IndexedValue(index, "changed$index").also {
|
||||
mutable[index] = it.value
|
||||
}
|
||||
}
|
||||
oldList.calculateDiff(mutable).apply {
|
||||
assertEquals(
|
||||
changes,
|
||||
replaced
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,43 @@
|
||||
package dev.inmo.micro_utils.common
|
||||
|
||||
import kotlinx.browser.window
|
||||
import org.w3c.dom.DOMRect
|
||||
import org.w3c.dom.Element
|
||||
|
||||
val DOMRect.isOnScreenByLeftEdge: Boolean
|
||||
get() = left >= 0 && left <= window.innerWidth
|
||||
inline val Element.isOnScreenByLeftEdge
|
||||
get() = getBoundingClientRect().isOnScreenByLeftEdge
|
||||
|
||||
val DOMRect.isOnScreenByRightEdge: Boolean
|
||||
get() = right >= 0 && right <= window.innerWidth
|
||||
inline val Element.isOnScreenByRightEdge
|
||||
get() = getBoundingClientRect().isOnScreenByRightEdge
|
||||
|
||||
internal val DOMRect.isOnScreenHorizontally: Boolean
|
||||
get() = isOnScreenByLeftEdge || isOnScreenByRightEdge
|
||||
|
||||
|
||||
val DOMRect.isOnScreenByTopEdge: Boolean
|
||||
get() = top >= 0 && top <= window.innerHeight
|
||||
inline val Element.isOnScreenByTopEdge
|
||||
get() = getBoundingClientRect().isOnScreenByTopEdge
|
||||
|
||||
val DOMRect.isOnScreenByBottomEdge: Boolean
|
||||
get() = bottom >= 0 && bottom <= window.innerHeight
|
||||
inline val Element.isOnScreenByBottomEdge
|
||||
get() = getBoundingClientRect().isOnScreenByBottomEdge
|
||||
|
||||
internal val DOMRect.isOnScreenVertically: Boolean
|
||||
get() = isOnScreenByLeftEdge || isOnScreenByRightEdge
|
||||
|
||||
|
||||
val DOMRect.isOnScreenFully: Boolean
|
||||
get() = isOnScreenByLeftEdge && isOnScreenByTopEdge && isOnScreenByRightEdge && isOnScreenByBottomEdge
|
||||
val Element.isOnScreenFully: Boolean
|
||||
get() = getBoundingClientRect().isOnScreenFully
|
||||
|
||||
val DOMRect.isOnScreen: Boolean
|
||||
get() = isOnScreenFully || (isOnScreenHorizontally && isOnScreenVertically)
|
||||
inline val Element.isOnScreen: Boolean
|
||||
get() = getBoundingClientRect().isOnScreen
|
@@ -0,0 +1,48 @@
|
||||
package dev.inmo.micro_utils.common
|
||||
|
||||
import kotlinx.browser.window
|
||||
import org.w3c.dom.Element
|
||||
import org.w3c.dom.css.CSSStyleDeclaration
|
||||
|
||||
sealed class Visibility
|
||||
object Visible : Visibility()
|
||||
object Invisible : Visibility()
|
||||
object Gone : Visibility()
|
||||
|
||||
var CSSStyleDeclaration.visibilityState: Visibility
|
||||
get() = when {
|
||||
display == "none" -> Gone
|
||||
visibility == "hidden" -> Invisible
|
||||
else -> Visible
|
||||
}
|
||||
set(value) {
|
||||
when (value) {
|
||||
Visible -> {
|
||||
if (display == "none") {
|
||||
display = "initial"
|
||||
}
|
||||
visibility = "visible"
|
||||
}
|
||||
Invisible -> {
|
||||
if (display == "none") {
|
||||
display = "initial"
|
||||
}
|
||||
visibility = "hidden"
|
||||
}
|
||||
Gone -> {
|
||||
display = "none"
|
||||
}
|
||||
}
|
||||
}
|
||||
inline var Element.visibilityState: Visibility
|
||||
get() = window.getComputedStyle(this).visibilityState
|
||||
set(value) {
|
||||
window.getComputedStyle(this).visibilityState = value
|
||||
}
|
||||
|
||||
inline val Element.isVisible: Boolean
|
||||
get() = visibilityState == Visible
|
||||
inline val Element.isInvisible: Boolean
|
||||
get() = visibilityState == Invisible
|
||||
inline val Element.isGone: Boolean
|
||||
get() = visibilityState == Gone
|
1
common/src/main/AndroidManifest.xml
Normal file
1
common/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
||||
<manifest package="dev.inmo.micro_utils.common"/>
|
@@ -0,0 +1,13 @@
|
||||
@file:Suppress("NOTHING_TO_INLINE")
|
||||
|
||||
package dev.inmo.micro_utils.common
|
||||
|
||||
import android.content.res.Resources
|
||||
|
||||
inline fun Resources.getSp(
|
||||
resId: Int
|
||||
) = getDimension(resId) / displayMetrics.scaledDensity
|
||||
|
||||
inline fun Resources.getDp(
|
||||
resId: Int
|
||||
) = getDimension(resId) * displayMetrics.density
|
@@ -0,0 +1,140 @@
|
||||
package dev.inmo.micro_utils.common
|
||||
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.animation.Animation
|
||||
import android.view.animation.Transformation
|
||||
|
||||
private fun View.performExpand(
|
||||
duration: Long = 500,
|
||||
targetWidth: Int = ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
targetHeight: Int = ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
onMeasured: View.() -> Unit,
|
||||
onPerformAnimation: View.(interpolatedTime: Float, t: Transformation?) -> Unit
|
||||
) {
|
||||
measure(targetWidth, targetHeight)
|
||||
onMeasured()
|
||||
show()
|
||||
val a: Animation = object : Animation() {
|
||||
override fun applyTransformation(interpolatedTime: Float, t: Transformation?) {
|
||||
super.applyTransformation(interpolatedTime, t)
|
||||
onPerformAnimation(interpolatedTime, t)
|
||||
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 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
a.duration = duration
|
||||
|
||||
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
|
||||
fun View.collapse(duration: Long = 500) {
|
||||
val initialHeight: Int = measuredHeight
|
||||
performCollapse(duration) { interpolatedTime, _ ->
|
||||
layoutParams.height = initialHeight - (initialHeight * interpolatedTime).toInt()
|
||||
}
|
||||
}
|
||||
|
||||
@PreviewFeature
|
||||
fun View.collapseHorizontally(duration: Long = 500) {
|
||||
val initialWidth: Int = measuredWidth
|
||||
performCollapse(duration) { interpolatedTime, _ ->
|
||||
layoutParams.width = initialWidth - (initialWidth * interpolatedTime).toInt()
|
||||
}
|
||||
}
|
||||
|
||||
@PreviewFeature
|
||||
inline val View.isCollapsed
|
||||
get() = visibility == View.GONE
|
||||
|
||||
@PreviewFeature
|
||||
inline val View.isExpanded
|
||||
get() = !isCollapsed
|
||||
|
||||
/**
|
||||
* @return true in case of expanding
|
||||
*/
|
||||
@PreviewFeature
|
||||
fun View.toggleExpandState(duration: Long = 500): Boolean = if (isCollapsed) {
|
||||
expand(duration)
|
||||
true
|
||||
} else {
|
||||
collapse(duration)
|
||||
false
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true in case of expanding
|
||||
*/
|
||||
@PreviewFeature
|
||||
fun View.toggleExpandHorizontallyState(duration: Long = 500): Boolean = if (isCollapsed) {
|
||||
expandHorizontally(duration)
|
||||
true
|
||||
} else {
|
||||
collapseHorizontally(duration)
|
||||
false
|
||||
}
|
@@ -0,0 +1,7 @@
|
||||
package dev.inmo.micro_utils.common
|
||||
|
||||
@Suppress("UNCHECKED_CAST", "SimplifiableCall")
|
||||
inline fun <T, R> Iterable<T>.mapNotNullA(transform: (T) -> R?): List<R> = map(transform).filter { it != null } as List<R>
|
||||
|
||||
@Suppress("UNCHECKED_CAST", "SimplifiableCall")
|
||||
inline fun <T, R> Array<T>.mapNotNullA(mapper: (T) -> R?): List<R> = map(mapper).filter { it != null } as List<R>
|
@@ -0,0 +1,34 @@
|
||||
@file:Suppress("NOTHING_TO_INLINE", "unused")
|
||||
|
||||
package dev.inmo.micro_utils.common
|
||||
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
|
||||
inline val View.enabled
|
||||
get() = isEnabled
|
||||
|
||||
inline val View.disabled
|
||||
get() = !enabled
|
||||
|
||||
fun View.disable() {
|
||||
if (this is ViewGroup) {
|
||||
(0 until childCount).forEach { getChildAt(it).disable() }
|
||||
}
|
||||
isEnabled = false
|
||||
}
|
||||
|
||||
fun View.enable() {
|
||||
if (this is ViewGroup) {
|
||||
(0 until childCount).forEach { getChildAt(it).enable() }
|
||||
}
|
||||
isEnabled = true
|
||||
}
|
||||
|
||||
fun View.toggleEnabledState(enabled: Boolean) {
|
||||
if (enabled) {
|
||||
enable()
|
||||
} else {
|
||||
disable()
|
||||
}
|
||||
}
|
@@ -0,0 +1,35 @@
|
||||
@file:Suppress("NOTHING_TO_INLINE", "unused")
|
||||
|
||||
package dev.inmo.micro_utils.common
|
||||
|
||||
import android.view.View
|
||||
|
||||
inline val View.gone
|
||||
get() = visibility == View.GONE
|
||||
inline fun View.gone() {
|
||||
visibility = View.GONE
|
||||
}
|
||||
|
||||
inline val View.hidden
|
||||
get() = visibility == View.INVISIBLE
|
||||
inline fun View.hide() {
|
||||
visibility = View.INVISIBLE
|
||||
}
|
||||
|
||||
inline val View.shown
|
||||
get() = visibility == View.VISIBLE
|
||||
inline fun View.show() {
|
||||
visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
fun View.toggleVisibility(goneOnHide: Boolean = true) {
|
||||
if (isShown) {
|
||||
if (goneOnHide) {
|
||||
gone()
|
||||
} else {
|
||||
hide()
|
||||
}
|
||||
} else {
|
||||
show()
|
||||
}
|
||||
}
|
@@ -1,6 +1,7 @@
|
||||
plugins {
|
||||
id "org.jetbrains.kotlin.multiplatform"
|
||||
id "org.jetbrains.kotlin.plugin.serialization"
|
||||
id "com.android.library"
|
||||
}
|
||||
|
||||
apply from: "$mppProjectWithSerializationPresetPath"
|
||||
@@ -12,5 +13,10 @@ kotlin {
|
||||
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version"
|
||||
}
|
||||
}
|
||||
androidMain {
|
||||
dependencies {
|
||||
api "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -17,3 +17,15 @@ fun <T> CoroutineScope.actor(
|
||||
return channel
|
||||
}
|
||||
|
||||
inline fun <T> CoroutineScope.safeActor(
|
||||
channelCapacity: Int = Channel.UNLIMITED,
|
||||
noinline onException: ExceptionHandler<Unit> = {},
|
||||
crossinline block: suspend (T) -> Unit
|
||||
): Channel<T> = actor(
|
||||
channelCapacity
|
||||
) {
|
||||
safely(onException) {
|
||||
block(it)
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,21 +0,0 @@
|
||||
package dev.inmo.micro_utils.coroutines
|
||||
|
||||
import kotlinx.coroutines.channels.*
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
@Suppress("FunctionName")
|
||||
fun <T> BroadcastFlow(
|
||||
internalChannelSize: Int = Channel.BUFFERED
|
||||
): BroadcastFlow<T> {
|
||||
val channel = BroadcastChannel<T>(internalChannelSize)
|
||||
|
||||
return BroadcastFlow(
|
||||
channel,
|
||||
channel.asFlow()
|
||||
)
|
||||
}
|
||||
|
||||
class BroadcastFlow<T> internal constructor(
|
||||
private val channel: BroadcastChannel<T>,
|
||||
private val flow: Flow<T>
|
||||
): Flow<T> by flow, SendChannel<T> by channel
|
@@ -1,33 +0,0 @@
|
||||
package dev.inmo.micro_utils.coroutines
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.channels.BroadcastChannel
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
class BroadcastStateFlow<T> internal constructor(
|
||||
parentFlow: Flow<T>,
|
||||
private val stateGetter: () -> T
|
||||
) : StateFlow<T>, Flow<T> by parentFlow {
|
||||
override val value: T
|
||||
get() = stateGetter()
|
||||
}
|
||||
|
||||
fun <T> BroadcastChannel<T>.asStateFlow(value: T, scope: CoroutineScope): StateFlow<T> = asFlow().let {
|
||||
var state: T = value
|
||||
it.onEach { state = it }.launchIn(scope)
|
||||
BroadcastStateFlow(it) {
|
||||
state
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> BroadcastChannel<T?>.asStateFlow(scope: CoroutineScope): StateFlow<T?> = asStateFlow(null, scope)
|
||||
|
||||
fun <T> broadcastStateFlow(initial: T, scope: CoroutineScope, channelSize: Int = Channel.BUFFERED) = BroadcastChannel<T>(
|
||||
channelSize
|
||||
).let {
|
||||
it to it.asStateFlow(initial, scope)
|
||||
}
|
||||
|
||||
fun <T> broadcastStateFlow(scope: CoroutineScope, channelSize: Int = Channel.BUFFERED) = broadcastStateFlow<T?>(null, scope, channelSize)
|
||||
|
@@ -0,0 +1,5 @@
|
||||
package dev.inmo.micro_utils.coroutines
|
||||
|
||||
import kotlinx.coroutines.flow.FlowCollector
|
||||
|
||||
suspend inline operator fun <T> FlowCollector<T>.invoke(value: T) = emit(value)
|
@@ -0,0 +1,37 @@
|
||||
@file:Suppress("NOTHING_TO_INLINE")
|
||||
|
||||
package dev.inmo.micro_utils.coroutines
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
/**
|
||||
* Shortcut for chain if [Flow.onEach] and [Flow.launchIn]
|
||||
*/
|
||||
inline fun <T> Flow<T>.subscribe(scope: CoroutineScope, noinline block: suspend (T) -> Unit) = onEach(block).launchIn(scope)
|
||||
|
||||
/**
|
||||
* Use [subscribe], but all [block]s will be called inside of [safely] function.
|
||||
* Use [onException] to set up your reaction for [Throwable]s
|
||||
*/
|
||||
inline fun <T> Flow<T>.subscribeSafely(
|
||||
scope: CoroutineScope,
|
||||
noinline onException: ExceptionHandler<Unit> = { throw it },
|
||||
noinline block: suspend (T) -> Unit
|
||||
) = subscribe(scope) {
|
||||
safely(onException) {
|
||||
block(it)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Use [subscribeSafelyWithoutExceptions], but all exceptions inside of [safely] will be skipped
|
||||
*/
|
||||
inline fun <T> Flow<T>.subscribeSafelyWithoutExceptions(
|
||||
scope: CoroutineScope,
|
||||
noinline block: suspend (T) -> Unit
|
||||
) = subscribeSafely(
|
||||
scope,
|
||||
{},
|
||||
block
|
||||
)
|
@@ -0,0 +1,8 @@
|
||||
@file:Suppress("NOTHING_TO_INLINE")
|
||||
|
||||
package dev.inmo.micro_utils.coroutines
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.merge
|
||||
|
||||
inline operator fun <T> Flow<T>.plus(other: Flow<T>) = merge(this, other)
|
@@ -21,3 +21,10 @@ suspend inline fun <T> safely(
|
||||
onException(e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shortcut for [safely] without exception handler (instead of this you will receive null as a result)
|
||||
*/
|
||||
suspend inline fun <T> safelyWithoutExceptions(
|
||||
noinline block: suspend CoroutineScope.() -> T
|
||||
): T? = safely({ null }, block)
|
||||
|
@@ -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,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
coroutines/src/main/AndroidManifest.xml
Normal file
1
coroutines/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
||||
<manifest package="dev.inmo.micro_utils.coroutines"/>
|
@@ -0,0 +1,10 @@
|
||||
package dev.inmo.micro_utils.coroutines
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
suspend inline fun <T> doInUI(noinline block: suspend CoroutineScope.() -> T) = withContext(
|
||||
Dispatchers.Main,
|
||||
block
|
||||
)
|
40
defaultAndroidSettings
Normal file
40
defaultAndroidSettings
Normal file
@@ -0,0 +1,40 @@
|
||||
apply plugin: 'com.getkeepsafe.dexcount'
|
||||
|
||||
android {
|
||||
compileSdkVersion "$android_compileSdkVersion".toInteger()
|
||||
buildToolsVersion "$android_buildToolsVersion"
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion "$android_minSdkVersion".toInteger()
|
||||
targetSdkVersion "$android_compileSdkVersion".toInteger()
|
||||
versionCode "${android_code_version}".toInteger()
|
||||
versionName "$version"
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
}
|
||||
debug {
|
||||
debuggable true
|
||||
}
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
exclude 'META-INF/kotlinx-serialization-runtime.kotlin_module'
|
||||
exclude 'META-INF/kotlinx-serialization-cbor.kotlin_module'
|
||||
exclude 'META-INF/kotlinx-serialization-properties.kotlin_module'
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main.java.srcDirs += 'src/main/kotlin'
|
||||
}
|
||||
}
|
134
dokka/build.gradle
Normal file
134
dokka/build.gradle
Normal file
@@ -0,0 +1,134 @@
|
||||
plugins {
|
||||
id "org.jetbrains.kotlin.multiplatform"
|
||||
id "org.jetbrains.kotlin.plugin.serialization"
|
||||
id "com.android.library"
|
||||
id "org.jetbrains.dokka"
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
jcenter()
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvm()
|
||||
js(BOTH) {
|
||||
browser()
|
||||
nodejs()
|
||||
}
|
||||
android {}
|
||||
|
||||
sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
implementation kotlin('stdlib')
|
||||
|
||||
project.parent.subprojects.forEach {
|
||||
if (
|
||||
it != project
|
||||
&& it.hasProperty("kotlin")
|
||||
&& it.kotlin.sourceSets.any { it.name.contains("commonMain") }
|
||||
&& it.kotlin.sourceSets.any { it.name.contains("jsMain") }
|
||||
&& it.kotlin.sourceSets.any { it.name.contains("jvmMain") }
|
||||
&& it.kotlin.sourceSets.any { it.name.contains("androidMain") }
|
||||
) {
|
||||
api it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
jsMain {
|
||||
dependencies {
|
||||
implementation kotlin('stdlib')
|
||||
|
||||
project.parent.subprojects.forEach {
|
||||
if (
|
||||
it != project
|
||||
&& it.hasProperty("kotlin")
|
||||
&& it.kotlin.sourceSets.any { it.name.contains("commonMain") }
|
||||
&& it.kotlin.sourceSets.any { it.name.contains("jsMain") }
|
||||
) {
|
||||
api it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
jvmMain {
|
||||
dependencies {
|
||||
implementation kotlin('stdlib')
|
||||
|
||||
project.parent.subprojects.forEach {
|
||||
if (
|
||||
it != project
|
||||
&& it.hasProperty("kotlin")
|
||||
&& it.kotlin.sourceSets.any { it.name.contains("commonMain") }
|
||||
&& it.kotlin.sourceSets.any { it.name.contains("jvmMain") }
|
||||
) {
|
||||
api it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
androidMain {
|
||||
dependencies {
|
||||
implementation kotlin('stdlib')
|
||||
|
||||
project.parent.subprojects.forEach {
|
||||
if (
|
||||
it != project
|
||||
&& it.hasProperty("kotlin")
|
||||
&& it.kotlin.sourceSets.any { it.name.contains("commonMain") }
|
||||
&& it.kotlin.sourceSets.any { it.name.contains("androidMain") }
|
||||
) {
|
||||
api it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<SourceDirectorySet> findSourcesWithName(String... approximateNames) {
|
||||
return parent.subprojects
|
||||
.findAll { it != project && it.hasProperty("kotlin") }
|
||||
.collectMany { it.kotlin.sourceSets }
|
||||
.findAll { sourceSet ->
|
||||
approximateNames.any { nameToFilter ->
|
||||
sourceSet.name.contains(nameToFilter)
|
||||
}
|
||||
}.collect { it.kotlin }
|
||||
}
|
||||
|
||||
tasks.dokkaHtml {
|
||||
dokkaSourceSets {
|
||||
configureEach {
|
||||
skipDeprecated.set(true)
|
||||
|
||||
sourceLink {
|
||||
localDirectory.set(file("./"))
|
||||
remoteUrl.set(new URL("https://github.com/InsanusMokrassar/MicroUtils/blob/master/"))
|
||||
remoteLineSuffix.set("#L")
|
||||
}
|
||||
}
|
||||
|
||||
named("commonMain") {
|
||||
sourceRoots.setFrom(findSourcesWithName("commonMain"))
|
||||
}
|
||||
|
||||
named("jsMain") {
|
||||
sourceRoots.setFrom(findSourcesWithName("jsMain", "commonMain"))
|
||||
}
|
||||
|
||||
named("jvmMain") {
|
||||
sourceRoots.setFrom(findSourcesWithName("jvmMain", "commonMain"))
|
||||
}
|
||||
|
||||
named("androidMain") {
|
||||
sourceRoots.setFrom(findSourcesWithName("androidMain", "commonMain"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
apply from: "$defaultAndroidSettingsPresetPath"
|
1
dokka/src/main/AndroidManifest.xml
Normal file
1
dokka/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
||||
<manifest package="dev.inmo.dokka"/>
|
@@ -10,17 +10,21 @@ allprojects {
|
||||
}
|
||||
|
||||
internalProject = { name ->
|
||||
if (releaseMode) {
|
||||
"$group:$name:$version"
|
||||
} else {
|
||||
projectByName("$name")
|
||||
}
|
||||
// if (releaseMode) {
|
||||
// "$group:$name:$version"
|
||||
// } else {
|
||||
// projectByName("$name")
|
||||
// }
|
||||
projectByName("$name")
|
||||
}
|
||||
|
||||
releaseMode = (project.hasProperty('RELEASE_MODE') && project.property('RELEASE_MODE') == "true") || System.getenv('RELEASE_MODE') == "true"
|
||||
|
||||
mppProjectWithSerializationPresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerialization"
|
||||
mppJavaProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJavaProject"
|
||||
mppAndroidProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppAndroidProject"
|
||||
|
||||
defaultAndroidSettingsPresetPath = "${rootProject.projectDir.absolutePath}/defaultAndroidSettings"
|
||||
|
||||
publishGradlePath = "${rootProject.projectDir.absolutePath}/publish.gradle"
|
||||
publishMavenPath = "${rootProject.projectDir.absolutePath}/maven.publish.gradle"
|
||||
|
@@ -1,8 +1,11 @@
|
||||
private String getCurrentVersionChangelog() {
|
||||
OutputStream changelogDataOS = new ByteArrayOutputStream()
|
||||
exec {
|
||||
commandLine 'chmod', "+x", './changelog_parser.sh'
|
||||
}
|
||||
exec {
|
||||
standardOutput = changelogDataOS
|
||||
commandLine './changelog_info_retriever', "$library_version", 'CHANGELOG.md'
|
||||
commandLine './changelog_parser.sh', "${project.version}", 'CHANGELOG.md'
|
||||
}
|
||||
|
||||
return changelogDataOS.toString().trim()
|
||||
@@ -18,9 +21,9 @@ if (new File(projectDir, "secret.gradle").exists()) {
|
||||
owner "InsanusMokrassar"
|
||||
repo "MicroUtils"
|
||||
|
||||
tagName "$library_version"
|
||||
releaseName "$library_version"
|
||||
targetCommitish "$library_version"
|
||||
tagName "${project.version}"
|
||||
releaseName "${project.version}"
|
||||
targetCommitish "${project.version}"
|
||||
|
||||
body getCurrentVersionChangelog()
|
||||
}
|
||||
|
@@ -3,20 +3,42 @@ org.gradle.parallel=true
|
||||
kotlin.js.generate.externals=true
|
||||
kotlin.incremental=true
|
||||
kotlin.incremental.js=true
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
|
||||
kotlin_version=1.4.10
|
||||
kotlin_coroutines_version=1.3.9
|
||||
kotlin_serialisation_core_version=1.0.0
|
||||
kotlin_exposed_version=0.27.1
|
||||
kotlin_version=1.4.20
|
||||
kotlin_coroutines_version=1.4.2
|
||||
kotlin_serialisation_core_version=1.0.1
|
||||
kotlin_exposed_version=0.28.1
|
||||
|
||||
ktor_version=1.4.1
|
||||
ktor_version=1.4.3
|
||||
|
||||
klockVersion=1.12.1
|
||||
klockVersion=2.0.1
|
||||
|
||||
gradle_bintray_plugin_version=1.8.5
|
||||
github_release_plugin_version=2.2.12
|
||||
|
||||
uuidVersion=0.2.2
|
||||
uuidVersion=0.2.3
|
||||
|
||||
# ANDROID
|
||||
|
||||
core_ktx_version=1.3.2
|
||||
androidx_recycler_version=1.1.0
|
||||
appcompat_version=1.2.0
|
||||
|
||||
android_minSdkVersion=19
|
||||
android_compileSdkVersion=30
|
||||
android_buildToolsVersion=30.0.2
|
||||
dexcount_version=2.0.0
|
||||
junit_version=4.12
|
||||
test_ext_junit_version=1.1.2
|
||||
espresso_core=3.3.0
|
||||
|
||||
# Dokka
|
||||
|
||||
dokka_version=1.4.20
|
||||
|
||||
# Project data
|
||||
|
||||
group=dev.inmo
|
||||
version=0.2.0
|
||||
version=0.4.13
|
||||
android_code_version=17
|
||||
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
@@ -1,6 +1,7 @@
|
||||
plugins {
|
||||
id "org.jetbrains.kotlin.multiplatform"
|
||||
id "org.jetbrains.kotlin.plugin.serialization"
|
||||
id "com.android.library"
|
||||
}
|
||||
|
||||
apply from: "$mppProjectWithSerializationPresetPath"
|
||||
|
@@ -4,7 +4,8 @@ import dev.inmo.micro_utils.coroutines.safely
|
||||
import dev.inmo.micro_utils.ktor.common.*
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.features.websocket.ws
|
||||
import io.ktor.http.cio.websocket.*
|
||||
import io.ktor.http.cio.websocket.Frame
|
||||
import io.ktor.http.cio.websocket.readBytes
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.channelFlow
|
||||
import kotlinx.serialization.DeserializationStrategy
|
||||
@@ -63,10 +64,11 @@ inline fun <T> HttpClient.createStandardWebsocketFlow(
|
||||
inline fun <T> HttpClient.createStandardWebsocketFlow(
|
||||
url: String,
|
||||
crossinline checkReconnection: (Throwable?) -> Boolean = { true },
|
||||
deserializer: DeserializationStrategy<T>
|
||||
deserializer: DeserializationStrategy<T>,
|
||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
|
||||
) = createStandardWebsocketFlow(
|
||||
url,
|
||||
checkReconnection
|
||||
) {
|
||||
standardKtorSerialFormat.decodeDefault(deserializer, it)
|
||||
serialFormat.decodeDefault(deserializer, it)
|
||||
}
|
||||
|
@@ -8,27 +8,57 @@ import kotlinx.serialization.*
|
||||
|
||||
typealias BodyPair<T> = Pair<SerializationStrategy<T>, T>
|
||||
|
||||
class UnifiedRequester(
|
||||
private val client: HttpClient = HttpClient(),
|
||||
private val serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
|
||||
) {
|
||||
suspend fun <ResultType> uniget(
|
||||
url: String,
|
||||
resultDeserializer: DeserializationStrategy<ResultType>
|
||||
): ResultType = client.get<StandardKtorSerialInputData>(
|
||||
url
|
||||
).let {
|
||||
serialFormat.decodeDefault(resultDeserializer, it)
|
||||
}
|
||||
|
||||
|
||||
fun <T> encodeUrlQueryValue(
|
||||
serializationStrategy: SerializationStrategy<T>,
|
||||
value: T
|
||||
) = serialFormat.encodeHex(
|
||||
serializationStrategy,
|
||||
value
|
||||
)
|
||||
|
||||
suspend fun <BodyType, ResultType> unipost(
|
||||
url: String,
|
||||
bodyInfo: BodyPair<BodyType>,
|
||||
resultDeserializer: DeserializationStrategy<ResultType>
|
||||
) = client.post<StandardKtorSerialInputData>(url) {
|
||||
body = serialFormat.encodeDefault(bodyInfo.first, bodyInfo.second)
|
||||
}.let {
|
||||
serialFormat.decodeDefault(resultDeserializer, it)
|
||||
}
|
||||
|
||||
fun <T> createStandardWebsocketFlow(
|
||||
url: String,
|
||||
checkReconnection: (Throwable?) -> Boolean = { true },
|
||||
deserializer: DeserializationStrategy<T>
|
||||
) = client.createStandardWebsocketFlow(url, checkReconnection, deserializer, serialFormat)
|
||||
}
|
||||
|
||||
val defaultRequester = UnifiedRequester()
|
||||
|
||||
suspend fun <ResultType> HttpClient.uniget(
|
||||
url: String,
|
||||
resultDeserializer: DeserializationStrategy<ResultType>
|
||||
) = get<StandardKtorSerialInputData>(
|
||||
url
|
||||
).let {
|
||||
standardKtorSerialFormat.decodeDefault(resultDeserializer, it)
|
||||
}
|
||||
) = defaultRequester.uniget(url, resultDeserializer)
|
||||
|
||||
|
||||
fun <T> SerializationStrategy<T>.encodeUrlQueryValue(value: T) = standardKtorSerialFormat.encodeHex(
|
||||
this,
|
||||
value
|
||||
)
|
||||
fun <T> SerializationStrategy<T>.encodeUrlQueryValue(value: T) = defaultRequester.encodeUrlQueryValue(this, value)
|
||||
|
||||
suspend fun <BodyType, ResultType> HttpClient.unipost(
|
||||
url: String,
|
||||
bodyInfo: BodyPair<BodyType>,
|
||||
resultDeserializer: DeserializationStrategy<ResultType>
|
||||
) = post<StandardKtorSerialInputData>(url) {
|
||||
body = standardKtorSerialFormat.encodeDefault(bodyInfo.first, bodyInfo.second)
|
||||
}.let {
|
||||
standardKtorSerialFormat.decodeDefault(resultDeserializer, it)
|
||||
}
|
||||
) = defaultRequester.unipost(url, bodyInfo, resultDeserializer)
|
||||
|
1
ktor/client/src/main/AndroidManifest.xml
Normal file
1
ktor/client/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
||||
<manifest package="dev.inmo.micro_utils.ktor.client"/>
|
@@ -1,6 +1,7 @@
|
||||
plugins {
|
||||
id "org.jetbrains.kotlin.multiplatform"
|
||||
id "org.jetbrains.kotlin.plugin.serialization"
|
||||
id "com.android.library"
|
||||
}
|
||||
|
||||
apply from: "$mppProjectWithSerializationPresetPath"
|
||||
|
@@ -3,16 +3,28 @@ package dev.inmo.micro_utils.ktor.common
|
||||
typealias QueryParam = Pair<String, String?>
|
||||
typealias QueryParams = Map<String, String?>
|
||||
|
||||
/**
|
||||
* Create query part which includes key=value pairs separated with &
|
||||
*/
|
||||
val QueryParams.asUrlQuery: String
|
||||
get() = keys.joinToString("&") { "${it}${get(it) ?.let { value -> "=$value" }}" }
|
||||
|
||||
/**
|
||||
* Create query part which includes key=value pairs separated with &
|
||||
*/
|
||||
val List<QueryParam>.asUrlQuery: String
|
||||
get() = joinToString("&") { (key, value) -> "${key}${value ?.let { _ -> "=$value" }}" }
|
||||
|
||||
/**
|
||||
* Create query part which includes key=value pairs separated with & and attach to receiver
|
||||
*/
|
||||
fun String.includeQueryParams(
|
||||
queryParams: QueryParams
|
||||
): String = "$this${if(queryParams.isNotEmpty()) "${if (contains("?")) "&" else "?"}${queryParams.asUrlQuery}" else ""}"
|
||||
|
||||
/**
|
||||
* Create query part which includes key=value pairs separated with & and attach to receiver
|
||||
*/
|
||||
fun String.includeQueryParams(
|
||||
queryParams: List<QueryParam>
|
||||
): String = "$this${if (contains("?")) "&" else "?"}${queryParams.asUrlQuery}"
|
||||
|
1
ktor/common/src/main/AndroidManifest.xml
Normal file
1
ktor/common/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
||||
<manifest package="dev.inmo.micro_utils.ktor.common"/>
|
@@ -22,23 +22,22 @@ fun <T> Route.includeWebsocketHandling(
|
||||
converter: (T) -> StandardKtorSerialInputData
|
||||
) {
|
||||
webSocket(suburl) {
|
||||
// println("connected")
|
||||
safely {
|
||||
flow.collect {
|
||||
send(converter(it))
|
||||
}
|
||||
}
|
||||
// println("disconnected")
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> Route.includeWebsocketHandling(
|
||||
suburl: String,
|
||||
flow: Flow<T>,
|
||||
serializer: SerializationStrategy<T>
|
||||
serializer: SerializationStrategy<T>,
|
||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
|
||||
) = includeWebsocketHandling(
|
||||
suburl,
|
||||
flow
|
||||
) {
|
||||
standardKtorSerialFormat.encodeDefault(serializer, it)
|
||||
serialFormat.encodeDefault(serializer, it)
|
||||
}
|
||||
|
@@ -3,13 +3,88 @@ package dev.inmo.micro_utils.ktor.server
|
||||
import dev.inmo.micro_utils.coroutines.safely
|
||||
import dev.inmo.micro_utils.ktor.common.*
|
||||
import io.ktor.application.ApplicationCall
|
||||
import io.ktor.application.call
|
||||
import io.ktor.http.ContentType
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.request.receive
|
||||
import io.ktor.response.respond
|
||||
import io.ktor.response.respondBytes
|
||||
import io.ktor.util.toByteArray
|
||||
import io.ktor.routing.Route
|
||||
import io.ktor.util.pipeline.PipelineContext
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.serialization.*
|
||||
|
||||
class UnifiedRouter(
|
||||
private val serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
|
||||
private val serialFormatContentType: ContentType = standardKtorSerialFormatContentType
|
||||
) {
|
||||
fun <T> Route.includeWebsocketHandling(
|
||||
suburl: String,
|
||||
flow: Flow<T>,
|
||||
serializer: SerializationStrategy<T>
|
||||
) = includeWebsocketHandling(suburl, flow, serializer, serialFormat)
|
||||
|
||||
suspend fun <T> PipelineContext<*, ApplicationCall>.unianswer(
|
||||
answerSerializer: SerializationStrategy<T>,
|
||||
answer: T
|
||||
) {
|
||||
call.respondBytes (
|
||||
serialFormat.encodeDefault(answerSerializer, answer),
|
||||
serialFormatContentType
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun <T> PipelineContext<*, ApplicationCall>.uniload(
|
||||
deserializer: DeserializationStrategy<T>
|
||||
) = safely {
|
||||
serialFormat.decodeDefault(
|
||||
deserializer,
|
||||
call.receive()
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun PipelineContext<*, ApplicationCall>.getParameterOrSendError(
|
||||
field: String
|
||||
) = call.parameters[field].also {
|
||||
if (it == null) {
|
||||
call.respond(HttpStatusCode.BadRequest, "Request must contains $field")
|
||||
}
|
||||
}
|
||||
|
||||
fun PipelineContext<*, ApplicationCall>.getQueryParameter(
|
||||
field: String
|
||||
) = call.request.queryParameters[field]
|
||||
|
||||
suspend fun PipelineContext<*, ApplicationCall>.getQueryParameterOrSendError(
|
||||
field: String
|
||||
) = getQueryParameter(field).also {
|
||||
if (it == null) {
|
||||
call.respond(HttpStatusCode.BadRequest, "Request query parameters must contains $field")
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> PipelineContext<*, ApplicationCall>.decodeUrlQueryValue(
|
||||
field: String,
|
||||
deserializer: DeserializationStrategy<T>
|
||||
) = getQueryParameter(field) ?.let {
|
||||
serialFormat.decodeHex(
|
||||
deserializer,
|
||||
it
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun <T> PipelineContext<*, ApplicationCall>.decodeUrlQueryValueOrSendError(
|
||||
field: String,
|
||||
deserializer: DeserializationStrategy<T>
|
||||
) = decodeUrlQueryValue(field, deserializer).also {
|
||||
if (it == null) {
|
||||
call.respond(HttpStatusCode.BadRequest, "Request query parameters must contains $field")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val defaultUnifiedRouter = UnifiedRouter()
|
||||
|
||||
suspend fun <T> ApplicationCall.unianswer(
|
||||
answerSerializer: SerializationStrategy<T>,
|
||||
answer: T
|
||||
|
@@ -1,60 +0,0 @@
|
||||
apply plugin: 'maven-publish'
|
||||
|
||||
task javadocsJar(type: Jar) {
|
||||
classifier = 'javadoc'
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
project.publishing.publications.all {
|
||||
// rename artifacts
|
||||
groupId "${project.group}"
|
||||
if (it.name.contains('kotlinMultiplatform')) {
|
||||
artifactId = "${project.name}"
|
||||
} else {
|
||||
artifactId = "${project.name}-$name"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications.all {
|
||||
artifact javadocsJar
|
||||
|
||||
pom {
|
||||
description = ""
|
||||
name = "${project.name}"
|
||||
url = "https://git.inmo.dev/InsanusMokrassar/MicroUtils_mirror"
|
||||
|
||||
scm {
|
||||
developerConnection = "scm:git:[fetch=]ssh://git@git.inmo.dev:8322/InsanusMokrassar/MicroUtils_mirror.git[push=]ssh://git@git.inmo.dev:8322/InsanusMokrassar/MicroUtils_mirror.git"
|
||||
url = "ssh://git@git.inmo.dev:8322/InsanusMokrassar/MicroUtils_mirror.git"
|
||||
}
|
||||
|
||||
developers {
|
||||
|
||||
developer {
|
||||
id = "InsanusMokrassar"
|
||||
name = "Aleksei Ovsiannikov"
|
||||
email = "ovsyannikov.alexey95@gmail.com"
|
||||
}
|
||||
|
||||
|
||||
developer {
|
||||
id = "000Sanya"
|
||||
name = "Syrov Aleksandr"
|
||||
email = "000sanya.000sanya@gmail.com"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
licenses {
|
||||
|
||||
license {
|
||||
name = "Apache Software License 2.0"
|
||||
url = "https://git.inmo.dev/InsanusMokrassar/MicroUtils_mirror/src/master/LICENSE"
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,6 +1,7 @@
|
||||
plugins {
|
||||
id "org.jetbrains.kotlin.multiplatform"
|
||||
id "org.jetbrains.kotlin.plugin.serialization"
|
||||
id "com.android.library"
|
||||
}
|
||||
|
||||
apply from: "$mppProjectWithSerializationPresetPath"
|
||||
|
1
mime_types/src/main/AndroidManifest.xml
Normal file
1
mime_types/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
||||
<manifest package="dev.inmo.micro_utils.mime_types"/>
|
26
mppAndroidProject
Normal file
26
mppAndroidProject
Normal file
@@ -0,0 +1,26 @@
|
||||
project.version = "$version"
|
||||
project.group = "$group"
|
||||
|
||||
apply from: "$publishGradlePath"
|
||||
|
||||
kotlin {
|
||||
android {
|
||||
publishAllLibraryVariants()
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
implementation kotlin('stdlib')
|
||||
}
|
||||
}
|
||||
commonTest {
|
||||
dependencies {
|
||||
implementation kotlin('test-common')
|
||||
implementation kotlin('test-annotations-common')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
apply from: "$defaultAndroidSettingsPresetPath"
|
@@ -9,6 +9,9 @@ kotlin {
|
||||
browser()
|
||||
nodejs()
|
||||
}
|
||||
android {
|
||||
publishAllLibraryVariants()
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
commonMain {
|
||||
@@ -34,5 +37,14 @@ kotlin {
|
||||
implementation kotlin('test-junit')
|
||||
}
|
||||
}
|
||||
androidTest {
|
||||
dependencies {
|
||||
implementation kotlin('test-junit')
|
||||
implementation "androidx.test.ext:junit:$test_ext_junit_version"
|
||||
implementation "androidx.test.espresso:espresso-core:$espresso_core"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
apply from: "$defaultAndroidSettingsPresetPath"
|
||||
|
@@ -1,6 +1,7 @@
|
||||
plugins {
|
||||
id "org.jetbrains.kotlin.multiplatform"
|
||||
id "org.jetbrains.kotlin.plugin.serialization"
|
||||
id "com.android.library"
|
||||
}
|
||||
|
||||
apply from: "$mppProjectWithSerializationPresetPath"
|
||||
|
@@ -22,12 +22,27 @@ interface Pagination {
|
||||
val size: Int
|
||||
}
|
||||
|
||||
/**
|
||||
* Logical shortcut for comparison that page is 0
|
||||
*/
|
||||
inline val Pagination.isFirstPage
|
||||
get() = page == 0
|
||||
|
||||
/**
|
||||
* First number in index of objects. It can be used as offset for databases or other data sources
|
||||
*/
|
||||
val Pagination.firstIndex: Int
|
||||
get() = page * size
|
||||
|
||||
/**
|
||||
* Last number in index of objects. In fact, one [Pagination] object represent data in next range:
|
||||
*
|
||||
* [[firstIndex], [lastIndex]]; That means, that for [Pagination] with [Pagination.size] == 10 and [Pagination.page] == 1
|
||||
* you will retrieve [Pagination.firstIndex] == 10 and [Pagination.lastIndex] == 19. Here [Pagination.lastIndexExclusive] == 20
|
||||
*/
|
||||
val Pagination.lastIndexExclusive: Int
|
||||
get() = firstIndex + size
|
||||
|
||||
/**
|
||||
* Last number in index of objects. In fact, one [Pagination] object represent data in next range:
|
||||
*
|
||||
@@ -35,7 +50,7 @@ val Pagination.firstIndex: Int
|
||||
* you will retrieve [Pagination.firstIndex] == 10 and [Pagination.lastIndex] == 19.
|
||||
*/
|
||||
val Pagination.lastIndex: Int
|
||||
get() = firstIndex + size - 1
|
||||
get() = lastIndexExclusive - 1
|
||||
|
||||
/**
|
||||
* Calculates pages count for given [datasetSize]
|
||||
@@ -43,6 +58,11 @@ val Pagination.lastIndex: Int
|
||||
fun calculatePagesNumber(datasetSize: Long, pageSize: Int): Int {
|
||||
return ceil(datasetSize.toDouble() / pageSize).toInt()
|
||||
}
|
||||
/**
|
||||
* Calculates pages count for given [datasetSize]. As a fact, it is shortcut for [calculatePagesNumber]
|
||||
* @return calculated page number which can be correctly used in [PaginationResult] as [PaginationResult.page] value
|
||||
*/
|
||||
fun calculatePagesNumber(pageSize: Int, datasetSize: Long): Int = calculatePagesNumber(datasetSize, pageSize)
|
||||
/**
|
||||
* Calculates pages count for given [datasetSize]
|
||||
*/
|
||||
|
@@ -14,6 +14,8 @@ inline fun FirstPagePagination(size: Int = defaultMediumPageSize) =
|
||||
size = size
|
||||
)
|
||||
|
||||
val emptyPagination = Pagination(0, 0)
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun Pagination.nextPage() =
|
||||
SimplePagination(
|
||||
@@ -27,7 +29,21 @@ data class SimplePagination(
|
||||
override val size: Int
|
||||
) : Pagination
|
||||
|
||||
/**
|
||||
* Factory for [SimplePagination]
|
||||
*/
|
||||
fun Pagination(
|
||||
page: Int,
|
||||
size: Int
|
||||
) = SimplePagination(page, size)
|
||||
|
||||
/**
|
||||
* @param firstIndex Inclusive first index of pagination
|
||||
* @param lastIndex INCLUSIVE last index of pagination (last index of object covered by result [SimplePagination])
|
||||
*/
|
||||
fun PaginationByIndexes(
|
||||
firstIndex: Int,
|
||||
lastIndex: Int
|
||||
) = maxOf(0, (lastIndex - firstIndex + 1)).let { size ->
|
||||
Pagination(calculatePage(firstIndex, size), size)
|
||||
}
|
||||
|
@@ -21,7 +21,12 @@ fun <T> Iterable<T>.paginate(with: Pagination): PaginationResult<T> {
|
||||
}
|
||||
|
||||
fun <T> List<T>.paginate(with: Pagination): PaginationResult<T> {
|
||||
return subList(with.firstIndex, with.lastIndex + 1).createPaginationResult(
|
||||
val firstIndex = maxOf(with.firstIndex, 0)
|
||||
val lastIndex = minOf(with.lastIndexExclusive, size)
|
||||
if (firstIndex > lastIndex) {
|
||||
return emptyPaginationResult()
|
||||
}
|
||||
return subList(firstIndex, lastIndex).createPaginationResult(
|
||||
with,
|
||||
size.toLong()
|
||||
)
|
||||
|
@@ -0,0 +1,41 @@
|
||||
package dev.inmo.micro_utils.pagination.utils
|
||||
|
||||
import dev.inmo.micro_utils.pagination.*
|
||||
|
||||
class PaginatedIterator<T>(
|
||||
pageSize: Int,
|
||||
private val countGetter: () -> Long,
|
||||
private val paginationResultGetter: Pagination.() -> PaginationResult<T>
|
||||
) : Iterator<T> {
|
||||
private var pagination = FirstPagePagination(pageSize)
|
||||
private val currentStack = mutableListOf<T>()
|
||||
override fun hasNext(): Boolean = currentStack.isNotEmpty() || (countGetter() < pagination.lastIndexExclusive)
|
||||
|
||||
override fun next(): T {
|
||||
if (currentStack.isEmpty()) {
|
||||
val resultPagination = paginationResultGetter.invoke(pagination)
|
||||
currentStack.addAll(resultPagination.results)
|
||||
require(currentStack.isNotEmpty()) { "There is no elements left" }
|
||||
pagination = resultPagination.nextPage()
|
||||
}
|
||||
return currentStack.removeFirst()
|
||||
}
|
||||
}
|
||||
|
||||
class PaginatedIterable<T>(
|
||||
private val pageSize: Int,
|
||||
private val countGetter: () -> Long,
|
||||
private val paginationResultGetter: Pagination.() -> PaginationResult<T>
|
||||
) : Iterable<T> {
|
||||
override fun iterator(): Iterator<T> = PaginatedIterator(pageSize, countGetter, paginationResultGetter)
|
||||
}
|
||||
|
||||
/**
|
||||
* Will make iterable using incoming [countGetter] and [paginationResultGetter]
|
||||
*/
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun <T> makeIterable(
|
||||
noinline countGetter: () -> Long,
|
||||
pageSize: Int = defaultMediumPageSize,
|
||||
noinline paginationResultGetter: Pagination.() -> PaginationResult<T>
|
||||
): Iterable<T> = PaginatedIterable(pageSize, countGetter, paginationResultGetter)
|
@@ -0,0 +1,28 @@
|
||||
package dev.inmo.micro_utils.pagination.utils
|
||||
|
||||
import dev.inmo.micro_utils.pagination.*
|
||||
|
||||
/**
|
||||
* Example:
|
||||
*
|
||||
* * `|__f__l_______________________|` will be transformed to `|_______________________f__l__|`
|
||||
* * `|__f__l_|` will be transformed to `|__f__l_|`
|
||||
*
|
||||
* @return Reversed version of this [Pagination]
|
||||
*/
|
||||
fun Pagination.reverse(datasetSize: Long): SimplePagination {
|
||||
val pagesNumber = calculatePagesNumber(size, datasetSize)
|
||||
val newPage = pagesNumber - page - 1
|
||||
return when {
|
||||
page < 0 || page >= pagesNumber -> emptyPagination
|
||||
else -> Pagination(
|
||||
newPage,
|
||||
size
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shortcut for [reverse]
|
||||
*/
|
||||
fun Pagination.reverse(objectsCount: Int) = reverse(objectsCount.toLong())
|
@@ -0,0 +1,24 @@
|
||||
package dev.inmo.micro_utils.pagination.utils
|
||||
|
||||
import dev.inmo.micro_utils.pagination.*
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class PaginationReversingTests {
|
||||
@Test
|
||||
fun testThatCommonCaseWorksOk() {
|
||||
val pageSize = 3
|
||||
val collectionSize = 9
|
||||
|
||||
assertEquals(Pagination(-1, pageSize).reverse(collectionSize), Pagination(0, 0))
|
||||
|
||||
val middleFirstIndex = collectionSize / 2 - pageSize / 2
|
||||
val middleLastIndex = middleFirstIndex + pageSize - 1
|
||||
assertEquals(
|
||||
PaginationByIndexes(middleFirstIndex, middleLastIndex).reverse(collectionSize),
|
||||
PaginationByIndexes(middleFirstIndex, middleLastIndex)
|
||||
)
|
||||
|
||||
assertEquals(Pagination(calculatePagesNumber(collectionSize, pageSize), pageSize).reverse(collectionSize), Pagination(0, 0))
|
||||
}
|
||||
}
|
1
pagination/common/src/main/AndroidManifest.xml
Normal file
1
pagination/common/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
||||
<manifest package="dev.inmo.micro_utils.pagination.common"/>
|
@@ -1,6 +1,7 @@
|
||||
plugins {
|
||||
id "org.jetbrains.kotlin.multiplatform"
|
||||
id "org.jetbrains.kotlin.plugin.serialization"
|
||||
id "com.android.library"
|
||||
}
|
||||
|
||||
apply from: "$mppProjectWithSerializationPresetPath"
|
||||
|
1
pagination/ktor/common/src/main/AndroidManifest.xml
Normal file
1
pagination/ktor/common/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
||||
<manifest package="dev.inmo.micro_utils.pagination.ktor.common"/>
|
@@ -1 +1 @@
|
||||
{"bintrayConfig":{"repo":"MicroUtils","packageName":"${project.name}","packageVcs":"https://github.com/InsanusMokrassar/MicroUtils","autoPublish":true,"overridePublish":true},"licenses":[{"id":"Apache-2.0","title":"Apache Software License 2.0","url":"https://git.inmo.dev/InsanusMokrassar/MicroUtils_mirror/src/master/LICENSE"}],"mavenConfig":{"name":"${project.name}","description":"","url":"https://git.inmo.dev/InsanusMokrassar/MicroUtils_mirror","vcsUrl":"ssh://git@git.inmo.dev:8322/InsanusMokrassar/MicroUtils_mirror.git","developers":[{"id":"InsanusMokrassar","name":"Aleksei Ovsiannikov","eMail":"ovsyannikov.alexey95@gmail.com"},{"id":"000Sanya","name":"Syrov Aleksandr","eMail":"000sanya.000sanya@gmail.com"}]},"type":"Multiplatform"}
|
||||
{"bintrayConfig":{"repo":"MicroUtils","packageName":"${project.name}","packageVcs":"https://github.com/InsanusMokrassar/MicroUtils","autoPublish":true,"overridePublish":true},"licenses":[{"id":"Apache-2.0","title":"Apache Software License 2.0","url":"https://git.inmo.dev/InsanusMokrassar/MicroUtils_mirror/src/master/LICENSE"}],"mavenConfig":{"name":"${project.name}","description":"","url":"https://git.inmo.dev/InsanusMokrassar/MicroUtils_mirror","vcsUrl":"ssh://git@git.inmo.dev:8322/InsanusMokrassar/MicroUtils_mirror.git","developers":[{"id":"InsanusMokrassar","name":"Aleksei Ovsiannikov","eMail":"ovsyannikov.alexey95@gmail.com"},{"id":"000Sanya","name":"Syrov Aleksandr","eMail":"000sanya.000sanya@gmail.com"}]}}
|
119
publish.gradle
119
publish.gradle
@@ -1,59 +1,76 @@
|
||||
apply plugin: 'com.jfrog.bintray'
|
||||
apply plugin: 'maven-publish'
|
||||
|
||||
apply from: "$publishMavenPath"
|
||||
|
||||
bintray {
|
||||
user = project.hasProperty('BINTRAY_USER') ? project.property('BINTRAY_USER') : System.getenv('BINTRAY_USER')
|
||||
key = project.hasProperty('BINTRAY_KEY') ? project.property('BINTRAY_KEY') : System.getenv('BINTRAY_KEY')
|
||||
filesSpec {
|
||||
from "${buildDir}/publications/"
|
||||
eachFile {
|
||||
String directorySubname = it.getFile().parentFile.name
|
||||
if (it.getName() == "module.json") {
|
||||
if (directorySubname == "kotlinMultiplatform") {
|
||||
it.setPath("${project.name}/${project.version}/${project.name}-${project.version}.module")
|
||||
} else {
|
||||
it.setPath("${project.name}-${directorySubname}/${project.version}/${project.name}-${directorySubname}-${project.version}.module")
|
||||
}
|
||||
} else {
|
||||
if (directorySubname == "kotlinMultiplatform" && it.getName() == "pom-default.xml") {
|
||||
it.setPath("${project.name}/${project.version}/${project.name}-${project.version}.pom")
|
||||
} else {
|
||||
it.exclude()
|
||||
}
|
||||
}
|
||||
}
|
||||
into "${project.group}".replace(".", "/")
|
||||
}
|
||||
|
||||
publish = true
|
||||
override = true
|
||||
|
||||
pkg {
|
||||
repo = "MicroUtils"
|
||||
name = "${project.name}"
|
||||
vcsUrl = "https://github.com/InsanusMokrassar/MicroUtils"
|
||||
licenses = ["Apache-2.0"]
|
||||
version {
|
||||
name = "${project.version}"
|
||||
released = new Date()
|
||||
vcsTag = "${project.version}"
|
||||
gpg {
|
||||
sign = true
|
||||
passphrase = project.hasProperty('signing.gnupg.passphrase') ? project.property('signing.gnupg.passphrase') : System.getenv('signing.gnupg.passphrase')
|
||||
}
|
||||
}
|
||||
}
|
||||
task javadocsJar(type: Jar) {
|
||||
classifier = 'javadoc'
|
||||
}
|
||||
task sourceJar (type : Jar) {
|
||||
classifier = 'sources'
|
||||
}
|
||||
|
||||
bintrayUpload.doFirst {
|
||||
publications = publishing.publications.collect {
|
||||
afterEvaluate {
|
||||
project.publishing.publications.all {
|
||||
// rename artifacts
|
||||
groupId "${project.group}"
|
||||
if (it.name.contains('kotlinMultiplatform')) {
|
||||
null
|
||||
artifactId = "${project.name}"
|
||||
artifact sourceJar
|
||||
} else {
|
||||
it.name
|
||||
artifactId = "${project.name}-$name"
|
||||
}
|
||||
} - null
|
||||
}
|
||||
}
|
||||
|
||||
bintrayUpload.dependsOn publishToMavenLocal
|
||||
publishing {
|
||||
publications.all {
|
||||
artifact javadocsJar
|
||||
|
||||
pom {
|
||||
description = ""
|
||||
name = "${project.name}"
|
||||
url = "https://git.inmo.dev/InsanusMokrassar/MicroUtils_mirror"
|
||||
|
||||
scm {
|
||||
developerConnection = "scm:git:[fetch=]ssh://git@git.inmo.dev:8322/InsanusMokrassar/MicroUtils_mirror.git[push=]ssh://git@git.inmo.dev:8322/InsanusMokrassar/MicroUtils_mirror.git"
|
||||
url = "ssh://git@git.inmo.dev:8322/InsanusMokrassar/MicroUtils_mirror.git"
|
||||
}
|
||||
|
||||
developers {
|
||||
|
||||
developer {
|
||||
id = "InsanusMokrassar"
|
||||
name = "Aleksei Ovsiannikov"
|
||||
email = "ovsyannikov.alexey95@gmail.com"
|
||||
}
|
||||
|
||||
|
||||
developer {
|
||||
id = "000Sanya"
|
||||
name = "Syrov Aleksandr"
|
||||
email = "000sanya.000sanya@gmail.com"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
licenses {
|
||||
|
||||
license {
|
||||
name = "Apache Software License 2.0"
|
||||
url = "https://git.inmo.dev/InsanusMokrassar/MicroUtils_mirror/src/master/LICENSE"
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
name = "bintray"
|
||||
url = uri("https://api.bintray.com/maven/${project.hasProperty('BINTRAY_USER') ? project.property('BINTRAY_USER') : System.getenv('BINTRAY_USER')}/MicroUtils/${project.name}/;publish=1;override=1")
|
||||
credentials {
|
||||
username = project.hasProperty('BINTRAY_USER') ? project.property('BINTRAY_USER') : System.getenv('BINTRAY_USER')
|
||||
password = project.hasProperty('BINTRAY_KEY') ? project.property('BINTRAY_KEY') : System.getenv('BINTRAY_KEY')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@@ -1,6 +1,7 @@
|
||||
plugins {
|
||||
id "org.jetbrains.kotlin.multiplatform"
|
||||
id "org.jetbrains.kotlin.plugin.serialization"
|
||||
id "com.android.library"
|
||||
}
|
||||
|
||||
apply from: "$mppProjectWithSerializationPresetPath"
|
||||
@@ -15,5 +16,18 @@ kotlin {
|
||||
api "com.benasher44:uuid:$uuidVersion"
|
||||
}
|
||||
}
|
||||
|
||||
jvmMain {
|
||||
dependencies {
|
||||
api internalProject("micro_utils.common")
|
||||
}
|
||||
}
|
||||
androidMain {
|
||||
dependencies {
|
||||
api "androidx.core:core-ktx:$core_ktx_version"
|
||||
api internalProject("micro_utils.common")
|
||||
api internalProject("micro_utils.coroutines")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
package dev.inmo.micro_utils.repos
|
||||
|
||||
interface MapperRepo<FromKey, FromValue, ToKey, ToValue> {
|
||||
suspend fun FromKey.toOutKey() = this as ToKey
|
||||
suspend fun FromValue.toOutValue() = this as ToValue
|
||||
|
||||
suspend fun ToKey.toInnerKey() = this as FromKey
|
||||
suspend fun ToValue.toInnerValue() = this as FromValue
|
||||
}
|
||||
|
||||
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> mapper(
|
||||
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
|
||||
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
|
||||
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
|
||||
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
|
||||
) = object : MapperRepo<FromKey, FromValue, ToKey, ToValue> {
|
||||
override suspend fun FromKey.toOutKey(): ToKey = keyFromToTo()
|
||||
override suspend fun FromValue.toOutValue(): ToValue = valueFromToTo()
|
||||
override suspend fun ToKey.toInnerKey(): FromKey = keyToToFrom()
|
||||
override suspend fun ToValue.toInnerValue(): FromValue = valueToToFrom()
|
||||
}
|
@@ -1,30 +1,110 @@
|
||||
package dev.inmo.micro_utils.repos
|
||||
|
||||
import dev.inmo.micro_utils.pagination.Pagination
|
||||
import dev.inmo.micro_utils.pagination.PaginationResult
|
||||
import dev.inmo.micro_utils.pagination.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface ReadOneToManyKeyValueRepo<Key, Value> : Repo {
|
||||
suspend fun get(k: Key, pagination: Pagination, reversed: Boolean = false): PaginationResult<Value>
|
||||
suspend fun keys(pagination: Pagination, reversed: Boolean = false): PaginationResult<Key>
|
||||
suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean = false): PaginationResult<Key>
|
||||
suspend fun contains(k: Key): Boolean
|
||||
suspend fun contains(k: Key, v: Value): Boolean
|
||||
suspend fun count(k: Key): Long
|
||||
suspend fun count(): Long
|
||||
|
||||
suspend fun getAll(k: Key, reversed: Boolean = false): List<Value> = mutableListOf<Value>().also { list ->
|
||||
doWithPagination {
|
||||
get(k, it).also {
|
||||
list.addAll(it.results)
|
||||
}.nextPageIfNotEmpty()
|
||||
}
|
||||
if (reversed) {
|
||||
list.reverse()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WARNING!!! THIS METHOD PROBABLY IS NOT EFFICIENT, USE WITH CAUTION
|
||||
*/
|
||||
suspend fun getAll(reverseLists: Boolean = false): Map<Key, List<Value>> = mutableMapOf<Key, List<Value>>().also { map ->
|
||||
doWithPagination {
|
||||
keys(it).also { paginationResult ->
|
||||
paginationResult.results.forEach { k ->
|
||||
map[k] = getAll(k, reverseLists)
|
||||
}
|
||||
}.nextPageIfNotEmpty()
|
||||
}
|
||||
}
|
||||
}
|
||||
@Deprecated("Renamed", ReplaceWith("ReadOneToManyKeyValueRepo", "dev.inmo.micro_utils.repos.ReadOneToManyKeyValueRepo"))
|
||||
typealias OneToManyReadKeyValueRepo<Key, Value> = ReadOneToManyKeyValueRepo<Key, Value>
|
||||
typealias ReadKeyValuesRepo<Key,Value> = ReadOneToManyKeyValueRepo<Key, Value>
|
||||
|
||||
interface WriteOneToManyKeyValueRepo<Key, Value> : Repo {
|
||||
val onNewValue: Flow<Pair<Key, Value>>
|
||||
val onValueRemoved: Flow<Pair<Key, Value>>
|
||||
val onDataCleared: Flow<Key>
|
||||
|
||||
suspend fun add(k: Key, v: Value)
|
||||
suspend fun remove(k: Key, v: Value)
|
||||
suspend fun clear(k: Key)
|
||||
}
|
||||
@Deprecated("Renamed", ReplaceWith("WriteOneToManyKeyValueRepo", "dev.inmo.micro_utils.repos.WriteOneToManyKeyValueRepo"))
|
||||
typealias OneToManyWriteKeyValueRepo<Key, Value> = WriteOneToManyKeyValueRepo<Key, Value>
|
||||
suspend fun add(toAdd: Map<Key, List<Value>>)
|
||||
|
||||
interface OneToManyKeyValueRepo<Key, Value> : ReadOneToManyKeyValueRepo<Key, Value>, WriteOneToManyKeyValueRepo<Key, Value>
|
||||
suspend fun remove(toRemove: Map<Key, List<Value>>)
|
||||
|
||||
suspend fun clear(k: Key)
|
||||
|
||||
suspend fun set(toSet: Map<Key, List<Value>>) {
|
||||
toSet.keys.forEach { key -> clear(key) }
|
||||
add(toSet)
|
||||
}
|
||||
}
|
||||
typealias WriteKeyValuesRepo<Key,Value> = WriteOneToManyKeyValueRepo<Key, Value>
|
||||
|
||||
suspend inline fun <Key, Value, REPO : WriteOneToManyKeyValueRepo<Key, Value>> REPO.add(
|
||||
keysAndValues: List<Pair<Key, List<Value>>>
|
||||
) = add(keysAndValues.toMap())
|
||||
|
||||
suspend inline fun <Key, Value, REPO : WriteOneToManyKeyValueRepo<Key, Value>> REPO.add(
|
||||
vararg keysAndValues: Pair<Key, List<Value>>
|
||||
) = add(keysAndValues.toMap())
|
||||
|
||||
suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.add(
|
||||
k: Key, v: List<Value>
|
||||
) = add(mapOf(k to v))
|
||||
|
||||
suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.add(
|
||||
k: Key, vararg v: Value
|
||||
) = add(k, v.toList())
|
||||
|
||||
suspend inline fun <Key, Value, REPO : WriteOneToManyKeyValueRepo<Key, Value>> REPO.set(
|
||||
keysAndValues: List<Pair<Key, List<Value>>>
|
||||
) = set(keysAndValues.toMap())
|
||||
|
||||
suspend inline fun <Key, Value, REPO : WriteOneToManyKeyValueRepo<Key, Value>> REPO.set(
|
||||
vararg keysAndValues: Pair<Key, List<Value>>
|
||||
) = set(keysAndValues.toMap())
|
||||
|
||||
suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.set(
|
||||
k: Key, v: List<Value>
|
||||
) = set(mapOf(k to v))
|
||||
|
||||
suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.set(
|
||||
k: Key, vararg v: Value
|
||||
) = set(k, v.toList())
|
||||
|
||||
interface OneToManyKeyValueRepo<Key, Value> : ReadOneToManyKeyValueRepo<Key, Value>, WriteOneToManyKeyValueRepo<Key, Value>
|
||||
typealias KeyValuesRepo<Key,Value> = OneToManyKeyValueRepo<Key, Value>
|
||||
|
||||
suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.remove(
|
||||
keysAndValues: List<Pair<Key, List<Value>>>
|
||||
) = remove(keysAndValues.toMap())
|
||||
|
||||
suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.remove(
|
||||
vararg keysAndValues: Pair<Key, List<Value>>
|
||||
) = remove(keysAndValues.toMap())
|
||||
|
||||
suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.remove(
|
||||
k: Key,
|
||||
v: List<Value>
|
||||
) = remove(mapOf(k to v))
|
||||
|
||||
suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.remove(
|
||||
k: Key,
|
||||
vararg v: Value
|
||||
) = remove(k, v.toList())
|
||||
|
@@ -10,6 +10,7 @@ interface ReadStandardCRUDRepo<ObjectType, IdType> : Repo {
|
||||
suspend fun contains(id: IdType): Boolean
|
||||
suspend fun count(): Long
|
||||
}
|
||||
typealias ReadCRUDRepo<ObjectType, IdType> = ReadStandardCRUDRepo<ObjectType, IdType>
|
||||
|
||||
typealias UpdatedValuePair<IdType, ValueType> = Pair<IdType, ValueType>
|
||||
val <IdType> UpdatedValuePair<IdType, *>.id
|
||||
@@ -27,6 +28,7 @@ interface WriteStandardCRUDRepo<ObjectType, IdType, InputValueType> : Repo {
|
||||
suspend fun update(values: List<UpdatedValuePair<IdType, InputValueType>>): List<ObjectType>
|
||||
suspend fun deleteById(ids: List<IdType>)
|
||||
}
|
||||
typealias WriteCRUDRepo<ObjectType, IdType, InputValueType> = WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>
|
||||
|
||||
suspend fun <ObjectType, IdType, InputValueType> WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>.create(
|
||||
vararg values: InputValueType
|
||||
@@ -39,4 +41,5 @@ suspend fun <ObjectType, IdType, InputValueType> WriteStandardCRUDRepo<ObjectTyp
|
||||
) = deleteById(ids.toList())
|
||||
|
||||
interface StandardCRUDRepo<ObjectType, IdType, InputValueType> : ReadStandardCRUDRepo<ObjectType, IdType>,
|
||||
WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>
|
||||
WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>
|
||||
typealias CRUDRepo<ObjectType, IdType, InputValueType> = StandardCRUDRepo<ObjectType, IdType, InputValueType>
|
@@ -1,23 +1,51 @@
|
||||
package dev.inmo.micro_utils.repos
|
||||
|
||||
import dev.inmo.micro_utils.pagination.Pagination
|
||||
import dev.inmo.micro_utils.pagination.PaginationResult
|
||||
import dev.inmo.micro_utils.pagination.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface ReadStandardKeyValueRepo<Key, Value> : Repo {
|
||||
suspend fun get(k: Key): Value?
|
||||
suspend fun values(pagination: Pagination, reversed: Boolean = false): PaginationResult<Value>
|
||||
suspend fun keys(pagination: Pagination, reversed: Boolean = false): PaginationResult<Key>
|
||||
suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean = false): PaginationResult<Key>
|
||||
suspend fun contains(key: Key): Boolean
|
||||
suspend fun count(): Long
|
||||
}
|
||||
typealias ReadKeyValueRepo<Key,Value> = ReadStandardKeyValueRepo<Key, Value>
|
||||
|
||||
interface WriteStandardKeyValueRepo<Key, Value> : Repo {
|
||||
val onNewValue: Flow<Pair<Key, Value>>
|
||||
val onValueRemoved: Flow<Key>
|
||||
|
||||
suspend fun set(k: Key, v: Value)
|
||||
suspend fun unset(k: Key)
|
||||
suspend fun set(toSet: Map<Key, Value>)
|
||||
suspend fun unset(toUnset: List<Key>)
|
||||
suspend fun unsetWithValues(toUnset: List<Value>)
|
||||
}
|
||||
typealias WriteKeyValueRepo<Key,Value> = WriteStandardKeyValueRepo<Key, Value>
|
||||
|
||||
interface StandardKeyValueRepo<Key, Value> : ReadStandardKeyValueRepo<Key, Value>, WriteStandardKeyValueRepo<Key, Value>
|
||||
suspend inline fun <Key, Value> WriteStandardKeyValueRepo<Key, Value>.set(
|
||||
vararg toSet: Pair<Key, Value>
|
||||
) = set(toSet.toMap())
|
||||
|
||||
suspend inline fun <Key, Value> WriteStandardKeyValueRepo<Key, Value>.set(
|
||||
k: Key, v: Value
|
||||
) = set(k to v)
|
||||
|
||||
suspend inline fun <Key, Value> WriteStandardKeyValueRepo<Key, Value>.unset(
|
||||
vararg k: Key
|
||||
) = unset(k.toList())
|
||||
|
||||
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>
|
||||
|
@@ -0,0 +1,150 @@
|
||||
package dev.inmo.micro_utils.repos.mappers
|
||||
|
||||
import dev.inmo.micro_utils.pagination.Pagination
|
||||
import dev.inmo.micro_utils.pagination.PaginationResult
|
||||
import dev.inmo.micro_utils.repos.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
open class MapperReadStandardKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
private val to: ReadStandardKeyValueRepo<ToKey, ToValue>,
|
||||
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||
) : ReadStandardKeyValueRepo<FromKey, FromValue>, MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper {
|
||||
override suspend fun get(k: FromKey): FromValue? = to.get(
|
||||
k.toOutKey()
|
||||
) ?.toInnerValue()
|
||||
|
||||
override suspend fun values(
|
||||
pagination: Pagination,
|
||||
reversed: Boolean
|
||||
): PaginationResult<FromValue> = to.values(
|
||||
pagination,
|
||||
reversed
|
||||
).let {
|
||||
PaginationResult(
|
||||
it.page,
|
||||
it.pagesNumber,
|
||||
it.results.map { it.toInnerValue() },
|
||||
it.size
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun keys(
|
||||
pagination: Pagination,
|
||||
reversed: Boolean
|
||||
): PaginationResult<FromKey> = to.keys(
|
||||
pagination,
|
||||
reversed
|
||||
).let {
|
||||
PaginationResult(
|
||||
it.page,
|
||||
it.pagesNumber,
|
||||
it.results.map { it.toInnerKey() },
|
||||
it.size
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun keys(
|
||||
v: FromValue,
|
||||
pagination: Pagination,
|
||||
reversed: Boolean
|
||||
): PaginationResult<FromKey> = to.keys(
|
||||
v.toOutValue(),
|
||||
pagination,
|
||||
reversed
|
||||
).let {
|
||||
PaginationResult(
|
||||
it.page,
|
||||
it.pagesNumber,
|
||||
it.results.map { it.toInnerKey() },
|
||||
it.size
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun contains(key: FromKey): Boolean = to.contains(
|
||||
key.toOutKey()
|
||||
)
|
||||
|
||||
override suspend fun count(): Long = to.count()
|
||||
}
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun <FromKey, FromValue, ToKey, ToValue> ReadStandardKeyValueRepo<ToKey, ToValue>.withMapper(
|
||||
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||
): ReadStandardKeyValueRepo<FromKey, FromValue> = MapperReadStandardKeyValueRepo(this, mapper)
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> ReadStandardKeyValueRepo<ToKey, ToValue>.withMapper(
|
||||
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
|
||||
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
|
||||
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
|
||||
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
|
||||
): ReadStandardKeyValueRepo<FromKey, FromValue> = withMapper(
|
||||
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
|
||||
)
|
||||
|
||||
open class MapperWriteStandardKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
private val to: WriteStandardKeyValueRepo<ToKey, ToValue>,
|
||||
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||
) : WriteStandardKeyValueRepo<FromKey, FromValue>, MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper {
|
||||
override val onNewValue: Flow<Pair<FromKey, FromValue>> = to.onNewValue.map { (k, v) ->
|
||||
k.toInnerKey() to v.toInnerValue()
|
||||
}
|
||||
override val onValueRemoved: Flow<FromKey> = to.onValueRemoved.map { k ->
|
||||
k.toInnerKey()
|
||||
}
|
||||
|
||||
override suspend fun set(toSet: Map<FromKey, FromValue>) = to.set(
|
||||
toSet.map { (k, v) ->
|
||||
k.toOutKey() to v.toOutValue()
|
||||
}.toMap()
|
||||
)
|
||||
|
||||
override suspend fun unset(toUnset: List<FromKey>) = to.unset(
|
||||
toUnset.map { k ->
|
||||
k.toOutKey()
|
||||
}
|
||||
)
|
||||
|
||||
override suspend fun unsetWithValues(toUnset: List<FromValue>) = to.unsetWithValues(
|
||||
toUnset.map { it.toOutValue() }
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun <FromKey, FromValue, ToKey, ToValue> WriteStandardKeyValueRepo<ToKey, ToValue>.withMapper(
|
||||
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||
): WriteStandardKeyValueRepo<FromKey, FromValue> = MapperWriteStandardKeyValueRepo(this, mapper)
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> WriteStandardKeyValueRepo<ToKey, ToValue>.withMapper(
|
||||
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
|
||||
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
|
||||
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
|
||||
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
|
||||
): WriteStandardKeyValueRepo<FromKey, FromValue> = withMapper(
|
||||
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
|
||||
)
|
||||
|
||||
open class MapperStandardKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
private val to: StandardKeyValueRepo<ToKey, ToValue>,
|
||||
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||
) : StandardKeyValueRepo<FromKey, FromValue>,
|
||||
MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper,
|
||||
ReadStandardKeyValueRepo<FromKey, FromValue> by MapperReadStandardKeyValueRepo(to, mapper),
|
||||
WriteStandardKeyValueRepo<FromKey, FromValue> by MapperWriteStandardKeyValueRepo(to, mapper)
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun <FromKey, FromValue, ToKey, ToValue> StandardKeyValueRepo<ToKey, ToValue>.withMapper(
|
||||
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||
): StandardKeyValueRepo<FromKey, FromValue> = MapperStandardKeyValueRepo(this, mapper)
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> StandardKeyValueRepo<ToKey, ToValue>.withMapper(
|
||||
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
|
||||
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
|
||||
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
|
||||
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
|
||||
): StandardKeyValueRepo<FromKey, FromValue> = withMapper(
|
||||
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
|
||||
)
|
@@ -0,0 +1,155 @@
|
||||
package dev.inmo.micro_utils.repos.mappers
|
||||
|
||||
import dev.inmo.micro_utils.pagination.Pagination
|
||||
import dev.inmo.micro_utils.pagination.PaginationResult
|
||||
import dev.inmo.micro_utils.repos.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
open class MapperReadOneToManyKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
private val to: ReadOneToManyKeyValueRepo<ToKey, ToValue>,
|
||||
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||
) : ReadOneToManyKeyValueRepo<FromKey, FromValue>, MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper {
|
||||
override suspend fun get(
|
||||
k: FromKey,
|
||||
pagination: Pagination,
|
||||
reversed: Boolean
|
||||
): PaginationResult<FromValue> = to.get(
|
||||
k.toOutKey(),
|
||||
pagination,
|
||||
reversed
|
||||
).let {
|
||||
PaginationResult(
|
||||
it.page,
|
||||
it.pagesNumber,
|
||||
it.results.map { it.toInnerValue() },
|
||||
it.size
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun keys(
|
||||
pagination: Pagination,
|
||||
reversed: Boolean
|
||||
): PaginationResult<FromKey> = to.keys(
|
||||
pagination,
|
||||
reversed
|
||||
).let {
|
||||
PaginationResult(
|
||||
it.page,
|
||||
it.pagesNumber,
|
||||
it.results.map { it.toInnerKey() },
|
||||
it.size
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun keys(
|
||||
v: FromValue,
|
||||
pagination: Pagination,
|
||||
reversed: Boolean
|
||||
): PaginationResult<FromKey> = to.keys(
|
||||
v.toOutValue(),
|
||||
pagination,
|
||||
reversed
|
||||
).let {
|
||||
PaginationResult(
|
||||
it.page,
|
||||
it.pagesNumber,
|
||||
it.results.map { it.toInnerKey() },
|
||||
it.size
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun contains(k: FromKey): Boolean = to.contains(k.toOutKey())
|
||||
override suspend fun contains(k: FromKey, v: FromValue): Boolean = to.contains(k.toOutKey(), v.toOutValue())
|
||||
|
||||
override suspend fun count(): Long = to.count()
|
||||
override suspend fun count(k: FromKey): Long = to.count(k.toOutKey())
|
||||
}
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun <FromKey, FromValue, ToKey, ToValue> ReadOneToManyKeyValueRepo<ToKey, ToValue>.withMapper(
|
||||
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||
): ReadOneToManyKeyValueRepo<FromKey, FromValue> = MapperReadOneToManyKeyValueRepo(this, mapper)
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> ReadOneToManyKeyValueRepo<ToKey, ToValue>.withMapper(
|
||||
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
|
||||
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
|
||||
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
|
||||
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
|
||||
): ReadOneToManyKeyValueRepo<FromKey, FromValue> = withMapper(
|
||||
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
|
||||
)
|
||||
|
||||
open class MapperWriteOneToManyKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
private val to: WriteOneToManyKeyValueRepo<ToKey, ToValue>,
|
||||
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||
) : WriteOneToManyKeyValueRepo<FromKey, FromValue>, MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper {
|
||||
override val onNewValue: Flow<Pair<FromKey, FromValue>> = to.onNewValue.map { (k, v) ->
|
||||
k.toInnerKey() to v.toInnerValue()
|
||||
}
|
||||
override val onValueRemoved: Flow<Pair<FromKey, FromValue>> = to.onValueRemoved.map { (k, v) ->
|
||||
k.toInnerKey() to v.toInnerValue()
|
||||
}
|
||||
override val onDataCleared: Flow<FromKey> = to.onDataCleared.map { k ->
|
||||
k.toInnerKey()
|
||||
}
|
||||
|
||||
override suspend fun add(toAdd: Map<FromKey, List<FromValue>>) = to.add(
|
||||
toAdd.map { (k, v) ->
|
||||
k.toOutKey() to v.map { it.toOutValue() }
|
||||
}.toMap()
|
||||
)
|
||||
|
||||
override suspend fun remove(toRemove: Map<FromKey, List<FromValue>>) = to.remove(
|
||||
toRemove.map { (k, v) ->
|
||||
k.toOutKey() to v.map { it.toOutValue() }
|
||||
}.toMap()
|
||||
)
|
||||
|
||||
override suspend fun set(toSet: Map<FromKey, List<FromValue>>) {
|
||||
to.set(
|
||||
toSet.map { (k, vs) -> k.toOutKey() to vs.map { v -> v.toOutValue() } }.toMap()
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun clear(k: FromKey) = to.clear(k.toOutKey())
|
||||
}
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun <FromKey, FromValue, ToKey, ToValue> WriteOneToManyKeyValueRepo<ToKey, ToValue>.withMapper(
|
||||
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||
): WriteOneToManyKeyValueRepo<FromKey, FromValue> = MapperWriteOneToManyKeyValueRepo(this, mapper)
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> WriteOneToManyKeyValueRepo<ToKey, ToValue>.withMapper(
|
||||
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
|
||||
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
|
||||
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
|
||||
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
|
||||
): WriteOneToManyKeyValueRepo<FromKey, FromValue> = withMapper(
|
||||
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
|
||||
)
|
||||
|
||||
open class MapperOneToManyKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
private val to: OneToManyKeyValueRepo<ToKey, ToValue>,
|
||||
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||
) : OneToManyKeyValueRepo<FromKey, FromValue>,
|
||||
MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper,
|
||||
ReadOneToManyKeyValueRepo<FromKey, FromValue> by MapperReadOneToManyKeyValueRepo(to, mapper),
|
||||
WriteOneToManyKeyValueRepo<FromKey, FromValue> by MapperWriteOneToManyKeyValueRepo(to, mapper)
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun <FromKey, FromValue, ToKey, ToValue> OneToManyKeyValueRepo<ToKey, ToValue>.withMapper(
|
||||
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||
): OneToManyKeyValueRepo<FromKey, FromValue> = MapperOneToManyKeyValueRepo(this, mapper)
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> OneToManyKeyValueRepo<ToKey, ToValue>.withMapper(
|
||||
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
|
||||
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
|
||||
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
|
||||
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
|
||||
): OneToManyKeyValueRepo<FromKey, FromValue> = withMapper(
|
||||
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
|
||||
)
|
@@ -1,7 +1,7 @@
|
||||
package dev.inmo.micro_utils.repos.pagination
|
||||
|
||||
import dev.inmo.micro_utils.pagination.*
|
||||
import dev.inmo.micro_utils.repos.*
|
||||
import dev.inmo.micro_utils.repos.ReadStandardKeyValueRepo
|
||||
|
||||
suspend inline fun <Key, Value, REPO : ReadStandardKeyValueRepo<Key, Value>> REPO.doForAll(
|
||||
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
|
||||
|
@@ -1,7 +1,7 @@
|
||||
package dev.inmo.micro_utils.repos.pagination
|
||||
|
||||
import dev.inmo.micro_utils.pagination.*
|
||||
import dev.inmo.micro_utils.repos.*
|
||||
import dev.inmo.micro_utils.repos.ReadOneToManyKeyValueRepo
|
||||
|
||||
suspend inline fun <Key, Value, REPO : ReadOneToManyKeyValueRepo<Key, Value>> REPO.doForAll(
|
||||
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
|
||||
|
@@ -0,0 +1,13 @@
|
||||
package dev.inmo.micro_utils.repos.versions
|
||||
|
||||
import dev.inmo.micro_utils.repos.StandardKeyValueRepo
|
||||
import dev.inmo.micro_utils.repos.set
|
||||
|
||||
class KeyValueBasedVersionsRepoProxy<T>(
|
||||
private val keyValueStore: StandardKeyValueRepo<String, Int>,
|
||||
override val database: T
|
||||
) : StandardVersionsRepoProxy<T> {
|
||||
override suspend fun getTableVersion(tableName: String): Int? = keyValueStore.get(tableName)
|
||||
|
||||
override suspend fun updateTableVersion(tableName: String, version: Int) { keyValueStore.set(tableName, version) }
|
||||
}
|
@@ -0,0 +1,36 @@
|
||||
package dev.inmo.micro_utils.repos.versions
|
||||
|
||||
import dev.inmo.micro_utils.repos.Repo
|
||||
|
||||
interface StandardVersionsRepoProxy<T> : Repo {
|
||||
val database: T
|
||||
|
||||
suspend fun getTableVersion(tableName: String): Int?
|
||||
suspend fun updateTableVersion(tableName: String, version: Int)
|
||||
}
|
||||
|
||||
class StandardVersionsRepo<T>(
|
||||
private val proxy: StandardVersionsRepoProxy<T>
|
||||
) : VersionsRepo<T> {
|
||||
override suspend fun setTableVersion(
|
||||
tableName: String,
|
||||
version: Int,
|
||||
onCreate: suspend T.() -> Unit,
|
||||
onUpdate: suspend T.(from: Int, to: Int) -> Unit
|
||||
) {
|
||||
var savedVersion = proxy.getTableVersion(tableName)
|
||||
if (savedVersion == null) {
|
||||
proxy.database.onCreate()
|
||||
proxy.updateTableVersion(tableName, version)
|
||||
} else {
|
||||
while (savedVersion != null && savedVersion < version) {
|
||||
val newVersion = savedVersion + 1
|
||||
|
||||
proxy.database.onUpdate(savedVersion, newVersion)
|
||||
|
||||
proxy.updateTableVersion(tableName, newVersion)
|
||||
savedVersion = newVersion
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,31 @@
|
||||
package dev.inmo.micro_utils.repos.versions
|
||||
|
||||
import dev.inmo.micro_utils.repos.Repo
|
||||
|
||||
/**
|
||||
* This interface has been created due to requirement to work with different versions of databases and make some
|
||||
* migrations between versions
|
||||
*
|
||||
* @param T It is a type of database, which will be used by this repo to retrieve current table version and update it
|
||||
*/
|
||||
interface VersionsRepo<T> : Repo {
|
||||
/**
|
||||
* By default, instance of this interface will check that version of table with name [tableName] is less than
|
||||
* [version] or is absent
|
||||
*
|
||||
* * In case if [tableName] didn't found, will be called [onCreate] and version of table will be set up to [version]
|
||||
* * In case if [tableName] have version less than parameter [version], it will increase version one-by-one
|
||||
* until database version will be equal to [version]
|
||||
*
|
||||
* @param version Current version of table
|
||||
* @param onCreate This callback will be called in case when table have no information about table
|
||||
* @param onUpdate This callback will be called after **iterative** changing of version. It is expected that parameter
|
||||
* "to" will always be greater than "from"
|
||||
*/
|
||||
suspend fun setTableVersion(
|
||||
tableName: String,
|
||||
version: Int,
|
||||
onCreate: suspend T.() -> Unit = {},
|
||||
onUpdate: suspend T.(from: Int, to: Int) -> Unit = { _, _ ->}
|
||||
)
|
||||
}
|
@@ -0,0 +1,183 @@
|
||||
package dev.inmo.micro_utils.repos
|
||||
|
||||
import dev.inmo.micro_utils.common.Warning
|
||||
import dev.inmo.micro_utils.pagination.*
|
||||
import dev.inmo.micro_utils.pagination.utils.reverse
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.*
|
||||
import java.io.File
|
||||
import java.nio.file.FileSystems
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.StandardWatchEventKinds.*
|
||||
|
||||
private inline val String.isAbsolute
|
||||
get() = startsWith(File.separator)
|
||||
|
||||
class FileReadStandardKeyValueRepo(
|
||||
private val folder: File
|
||||
) : ReadStandardKeyValueRepo<String, File> {
|
||||
init {
|
||||
folder.mkdirs()
|
||||
}
|
||||
|
||||
override suspend fun get(k: String): File? {
|
||||
val file = File(folder, k)
|
||||
if (file.exists()) {
|
||||
return file
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override suspend fun values(pagination: Pagination, reversed: Boolean): PaginationResult<File> {
|
||||
val count = count()
|
||||
val resultPagination = if (reversed) pagination.reverse(count) else pagination
|
||||
val filesPaths = folder.list() ?.copyOfRange(resultPagination.firstIndex, resultPagination.lastIndex) ?: return emptyPaginationResult()
|
||||
if (reversed) {
|
||||
filesPaths.reverse()
|
||||
}
|
||||
return filesPaths.map { File(folder, it) }.createPaginationResult(
|
||||
resultPagination,
|
||||
count
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult<String> {
|
||||
val count = count()
|
||||
val resultPagination = if (reversed) pagination.reverse(count) else pagination
|
||||
val filesPaths = folder.list() ?.copyOfRange(resultPagination.firstIndex, resultPagination.lastIndex) ?: return emptyPaginationResult()
|
||||
if (reversed) {
|
||||
filesPaths.reverse()
|
||||
}
|
||||
return filesPaths.toList().createPaginationResult(
|
||||
resultPagination,
|
||||
count
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun keys(
|
||||
v: File,
|
||||
pagination: Pagination,
|
||||
reversed: Boolean
|
||||
): PaginationResult<String> {
|
||||
val resultPagination = if (reversed) pagination.reverse(1L) else pagination
|
||||
return if (resultPagination.isFirstPage) {
|
||||
val fileSubpath = v.absolutePath.removePrefix(folder.absolutePath)
|
||||
if (fileSubpath == v.absolutePath) {
|
||||
emptyList()
|
||||
} else {
|
||||
listOf(fileSubpath)
|
||||
}
|
||||
} else {
|
||||
emptyList()
|
||||
}.createPaginationResult(resultPagination, 1L)
|
||||
}
|
||||
|
||||
override suspend fun contains(key: String): Boolean {
|
||||
return File(folder, key).exists()
|
||||
}
|
||||
|
||||
override suspend fun count(): Long = folder.list() ?.size ?.toLong() ?: 0L
|
||||
}
|
||||
|
||||
/**
|
||||
* Files watching will not correctly works on Android 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")
|
||||
class FileWriteStandardKeyValueRepo(
|
||||
private val folder: File,
|
||||
filesChangedProcessingScope: CoroutineScope? = null
|
||||
) : WriteStandardKeyValueRepo<String, File> {
|
||||
private val _onNewValue = MutableSharedFlow<Pair<String, File>>()
|
||||
override val onNewValue: Flow<Pair<String, File>> = _onNewValue.asSharedFlow()
|
||||
private val _onValueRemoved = MutableSharedFlow<String>()
|
||||
override val onValueRemoved: Flow<String> = _onValueRemoved.asSharedFlow()
|
||||
|
||||
init {
|
||||
folder.mkdirs()
|
||||
filesChangedProcessingScope ?.let {
|
||||
it.launch {
|
||||
try {
|
||||
val folderPath = folder.toPath()
|
||||
while (isActive) {
|
||||
val key = try {
|
||||
folderPath.register(FileSystems.getDefault().newWatchService(), ENTRY_CREATE, ENTRY_MODIFY, ENTRY_DELETE)
|
||||
} catch (e: Exception) {
|
||||
// add verbose way to show that file watching is not working
|
||||
return@launch
|
||||
}
|
||||
|
||||
for (event in key.pollEvents()) {
|
||||
val relativeFilePath = (event.context() as? Path) ?: continue
|
||||
val file = relativeFilePath.toFile()
|
||||
val relativePath = file.toRelativeString(folder)
|
||||
|
||||
when (event.kind()) {
|
||||
ENTRY_CREATE, ENTRY_MODIFY -> {
|
||||
launch {
|
||||
_onNewValue.emit(relativePath to file)
|
||||
}
|
||||
}
|
||||
ENTRY_DELETE -> {
|
||||
launch {
|
||||
_onValueRemoved.emit(relativePath)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (key.isValid || folder.exists()) {
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
// add verbose way to notify that this functionality is disabled
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun set(toSet: Map<String, File>) {
|
||||
supervisorScope {
|
||||
toSet.map { (filename, fileSource) ->
|
||||
launch {
|
||||
val file = File(folder, filename)
|
||||
|
||||
file.delete()
|
||||
fileSource.copyTo(file, overwrite = true)
|
||||
_onNewValue.emit(filename to file)
|
||||
}
|
||||
}
|
||||
}.joinAll()
|
||||
}
|
||||
|
||||
override suspend fun unset(toUnset: List<String>) {
|
||||
toUnset.forEach {
|
||||
val file = File(folder, it)
|
||||
if (file.exists()) {
|
||||
file.delete()
|
||||
_onValueRemoved.emit(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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")
|
||||
class FileStandardKeyValueRepo(
|
||||
folder: File,
|
||||
filesChangedProcessingScope: CoroutineScope? = null
|
||||
) : StandardKeyValueRepo<String, File>,
|
||||
WriteStandardKeyValueRepo<String, File> by FileWriteStandardKeyValueRepo(folder, filesChangedProcessingScope),
|
||||
ReadStandardKeyValueRepo<String, File> by FileReadStandardKeyValueRepo(folder)
|
1
repos/common/src/main/AndroidManifest.xml
Normal file
1
repos/common/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
||||
<manifest package="dev.inmo.micro_utils.repos.common"/>
|
@@ -0,0 +1,46 @@
|
||||
package dev.inmo.micro_utils.repos
|
||||
|
||||
sealed class ColumnType(
|
||||
typeName: String,
|
||||
nullable: Boolean
|
||||
) {
|
||||
open val asType: String = "$typeName${if (!nullable) " not null" else ""}"
|
||||
sealed class Text(
|
||||
nullable: Boolean
|
||||
) : ColumnType("text", nullable) {
|
||||
object NULLABLE : Text(true)
|
||||
object NOT_NULLABLE : Text(false)
|
||||
}
|
||||
sealed class Numeric(
|
||||
typeName: String,
|
||||
autoincrement: Boolean = false,
|
||||
primaryKey: Boolean = false,
|
||||
nullable: Boolean = false
|
||||
) : ColumnType(
|
||||
typeName,
|
||||
nullable
|
||||
) {
|
||||
override val asType: String = "${super.asType}${if (primaryKey) " primary key" else ""}${if (autoincrement) " autoincrement" else ""}"
|
||||
|
||||
class INTEGER(
|
||||
autoincrement: Boolean = false,
|
||||
primaryKey: Boolean = false,
|
||||
nullable: Boolean = false
|
||||
) : Numeric(
|
||||
"integer",
|
||||
autoincrement,
|
||||
primaryKey,
|
||||
nullable
|
||||
)
|
||||
class DOUBLE(autoincrement: Boolean = false, primaryKey: Boolean = false, nullable: Boolean = false) : Numeric(
|
||||
"double",
|
||||
autoincrement,
|
||||
primaryKey,
|
||||
nullable
|
||||
)
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return asType
|
||||
}
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
package dev.inmo.micro_utils.repos
|
||||
|
||||
import androidx.core.content.contentValuesOf
|
||||
|
||||
@Suppress("UNCHECKED_CAST", "SimplifiableCall")
|
||||
fun contentValuesOfNotNull(vararg pairs: Pair<String, Any?>?) = contentValuesOf(
|
||||
*(pairs.filter { it != null } as List<Pair<String, Any?>>).toTypedArray()
|
||||
)
|
@@ -0,0 +1,21 @@
|
||||
package dev.inmo.micro_utils.repos
|
||||
|
||||
import android.database.Cursor
|
||||
|
||||
inline fun <T> Cursor.map(
|
||||
block: (Cursor) -> T
|
||||
): List<T> {
|
||||
val result = mutableListOf<T>()
|
||||
if (moveToFirst()) {
|
||||
do {
|
||||
result.add(block(this))
|
||||
} while (moveToNext())
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
fun Cursor.firstOrNull(): Cursor? = if (moveToFirst()) {
|
||||
this
|
||||
} else {
|
||||
null
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user