mirror of
https://github.com/InsanusMokrassar/MicroUtils.git
synced 2025-09-18 06:49:20 +00:00
Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
3e24cf1768 |
9
.github/workflows/build.yml
vendored
9
.github/workflows/build.yml
vendored
@@ -8,7 +8,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- uses: actions/setup-java@v1
|
- uses: actions/setup-java@v1
|
||||||
with:
|
with:
|
||||||
java-version: 17
|
java-version: 11
|
||||||
- name: Rewrite version
|
- name: Rewrite version
|
||||||
run: |
|
run: |
|
||||||
branch="`echo "${{ github.ref }}" | grep -o "[^/]*$"`"
|
branch="`echo "${{ github.ref }}" | grep -o "[^/]*$"`"
|
||||||
@@ -17,9 +17,8 @@ jobs:
|
|||||||
mv gradle.properties.tmp gradle.properties
|
mv gradle.properties.tmp gradle.properties
|
||||||
- name: Build
|
- name: Build
|
||||||
run: ./gradlew build
|
run: ./gradlew build
|
||||||
- name: Publish to InmoNexus
|
- name: Publish
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
run: ./gradlew publishAllPublicationsToInmoNexusRepository
|
run: ./gradlew publishAllPublicationsToGiteaRepository
|
||||||
env:
|
env:
|
||||||
INMONEXUS_USER: ${{ secrets.INMONEXUS_USER }}
|
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||||
INMONEXUS_PASSWORD: ${{ secrets.INMONEXUS_PASSWORD }}
|
|
||||||
|
2
.github/workflows/dokka_push.yml
vendored
2
.github/workflows/dokka_push.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- uses: actions/setup-java@v1
|
- uses: actions/setup-java@v1
|
||||||
with:
|
with:
|
||||||
java-version: 17
|
java-version: 11
|
||||||
- name: Build
|
- name: Build
|
||||||
run: ./gradlew build && ./gradlew dokkaHtml
|
run: ./gradlew build && ./gradlew dokkaHtml
|
||||||
- name: Publish KDocs
|
- name: Publish KDocs
|
||||||
|
7
.gitignore
vendored
7
.gitignore
vendored
@@ -1,5 +1,4 @@
|
|||||||
.idea
|
.idea
|
||||||
.kotlin
|
|
||||||
out/*
|
out/*
|
||||||
*.iml
|
*.iml
|
||||||
target
|
target
|
||||||
@@ -15,9 +14,3 @@ local.properties
|
|||||||
kotlin-js-store
|
kotlin-js-store
|
||||||
|
|
||||||
publishing.sh
|
publishing.sh
|
||||||
|
|
||||||
local.*
|
|
||||||
local/
|
|
||||||
**/*.local.*
|
|
||||||
|
|
||||||
.kotlin/
|
|
||||||
|
985
CHANGELOG.md
985
CHANGELOG.md
@@ -1,990 +1,9 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## 0.25.2
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Exposed`: `0.59.0` -> `0.60.0`
|
|
||||||
* `Repo`:
|
|
||||||
* `Cache`:
|
|
||||||
* Add extensions `alsoInvalidate`, `alsoInvalidateAsync`, `alsoInvalidateSync` and `alsoInvalidateSyncLogging`
|
|
||||||
* `Koin`:
|
|
||||||
* Add extensions `singleSuspend` and `factorySuspend` for defining of dependencies with suspendable blocks
|
|
||||||
|
|
||||||
## 0.25.1
|
|
||||||
|
|
||||||
* `Coroutines`:
|
|
||||||
* Add `SortedMapLikeBinaryTreeNode`
|
|
||||||
* `Pagination`:
|
|
||||||
* `Compose`:
|
|
||||||
* One more rework of `InfinityPagedComponent` and `PagedComponent`
|
|
||||||
|
|
||||||
## 0.25.0
|
|
||||||
|
|
||||||
* `Repos`:
|
|
||||||
* `Cache`:
|
|
||||||
* All cache repos now do not have `open` vals - to avoid collisions in runtime
|
|
||||||
|
|
||||||
## 0.24.9
|
|
||||||
|
|
||||||
* `Pagination`:
|
|
||||||
* Make alternative constructor parameter `size` of `PaginationResult` with default value
|
|
||||||
* Add `Pagination.previousPage` extension
|
|
||||||
* `Compose`:
|
|
||||||
* Rework of `InfinityPagedComponentContext`
|
|
||||||
* Rework of `PagedComponent`
|
|
||||||
|
|
||||||
## 0.24.8
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Ktor`: `3.1.0` -> `3.1.1`
|
|
||||||
* `KSP`: `2.1.10-1.0.30` -> `2.1.10-1.0.31`
|
|
||||||
* `Common`:
|
|
||||||
* `Compose`:
|
|
||||||
* Add component `LoadableComponent`
|
|
||||||
* `Coroutines`:
|
|
||||||
* Add `SortedBinaryTreeNode`
|
|
||||||
* `Pagination`:
|
|
||||||
* `Compose`:
|
|
||||||
* Add components `PagedComponent` and `InfinityPagedComponent`
|
|
||||||
|
|
||||||
## 0.24.7
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `SQLite`: `3.49.0.0` -> `3.49.1.0`
|
|
||||||
* `Common`:
|
|
||||||
* Add `retryOnFailure` utility for simple retries code writing
|
|
||||||
* `Repos`:
|
|
||||||
* `Cache`:
|
|
||||||
* Fix of `FullKeyValueCacheRepo` fields usage
|
|
||||||
* `Exposed`:
|
|
||||||
* `AbstractExposedKeyValuesRepo` will produce `onValueRemoved` event on `set` if some data has been removed
|
|
||||||
|
|
||||||
## 0.24.6
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `KSLog`: `1.4.0` -> `1.4.1`
|
|
||||||
* `Exposed`: `0.58.0` -> `0.59.0`
|
|
||||||
* `SQLite`: `3.48.0.0` -> `3.49.0.0`
|
|
||||||
* `AndroidFragment`: `1.8.5` -> `1.8.6`
|
|
||||||
* `Coroutines`:
|
|
||||||
* Safely functions has been replaced with `Logging` variations (resolve of [#541](https://github.com/InsanusMokrassar/MicroUtils/issues/541))
|
|
||||||
* `KSP`:
|
|
||||||
* `Variations`:
|
|
||||||
* Module has been created
|
|
||||||
|
|
||||||
## 0.24.5
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Kotlin`: `2.1.0` -> `2.1.10`
|
|
||||||
* `SQLite`: `3.47.2.0` -> `3.48.0.0`
|
|
||||||
* `Koin`: `4.0.1` -> `4.0.2`
|
|
||||||
* `Android RecyclerView`: `1.3.2` -> `1.4.0`
|
|
||||||
|
|
||||||
## 0.24.4
|
|
||||||
|
|
||||||
* `Repos`:
|
|
||||||
* `Exposed`:
|
|
||||||
* Improve `CommonExposedRepo.selectByIds`
|
|
||||||
* `FSM`:
|
|
||||||
* Fixes and improvements
|
|
||||||
|
|
||||||
## 0.24.3
|
|
||||||
|
|
||||||
* `Ksp`:
|
|
||||||
* `Sealed`:
|
|
||||||
* Fixes in processing of `GenerateSealedTypesWorkaround` annotations
|
|
||||||
|
|
||||||
## 0.24.2
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Exposed`: `0.57.0` -> `0.58.0`
|
|
||||||
* `Ksp`:
|
|
||||||
* `Sealed`:
|
|
||||||
* Add annotation `GenerateSealedTypesWorkaround` which allow to generate `subtypes` lists
|
|
||||||
|
|
||||||
## 0.24.1
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Serialization`: `1.7.3` -> `1.8.0`
|
|
||||||
* `SQLite`: `3.47.1.0` -> `3.47.2.0`
|
|
||||||
* `Koin`: `4.0.0` -> `3.10.2`
|
|
||||||
* `OKio`: `3.9.1` -> `3.10.2`
|
|
||||||
|
|
||||||
## 0.24.0
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Coroutines`: `1.9.0` -> `1.10.1`
|
|
||||||
* `KSLog`: `1.3.6` -> `1.4.0`
|
|
||||||
* `Compose`: `1.7.1` -> `1.7.3`
|
|
||||||
* `Ktor`: `3.0.2` -> `3.0.3`
|
|
||||||
* `Common`:
|
|
||||||
* Rename `Progress` to more common `Percentage`. `Progress` now is typealias
|
|
||||||
* Fix of `Progress.compareTo` extension
|
|
||||||
|
|
||||||
## 0.23.2
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Kotlin`: `2.0.21` -> `2.1.0`
|
|
||||||
* `Exposed`: `0.56.0` -> `0.57.0`
|
|
||||||
* `Xerial SQLite`: `3.47.0.0` -> `3.47.1.0`
|
|
||||||
* `Ktor`: `3.0.1` -> `3.0.2`
|
|
||||||
* `Coroutines`:
|
|
||||||
* Small refactor in `AccumulatorFlow` to use `runCatching` instead of `runCatchingSafely`
|
|
||||||
|
|
||||||
## 0.23.1
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Compose`: `1.7.0` -> `1.7.1`
|
|
||||||
* `Exposed`: `0.55.0` -> `0.56.0`
|
|
||||||
* `Xerial SQLite`: `3.46.1.3` -> `3.47.0.0`
|
|
||||||
* `Android CoreKTX`: `1.13.1` -> `1.15.0`
|
|
||||||
* `Android Fragment`: `1.8.4` -> `1.8.5`
|
|
||||||
* `Coroutines`:
|
|
||||||
* `Compose`:
|
|
||||||
* Add `StyleSheetsAggregator`
|
|
||||||
|
|
||||||
## 0.23.0
|
|
||||||
|
|
||||||
**THIS UPDATE MAY CONTAINS SOME BREAKING CHANGES (INCLUDING BREAKING CHANGES IN BYTECODE LAYER) RELATED TO UPDATE OF
|
|
||||||
KTOR DEPENDENCY**
|
|
||||||
|
|
||||||
**THIS UPDATE CONTAINS CHANGES ACCORDING TO MIGRATION [GUIDE FROM KTOR](https://ktor.io/docs/migrating-3.html)**
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Ktor`: `2.3.12` -> `3.0.1`
|
|
||||||
* `Ktor`:
|
|
||||||
* `Common`:
|
|
||||||
* Extension `Input.downloadToTempFile` has changed its receiver to `Source`. Its API can be broken
|
|
||||||
* `Client`:
|
|
||||||
* Extension `HttpClient.tempUpload` has changed type of `onUpload` argument from `OnUploadCallback` to `ProgressListener`
|
|
||||||
* All extensions `HttpClient.uniUpload` have changed type of `onUpload` argument from `OnUploadCallback` to `ProgressListener`
|
|
||||||
* `Server`:
|
|
||||||
* Remove redundant `ApplicationCall.respond` extension due to its presence in the ktor library
|
|
||||||
|
|
||||||
## 0.22.9
|
|
||||||
|
|
||||||
* `Repos`:
|
|
||||||
* `Cache`:
|
|
||||||
* Add direct caching repos
|
|
||||||
|
|
||||||
## 0.22.8
|
|
||||||
|
|
||||||
* `Common`:
|
|
||||||
* Add `List.breakAsPairs` extension
|
|
||||||
* Add `Sequence.padWith`/`Sequence.padStart`/`Sequence.padEnd` and `List.padWith`/`List.padStart`/`List.padEnd` extensions
|
|
||||||
|
|
||||||
## 0.22.7
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Kotlin`: `2.0.20` -> `2.0.21`
|
|
||||||
* `Compose`: `1.7.0-rc01` -> `1.7.0`
|
|
||||||
* `KSP`:
|
|
||||||
* `Sealed`:
|
|
||||||
* Change package of `GenerateSealedWorkaround`. Migration: replace `dev.inmo.microutils.kps.sealed.GenerateSealedWorkaround` -> `dev.inmo.micro_utils.ksp.sealed.GenerateSealedWorkaround`
|
|
||||||
|
|
||||||
## 0.22.6
|
|
||||||
|
|
||||||
* `KSP`:
|
|
||||||
* `Generator`:
|
|
||||||
* Add extension `KSClassDeclaration.buildSubFileName`
|
|
||||||
* Add extension `KSClassDeclaration.companion`
|
|
||||||
* Add extension `KSClassDeclaration.resolveSubclasses`
|
|
||||||
* `Sealed`:
|
|
||||||
* Improvements
|
|
||||||
|
|
||||||
## 0.22.5
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Compose`: `1.7.0-beta02` -> `1.7.0-rc01`
|
|
||||||
* `SQLite`: `3.46.1.2` -> `3.46.1.3`
|
|
||||||
* `AndroidXFragment`: `1.8.3` -> `1.8.4`
|
|
||||||
* `Common`:
|
|
||||||
* Add extension `withReplacedAt`/`withReplaced` ([#489](https://github.com/InsanusMokrassar/MicroUtils/issues/489))
|
|
||||||
* `Coroutines`:
|
|
||||||
* Add extension `Flow.debouncedBy`
|
|
||||||
* `Ktor`:
|
|
||||||
* `Server`:
|
|
||||||
* Add `KtorApplicationConfigurator.Routing.Static` as solution for [#488](https://github.com/InsanusMokrassar/MicroUtils/issues/488)
|
|
||||||
|
|
||||||
## 0.22.4
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Exposed`: `0.54.0` -> `0.55.0`
|
|
||||||
* `SQLite`: `3.46.1.0` -> `3.46.1.2`
|
|
||||||
|
|
||||||
## 0.22.3
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Serialization`: `1.7.2` -> `1.7.3`
|
|
||||||
* `Coroutines`: `1.8.1` -> `1.9.0`
|
|
||||||
* `Compose`: `1.7.0-alpha03` -> `1.7.0-beta02`
|
|
||||||
* `Koin`: `3.5.6` -> `4.0.0`
|
|
||||||
* `Okio`: `3.9.0` -> `3.9.1`
|
|
||||||
* `AndroidFragment`: `1.8.2` -> `1.8.3`
|
|
||||||
* `androidx.compose.material3:material3` has been replaced with `org.jetbrains.compose.material3:material3`
|
|
||||||
* `Common`:
|
|
||||||
* `JS`:
|
|
||||||
* Add several useful extensions
|
|
||||||
* `Compose`:
|
|
||||||
* `JS`:
|
|
||||||
* Add several useful extensions
|
|
||||||
|
|
||||||
## 0.22.2
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Exposed`: `0.53.0` -> `0.54.0`
|
|
||||||
* `SQLite`: `3.46.0.1` -> `3.46.1.0`
|
|
||||||
|
|
||||||
## 0.22.1
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Kotlin`: `2.0.10` -> `2.0.20`
|
|
||||||
* `Serialization`: `1.7.1` -> `1.7.2`
|
|
||||||
* `KSLog`: `1.3.5` -> `1.3.6`
|
|
||||||
* `Compose`: `1.7.0-alpha02` -> `1.7.0-alpha03`
|
|
||||||
* `Ktor`: `2.3.11` -> `2.3.12`
|
|
||||||
|
|
||||||
## 0.22.0
|
|
||||||
|
|
||||||
**THIS UPDATE CONTAINS BREAKING CHANGES ACCORDING TO UPDATE UP TO KOTLIN 2.0**
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Kotlin`: `1.9.23` -> `2.0.10`
|
|
||||||
* `Serialization`: `1.6.3` -> `1.7.1`
|
|
||||||
* `KSLog`: `1.3.4` -> `1.3.5`
|
|
||||||
* `Compose`: `1.6.2` -> `1.7.0-alpha02`
|
|
||||||
* `Exposed`: `0.50.1` -> `0.53.0`
|
|
||||||
* `AndroidAppCompat`: `1.6.1` -> `1.7.0`
|
|
||||||
* `AndroidFragment`: `1.7.1` -> `1.8.2`
|
|
||||||
|
|
||||||
## 0.21.6
|
|
||||||
|
|
||||||
* `KSP`:
|
|
||||||
* `Sealed`:
|
|
||||||
* Fixes in generation
|
|
||||||
|
|
||||||
## 0.21.5
|
|
||||||
|
|
||||||
* `KSP`:
|
|
||||||
* Add utility functions `KSClassDeclaration.findSubClasses`
|
|
||||||
* `Sealed`:
|
|
||||||
* Improve generation
|
|
||||||
|
|
||||||
## 0.21.4
|
|
||||||
|
|
||||||
* `Common`:
|
|
||||||
* `Compose`:
|
|
||||||
* Add support of mingw, linux, arm64 targets
|
|
||||||
* `Coroutines`:
|
|
||||||
* `Compose`:
|
|
||||||
* Add support of mingw, linux, arm64 targets
|
|
||||||
* `Koin`:
|
|
||||||
* Add support of mingw, linux, arm64 targets
|
|
||||||
* `KSP`:
|
|
||||||
* `ClassCasts`:
|
|
||||||
* Add support of mingw, linux, arm64 targets
|
|
||||||
* `Sealed`:
|
|
||||||
* Add support of mingw, linux, arm64 targets
|
|
||||||
|
|
||||||
## 0.21.3
|
|
||||||
|
|
||||||
* `Colors`:
|
|
||||||
* Added as a module. It should be used by default in case you wish all the API currently realized for `HEXAColor`
|
|
||||||
* `Coroutines`:
|
|
||||||
* Fix of [#374](https://github.com/InsanusMokrassar/MicroUtils/issues/374):
|
|
||||||
* Add vararg variants of `awaitFirst`
|
|
||||||
* Add `joinFirst`
|
|
||||||
|
|
||||||
## 0.21.2
|
|
||||||
|
|
||||||
* `KSP`:
|
|
||||||
* `ClassCasts`:
|
|
||||||
* Module has been initialized
|
|
||||||
|
|
||||||
## 0.21.1
|
|
||||||
|
|
||||||
* `KSP`:
|
|
||||||
* Module has been initialized
|
|
||||||
* `Generator`:
|
|
||||||
* Module has been initialized
|
|
||||||
* `Sealed`:
|
|
||||||
* Module has been initialized
|
|
||||||
|
|
||||||
## 0.21.0
|
|
||||||
|
|
||||||
**THIS UPDATE CONTAINS BREAKING CHANGES IN `safely*`-ORIENTED FUNCTIONS**
|
|
||||||
|
|
||||||
* `Coroutines`:
|
|
||||||
* **All `safely` functions lost their `supervisorScope` in favor to wrapping `runCatching`**
|
|
||||||
* `runCatchingSafely` is the main handling function of all `safely` functions
|
|
||||||
* `launchSafely*` and `asyncSafely*` blocks lost `CoroutineScope` as their receiver
|
|
||||||
|
|
||||||
## 0.20.52
|
|
||||||
|
|
||||||
* `Coroutines`:
|
|
||||||
* Small rework of weak jobs: add `WeakScope` factory, rename old weal extensions and add kdocs
|
|
||||||
|
|
||||||
## 0.20.51
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Android Fragment`: `1.7.0` -> `1.7.1`
|
|
||||||
* `Pagination`:
|
|
||||||
* Add `Pagination.nextPageIfTrue` and `Pagination.thisPageIftrue` extensions to get the page according to condition
|
|
||||||
pass status
|
|
||||||
* Add `PaginationResult.nextPageIfNotEmptyOrLastPage` and `PaginationResult.thisPageIfNotEmptyOrLastPage`
|
|
||||||
* Change all `doForAll` and `getAll` extensions fo pagination to work basing on `nextPageIfNotEmptyOrLastPage` and
|
|
||||||
`thisPageIfNotEmptyOrLastPage`
|
|
||||||
|
|
||||||
## 0.20.50
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Coroutines`: `1.8.0` -> `1.8.1`
|
|
||||||
* `KSLog`: `1.3.3` -> `1.3.4`
|
|
||||||
* `Exposed`: `0.50.0` -> `0.50.1`
|
|
||||||
* `Ktor`: `2.3.10` -> `2.3.11`
|
|
||||||
* A lot of inline functions became common functions due to inline with only noinline callbacks in arguments leads to
|
|
||||||
low performance
|
|
||||||
* `Coroutines`:
|
|
||||||
* `SmartMutex`, `SmartSemaphore` and `SmartRWLocker` as their user changed their state flow to `SpecialMutableStateFlow`
|
|
||||||
|
|
||||||
## 0.20.49
|
|
||||||
|
|
||||||
* `Repos`:
|
|
||||||
* `Common`:
|
|
||||||
* All `Repo`s get `diff` extensions
|
|
||||||
* `KeyValueRepo` and `KeyValuesRepo` get `applyDiff` extension
|
|
||||||
* Add new extensions `on*` flows for crud repos
|
|
||||||
|
|
||||||
## 0.20.48
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Android Core KTX`: `1.13.0` -> `1.13.1`
|
|
||||||
* `AndroidX Fragment`: `1.6.2` -> `1.7.0`
|
|
||||||
|
|
||||||
## 0.20.47
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Exposed`: `0.49.0` -> `0.50.0`
|
|
||||||
|
|
||||||
## 0.20.46
|
|
||||||
|
|
||||||
* `Common`:
|
|
||||||
* Now this repo depends on `klock`
|
|
||||||
* Add new object-serializer `DateTimeSerializer` for `klock` serializer
|
|
||||||
|
|
||||||
## 0.20.45
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Android Core KTX`: `1.12.0` -> `1.13.0`
|
|
||||||
|
|
||||||
## 0.20.44
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Compose`: `1.6.1` -> `1.6.2`
|
|
||||||
* `Koin`: `3.5.4` -> `3.5.6`
|
|
||||||
|
|
||||||
## 0.20.43
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Ktor`: `2.3.9` -> `2.3.10`
|
|
||||||
* `Koin`: `3.5.3` -> `3.5.4`
|
|
||||||
|
|
||||||
## 0.20.42
|
|
||||||
|
|
||||||
* `Repos`:
|
|
||||||
* `Generator`:
|
|
||||||
* Improvements
|
|
||||||
|
|
||||||
## 0.20.41
|
|
||||||
|
|
||||||
* `Repos`:
|
|
||||||
* `Exposed`:
|
|
||||||
* `AbstractExposedKeyValueRepo`, `ExposedKeyValueRepo`, `AbstractExposedKeyValuesRepo`, `ExposedKeyValuesRepo` got opportunity to setup some part of their flows
|
|
||||||
|
|
||||||
## 0.20.40
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `KSLog`: `1.3.2` -> `1.3.3`
|
|
||||||
* `Exposed`: `0.48.0` -> `0.49.0`
|
|
||||||
* `UUID`: `0.8.2` -> `0.8.4`
|
|
||||||
|
|
||||||
## 0.20.39
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Kotlin`: `1.9.22` -> `1.9.23`
|
|
||||||
* `Korlibs`: `5.3.2` -> `5.4.0`
|
|
||||||
* `Okio`: `3.8.0` -> `3.9.0`
|
|
||||||
* `Compose`: `1.6.0` -> `1.6.1`
|
|
||||||
* `ComposeMaterial3`: `1.2.0` -> `1.2.1`
|
|
||||||
|
|
||||||
## 0.20.38
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Ktor`: `2.3.8` -> `2.3.9`
|
|
||||||
|
|
||||||
## 0.20.37
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Compose`: `1.5.12` -> `1.6.0`
|
|
||||||
* `Exposed`: `0.47.0` -> `0.48.0`
|
|
||||||
|
|
||||||
## 0.20.36
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Serialization`: `1.6.2` -> `1.6.3`
|
|
||||||
* `Korlibs`: `5.3.1` -> `5.3.2`
|
|
||||||
* `Repos`:
|
|
||||||
* `Cache`:
|
|
||||||
* Improve work and functionality of `actualizeAll` and subsequent functions
|
|
||||||
* All internal repos `invalidate`/`actualizeAll` now use common `actualizeAll` functions
|
|
||||||
|
|
||||||
## 0.20.35
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Coroutines`: `1.7.3` -> `1.8.0`
|
|
||||||
* `Material3`: `1.1.2` -> `1.2.0`
|
|
||||||
|
|
||||||
## 0.20.34
|
|
||||||
|
|
||||||
* `Repos`:
|
|
||||||
* `Common`:
|
|
||||||
* Improve default `set` realization of `KeyValuesRepo`
|
|
||||||
|
|
||||||
## 0.20.33
|
|
||||||
|
|
||||||
* `Colors`
|
|
||||||
* `Common`:
|
|
||||||
* Add opportunity to use `HEXAColor` with `ahex` colors
|
|
||||||
|
|
||||||
## 0.20.32
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Okio`: `3.7.0` -> `3.8.0`
|
|
||||||
* `Resources`:
|
|
||||||
* Make `StringResource` serializable
|
|
||||||
* Add several variants of builder usages
|
|
||||||
|
|
||||||
## 0.20.31
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Ktor`: `2.3.7` -> `2.3.8`
|
|
||||||
|
|
||||||
## 0.20.30
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Exposed`: `0.46.0` -> `0.47.0`
|
|
||||||
|
|
||||||
## 0.20.29
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Kotlin`: `1.9.21` -> `1.9.22`
|
|
||||||
* `Compose`: `1.5.11` -> `1.5.12`
|
|
||||||
* `Korlibs`: `5.3.0` -> `5.3.1`
|
|
||||||
|
|
||||||
## 0.20.28
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Kotlin`: `1.9.22` -> `1.9.21` (downgrade)
|
|
||||||
* `Compose`: `1.6.0-dev13691` -> `1.5.11` (downgrade)
|
|
||||||
|
|
||||||
## 0.20.27
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Kotlin`: `1.9.21` -> `1.9.22`
|
|
||||||
* `Compose`: `1.5.11` -> `1.6.0-dev13691`
|
|
||||||
|
|
||||||
## 0.20.26
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Exposed`: `0.45.0` -> `0.46.0`. **This update brinds new api deprecations in exposed**
|
|
||||||
* `Resources`:
|
|
||||||
* Add opportunity to get default translation by passing `null` as `IetfLang` argument
|
|
||||||
* Add several useful extensions to get translations in `JS` target
|
|
||||||
|
|
||||||
## 0.20.25
|
|
||||||
|
|
||||||
* `Colors`:
|
|
||||||
* `Common`:
|
|
||||||
* Module inited
|
|
||||||
|
|
||||||
## 0.20.24
|
|
||||||
|
|
||||||
**Since this version depdendencies of klock and krypto replaced with `com.soywiz.korge:korlibs-time` and `com.soywiz.korge:korlibs-crypto`**
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Klock` (since now `KorlibsTime`): `4.0.10` -> `5.3.0`
|
|
||||||
* `Krypto` (since now `KorlibsCrypto`): `4.0.10` -> `5.3.0`
|
|
||||||
* `Serialization`:
|
|
||||||
* `Mapper`:
|
|
||||||
* `Mapper` pass decoder into callback of deserialization strategy
|
|
||||||
* `Mapper` pass encoder into callback of serialization strategy
|
|
||||||
|
|
||||||
## 0.20.23
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Koin`: `3.5.0` -> `3.5.3`
|
|
||||||
* `Okio`: `3.6.0` -> `3.7.0`
|
|
||||||
* `LanguageCodes`:
|
|
||||||
* Fixes in intermediate language codes (like `Chinese.Hans`)
|
|
||||||
* Rename `IetfLanguageCode` to `IetfLang`
|
|
||||||
* Rename all subsequent functions (including serializer)
|
|
||||||
* New lazy properties `knownLanguageCodesMap`, `knownLanguageCodesMapByLowerCasedKeys` and several others
|
|
||||||
|
|
||||||
## 0.20.22
|
|
||||||
|
|
||||||
* `Common`:
|
|
||||||
* Add opportunity to create own `Diff` with base constructor
|
|
||||||
|
|
||||||
## 0.20.21
|
|
||||||
|
|
||||||
* `Resources`:
|
|
||||||
* Inited
|
|
||||||
|
|
||||||
## 0.20.20
|
|
||||||
|
|
||||||
* `Repos`:
|
|
||||||
* `Exposed`:
|
|
||||||
* Add opportunity for setup flows in `AbstractExposedCRUDRepo`
|
|
||||||
|
|
||||||
## 0.20.19
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Ktor`: `2.3.6` -> `2.3.7`
|
|
||||||
|
|
||||||
## 0.20.18
|
|
||||||
|
|
||||||
* `Coroutines`:
|
|
||||||
* `SpecialMutableStateFlow` now extends `MutableStateFlow`
|
|
||||||
* `Compose`:
|
|
||||||
* Deprecate `FlowState` due to its complexity in fixes
|
|
||||||
|
|
||||||
## 0.20.17
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Serialization`: `1.6.1` -> `1.6.2`
|
|
||||||
|
|
||||||
## 0.20.16
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Exposed`: `0.44.1` -> `0.45.0`
|
|
||||||
* `Coroutines`:
|
|
||||||
* Add `SpecialMutableStateFlow`
|
|
||||||
* `Compose`:
|
|
||||||
* Add `FlowState`
|
|
||||||
|
|
||||||
## 0.20.15
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Kotlin`: `1.9.20` -> `1.9.21`
|
|
||||||
* `KSLog`: `1.3.0` -> `1.3.1`
|
|
||||||
* `Compose`: `1.5.10` -> `1.5.11`
|
|
||||||
|
|
||||||
## 0.20.14
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Serialization`: `1.6.0` -> `1.6.1`
|
|
||||||
* `KSLog`: `1.2.4` -> `1.3.0`
|
|
||||||
|
|
||||||
## 0.20.13
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Ktor`: `2.3.5` -> `2.3.6`
|
|
||||||
* `UUID`: `0.8.1` -> `0.8.2`
|
|
||||||
|
|
||||||
## 0.20.12
|
|
||||||
|
|
||||||
**It is experimental migration onto new gradle version. Be careful in use of this version**
|
|
||||||
|
|
||||||
**This update have JDK 17 in `compatibility` and `target` versions**
|
|
||||||
|
|
||||||
## 0.20.11
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Kotlin`: `1.9.20-RC2` -> `1.9.20`
|
|
||||||
* `Exposed`: `0.44.0` -> `0.44.1`
|
|
||||||
* `Compose`: `1.5.10-rc02` -> `1.5.10`
|
|
||||||
* `Coroutines`:
|
|
||||||
* `SmartRWLocker` now will wait first unlock of write mutex for acquiring read
|
|
||||||
|
|
||||||
## 0.20.10
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Kotlin`: `1.9.20-RC` -> `1.9.20-RC1`
|
|
||||||
* `KSLog`: `1.2.1` -> `1.2.2`
|
|
||||||
* `Compose`: `1.5.10-rc01` -> `1.5.10-rc02`
|
|
||||||
* `RecyclerView`: `1.3.1` -> `1.3.2`
|
|
||||||
|
|
||||||
## 0.20.9
|
|
||||||
|
|
||||||
* Most of common modules now supports `linuxArm64` target
|
|
||||||
|
|
||||||
## 0.20.8
|
|
||||||
|
|
||||||
**THIS VERSION CONTAINS UPDATES OF DEPENDENCIES UP TO RC VERSIONS. USE WITH CAUTION**
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Kotlin`: `1.9.20-Beta2` -> `1.9.20-RC`
|
|
||||||
* `Compose`: `1.5.10-beta02` -> `1.5.10-rc01`
|
|
||||||
|
|
||||||
## 0.20.7
|
|
||||||
|
|
||||||
**THIS VERSION CONTAINS UPDATES OF DEPENDENCIES UP TO BETA VERSIONS. USE WITH CAUTION**
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Kotlin`: `1.9.10` -> `1.9.20-Beta2`
|
|
||||||
* `Compose`: `1.5.1` -> `1.5.10-beta02`
|
|
||||||
* `Exposed`: `0.43.0` -> `0.44.0`
|
|
||||||
* `Ktor`: `2.3.4` -> `2.3.5`
|
|
||||||
* `Koin`: `3.4.3` -> `3.5.0`
|
|
||||||
* `Okio`: `3.5.0` -> `3.6.0`
|
|
||||||
* `Android Core`: `1.10.1` -> `1.12.0`
|
|
||||||
* `Android Compose Material`: `1.1.1` -> `1.1.2`
|
|
||||||
|
|
||||||
## 0.20.6
|
|
||||||
|
|
||||||
* `Repos`:
|
|
||||||
* `Exposed`
|
|
||||||
* Fixes in exposed key-values repos
|
|
||||||
|
|
||||||
## 0.20.5
|
|
||||||
|
|
||||||
* `Coroutines`:
|
|
||||||
* Fixes in `SmartRWLocker`
|
|
||||||
|
|
||||||
## 0.20.4
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Kotlin`: `1.9.0` -> `1.9.10`
|
|
||||||
* `KSLog`: `1.2.0` -> `1.2.1`
|
|
||||||
* `Compose`: `1.5.0` -> `1.5.1`
|
|
||||||
* `UUID`: `0.8.0` -> `0.8.1`
|
|
||||||
|
|
||||||
## 0.20.3
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Compose`: `1.4.3` -> `1.5.0`
|
|
||||||
* `Exposed`: `0.42.1` -> `0.43.0`
|
|
||||||
* `Ktor`: `2.3.3` -> `2.3.4`
|
|
||||||
* `Repos`:
|
|
||||||
* `Cache`:
|
|
||||||
* Fixes in locks of caches
|
|
||||||
|
|
||||||
## 0.20.2
|
|
||||||
|
|
||||||
* All main repos uses `SmartRWLocker`
|
|
||||||
* `Versions`:
|
|
||||||
* `Serialization`: `1.5.1` -> `1.6.0`
|
|
||||||
* `Exposed`: `0.42.0` -> `0.42.1`
|
|
||||||
* `Korlibs`: `4.0.9` -> `4.0.10`
|
|
||||||
* `Androis SDK`: `33` -> `34`
|
|
||||||
|
|
||||||
## 0.20.1
|
|
||||||
|
|
||||||
* `SmallTextField`:
|
|
||||||
* Module is initialized
|
|
||||||
* `Pickers`:
|
|
||||||
* Module is initialized
|
|
||||||
* `Coroutines`:
|
|
||||||
* Add `SmartSemaphore`
|
|
||||||
* Add `SmartRWLocker`
|
|
||||||
|
|
||||||
## 0.20.0
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Kotlin`: `1.8.22` -> `1.9.0`
|
|
||||||
* `KSLog`: `1.1.1` -> `1.2.0`
|
|
||||||
* `Exposed`: `0.41.1` -> `0.42.0`
|
|
||||||
* `UUID`: `0.7.1` -> `0.8.0`
|
|
||||||
* `Korlibs`: `4.0.3` -> `4.0.9`
|
|
||||||
* `Ktor`: `2.3.2` -> `2.3.3`
|
|
||||||
* `Okio`: `3.4.0` -> `3.5.0`
|
|
||||||
|
|
||||||
## 0.19.9
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Koin`: `3.4.2` -> `3.4.3`
|
|
||||||
* `Startup`:
|
|
||||||
* Now it is possible to start application in synchronous way
|
|
||||||
|
|
||||||
## 0.19.8
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Coroutines`: `1.7.2` -> `1.7.3`
|
|
||||||
* `Kotlin`: `1.8.20` -> `1.8.22`
|
|
||||||
* `Compose`: `1.4.1` -> `1.4.3`
|
|
||||||
* `Okio`: `3.3.0` -> `3.4.0`
|
|
||||||
* `RecyclerView`: `1.3.0` -> `1.3.1`
|
|
||||||
* `Fragment`: `1.6.0` -> `1.6.1`
|
|
||||||
* `Repos`:
|
|
||||||
* Fixes In `KeyValueRepo.clear()` of almost all inheritors of `KeyValueRepo`
|
|
||||||
* `Cache`:
|
|
||||||
* All full caches got `skipStartInvalidate` property. By default, this property is `false` and fully caching repos
|
|
||||||
will be automatically invalidated on start of their work
|
|
||||||
|
|
||||||
## 0.19.7
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Coroutines`: `1.7.1` -> `1.7.2`
|
|
||||||
|
|
||||||
## 0.19.6
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Coroutines`: `1.6.4` -> `1.7.1`
|
|
||||||
* `Ktor`: `2.3.1` -> `2.3.2`
|
|
||||||
* `Compose`: `1.4.0` -> `1.4.1`
|
|
||||||
|
|
||||||
## 0.19.5
|
|
||||||
|
|
||||||
* `Repos`:
|
|
||||||
* `Generator`:
|
|
||||||
* Fixes in new type generation
|
|
||||||
|
|
||||||
## 0.19.4
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Koin`: `3.4.1` -> `3.4.2`
|
|
||||||
* `Android Fragments`: `1.5.7` -> `1.6.0`
|
|
||||||
* `Koin`
|
|
||||||
* `Generator`
|
|
||||||
* Fixes in new generic generator part
|
|
||||||
|
|
||||||
## 0.19.3
|
|
||||||
|
|
||||||
* `Koin`
|
|
||||||
* `Generator`
|
|
||||||
* New getter methods now available with opportunity to use parameters
|
|
||||||
* Old notation `*Single` and `*Factory` is deprecated since this release. With old
|
|
||||||
will be generated new `single*` and `factory*` notations for new generations
|
|
||||||
* Add opportunity to use generic-oriented koin definitions
|
|
||||||
|
|
||||||
## 0.19.2
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Ktor`: `2.3.0` -> `2.3.1`
|
|
||||||
* `Koin`: `3.4.0` -> `3.4.1`
|
|
||||||
* `Uuid`: `0.7.0` -> `0.7.1`
|
|
||||||
|
|
||||||
## 0.19.1
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Korlibs`: `4.0.1` -> `4.0.3`
|
|
||||||
* `Kotlin Poet`: `1.13.2` -> `1.14.0`
|
|
||||||
|
|
||||||
## 0.19.0
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Korlibs`: `3.4.0` -> `4.0.1`
|
|
||||||
|
|
||||||
## 0.18.4
|
|
||||||
|
|
||||||
* `Koin`:
|
|
||||||
* New extension `lazyInject`
|
|
||||||
|
|
||||||
## 0.18.3
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Serialization`: `1.5.0` -> `1.5.1`
|
|
||||||
* `Android Cor Ktx`: `1.10.0` -> `1.10.1`
|
|
||||||
|
|
||||||
## 0.18.2
|
|
||||||
|
|
||||||
* `Startup`:
|
|
||||||
* Now internal `Json` is fully customizable
|
|
||||||
|
|
||||||
## 0.18.1
|
|
||||||
|
|
||||||
* `Common`:
|
|
||||||
* Add `MapDiff`
|
|
||||||
* `Coroutines`:
|
|
||||||
* Add `SmartMutex`
|
|
||||||
|
|
||||||
## 0.18.0
|
|
||||||
|
|
||||||
**ALL PREVIOUSLY DEPRECATED FUNCTIONALITY HAVE BEEN REMOVED**
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Android Fragments`: `1.5.6` -> `1.5.7`
|
|
||||||
* `Ktor`:
|
|
||||||
* `Server`:
|
|
||||||
* Now it is possible to take query parameters as list
|
|
||||||
* `Repos`:
|
|
||||||
* `Common`:
|
|
||||||
* New `WriteKeyValuesRepo.removeWithValue`
|
|
||||||
* `Cache`:
|
|
||||||
* Rename full caching factories functions to `fullyCached`
|
|
||||||
|
|
||||||
## 0.17.8
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Ktor`: `2.2.4` -> `2.3.0`
|
|
||||||
|
|
||||||
## 0.17.7
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Android CoreKtx`: `1.9.0` -> `1.10.0`
|
|
||||||
* `Startup`:
|
|
||||||
* Add support of `linuxX64` and `mingwX64` platforms
|
|
||||||
|
|
||||||
## 0.17.6
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Kotlin`: `1.8.10` -> `1.8.20`
|
|
||||||
* `KSLog`: `1.0.0` -> `1.1.1`
|
|
||||||
* `Compose`: `1.3.1` -> `1.4.0`
|
|
||||||
* `Koin`: `3.3.2` -> `3.4.0`
|
|
||||||
* `RecyclerView`: `1.2.1` -> `1.3.0`
|
|
||||||
* `Fragment`: `1.5.5` -> `1.5.6`
|
|
||||||
* Experimentally (`!!!`) add `linuxX64` and `mingwX64` targets
|
|
||||||
|
|
||||||
## 0.17.5
|
|
||||||
|
|
||||||
* `Common`:
|
|
||||||
* Conversations of number primitives with bounds care
|
|
||||||
* `Repos`:
|
|
||||||
* `Common`:
|
|
||||||
* By default, `getAll` for repos will take all the size of repo as page size
|
|
||||||
* New extension for all built-in repos `maxPagePagination`
|
|
||||||
* All the repos got `getAll` functions
|
|
||||||
|
|
||||||
## 0.17.4
|
|
||||||
|
|
||||||
* `Serialization`:
|
|
||||||
* `Mapper`:
|
|
||||||
* Module inited
|
|
||||||
* `Versions`:
|
|
||||||
* `Compose`: `1.3.1-rc02` -> `1.3.1`
|
|
||||||
|
|
||||||
## 0.17.3
|
|
||||||
|
|
||||||
* `Common`:
|
|
||||||
* Add `fixed` extensions for `Float` and `Double`
|
|
||||||
* New function `emptyDiff`
|
|
||||||
* Now you may pass custom `comparisonFun` to all `diff` functions
|
|
||||||
|
|
||||||
## 0.17.2
|
|
||||||
|
|
||||||
* `FSM`:
|
|
||||||
* `DefaultStatesManager.onUpdateContextsConflictResolver` and `DefaultStatesManager.onStartContextsConflictResolver` now return `false` by default
|
|
||||||
|
|
||||||
## 0.17.1
|
|
||||||
|
|
||||||
* **Hotfix** for absence of jvm dependencies in android modules
|
|
||||||
* `Versions`:
|
|
||||||
* `Ktor`: `2.2.3` -> `2.2.4`
|
|
||||||
|
|
||||||
## 0.17.0
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Kotlin`: `1.7.20` -> `1.8.10`
|
|
||||||
* `Serialization`: `1.4.1` -> `1.5.0`
|
|
||||||
* `KSLog`: `0.5.4` -> `1.0.0`
|
|
||||||
* `AppCompat`: `1.6.0` -> `1.6.1`
|
|
||||||
|
|
||||||
## 0.16.13
|
|
||||||
|
|
||||||
* `Repos`:
|
|
||||||
* `Generator`:
|
|
||||||
* Module has been created
|
|
||||||
|
|
||||||
## 0.16.12
|
|
||||||
|
|
||||||
* `Repos`:
|
|
||||||
* `Exposed`:
|
|
||||||
* `CommonExposedRepo.selectByIds` uses `foldRight` by default instead of raw foreach
|
|
||||||
* `Koin`:
|
|
||||||
* `Generator`:
|
|
||||||
* Module has been created
|
|
||||||
|
|
||||||
## 0.16.11
|
|
||||||
|
|
||||||
* `LanguageCodes`:
|
|
||||||
* In android and JVM targets now available `toJavaLocale` and from Java `Locale` conversations from/to
|
|
||||||
`IetfLanguageCode`
|
|
||||||
|
|
||||||
## 0.16.10
|
|
||||||
|
|
||||||
* `Repos`:
|
|
||||||
* `Cache`:
|
|
||||||
* New transformer type: `ReadCRUDFromKeyValueRepo`
|
|
||||||
* New transformer type: `ReadKeyValueFromCRUDRepo`
|
|
||||||
* `Pagination`:
|
|
||||||
* New `paginate` extensions with `reversed` support for `List`/`Set`
|
|
||||||
|
|
||||||
## 0.16.9
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Koin`: `3.2.2` -> `3.3.2`
|
|
||||||
* `AppCompat`: `1.5.1` -> `1.6.0`
|
|
||||||
* `Ktor`:
|
|
||||||
* `Client`
|
|
||||||
* `HttpResponse.bodyOrNull` now retrieve callback to check if body should be received or null
|
|
||||||
* New extension `HttpResponse.bodyOrNullOnNoContent`
|
|
||||||
|
|
||||||
## 0.16.8
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Ktor`: `2.2.2` -> `2.2.3`
|
|
||||||
* `Ktor`:
|
|
||||||
* `Client`
|
|
||||||
* Fixes in `HttpClient.uniUpload`
|
|
||||||
* `Server`
|
|
||||||
* Fixes in `PartData.FileItem.download`
|
|
||||||
* `Repos`:
|
|
||||||
* `Cache`:
|
|
||||||
* New type of caches: `FallbackCacheRepo`
|
|
||||||
* Fixes in `Write*` variants of cached repos
|
|
||||||
* New type `ActionWrapper`
|
|
||||||
* New `AutoRecache*` classes for all types of repos as `FallbackCacheRepo`s
|
|
||||||
* `Common`:
|
|
||||||
* New transformations for key-value and key-values vice-verse
|
|
||||||
* Fixes in `FileReadKeyValueRepo`
|
|
||||||
|
|
||||||
## 0.16.7
|
|
||||||
|
|
||||||
* `Common`:
|
|
||||||
* New extensions `ifTrue`/`ifFalse`/`alsoIfTrue`/`alsoIfFalse`/`letIfTrue`/`letIfFalse`
|
|
||||||
* `Diff` now is serializable
|
|
||||||
* Add `IndexedValue` serializer
|
|
||||||
* `repeatOnFailure` extending: now you may pass any lambda to check if continue to try/do something
|
|
||||||
* `Compose`:
|
|
||||||
* New extension `MutableState.asState`
|
|
||||||
* `Coroutines`:
|
|
||||||
* `Compose`:
|
|
||||||
* All the `Flow` conversations to compose `State`/`MutableState`/`SnapshotStateList`/`List` got several new
|
|
||||||
parameters
|
|
||||||
* `Flow.toMutableState` now is deprecated in favor to `asMutableComposeState`
|
|
||||||
* `Repos`:
|
|
||||||
* `Cache`:
|
|
||||||
* New type `FullCacheRepo`
|
|
||||||
* New type `CommonCacheRepo`
|
|
||||||
* `CacheRepo` got `invalidate` method. It will fully reload `FullCacheRepo` and just clear `CommonCacheRepo`
|
|
||||||
* New extensions `KVCache.actualizeAll`
|
|
||||||
|
|
||||||
## 0.16.6
|
|
||||||
|
|
||||||
* `Startup`:
|
|
||||||
* `Launcher`:
|
|
||||||
* Improvements in `StartLauncherPlugin#start` methods
|
|
||||||
* Add opportunity to pass second argument on `JVM` platform as log level
|
|
||||||
* `Repos`:
|
|
||||||
* `Ktor`:
|
|
||||||
* `Client`:
|
|
||||||
* All clients repos got opportunity to customize their flows
|
|
||||||
* `Exposed`:
|
|
||||||
* Extensions `eqOrIsNull` and `neqOrIsNotNull` for `Column`
|
|
||||||
|
|
||||||
## 0.16.5
|
|
||||||
|
|
||||||
* `Versions`:
|
|
||||||
* `Ktor`: `2.2.1` -> `2.2.2`
|
|
||||||
|
|
||||||
## 0.16.4
|
|
||||||
|
|
||||||
* `Coroutines`:
|
|
||||||
* Create `launchInCurrentThread`
|
|
||||||
|
|
||||||
## 0.16.3
|
## 0.16.3
|
||||||
|
|
||||||
|
* `Coroutines`:
|
||||||
|
* Create `launchInCurrentThread`
|
||||||
* `Startup`:
|
* `Startup`:
|
||||||
* `Launcher`:
|
* `Launcher`:
|
||||||
* All starting API have been moved into `StartLauncherPlugin` and do not require serialize/deserialize cycle for now
|
* All starting API have been moved into `StartLauncherPlugin` and do not require serialize/deserialize cycle for now
|
||||||
|
@@ -4,7 +4,7 @@ plugins {
|
|||||||
id "com.android.library"
|
id "com.android.library"
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$mppAndroidProject"
|
apply from: "$mppAndroidProjectPresetPath"
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
sourceSets {
|
sourceSets {
|
||||||
|
@@ -1 +0,0 @@
|
|||||||
<manifest/>
|
|
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"/>
|
@@ -4,7 +4,7 @@ plugins {
|
|||||||
id "com.android.library"
|
id "com.android.library"
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$mppAndroidProject"
|
apply from: "$mppAndroidProjectPresetPath"
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
sourceSets {
|
sourceSets {
|
||||||
|
@@ -1 +0,0 @@
|
|||||||
<manifest/>
|
|
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"/>
|
@@ -1,19 +0,0 @@
|
|||||||
plugins {
|
|
||||||
id "org.jetbrains.kotlin.multiplatform"
|
|
||||||
id "org.jetbrains.kotlin.plugin.serialization"
|
|
||||||
id "com.android.library"
|
|
||||||
alias(libs.plugins.jb.compose)
|
|
||||||
alias(libs.plugins.kt.jb.compose)
|
|
||||||
}
|
|
||||||
|
|
||||||
apply from: "$mppProjectWithSerializationAndCompose"
|
|
||||||
|
|
||||||
kotlin {
|
|
||||||
sourceSets {
|
|
||||||
androidMain {
|
|
||||||
dependencies {
|
|
||||||
api project(":micro_utils.android.smalltextfield")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1 +0,0 @@
|
|||||||
<manifest/>
|
|
@@ -1,27 +0,0 @@
|
|||||||
package dev.inmo.micro_utils.android.pickers
|
|
||||||
|
|
||||||
import androidx.compose.animation.core.*
|
|
||||||
|
|
||||||
internal suspend fun Animatable<Float, AnimationVector1D>.fling(
|
|
||||||
initialVelocity: Float,
|
|
||||||
animationSpec: DecayAnimationSpec<Float>,
|
|
||||||
adjustTarget: ((Float) -> Float)?,
|
|
||||||
block: (Animatable<Float, AnimationVector1D>.() -> Unit)? = null,
|
|
||||||
): AnimationResult<Float, AnimationVector1D> {
|
|
||||||
val targetValue = animationSpec.calculateTargetValue(value, initialVelocity)
|
|
||||||
val adjustedTarget = adjustTarget?.invoke(targetValue)
|
|
||||||
|
|
||||||
return if (adjustedTarget != null) {
|
|
||||||
animateTo(
|
|
||||||
targetValue = adjustedTarget,
|
|
||||||
initialVelocity = initialVelocity,
|
|
||||||
block = block
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
animateDecay(
|
|
||||||
initialVelocity = initialVelocity,
|
|
||||||
animationSpec = animationSpec,
|
|
||||||
block = block,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,219 +0,0 @@
|
|||||||
package dev.inmo.micro_utils.android.pickers
|
|
||||||
|
|
||||||
import androidx.compose.animation.core.Animatable
|
|
||||||
import androidx.compose.animation.core.exponentialDecay
|
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.gestures.*
|
|
||||||
import androidx.compose.foundation.layout.*
|
|
||||||
import androidx.compose.foundation.text.KeyboardActions
|
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.filled.KeyboardArrowDown
|
|
||||||
import androidx.compose.material.icons.filled.KeyboardArrowUp
|
|
||||||
import androidx.compose.material3.*
|
|
||||||
import androidx.compose.runtime.*
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.draw.alpha
|
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
|
||||||
import androidx.compose.ui.focus.focusRequester
|
|
||||||
import androidx.compose.ui.geometry.Offset
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.input.pointer.PointerInputScope
|
|
||||||
import androidx.compose.ui.input.pointer.pointerInput
|
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
|
||||||
import androidx.compose.ui.text.ExperimentalTextApi
|
|
||||||
import androidx.compose.ui.text.TextStyle
|
|
||||||
import androidx.compose.ui.text.input.KeyboardType
|
|
||||||
import androidx.compose.ui.unit.IntOffset
|
|
||||||
import androidx.compose.ui.unit.center
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import dev.inmo.micro_utils.android.smalltextfield.SmallTextField
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlin.math.abs
|
|
||||||
import kotlin.math.absoluteValue
|
|
||||||
import kotlin.math.roundToInt
|
|
||||||
|
|
||||||
private inline fun PointerInputScope.checkContains(offset: Offset): Boolean {
|
|
||||||
return ((size.center.x - offset.x).absoluteValue < size.width / 2) && ((size.center.y - offset.y).absoluteValue < size.height / 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// src: https://gist.github.com/vganin/a9a84653a9f48a2d669910fbd48e32d5
|
|
||||||
|
|
||||||
@OptIn(ExperimentalTextApi::class, ExperimentalComposeUiApi::class)
|
|
||||||
@Composable
|
|
||||||
fun NumberPicker(
|
|
||||||
number: Int,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
range: IntRange? = null,
|
|
||||||
textStyle: TextStyle = LocalTextStyle.current,
|
|
||||||
arrowsColor: Color = MaterialTheme.colorScheme.primary,
|
|
||||||
allowUseManualInput: Boolean = true,
|
|
||||||
onStateChanged: (Int) -> Unit = {},
|
|
||||||
) {
|
|
||||||
val coroutineScope = rememberCoroutineScope()
|
|
||||||
val numbersColumnHeight = 36.dp
|
|
||||||
val halvedNumbersColumnHeight = numbersColumnHeight / 2
|
|
||||||
val halvedNumbersColumnHeightPx = with(LocalDensity.current) { halvedNumbersColumnHeight.toPx() }
|
|
||||||
|
|
||||||
fun animatedStateValue(offset: Float): Int = number - (offset / halvedNumbersColumnHeightPx).toInt()
|
|
||||||
|
|
||||||
val animatedOffset = remember { Animatable(0f) }.apply {
|
|
||||||
if (range != null) {
|
|
||||||
val offsetRange = remember(number, range) {
|
|
||||||
val value = number
|
|
||||||
val first = -(range.last - value) * halvedNumbersColumnHeightPx
|
|
||||||
val last = -(range.first - value) * halvedNumbersColumnHeightPx
|
|
||||||
first..last
|
|
||||||
}
|
|
||||||
updateBounds(offsetRange.start, offsetRange.endInclusive)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val coercedAnimatedOffset = animatedOffset.value % halvedNumbersColumnHeightPx
|
|
||||||
val animatedStateValue = animatedStateValue(animatedOffset.value)
|
|
||||||
val disabledArrowsColor = arrowsColor.copy(alpha = 0f)
|
|
||||||
|
|
||||||
val inputFieldShown = if (allowUseManualInput) {
|
|
||||||
remember { mutableStateOf(false) }
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
|
|
||||||
Column(
|
|
||||||
modifier = modifier
|
|
||||||
.wrapContentSize()
|
|
||||||
.draggable(
|
|
||||||
orientation = Orientation.Vertical,
|
|
||||||
state = rememberDraggableState { deltaY ->
|
|
||||||
if (inputFieldShown ?.value != true) {
|
|
||||||
coroutineScope.launch {
|
|
||||||
animatedOffset.snapTo(animatedOffset.value + deltaY)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onDragStopped = { velocity ->
|
|
||||||
if (inputFieldShown ?.value != true) {
|
|
||||||
coroutineScope.launch {
|
|
||||||
val endValue = animatedOffset.fling(
|
|
||||||
initialVelocity = velocity,
|
|
||||||
animationSpec = exponentialDecay(frictionMultiplier = 20f),
|
|
||||||
adjustTarget = { target ->
|
|
||||||
val coercedTarget = target % halvedNumbersColumnHeightPx
|
|
||||||
val coercedAnchors =
|
|
||||||
listOf(-halvedNumbersColumnHeightPx, 0f, halvedNumbersColumnHeightPx)
|
|
||||||
val coercedPoint = coercedAnchors.minByOrNull { abs(it - coercedTarget) }!!
|
|
||||||
val base =
|
|
||||||
halvedNumbersColumnHeightPx * (target / halvedNumbersColumnHeightPx).toInt()
|
|
||||||
coercedPoint + base
|
|
||||||
}
|
|
||||||
).endState.value
|
|
||||||
|
|
||||||
onStateChanged(animatedStateValue(endValue))
|
|
||||||
animatedOffset.snapTo(0f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
),
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
|
||||||
) {
|
|
||||||
val spacing = 4.dp
|
|
||||||
|
|
||||||
val upEnabled = range == null || range.first < number
|
|
||||||
IconButton(
|
|
||||||
{
|
|
||||||
onStateChanged(number - 1)
|
|
||||||
inputFieldShown ?.value = false
|
|
||||||
},
|
|
||||||
enabled = upEnabled
|
|
||||||
) {
|
|
||||||
Icon(Icons.Default.KeyboardArrowUp, "", tint = if (upEnabled) arrowsColor else disabledArrowsColor)
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(spacing))
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.offset { IntOffset(x = 0, y = coercedAnimatedOffset.roundToInt()) },
|
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
|
||||||
val baseLabelModifier = Modifier.align(Alignment.Center)
|
|
||||||
ProvideTextStyle(textStyle) {
|
|
||||||
Text(
|
|
||||||
text = (animatedStateValue - 1).toString(),
|
|
||||||
modifier = baseLabelModifier
|
|
||||||
.offset(y = -halvedNumbersColumnHeight)
|
|
||||||
.alpha(coercedAnimatedOffset / halvedNumbersColumnHeightPx)
|
|
||||||
)
|
|
||||||
|
|
||||||
if (inputFieldShown ?.value == true) {
|
|
||||||
val currentValue = remember { mutableStateOf(number.toString()) }
|
|
||||||
|
|
||||||
val focusRequester = remember { FocusRequester() }
|
|
||||||
SmallTextField(
|
|
||||||
currentValue.value,
|
|
||||||
{
|
|
||||||
val asDigit = it.toIntOrNull()
|
|
||||||
when {
|
|
||||||
(asDigit == null && it.isEmpty()) -> currentValue.value = (range ?.first ?: 0).toString()
|
|
||||||
(asDigit != null && (range == null || asDigit in range)) -> currentValue.value = it
|
|
||||||
else -> { /* do nothing */ }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
baseLabelModifier.focusRequester(focusRequester).width(IntrinsicSize.Min).pointerInput(number) {
|
|
||||||
detectTapGestures {
|
|
||||||
if (!checkContains(it)) {
|
|
||||||
currentValue.value.toIntOrNull() ?.let(onStateChanged)
|
|
||||||
inputFieldShown.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
keyboardOptions = KeyboardOptions(
|
|
||||||
keyboardType = KeyboardType.Number
|
|
||||||
),
|
|
||||||
keyboardActions = KeyboardActions {
|
|
||||||
currentValue.value.toIntOrNull() ?.let(onStateChanged)
|
|
||||||
inputFieldShown.value = false
|
|
||||||
},
|
|
||||||
singleLine = true,
|
|
||||||
textStyle = textStyle
|
|
||||||
)
|
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
focusRequester.requestFocus()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Text(
|
|
||||||
text = animatedStateValue.toString(),
|
|
||||||
modifier = baseLabelModifier
|
|
||||||
.alpha(1 - abs(coercedAnimatedOffset) / halvedNumbersColumnHeightPx)
|
|
||||||
.clickable {
|
|
||||||
if (inputFieldShown ?.value == false) {
|
|
||||||
inputFieldShown.value = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Text(
|
|
||||||
text = (animatedStateValue + 1).toString(),
|
|
||||||
modifier = baseLabelModifier
|
|
||||||
.offset(y = halvedNumbersColumnHeight)
|
|
||||||
.alpha(-coercedAnimatedOffset / halvedNumbersColumnHeightPx)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(spacing))
|
|
||||||
|
|
||||||
val downEnabled = range == null || range.last > number
|
|
||||||
IconButton(
|
|
||||||
{
|
|
||||||
onStateChanged(number + 1)
|
|
||||||
inputFieldShown ?.value = false
|
|
||||||
},
|
|
||||||
enabled = downEnabled
|
|
||||||
) {
|
|
||||||
Icon(Icons.Default.KeyboardArrowDown, "", tint = if (downEnabled) arrowsColor else disabledArrowsColor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@@ -1,153 +0,0 @@
|
|||||||
package dev.inmo.micro_utils.android.pickers
|
|
||||||
|
|
||||||
import androidx.compose.animation.core.Animatable
|
|
||||||
import androidx.compose.animation.core.exponentialDecay
|
|
||||||
import androidx.compose.foundation.gestures.*
|
|
||||||
import androidx.compose.foundation.layout.*
|
|
||||||
import androidx.compose.foundation.rememberScrollState
|
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.filled.KeyboardArrowDown
|
|
||||||
import androidx.compose.material.icons.filled.KeyboardArrowUp
|
|
||||||
import androidx.compose.material3.*
|
|
||||||
import androidx.compose.runtime.*
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.draw.alpha
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
|
||||||
import androidx.compose.ui.text.ExperimentalTextApi
|
|
||||||
import androidx.compose.ui.text.TextStyle
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlin.math.*
|
|
||||||
|
|
||||||
@OptIn(ExperimentalTextApi::class, ExperimentalComposeUiApi::class)
|
|
||||||
@Composable
|
|
||||||
fun <T> SetPicker(
|
|
||||||
current: T,
|
|
||||||
dataList: List<T>,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
textStyle: TextStyle = LocalTextStyle.current,
|
|
||||||
arrowsColor: Color = MaterialTheme.colorScheme.primary,
|
|
||||||
dataToString: @Composable (T) -> String = { it.toString() },
|
|
||||||
onStateChanged: (T) -> Unit = {},
|
|
||||||
) {
|
|
||||||
val coroutineScope = rememberCoroutineScope()
|
|
||||||
val numbersColumnHeight = 8.dp + with(LocalDensity.current) {
|
|
||||||
textStyle.lineHeight.toDp()
|
|
||||||
}
|
|
||||||
val numbersColumnHeightPx = with(LocalDensity.current) { numbersColumnHeight.toPx() }
|
|
||||||
val halvedNumbersColumnHeight = numbersColumnHeight / 2
|
|
||||||
val halvedNumbersColumnHeightPx = with(LocalDensity.current) { halvedNumbersColumnHeight.toPx() }
|
|
||||||
|
|
||||||
val index = dataList.indexOfFirst { it === current }.takeIf { it > -1 } ?: dataList.indexOf(current)
|
|
||||||
val lastIndex = dataList.size - 1
|
|
||||||
|
|
||||||
fun animatedStateValue(offset: Float): Int = index - (offset / halvedNumbersColumnHeightPx).toInt()
|
|
||||||
|
|
||||||
val animatedOffset = remember { Animatable(0f) }.apply {
|
|
||||||
val offsetRange = remember(index, lastIndex) {
|
|
||||||
val value = index
|
|
||||||
val first = -(lastIndex - value) * halvedNumbersColumnHeightPx
|
|
||||||
val last = value * halvedNumbersColumnHeightPx
|
|
||||||
first..last
|
|
||||||
}
|
|
||||||
updateBounds(offsetRange.start, offsetRange.endInclusive)
|
|
||||||
}
|
|
||||||
val indexAnimatedOffset = if (animatedOffset.value > 0) {
|
|
||||||
(index - floor(animatedOffset.value / halvedNumbersColumnHeightPx).toInt())
|
|
||||||
} else {
|
|
||||||
(index - ceil(animatedOffset.value / halvedNumbersColumnHeightPx).toInt())
|
|
||||||
}
|
|
||||||
val coercedAnimatedOffset = animatedOffset.value % halvedNumbersColumnHeightPx
|
|
||||||
val disabledArrowsColor = arrowsColor.copy(alpha = 0f)
|
|
||||||
|
|
||||||
Column(
|
|
||||||
modifier = modifier
|
|
||||||
.wrapContentSize()
|
|
||||||
.draggable(
|
|
||||||
orientation = Orientation.Vertical,
|
|
||||||
state = rememberDraggableState { deltaY ->
|
|
||||||
coroutineScope.launch {
|
|
||||||
animatedOffset.snapTo(animatedOffset.value + deltaY)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onDragStopped = { velocity ->
|
|
||||||
coroutineScope.launch {
|
|
||||||
val endValue = animatedOffset.fling(
|
|
||||||
initialVelocity = velocity,
|
|
||||||
animationSpec = exponentialDecay(frictionMultiplier = 20f),
|
|
||||||
adjustTarget = { target ->
|
|
||||||
val coercedTarget = target % halvedNumbersColumnHeightPx
|
|
||||||
val coercedAnchors =
|
|
||||||
listOf(-halvedNumbersColumnHeightPx, 0f, halvedNumbersColumnHeightPx)
|
|
||||||
val coercedPoint = coercedAnchors.minByOrNull { abs(it - coercedTarget) }!!
|
|
||||||
val base =
|
|
||||||
halvedNumbersColumnHeightPx * (target / halvedNumbersColumnHeightPx).toInt()
|
|
||||||
coercedPoint + base
|
|
||||||
}
|
|
||||||
).endState.value
|
|
||||||
|
|
||||||
onStateChanged(dataList.elementAt(animatedStateValue(endValue)))
|
|
||||||
animatedOffset.snapTo(0f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
),
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
|
||||||
) {
|
|
||||||
val spacing = 4.dp
|
|
||||||
|
|
||||||
val upEnabled = index > 0
|
|
||||||
IconButton(
|
|
||||||
{
|
|
||||||
onStateChanged(dataList.elementAt(index - 1))
|
|
||||||
},
|
|
||||||
enabled = upEnabled
|
|
||||||
) {
|
|
||||||
Icon(Icons.Default.KeyboardArrowUp, "", tint = if (upEnabled) arrowsColor else disabledArrowsColor)
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(spacing))
|
|
||||||
Box(
|
|
||||||
modifier = Modifier,
|
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
|
||||||
ProvideTextStyle(textStyle) {
|
|
||||||
dataList.forEachIndexed { i, t ->
|
|
||||||
val alpha = when {
|
|
||||||
i == indexAnimatedOffset - 1 -> coercedAnimatedOffset / halvedNumbersColumnHeightPx
|
|
||||||
i == indexAnimatedOffset -> 1 - (abs(coercedAnimatedOffset) / halvedNumbersColumnHeightPx)
|
|
||||||
i == indexAnimatedOffset + 1 -> -coercedAnimatedOffset / halvedNumbersColumnHeightPx
|
|
||||||
else -> return@forEachIndexed
|
|
||||||
}
|
|
||||||
val offset = when {
|
|
||||||
i == indexAnimatedOffset - 1 && coercedAnimatedOffset > 0 -> coercedAnimatedOffset - halvedNumbersColumnHeightPx
|
|
||||||
i == indexAnimatedOffset -> coercedAnimatedOffset
|
|
||||||
i == indexAnimatedOffset + 1 && coercedAnimatedOffset < 0 -> coercedAnimatedOffset + halvedNumbersColumnHeightPx
|
|
||||||
else -> return@forEachIndexed
|
|
||||||
}
|
|
||||||
Text(
|
|
||||||
text = dataToString(t),
|
|
||||||
modifier = Modifier
|
|
||||||
.alpha(alpha)
|
|
||||||
.offset(y = with(LocalDensity.current) { offset.toDp() })
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(spacing))
|
|
||||||
|
|
||||||
val downEnabled = index < lastIndex
|
|
||||||
IconButton(
|
|
||||||
{
|
|
||||||
onStateChanged(dataList.elementAt(index + 1))
|
|
||||||
},
|
|
||||||
enabled = downEnabled
|
|
||||||
) {
|
|
||||||
Icon(Icons.Default.KeyboardArrowDown, "", tint = if (downEnabled) arrowsColor else disabledArrowsColor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -4,7 +4,7 @@ plugins {
|
|||||||
id "com.android.library"
|
id "com.android.library"
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$mppAndroidProject"
|
apply from: "$mppAndroidProjectPresetPath"
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
sourceSets {
|
sourceSets {
|
||||||
|
@@ -1 +0,0 @@
|
|||||||
<manifest/>
|
|
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"/>
|
@@ -1,19 +0,0 @@
|
|||||||
plugins {
|
|
||||||
id "org.jetbrains.kotlin.multiplatform"
|
|
||||||
id "org.jetbrains.kotlin.plugin.serialization"
|
|
||||||
id "com.android.library"
|
|
||||||
alias(libs.plugins.jb.compose)
|
|
||||||
alias(libs.plugins.kt.jb.compose)
|
|
||||||
}
|
|
||||||
|
|
||||||
apply from: "$mppProjectWithSerializationAndCompose"
|
|
||||||
|
|
||||||
kotlin {
|
|
||||||
sourceSets {
|
|
||||||
androidMain {
|
|
||||||
dependencies {
|
|
||||||
api libs.jb.compose.material3
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1 +0,0 @@
|
|||||||
<manifest/>
|
|
@@ -1,66 +0,0 @@
|
|||||||
package dev.inmo.micro_utils.android.smalltextfield
|
|
||||||
|
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.defaultMinSize
|
|
||||||
import androidx.compose.foundation.text.BasicTextField
|
|
||||||
import androidx.compose.foundation.text.KeyboardActions
|
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
|
||||||
import androidx.compose.foundation.text.selection.LocalTextSelectionColors
|
|
||||||
import androidx.compose.material3.*
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.graphics.Shape
|
|
||||||
import androidx.compose.ui.graphics.SolidColor
|
|
||||||
import androidx.compose.ui.graphics.takeOrElse
|
|
||||||
import androidx.compose.ui.text.TextStyle
|
|
||||||
import androidx.compose.ui.text.input.VisualTransformation
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
|
||||||
fun SmallTextField(
|
|
||||||
value: String,
|
|
||||||
onValueChange: (String) -> Unit,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
enabled: Boolean = true,
|
|
||||||
readOnly: Boolean = false,
|
|
||||||
textStyle: TextStyle = LocalTextStyle.current,
|
|
||||||
textColor: Color = textStyle.color.takeOrElse {
|
|
||||||
LocalContentColor.current
|
|
||||||
},
|
|
||||||
visualTransformation: VisualTransformation = VisualTransformation.None,
|
|
||||||
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
|
|
||||||
keyboardActions: KeyboardActions = KeyboardActions.Default,
|
|
||||||
singleLine: Boolean = false,
|
|
||||||
maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE,
|
|
||||||
minLines: Int = 1,
|
|
||||||
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
|
||||||
) {
|
|
||||||
BasicTextField(
|
|
||||||
value = value,
|
|
||||||
modifier = modifier,
|
|
||||||
onValueChange = onValueChange,
|
|
||||||
enabled = enabled,
|
|
||||||
readOnly = readOnly,
|
|
||||||
textStyle = textStyle.copy(
|
|
||||||
color = textColor
|
|
||||||
),
|
|
||||||
visualTransformation = visualTransformation,
|
|
||||||
keyboardOptions = keyboardOptions,
|
|
||||||
keyboardActions = keyboardActions,
|
|
||||||
interactionSource = interactionSource,
|
|
||||||
singleLine = singleLine,
|
|
||||||
maxLines = maxLines,
|
|
||||||
minLines = minLines,
|
|
||||||
cursorBrush = SolidColor(
|
|
||||||
textStyle.color.takeOrElse {
|
|
||||||
LocalContentColor.current
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
16
build.gradle
16
build.gradle
@@ -9,7 +9,6 @@ buildscript {
|
|||||||
dependencies {
|
dependencies {
|
||||||
classpath libs.buildscript.kt.gradle
|
classpath libs.buildscript.kt.gradle
|
||||||
classpath libs.buildscript.kt.serialization
|
classpath libs.buildscript.kt.serialization
|
||||||
classpath libs.buildscript.kt.ksp
|
|
||||||
classpath libs.buildscript.jb.dokka
|
classpath libs.buildscript.jb.dokka
|
||||||
classpath libs.buildscript.gh.release
|
classpath libs.buildscript.gh.release
|
||||||
classpath libs.buildscript.android.gradle
|
classpath libs.buildscript.android.gradle
|
||||||
@@ -17,20 +16,23 @@ buildscript {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
plugins {
|
|
||||||
alias(libs.plugins.versions)
|
|
||||||
}
|
|
||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
repositories {
|
repositories {
|
||||||
mavenLocal()
|
mavenLocal()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
google()
|
google()
|
||||||
maven { url "https://maven.pkg.jetbrains.space/public/p/compose/dev" }
|
maven { url "https://maven.pkg.jetbrains.space/public/p/compose/dev" }
|
||||||
maven { url "https://nexus.inmo.dev/repository/maven-releases/" }
|
}
|
||||||
|
|
||||||
|
// temporal crutch until legacy tests will be stabled or legacy target will be removed
|
||||||
|
if (it != rootProject.findProject("docs")) {
|
||||||
|
tasks.whenTaskAdded { task ->
|
||||||
|
if(task.name == "jsLegacyBrowserTest" || task.name == "jsLegacyNodeTest") {
|
||||||
|
task.enabled = false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "./extensions.gradle"
|
apply from: "./extensions.gradle"
|
||||||
apply from: "./github_release.gradle"
|
apply from: "./github_release.gradle"
|
||||||
apply from: "./versions_plugin_setup.gradle"
|
|
||||||
|
@@ -1,17 +0,0 @@
|
|||||||
plugins {
|
|
||||||
id "org.jetbrains.kotlin.multiplatform"
|
|
||||||
id "org.jetbrains.kotlin.plugin.serialization"
|
|
||||||
id "com.android.library"
|
|
||||||
}
|
|
||||||
|
|
||||||
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64Project"
|
|
||||||
|
|
||||||
kotlin {
|
|
||||||
sourceSets {
|
|
||||||
commonMain {
|
|
||||||
dependencies {
|
|
||||||
api project(":micro_utils.colors.common")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,7 +0,0 @@
|
|||||||
plugins {
|
|
||||||
id "org.jetbrains.kotlin.multiplatform"
|
|
||||||
id "org.jetbrains.kotlin.plugin.serialization"
|
|
||||||
id "com.android.library"
|
|
||||||
}
|
|
||||||
|
|
||||||
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64Project"
|
|
@@ -1,174 +0,0 @@
|
|||||||
package dev.inmo.micro_utils.colors.common
|
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
import kotlin.jvm.JvmInline
|
|
||||||
import kotlin.math.floor
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wrapper for RGBA colors. Receiving [UInt] in main constructor. Each part in main constructor
|
|
||||||
* configured with `0x00 - 0xff` range. Examples:
|
|
||||||
*
|
|
||||||
* * Red: `0xff0000ffu`
|
|
||||||
* * Red (0.5 capacity): `0xff000088u`
|
|
||||||
*
|
|
||||||
* Anyway it is recommended to use
|
|
||||||
*
|
|
||||||
* @param hexaUInt rgba [UInt] in format `0xFFEEBBAA` where FF - red, EE - green, BB - blue` and AA - alpha
|
|
||||||
*/
|
|
||||||
@Serializable
|
|
||||||
@JvmInline
|
|
||||||
value class HEXAColor (
|
|
||||||
val hexaUInt: UInt
|
|
||||||
) : Comparable<HEXAColor> {
|
|
||||||
/**
|
|
||||||
* @returns [hexaUInt] as a string with format `#FFEEBBAA` where FF - red, EE - green, BB - blue and AA - alpha
|
|
||||||
*/
|
|
||||||
val hexa: String
|
|
||||||
get() = "#${hexaUInt.toString(16).padStart(8, '0')}"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns [hexaUInt] as a string with format `#FFEEBB` where FF - red, EE - green and BB - blue
|
|
||||||
*/
|
|
||||||
val hex: String
|
|
||||||
get() = hexa.take(7)
|
|
||||||
/**
|
|
||||||
* @returns [hexaUInt] as a string with format `#AAFFEEBB` where AA - alpha, FF - red, EE - green and BB - blue
|
|
||||||
*/
|
|
||||||
val ahex: String
|
|
||||||
get() = "#${a.toString(16).padStart(2, '2')}${hex.drop(1)}"
|
|
||||||
val rgba: String
|
|
||||||
get() = "rgba($r,$g,$b,${aOfOne.toString().take(5)})"
|
|
||||||
val rgb: String
|
|
||||||
get() = "rgb($r,$g,$b)"
|
|
||||||
val shortHex: String
|
|
||||||
get() = "#${r.shortPart()}${g.shortPart()}${b.shortPart()}"
|
|
||||||
val shortHexa: String
|
|
||||||
get() = "$shortHex${a.shortPart()}"
|
|
||||||
val rgbUInt: UInt
|
|
||||||
get() = (hexaUInt / 256u)
|
|
||||||
val rgbInt: Int
|
|
||||||
get() = rgbUInt.toInt()
|
|
||||||
val ahexUInt
|
|
||||||
get() = (a * 0x1000000).toUInt() + rgbUInt
|
|
||||||
|
|
||||||
val r: Int
|
|
||||||
get() = ((hexaUInt and 0xff000000u) / 0x1000000u).toInt()
|
|
||||||
val g: Int
|
|
||||||
get() = ((hexaUInt and 0x00ff0000u) / 0x10000u).toInt()
|
|
||||||
val b: Int
|
|
||||||
get() = ((hexaUInt and 0x0000ff00u) / 0x100u).toInt()
|
|
||||||
val a: Int
|
|
||||||
get() = ((hexaUInt and 0x000000ffu)).toInt()
|
|
||||||
val aOfOne: Float
|
|
||||||
get() = a.toFloat() / (0xff)
|
|
||||||
init {
|
|
||||||
require(hexaUInt in 0u ..0xffffffffu)
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(r: Int, g: Int, b: Int, a: Int) : this(
|
|
||||||
((r * 0x1000000).toLong() + g * 0x10000 + b * 0x100 + a).toUInt()
|
|
||||||
) {
|
|
||||||
require(r in 0 ..0xff)
|
|
||||||
require(g in 0 ..0xff)
|
|
||||||
require(b in 0 ..0xff)
|
|
||||||
require(a in 0 ..0xff)
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(r: Int, g: Int, b: Int, aOfOne: Float = 1f) : this(
|
|
||||||
r = r, g = g, b = b, a = (aOfOne * 0xff).toInt()
|
|
||||||
)
|
|
||||||
|
|
||||||
override fun toString(): String {
|
|
||||||
return hexa
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun compareTo(other: HEXAColor): Int = (hexaUInt - other.hexaUInt).coerceIn(Int.MIN_VALUE.toUInt(), Int.MAX_VALUE.toLong().toUInt()).toInt()
|
|
||||||
|
|
||||||
fun copy(
|
|
||||||
r: Int = this.r,
|
|
||||||
g: Int = this.g,
|
|
||||||
b: Int = this.b,
|
|
||||||
aOfOne: Float = this.aOfOne
|
|
||||||
) = HEXAColor(r = r, g = g, b = b, aOfOne = aOfOne)
|
|
||||||
fun copy(
|
|
||||||
r: Int = this.r,
|
|
||||||
g: Int = this.g,
|
|
||||||
b: Int = this.b,
|
|
||||||
a: Int
|
|
||||||
) = HEXAColor(r = r, g = g, b = b, a = a)
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
/**
|
|
||||||
* Parsing color from [color]
|
|
||||||
*
|
|
||||||
* Supported formats samples (on Red color based):
|
|
||||||
*
|
|
||||||
* * `#f00`
|
|
||||||
* * `#f00f`
|
|
||||||
* * `#ff0000`
|
|
||||||
* * `#ff0000ff`
|
|
||||||
* * `rgb(255, 0, 0)`
|
|
||||||
* * `rgba(255, 0, 0, 1)`
|
|
||||||
*/
|
|
||||||
fun parseStringColor(color: String): HEXAColor = when {
|
|
||||||
color.startsWith("#") -> color.removePrefix("#").let { color ->
|
|
||||||
when (color.length) {
|
|
||||||
3 -> color.map { "$it$it" }.joinToString(separator = "", postfix = "ff")
|
|
||||||
4 -> color.take(3).map { "$it$it" }.joinToString(separator = "", postfix = color.takeLast(1).let { "${it}0" })
|
|
||||||
6 -> "${color}ff"
|
|
||||||
8 -> color
|
|
||||||
else -> error("Malfurmed color string: $color. It is expected that color started with # will contains 3, 6 or 8 valuable parts")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
color.startsWith("rgb(") -> color
|
|
||||||
.removePrefix("rgb(")
|
|
||||||
.removeSuffix(")")
|
|
||||||
.replace(Regex("\\s"), "")
|
|
||||||
.split(",")
|
|
||||||
.joinToString("", postfix = "ff") {
|
|
||||||
it.toInt().toString(16).padStart(2, '0')
|
|
||||||
}
|
|
||||||
color.startsWith("rgba(") -> color
|
|
||||||
.removePrefix("rgba(")
|
|
||||||
.removeSuffix(")")
|
|
||||||
.replace(Regex("\\s"), "")
|
|
||||||
.split(",").let {
|
|
||||||
it.take(3).map { it.toInt().toString(16).padStart(2, '0') } + (it.last().toFloat() * 0xff).toInt().toString(16).padStart(2, '0')
|
|
||||||
}
|
|
||||||
.joinToString("")
|
|
||||||
else -> color
|
|
||||||
}.lowercase().toUInt(16).let(::HEXAColor)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates [HEXAColor] from [uint] presume it is in format `0xFFEEBBAA` where FF - red, EE - green, BB - blue` and AA - alpha
|
|
||||||
*/
|
|
||||||
fun fromHexa(uint: UInt) = HEXAColor(uint)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates [HEXAColor] from [uint] presume it is in format `0xAAFFEEBB` where AA - alpha, FF - red, EE - green and BB - blue`
|
|
||||||
*/
|
|
||||||
fun fromAhex(uint: UInt) = HEXAColor(
|
|
||||||
a = ((uint and 0xff000000u) / 0x1000000u).toInt(),
|
|
||||||
r = ((uint and 0x00ff0000u) / 0x10000u).toInt(),
|
|
||||||
g = ((uint and 0x0000ff00u) / 0x100u).toInt(),
|
|
||||||
b = ((uint and 0x000000ffu)).toInt()
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parsing color from [color]
|
|
||||||
*
|
|
||||||
* Supported formats samples (on Red color based):
|
|
||||||
*
|
|
||||||
* * `#f00`
|
|
||||||
* * `#ff0000`
|
|
||||||
* * `#ff0000ff`
|
|
||||||
* * `rgb(255, 0, 0)`
|
|
||||||
* * `rgba(255, 0, 0, 1)`
|
|
||||||
*/
|
|
||||||
operator fun invoke(color: String) = parseStringColor(color)
|
|
||||||
|
|
||||||
private fun Int.shortPart(): String {
|
|
||||||
return (floor(toFloat() / 16)).toInt().toString(16)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,209 +0,0 @@
|
|||||||
package dev.inmo.micro_utils.colors.common
|
|
||||||
|
|
||||||
import kotlin.math.floor
|
|
||||||
import kotlin.test.Test
|
|
||||||
import kotlin.test.assertEquals
|
|
||||||
import kotlin.test.assertTrue
|
|
||||||
|
|
||||||
class HexColorTests {
|
|
||||||
val alphaRgbaPrecision = 5
|
|
||||||
class TestColor(
|
|
||||||
val color: HEXAColor,
|
|
||||||
val shortHex: String,
|
|
||||||
val shortHexa: String,
|
|
||||||
val hex: String,
|
|
||||||
val hexa: String,
|
|
||||||
val ahex: String,
|
|
||||||
val ahexUInt: UInt,
|
|
||||||
val rgbUInt: UInt,
|
|
||||||
val rgb: String,
|
|
||||||
val rgba: String,
|
|
||||||
val r: Int,
|
|
||||||
val g: Int,
|
|
||||||
val b: Int,
|
|
||||||
val a: Int,
|
|
||||||
vararg val additionalRGBAVariants: String
|
|
||||||
)
|
|
||||||
val testColors: List<TestColor>
|
|
||||||
get() = listOf(
|
|
||||||
TestColor(
|
|
||||||
color = HEXAColor(hexaUInt = 0xff0000ffu),
|
|
||||||
shortHex = "#f00",
|
|
||||||
shortHexa = "#f00f",
|
|
||||||
hex = "#ff0000",
|
|
||||||
hexa = "#ff0000ff",
|
|
||||||
ahex = "#ffff0000",
|
|
||||||
ahexUInt = 0xffff0000u,
|
|
||||||
rgbUInt = 0xff0000u,
|
|
||||||
rgb = "rgb(255,0,0)",
|
|
||||||
rgba = "rgba(255,0,0,1.0)",
|
|
||||||
r = 0xff,
|
|
||||||
g = 0x00,
|
|
||||||
b = 0x00,
|
|
||||||
a = 0xff,
|
|
||||||
"rgba(255,0,0,1)",
|
|
||||||
),
|
|
||||||
TestColor(
|
|
||||||
color = HEXAColor(hexaUInt = 0x00ff00ffu),
|
|
||||||
shortHex = "#0f0",
|
|
||||||
shortHexa = "#0f0f",
|
|
||||||
hex = "#00ff00",
|
|
||||||
hexa = "#00ff00ff",
|
|
||||||
ahex = "#ff00ff00",
|
|
||||||
ahexUInt = 0xff00ff00u,
|
|
||||||
rgbUInt = 0x00ff00u,
|
|
||||||
rgb = "rgb(0,255,0)",
|
|
||||||
rgba = "rgba(0,255,0,1.0)",
|
|
||||||
r = 0x00,
|
|
||||||
g = 0xff,
|
|
||||||
b = 0x00,
|
|
||||||
a = 0xff,
|
|
||||||
"rgba(0,255,0,1)"
|
|
||||||
),
|
|
||||||
TestColor(
|
|
||||||
color = HEXAColor(0x0000ffffu),
|
|
||||||
shortHex = "#00f",
|
|
||||||
shortHexa = "#00ff",
|
|
||||||
hex = "#0000ff",
|
|
||||||
hexa = "#0000ffff",
|
|
||||||
ahex = "#ff0000ff",
|
|
||||||
ahexUInt = 0xff0000ffu,
|
|
||||||
rgbUInt = 0x0000ffu,
|
|
||||||
rgb = "rgb(0,0,255)",
|
|
||||||
rgba = "rgba(0,0,255,1.0)",
|
|
||||||
r = 0x00,
|
|
||||||
g = 0x00,
|
|
||||||
b = 0xff,
|
|
||||||
a = 0xff,
|
|
||||||
"rgba(0,0,255,1)"
|
|
||||||
),
|
|
||||||
TestColor(
|
|
||||||
color = HEXAColor(0xff000088u),
|
|
||||||
shortHex = "#f00",
|
|
||||||
shortHexa = "#f008",
|
|
||||||
hex = "#ff0000",
|
|
||||||
hexa = "#ff000088",
|
|
||||||
ahex = "#88ff0000",
|
|
||||||
ahexUInt = 0x88ff0000u,
|
|
||||||
rgbUInt = 0xff0000u,
|
|
||||||
rgb = "rgb(255,0,0)",
|
|
||||||
rgba = "rgba(255,0,0,0.533)",
|
|
||||||
r = 0xff,
|
|
||||||
g = 0x00,
|
|
||||||
b = 0x00,
|
|
||||||
a = 0x88,
|
|
||||||
),
|
|
||||||
TestColor(
|
|
||||||
color = HEXAColor(0x00ff0088u),
|
|
||||||
shortHex = "#0f0",
|
|
||||||
shortHexa = "#0f08",
|
|
||||||
hex = "#00ff00",
|
|
||||||
hexa = "#00ff0088",
|
|
||||||
ahex = "#8800ff00",
|
|
||||||
ahexUInt = 0x8800ff00u,
|
|
||||||
rgbUInt = 0x00ff00u,
|
|
||||||
rgb = "rgb(0,255,0)",
|
|
||||||
rgba = "rgba(0,255,0,0.533)",
|
|
||||||
r = 0x00,
|
|
||||||
g = 0xff,
|
|
||||||
b = 0x00,
|
|
||||||
a = 0x88,
|
|
||||||
),
|
|
||||||
TestColor(
|
|
||||||
color = HEXAColor(0x0000ff88u),
|
|
||||||
shortHex = "#00f",
|
|
||||||
shortHexa = "#00f8",
|
|
||||||
hex = "#0000ff",
|
|
||||||
hexa = "#0000ff88",
|
|
||||||
ahex = "#880000ff",
|
|
||||||
ahexUInt = 0x880000ffu,
|
|
||||||
rgbUInt = 0x0000ffu,
|
|
||||||
rgb = "rgb(0,0,255)",
|
|
||||||
rgba = "rgba(0,0,255,0.533)",
|
|
||||||
r = 0x00,
|
|
||||||
g = 0x00,
|
|
||||||
b = 0xff,
|
|
||||||
a = 0x88,
|
|
||||||
),
|
|
||||||
TestColor(
|
|
||||||
color = HEXAColor(0xff000022u),
|
|
||||||
shortHex = "#f00",
|
|
||||||
shortHexa = "#f002",
|
|
||||||
hex = "#ff0000",
|
|
||||||
hexa = "#ff000022",
|
|
||||||
ahex = "#22ff0000",
|
|
||||||
ahexUInt = 0x22ff0000u,
|
|
||||||
rgbUInt = 0xff0000u,
|
|
||||||
rgb = "rgb(255,0,0)",
|
|
||||||
rgba = "rgba(255,0,0,0.133)",
|
|
||||||
r = 0xff,
|
|
||||||
g = 0x00,
|
|
||||||
b = 0x00,
|
|
||||||
a = 0x22,
|
|
||||||
),
|
|
||||||
TestColor(
|
|
||||||
color = HEXAColor(0x00ff0022u),
|
|
||||||
shortHex = "#0f0",
|
|
||||||
shortHexa = "#0f02",
|
|
||||||
hex = "#00ff00",
|
|
||||||
hexa = "#00ff0022",
|
|
||||||
ahex = "#2200ff00",
|
|
||||||
ahexUInt = 0x2200ff00u,
|
|
||||||
rgbUInt = 0x00ff00u,
|
|
||||||
rgb = "rgb(0,255,0)",
|
|
||||||
rgba = "rgba(0,255,0,0.133)",
|
|
||||||
r = 0x00,
|
|
||||||
g = 0xff,
|
|
||||||
b = 0x00,
|
|
||||||
a = 0x22,
|
|
||||||
),
|
|
||||||
TestColor(
|
|
||||||
color = HEXAColor(0x0000ff22u),
|
|
||||||
shortHex = "#00f",
|
|
||||||
shortHexa = "#00f2",
|
|
||||||
hex = "#0000ff",
|
|
||||||
hexa = "#0000ff22",
|
|
||||||
ahex = "#220000ff",
|
|
||||||
ahexUInt = 0x220000ffu,
|
|
||||||
rgbUInt = 0x0000ffu,
|
|
||||||
rgb = "rgb(0,0,255)",
|
|
||||||
rgba = "rgba(0,0,255,0.133)",
|
|
||||||
r = 0x00,
|
|
||||||
g = 0x00,
|
|
||||||
b = 0xff,
|
|
||||||
a = 0x22,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun baseTest() {
|
|
||||||
testColors.forEach {
|
|
||||||
assertEquals(it.hex, it.color.hex)
|
|
||||||
assertEquals(it.hexa, it.color.hexa)
|
|
||||||
assertEquals(it.ahex, it.color.ahex)
|
|
||||||
assertEquals(it.rgbUInt, it.color.rgbUInt)
|
|
||||||
assertEquals(it.ahexUInt, it.color.ahexUInt)
|
|
||||||
assertEquals(it.shortHex, it.color.shortHex)
|
|
||||||
assertEquals(it.shortHexa, it.color.shortHexa)
|
|
||||||
assertEquals(it.rgb, it.color.rgb)
|
|
||||||
assertTrue(it.rgba == it.color.rgba || it.color.rgba in it.additionalRGBAVariants)
|
|
||||||
assertEquals(it.r, it.color.r)
|
|
||||||
assertEquals(it.g, it.color.g)
|
|
||||||
assertEquals(it.b, it.color.b)
|
|
||||||
assertEquals(it.a, it.color.a)
|
|
||||||
assertEquals(it.color, HEXAColor.fromAhex(it.ahexUInt))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testHexParseColor() {
|
|
||||||
testColors.forEach {
|
|
||||||
assertEquals(it.color.copy(aOfOne = 1f), HEXAColor.parseStringColor(it.hex))
|
|
||||||
assertEquals(it.color, HEXAColor.parseStringColor(it.hexa))
|
|
||||||
assertEquals(it.color.copy(aOfOne = 1f), HEXAColor.parseStringColor(it.rgb))
|
|
||||||
assertTrue(it.color.hexaUInt.toInt() - HEXAColor.parseStringColor(it.rgba).hexaUInt.toInt() in -0x1 .. 0x1, )
|
|
||||||
assertEquals(it.color.copy(aOfOne = 1f), HEXAColor.parseStringColor(it.shortHex))
|
|
||||||
assertEquals(it.color.copy(a = floor(it.color.a.toFloat() / 16).toInt() * 0x10), HEXAColor.parseStringColor(it.shortHexa))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,298 +0,0 @@
|
|||||||
package dev.inmo.micro_utils.colors
|
|
||||||
|
|
||||||
import dev.inmo.micro_utils.colors.common.HEXAColor
|
|
||||||
|
|
||||||
val HEXAColor.Companion.aliceblue
|
|
||||||
get() = HEXAColor(0xF0F8FFFFu)
|
|
||||||
val HEXAColor.Companion.antiquewhite
|
|
||||||
get() = HEXAColor(0xFAEBD7FFu)
|
|
||||||
val HEXAColor.Companion.aqua
|
|
||||||
get() = HEXAColor(0x00FFFFFFu)
|
|
||||||
val HEXAColor.Companion.aquamarine
|
|
||||||
get() = HEXAColor(0x7FFFD4FFu)
|
|
||||||
val HEXAColor.Companion.azure
|
|
||||||
get() = HEXAColor(0xF0FFFFFFu)
|
|
||||||
val HEXAColor.Companion.beige
|
|
||||||
get() = HEXAColor(0xF5F5DCFFu)
|
|
||||||
val HEXAColor.Companion.bisque
|
|
||||||
get() = HEXAColor(0xFFE4C4FFu)
|
|
||||||
val HEXAColor.Companion.black
|
|
||||||
get() = HEXAColor(0x000000FFu)
|
|
||||||
val HEXAColor.Companion.blanchedalmond
|
|
||||||
get() = HEXAColor(0xFFEBCDFFu)
|
|
||||||
val HEXAColor.Companion.blue
|
|
||||||
get() = HEXAColor(0x0000FFFFu)
|
|
||||||
val HEXAColor.Companion.blueviolet
|
|
||||||
get() = HEXAColor(0x8A2BE2FFu)
|
|
||||||
val HEXAColor.Companion.brown
|
|
||||||
get() = HEXAColor(0xA52A2AFFu)
|
|
||||||
val HEXAColor.Companion.burlywood
|
|
||||||
get() = HEXAColor(0xDEB887FFu)
|
|
||||||
val HEXAColor.Companion.cadetblue
|
|
||||||
get() = HEXAColor(0x5F9EA0FFu)
|
|
||||||
val HEXAColor.Companion.chartreuse
|
|
||||||
get() = HEXAColor(0x7FFF00FFu)
|
|
||||||
val HEXAColor.Companion.chocolate
|
|
||||||
get() = HEXAColor(0xD2691EFFu)
|
|
||||||
val HEXAColor.Companion.coral
|
|
||||||
get() = HEXAColor(0xFF7F50FFu)
|
|
||||||
val HEXAColor.Companion.cornflowerblue
|
|
||||||
get() = HEXAColor(0x6495EDFFu)
|
|
||||||
val HEXAColor.Companion.cornsilk
|
|
||||||
get() = HEXAColor(0xFFF8DCFFu)
|
|
||||||
val HEXAColor.Companion.crimson
|
|
||||||
get() = HEXAColor(0xDC143CFFu)
|
|
||||||
val HEXAColor.Companion.cyan
|
|
||||||
get() = HEXAColor(0x00FFFFFFu)
|
|
||||||
val HEXAColor.Companion.darkblue
|
|
||||||
get() = HEXAColor(0x00008BFFu)
|
|
||||||
val HEXAColor.Companion.darkcyan
|
|
||||||
get() = HEXAColor(0x008B8BFFu)
|
|
||||||
val HEXAColor.Companion.darkgoldenrod
|
|
||||||
get() = HEXAColor(0xB8860BFFu)
|
|
||||||
val HEXAColor.Companion.darkgray
|
|
||||||
get() = HEXAColor(0xA9A9A9FFu)
|
|
||||||
val HEXAColor.Companion.darkgreen
|
|
||||||
get() = HEXAColor(0x006400FFu)
|
|
||||||
val HEXAColor.Companion.darkgrey
|
|
||||||
get() = HEXAColor(0xA9A9A9FFu)
|
|
||||||
val HEXAColor.Companion.darkkhaki
|
|
||||||
get() = HEXAColor(0xBDB76BFFu)
|
|
||||||
val HEXAColor.Companion.darkmagenta
|
|
||||||
get() = HEXAColor(0x8B008BFFu)
|
|
||||||
val HEXAColor.Companion.darkolivegreen
|
|
||||||
get() = HEXAColor(0x556B2FFFu)
|
|
||||||
val HEXAColor.Companion.darkorange
|
|
||||||
get() = HEXAColor(0xFF8C00FFu)
|
|
||||||
val HEXAColor.Companion.darkorchid
|
|
||||||
get() = HEXAColor(0x9932CCFFu)
|
|
||||||
val HEXAColor.Companion.darkred
|
|
||||||
get() = HEXAColor(0x8B0000FFu)
|
|
||||||
val HEXAColor.Companion.darksalmon
|
|
||||||
get() = HEXAColor(0xE9967AFFu)
|
|
||||||
val HEXAColor.Companion.darkseagreen
|
|
||||||
get() = HEXAColor(0x8FBC8FFFu)
|
|
||||||
val HEXAColor.Companion.darkslateblue
|
|
||||||
get() = HEXAColor(0x483D8BFFu)
|
|
||||||
val HEXAColor.Companion.darkslategray
|
|
||||||
get() = HEXAColor(0x2F4F4FFFu)
|
|
||||||
val HEXAColor.Companion.darkslategrey
|
|
||||||
get() = HEXAColor(0x2F4F4FFFu)
|
|
||||||
val HEXAColor.Companion.darkturquoise
|
|
||||||
get() = HEXAColor(0x00CED1FFu)
|
|
||||||
val HEXAColor.Companion.darkviolet
|
|
||||||
get() = HEXAColor(0x9400D3FFu)
|
|
||||||
val HEXAColor.Companion.deeppink
|
|
||||||
get() = HEXAColor(0xFF1493FFu)
|
|
||||||
val HEXAColor.Companion.deepskyblue
|
|
||||||
get() = HEXAColor(0x00BFFFFFu)
|
|
||||||
val HEXAColor.Companion.dimgray
|
|
||||||
get() = HEXAColor(0x696969FFu)
|
|
||||||
val HEXAColor.Companion.dimgrey
|
|
||||||
get() = HEXAColor(0x696969FFu)
|
|
||||||
val HEXAColor.Companion.dodgerblue
|
|
||||||
get() = HEXAColor(0x1E90FFFFu)
|
|
||||||
val HEXAColor.Companion.firebrick
|
|
||||||
get() = HEXAColor(0xB22222FFu)
|
|
||||||
val HEXAColor.Companion.floralwhite
|
|
||||||
get() = HEXAColor(0xFFFAF0FFu)
|
|
||||||
val HEXAColor.Companion.forestgreen
|
|
||||||
get() = HEXAColor(0x228B22FFu)
|
|
||||||
val HEXAColor.Companion.fuchsia
|
|
||||||
get() = HEXAColor(0xFF00FFFFu)
|
|
||||||
val HEXAColor.Companion.gainsboro
|
|
||||||
get() = HEXAColor(0xDCDCDCFFu)
|
|
||||||
val HEXAColor.Companion.ghostwhite
|
|
||||||
get() = HEXAColor(0xF8F8FFFFu)
|
|
||||||
val HEXAColor.Companion.gold
|
|
||||||
get() = HEXAColor(0xFFD700FFu)
|
|
||||||
val HEXAColor.Companion.goldenrod
|
|
||||||
get() = HEXAColor(0xDAA520FFu)
|
|
||||||
val HEXAColor.Companion.gray
|
|
||||||
get() = HEXAColor(0x808080FFu)
|
|
||||||
val HEXAColor.Companion.green
|
|
||||||
get() = HEXAColor(0x008000FFu)
|
|
||||||
val HEXAColor.Companion.greenyellow
|
|
||||||
get() = HEXAColor(0xADFF2FFFu)
|
|
||||||
val HEXAColor.Companion.grey
|
|
||||||
get() = HEXAColor(0x808080FFu)
|
|
||||||
val HEXAColor.Companion.honeydew
|
|
||||||
get() = HEXAColor(0xF0FFF0FFu)
|
|
||||||
val HEXAColor.Companion.hotpink
|
|
||||||
get() = HEXAColor(0xFF69B4FFu)
|
|
||||||
val HEXAColor.Companion.indianred
|
|
||||||
get() = HEXAColor(0xCD5C5CFFu)
|
|
||||||
val HEXAColor.Companion.indigo
|
|
||||||
get() = HEXAColor(0x4B0082FFu)
|
|
||||||
val HEXAColor.Companion.ivory
|
|
||||||
get() = HEXAColor(0xFFFFF0FFu)
|
|
||||||
val HEXAColor.Companion.khaki
|
|
||||||
get() = HEXAColor(0xF0E68CFFu)
|
|
||||||
val HEXAColor.Companion.lavender
|
|
||||||
get() = HEXAColor(0xE6E6FAFFu)
|
|
||||||
val HEXAColor.Companion.lavenderblush
|
|
||||||
get() = HEXAColor(0xFFF0F5FFu)
|
|
||||||
val HEXAColor.Companion.lawngreen
|
|
||||||
get() = HEXAColor(0x7CFC00FFu)
|
|
||||||
val HEXAColor.Companion.lemonchiffon
|
|
||||||
get() = HEXAColor(0xFFFACDFFu)
|
|
||||||
val HEXAColor.Companion.lightblue
|
|
||||||
get() = HEXAColor(0xADD8E6FFu)
|
|
||||||
val HEXAColor.Companion.lightcoral
|
|
||||||
get() = HEXAColor(0xF08080FFu)
|
|
||||||
val HEXAColor.Companion.lightcyan
|
|
||||||
get() = HEXAColor(0xE0FFFFFFu)
|
|
||||||
val HEXAColor.Companion.lightgoldenrodyellow
|
|
||||||
get() = HEXAColor(0xFAFAD2FFu)
|
|
||||||
val HEXAColor.Companion.lightgray
|
|
||||||
get() = HEXAColor(0xD3D3D3FFu)
|
|
||||||
val HEXAColor.Companion.lightgreen
|
|
||||||
get() = HEXAColor(0x90EE90FFu)
|
|
||||||
val HEXAColor.Companion.lightgrey
|
|
||||||
get() = HEXAColor(0xD3D3D3FFu)
|
|
||||||
val HEXAColor.Companion.lightpink
|
|
||||||
get() = HEXAColor(0xFFB6C1FFu)
|
|
||||||
val HEXAColor.Companion.lightsalmon
|
|
||||||
get() = HEXAColor(0xFFA07AFFu)
|
|
||||||
val HEXAColor.Companion.lightseagreen
|
|
||||||
get() = HEXAColor(0x20B2AAFFu)
|
|
||||||
val HEXAColor.Companion.lightskyblue
|
|
||||||
get() = HEXAColor(0x87CEFAFFu)
|
|
||||||
val HEXAColor.Companion.lightslategray
|
|
||||||
get() = HEXAColor(0x778899FFu)
|
|
||||||
val HEXAColor.Companion.lightslategrey
|
|
||||||
get() = HEXAColor(0x778899FFu)
|
|
||||||
val HEXAColor.Companion.lightsteelblue
|
|
||||||
get() = HEXAColor(0xB0C4DEFFu)
|
|
||||||
val HEXAColor.Companion.lightyellow
|
|
||||||
get() = HEXAColor(0xFFFFE0FFu)
|
|
||||||
val HEXAColor.Companion.lime
|
|
||||||
get() = HEXAColor(0x00FF00FFu)
|
|
||||||
val HEXAColor.Companion.limegreen
|
|
||||||
get() = HEXAColor(0x32CD32FFu)
|
|
||||||
val HEXAColor.Companion.linen
|
|
||||||
get() = HEXAColor(0xFAF0E6FFu)
|
|
||||||
val HEXAColor.Companion.magenta
|
|
||||||
get() = HEXAColor(0xFF00FFFFu)
|
|
||||||
val HEXAColor.Companion.maroon
|
|
||||||
get() = HEXAColor(0x800000FFu)
|
|
||||||
val HEXAColor.Companion.mediumaquamarine
|
|
||||||
get() = HEXAColor(0x66CDAAFFu)
|
|
||||||
val HEXAColor.Companion.mediumblue
|
|
||||||
get() = HEXAColor(0x0000CDFFu)
|
|
||||||
val HEXAColor.Companion.mediumorchid
|
|
||||||
get() = HEXAColor(0xBA55D3FFu)
|
|
||||||
val HEXAColor.Companion.mediumpurple
|
|
||||||
get() = HEXAColor(0x9370DBFFu)
|
|
||||||
val HEXAColor.Companion.mediumseagreen
|
|
||||||
get() = HEXAColor(0x3CB371FFu)
|
|
||||||
val HEXAColor.Companion.mediumslateblue
|
|
||||||
get() = HEXAColor(0x7B68EEFFu)
|
|
||||||
val HEXAColor.Companion.mediumspringgreen
|
|
||||||
get() = HEXAColor(0x00FA9AFFu)
|
|
||||||
val HEXAColor.Companion.mediumturquoise
|
|
||||||
get() = HEXAColor(0x48D1CCFFu)
|
|
||||||
val HEXAColor.Companion.mediumvioletred
|
|
||||||
get() = HEXAColor(0xC71585FFu)
|
|
||||||
val HEXAColor.Companion.midnightblue
|
|
||||||
get() = HEXAColor(0x191970FFu)
|
|
||||||
val HEXAColor.Companion.mintcream
|
|
||||||
get() = HEXAColor(0xF5FFFAFFu)
|
|
||||||
val HEXAColor.Companion.mistyrose
|
|
||||||
get() = HEXAColor(0xFFE4E1FFu)
|
|
||||||
val HEXAColor.Companion.moccasin
|
|
||||||
get() = HEXAColor(0xFFE4B5FFu)
|
|
||||||
val HEXAColor.Companion.navajowhite
|
|
||||||
get() = HEXAColor(0xFFDEADFFu)
|
|
||||||
val HEXAColor.Companion.navy
|
|
||||||
get() = HEXAColor(0x000080FFu)
|
|
||||||
val HEXAColor.Companion.oldlace
|
|
||||||
get() = HEXAColor(0xFDF5E6FFu)
|
|
||||||
val HEXAColor.Companion.olive
|
|
||||||
get() = HEXAColor(0x808000FFu)
|
|
||||||
val HEXAColor.Companion.olivedrab
|
|
||||||
get() = HEXAColor(0x6B8E23FFu)
|
|
||||||
val HEXAColor.Companion.orange
|
|
||||||
get() = HEXAColor(0xFFA500FFu)
|
|
||||||
val HEXAColor.Companion.orangered
|
|
||||||
get() = HEXAColor(0xFF4500FFu)
|
|
||||||
val HEXAColor.Companion.orchid
|
|
||||||
get() = HEXAColor(0xDA70D6FFu)
|
|
||||||
val HEXAColor.Companion.palegoldenrod
|
|
||||||
get() = HEXAColor(0xEEE8AAFFu)
|
|
||||||
val HEXAColor.Companion.palegreen
|
|
||||||
get() = HEXAColor(0x98FB98FFu)
|
|
||||||
val HEXAColor.Companion.paleturquoise
|
|
||||||
get() = HEXAColor(0xAFEEEEFFu)
|
|
||||||
val HEXAColor.Companion.palevioletred
|
|
||||||
get() = HEXAColor(0xDB7093FFu)
|
|
||||||
val HEXAColor.Companion.papayawhip
|
|
||||||
get() = HEXAColor(0xFFEFD5FFu)
|
|
||||||
val HEXAColor.Companion.peachpuff
|
|
||||||
get() = HEXAColor(0xFFDAB9FFu)
|
|
||||||
val HEXAColor.Companion.peru
|
|
||||||
get() = HEXAColor(0xCD853FFFu)
|
|
||||||
val HEXAColor.Companion.pink
|
|
||||||
get() = HEXAColor(0xFFC0CBFFu)
|
|
||||||
val HEXAColor.Companion.plum
|
|
||||||
get() = HEXAColor(0xDDA0DDFFu)
|
|
||||||
val HEXAColor.Companion.powderblue
|
|
||||||
get() = HEXAColor(0xB0E0E6FFu)
|
|
||||||
val HEXAColor.Companion.purple
|
|
||||||
get() = HEXAColor(0x800080FFu)
|
|
||||||
val HEXAColor.Companion.red
|
|
||||||
get() = HEXAColor(0xFF0000FFu)
|
|
||||||
val HEXAColor.Companion.rosybrown
|
|
||||||
get() = HEXAColor(0xBC8F8FFFu)
|
|
||||||
val HEXAColor.Companion.royalblue
|
|
||||||
get() = HEXAColor(0x4169E1FFu)
|
|
||||||
val HEXAColor.Companion.saddlebrown
|
|
||||||
get() = HEXAColor(0x8B4513FFu)
|
|
||||||
val HEXAColor.Companion.salmon
|
|
||||||
get() = HEXAColor(0xFA8072FFu)
|
|
||||||
val HEXAColor.Companion.sandybrown
|
|
||||||
get() = HEXAColor(0xF4A460FFu)
|
|
||||||
val HEXAColor.Companion.seagreen
|
|
||||||
get() = HEXAColor(0x2E8B57FFu)
|
|
||||||
val HEXAColor.Companion.seashell
|
|
||||||
get() = HEXAColor(0xFFF5EEFFu)
|
|
||||||
val HEXAColor.Companion.sienna
|
|
||||||
get() = HEXAColor(0xA0522DFFu)
|
|
||||||
val HEXAColor.Companion.silver
|
|
||||||
get() = HEXAColor(0xC0C0C0FFu)
|
|
||||||
val HEXAColor.Companion.skyblue
|
|
||||||
get() = HEXAColor(0x87CEEBFFu)
|
|
||||||
val HEXAColor.Companion.slateblue
|
|
||||||
get() = HEXAColor(0x6A5ACDFFu)
|
|
||||||
val HEXAColor.Companion.slategray
|
|
||||||
get() = HEXAColor(0x708090FFu)
|
|
||||||
val HEXAColor.Companion.slategrey
|
|
||||||
get() = HEXAColor(0x708090FFu)
|
|
||||||
val HEXAColor.Companion.snow
|
|
||||||
get() = HEXAColor(0xFFFAFAFFu)
|
|
||||||
val HEXAColor.Companion.springgreen
|
|
||||||
get() = HEXAColor(0x00FF7FFFu)
|
|
||||||
val HEXAColor.Companion.steelblue
|
|
||||||
get() = HEXAColor(0x4682B4FFu)
|
|
||||||
val HEXAColor.Companion.tan
|
|
||||||
get() = HEXAColor(0xD2B48CFFu)
|
|
||||||
val HEXAColor.Companion.teal
|
|
||||||
get() = HEXAColor(0x008080FFu)
|
|
||||||
val HEXAColor.Companion.thistle
|
|
||||||
get() = HEXAColor(0xD8BFD8FFu)
|
|
||||||
val HEXAColor.Companion.tomato
|
|
||||||
get() = HEXAColor(0xFF6347FFu)
|
|
||||||
val HEXAColor.Companion.turquoise
|
|
||||||
get() = HEXAColor(0x40E0D0FFu)
|
|
||||||
val HEXAColor.Companion.violet
|
|
||||||
get() = HEXAColor(0xEE82EEFFu)
|
|
||||||
val HEXAColor.Companion.wheat
|
|
||||||
get() = HEXAColor(0xF5DEB3FFu)
|
|
||||||
val HEXAColor.Companion.white
|
|
||||||
get() = HEXAColor(0xFFFFFFFFu)
|
|
||||||
val HEXAColor.Companion.whitesmoke
|
|
||||||
get() = HEXAColor(0xF5F5F5FFu)
|
|
||||||
val HEXAColor.Companion.yellow
|
|
||||||
get() = HEXAColor(0xFFFF00FFu)
|
|
||||||
val HEXAColor.Companion.yellowgreen
|
|
||||||
get() = HEXAColor(0x9ACD32FFu)
|
|
@@ -4,15 +4,10 @@ plugins {
|
|||||||
id "com.android.library"
|
id "com.android.library"
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64Project"
|
apply from: "$mppProjectWithSerializationPresetPath"
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
sourceSets {
|
sourceSets {
|
||||||
commonMain {
|
|
||||||
dependencies {
|
|
||||||
api libs.klock
|
|
||||||
}
|
|
||||||
}
|
|
||||||
jvmMain {
|
jvmMain {
|
||||||
dependencies {
|
dependencies {
|
||||||
api project(":micro_utils.coroutines")
|
api project(":micro_utils.coroutines")
|
||||||
@@ -20,14 +15,9 @@ kotlin {
|
|||||||
}
|
}
|
||||||
androidMain {
|
androidMain {
|
||||||
dependencies {
|
dependencies {
|
||||||
|
api project(":micro_utils.coroutines")
|
||||||
api libs.android.fragment
|
api libs.android.fragment
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
nativeMain {
|
|
||||||
dependencies {
|
|
||||||
api libs.okio
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -3,17 +3,15 @@ plugins {
|
|||||||
id "org.jetbrains.kotlin.plugin.serialization"
|
id "org.jetbrains.kotlin.plugin.serialization"
|
||||||
id "com.android.library"
|
id "com.android.library"
|
||||||
alias(libs.plugins.jb.compose)
|
alias(libs.plugins.jb.compose)
|
||||||
alias(libs.plugins.kt.jb.compose)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$mppComposeJvmJsAndroidLinuxMingwLinuxArm64Project"
|
apply from: "$mppProjectWithSerializationAndComposePresetPath"
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
sourceSets {
|
sourceSets {
|
||||||
commonMain {
|
commonMain {
|
||||||
dependencies {
|
dependencies {
|
||||||
api project(":micro_utils.common")
|
api project(":micro_utils.common")
|
||||||
api libs.kt.coroutines
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1 +0,0 @@
|
|||||||
<manifest/>
|
|
@@ -1,74 +0,0 @@
|
|||||||
package dev.inmo.micro_utils.common.compose
|
|
||||||
|
|
||||||
import androidx.compose.runtime.*
|
|
||||||
import dev.inmo.micro_utils.common.Optional
|
|
||||||
import dev.inmo.micro_utils.common.dataOrThrow
|
|
||||||
import dev.inmo.micro_utils.common.optional
|
|
||||||
|
|
||||||
class LoadableComponentContext<T> internal constructor(
|
|
||||||
presetOptional: Optional<T>,
|
|
||||||
) {
|
|
||||||
internal val iterationState: MutableState<Int> = mutableStateOf(0)
|
|
||||||
|
|
||||||
internal var dataOptional: Optional<T> = if (presetOptional.dataPresented) presetOptional else Optional.absent()
|
|
||||||
private set
|
|
||||||
internal val dataState: MutableState<Optional<T>> = mutableStateOf(dataOptional)
|
|
||||||
|
|
||||||
fun reload() {
|
|
||||||
iterationState.value++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Showing data with ability to reload data
|
|
||||||
*
|
|
||||||
* [block] will be shown when [loader] will complete loading. If you want to reload data, just call
|
|
||||||
* [LoadableComponentContext.reload]
|
|
||||||
*/
|
|
||||||
@Composable
|
|
||||||
fun <T> LoadableComponent(
|
|
||||||
preload: Optional<T>,
|
|
||||||
loader: suspend LoadableComponentContext<T>.() -> T,
|
|
||||||
block: @Composable LoadableComponentContext<T>.(T) -> Unit
|
|
||||||
) {
|
|
||||||
val context = remember { LoadableComponentContext(preload) }
|
|
||||||
|
|
||||||
LaunchedEffect(context.iterationState.value) {
|
|
||||||
context.dataState.value = loader(context).optional
|
|
||||||
}
|
|
||||||
|
|
||||||
context.dataState.let {
|
|
||||||
if (it.value.dataPresented) {
|
|
||||||
context.block(it.value.dataOrThrow(IllegalStateException("Data must be presented, but optional has been changed by some way")))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Showing data with ability to reload data
|
|
||||||
*
|
|
||||||
* [block] will be shown when [loader] will complete loading. If you want to reload data, just call
|
|
||||||
* [LoadableComponentContext.reload]
|
|
||||||
*/
|
|
||||||
@Composable
|
|
||||||
fun <T> LoadableComponent(
|
|
||||||
preload: T,
|
|
||||||
loader: suspend LoadableComponentContext<T>.() -> T,
|
|
||||||
block: @Composable LoadableComponentContext<T>.(T) -> Unit
|
|
||||||
) {
|
|
||||||
LoadableComponent(preload.optional, loader, block)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Showing data with ability to reload data
|
|
||||||
*
|
|
||||||
* [block] will be shown when [loader] will complete loading. If you want to reload data, just call
|
|
||||||
* [LoadableComponentContext.reload]
|
|
||||||
*/
|
|
||||||
@Composable
|
|
||||||
fun <T> LoadableComponent(
|
|
||||||
loader: suspend LoadableComponentContext<T>.() -> T,
|
|
||||||
block: @Composable LoadableComponentContext<T>.(T) -> Unit
|
|
||||||
) {
|
|
||||||
LoadableComponent(Optional.absent(), loader, block)
|
|
||||||
}
|
|
@@ -1,10 +0,0 @@
|
|||||||
package dev.inmo.micro_utils.common.compose
|
|
||||||
|
|
||||||
import androidx.compose.runtime.MutableState
|
|
||||||
import androidx.compose.runtime.State
|
|
||||||
import androidx.compose.runtime.derivedStateOf
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts current [MutableState] to immutable [State] using [derivedStateOf]
|
|
||||||
*/
|
|
||||||
fun <T> MutableState<T>.asState(): State<T> = derivedStateOf { this.value }
|
|
@@ -1,19 +0,0 @@
|
|||||||
package dev.inmo.micro_utils.common.compose
|
|
||||||
|
|
||||||
import org.jetbrains.compose.web.dom.AttrBuilderContext
|
|
||||||
import org.w3c.dom.Element
|
|
||||||
|
|
||||||
operator fun <T : Element> AttrBuilderContext<T>?.plus(
|
|
||||||
other: AttrBuilderContext<T>?
|
|
||||||
) = when (this) {
|
|
||||||
null -> other ?: {}
|
|
||||||
else -> when (other) {
|
|
||||||
null -> this ?: {}
|
|
||||||
else -> {
|
|
||||||
{
|
|
||||||
invoke(this)
|
|
||||||
other(this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,22 +0,0 @@
|
|||||||
package dev.inmo.micro_utils.common.compose
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.DisposableEffect
|
|
||||||
import androidx.compose.runtime.DisposableEffectResult
|
|
||||||
import androidx.compose.runtime.DisposableEffectScope
|
|
||||||
import org.jetbrains.compose.web.attributes.AttrsScope
|
|
||||||
import org.jetbrains.compose.web.dom.ElementScope
|
|
||||||
import org.w3c.dom.Element
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function must be called in the context of your tag content. It works like default [AttrsScope.ref],
|
|
||||||
* but able to be used several times. Uses [DisposableEffect] under the hood
|
|
||||||
*/
|
|
||||||
@Composable
|
|
||||||
fun <T : Element> ElementScope<T>.ref(
|
|
||||||
block: DisposableEffectScope.(T) -> DisposableEffectResult
|
|
||||||
) {
|
|
||||||
DisposableEffect(0) {
|
|
||||||
block(scopeElement)
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,11 +0,0 @@
|
|||||||
package dev.inmo.micro_utils.common.compose
|
|
||||||
|
|
||||||
import org.jetbrains.compose.web.dom.AttrBuilderContext
|
|
||||||
|
|
||||||
fun tagClasses(vararg classnames: String): AttrBuilderContext<*> = {
|
|
||||||
classes(*classnames)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun tagId(id: String): AttrBuilderContext<*> = {
|
|
||||||
id(id)
|
|
||||||
}
|
|
@@ -1,42 +0,0 @@
|
|||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.ui.test.ExperimentalTestApi
|
|
||||||
import androidx.compose.ui.test.runComposeUiTest
|
|
||||||
import dev.inmo.micro_utils.common.compose.LoadableComponent
|
|
||||||
import dev.inmo.micro_utils.coroutines.SpecialMutableStateFlow
|
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
|
||||||
import kotlinx.coroutines.flow.filter
|
|
||||||
import kotlinx.coroutines.flow.first
|
|
||||||
import kotlinx.coroutines.flow.firstOrNull
|
|
||||||
import org.jetbrains.annotations.TestOnly
|
|
||||||
import kotlin.test.Test
|
|
||||||
import kotlin.test.assertTrue
|
|
||||||
|
|
||||||
class LoadableComponentTests {
|
|
||||||
@OptIn(ExperimentalTestApi::class)
|
|
||||||
@Test
|
|
||||||
@TestOnly
|
|
||||||
fun testSimpleLoad() = runComposeUiTest {
|
|
||||||
val loadingFlow = SpecialMutableStateFlow<Int>(0)
|
|
||||||
val loadedFlow = SpecialMutableStateFlow<Int>(0)
|
|
||||||
setContent {
|
|
||||||
LoadableComponent<Int>({
|
|
||||||
loadingFlow.filter { it == 1 }.first()
|
|
||||||
}) {
|
|
||||||
assert(dataState.value.data == 1)
|
|
||||||
remember {
|
|
||||||
loadedFlow.value = 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
waitForIdle()
|
|
||||||
|
|
||||||
assertTrue(loadedFlow.value == 0)
|
|
||||||
|
|
||||||
loadingFlow.value = 1
|
|
||||||
|
|
||||||
waitForIdle()
|
|
||||||
|
|
||||||
assertTrue(loadedFlow.value == 2)
|
|
||||||
}
|
|
||||||
}
|
|
1
common/compose/src/main/AndroidManifest.xml
Normal file
1
common/compose/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<manifest package="dev.inmo.micro_utils.common.compose"/>
|
@@ -1,13 +0,0 @@
|
|||||||
package dev.inmo.micro_utils.common
|
|
||||||
|
|
||||||
fun <T> List<T>.breakAsPairs(): List<Pair<T, T>> {
|
|
||||||
val result = mutableListOf<Pair<T, T>>()
|
|
||||||
|
|
||||||
for (i in 0 until size - 1) {
|
|
||||||
val first = get(i)
|
|
||||||
val second = get(i + 1)
|
|
||||||
result.add(first to second)
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
@@ -1,6 +0,0 @@
|
|||||||
package dev.inmo.micro_utils.common
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates simple [Comparator] which will use [compareTo] of [T] for both objects
|
|
||||||
*/
|
|
||||||
fun <T : Comparable<C>, C : T> T.createComparator() = Comparator<C> { o1, o2 -> o1.compareTo(o2) }
|
|
@@ -1,25 +0,0 @@
|
|||||||
package dev.inmo.micro_utils.common
|
|
||||||
|
|
||||||
import korlibs.time.DateTime
|
|
||||||
import kotlinx.serialization.KSerializer
|
|
||||||
import kotlinx.serialization.Serializer
|
|
||||||
import kotlinx.serialization.builtins.serializer
|
|
||||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
|
||||||
import kotlinx.serialization.encoding.Decoder
|
|
||||||
import kotlinx.serialization.encoding.Encoder
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Serializes [DateTime] as its raw [DateTime.unixMillis] and deserializes in the same way
|
|
||||||
*/
|
|
||||||
object DateTimeSerializer : KSerializer<DateTime> {
|
|
||||||
override val descriptor: SerialDescriptor
|
|
||||||
get() = Double.serializer().descriptor
|
|
||||||
|
|
||||||
override fun deserialize(decoder: Decoder): DateTime {
|
|
||||||
return DateTime(decoder.decodeDouble())
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun serialize(encoder: Encoder, value: DateTime) {
|
|
||||||
encoder.encodeDouble(value.unixMillis)
|
|
||||||
}
|
|
||||||
}
|
|
@@ -2,13 +2,11 @@
|
|||||||
|
|
||||||
package dev.inmo.micro_utils.common
|
package dev.inmo.micro_utils.common
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
private inline fun <T> getObject(
|
private inline fun <T> getObject(
|
||||||
additional: MutableList<T>,
|
additional: MutableList<T>,
|
||||||
iterator: Iterator<T>
|
iterator: Iterator<T>
|
||||||
): T? = when {
|
): T? = when {
|
||||||
additional.isNotEmpty() -> additional.removeAt(0)
|
additional.isNotEmpty() -> additional.removeFirst()
|
||||||
iterator.hasNext() -> iterator.next()
|
iterator.hasNext() -> iterator.next()
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
@@ -26,23 +24,14 @@ private inline fun <T> getObject(
|
|||||||
*
|
*
|
||||||
* @see calculateDiff
|
* @see calculateDiff
|
||||||
*/
|
*/
|
||||||
@Serializable
|
data class Diff<T> internal constructor(
|
||||||
data class Diff<T> @Warning(warning) constructor(
|
val removed: List<IndexedValue<T>>,
|
||||||
val removed: List<@Serializable(IndexedValueSerializer::class) IndexedValue<T>>,
|
|
||||||
/**
|
/**
|
||||||
* Old-New values pairs
|
* Old-New values pairs
|
||||||
*/
|
*/
|
||||||
val replaced: List<Pair<@Serializable(IndexedValueSerializer::class) IndexedValue<T>, @Serializable(IndexedValueSerializer::class) IndexedValue<T>>>,
|
val replaced: List<Pair<IndexedValue<T>, IndexedValue<T>>>,
|
||||||
val added: List<@Serializable(IndexedValueSerializer::class) IndexedValue<T>>
|
val added: List<IndexedValue<T>>
|
||||||
) {
|
)
|
||||||
fun isEmpty(): Boolean = removed.isEmpty() && replaced.isEmpty() && added.isEmpty()
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val warning = "This feature can be changed without any warranties. Use with caution and only in case you know what you are doing"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <T> emptyDiff(): Diff<T> = Diff(emptyList(), emptyList(), emptyList())
|
|
||||||
|
|
||||||
private inline fun <T> performChanges(
|
private inline fun <T> performChanges(
|
||||||
potentialChanges: MutableList<Pair<IndexedValue<T>?, IndexedValue<T>?>>,
|
potentialChanges: MutableList<Pair<IndexedValue<T>?, IndexedValue<T>?>>,
|
||||||
@@ -51,14 +40,14 @@ private inline fun <T> performChanges(
|
|||||||
changedList: MutableList<Pair<IndexedValue<T>, IndexedValue<T>>>,
|
changedList: MutableList<Pair<IndexedValue<T>, IndexedValue<T>>>,
|
||||||
removedList: MutableList<IndexedValue<T>>,
|
removedList: MutableList<IndexedValue<T>>,
|
||||||
addedList: MutableList<IndexedValue<T>>,
|
addedList: MutableList<IndexedValue<T>>,
|
||||||
comparisonFun: (T?, T?) -> Boolean
|
strictComparison: Boolean
|
||||||
) {
|
) {
|
||||||
var i = -1
|
var i = -1
|
||||||
val (oldObject, newObject) = potentialChanges.lastOrNull() ?: return
|
val (oldObject, newObject) = potentialChanges.lastOrNull() ?: return
|
||||||
for ((old, new) in potentialChanges.take(potentialChanges.size - 1)) {
|
for ((old, new) in potentialChanges.take(potentialChanges.size - 1)) {
|
||||||
i++
|
i++
|
||||||
val oldOneEqualToNewObject = comparisonFun(old ?.value, newObject ?.value)
|
val oldOneEqualToNewObject = old ?.value === newObject ?.value || (old ?.value == newObject ?.value && !strictComparison)
|
||||||
val newOneEqualToOldObject = comparisonFun(new ?.value, oldObject ?.value)
|
val newOneEqualToOldObject = new ?.value === oldObject ?.value || (new ?.value == oldObject ?.value && !strictComparison)
|
||||||
if (oldOneEqualToNewObject || newOneEqualToOldObject) {
|
if (oldOneEqualToNewObject || newOneEqualToOldObject) {
|
||||||
changedList.addAll(
|
changedList.addAll(
|
||||||
potentialChanges.take(i).mapNotNull {
|
potentialChanges.take(i).mapNotNull {
|
||||||
@@ -112,7 +101,7 @@ private inline fun <T> performChanges(
|
|||||||
*/
|
*/
|
||||||
fun <T> Iterable<T>.calculateDiff(
|
fun <T> Iterable<T>.calculateDiff(
|
||||||
other: Iterable<T>,
|
other: Iterable<T>,
|
||||||
comparisonFun: (T?, T?) -> Boolean
|
strictComparison: Boolean = false
|
||||||
): Diff<T> {
|
): Diff<T> {
|
||||||
var i = -1
|
var i = -1
|
||||||
var j = -1
|
var j = -1
|
||||||
@@ -140,7 +129,7 @@ fun <T> Iterable<T>.calculateDiff(
|
|||||||
}
|
}
|
||||||
|
|
||||||
when {
|
when {
|
||||||
comparisonFun(oldObject, newObject) -> {
|
oldObject === newObject || (oldObject == newObject && !strictComparison) -> {
|
||||||
changedObjects.addAll(potentiallyChangedObjects.map {
|
changedObjects.addAll(potentiallyChangedObjects.map {
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
it as Pair<IndexedValue<T>, IndexedValue<T>>
|
it as Pair<IndexedValue<T>, IndexedValue<T>>
|
||||||
@@ -151,49 +140,23 @@ fun <T> Iterable<T>.calculateDiff(
|
|||||||
potentiallyChangedObjects.add(oldObject ?.let { IndexedValue(i, oldObject) } to newObject ?.let { IndexedValue(j, newObject) })
|
potentiallyChangedObjects.add(oldObject ?.let { IndexedValue(i, oldObject) } to newObject ?.let { IndexedValue(j, newObject) })
|
||||||
val previousOldsAdditionsSize = additionalInOld.size
|
val previousOldsAdditionsSize = additionalInOld.size
|
||||||
val previousNewsAdditionsSize = additionalInNew.size
|
val previousNewsAdditionsSize = additionalInNew.size
|
||||||
performChanges(potentiallyChangedObjects, additionalInOld, additionalInNew, changedObjects, removedObjects, addedObjects, comparisonFun)
|
performChanges(potentiallyChangedObjects, additionalInOld, additionalInNew, changedObjects, removedObjects, addedObjects, strictComparison)
|
||||||
i -= (additionalInOld.size - previousOldsAdditionsSize)
|
i -= (additionalInOld.size - previousOldsAdditionsSize)
|
||||||
j -= (additionalInNew.size - previousNewsAdditionsSize)
|
j -= (additionalInNew.size - previousNewsAdditionsSize)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
potentiallyChangedObjects.add(null to null)
|
potentiallyChangedObjects.add(null to null)
|
||||||
performChanges(potentiallyChangedObjects, additionalInOld, additionalInNew, changedObjects, removedObjects, addedObjects, comparisonFun)
|
performChanges(potentiallyChangedObjects, additionalInOld, additionalInNew, changedObjects, removedObjects, addedObjects, strictComparison)
|
||||||
|
|
||||||
return Diff(removedObjects.toList(), changedObjects.toList(), addedObjects.toList())
|
return Diff(removedObjects.toList(), changedObjects.toList(), addedObjects.toList())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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> = calculateDiff(
|
|
||||||
other,
|
|
||||||
comparisonFun = if (strictComparison) {
|
|
||||||
{ t1, t2 ->
|
|
||||||
t1 === t2
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
{ t1, t2 ->
|
|
||||||
t1 === t2 || t1 == t2 // small optimization for cases when t1 and t2 are the same - comparison will be faster potentially
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
inline fun <T> Iterable<T>.diff(
|
inline fun <T> Iterable<T>.diff(
|
||||||
other: Iterable<T>,
|
other: Iterable<T>,
|
||||||
strictComparison: Boolean = false
|
strictComparison: Boolean = false
|
||||||
): Diff<T> = calculateDiff(other, strictComparison)
|
): Diff<T> = calculateDiff(other, strictComparison)
|
||||||
inline fun <T> Iterable<T>.diff(
|
|
||||||
other: Iterable<T>,
|
|
||||||
noinline comparisonFun: (T?, T?) -> Boolean
|
|
||||||
): Diff<T> = calculateDiff(other, comparisonFun)
|
|
||||||
|
|
||||||
inline fun <T> Diff(old: Iterable<T>, new: Iterable<T>) = old.calculateDiff(new, strictComparison = false)
|
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)
|
inline fun <T> StrictDiff(old: Iterable<T>, new: Iterable<T>) = old.calculateDiff(new, true)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -203,23 +166,6 @@ inline fun <T> Iterable<T>.calculateStrictDiff(
|
|||||||
other: Iterable<T>
|
other: Iterable<T>
|
||||||
) = calculateDiff(other, strictComparison = true)
|
) = calculateDiff(other, strictComparison = true)
|
||||||
|
|
||||||
/**
|
|
||||||
* Applies [diff] to [this] [MutableList]
|
|
||||||
*/
|
|
||||||
fun <T> MutableList<T>.applyDiff(
|
|
||||||
diff: Diff<T>
|
|
||||||
) {
|
|
||||||
for (i in diff.removed.indices.sortedDescending()) {
|
|
||||||
removeAt(diff.removed[i].index)
|
|
||||||
}
|
|
||||||
diff.added.forEach { (i, t) ->
|
|
||||||
add(i, t)
|
|
||||||
}
|
|
||||||
diff.replaced.forEach { (_, new) ->
|
|
||||||
set(new.index, new.value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method call [calculateDiff] with strict mode [strictComparison] and then apply differences to [this]
|
* This method call [calculateDiff] with strict mode [strictComparison] and then apply differences to [this]
|
||||||
* mutable list
|
* mutable list
|
||||||
@@ -228,26 +174,13 @@ fun <T> MutableList<T>.applyDiff(
|
|||||||
source: Iterable<T>,
|
source: Iterable<T>,
|
||||||
strictComparison: Boolean = false
|
strictComparison: Boolean = false
|
||||||
): Diff<T> = calculateDiff(source, strictComparison).also {
|
): Diff<T> = calculateDiff(source, strictComparison).also {
|
||||||
applyDiff(it)
|
for (i in it.removed.indices.sortedDescending()) {
|
||||||
|
removeAt(it.removed[i].index)
|
||||||
|
}
|
||||||
|
it.added.forEach { (i, t) ->
|
||||||
|
add(i, t)
|
||||||
|
}
|
||||||
|
it.replaced.forEach { (_, new) ->
|
||||||
|
set(new.index, new.value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* This method call [calculateDiff] and then apply differences to [this]
|
|
||||||
* mutable list
|
|
||||||
*/
|
|
||||||
fun <T> MutableList<T>.applyDiff(
|
|
||||||
source: Iterable<T>,
|
|
||||||
comparisonFun: (T?, T?) -> Boolean
|
|
||||||
): Diff<T> = calculateDiff(source, comparisonFun).also {
|
|
||||||
applyDiff(it)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reverse [this] [Diff]. Result will contain [Diff.added] on [Diff.removed] (and vice-verse), all the
|
|
||||||
* [Diff.replaced] values will be reversed too
|
|
||||||
*/
|
|
||||||
fun <T> Diff<T>.reversed() = Diff(
|
|
||||||
removed = added,
|
|
||||||
replaced = replaced.map { it.second to it.first },
|
|
||||||
added = removed
|
|
||||||
)
|
|
||||||
|
@@ -1,43 +0,0 @@
|
|||||||
package dev.inmo.micro_utils.common
|
|
||||||
|
|
||||||
inline fun <T> Boolean.letIfTrue(block: () -> T): T? {
|
|
||||||
return if (this) {
|
|
||||||
block()
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun <T> Boolean.letIfFalse(block: () -> T): T? {
|
|
||||||
return if (this) {
|
|
||||||
null
|
|
||||||
} else {
|
|
||||||
block()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun Boolean.alsoIfTrue(block: () -> Unit): Boolean {
|
|
||||||
letIfTrue(block)
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun Boolean.alsoIfFalse(block: () -> Unit): Boolean {
|
|
||||||
letIfFalse(block)
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun <T> Boolean.ifTrue(block: () -> T): T? {
|
|
||||||
return if (this) {
|
|
||||||
block()
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun <T> Boolean.ifFalse(block: () -> T): T? {
|
|
||||||
return if (this) {
|
|
||||||
null
|
|
||||||
} else {
|
|
||||||
block()
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,30 +0,0 @@
|
|||||||
package dev.inmo.micro_utils.common
|
|
||||||
|
|
||||||
import kotlinx.serialization.KSerializer
|
|
||||||
import kotlinx.serialization.Serializer
|
|
||||||
import kotlinx.serialization.builtins.PairSerializer
|
|
||||||
import kotlinx.serialization.builtins.serializer
|
|
||||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
|
||||||
import kotlinx.serialization.encoding.Decoder
|
|
||||||
import kotlinx.serialization.encoding.Encoder
|
|
||||||
|
|
||||||
class IndexedValueSerializer<T>(private val subSerializer: KSerializer<T>) : KSerializer<IndexedValue<T>> {
|
|
||||||
private val originalSerializer = PairSerializer(Int.serializer(), subSerializer)
|
|
||||||
override val descriptor: SerialDescriptor
|
|
||||||
get() = originalSerializer.descriptor
|
|
||||||
|
|
||||||
override fun deserialize(decoder: Decoder): IndexedValue<T> {
|
|
||||||
val pair = originalSerializer.deserialize(decoder)
|
|
||||||
return IndexedValue(
|
|
||||||
pair.first,
|
|
||||||
pair.second
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun serialize(encoder: Encoder, value: IndexedValue<T>) {
|
|
||||||
originalSerializer.serialize(
|
|
||||||
encoder,
|
|
||||||
Pair(value.index, value.value)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,135 +0,0 @@
|
|||||||
package dev.inmo.micro_utils.common
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Contains diff based on the comparison of objects with the same [K].
|
|
||||||
*
|
|
||||||
* @param removed Contains map with keys removed from parent map
|
|
||||||
* @param changed Contains map with keys values changed new map in comparison with old one
|
|
||||||
* @param added Contains map with new keys and values
|
|
||||||
*/
|
|
||||||
data class MapDiff<K, V> @Warning(warning) constructor(
|
|
||||||
val removed: Map<K, V>,
|
|
||||||
val changed: Map<K, Pair<V, V>>,
|
|
||||||
val added: Map<K, V>
|
|
||||||
) {
|
|
||||||
fun isEmpty() = removed.isEmpty() && changed.isEmpty() && added.isEmpty()
|
|
||||||
inline fun isNotEmpty() = !isEmpty()
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val warning = "This feature can be changed without any warranties. Use with caution and only in case you know what you are doing"
|
|
||||||
fun <K, V> empty() = MapDiff<K, V>(emptyMap(), emptyMap(), emptyMap())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private inline fun <K, V> createCompareFun(
|
|
||||||
strictComparison: Boolean
|
|
||||||
): (K, V, V) -> Boolean = if (strictComparison) {
|
|
||||||
{ _, first, second -> first === second }
|
|
||||||
} else {
|
|
||||||
{ _, first, second -> first == second }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compare [this] [Map] with the [other] one in principle when [other] is newer than [this]
|
|
||||||
*
|
|
||||||
* @param compareFun Will be used to determine changed values
|
|
||||||
*/
|
|
||||||
fun <K, V> Map<K, V>.diff(
|
|
||||||
other: Map<K, V>,
|
|
||||||
compareFun: (K, V, V) -> Boolean
|
|
||||||
): MapDiff<K, V> {
|
|
||||||
val removed: Map<K, V> = (keys - other.keys).associateWith {
|
|
||||||
getValue(it)
|
|
||||||
}
|
|
||||||
val added: Map<K, V> = (other.keys - keys).associateWith {
|
|
||||||
other.getValue(it)
|
|
||||||
}
|
|
||||||
val changed = keys.intersect(other.keys).mapNotNull {
|
|
||||||
val old = getValue(it)
|
|
||||||
val new = other.getValue(it)
|
|
||||||
if (compareFun(it, old, new)) {
|
|
||||||
return@mapNotNull null
|
|
||||||
} else {
|
|
||||||
it to (old to new)
|
|
||||||
}
|
|
||||||
}.toMap()
|
|
||||||
|
|
||||||
return MapDiff(
|
|
||||||
removed,
|
|
||||||
changed,
|
|
||||||
added
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compare [this] [Map] with the [other] one in principle when [other] is newer than [this]
|
|
||||||
*
|
|
||||||
* @param strictComparison If true, will use strict (===) comparison for the values' comparison. Otherwise, standard
|
|
||||||
* `equals` will be used
|
|
||||||
*/
|
|
||||||
fun <K, V> Map<K, V>.diff(
|
|
||||||
other: Map<K, V>,
|
|
||||||
strictComparison: Boolean = false
|
|
||||||
): MapDiff<K, V> = diff(
|
|
||||||
other,
|
|
||||||
compareFun = createCompareFun(strictComparison)
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Will apply [mapDiff] to [this] [MutableMap]
|
|
||||||
*/
|
|
||||||
fun <K, V> MutableMap<K, V>.applyDiff(
|
|
||||||
mapDiff: MapDiff<K, V>
|
|
||||||
) {
|
|
||||||
mapDiff.apply {
|
|
||||||
removed.keys.forEach { remove(it) }
|
|
||||||
changed.forEach { (k, oldNew) ->
|
|
||||||
put(k, oldNew.second)
|
|
||||||
}
|
|
||||||
added.forEach { (k, new) ->
|
|
||||||
put(k, new)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Will apply changes with [from] map into [this] one
|
|
||||||
*
|
|
||||||
* @param compareFun Will be used to determine changed values
|
|
||||||
*
|
|
||||||
* @return [MapDiff] applied to [this] [MutableMap]
|
|
||||||
*/
|
|
||||||
fun <K, V> MutableMap<K, V>.applyDiff(
|
|
||||||
from: Map<K, V>,
|
|
||||||
compareFun: (K, V, V) -> Boolean
|
|
||||||
): MapDiff<K, V> {
|
|
||||||
return diff(from, compareFun).also {
|
|
||||||
applyDiff(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Will apply changes with [from] map into [this] one
|
|
||||||
*
|
|
||||||
* @param strictComparison If true, will use strict (===) comparison for the values' comparison. Otherwise, standard
|
|
||||||
* `equals` will be used
|
|
||||||
*
|
|
||||||
* @return [MapDiff] applied to [this] [MutableMap]
|
|
||||||
*/
|
|
||||||
fun <K, V> MutableMap<K, V>.applyDiff(
|
|
||||||
from: Map<K, V>,
|
|
||||||
strictComparison: Boolean = false
|
|
||||||
): MapDiff<K, V> = applyDiff(
|
|
||||||
from,
|
|
||||||
compareFun = createCompareFun(strictComparison)
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reverse [this] [MapDiff]. Result will contain [MapDiff.added] on [MapDiff.removed] (and vice-verse), all the
|
|
||||||
* [MapDiff.changed] values will be reversed too
|
|
||||||
*/
|
|
||||||
fun <K, V> MapDiff<K, V>.reversed(): MapDiff<K, V> = MapDiff(
|
|
||||||
removed = added,
|
|
||||||
changed = changed.mapValues { (_, oldNew) -> oldNew.second to oldNew.first },
|
|
||||||
added = removed
|
|
||||||
)
|
|
@@ -1,32 +0,0 @@
|
|||||||
package dev.inmo.micro_utils.common
|
|
||||||
|
|
||||||
inline fun <T> Sequence<T>.padWith(size: Int, inserter: (Sequence<T>) -> Sequence<T>): Sequence<T> {
|
|
||||||
var result = this
|
|
||||||
while (result.count() < size) {
|
|
||||||
result = inserter(result)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun <T> Sequence<T>.padEnd(size: Int, padBlock: (Int) -> T): Sequence<T> = padWith(size) { it + padBlock(it.count()) }
|
|
||||||
|
|
||||||
inline fun <T> Sequence<T>.padEnd(size: Int, o: T) = padEnd(size) { o }
|
|
||||||
|
|
||||||
inline fun <T> List<T>.padWith(size: Int, inserter: (List<T>) -> List<T>): List<T> {
|
|
||||||
var result = this
|
|
||||||
while (result.size < size) {
|
|
||||||
result = inserter(result)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
inline fun <T> List<T>.padEnd(size: Int, padBlock: (Int) -> T): List<T> = asSequence().padEnd(size, padBlock).toList()
|
|
||||||
|
|
||||||
inline fun <T> List<T>.padEnd(size: Int, o: T): List<T> = asSequence().padEnd(size, o).toList()
|
|
||||||
|
|
||||||
inline fun <T> Sequence<T>.padStart(size: Int, padBlock: (Int) -> T): Sequence<T> = padWith(size) { sequenceOf(padBlock(it.count())) + it }
|
|
||||||
|
|
||||||
inline fun <T> Sequence<T>.padStart(size: Int, o: T) = padStart(size) { o }
|
|
||||||
|
|
||||||
inline fun <T> List<T>.padStart(size: Int, padBlock: (Int) -> T): List<T> = asSequence().padStart(size, padBlock).toList()
|
|
||||||
|
|
||||||
inline fun <T> List<T>.padStart(size: Int, o: T): List<T> = asSequence().padStart(size, o).toList()
|
|
@@ -1,76 +0,0 @@
|
|||||||
package dev.inmo.micro_utils.common
|
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
import kotlin.jvm.JvmInline
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Contains [of1] as main value, where 100% of percentage is when of1 == 1
|
|
||||||
*
|
|
||||||
* @see invoke
|
|
||||||
* @see partOfTotal
|
|
||||||
* @see of100
|
|
||||||
*/
|
|
||||||
@Serializable
|
|
||||||
@JvmInline
|
|
||||||
value class Percentage private constructor(
|
|
||||||
/**
|
|
||||||
* Value of percentage. When it equals to 1, means 100%
|
|
||||||
*/
|
|
||||||
val of1: Double
|
|
||||||
) {
|
|
||||||
/**
|
|
||||||
* Same as [of1], but float (using [Double.toFloat])
|
|
||||||
*/
|
|
||||||
val of1Float
|
|
||||||
get() = of1.toFloat()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represent this percentage as common percentage where 100% is 100%
|
|
||||||
*/
|
|
||||||
val of100
|
|
||||||
get() = of1 * 100
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Same as [of100], but float (using [Double.toFloat])
|
|
||||||
*/
|
|
||||||
val of100Float
|
|
||||||
get() = of100.toFloat()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Same as [of100], but int (using [Double.toInt])
|
|
||||||
*/
|
|
||||||
val of100Int
|
|
||||||
get() = of100.toInt()
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
val rangeOfValues = 0.0 .. 1.0
|
|
||||||
|
|
||||||
val START = Percentage(rangeOfValues.start)
|
|
||||||
val COMPLETED = Percentage(rangeOfValues.endInclusive)
|
|
||||||
|
|
||||||
operator fun invoke(of1: Double) = Percentage(of1.coerceIn(rangeOfValues))
|
|
||||||
operator fun invoke(part: Number, total: Number) = Percentage(
|
|
||||||
part.toDouble() / total.toDouble()
|
|
||||||
)
|
|
||||||
fun of1(of1: Double) = Percentage(of1 = of1)
|
|
||||||
fun of100(of100: Double) = Percentage(of1 = of100 / 100)
|
|
||||||
fun partOfTotal(part: Number, total: Number) = Percentage(part = part, total = total)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
typealias Progress = Percentage
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Will return [this] [Progress] if [Percentage.of1] in `0 .. 1` range
|
|
||||||
*/
|
|
||||||
fun Progress.ensureStrictOrNull(): Progress? = if (of1 in Percentage.rangeOfValues) this else null
|
|
||||||
/**
|
|
||||||
* Will return [this] [Progress] if [Percentage.of1] in `0 .. 1` range. Otherwise, will throw error
|
|
||||||
* [IllegalArgumentException] due to [require] failure
|
|
||||||
*/
|
|
||||||
fun Progress.ensureStrictOrThrow(): Progress {
|
|
||||||
require(of1 in Percentage.rangeOfValues) {
|
|
||||||
"For strict checks value of percentage must be in ${Percentage.rangeOfValues}, but actual value is $of1"
|
|
||||||
}
|
|
||||||
return this
|
|
||||||
}
|
|
@@ -1,80 +0,0 @@
|
|||||||
@file:Suppress(
|
|
||||||
"RemoveRedundantCallsOfConversionMethods",
|
|
||||||
"RedundantVisibilityModifier",
|
|
||||||
)
|
|
||||||
|
|
||||||
package dev.inmo.micro_utils.common
|
|
||||||
|
|
||||||
import kotlin.Byte
|
|
||||||
import kotlin.Double
|
|
||||||
import kotlin.Float
|
|
||||||
import kotlin.Int
|
|
||||||
import kotlin.Long
|
|
||||||
import kotlin.Short
|
|
||||||
import kotlin.Suppress
|
|
||||||
|
|
||||||
public operator fun Percentage.plus(other: Percentage): Percentage = Percentage(of1 + other.of1)
|
|
||||||
|
|
||||||
public operator fun Percentage.minus(other: Percentage): Percentage = Percentage(of1 - other.of1)
|
|
||||||
|
|
||||||
public operator fun Percentage.plus(i: Byte): Percentage = Percentage((of1 + i).toDouble())
|
|
||||||
|
|
||||||
public operator fun Percentage.minus(i: Byte): Percentage = Percentage((of1 - i).toDouble())
|
|
||||||
|
|
||||||
public operator fun Percentage.times(i: Byte): Percentage = Percentage((of1 * i).toDouble())
|
|
||||||
|
|
||||||
public operator fun Percentage.div(i: Byte): Percentage = Percentage((of1 / i).toDouble())
|
|
||||||
|
|
||||||
public operator fun Percentage.rem(i: Byte): Percentage = Percentage((of1 % i).toDouble())
|
|
||||||
|
|
||||||
public operator fun Percentage.plus(i: Short): Percentage = Percentage((of1 + i).toDouble())
|
|
||||||
|
|
||||||
public operator fun Percentage.minus(i: Short): Percentage = Percentage((of1 - i).toDouble())
|
|
||||||
|
|
||||||
public operator fun Percentage.times(i: Short): Percentage = Percentage((of1 * i).toDouble())
|
|
||||||
|
|
||||||
public operator fun Percentage.div(i: Short): Percentage = Percentage((of1 / i).toDouble())
|
|
||||||
|
|
||||||
public operator fun Percentage.rem(i: Short): Percentage = Percentage((of1 % i).toDouble())
|
|
||||||
|
|
||||||
public operator fun Percentage.plus(i: Int): Percentage = Percentage((of1 + i).toDouble())
|
|
||||||
|
|
||||||
public operator fun Percentage.minus(i: Int): Percentage = Percentage((of1 - i).toDouble())
|
|
||||||
|
|
||||||
public operator fun Percentage.times(i: Int): Percentage = Percentage((of1 * i).toDouble())
|
|
||||||
|
|
||||||
public operator fun Percentage.div(i: Int): Percentage = Percentage((of1 / i).toDouble())
|
|
||||||
|
|
||||||
public operator fun Percentage.rem(i: Int): Percentage = Percentage((of1 % i).toDouble())
|
|
||||||
|
|
||||||
public operator fun Percentage.plus(i: Long): Percentage = Percentage((of1 + i).toDouble())
|
|
||||||
|
|
||||||
public operator fun Percentage.minus(i: Long): Percentage = Percentage((of1 - i).toDouble())
|
|
||||||
|
|
||||||
public operator fun Percentage.times(i: Long): Percentage = Percentage((of1 * i).toDouble())
|
|
||||||
|
|
||||||
public operator fun Percentage.div(i: Long): Percentage = Percentage((of1 / i).toDouble())
|
|
||||||
|
|
||||||
public operator fun Percentage.rem(i: Long): Percentage = Percentage((of1 % i).toDouble())
|
|
||||||
|
|
||||||
public operator fun Percentage.plus(i: Float): Percentage = Percentage((of1 + i).toDouble())
|
|
||||||
|
|
||||||
public operator fun Percentage.minus(i: Float): Percentage = Percentage((of1 - i).toDouble())
|
|
||||||
|
|
||||||
public operator fun Percentage.times(i: Float): Percentage = Percentage((of1 * i).toDouble())
|
|
||||||
|
|
||||||
public operator fun Percentage.div(i: Float): Percentage = Percentage((of1 / i).toDouble())
|
|
||||||
|
|
||||||
public operator fun Percentage.rem(i: Float): Percentage = Percentage((of1 % i).toDouble())
|
|
||||||
|
|
||||||
public operator fun Percentage.plus(i: Double): Percentage = Percentage((of1 + i).toDouble())
|
|
||||||
|
|
||||||
public operator fun Percentage.minus(i: Double): Percentage = Percentage((of1 - i).toDouble())
|
|
||||||
|
|
||||||
public operator fun Percentage.times(i: Double): Percentage = Percentage((of1 * i).toDouble())
|
|
||||||
|
|
||||||
public operator fun Percentage.div(i: Double): Percentage = Percentage((of1 / i).toDouble())
|
|
||||||
|
|
||||||
public operator fun Percentage.rem(i: Double): Percentage = Percentage((of1 % i).toDouble())
|
|
||||||
|
|
||||||
public operator fun Percentage.compareTo(other: Percentage): Int = (of1.compareTo(other.of1))
|
|
@@ -1,28 +0,0 @@
|
|||||||
package dev.inmo.micro_utils.common
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert [this] [Long] to [Int] with bounds of [Int.MIN_VALUE] and [Int.MAX_VALUE]
|
|
||||||
*/
|
|
||||||
fun Long.toCoercedInt(): Int = coerceIn(Int.MIN_VALUE.toLong(), Int.MAX_VALUE.toLong()).toInt()
|
|
||||||
/**
|
|
||||||
* Convert [this] [Long] to [Short] with bounds of [Short.MIN_VALUE] and [Short.MAX_VALUE]
|
|
||||||
*/
|
|
||||||
fun Long.toCoercedShort(): Short = coerceIn(Short.MIN_VALUE.toLong(), Short.MAX_VALUE.toLong()).toShort()
|
|
||||||
/**
|
|
||||||
* Convert [this] [Long] to [Byte] with bounds of [Byte.MIN_VALUE] and [Byte.MAX_VALUE]
|
|
||||||
*/
|
|
||||||
fun Long.toCoercedByte(): Byte = coerceIn(Byte.MIN_VALUE.toLong(), Byte.MAX_VALUE.toLong()).toByte()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert [this] [Int] to [Short] with bounds of [Short.MIN_VALUE] and [Short.MAX_VALUE]
|
|
||||||
*/
|
|
||||||
fun Int.toCoercedShort(): Short = coerceIn(Short.MIN_VALUE.toInt(), Short.MAX_VALUE.toInt()).toShort()
|
|
||||||
/**
|
|
||||||
* Convert [this] [Int] to [Byte] with bounds of [Byte.MIN_VALUE] and [Byte.MAX_VALUE]
|
|
||||||
*/
|
|
||||||
fun Int.toCoercedByte(): Byte = coerceIn(Byte.MIN_VALUE.toInt(), Byte.MAX_VALUE.toInt()).toByte()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert [this] [Short] to [Byte] with bounds of [Byte.MIN_VALUE] and [Byte.MAX_VALUE]
|
|
||||||
*/
|
|
||||||
fun Short.toCoercedByte(): Byte = coerceIn(Byte.MIN_VALUE.toShort(), Byte.MAX_VALUE.toShort()).toByte()
|
|
@@ -0,0 +1,37 @@
|
|||||||
|
package dev.inmo.micro_utils.common
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlin.jvm.JvmInline
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
@JvmInline
|
||||||
|
value class Progress private constructor(
|
||||||
|
val of1: Double
|
||||||
|
) {
|
||||||
|
val of1Float
|
||||||
|
get() = of1.toFloat()
|
||||||
|
val of100
|
||||||
|
get() = of1 * 100
|
||||||
|
val of100Float
|
||||||
|
get() = of100.toFloat()
|
||||||
|
val of100Int
|
||||||
|
get() = of100.toInt()
|
||||||
|
|
||||||
|
init {
|
||||||
|
require(of1 in rangeOfValues) {
|
||||||
|
"Progress main value should be in $rangeOfValues, but incoming value is $of1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val rangeOfValues = 0.0 .. 1.0
|
||||||
|
|
||||||
|
val START = Progress(rangeOfValues.start)
|
||||||
|
val COMPLETED = Progress(rangeOfValues.endInclusive)
|
||||||
|
|
||||||
|
operator fun invoke(of1: Double) = Progress(of1.coerceIn(rangeOfValues))
|
||||||
|
operator fun invoke(part: Number, total: Number) = Progress(
|
||||||
|
part.toDouble() / total.toDouble()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,80 @@
|
|||||||
|
@file:Suppress(
|
||||||
|
"RemoveRedundantCallsOfConversionMethods",
|
||||||
|
"RedundantVisibilityModifier",
|
||||||
|
)
|
||||||
|
|
||||||
|
package dev.inmo.micro_utils.common
|
||||||
|
|
||||||
|
import kotlin.Byte
|
||||||
|
import kotlin.Double
|
||||||
|
import kotlin.Float
|
||||||
|
import kotlin.Int
|
||||||
|
import kotlin.Long
|
||||||
|
import kotlin.Short
|
||||||
|
import kotlin.Suppress
|
||||||
|
|
||||||
|
public operator fun Progress.plus(other: Progress): Progress = Progress(of1 + other.of1)
|
||||||
|
|
||||||
|
public operator fun Progress.minus(other: Progress): Progress = Progress(of1 - other.of1)
|
||||||
|
|
||||||
|
public operator fun Progress.plus(i: Byte): Progress = Progress((of1 + i).toDouble())
|
||||||
|
|
||||||
|
public operator fun Progress.minus(i: Byte): Progress = Progress((of1 - i).toDouble())
|
||||||
|
|
||||||
|
public operator fun Progress.times(i: Byte): Progress = Progress((of1 * i).toDouble())
|
||||||
|
|
||||||
|
public operator fun Progress.div(i: Byte): Progress = Progress((of1 / i).toDouble())
|
||||||
|
|
||||||
|
public operator fun Progress.rem(i: Byte): Progress = Progress((of1 % i).toDouble())
|
||||||
|
|
||||||
|
public operator fun Progress.plus(i: Short): Progress = Progress((of1 + i).toDouble())
|
||||||
|
|
||||||
|
public operator fun Progress.minus(i: Short): Progress = Progress((of1 - i).toDouble())
|
||||||
|
|
||||||
|
public operator fun Progress.times(i: Short): Progress = Progress((of1 * i).toDouble())
|
||||||
|
|
||||||
|
public operator fun Progress.div(i: Short): Progress = Progress((of1 / i).toDouble())
|
||||||
|
|
||||||
|
public operator fun Progress.rem(i: Short): Progress = Progress((of1 % i).toDouble())
|
||||||
|
|
||||||
|
public operator fun Progress.plus(i: Int): Progress = Progress((of1 + i).toDouble())
|
||||||
|
|
||||||
|
public operator fun Progress.minus(i: Int): Progress = Progress((of1 - i).toDouble())
|
||||||
|
|
||||||
|
public operator fun Progress.times(i: Int): Progress = Progress((of1 * i).toDouble())
|
||||||
|
|
||||||
|
public operator fun Progress.div(i: Int): Progress = Progress((of1 / i).toDouble())
|
||||||
|
|
||||||
|
public operator fun Progress.rem(i: Int): Progress = Progress((of1 % i).toDouble())
|
||||||
|
|
||||||
|
public operator fun Progress.plus(i: Long): Progress = Progress((of1 + i).toDouble())
|
||||||
|
|
||||||
|
public operator fun Progress.minus(i: Long): Progress = Progress((of1 - i).toDouble())
|
||||||
|
|
||||||
|
public operator fun Progress.times(i: Long): Progress = Progress((of1 * i).toDouble())
|
||||||
|
|
||||||
|
public operator fun Progress.div(i: Long): Progress = Progress((of1 / i).toDouble())
|
||||||
|
|
||||||
|
public operator fun Progress.rem(i: Long): Progress = Progress((of1 % i).toDouble())
|
||||||
|
|
||||||
|
public operator fun Progress.plus(i: Float): Progress = Progress((of1 + i).toDouble())
|
||||||
|
|
||||||
|
public operator fun Progress.minus(i: Float): Progress = Progress((of1 - i).toDouble())
|
||||||
|
|
||||||
|
public operator fun Progress.times(i: Float): Progress = Progress((of1 * i).toDouble())
|
||||||
|
|
||||||
|
public operator fun Progress.div(i: Float): Progress = Progress((of1 / i).toDouble())
|
||||||
|
|
||||||
|
public operator fun Progress.rem(i: Float): Progress = Progress((of1 % i).toDouble())
|
||||||
|
|
||||||
|
public operator fun Progress.plus(i: Double): Progress = Progress((of1 + i).toDouble())
|
||||||
|
|
||||||
|
public operator fun Progress.minus(i: Double): Progress = Progress((of1 - i).toDouble())
|
||||||
|
|
||||||
|
public operator fun Progress.times(i: Double): Progress = Progress((of1 * i).toDouble())
|
||||||
|
|
||||||
|
public operator fun Progress.div(i: Double): Progress = Progress((of1 / i).toDouble())
|
||||||
|
|
||||||
|
public operator fun Progress.rem(i: Double): Progress = Progress((of1 % i).toDouble())
|
||||||
|
|
||||||
|
public operator fun Progress.compareTo(other: Progress): Int = (of1 - other.of1).toInt()
|
@@ -1,27 +1,5 @@
|
|||||||
package dev.inmo.micro_utils.common
|
package dev.inmo.micro_utils.common
|
||||||
|
|
||||||
/**
|
|
||||||
* Executes the given [action] until getting of successful result specified number of [times].
|
|
||||||
*
|
|
||||||
* A zero-based index of current iteration is passed as a parameter to [action].
|
|
||||||
*/
|
|
||||||
inline fun <R> repeatOnFailure(
|
|
||||||
onFailure: (Throwable) -> Boolean,
|
|
||||||
action: () -> R
|
|
||||||
): Result<R> {
|
|
||||||
do {
|
|
||||||
runCatching {
|
|
||||||
action()
|
|
||||||
}.onFailure {
|
|
||||||
if (!onFailure(it)) {
|
|
||||||
return Result.failure(it)
|
|
||||||
}
|
|
||||||
}.onSuccess {
|
|
||||||
return Result.success(it)
|
|
||||||
}
|
|
||||||
} while (true)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executes the given [action] until getting of successful result specified number of [times].
|
* Executes the given [action] until getting of successful result specified number of [times].
|
||||||
*
|
*
|
||||||
@@ -32,23 +10,12 @@ inline fun <R> repeatOnFailure(
|
|||||||
onEachFailure: (Throwable) -> Unit = {},
|
onEachFailure: (Throwable) -> Unit = {},
|
||||||
action: (Int) -> R
|
action: (Int) -> R
|
||||||
): Optional<R> {
|
): Optional<R> {
|
||||||
var i = 0
|
repeat(times) {
|
||||||
val result = repeatOnFailure(
|
runCatching {
|
||||||
{
|
action(it)
|
||||||
onEachFailure(it)
|
}.onFailure(onEachFailure).onSuccess {
|
||||||
if (i < times) {
|
return Optional.presented(it)
|
||||||
i++
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
) {
|
|
||||||
action(i)
|
|
||||||
}
|
|
||||||
return if (result.isSuccess) {
|
|
||||||
Optional.presented(result.getOrThrow())
|
|
||||||
} else {
|
|
||||||
Optional.absent()
|
|
||||||
}
|
}
|
||||||
|
return Optional.absent()
|
||||||
}
|
}
|
||||||
|
@@ -1,26 +0,0 @@
|
|||||||
package dev.inmo.micro_utils.common
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Will try to execute [action] and, if any exception will happen, execution will be retried.
|
|
||||||
* This process will happen at most [count] times. There is no any limits on [count] value, but [action] will run at
|
|
||||||
* least once and [retryOnFailure] will return its result if it is successful
|
|
||||||
*/
|
|
||||||
inline fun <T> retryOnFailure(count: Int, action: () -> T): T {
|
|
||||||
var triesCount = 0
|
|
||||||
while (true) {
|
|
||||||
val result = runCatching {
|
|
||||||
action()
|
|
||||||
}.onFailure {
|
|
||||||
triesCount++
|
|
||||||
|
|
||||||
if (triesCount >= count) {
|
|
||||||
throw it
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.isSuccess) return result.getOrThrow()
|
|
||||||
}
|
|
||||||
error("Unreachable code: retry must throw latest exception if error happen or success value if not")
|
|
||||||
}
|
|
@@ -1,6 +0,0 @@
|
|||||||
package dev.inmo.micro_utils.common
|
|
||||||
|
|
||||||
val FixedSignsRange = 0 .. 100
|
|
||||||
|
|
||||||
expect fun Float.fixed(signs: Int): Float
|
|
||||||
expect fun Double.fixed(signs: Int): Double
|
|
@@ -1,5 +0,0 @@
|
|||||||
package dev.inmo.micro_utils.common
|
|
||||||
|
|
||||||
fun <T> Iterable<T>.withReplacedAt(i: Int, block: (T) -> T): List<T> = take(i) + block(elementAt(i)) + drop(i + 1)
|
|
||||||
fun <T> Iterable<T>.withReplaced(t: T, block: (T) -> T): List<T> = withReplacedAt(indexOf(t), block)
|
|
||||||
|
|
@@ -1,29 +0,0 @@
|
|||||||
package dev.inmo.micro_utils.common
|
|
||||||
|
|
||||||
import kotlin.test.Test
|
|
||||||
import kotlin.test.assertEquals
|
|
||||||
|
|
||||||
class PercentageTests {
|
|
||||||
@Test
|
|
||||||
fun testCompareTo() {
|
|
||||||
val step = 0.01
|
|
||||||
|
|
||||||
var i = Percentage.START.of1
|
|
||||||
while (i <= Percentage.COMPLETED.of1) {
|
|
||||||
val percentageI = Percentage(i)
|
|
||||||
|
|
||||||
var j = Percentage.START.of1
|
|
||||||
while (j <= Percentage.COMPLETED.of1) {
|
|
||||||
val percentageJ = Percentage(j)
|
|
||||||
|
|
||||||
assertEquals(percentageI.of1.compareTo(percentageJ.of1), percentageI.compareTo(percentageJ))
|
|
||||||
assertEquals(percentageI.of1 > percentageJ.of1, percentageI > percentageJ)
|
|
||||||
assertEquals(percentageI.of1 < percentageJ.of1, percentageI < percentageJ)
|
|
||||||
|
|
||||||
j += step
|
|
||||||
}
|
|
||||||
|
|
||||||
i += step
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,21 +0,0 @@
|
|||||||
package dev.inmo.micro_utils.common
|
|
||||||
|
|
||||||
import kotlin.test.Test
|
|
||||||
import kotlin.test.assertEquals
|
|
||||||
|
|
||||||
class WithReplacedTest {
|
|
||||||
@Test
|
|
||||||
fun testReplaced() {
|
|
||||||
val data = 0 until 10
|
|
||||||
val testData = Int.MAX_VALUE
|
|
||||||
|
|
||||||
for (i in 0 until data.last) {
|
|
||||||
val withReplaced = data.withReplacedAt(i) {
|
|
||||||
testData
|
|
||||||
}
|
|
||||||
val dataAsMutableList = data.toMutableList()
|
|
||||||
dataAsMutableList[i] = testData
|
|
||||||
assertEquals(withReplaced, dataAsMutableList.toList())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,13 +0,0 @@
|
|||||||
package dev.inmo.micro_utils.common
|
|
||||||
|
|
||||||
import kotlinx.browser.window
|
|
||||||
|
|
||||||
fun copyToClipboard(text: String): Boolean {
|
|
||||||
return runCatching {
|
|
||||||
window.navigator.clipboard.writeText(
|
|
||||||
text
|
|
||||||
)
|
|
||||||
}.onFailure {
|
|
||||||
it.printStackTrace()
|
|
||||||
}.isSuccess
|
|
||||||
}
|
|
@@ -1,29 +0,0 @@
|
|||||||
package dev.inmo.micro_utils.common
|
|
||||||
|
|
||||||
import kotlinx.browser.window
|
|
||||||
import org.w3c.files.Blob
|
|
||||||
import org.w3c.files.BlobPropertyBag
|
|
||||||
import kotlin.js.json
|
|
||||||
|
|
||||||
external class ClipboardItem(data: dynamic)
|
|
||||||
|
|
||||||
inline fun Blob.convertToClipboardItem(): ClipboardItem {
|
|
||||||
val itemData: dynamic = json(this.type to this)
|
|
||||||
return ClipboardItem(itemData)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun copyImageURLToClipboard(imageUrl: String): Boolean {
|
|
||||||
return runCatching {
|
|
||||||
val response = window.fetch(imageUrl).await()
|
|
||||||
val blob = response.blob().await()
|
|
||||||
val data = arrayOf(
|
|
||||||
Blob(
|
|
||||||
arrayOf(blob),
|
|
||||||
BlobPropertyBag("image/png")
|
|
||||||
).convertToClipboardItem()
|
|
||||||
).asDynamic()
|
|
||||||
window.navigator.clipboard.write(data)
|
|
||||||
}.onFailure {
|
|
||||||
it.printStackTrace()
|
|
||||||
}.isSuccess
|
|
||||||
}
|
|
@@ -1,4 +0,0 @@
|
|||||||
package dev.inmo.micro_utils.common
|
|
||||||
|
|
||||||
actual fun Float.fixed(signs: Int): Float = this.asDynamic().toFixed(signs.coerceIn(FixedSignsRange)).unsafeCast<String>().toFloat()
|
|
||||||
actual fun Double.fixed(signs: Int): Double = this.asDynamic().toFixed(signs.coerceIn(FixedSignsRange)).unsafeCast<String>().toDouble()
|
|
@@ -1,12 +0,0 @@
|
|||||||
package dev.inmo.micro_utils.common
|
|
||||||
|
|
||||||
import java.math.BigDecimal
|
|
||||||
import java.math.RoundingMode
|
|
||||||
|
|
||||||
actual fun Float.fixed(signs: Int): Float = BigDecimal.valueOf(this.toDouble())
|
|
||||||
.setScale(signs.coerceIn(FixedSignsRange), RoundingMode.HALF_UP)
|
|
||||||
.toFloat();
|
|
||||||
|
|
||||||
actual fun Double.fixed(signs: Int): Double = BigDecimal.valueOf(this)
|
|
||||||
.setScale(signs.coerceIn(FixedSignsRange), RoundingMode.HALF_UP)
|
|
||||||
.toDouble();
|
|
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"/>
|
@@ -1,36 +0,0 @@
|
|||||||
package dev.inmo.micro_utils.common
|
|
||||||
|
|
||||||
import okio.FileSystem
|
|
||||||
import okio.Path
|
|
||||||
import okio.use
|
|
||||||
|
|
||||||
actual typealias MPPFile = Path
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @suppress
|
|
||||||
*/
|
|
||||||
actual val MPPFile.filename: FileName
|
|
||||||
get() = FileName(toString())
|
|
||||||
/**
|
|
||||||
* @suppress
|
|
||||||
*/
|
|
||||||
actual val MPPFile.filesize: Long
|
|
||||||
get() = FileSystem.SYSTEM.openReadOnly(this).use {
|
|
||||||
it.size()
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @suppress
|
|
||||||
*/
|
|
||||||
actual val MPPFile.bytesAllocatorSync: ByteArrayAllocator
|
|
||||||
get() = {
|
|
||||||
FileSystem.SYSTEM.read(this) {
|
|
||||||
readByteArray()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @suppress
|
|
||||||
*/
|
|
||||||
actual val MPPFile.bytesAllocator: SuspendByteArrayAllocator
|
|
||||||
get() = {
|
|
||||||
bytesAllocatorSync()
|
|
||||||
}
|
|
@@ -1,25 +0,0 @@
|
|||||||
package dev.inmo.micro_utils.common
|
|
||||||
|
|
||||||
import kotlinx.cinterop.*
|
|
||||||
import platform.posix.snprintf
|
|
||||||
import platform.posix.sprintf
|
|
||||||
|
|
||||||
@OptIn(ExperimentalForeignApi::class)
|
|
||||||
actual fun Float.fixed(signs: Int): Float {
|
|
||||||
return memScoped {
|
|
||||||
val buff = allocArray<ByteVar>(Float.SIZE_BYTES * 2)
|
|
||||||
|
|
||||||
sprintf(buff, "%.${signs}f", this@fixed)
|
|
||||||
buff.toKString().toFloat()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalForeignApi::class)
|
|
||||||
actual fun Double.fixed(signs: Int): Double {
|
|
||||||
return memScoped {
|
|
||||||
val buff = allocArray<ByteVar>(Double.SIZE_BYTES * 2)
|
|
||||||
|
|
||||||
sprintf(buff, "%.${signs}f", this@fixed)
|
|
||||||
buff.toKString().toDouble()
|
|
||||||
}
|
|
||||||
}
|
|
@@ -4,14 +4,13 @@ plugins {
|
|||||||
id "com.android.library"
|
id "com.android.library"
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64Project"
|
apply from: "$mppProjectWithSerializationPresetPath"
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
sourceSets {
|
sourceSets {
|
||||||
commonMain {
|
commonMain {
|
||||||
dependencies {
|
dependencies {
|
||||||
api libs.kt.coroutines
|
api libs.kt.coroutines
|
||||||
api libs.kslog
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
jsMain {
|
jsMain {
|
||||||
@@ -23,6 +22,7 @@ kotlin {
|
|||||||
dependencies {
|
dependencies {
|
||||||
api libs.kt.coroutines.android
|
api libs.kt.coroutines.android
|
||||||
}
|
}
|
||||||
|
dependsOn(jvmMain)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -3,10 +3,9 @@ plugins {
|
|||||||
id "org.jetbrains.kotlin.plugin.serialization"
|
id "org.jetbrains.kotlin.plugin.serialization"
|
||||||
id "com.android.library"
|
id "com.android.library"
|
||||||
alias(libs.plugins.jb.compose)
|
alias(libs.plugins.jb.compose)
|
||||||
alias(libs.plugins.kt.jb.compose)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$mppComposeJvmJsAndroidLinuxMingwLinuxArm64Project"
|
apply from: "$mppProjectWithSerializationAndComposePresetPath"
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
sourceSets {
|
sourceSets {
|
||||||
|
@@ -3,58 +3,24 @@ package dev.inmo.micro_utils.coroutines.compose
|
|||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.runtime.snapshots.SnapshotStateList
|
import androidx.compose.runtime.snapshots.SnapshotStateList
|
||||||
import dev.inmo.micro_utils.common.applyDiff
|
import dev.inmo.micro_utils.common.applyDiff
|
||||||
import dev.inmo.micro_utils.coroutines.ExceptionHandler
|
|
||||||
import dev.inmo.micro_utils.coroutines.defaultSafelyWithoutExceptionHandlerWithNull
|
|
||||||
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import kotlin.coroutines.CoroutineContext
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Each value of [this] [Flow] will trigger [applyDiff] to the result [SnapshotStateList]
|
|
||||||
*
|
|
||||||
* @param scope Will be used to [subscribeSafelyWithoutExceptions] on [this] to update returned [SnapshotStateList]
|
|
||||||
* @param useContextOnChange Will be used to change context inside of [subscribeSafelyWithoutExceptions] to ensure that
|
|
||||||
* change will happen in the required [CoroutineContext]. [Dispatchers.Main] by default
|
|
||||||
* @param onException Will be passed to the [subscribeSafelyWithoutExceptions] as uncaught exceptions handler
|
|
||||||
*/
|
|
||||||
@Suppress("NOTHING_TO_INLINE")
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
inline fun <reified T> Flow<List<T>>.asMutableComposeListState(
|
inline fun <reified T> Flow<List<T>>.asMutableComposeListState(
|
||||||
scope: CoroutineScope,
|
scope: CoroutineScope
|
||||||
useContextOnChange: CoroutineContext? = Dispatchers.Main,
|
|
||||||
noinline onException: ExceptionHandler<List<T>?> = defaultSafelyWithoutExceptionHandlerWithNull,
|
|
||||||
): SnapshotStateList<T> {
|
): SnapshotStateList<T> {
|
||||||
val state = mutableStateListOf<T>()
|
val state = mutableStateListOf<T>()
|
||||||
val changeBlock: suspend (List<T>) -> Unit = useContextOnChange ?.let {
|
subscribeSafelyWithoutExceptions(scope) {
|
||||||
{
|
|
||||||
withContext(useContextOnChange) {
|
|
||||||
state.applyDiff(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} ?: {
|
|
||||||
state.applyDiff(it)
|
state.applyDiff(it)
|
||||||
}
|
}
|
||||||
subscribeSafelyWithoutExceptions(scope, onException, changeBlock)
|
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* In fact, it is just classcast of [asMutableComposeListState] to [List]
|
|
||||||
*
|
|
||||||
* @param scope Will be used to [subscribeSafelyWithoutExceptions] on [this] to update returned [List]
|
|
||||||
* @param useContextOnChange Will be used to change context inside of [subscribeSafelyWithoutExceptions] to ensure that
|
|
||||||
* change will happen in the required [CoroutineContext]. [Dispatchers.Main] by default
|
|
||||||
* @param onException Will be passed to the [subscribeSafelyWithoutExceptions] as uncaught exceptions handler
|
|
||||||
*
|
|
||||||
* @return Changing in time [List] which follow [Flow] values
|
|
||||||
*/
|
|
||||||
@Suppress("NOTHING_TO_INLINE")
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
inline fun <reified T> Flow<List<T>>.asComposeList(
|
inline fun <reified T> Flow<List<T>>.asComposeList(
|
||||||
scope: CoroutineScope,
|
scope: CoroutineScope
|
||||||
useContextOnChange: CoroutineContext? = Dispatchers.Main,
|
): List<T> = asMutableComposeListState(scope)
|
||||||
noinline onException: ExceptionHandler<List<T>?> = defaultSafelyWithoutExceptionHandlerWithNull,
|
|
||||||
): List<T> = asMutableComposeListState(scope, useContextOnChange, onException)
|
|
||||||
|
|
||||||
|
@@ -1,94 +1,35 @@
|
|||||||
package dev.inmo.micro_utils.coroutines.compose
|
package dev.inmo.micro_utils.coroutines.compose
|
||||||
|
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import dev.inmo.micro_utils.common.compose.asState
|
|
||||||
import dev.inmo.micro_utils.coroutines.ExceptionHandler
|
|
||||||
import dev.inmo.micro_utils.coroutines.defaultSafelyWithoutExceptionHandlerWithNull
|
|
||||||
import dev.inmo.micro_utils.coroutines.doInUI
|
|
||||||
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import kotlin.coroutines.CoroutineContext
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Will map [this] [Flow] as [MutableState]. Returned [MutableState] WILL NOT change source [Flow]
|
|
||||||
*
|
|
||||||
* @param initial First value which will be passed to the result [MutableState]
|
|
||||||
* @param scope Will be used to [subscribeSafelyWithoutExceptions] on [this] to update returned [MutableState]
|
|
||||||
* @param useContextOnChange Will be used to change context inside of [subscribeSafelyWithoutExceptions] to ensure that
|
|
||||||
* change will happen in the required [CoroutineContext]. [Dispatchers.Main] by default
|
|
||||||
* @param onException Will be passed to the [subscribeSafelyWithoutExceptions] as uncaught exceptions handler
|
|
||||||
*/
|
|
||||||
fun <T> Flow<T>.asMutableComposeState(
|
fun <T> Flow<T>.asMutableComposeState(
|
||||||
initial: T,
|
initial: T,
|
||||||
scope: CoroutineScope,
|
scope: CoroutineScope
|
||||||
useContextOnChange: CoroutineContext? = Dispatchers.Main,
|
|
||||||
onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
|
|
||||||
): MutableState<T> {
|
): MutableState<T> {
|
||||||
val state = mutableStateOf(initial)
|
val state = mutableStateOf(initial)
|
||||||
val changeBlock: suspend (T) -> Unit = useContextOnChange ?.let {
|
subscribeSafelyWithoutExceptions(scope) { state.value = it }
|
||||||
{
|
|
||||||
withContext(useContextOnChange) {
|
|
||||||
state.value = it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} ?: {
|
|
||||||
state.value = it
|
|
||||||
}
|
|
||||||
subscribeSafelyWithoutExceptions(scope, onException, block = changeBlock)
|
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Will map [this] [StateFlow] as [MutableState]. Returned [MutableState] WILL NOT change source [StateFlow].
|
|
||||||
* This conversation will pass its [StateFlow.value] as the first value
|
|
||||||
*
|
|
||||||
* @param scope Will be used to [subscribeSafelyWithoutExceptions] on [this] to update returned [MutableState]
|
|
||||||
* @param useContextOnChange Will be used to change context inside of [subscribeSafelyWithoutExceptions] to ensure that
|
|
||||||
* change will happen in the required [CoroutineContext]. [Dispatchers.Main] by default
|
|
||||||
* @param onException Will be passed to the [subscribeSafelyWithoutExceptions] as uncaught exceptions handler
|
|
||||||
*/
|
|
||||||
@Suppress("NOTHING_TO_INLINE")
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
inline fun <T> StateFlow<T>.asMutableComposeState(
|
inline fun <T> StateFlow<T>.asMutableComposeState(
|
||||||
scope: CoroutineScope,
|
scope: CoroutineScope
|
||||||
useContextOnChange: CoroutineContext? = Dispatchers.Main,
|
): MutableState<T> = asMutableComposeState(value, scope)
|
||||||
noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
|
|
||||||
): MutableState<T> = asMutableComposeState(value, scope, useContextOnChange, onException)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Will create [MutableState] using [asMutableComposeState] and use [asState] to convert it as immutable state
|
|
||||||
*
|
|
||||||
* @param initial First value which will be passed to the result [State]
|
|
||||||
* @param scope Will be used to [subscribeSafelyWithoutExceptions] on [this] to update returned [State]
|
|
||||||
* @param useContextOnChange Will be used to change context inside of [subscribeSafelyWithoutExceptions] to ensure that
|
|
||||||
* change will happen in the required [CoroutineContext]. [Dispatchers.Main] by default
|
|
||||||
* @param onException Will be passed to the [subscribeSafelyWithoutExceptions] as uncaught exceptions handler
|
|
||||||
*/
|
|
||||||
fun <T> Flow<T>.asComposeState(
|
fun <T> Flow<T>.asComposeState(
|
||||||
initial: T,
|
initial: T,
|
||||||
scope: CoroutineScope,
|
scope: CoroutineScope
|
||||||
useContextOnChange: CoroutineContext? = Dispatchers.Main,
|
|
||||||
onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
|
|
||||||
): State<T> {
|
): State<T> {
|
||||||
val state = asMutableComposeState(initial, scope, useContextOnChange, onException)
|
val state = asMutableComposeState(initial, scope)
|
||||||
return state.asState()
|
return derivedStateOf { state.value }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Will map [this] [StateFlow] as [State]. This conversation will pass its [StateFlow.value] as the first value
|
|
||||||
*
|
|
||||||
* @param scope Will be used to [subscribeSafelyWithoutExceptions] on [this] to update returned [State]
|
|
||||||
* @param useContextOnChange Will be used to change context inside of [subscribeSafelyWithoutExceptions] to ensure that
|
|
||||||
* change will happen in the required [CoroutineContext]. [Dispatchers.Main] by default
|
|
||||||
* @param onException Will be passed to the [subscribeSafelyWithoutExceptions] as uncaught exceptions handler
|
|
||||||
*/
|
|
||||||
@Suppress("NOTHING_TO_INLINE")
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
inline fun <T> StateFlow<T>.asComposeState(
|
inline fun <T> StateFlow<T>.asComposeState(
|
||||||
scope: CoroutineScope,
|
scope: CoroutineScope
|
||||||
useContextOnChange: CoroutineContext? = Dispatchers.Main,
|
): State<T> = asComposeState(value, scope)
|
||||||
noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
|
|
||||||
): State<T> = asComposeState(value, scope, useContextOnChange, onException)
|
|
||||||
|
|
||||||
|
@@ -0,0 +1,23 @@
|
|||||||
|
package dev.inmo.micro_utils.coroutines.compose
|
||||||
|
|
||||||
|
import androidx.compose.runtime.MutableState
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
|
||||||
|
fun <T> Flow<T>.toMutableState(
|
||||||
|
initial: T,
|
||||||
|
scope: CoroutineScope
|
||||||
|
): MutableState<T> {
|
||||||
|
val state = mutableStateOf(initial)
|
||||||
|
subscribeSafelyWithoutExceptions(scope) { state.value = it }
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
|
inline fun <T> StateFlow<T>.toMutableState(
|
||||||
|
scope: CoroutineScope
|
||||||
|
): MutableState<T> = toMutableState(value, scope)
|
||||||
|
|
@@ -1,66 +0,0 @@
|
|||||||
package dev.inmo.micro_utils.coroutines.compose
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.collectAsState
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import dev.inmo.micro_utils.coroutines.SpecialMutableStateFlow
|
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
|
||||||
import kotlinx.coroutines.flow.debounce
|
|
||||||
import org.jetbrains.compose.web.css.CSSRulesHolder
|
|
||||||
import org.jetbrains.compose.web.css.Style
|
|
||||||
import org.jetbrains.compose.web.css.StyleSheet
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Aggregator of Compose CSS StyleSheet. Allowing to add [StyleSheet] in it and draw it in one place without requiring
|
|
||||||
* to add `Style(stylesheet)` on every compose function call
|
|
||||||
*/
|
|
||||||
object StyleSheetsAggregator {
|
|
||||||
private val _stylesFlow = SpecialMutableStateFlow<Set<CSSRulesHolder>>(emptySet())
|
|
||||||
val stylesFlow: StateFlow<Set<CSSRulesHolder>> = _stylesFlow.asStateFlow()
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun draw() {
|
|
||||||
_stylesFlow.debounce(13L).collectAsState(emptySet()).value.forEach {
|
|
||||||
Style(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adding [styleSheet] into the [Set] of included stylesheets. If you called [enableStyleSheetsAggregator],
|
|
||||||
* new styles will be enabled in the document
|
|
||||||
*/
|
|
||||||
fun addStyleSheet(styleSheet: CSSRulesHolder) {
|
|
||||||
_stylesFlow.value += styleSheet
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removed [styleSheet] into the [Set] of included stylesheets
|
|
||||||
*/
|
|
||||||
fun removeStyleSheet(styleSheet: CSSRulesHolder) {
|
|
||||||
_stylesFlow.value -= styleSheet
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Drawing [StyleSheetsAggregator] in place. You may pass [Set] of [CSSRulesHolder]/[StyleSheet]s as preset of styles
|
|
||||||
*/
|
|
||||||
@Composable
|
|
||||||
fun enableStyleSheetsAggregator(
|
|
||||||
stylesPreset: Set<CSSRulesHolder> = emptySet(),
|
|
||||||
) {
|
|
||||||
remember {
|
|
||||||
stylesPreset.forEach {
|
|
||||||
StyleSheetsAggregator.addStyleSheet(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
StyleSheetsAggregator.draw()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Will include [this] [CSSRulesHolder]/[StyleSheet] in the [StyleSheetsAggregator] using its
|
|
||||||
* [StyleSheetsAggregator.addStyleSheet]
|
|
||||||
*/
|
|
||||||
fun CSSRulesHolder.includeInStyleSheetsAggregator() {
|
|
||||||
StyleSheetsAggregator.addStyleSheet(this)
|
|
||||||
}
|
|
@@ -1,24 +0,0 @@
|
|||||||
import androidx.compose.material.Button
|
|
||||||
import androidx.compose.material.Text
|
|
||||||
import androidx.compose.runtime.collectAsState
|
|
||||||
import androidx.compose.ui.test.*
|
|
||||||
import dev.inmo.micro_utils.coroutines.SpecialMutableStateFlow
|
|
||||||
import org.jetbrains.annotations.TestOnly
|
|
||||||
import kotlin.test.Test
|
|
||||||
|
|
||||||
class FlowStateTests {
|
|
||||||
@OptIn(ExperimentalTestApi::class)
|
|
||||||
@Test
|
|
||||||
@TestOnly
|
|
||||||
fun simpleTest() = runComposeUiTest {
|
|
||||||
val flowState = SpecialMutableStateFlow(0)
|
|
||||||
setContent {
|
|
||||||
Button({ flowState.value++ }) { Text("Click") }
|
|
||||||
Text(flowState.collectAsState().value.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
onNodeWithText(0.toString()).assertExists()
|
|
||||||
onNodeWithText("Click").performClick()
|
|
||||||
onNodeWithText(1.toString()).assertExists()
|
|
||||||
}
|
|
||||||
}
|
|
1
coroutines/compose/src/main/AndroidManifest.xml
Normal file
1
coroutines/compose/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<manifest package="dev.inmo.micro_utils.coroutines.compose"/>
|
@@ -1 +0,0 @@
|
|||||||
<manifest/>
|
|
@@ -68,9 +68,9 @@ class AccumulatorFlow<T>(
|
|||||||
override suspend fun collectSafely(collector: FlowCollector<T>) {
|
override suspend fun collectSafely(collector: FlowCollector<T>) {
|
||||||
val channel = Channel<T>(Channel.UNLIMITED, BufferOverflow.SUSPEND)
|
val channel = Channel<T>(Channel.UNLIMITED, BufferOverflow.SUSPEND)
|
||||||
steps.send(SubscribeAccumulatorFlowStep(channel))
|
steps.send(SubscribeAccumulatorFlowStep(channel))
|
||||||
val result = runCatching {
|
val result = runCatchingSafely {
|
||||||
for (data in channel) {
|
for (data in channel) {
|
||||||
val emitResult = runCatching {
|
val emitResult = runCatchingSafely {
|
||||||
collector.emit(data)
|
collector.emit(data)
|
||||||
}
|
}
|
||||||
if (emitResult.isSuccess || emitResult.exceptionOrNull() is CancellationException) {
|
if (emitResult.isSuccess || emitResult.exceptionOrNull() is CancellationException) {
|
||||||
|
@@ -3,12 +3,6 @@ package dev.inmo.micro_utils.coroutines
|
|||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import kotlin.coroutines.*
|
import kotlin.coroutines.*
|
||||||
|
|
||||||
/**
|
|
||||||
* Trying to [Deferred.await] on all [this] [Deferred]s. The first [Deferred] completed its work will interrupt all
|
|
||||||
* others awaits and, if [cancelOnResult] passed as true (**by default**), will also cancel all the others [Deferred]s
|
|
||||||
*
|
|
||||||
* @param scope Will be used to create [CoroutineScope.LinkedSupervisorScope] and launch joining of all [Job]s in it
|
|
||||||
*/
|
|
||||||
suspend fun <T> Iterable<Deferred<T>>.awaitFirstWithDeferred(
|
suspend fun <T> Iterable<Deferred<T>>.awaitFirstWithDeferred(
|
||||||
scope: CoroutineScope,
|
scope: CoroutineScope,
|
||||||
cancelOnResult: Boolean = true
|
cancelOnResult: Boolean = true
|
||||||
@@ -30,45 +24,10 @@ suspend fun <T> Iterable<Deferred<T>>.awaitFirstWithDeferred(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Trying to [Deferred.await] on all [this] [Deferred]s. The first [Deferred] completed its work will interrupt all
|
|
||||||
* others awaits and, if [cancelOnResult] passed as true (**by default**), will also cancel all the others [Deferred]s
|
|
||||||
*
|
|
||||||
* @param scope Will be used to create [CoroutineScope.LinkedSupervisorScope] and launch joining of all [Job]s in it
|
|
||||||
*/
|
|
||||||
suspend fun <T> Iterable<Deferred<T>>.awaitFirst(
|
suspend fun <T> Iterable<Deferred<T>>.awaitFirst(
|
||||||
scope: CoroutineScope,
|
scope: CoroutineScope,
|
||||||
cancelOnResult: Boolean = true
|
cancelOnResult: Boolean = true
|
||||||
): T = awaitFirstWithDeferred(scope, cancelOnResult).second
|
): T = awaitFirstWithDeferred(scope, cancelOnResult).second
|
||||||
|
|
||||||
/**
|
|
||||||
* Trying to [Deferred.await] on all [this] [Deferred]s. The first [Deferred] completed its work will interrupt all
|
|
||||||
* others awaits and, if [cancelOthers] passed as true (**by default**), will also cancel all the others [Deferred]s
|
|
||||||
*
|
|
||||||
* Creates [CoroutineScope] using [coroutineContext] for internal purposes
|
|
||||||
*/
|
|
||||||
suspend fun <T> Iterable<Deferred<T>>.awaitFirst(
|
suspend fun <T> Iterable<Deferred<T>>.awaitFirst(
|
||||||
cancelOthers: Boolean = true
|
cancelOthers: Boolean = true
|
||||||
): T = awaitFirst(CoroutineScope(coroutineContext), cancelOthers)
|
): T = awaitFirst(CoroutineScope(coroutineContext), cancelOthers)
|
||||||
|
|
||||||
/**
|
|
||||||
* Trying to [Deferred.await] on all [deferreds]. The first [Deferred] completed its work will interrupt all
|
|
||||||
* others awaits and, if [cancelOnResult] passed as true (**by default**), will also cancel all the others [deferreds]
|
|
||||||
*
|
|
||||||
* @param scope Will be used to create [CoroutineScope.LinkedSupervisorScope] and launch joining of all [Job]s in it
|
|
||||||
*/
|
|
||||||
suspend fun <T> awaitFirst(
|
|
||||||
vararg deferreds: Deferred<T>,
|
|
||||||
scope: CoroutineScope,
|
|
||||||
cancelOnResult: Boolean = true
|
|
||||||
): T = deferreds.toList().awaitFirstWithDeferred(scope, cancelOnResult).second
|
|
||||||
/**
|
|
||||||
* Trying to [Deferred.await] on all [deferreds]. The first [Deferred] completed its work will interrupt all
|
|
||||||
* others awaits and, if [cancelOthers] passed as true (**by default**), will also cancel all the others [deferreds]
|
|
||||||
*
|
|
||||||
* Creates [CoroutineScope] using [coroutineContext] for internal purposes
|
|
||||||
*/
|
|
||||||
suspend fun <T> awaitFirst(
|
|
||||||
vararg deferreds: Deferred<T>,
|
|
||||||
cancelOthers: Boolean = true
|
|
||||||
): T = awaitFirst(*deferreds, scope = CoroutineScope(coroutineContext), cancelOnResult = cancelOthers)
|
|
||||||
|
@@ -1,44 +0,0 @@
|
|||||||
package dev.inmo.micro_utils.coroutines
|
|
||||||
|
|
||||||
import kotlin.coroutines.CoroutineContext
|
|
||||||
import kotlin.coroutines.coroutineContext
|
|
||||||
|
|
||||||
typealias ExceptionHandler<T> = suspend (Throwable) -> T
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This instance will be used in all calls of [safely] where exception handler has not been passed
|
|
||||||
*/
|
|
||||||
var defaultSafelyExceptionHandler: ExceptionHandler<Nothing> = { throw it }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This instance will be used in all calls of [safelyWithoutExceptions] as an exception handler for [safely] call
|
|
||||||
*/
|
|
||||||
var defaultSafelyWithoutExceptionHandler: ExceptionHandler<Unit> = {
|
|
||||||
try {
|
|
||||||
defaultSafelyExceptionHandler(it)
|
|
||||||
} catch (e: Throwable) {
|
|
||||||
// do nothing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This key can (and will) be used to get [ContextSafelyExceptionHandler] from [coroutineContext] of suspend functions
|
|
||||||
* and in [ContextSafelyExceptionHandler] for defining of its [CoroutineContext.Element.key]
|
|
||||||
*
|
|
||||||
* @see safelyWithContextExceptionHandler
|
|
||||||
* @see ContextSafelyExceptionHandler
|
|
||||||
*/
|
|
||||||
object ContextSafelyExceptionHandlerKey : CoroutineContext.Key<ContextSafelyExceptionHandler>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* [ExceptionHandler] wrapper which was created to make possible to use [handler] across all coroutines calls
|
|
||||||
*
|
|
||||||
* @see safelyWithContextExceptionHandler
|
|
||||||
* @see ContextSafelyExceptionHandlerKey
|
|
||||||
*/
|
|
||||||
class ContextSafelyExceptionHandler(
|
|
||||||
val handler: ExceptionHandler<Unit>
|
|
||||||
) : CoroutineContext.Element {
|
|
||||||
override val key: CoroutineContext.Key<*>
|
|
||||||
get() = ContextSafelyExceptionHandlerKey
|
|
||||||
}
|
|
@@ -16,8 +16,8 @@ class DoWithFirstBuilder<T>(
|
|||||||
operator fun plus(block: suspend CoroutineScope.() -> T) {
|
operator fun plus(block: suspend CoroutineScope.() -> T) {
|
||||||
deferreds.add(scope.async(start = CoroutineStart.LAZY, block = block))
|
deferreds.add(scope.async(start = CoroutineStart.LAZY, block = block))
|
||||||
}
|
}
|
||||||
fun add(block: suspend CoroutineScope.() -> T) = plus(block)
|
inline fun add(noinline block: suspend CoroutineScope.() -> T) = plus(block)
|
||||||
fun include(block: suspend CoroutineScope.() -> T) = plus(block)
|
inline fun include(noinline block: suspend CoroutineScope.() -> T) = plus(block)
|
||||||
|
|
||||||
fun build() = deferreds.toList()
|
fun build() = deferreds.toList()
|
||||||
}
|
}
|
||||||
|
@@ -1,40 +0,0 @@
|
|||||||
package dev.inmo.micro_utils.coroutines
|
|
||||||
|
|
||||||
import kotlinx.coroutines.Job
|
|
||||||
import kotlinx.coroutines.async
|
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.flow.*
|
|
||||||
import kotlinx.coroutines.sync.Mutex
|
|
||||||
import kotlinx.coroutines.sync.withLock
|
|
||||||
import kotlin.jvm.JvmInline
|
|
||||||
import kotlin.time.Duration
|
|
||||||
|
|
||||||
@JvmInline
|
|
||||||
private value class DebouncedByData<T>(
|
|
||||||
val millisToData: Pair<Long, T>
|
|
||||||
)
|
|
||||||
|
|
||||||
fun <T> Flow<T>.debouncedBy(timeout: (T) -> Long, markerFactory: (T) -> Any?): Flow<T> = channelFlow {
|
|
||||||
val jobs = mutableMapOf<Any?, Job>()
|
|
||||||
val mutex = Mutex()
|
|
||||||
subscribe(this) {
|
|
||||||
mutex.withLock {
|
|
||||||
val marker = markerFactory(it)
|
|
||||||
lateinit var job: Job
|
|
||||||
job = async {
|
|
||||||
delay(timeout(it))
|
|
||||||
mutex.withLock {
|
|
||||||
if (jobs[marker] === job) {
|
|
||||||
this@channelFlow.send(it)
|
|
||||||
jobs.remove(marker)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
jobs[marker] ?.cancel()
|
|
||||||
jobs[marker] = job
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <T> Flow<T>.debouncedBy(timeout: Long, markerFactory: (T) -> Any?): Flow<T> = debouncedBy({ timeout }, markerFactory)
|
|
||||||
fun <T> Flow<T>.debouncedBy(timeout: Duration, markerFactory: (T) -> Any?): Flow<T> = debouncedBy({ timeout.inWholeMilliseconds }, markerFactory)
|
|
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
package dev.inmo.micro_utils.coroutines
|
package dev.inmo.micro_utils.coroutines
|
||||||
|
|
||||||
import dev.inmo.kslog.common.KSLog
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
@@ -17,45 +16,6 @@ inline fun <T> Flow<T>.subscribe(scope: CoroutineScope, noinline block: suspend
|
|||||||
* Use [subscribe], but all [block]s will be called inside of [safely] function.
|
* Use [subscribe], but all [block]s will be called inside of [safely] function.
|
||||||
* Use [onException] to set up your reaction for [Throwable]s
|
* Use [onException] to set up your reaction for [Throwable]s
|
||||||
*/
|
*/
|
||||||
inline fun <T> Flow<T>.subscribeLogging(
|
|
||||||
scope: CoroutineScope,
|
|
||||||
noinline errorMessageBuilder: T.(Throwable) -> Any = { "Something web wrong" },
|
|
||||||
logger: KSLog = KSLog,
|
|
||||||
noinline block: suspend (T) -> Unit
|
|
||||||
) = subscribe(scope) {
|
|
||||||
it.runCatchingLogging(
|
|
||||||
errorMessageBuilder,
|
|
||||||
logger
|
|
||||||
) {
|
|
||||||
block(it)
|
|
||||||
}.getOrThrow()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Use [subscribeSafelyWithoutExceptions], but all exceptions will be passed to [defaultSafelyExceptionHandler]
|
|
||||||
*/
|
|
||||||
inline fun <T> Flow<T>.subscribeLoggingDropExceptions(
|
|
||||||
scope: CoroutineScope,
|
|
||||||
noinline errorMessageBuilder: T.(Throwable) -> Any = { "Something web wrong" },
|
|
||||||
logger: KSLog = KSLog,
|
|
||||||
noinline block: suspend (T) -> Unit
|
|
||||||
) = subscribe(scope) {
|
|
||||||
it.runCatchingLogging(
|
|
||||||
errorMessageBuilder,
|
|
||||||
logger
|
|
||||||
) {
|
|
||||||
block(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Use [subscribe], but all [block]s will be called inside of [safely] function.
|
|
||||||
* Use [onException] to set up your reaction for [Throwable]s
|
|
||||||
*/
|
|
||||||
@Deprecated(
|
|
||||||
"Will be removed soon due to replacement by subscribeLogging",
|
|
||||||
ReplaceWith("this.subscribeLogging(scope = scope, block = block)")
|
|
||||||
)
|
|
||||||
inline fun <T> Flow<T>.subscribeSafely(
|
inline fun <T> Flow<T>.subscribeSafely(
|
||||||
scope: CoroutineScope,
|
scope: CoroutineScope,
|
||||||
noinline onException: ExceptionHandler<Unit> = defaultSafelyExceptionHandler,
|
noinline onException: ExceptionHandler<Unit> = defaultSafelyExceptionHandler,
|
||||||
@@ -69,10 +29,6 @@ inline fun <T> Flow<T>.subscribeSafely(
|
|||||||
/**
|
/**
|
||||||
* Use [subscribeSafelyWithoutExceptions], but all exceptions will be passed to [defaultSafelyExceptionHandler]
|
* Use [subscribeSafelyWithoutExceptions], but all exceptions will be passed to [defaultSafelyExceptionHandler]
|
||||||
*/
|
*/
|
||||||
@Deprecated(
|
|
||||||
"Will be removed soon due to replacement by subscribeLoggingDropExceptions",
|
|
||||||
ReplaceWith("this.subscribeLoggingDropExceptions(scope = scope, block = block)")
|
|
||||||
)
|
|
||||||
inline fun <T> Flow<T>.subscribeSafelyWithoutExceptions(
|
inline fun <T> Flow<T>.subscribeSafelyWithoutExceptions(
|
||||||
scope: CoroutineScope,
|
scope: CoroutineScope,
|
||||||
noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
|
noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
|
||||||
@@ -86,10 +42,6 @@ inline fun <T> Flow<T>.subscribeSafelyWithoutExceptions(
|
|||||||
/**
|
/**
|
||||||
* Use [subscribeSafelyWithoutExceptions], but all exceptions inside of [safely] will be skipped
|
* Use [subscribeSafelyWithoutExceptions], but all exceptions inside of [safely] will be skipped
|
||||||
*/
|
*/
|
||||||
@Deprecated(
|
|
||||||
"Will be removed soon due to replacement by subscribeLoggingDropExceptions",
|
|
||||||
ReplaceWith("this.subscribeLoggingDropExceptions(scope = scope, block = block)")
|
|
||||||
)
|
|
||||||
inline fun <T> Flow<T>.subscribeSafelySkippingExceptions(
|
inline fun <T> Flow<T>.subscribeSafelySkippingExceptions(
|
||||||
scope: CoroutineScope,
|
scope: CoroutineScope,
|
||||||
noinline block: suspend (T) -> Unit
|
noinline block: suspend (T) -> Unit
|
||||||
|
@@ -15,7 +15,7 @@ private class SubscribeAsyncReceiver<T>(
|
|||||||
get() = dataChannel
|
get() = dataChannel
|
||||||
|
|
||||||
init {
|
init {
|
||||||
scope.launchLoggingDropExceptions {
|
scope.launchSafelyWithoutExceptions {
|
||||||
for (data in dataChannel) {
|
for (data in dataChannel) {
|
||||||
output(data)
|
output(data)
|
||||||
}
|
}
|
||||||
@@ -71,7 +71,7 @@ fun <T, M> Flow<T>.subscribeAsync(
|
|||||||
it.invoke(markersMap)
|
it.invoke(markersMap)
|
||||||
}
|
}
|
||||||
|
|
||||||
val job = subscribeLoggingDropExceptions(subscope) { data ->
|
val job = subscribeSafelyWithoutExceptions(subscope) { data ->
|
||||||
val dataCommand = AsyncSubscriptionCommandData(data, subscope, markerFactory, block) { marker ->
|
val dataCommand = AsyncSubscriptionCommandData(data, subscope, markerFactory, block) { marker ->
|
||||||
actor.send(
|
actor.send(
|
||||||
AsyncSubscriptionCommandClearReceiver(marker)
|
AsyncSubscriptionCommandClearReceiver(marker)
|
||||||
@@ -85,32 +85,32 @@ fun <T, M> Flow<T>.subscribeAsync(
|
|||||||
return job
|
return job
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <T, M> Flow<T>.subscribeSafelyAsync(
|
inline fun <T, M> Flow<T>.subscribeSafelyAsync(
|
||||||
scope: CoroutineScope,
|
scope: CoroutineScope,
|
||||||
markerFactory: suspend (T) -> M,
|
noinline markerFactory: suspend (T) -> M,
|
||||||
onException: ExceptionHandler<Unit> = defaultSafelyExceptionHandler,
|
noinline onException: ExceptionHandler<Unit> = defaultSafelyExceptionHandler,
|
||||||
block: suspend (T) -> Unit
|
noinline block: suspend (T) -> Unit
|
||||||
) = subscribeAsync(scope, markerFactory) {
|
) = subscribeAsync(scope, markerFactory) {
|
||||||
safely(onException) {
|
safely(onException) {
|
||||||
block(it)
|
block(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <T, M> Flow<T>.subscribeSafelyWithoutExceptionsAsync(
|
inline fun <T, M> Flow<T>.subscribeSafelyWithoutExceptionsAsync(
|
||||||
scope: CoroutineScope,
|
scope: CoroutineScope,
|
||||||
markerFactory: suspend (T) -> M,
|
noinline markerFactory: suspend (T) -> M,
|
||||||
onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
|
noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
|
||||||
block: suspend (T) -> Unit
|
noinline block: suspend (T) -> Unit
|
||||||
) = subscribeAsync(scope, markerFactory) {
|
) = subscribeAsync(scope, markerFactory) {
|
||||||
safelyWithoutExceptions(onException) {
|
safelyWithoutExceptions(onException) {
|
||||||
block(it)
|
block(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <T, M> Flow<T>.subscribeSafelySkippingExceptionsAsync(
|
inline fun <T, M> Flow<T>.subscribeSafelySkippingExceptionsAsync(
|
||||||
scope: CoroutineScope,
|
scope: CoroutineScope,
|
||||||
markerFactory: suspend (T) -> M,
|
noinline markerFactory: suspend (T) -> M,
|
||||||
block: suspend (T) -> Unit
|
noinline block: suspend (T) -> Unit
|
||||||
) = subscribeAsync(scope, markerFactory) {
|
) = subscribeAsync(scope, markerFactory) {
|
||||||
safelyWithoutExceptions({ /* do nothing */}) {
|
safelyWithoutExceptions({ /* do nothing */}) {
|
||||||
block(it)
|
block(it)
|
||||||
|
@@ -4,74 +4,46 @@ import kotlinx.coroutines.*
|
|||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
import kotlin.coroutines.coroutineContext
|
import kotlin.coroutines.coroutineContext
|
||||||
|
|
||||||
|
typealias ExceptionHandler<T> = suspend (Throwable) -> T
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Launching [block] in [runCatching]. In case of failure, it will:
|
* This instance will be used in all calls of [safely] where exception handler has not been passed
|
||||||
*
|
|
||||||
* * Try to get [ContextSafelyExceptionHandler] from current [coroutineContext] and call its
|
|
||||||
* [ContextSafelyExceptionHandler.handler] invoke. **Thrown exception from its handler
|
|
||||||
* will pass out of [runCatchingSafely]**
|
|
||||||
* * Execute [onException] inside of new [runCatching] and return its result. Throws exception
|
|
||||||
* will be caught by [runCatching] and wrapped in [Result]
|
|
||||||
*
|
|
||||||
* @return [Result] with result of [block] if no exceptions or [Result] from [onException] execution
|
|
||||||
*/
|
*/
|
||||||
@Deprecated(
|
var defaultSafelyExceptionHandler: ExceptionHandler<Nothing> = { throw it }
|
||||||
"This function become redundant since coroutines correctly handling throwing exceptions",
|
|
||||||
replaceWith = ReplaceWith("runCatching(block).replaceIfFailure { onException(it) }")
|
/**
|
||||||
)
|
* This instance will be used in all calls of [safelyWithoutExceptions] as an exception handler for [safely] call
|
||||||
suspend inline fun <T> runCatchingSafely(
|
*/
|
||||||
onException: ExceptionHandler<T>,
|
var defaultSafelyWithoutExceptionHandler: ExceptionHandler<Unit> = {
|
||||||
block: suspend () -> T
|
try {
|
||||||
): Result<T> {
|
defaultSafelyExceptionHandler(it)
|
||||||
return runCatching {
|
} catch (e: Throwable) {
|
||||||
block()
|
// do nothing
|
||||||
}.onFailure {
|
|
||||||
coroutineContext[ContextSafelyExceptionHandlerKey] ?.handler ?.invoke(it)
|
|
||||||
return runCatching {
|
|
||||||
onException(it)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated(
|
/**
|
||||||
"This function become redundant since coroutines correctly handling throwing exceptions",
|
* This key can (and will) be used to get [ContextSafelyExceptionHandler] from [coroutineContext] of suspend functions
|
||||||
replaceWith = ReplaceWith("runCatching(block).replaceIfFailure { onException(it) }")
|
* and in [ContextSafelyExceptionHandler] for defining of its [CoroutineContext.Element.key]
|
||||||
)
|
*
|
||||||
suspend inline fun <T, R> R.runCatchingSafely(
|
* @see safelyWithContextExceptionHandler
|
||||||
onException: ExceptionHandler<T>,
|
* @see ContextSafelyExceptionHandler
|
||||||
block: suspend R.() -> T
|
*/
|
||||||
): Result<T> = runCatchingSafely<T>(onException) {
|
object ContextSafelyExceptionHandlerKey : CoroutineContext.Key<ContextSafelyExceptionHandler>
|
||||||
block()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Launching [runCatchingSafely] with [defaultSafelyExceptionHandler] as `onException` parameter
|
* [ExceptionHandler] wrapper which was created to make possible to use [handler] across all coroutines calls
|
||||||
|
*
|
||||||
|
* @see safelyWithContextExceptionHandler
|
||||||
|
* @see ContextSafelyExceptionHandlerKey
|
||||||
*/
|
*/
|
||||||
@Deprecated(
|
class ContextSafelyExceptionHandler(
|
||||||
"This function become redundant since coroutines correctly handling throwing exceptions",
|
val handler: ExceptionHandler<Unit>
|
||||||
replaceWith = ReplaceWith("runCatching(block).replaceIfFailure { defaultSafelyExceptionHandler(it) }")
|
) : CoroutineContext.Element {
|
||||||
)
|
override val key: CoroutineContext.Key<*>
|
||||||
suspend inline fun <T> runCatchingSafely(
|
get() = ContextSafelyExceptionHandlerKey
|
||||||
block: suspend () -> T
|
|
||||||
): Result<T> = runCatchingSafely(defaultSafelyExceptionHandler, block)
|
|
||||||
|
|
||||||
@Deprecated(
|
|
||||||
"This function become redundant since coroutines correctly handling throwing exceptions",
|
|
||||||
replaceWith = ReplaceWith("runCatching(block).replaceIfFailure { defaultSafelyExceptionHandler(it) }")
|
|
||||||
)
|
|
||||||
suspend inline fun <T, R> R.runCatchingSafely(
|
|
||||||
block: suspend R.() -> T
|
|
||||||
): Result<T> = runCatchingSafely<T> {
|
|
||||||
block()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//suspend inline fun <T, R> T.runCatchingSafely(
|
|
||||||
// onException: ExceptionHandler<R>,
|
|
||||||
// block: suspend T.() -> R
|
|
||||||
//): Result<R> = runCatchingSafely(onException) {
|
|
||||||
// block()
|
|
||||||
//}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return [ContextSafelyExceptionHandler] from [coroutineContext] by key [ContextSafelyExceptionHandlerKey] if
|
* @return [ContextSafelyExceptionHandler] from [coroutineContext] by key [ContextSafelyExceptionHandlerKey] if
|
||||||
* exists
|
* exists
|
||||||
@@ -79,7 +51,7 @@ suspend inline fun <T, R> R.runCatchingSafely(
|
|||||||
* @see ContextSafelyExceptionHandler
|
* @see ContextSafelyExceptionHandler
|
||||||
* @see ContextSafelyExceptionHandlerKey
|
* @see ContextSafelyExceptionHandlerKey
|
||||||
*/
|
*/
|
||||||
suspend fun contextSafelyExceptionHandler() = coroutineContext[ContextSafelyExceptionHandlerKey]
|
suspend inline fun contextSafelyExceptionHandler() = coroutineContext[ContextSafelyExceptionHandlerKey]
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method will set new [coroutineContext] with [ContextSafelyExceptionHandler]. In case if [coroutineContext]
|
* This method will set new [coroutineContext] with [ContextSafelyExceptionHandler]. In case if [coroutineContext]
|
||||||
@@ -89,13 +61,10 @@ suspend fun contextSafelyExceptionHandler() = coroutineContext[ContextSafelyExce
|
|||||||
* After all, will be called [withContext] method with created [ContextSafelyExceptionHandler] and block which will call
|
* After all, will be called [withContext] method with created [ContextSafelyExceptionHandler] and block which will call
|
||||||
* [safely] method with [safelyExceptionHandler] as onException parameter and [block] as execution block
|
* [safely] method with [safelyExceptionHandler] as onException parameter and [block] as execution block
|
||||||
*/
|
*/
|
||||||
@Deprecated(
|
|
||||||
"This function become redundant since coroutines correctly handling throwing exceptions",
|
|
||||||
)
|
|
||||||
suspend fun <T> safelyWithContextExceptionHandler(
|
suspend fun <T> safelyWithContextExceptionHandler(
|
||||||
contextExceptionHandler: ExceptionHandler<Unit>,
|
contextExceptionHandler: ExceptionHandler<Unit>,
|
||||||
safelyExceptionHandler: ExceptionHandler<T> = defaultSafelyExceptionHandler,
|
safelyExceptionHandler: ExceptionHandler<T> = defaultSafelyExceptionHandler,
|
||||||
block: suspend () -> T
|
block: suspend CoroutineScope.() -> T
|
||||||
): T {
|
): T {
|
||||||
val contextSafelyExceptionHandler = contextSafelyExceptionHandler() ?.handler ?.let { oldHandler ->
|
val contextSafelyExceptionHandler = contextSafelyExceptionHandler() ?.handler ?.let { oldHandler ->
|
||||||
ContextSafelyExceptionHandler {
|
ContextSafelyExceptionHandler {
|
||||||
@@ -109,47 +78,57 @@ suspend fun <T> safelyWithContextExceptionHandler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calls [runCatchingSafely] and getting the result via [Result.getOrThrow]
|
* It will run [block] inside of [supervisorScope] to avoid problems with catching of exceptions
|
||||||
*
|
*
|
||||||
* @see runCatchingSafely
|
* Priorities of [ExceptionHandler]s:
|
||||||
*/
|
|
||||||
@Deprecated(
|
|
||||||
"This function become redundant since coroutines correctly handling throwing exceptions",
|
|
||||||
replaceWith = ReplaceWith("runCatching(block).replaceIfFailure { onException(it) }.getOrThrow()")
|
|
||||||
)
|
|
||||||
suspend inline fun <T> safely(
|
|
||||||
onException: ExceptionHandler<T>,
|
|
||||||
block: suspend () -> T
|
|
||||||
): T = runCatchingSafely(onException, block).getOrThrow()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calls [safely] with passing of [defaultSafelyExceptionHandler] as `onException`
|
|
||||||
*
|
*
|
||||||
* @see runCatchingSafely
|
* * [onException] In case if custom (will be used anyway if not [defaultSafelyExceptionHandler])
|
||||||
|
* * [CoroutineContext.get] with [SafelyExceptionHandlerKey] as key
|
||||||
|
* * [defaultSafelyExceptionHandler]
|
||||||
|
*
|
||||||
|
* Remember, that [ExceptionHandler] from [CoroutineContext.get] will be used anyway if it is available. After it will
|
||||||
|
* be called [onException]
|
||||||
|
*
|
||||||
|
* @param [onException] Will be called when happen exception inside of [block]. By default will throw exception - this
|
||||||
|
* exception will be available for catching
|
||||||
|
*
|
||||||
|
* @see defaultSafelyExceptionHandler
|
||||||
|
* @see safelyWithoutExceptions
|
||||||
|
* @see safelyWithContextExceptionHandler
|
||||||
*/
|
*/
|
||||||
@Deprecated(
|
|
||||||
"This function become redundant since coroutines correctly handling throwing exceptions",
|
|
||||||
replaceWith = ReplaceWith("runCatching(block).replaceIfFailure { defaultSafelyExceptionHandler(it) }.getOrThrow()")
|
|
||||||
)
|
|
||||||
suspend inline fun <T> safely(
|
suspend inline fun <T> safely(
|
||||||
block: suspend () -> T
|
noinline onException: ExceptionHandler<T> = defaultSafelyExceptionHandler,
|
||||||
): T = safely(defaultSafelyExceptionHandler, block)
|
noinline block: suspend CoroutineScope.() -> T
|
||||||
@Deprecated(
|
): T {
|
||||||
"This function become redundant since coroutines correctly handling throwing exceptions",
|
return try {
|
||||||
replaceWith = ReplaceWith("runCatching(block).replaceIfFailure { defaultSafelyExceptionHandler(it) }.getOrThrow()")
|
supervisorScope(block)
|
||||||
)
|
} catch (e: Throwable) {
|
||||||
suspend inline fun <T, R> R.safely(
|
coroutineContext[ContextSafelyExceptionHandlerKey] ?.handler ?.invoke(e)
|
||||||
block: suspend R.() -> T
|
onException(e)
|
||||||
): T = safely<T> { block() }
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Deprecated("Renamed", ReplaceWith("runCatchingSafely(block)", "dev.inmo.micro_utils.coroutines.runCatchingSafely"))
|
suspend inline fun <T> runCatchingSafely(
|
||||||
suspend fun <T> safelyWithResult(
|
noinline onException: ExceptionHandler<T> = defaultSafelyExceptionHandler,
|
||||||
block: suspend () -> T
|
noinline block: suspend CoroutineScope.() -> T
|
||||||
|
): Result<T> = runCatching {
|
||||||
|
safely(onException, block)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend inline fun <T, R> T.runCatchingSafely(
|
||||||
|
noinline onException: ExceptionHandler<R> = defaultSafelyExceptionHandler,
|
||||||
|
noinline block: suspend T.() -> R
|
||||||
|
): Result<R> = runCatching {
|
||||||
|
safely(onException) { block() }
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend inline fun <T> safelyWithResult(
|
||||||
|
noinline block: suspend CoroutineScope.() -> T
|
||||||
): Result<T> = runCatchingSafely(defaultSafelyExceptionHandler, block)
|
): Result<T> = runCatchingSafely(defaultSafelyExceptionHandler, block)
|
||||||
@Deprecated("Renamed", ReplaceWith("this.runCatchingSafely(block)", "dev.inmo.micro_utils.coroutines.runCatchingSafely"))
|
|
||||||
suspend fun <T, R> R.safelyWithResult(
|
suspend inline fun <T, R> T.safelyWithResult(
|
||||||
block: suspend R.() -> T
|
noinline block: suspend T.() -> R
|
||||||
): Result<T> = safelyWithResult<T> { block() }
|
): Result<R> = runCatchingSafely(defaultSafelyExceptionHandler, block)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use this handler in cases you wish to include handling of exceptions by [defaultSafelyWithoutExceptionHandler] and
|
* Use this handler in cases you wish to include handling of exceptions by [defaultSafelyWithoutExceptionHandler] and
|
||||||
@@ -168,31 +147,21 @@ val defaultSafelyWithoutExceptionHandlerWithNull: ExceptionHandler<Nothing?> = {
|
|||||||
* Shortcut for [safely] with exception handler, that as expected must return null in case of impossible creating of
|
* Shortcut for [safely] with exception handler, that as expected must return null in case of impossible creating of
|
||||||
* result from exception (instead of throwing it, by default always returns null)
|
* result from exception (instead of throwing it, by default always returns null)
|
||||||
*/
|
*/
|
||||||
@Deprecated(
|
suspend inline fun <T> safelyWithoutExceptions(
|
||||||
"This function become redundant since coroutines correctly handling throwing exceptions",
|
noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
|
||||||
replaceWith = ReplaceWith("runCatching(block).replaceIfFailure { onException(it) }.getOrNull()")
|
noinline block: suspend CoroutineScope.() -> T
|
||||||
)
|
): T? = safely(onException, block)
|
||||||
suspend fun <T> safelyWithoutExceptions(
|
|
||||||
onException: ExceptionHandler<T> = defaultSafelyExceptionHandler,
|
|
||||||
block: suspend () -> T
|
|
||||||
): T? = runCatchingSafely(onException, block).getOrNull()
|
|
||||||
|
|
||||||
@Deprecated(
|
suspend inline fun <T> runCatchingSafelyWithoutExceptions(
|
||||||
"This function become redundant since coroutines correctly handling throwing exceptions",
|
noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
|
||||||
replaceWith = ReplaceWith("runCatching(block).replaceIfFailure { onException(it) }.getOrNull()")
|
noinline block: suspend CoroutineScope.() -> T
|
||||||
)
|
): Result<T?> = runCatching {
|
||||||
suspend fun <T> runCatchingSafelyWithoutExceptions(
|
safelyWithoutExceptions(onException, block)
|
||||||
onException: ExceptionHandler<T?> = defaultSafelyExceptionHandler,
|
|
||||||
block: suspend () -> T
|
|
||||||
): Result<T?> = runCatchingSafely(onException, block).let {
|
|
||||||
if (it.isFailure) return Result.success<T?>(null)
|
|
||||||
|
|
||||||
it
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun CoroutineScopeWithDefaultFallback(
|
inline fun CoroutineScope(
|
||||||
context: CoroutineContext,
|
context: CoroutineContext,
|
||||||
defaultExceptionsHandler: ExceptionHandler<Unit>
|
noinline defaultExceptionsHandler: ExceptionHandler<Unit>
|
||||||
) = CoroutineScope(
|
) = CoroutineScope(
|
||||||
context + ContextSafelyExceptionHandler(defaultExceptionsHandler)
|
context + ContextSafelyExceptionHandler(defaultExceptionsHandler)
|
||||||
)
|
)
|
||||||
|
@@ -1,64 +0,0 @@
|
|||||||
package dev.inmo.micro_utils.coroutines
|
|
||||||
|
|
||||||
import kotlinx.coroutines.*
|
|
||||||
import kotlin.coroutines.*
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Trying to [Job.join] on all [this] [Job]s. The first [Job] completed its work will interrupt all others joins
|
|
||||||
* and, if [cancelOthers] passed as true (**by default**), will also cancel all the others [Job]s
|
|
||||||
*
|
|
||||||
* @param scope Will be used to create [CoroutineScope.LinkedSupervisorScope] and launch joining of all [Job]s in it
|
|
||||||
*/
|
|
||||||
suspend fun Iterable<Job>.joinFirst(
|
|
||||||
scope: CoroutineScope,
|
|
||||||
cancelOthers: Boolean = true
|
|
||||||
): Job {
|
|
||||||
val resultDeferred = CompletableDeferred<Job>()
|
|
||||||
val scope = scope.LinkedSupervisorScope()
|
|
||||||
forEach {
|
|
||||||
scope.launch {
|
|
||||||
it.join()
|
|
||||||
resultDeferred.complete(it)
|
|
||||||
scope.cancel()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return resultDeferred.await().also {
|
|
||||||
if (cancelOthers) {
|
|
||||||
forEach {
|
|
||||||
runCatchingSafely { it.cancel() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Trying to [Job.join] on all [this] [Job]s. The first [Job] completed its work will interrupt all others joins
|
|
||||||
* and, if [cancelOthers] passed as true (**by default**), will also cancel all the others [Job]s
|
|
||||||
*
|
|
||||||
* Creates [CoroutineScope] using [coroutineContext] for internal purposes
|
|
||||||
*/
|
|
||||||
suspend fun Iterable<Job>.joinFirst(
|
|
||||||
cancelOthers: Boolean = true
|
|
||||||
): Job = joinFirst(CoroutineScope(coroutineContext), cancelOthers)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Trying to [Job.join] on all [jobs]. The first [Job] completed its work will interrupt all others joins
|
|
||||||
* and, if [cancelOthers] passed as true (**by default**), will also cancel all the others [Job]s
|
|
||||||
*
|
|
||||||
* @param scope Will be used to create [CoroutineScope.LinkedSupervisorScope] and launch joining of all [Job]s in it
|
|
||||||
*/
|
|
||||||
suspend fun joinFirst(
|
|
||||||
vararg jobs: Job,
|
|
||||||
scope: CoroutineScope,
|
|
||||||
cancelOthers: Boolean = true
|
|
||||||
): Job = jobs.toList().joinFirst(scope, cancelOthers)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Trying to [Job.join] on all [jobs]. The first [Job] completed its work will interrupt all others joins
|
|
||||||
* and, if [cancelOthers] passed as true (**by default**), will also cancel all the others [Job]s
|
|
||||||
*
|
|
||||||
* Creates [CoroutineScope] using [coroutineContext] for internal purposes
|
|
||||||
*/
|
|
||||||
suspend fun joinFirst(
|
|
||||||
vararg jobs: Job,
|
|
||||||
cancelOthers: Boolean = true
|
|
||||||
): Job = joinFirst(*jobs, scope = CoroutineScope(coroutineContext), cancelOthers = cancelOthers)
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user