Compare commits

...

153 Commits
0.1.1 ... 0.4.8

Author SHA1 Message Date
e38094df58 change order in iterable to skip changing of pagination on no left elements error 2020-12-01 15:48:37 +06:00
c25e3f5867 update versions 2020-12-01 15:38:44 +06:00
f78e81d175 PaginatedIterable 2020-12-01 15:34:12 +06:00
3837ae237d start 0.4.8 2020-12-01 15:11:18 +06:00
2b6ef8b4ff Merge pull request #24 from InsanusMokrassar/0.4.7
0.4.7
2020-11-27 19:30:40 +06:00
6cc0eefb3e upfix of default in includeWebsocketHandling 2020-11-27 17:53:29 +06:00
ab11e28bf7 UnifiedRouter 2020-11-27 14:35:00 +06:00
26d5f5a5f5 UnifiedRequester 2020-11-27 13:30:02 +06:00
74ae91cba6 start 0.4.7 2020-11-27 11:57:40 +06:00
70509c7edd update wrapper 2020-11-26 22:50:44 +06:00
5a69bd6c63 Merge pull request #23 from InsanusMokrassar/0.4.6
0.4.6
2020-11-26 22:50:08 +06:00
091bb1394f Warning annotation 2020-11-26 18:01:12 +06:00
b82c3864a0 typealiases for repos 2020-11-26 15:32:00 +06:00
49ee38a936 FileStandardKeyValueRepo 2020-11-26 15:03:40 +06:00
c201866c51 FlowCollector#invoke 2020-11-26 15:02:30 +06:00
023f841fb5 Pagination#isFirstPage 2020-11-26 15:01:45 +06:00
76102e9ab3 start 0.4.6 2020-11-26 15:00:32 +06:00
b2fc5e2a4d add query params docs 2020-11-25 11:09:54 +06:00
55aacb8753 update publish script 2020-11-23 22:46:40 +06:00
8702846216 Merge pull request #22 from InsanusMokrassar/0.4.5
0.4.5
2020-11-23 18:48:57 +06:00
3347a6d189 Update build.gradle 2020-11-23 18:46:52 +06:00
47b3b42949 replaces 2020-11-23 18:32:14 +06:00
e985631621 add recyclerview.alerts project 2020-11-23 18:10:53 +06:00
e15034bfa4 add tools to android 2020-11-23 17:39:17 +06:00
e5dbcd25dd remove kotlin android extensions 2020-11-23 17:27:52 +06:00
3714c02c12 add Android.Alerts project 2020-11-23 17:24:02 +06:00
fa636b4146 start 0.4.5 2020-11-23 17:15:21 +06:00
3d825aebc3 Update LeftItems.kt 2020-11-23 01:18:30 +06:00
629884a396 update left items 2020-11-23 00:36:37 +06:00
e7a0fa4e8f Merge pull request #21 from InsanusMokrassar/0.4.4
0.4.4
2020-11-22 23:47:04 +06:00
1a41f37a9d update changelog 2020-11-22 23:42:01 +06:00
07a65e0bb5 fixes 2020-11-22 23:19:16 +06:00
615f7f99c3 one more time update mechanism of sql transactions in android 2020-11-22 22:00:50 +06:00
3de5558ed4 new mechanism of transactions 2020-11-22 21:54:51 +06:00
6bbe3a271f update defaults in VersionsRepo 2020-11-22 19:27:53 +06:00
8bee29f683 update version of klock and remove gradle bintray plugin 2020-11-22 19:12:10 +06:00
c40f0fdcb9 VersionsRepo 2020-11-22 19:07:37 +06:00
30d1453a12 start 0.4.4 2020-11-22 17:49:39 +06:00
75fa88b00d Merge pull request #20 from InsanusMokrassar/0.4.3
0.4.3
2020-11-20 13:28:44 +06:00
0f817ad212 update kotlin 2020-11-20 13:10:40 +06:00
d7b46ae0d4 getSp and getDp 2020-11-20 10:23:40 +06:00
b6be14ecca start 0.4.3 2020-11-20 10:20:35 +06:00
33dbfc6f69 Merge pull request #19 from InsanusMokrassar/0.4.2
0.4.2
2020-11-18 20:32:42 +06:00
fcacdcd544 set method in realizations 2020-11-18 20:27:34 +06:00
dd2fc5a86f start 0.4.2 2020-11-18 20:26:08 +06:00
0f8a6f6bde update publishing files 2020-11-18 18:22:19 +06:00
1efd94181d Merge pull request #18 from InsanusMokrassar/0.4.1
0.4.1
2020-11-17 00:51:25 +06:00
71ff0232aa fix keyvalue exposed repo initiation 2020-11-16 20:05:22 +06:00
63921cd984 fix of shared flow size in ExposedKeyValueRepo 2020-11-16 19:48:28 +06:00
051e03bed3 start 0.4.1 2020-11-16 19:46:50 +06:00
a051394f4f Merge pull request #17 from InsanusMokrassar/0.4.0
0.4.0
2020-11-14 18:04:59 +06:00
b872babe45 new extensions withMapper 2020-11-14 17:35:11 +06:00
5a9cabc4bd add ReadStandardKeyValueRepo#keys with value parameter 2020-11-14 17:27:26 +06:00
3ba630684a new ReadOneToManyKeyValueRepo#keys 2020-11-14 16:44:28 +06:00
498cd12f94 start 0.4.0 2020-11-14 16:18:42 +06:00
062848f2e4 Merge pull request #16 from InsanusMokrassar/0.3.3
0.3.3
2020-11-12 22:20:11 +06:00
d4b4547718 plus for flows 2020-11-12 22:16:21 +06:00
22cd440dd7 start 0.3.3 2020-11-12 22:10:41 +06:00
6fc64526d4 update renaming of android parts 2020-11-11 16:29:43 +06:00
08075dfafe update min sdk for kotlin parts 2020-11-11 16:17:19 +06:00
efcb25622e fixes in gradle files 2020-11-11 16:06:44 +06:00
5ebf29d1fb Merge pull request #15 from InsanusMokrassar/0.3.2
0.3.2
2020-11-11 13:14:46 +06:00
b7d0ce3c97 formatting fix in keyvalue 2020-11-11 12:56:31 +06:00
e20929aec4 mappers workaround 2020-11-11 12:56:31 +06:00
f74167cc65 start 0.3.2 2020-11-11 12:56:31 +06:00
c55cd5342e update travis
trying to manually install packages in travis
2020-11-11 12:53:34 +06:00
237d89d611 experimentally update extension internalProject 2020-11-10 23:55:11 +06:00
aff9b52f1c update max heap size of jvm for gradle 2020-11-10 21:46:57 +06:00
d8cc6ed06e update max heap size of jvm for gradle 2020-11-10 21:46:19 +06:00
dffafa54c3 update publishing scripts 2020-11-10 20:53:08 +06:00
776a3af497 Merge pull request #14 from InsanusMokrassar/0.3.1
0.3.1
2020-11-10 19:50:57 +06:00
5b3d9e8d64 fill changelog 2020-11-10 19:49:21 +06:00
90b7d74a0c update for correct work 2020-11-10 18:06:10 +06:00
5b596c76e0 updates of dokka 2020-11-10 17:10:32 +06:00
c59e601e2e update gradle script to include android 2020-11-10 16:12:10 +06:00
5ce71ee6f6 add android tools 2020-11-10 15:43:15 +06:00
97dadf517a successfuly add android project 2020-11-10 14:33:47 +06:00
a739e874bf partially fix build 2020-11-10 12:48:22 +06:00
c73cb14615 temporal version 2020-11-09 22:51:47 +06:00
d9464f7b90 start 0.3.1 2020-11-09 21:46:58 +06:00
aeee41680e Merge pull request #13 from InsanusMokrassar/0.3.0
0.3.0
2020-11-09 00:52:30 +06:00
c914f8c44a fixes 2020-11-09 00:51:08 +06:00
abfda3627c repos rework 2020-11-09 00:46:26 +06:00
5b5bfa02db complete with major version change 0.3.0 2020-11-08 23:50:53 +06:00
0fdb072385 update: Replace channels by sharedflows in exposed CRUD realization 2020-11-08 20:45:45 +06:00
7b4c9d59b0 start 0.2.8 2020-11-08 20:44:40 +06:00
bd5923716f Merge pull request #11 from InsanusMokrassar/0.2.7
0.2.7
2020-11-07 02:28:38 +06:00
64cc42a23b upfill of changelog 2020-11-07 02:27:54 +06:00
325f178763 repos update 2020-11-07 02:25:50 +06:00
fc8d0e52ef start 0.2.7 2020-11-04 18:47:42 +06:00
e58348907e Merge pull request #10 from InsanusMokrassar/0.2.6
0.2.6
2020-11-03 20:08:06 +06:00
eaba9173ae more fixes:) 2020-11-03 20:07:33 +06:00
2f42b30f87 fix for paginate (again) and lastIndexExclusive 2020-11-03 19:43:45 +06:00
35913b95be fixes in pagintion 2020-11-03 18:37:50 +06:00
8023fa1b76 start 0.2.6 2020-11-03 18:35:22 +06:00
4cbe2d1d61 Merge pull request #9 from InsanusMokrassar/0.2.5
0.2.5
2020-11-02 21:44:53 +06:00
740036e8d9 link fix in ktor server 2020-11-02 21:44:12 +06:00
273a7aa9a4 optimize imports 2020-11-02 21:34:49 +06:00
dccb479c3c CoroutineScope#safeActor 2020-11-02 21:32:04 +06:00
6f5c6e5ebe safelyWithoutExceptions 2020-11-02 21:25:33 +06:00
8495cc6263 start 0.2.5 2020-11-02 21:24:20 +06:00
d242d8dcf4 Merge pull request #8 from InsanusMokrassar/0.2.4
0.2.4
2020-11-02 00:19:44 +06:00
864d576e70 fix add test for common diff utils 2020-11-02 00:19:23 +06:00
f0127b018e hotfix 2020-11-02 00:14:25 +06:00
5eb48a58bf diff utils rewriting 2020-11-02 00:12:47 +06:00
b690f68c7f add Flow#subscribe 2020-11-01 00:54:07 +06:00
7f19b83828 deprecate broadcast channel flow extensions 2020-11-01 00:43:47 +06:00
98a1ec82db update serialization version 2020-11-01 00:41:57 +06:00
63313ff964 start 0.2.4 2020-11-01 00:41:02 +06:00
210e32bed4 Merge pull request #7 from InsanusMokrassar/0.2.3
0.2.3
2020-10-28 14:47:30 +06:00
5b325a8ff9 add dokka 2020-10-28 14:47:12 +06:00
4582e0c817 small refactoring of BroadcastStateFlow 2020-10-28 14:19:57 +06:00
e2d1c5d6a1 corotoutines actualization 2020-10-28 14:17:39 +06:00
4019ad7d31 update exposed and coroutines versions 2020-10-28 13:58:23 +06:00
e7df21e91a common js visibility and on screen extensions 2020-10-28 13:57:24 +06:00
b2e30c9f6d start 0.2.3 2020-10-28 13:55:49 +06:00
0b27b5cc06 Revert "start add recycler view"
This reverts commit 347e9c32fe.
2020-10-28 13:51:24 +06:00
347e9c32fe start add recycler view 2020-10-28 13:51:16 +06:00
cc18f58e4c Merge pull request #6 from InsanusMokrassar/0.2.2
0.2.2
2020-10-25 22:26:18 +06:00
dbd2e963a1 change order of functions in read one to many interface 2020-10-25 22:23:24 +06:00
6928ca5329 optimize imports 2020-10-25 22:21:54 +06:00
9ece160aa8 add extensions for onetomany functions 2020-10-25 22:21:23 +06:00
6115c1bcac add extensions for OneToManyKeyValueRepo 2020-10-25 22:08:42 +06:00
e026e94cbf start 0.2.2 2020-10-25 21:52:36 +06:00
56cdd8d6af remove fixing of last index due to solving that pages size must be saved in reversing of pagination 2020-10-22 18:50:17 +06:00
899e6760e1 fix for lastIndex in reversing of pagination 2020-10-22 18:48:05 +06:00
864d0ffcc6 hope last fix 2020-10-22 18:37:56 +06:00
c6f417f8c8 fixes in pagination 2020-10-22 17:57:27 +06:00
9091fa5bd8 Merge pull request #4 from InsanusMokrassar/0.2.1
0.2.1
2020-10-22 15:50:30 +06:00
418af7874d update repos to use new reverse method 2020-10-22 15:47:28 +06:00
4972cc9daa several additions and tests for pagination reversing 2020-10-22 15:41:21 +06:00
ff905e1491 fixes in reverse extension 2020-10-22 14:54:27 +06:00
64b0184a17 Pagination#reverse 2020-10-22 14:39:39 +06:00
97cbe44fb5 start 0.2.1 2020-10-22 14:38:53 +06:00
452d8778c5 update changelog 2020-10-17 18:53:20 +06:00
02ad1a748e fix table name parameter in keyvalue and onetomany repos 2020-10-17 18:51:48 +06:00
c5a32e2b1b Merge pull request #3 from InsanusMokrassar/0.2.0
0.2.0
2020-10-17 18:09:36 +06:00
d34c5d845a remove onInit() 2020-10-17 18:01:22 +06:00
82b39baada fill CHANGELOG 2020-10-17 17:50:54 +06:00
712ed06b69 exposed tables changes 2020-10-17 17:41:56 +06:00
3365c63e07 start 0.2.0 2020-10-17 17:20:11 +06:00
702300f761 Update github_release.gradle 2020-10-16 21:39:52 +06:00
efcaa08b70 Update github_release.gradle 2020-10-16 21:38:31 +06:00
7f7369b3a8 Update and rename changelog_info_retriever to changelog_parser.sh 2020-10-16 21:37:24 +06:00
45a9a95888 Update github_release.gradle 2020-10-16 21:36:01 +06:00
90c1731bd1 Revert "more annotations to god of annotations"
This reverts commit f8a8808508.
2020-10-16 19:40:04 +06:00
893fd1ac07 Revert "less annotations to god of lessannotations"
This reverts commit 87230d010c.
2020-10-16 19:39:55 +06:00
9cac05f98b reorder repositories 2020-10-16 19:39:18 +06:00
3babc94842 update repos 2020-10-16 17:59:51 +06:00
87230d010c less annotations to god of lessannotations 2020-10-15 17:33:44 +06:00
f8a8808508 more annotations to god of annotations 2020-10-15 00:26:37 +06:00
f44174b5b3 update .gitignore 2020-10-14 22:58:35 +06:00
f6eb339cd0 Merge pull request #2 from InsanusMokrassar/0.1.1
0.1.1 (additions)
2020-10-14 22:31:25 +06:00
153 changed files with 4774 additions and 864 deletions

3
.gitignore vendored
View File

@@ -10,3 +10,6 @@ build/
out/
secret.gradle
local.properties
publishing.sh

View File

@@ -1,13 +1,27 @@
language: java
language: android
install: true
os: linux
dist: trusty
jdk: oraclejdk8
android:
components:
- tools
- platform-tools
- build-tools-30.0.2
- android-30
- add-on
- extra
before_script:
- yes | /usr/local/android-sdk/tools/bin/sdkmanager "build-tools;30.0.2"
- yes | /usr/local/android-sdk/tools/bin/sdkmanager "platforms;android-30"
jobs:
include:
- stage: build
script: ./gradlew build -s -x jvmTest -x jsIrTest -x jsIrBrowserTest -x jsIrNodeTest -x jsLegacyTest -x jsLegacyBrowserTest -x jsLegacyNodeTest
- state: test
script: ./gradlew allTests
# Tests are temporarily disabled on public travis due to the problems of launching
# - state: test
# script: ./gradlew allTests

View File

@@ -1,10 +1,279 @@
# Changelog
## 0.1.0
## 0.4.8
Inited :)
* `Versions`:
* `Coroutines`: `1.4.1` -> `1.4.2`
* `UUID`: `0.2.2` -> `0.2.3`
* `Pagination`
* Add `PaginatedIterable` and `PaginatedIterator`
### 0.1.1
## 0.4.7
* `Ktor`
* `Client`
* New class `UnifiedRequester`
* `Server`
* New class `UnifiedRouter`
* `Repos`
* `Ktor`
* `Client`
* Rewriting of all clients on new `UnifiedRequester`
* `Server`
* Rewriting of all clients on new `UnifiedRouter`
## 0.4.6
* `Common`
* New annotation `Warning` has been added
* `Pagination`
* `Common`
* `Pagination` got new extension: `Pagination#isFirstPage`
* `Coroutines`:
* New extension `FlowCollector#invoke` has been added
* `Repos`
* `Common`
* `JVM` (and `Android` since `Android API 26`):
* `FileStandardKeyValueRepo` has been added
* Add several `typealias`es for each type of repos
## 0.4.5
* `Android`
* `Alerts`
* `Common`
* Project has been created
* `RecyclerView`
* Project has been created
* `Common`
* Annotation `PreviewFeature` has been added
* `Android`
* Added tools to work with visibility in more comfortable way
* Added tools to work with disabled/enabled state in more comfortable way
* Added tools to work with expanded/collapsed state in more comfortable way (in preview mode)
## 0.4.4
* `Versions`:
* `Klock`: `1.12.1` -> `2.0.0`
* `Commons`:
* Update left items functionality to include work with `GridLayoutManager`
* `Repos`:
* Add interface `VersionsRepo`
* Add default realization of `VersionsRepo` named `StandardVersionsRepo` which use `StandardVersionsRepoProxy`
to get access to some end-store
* Add default realization of `StandardVersionsRepoProxy` based on `KeyValue` repos
* Add realizations of `StandardVersionsRepoProxy` for exposed and android (`SQL` and `SharedPreferences`)
* `Commons`:
* In Android fully reworked transactions functions
* Now `DatabaseCoroutineContext` is a shortcut for `Dispatchers.IO`
## 0.4.3
* `Versions`:
* `Kotlin`: `1.4.10` -> `1.4.20`
* `Common`:
* Two new extensions for Android:
* `Resources#getSp`
* `Resources#getDp`
## 0.4.2
* `Repos`:
* Add `WriteOneToManyKeyValueRepo#set` function and extensions
## 0.4.1
* `Repos`:
* Fixed error in `ExposedKeyValueRepo` related to negative size of shared flow
* Fixed error in `ExposedKeyValueRepo` related to avoiding of table initiation
## 0.4.0
* `Repos`:
* `ReadOneToManyKeyValueRepo` got `keys` method with value parameter
* All implementations inside of this library has been updated
* `ReadStandardKeyValueRepo` got `keys` method with value parameter
* All implementations inside of this library has been updated
* New extensions `withMapper`
## 0.3.3
* `Coroutines`:
* New extension `Flow<T>#plus`
## 0.3.2
* `Versions`:
* `Coroutines`: `1.4.1` -> `1.4.2`
* `Repos`:
* `Common`:
* New inline function `mapper` for simplier creating of `MapperRepo` objects
* Extensions `withMapper` for keyvalue repos and onetomany repos
## 0.3.1
**ANDROID PACKAGES**
* `Android`:
* `RecyclerView`:
* Library has been created
* `Common`
* Now available package `dev.inmo:micro_utils.common-android`
* `Coroutines`
* Now available package `dev.inmo:micro_utils.coroutines-android`
* `Ktor`
* `Common`
* Now available package `dev.inmo:micro_utils.ktor.common-android`
* `Client`
* Now available package `dev.inmo:micro_utils.ktor.client-android`
* `MimeTypes`
* Now available package `dev.inmo:micro_utils.mime_types-android`
* `Pagination`
* `Common`
* Now available package `dev.inmo:micro_utils.pagination.common-android`
* `Ktor`
* `Common`
* Now available package `dev.inmo:micro_utils.pagination.ktor.common-android`
* `Repos`
* `Common`
* Now available package `dev.inmo:micro_utils.repos.common-android`
* Now it is possible to use default realizations of repos abstractions natively on android
* `Inmemory`
* Now available package `dev.inmo:micro_utils.repos.inmemory-android`
* `Ktor`
* `Common`
* Now available package `dev.inmo:micro_utils.repos.ktor.common-android`
* `Common`
* Now available package `dev.inmo:micro_utils.repos.ktor.client-android`
## 0.3.0
All deprecations has been removed
* `Repos`:
* `Common`:
* `KeyValue` and `OneToMany` repos lost their deprecated methods
* `OneToMany` write repos got additional extensions for mutation of repo
* `KeyValue` write repos got additional extensions for mutation of repo
* New interface `MapperRepo` and new classes which are using this:
* `KeyValue`
* `MapperReadStandardKeyValueRepo`
* `MapperWriteStandardKeyValueRepo`
* `MapperStandardKeyValueRepo`
* `OneToMany`
* `MapperReadOneToManyKeyValueRepo`
* `MapperWriteOneToManyKeyValueRepo`
* `MapperOneToManyKeyValueRepo`
* `Exposed`:
* CRUD realizations replaced their channels to shared flows
## 0.2.7
* `Versions`:
* `Coroutines`: `1.4.0` -> `1.4.1`
* `Repos`:
* `WriteStandardKeyValueRepo` got new methods `set` and `unset` with collections
* All standard realizations of repos got collections methods realizations
* All old usages of `BroadcastFlow` and `BroadcastChannel` has been replaced with `MutableSharedFlow`
* `Ktor`:
* `Server`:
* Fixed incorrect answer for `keyvalue`
## 0.2.6
* `Pagination`
* Fixes in function `List#paginate`
* Extension property `Pagination#lastIndexExclusive`
## 0.2.5
* `Coroutines`
* Function `safelyWithoutExceptions`
* Extension `CoroutineScope#safeActor`
## 0.2.4
* `Versions`
* `Serialization`: `1.0.0` -> `1.0.1`
* `Common`
* Full rework of `DiffUtils`
* Data class `Diff` has been added
* Extension `Iterable#calculateDiff` has been added
* Extension `Iterable#calculateStrictDiff` as replacement for `Iterable#calculateDiff` with
`strictComparison` mode enabled
* Functions `Diff` (as analog of `Iterable#calculateDiff`) and `StrictDiff` (as analog of
`Iterable#calculateStrictDiff`)
* `Coroutines`
* `BroadcastFlow` now is deprecated
* `BroadcastStateFlow` now is deprecated
* New extensions for `Flow`s:
* `Flow#subscribe`
* `Flow#subscribeSafely`
* `Flow#subscribeSafelyWithoutExceptions`
## 0.2.3
* `Versions`
* `Coroutines`: `1.3.9` -> `1.4.0`
* `Exposed`: `0.27.1` -> `0.28.1`
* `Common`
* `K/JS`
* Add several extensions for `Element` objects to detect that object is on screen viewport
* Add several extensions for `Element` objects to detect object visibility
* `Coroutines`
* `BroadcastStateFlow` now use different strategy for getting of state and implements `replayCache`
## 0.2.2
* `Repos`
* `Common`
* Several new methods `ReadOneToManyKeyValueRepo#getAll`
* Several new method `WriteOneToManyKeyValueRepo#add` and several extensions
* Several new method `WriteOneToManyKeyValueRepo#remove` and several extensions
## 0.2.1
* `Pagination`
* `Common`:
* Extension `Pagination#reverse` has been added
* Factory `PaginationByIndexes`
* Shortcut `calculatePagesNumber` with reversed parameters
* Value `emptyPagination` for empty `SimplePagination` cases
## 0.2.0
* `Repos`
* `Exposed`
* Now this project depend on `micro_utils.coroutines`
* Typealias `ColumnAllocator` has been replaced to root exposed package
* Interface `ExposedRepo` has been added
* `ExposedCRUDRepo` now extends `ExposedRepo` instead of simple `Repo`
* New extension `initTable` for classes which are `Table` and `ExposedRepo` at the same time
* `KeyValue`:
* `tableName` parameter
* Class `AbstractExposedReadKeyValueRepo`
* Renamed to `ExposedReadKeyValueRepo`
* Changed incoming types to `ColumnAllocator`
* `open` instead of `abstract`
* Implements `ExposedRepo`
* Class `AbstractExposedKeyValueRepo`
* Renamed to `ExposedKeyValueRepo`
* Changed incoming types to `ColumnAllocator`
* `open` instead of `abstract`
* `OneToMany`:
* `tableName` parameter
* Class `AbstractExposedReadOneToManyKeyValueRepo`
* Renamed to `ExposedReadOneToManyKeyValueRepo`
* Changed incoming arguments order
* Implements `ExposedRepo`
* Class `AbstractExposedOneToManyKeyValueRepo`
* Renamed to `ExposedKeyValueRepo`
* Changed incoming arguments order
* `open` instead of `abstract`
* Release for every `Flow` in parent interfaces
## 0.1.1
* `Versions`:
* `kotlinx.serialization`: `1.0.0-RC2` -> `1.0.0`
@@ -39,3 +308,8 @@ Inited :)
* `MapCRUDRepo` class as implementation of `StandardCRUDRepo` on top of `MutableMap` has been added
* `MapKeyValueRepo` class as implementation of `StandardKeyValueRepo` on top of `MutableMap` has been added
* `MapOneToManyKeyValueRepo` class as implementation of `OneToManyKeyValueRepo` on top of `MutableMap` has been added
## 0.1.0
Inited :)

View File

@@ -0,0 +1,17 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
}
apply from: "$mppAndroidProjectPresetPath"
kotlin {
sourceSets {
androidMain {
dependencies {
api "androidx.appcompat:appcompat-resources:$appcompat_version"
}
}
}
}

View File

@@ -0,0 +1 @@
<manifest package="dev.inmo.micro_utils.android.alerts.common"/>

View File

@@ -0,0 +1,55 @@
@file:Suppress("NOTHING_TO_INLINE", "unused")
package dev.inmo.micro_utils.android.alerts.common
import android.app.AlertDialog
import android.content.Context
import android.content.DialogInterface
typealias AlertDialogCallback = (DialogInterface) -> Unit
inline fun Context.createAlertDialogTemplate(
title: String? = null,
positivePair: Pair<String, AlertDialogCallback?>? = null,
neutralPair: Pair<String, AlertDialogCallback?>? = null,
negativePair: Pair<String, AlertDialogCallback?>? = null
): AlertDialog.Builder {
val builder = AlertDialog.Builder(this)
title ?.let {
builder.setTitle(title)
}
positivePair ?. let {
builder.setPositiveButton(it.first) { di, _ -> it.second ?. invoke(di) }
}
negativePair ?. let {
builder.setNegativeButton(it.first) { di, _ -> it.second ?. invoke(di) }
}
neutralPair ?. let {
builder.setNeutralButton(it.first) { di, _ -> it.second ?. invoke(di) }
}
return builder
}
inline fun Context.createAlertDialogTemplateWithResources(
title: Int? = null,
positivePair: Pair<Int, AlertDialogCallback?>? = null,
neutralPair: Pair<Int, AlertDialogCallback?>? = null,
negativePair: Pair<Int, AlertDialogCallback?>? = null
): AlertDialog.Builder = createAlertDialogTemplate(
title ?.let { getString(it) },
positivePair ?.let { getString(it.first) to it.second },
neutralPair ?.let { getString(it.first) to it.second },
negativePair ?.let { getString(it.first) to it.second }
)
inline fun AlertDialog.setDismissChecker(noinline checker: () -> Boolean) : AlertDialog {
setOnDismissListener {
if (!checker()) {
show()
}
}
return this
}

View File

@@ -0,0 +1,38 @@
@file:Suppress("NOTHING_TO_INLINE", "unused")
package dev.inmo.micro_utils.android.alerts.common
import android.app.AlertDialog
import android.content.Context
import android.view.View
inline fun <T: View> Context.createCustomViewAlertDialog(
title: String? = null,
positivePair: Pair<String, AlertDialogCallback?>? = null,
neutralPair: Pair<String, AlertDialogCallback?>? = null,
negativePair: Pair<String, AlertDialogCallback?>? = null,
show: Boolean = true,
viewCreator: (Context) -> T
): AlertDialog = createAlertDialogTemplate(
title, positivePair, neutralPair, negativePair
).apply {
setView(viewCreator(this@createCustomViewAlertDialog))
}.create().apply {
if (show) show()
}
inline fun <T: View> Context.createCustomViewAlertDialogWithResources(
title: Int? = null,
positivePair: Pair<Int, AlertDialogCallback?>? = null,
neutralPair: Pair<Int, AlertDialogCallback?>? = null,
negativePair: Pair<Int, AlertDialogCallback?>? = null,
show: Boolean = true,
viewCreator: (Context) -> T
): AlertDialog = createCustomViewAlertDialog(
title ?.let { getString(it) },
positivePair ?.let { getString(it.first) to it.second },
neutralPair ?.let { getString(it.first) to it.second },
negativePair ?.let { getString(it.first) to it.second },
show,
viewCreator
)

View File

@@ -0,0 +1,45 @@
@file:Suppress("NOTHING_TO_INLINE", "unused")
package dev.inmo.micro_utils.android.alerts.common
import android.app.AlertDialog
import android.content.Context
import androidx.annotation.StringRes
inline fun Context.createSimpleTextAlertDialog(
text: String,
title: String? = null,
positivePair: Pair<String, AlertDialogCallback?>? = null,
neutralPair: Pair<String, AlertDialogCallback?>? = null,
negativePair: Pair<String, AlertDialogCallback?>? = null,
show: Boolean = true
): AlertDialog = createAlertDialogTemplate(
title,
positivePair,
neutralPair,
negativePair
).apply {
setMessage(text)
}.create().apply {
if (show) {
show()
}
}
inline fun Context.createSimpleTextAlertDialog(
@StringRes
text: Int,
@StringRes
title: Int? = null,
positivePair: Pair<Int, AlertDialogCallback?>? = null,
neutralPair: Pair<Int, AlertDialogCallback?>? = null,
negativePair: Pair<Int, AlertDialogCallback?>? = null,
show: Boolean = true
): AlertDialog = createSimpleTextAlertDialog(
getString(text),
title ?.let { getString(it) },
positivePair ?.let { getString(it.first) to it.second },
neutralPair ?.let { getString(it.first) to it.second },
negativePair ?.let { getString(it.first) to it.second },
show
)

View File

@@ -0,0 +1,18 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
}
apply from: "$mppAndroidProjectPresetPath"
kotlin {
sourceSets {
commonMain {
dependencies {
api internalProject("micro_utils.android.alerts.common")
api internalProject("micro_utils.android.recyclerview")
}
}
}
}

View File

@@ -0,0 +1 @@
<manifest package="dev.inmo.micro_utils.android.alerts.recyclerview"/>

View File

@@ -0,0 +1,65 @@
@file:Suppress("unused")
package dev.inmo.micro_utils.android.alerts.recyclerview
import android.app.AlertDialog
import android.content.Context
import android.content.DialogInterface
import android.view.ViewGroup
import android.widget.TextView
import dev.inmo.micro_utils.android.alerts.common.AlertDialogCallback
import dev.inmo.micro_utils.android.recyclerview.*
data class AlertAction(
val title: String,
val callback: (DialogInterface) -> Unit
)
private class ActionViewHolder(
container: ViewGroup, dialogInterfaceGetter: () -> DialogInterface
) : AbstractStandardViewHolder<AlertAction>(container, android.R.layout.simple_list_item_1) {
private lateinit var action: AlertAction
private val textView: TextView
get() = itemView.findViewById(android.R.id.text1)
init {
itemView.setOnClickListener {
action.callback(dialogInterfaceGetter())
}
}
override fun onBind(item: AlertAction) {
action = item
textView.text = item.title
}
}
private class ActionsRecyclerViewAdapter(
data: List<AlertAction>,
private val dialogInterfaceGetter: () -> DialogInterface
) : RecyclerViewAdapter<AlertAction>(data) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AbstractViewHolder<AlertAction> = ActionViewHolder(
parent, dialogInterfaceGetter
)
}
fun Context.createActionsAlertDialog(
actions: List<AlertAction>,
title: Int? = null,
positivePair: Pair<Int, AlertDialogCallback?>? = null,
neutralPair: Pair<Int, AlertDialogCallback?>? = null,
negativePair: Pair<Int, AlertDialogCallback?>? = null,
show: Boolean = true
): AlertDialog {
lateinit var dialogInterface: DialogInterface
return createRecyclerViewDialog(
title, positivePair, neutralPair, negativePair, show
) {
ActionsRecyclerViewAdapter(
actions
) {
dialogInterface
}
}.also { dialogInterface = it }
}

View File

@@ -0,0 +1,43 @@
package dev.inmo.micro_utils.android.alerts.recyclerview
import android.app.AlertDialog
import android.content.Context
import android.widget.LinearLayout
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import dev.inmo.micro_utils.android.alerts.common.AlertDialogCallback
import dev.inmo.micro_utils.android.alerts.common.createCustomViewAlertDialogWithResources
fun Context.createRecyclerViewDialog(
title: Int? = null,
positivePair: Pair<Int, AlertDialogCallback?>? = null,
neutralPair: Pair<Int, AlertDialogCallback?>? = null,
negativePair: Pair<Int, AlertDialogCallback?>? = null,
show: Boolean = true,
layoutManager: RecyclerView.LayoutManager = LinearLayoutManager(this),
marginOfRecyclerView: Int = 8, // dp
recyclerViewSetUp: RecyclerView.() -> Unit = {},
adapterFactory: () -> RecyclerView.Adapter<*>
): AlertDialog {
val recyclerView = RecyclerView(this).apply {
layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
).apply {
setMargins(marginOfRecyclerView, marginOfRecyclerView, marginOfRecyclerView, marginOfRecyclerView)
}
this.layoutManager = layoutManager
adapter = adapterFactory()
recyclerViewSetUp()
}
return createCustomViewAlertDialogWithResources(
title,
positivePair,
neutralPair,
negativePair,
show
) {
recyclerView
}
}

View File

@@ -0,0 +1,22 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
}
apply from: "$mppAndroidProjectPresetPath"
kotlin {
sourceSets {
commonMain {
dependencies {
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version"
}
}
androidMain {
dependencies {
api "androidx.recyclerview:recyclerview:$androidx_recycler_version"
}
}
}
}

View File

@@ -0,0 +1 @@
<manifest package="dev.inmo.micro_utils.android.recyclerview"/>

View File

@@ -0,0 +1,22 @@
package dev.inmo.micro_utils.android.recyclerview
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
abstract class AbstractStandardViewHolder<T>(
inflater: LayoutInflater,
container: ViewGroup?,
viewId: Int,
onViewInflated: ((View) -> Unit)? = null
) : AbstractViewHolder<T>(
inflater.inflate(viewId, container, false).also {
onViewInflated ?.invoke(it)
}
) {
constructor(
container: ViewGroup,
viewId: Int,
onViewInflated: ((View) -> Unit)? = null
) : this(LayoutInflater.from(container.context), container, viewId, onViewInflated)
}

View File

@@ -0,0 +1,10 @@
package dev.inmo.micro_utils.android.recyclerview
import android.view.View
import androidx.recyclerview.widget.RecyclerView
abstract class AbstractViewHolder<in T>(
view: View
) : RecyclerView.ViewHolder(view) {
abstract fun onBind(item: T)
}

View File

@@ -0,0 +1,9 @@
package dev.inmo.micro_utils.android.recyclerview
import android.content.Context
import android.widget.LinearLayout
import androidx.recyclerview.widget.DividerItemDecoration
val Context.recyclerViewItemsDecoration
get() = DividerItemDecoration(this, LinearLayout.VERTICAL)

View File

@@ -0,0 +1,53 @@
package dev.inmo.micro_utils.android.recyclerview
import androidx.recyclerview.widget.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.*
@Suppress("NOTHING_TO_INLINE")
private inline fun RecyclerView.LayoutManager.findLastVisibleItemPositionGetter(): (() -> Int)? = when (this) {
is LinearLayoutManager -> ::findLastVisibleItemPosition
is GridLayoutManager -> ::findLastVisibleItemPosition
else -> null
}
fun RecyclerView.lastVisibleItemFlow(
completingScope: CoroutineScope
): Flow<Int> {
val lastVisibleElementFun: () -> Int = layoutManager ?.findLastVisibleItemPositionGetter() ?: error("Currently supported only linear and grid layout manager")
val lastVisibleFlow = MutableStateFlow(lastVisibleElementFun())
addOnScrollListener(
object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
lastVisibleFlow.value = lastVisibleElementFun()
}
}.also { scrollListener ->
lastVisibleFlow.onCompletion {
removeOnScrollListener(scrollListener)
}.launchIn(completingScope)
}
)
return lastVisibleFlow.asStateFlow()
}
inline fun Flow<Int>.mapLeftItems(
crossinline countGetter: () -> Int
): Flow<Int> = map { countGetter() - it }
inline fun Flow<Int>.mapRequireFilling(
minimalLeftItems: Int,
crossinline countGetter: () -> Int
): Flow<Int> = mapLeftItems(countGetter).mapNotNull {
if (it < minimalLeftItems) {
it
} else {
null
}
}
inline fun RecyclerView.mapRequireFilling(
minimalLeftItems: Int,
completingScope: CoroutineScope,
crossinline countGetter: () -> Int
): Flow<Int> = lastVisibleItemFlow(completingScope).mapRequireFilling(minimalLeftItems, countGetter)

View File

@@ -0,0 +1,68 @@
package dev.inmo.micro_utils.android.recyclerview
import android.view.View
import androidx.recyclerview.widget.RecyclerView
abstract class RecyclerViewAdapter<T>(
val data: List<T>
): RecyclerView.Adapter<AbstractViewHolder<T>>() {
var emptyView: View? = null
set(value) {
field = value
checkEmpty()
}
init {
registerAdapterDataObserver(
object : RecyclerView.AdapterDataObserver() {
override fun onItemRangeChanged(positionStart: Int, itemCount: Int) {
super.onItemRangeChanged(positionStart, itemCount)
checkEmpty()
}
override fun onItemRangeChanged(positionStart: Int, itemCount: Int, payload: Any?) {
super.onItemRangeChanged(positionStart, itemCount, payload)
checkEmpty()
}
override fun onChanged() {
super.onChanged()
checkEmpty()
}
override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
super.onItemRangeRemoved(positionStart, itemCount)
checkEmpty()
}
override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) {
super.onItemRangeMoved(fromPosition, toPosition, itemCount)
checkEmpty()
}
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
super.onItemRangeInserted(positionStart, itemCount)
checkEmpty()
}
}
)
checkEmpty()
}
override fun getItemCount(): Int = data.size
override fun onBindViewHolder(holder: AbstractViewHolder<T>, position: Int) {
holder.onBind(data[position])
}
private fun checkEmpty() {
emptyView ?. let {
if (data.isEmpty()) {
it.visibility = View.VISIBLE
} else {
it.visibility = View.GONE
}
}
}
}

View File

@@ -1,24 +1,30 @@
buildscript {
repositories {
mavenLocal()
jcenter()
google()
mavenCentral()
mavenLocal()
maven { url "https://plugins.gradle.org/m2/" }
}
dependencies {
classpath 'com.android.tools.build:gradle:4.0.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
classpath "com.jfrog.bintray.gradle:gradle-bintray-plugin:$gradle_bintray_plugin_version"
classpath "com.getkeepsafe.dexcount:dexcount-gradle-plugin:$dexcount_version"
classpath "com.github.breadmoirai:github-release:$github_release_plugin_version"
classpath "org.jetbrains.dokka:dokka-gradle-plugin:$dokka_version"
}
}
repositories {
mavenLocal()
jcenter()
mavenCentral()
maven { url "https://kotlin.bintray.com/kotlinx" }
allprojects {
repositories {
mavenLocal()
jcenter()
mavenCentral()
google()
maven { url "https://kotlin.bintray.com/kotlinx" }
}
}
apply from: "./extensions.gradle"

View File

@@ -12,4 +12,7 @@ function assert_success() {
export RELEASE_MODE=true
project="$1"
assert_success ./gradlew clean "$project:clean" "$project:build" "$project:publishToMavenLocal" "$project:bintrayUpload"
assert_success ./gradlew clean
assert_success ./gradlew "$project:build"
assert_success ./gradlew "$project:publishToMavenLocal"
assert_success ./gradlew "$project:bintrayUpload"

View File

@@ -1,24 +0,0 @@
#!/bin/bash
function parse() {
version=$1
while IFS= read -r line && [ -z "`echo $line | grep -e "^#\+ $version"`" ]
do
: # do nothing
done
while IFS= read -r line && [ -z "`echo $line | grep -e "^#\+"`" ]
do
echo "$line"
done
}
version=$1
file=$2
if [ -n "$file" ]; then
parse $version < "$file"
else
parse $version
fi

24
changelog_parser.sh Executable file
View File

@@ -0,0 +1,24 @@
#!/bin/bash
function parse() {
version="$1"
while IFS= read -r line && [ -z "`echo "$line" | grep -e "^#\+ $version"`" ]
do
: # do nothing
done
while IFS= read -r line && [ -z "`echo "$line" | grep -e "^#\+"`" ]
do
echo "$line"
done
}
version="$1"
file="$2"
if [ -n "$file" ]; then
parse "$version" < "$file"
else
parse "$version"
fi

View File

@@ -1,6 +1,7 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
}
apply from: "$mppProjectWithSerializationPresetPath"

View File

@@ -0,0 +1,37 @@
package dev.inmo.micro_utils.common
@RequiresOptIn(
"It is possible, that behaviour of this thing will be changed or removed in future releases",
RequiresOptIn.Level.WARNING
)
@Target(
AnnotationTarget.CLASS,
AnnotationTarget.CONSTRUCTOR,
AnnotationTarget.FIELD,
AnnotationTarget.PROPERTY,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER,
AnnotationTarget.FUNCTION,
AnnotationTarget.TYPE,
AnnotationTarget.TYPEALIAS,
AnnotationTarget.TYPE_PARAMETER
)
annotation class PreviewFeature
@RequiresOptIn(
"This thing is marked as warned. See message of warn to get more info",
RequiresOptIn.Level.WARNING
)
@Target(
AnnotationTarget.CLASS,
AnnotationTarget.CONSTRUCTOR,
AnnotationTarget.FIELD,
AnnotationTarget.PROPERTY,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER,
AnnotationTarget.FUNCTION,
AnnotationTarget.TYPE,
AnnotationTarget.TYPEALIAS,
AnnotationTarget.TYPE_PARAMETER
)
annotation class Warning(val message: String)

View File

@@ -1,10 +1,151 @@
@file:Suppress("NOTHING_TO_INLINE")
package dev.inmo.micro_utils.common
fun <T> Iterable<T>.syncWith(
other: Iterable<T>,
removed: (List<T>) -> Unit = {},
added: (List<T>) -> Unit = {}
) {
removed(filter { it !in other })
added(other.filter { it !in this })
private inline fun <T> getObject(
additional: MutableList<T>,
iterator: Iterator<T>
): T? = when {
additional.isNotEmpty() -> additional.removeFirst()
iterator.hasNext() -> iterator.next()
else -> null
}
/**
* Diff object which contains information about differences between two [Iterable]s
*
* @see calculateDiff
*/
data class Diff<T> internal constructor(
val removed: List<IndexedValue<T>>,
/**
* Old-New values pairs
*/
val replaced: List<Pair<IndexedValue<T>, IndexedValue<T>>>,
val added: List<IndexedValue<T>>
)
private inline fun <T> performChanges(
potentialChanges: MutableList<Pair<IndexedValue<T>?, IndexedValue<T>?>>,
additionalsInOld: MutableList<T>,
additionalsInNew: MutableList<T>,
changedList: MutableList<Pair<IndexedValue<T>, IndexedValue<T>>>,
removedList: MutableList<IndexedValue<T>>,
addedList: MutableList<IndexedValue<T>>,
strictComparison: Boolean
) {
var i = -1
val (oldObject, newObject) = potentialChanges.lastOrNull() ?: return
for ((old, new) in potentialChanges.take(potentialChanges.size - 1)) {
i++
val oldOneEqualToNewObject = old ?.value === newObject ?.value || (old ?.value == newObject ?.value && !strictComparison)
val newOneEqualToOldObject = new ?.value === oldObject ?.value || (new ?.value == oldObject ?.value && !strictComparison)
if (oldOneEqualToNewObject || newOneEqualToOldObject) {
changedList.addAll(
potentialChanges.take(i).mapNotNull {
if (it.first != null && it.second != null) it as Pair<IndexedValue<T>, IndexedValue<T>> else null
}
)
val newPotentials = potentialChanges.drop(i).take(potentialChanges.size - i)
when {
oldOneEqualToNewObject -> {
newPotentials.first().second ?.let { addedList.add(it) }
newPotentials.drop(1).take(newPotentials.size - 2).forEach { (oldOne, newOne) ->
addedList.add(newOne!!)
oldOne ?.let { additionalsInOld.add(oldOne.value) }
}
if (newPotentials.size > 1) {
newPotentials.last().first ?.value ?.let { additionalsInOld.add(it) }
}
}
newOneEqualToOldObject -> {
newPotentials.first().first ?.let { removedList.add(it) }
newPotentials.drop(1).take(newPotentials.size - 2).forEach { (oldOne, newOne) ->
removedList.add(oldOne!!)
newOne ?.let { additionalsInNew.add(newOne.value) }
}
if (newPotentials.size > 1) {
newPotentials.last().second ?.value ?.let { additionalsInNew.add(it) }
}
}
}
potentialChanges.clear()
return
}
}
if (potentialChanges.isNotEmpty() && potentialChanges.last().let { it.first == null && it.second == null }) {
potentialChanges.dropLast(1).forEach { (old, new) ->
when {
old != null && new != null -> changedList.add(old to new)
old != null -> removedList.add(old)
new != null -> addedList.add(new)
}
}
}
}
/**
* Calculating [Diff] object
*
* @param strictComparison If this parameter set to true, objects which are not equal by links will be used as different
* objects. For example, in case of two "Example" string they will be equal by value, but CAN be different by links
*/
fun <T> Iterable<T>.calculateDiff(
other: Iterable<T>,
strictComparison: Boolean = false
): Diff<T> {
var i = -1
var j = -1
val additionalInOld = mutableListOf<T>()
val additionalInNew = mutableListOf<T>()
val oldIterator = iterator()
val newIterator = other.iterator()
val potentiallyChangedObjects = mutableListOf<Pair<IndexedValue<T>?, IndexedValue<T>?>>()
val changedObjects = mutableListOf<Pair<IndexedValue<T>, IndexedValue<T>>>()
val addedObjects = mutableListOf<IndexedValue<T>>()
val removedObjects = mutableListOf<IndexedValue<T>>()
while (true) {
i++
j++
val oldObject = getObject(additionalInOld, oldIterator)
val newObject = getObject(additionalInNew, newIterator)
if (oldObject == null && newObject == null) {
break
}
when {
oldObject === newObject || (oldObject == newObject && !strictComparison) -> {
changedObjects.addAll(potentiallyChangedObjects.map { it as Pair<IndexedValue<T>, IndexedValue<T>> })
potentiallyChangedObjects.clear()
}
else -> {
potentiallyChangedObjects.add(oldObject ?.let { IndexedValue(i, oldObject) } to newObject ?.let { IndexedValue(j, newObject) })
val previousOldsAdditionsSize = additionalInOld.size
val previousNewsAdditionsSize = additionalInNew.size
performChanges(potentiallyChangedObjects, additionalInOld, additionalInNew, changedObjects, removedObjects, addedObjects, strictComparison)
i -= (additionalInOld.size - previousOldsAdditionsSize)
j -= (additionalInNew.size - previousNewsAdditionsSize)
}
}
}
potentiallyChangedObjects.add(null to null)
performChanges(potentiallyChangedObjects, additionalInOld, additionalInNew, changedObjects, removedObjects, addedObjects, strictComparison)
return Diff(removedObjects.toList(), changedObjects.toList(), addedObjects.toList())
}
inline fun <T> Diff(old: Iterable<T>, new: Iterable<T>) = old.calculateDiff(new)
inline fun <T> StrictDiff(old: Iterable<T>, new: Iterable<T>) = old.calculateDiff(new, true)
/**
* This method call [calculateDiff] with strict mode enabled
*/
inline fun <T> Iterable<T>.calculateStrictDiff(
other: Iterable<T>
) = calculateDiff(other, strictComparison = true)

View File

@@ -0,0 +1,76 @@
package dev.inmo.micro_utils.common
import kotlin.math.floor
import kotlin.test.Test
import kotlin.test.assertEquals
class DiffUtilsTests {
@Test
fun testThatSimpleRemoveWorks() {
val oldList = (0 until 10).toList()
val withIndex = oldList.withIndex()
for (count in 1 .. (floor(oldList.size.toFloat() / 2).toInt())) {
for ((i, v) in withIndex) {
if (i + count > oldList.lastIndex) {
continue
}
val removedSublist = oldList.subList(i, i + count)
oldList.calculateDiff(oldList - removedSublist).apply {
assertEquals(
removedSublist.mapIndexed { j, o -> IndexedValue(i + j, o) },
removed
)
}
}
}
}
@Test
fun testThatSimpleAddWorks() {
val oldList = (0 until 10).map { it.toString() }
val withIndex = oldList.withIndex()
for (count in 1 .. (floor(oldList.size.toFloat() / 2).toInt())) {
for ((i, v) in withIndex) {
if (i + count > oldList.lastIndex) {
continue
}
val addedSublist = oldList.subList(i, i + count).map { "added$it" }
val mutable = oldList.toMutableList()
mutable.addAll(i, addedSublist)
oldList.calculateDiff(mutable).apply {
assertEquals(
addedSublist.mapIndexed { j, o -> IndexedValue(i + j, o) },
added
)
}
}
}
}
@Test
fun testThatSimpleChangesWorks() {
val oldList = (0 until 10).map { it.toString() }
val withIndex = oldList.withIndex()
for (step in 0 until oldList.size) {
for ((i, v) in withIndex) {
val mutable = oldList.toMutableList()
val changes = (
if (step == 0) i until oldList.size else (i until oldList.size step step)
).map { index ->
IndexedValue(index, mutable[index]) to IndexedValue(index, "changed$index").also {
mutable[index] = it.value
}
}
oldList.calculateDiff(mutable).apply {
assertEquals(
changes,
replaced
)
}
}
}
}
}

View File

@@ -0,0 +1,43 @@
package dev.inmo.micro_utils.common
import kotlinx.browser.window
import org.w3c.dom.DOMRect
import org.w3c.dom.Element
val DOMRect.isOnScreenByLeftEdge: Boolean
get() = left >= 0 && left <= window.innerWidth
inline val Element.isOnScreenByLeftEdge
get() = getBoundingClientRect().isOnScreenByLeftEdge
val DOMRect.isOnScreenByRightEdge: Boolean
get() = right >= 0 && right <= window.innerWidth
inline val Element.isOnScreenByRightEdge
get() = getBoundingClientRect().isOnScreenByRightEdge
internal val DOMRect.isOnScreenHorizontally: Boolean
get() = isOnScreenByLeftEdge || isOnScreenByRightEdge
val DOMRect.isOnScreenByTopEdge: Boolean
get() = top >= 0 && top <= window.innerHeight
inline val Element.isOnScreenByTopEdge
get() = getBoundingClientRect().isOnScreenByTopEdge
val DOMRect.isOnScreenByBottomEdge: Boolean
get() = bottom >= 0 && bottom <= window.innerHeight
inline val Element.isOnScreenByBottomEdge
get() = getBoundingClientRect().isOnScreenByBottomEdge
internal val DOMRect.isOnScreenVertically: Boolean
get() = isOnScreenByLeftEdge || isOnScreenByRightEdge
val DOMRect.isOnScreenFully: Boolean
get() = isOnScreenByLeftEdge && isOnScreenByTopEdge && isOnScreenByRightEdge && isOnScreenByBottomEdge
val Element.isOnScreenFully: Boolean
get() = getBoundingClientRect().isOnScreenFully
val DOMRect.isOnScreen: Boolean
get() = isOnScreenFully || (isOnScreenHorizontally && isOnScreenVertically)
inline val Element.isOnScreen: Boolean
get() = getBoundingClientRect().isOnScreen

View File

@@ -0,0 +1,48 @@
package dev.inmo.micro_utils.common
import kotlinx.browser.window
import org.w3c.dom.Element
import org.w3c.dom.css.CSSStyleDeclaration
sealed class Visibility
object Visible : Visibility()
object Invisible : Visibility()
object Gone : Visibility()
var CSSStyleDeclaration.visibilityState: Visibility
get() = when {
display == "none" -> Gone
visibility == "hidden" -> Invisible
else -> Visible
}
set(value) {
when (value) {
Visible -> {
if (display == "none") {
display = "initial"
}
visibility = "visible"
}
Invisible -> {
if (display == "none") {
display = "initial"
}
visibility = "hidden"
}
Gone -> {
display = "none"
}
}
}
inline var Element.visibilityState: Visibility
get() = window.getComputedStyle(this).visibilityState
set(value) {
window.getComputedStyle(this).visibilityState = value
}
inline val Element.isVisible: Boolean
get() = visibilityState == Visible
inline val Element.isInvisible: Boolean
get() = visibilityState == Invisible
inline val Element.isGone: Boolean
get() = visibilityState == Gone

View File

@@ -0,0 +1 @@
<manifest package="dev.inmo.micro_utils.common"/>

View File

@@ -0,0 +1,13 @@
@file:Suppress("NOTHING_TO_INLINE")
package dev.inmo.micro_utils.common
import android.content.res.Resources
inline fun Resources.getSp(
resId: Int
) = getDimension(resId) / displayMetrics.scaledDensity
inline fun Resources.getDp(
resId: Int
) = getDimension(resId) * displayMetrics.density

View File

@@ -0,0 +1,76 @@
package dev.inmo.micro_utils.common
import android.view.View
import android.view.ViewGroup
import android.view.animation.Animation
import android.view.animation.Transformation
@PreviewFeature
fun View.expand(
duration: Long = 500,
targetWidth: Int = ViewGroup.LayoutParams.MATCH_PARENT,
targetHeight: Int = ViewGroup.LayoutParams.WRAP_CONTENT
) {
measure(targetWidth, targetHeight)
val measuredHeight: Int = measuredHeight
layoutParams.height = 0
visibility = View.VISIBLE
val a: Animation = object : Animation() {
override fun applyTransformation(interpolatedTime: Float, t: Transformation?) {
super.applyTransformation(interpolatedTime, t)
layoutParams.height = if (interpolatedTime == 1f) targetHeight else (measuredHeight * interpolatedTime).toInt()
requestLayout()
}
override fun willChangeBounds(): Boolean {
return true
}
}
a.duration = duration
startAnimation(a)
}
@PreviewFeature
fun View.collapse(duration: Long = 500) {
val initialHeight: Int = measuredHeight
val a: Animation = object : Animation() {
override fun applyTransformation(interpolatedTime: Float, t: Transformation?) {
if (interpolatedTime == 1f) {
visibility = View.GONE
} else {
layoutParams.height = initialHeight - (initialHeight * interpolatedTime).toInt()
requestLayout()
}
}
override fun willChangeBounds(): Boolean {
return true
}
}
a.duration = duration
startAnimation(a)
}
@PreviewFeature
inline val View.isCollapsed
get() = visibility == View.GONE
@PreviewFeature
inline val View.isExpanded
get() = !isCollapsed
/**
* @return true in case of expanding
*/
@PreviewFeature
fun View.toggleExpandState(duration: Long = 500): Boolean = if (isCollapsed) {
expand(duration)
true
} else {
collapse(duration)
false
}

View File

@@ -0,0 +1,7 @@
package dev.inmo.micro_utils.common
@Suppress("UNCHECKED_CAST", "SimplifiableCall")
inline fun <T, R> Iterable<T>.mapNotNullA(transform: (T) -> R?): List<R> = map(transform).filter { it != null } as List<R>
@Suppress("UNCHECKED_CAST", "SimplifiableCall")
inline fun <T, R> Array<T>.mapNotNullA(mapper: (T) -> R?): List<R> = map(mapper).filter { it != null } as List<R>

View File

@@ -0,0 +1,34 @@
@file:Suppress("NOTHING_TO_INLINE", "unused")
package dev.inmo.micro_utils.common
import android.view.View
import android.view.ViewGroup
inline val View.enabled
get() = isEnabled
inline val View.disabled
get() = !enabled
fun View.disable() {
if (this is ViewGroup) {
(0 until childCount).forEach { getChildAt(it).disable() }
}
isEnabled = false
}
fun View.enable() {
if (this is ViewGroup) {
(0 until childCount).forEach { getChildAt(it).enable() }
}
isEnabled = true
}
fun View.toggleEnabledState(enabled: Boolean) {
if (enabled) {
enable()
} else {
disable()
}
}

View File

@@ -0,0 +1,35 @@
@file:Suppress("NOTHING_TO_INLINE", "unused")
package dev.inmo.micro_utils.common
import android.view.View
inline val View.gone
get() = visibility == View.GONE
inline fun View.gone() {
visibility = View.GONE
}
inline val View.hidden
get() = visibility == View.INVISIBLE
inline fun View.hide() {
visibility = View.INVISIBLE
}
inline val View.shown
get() = visibility == View.VISIBLE
inline fun View.show() {
visibility = View.VISIBLE
}
fun View.toggleVisibility(goneOnHide: Boolean = true) {
if (isShown) {
if (goneOnHide) {
gone()
} else {
hide()
}
} else {
show()
}
}

View File

@@ -1,6 +1,7 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
}
apply from: "$mppProjectWithSerializationPresetPath"
@@ -12,5 +13,10 @@ kotlin {
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version"
}
}
androidMain {
dependencies {
api "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
}
}
}
}

View File

@@ -17,3 +17,15 @@ fun <T> CoroutineScope.actor(
return channel
}
inline fun <T> CoroutineScope.safeActor(
channelCapacity: Int = Channel.UNLIMITED,
noinline onException: ExceptionHandler<Unit> = {},
crossinline block: suspend (T) -> Unit
): Channel<T> = actor(
channelCapacity
) {
safely(onException) {
block(it)
}
}

View File

@@ -1,21 +0,0 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.channels.*
import kotlinx.coroutines.flow.*
@Suppress("FunctionName")
fun <T> BroadcastFlow(
internalChannelSize: Int = Channel.BUFFERED
): BroadcastFlow<T> {
val channel = BroadcastChannel<T>(internalChannelSize)
return BroadcastFlow(
channel,
channel.asFlow()
)
}
class BroadcastFlow<T> internal constructor(
private val channel: BroadcastChannel<T>,
private val flow: Flow<T>
): Flow<T> by flow, SendChannel<T> by channel

View File

@@ -1,33 +0,0 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.BroadcastChannel
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.*
class BroadcastStateFlow<T> internal constructor(
parentFlow: Flow<T>,
private val stateGetter: () -> T
) : StateFlow<T>, Flow<T> by parentFlow {
override val value: T
get() = stateGetter()
}
fun <T> BroadcastChannel<T>.asStateFlow(value: T, scope: CoroutineScope): StateFlow<T> = asFlow().let {
var state: T = value
it.onEach { state = it }.launchIn(scope)
BroadcastStateFlow(it) {
state
}
}
fun <T> BroadcastChannel<T?>.asStateFlow(scope: CoroutineScope): StateFlow<T?> = asStateFlow(null, scope)
fun <T> broadcastStateFlow(initial: T, scope: CoroutineScope, channelSize: Int = Channel.BUFFERED) = BroadcastChannel<T>(
channelSize
).let {
it to it.asStateFlow(initial, scope)
}
fun <T> broadcastStateFlow(scope: CoroutineScope, channelSize: Int = Channel.BUFFERED) = broadcastStateFlow<T?>(null, scope, channelSize)

View File

@@ -0,0 +1,5 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.flow.FlowCollector
suspend inline operator fun <T> FlowCollector<T>.invoke(value: T) = emit(value)

View File

@@ -0,0 +1,37 @@
@file:Suppress("NOTHING_TO_INLINE")
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.*
/**
* Shortcut for chain if [Flow.onEach] and [Flow.launchIn]
*/
inline fun <T> Flow<T>.subscribe(scope: CoroutineScope, noinline block: suspend (T) -> Unit) = onEach(block).launchIn(scope)
/**
* Use [subscribe], but all [block]s will be called inside of [safely] function.
* Use [onException] to set up your reaction for [Throwable]s
*/
inline fun <T> Flow<T>.subscribeSafely(
scope: CoroutineScope,
noinline onException: ExceptionHandler<Unit> = { throw it },
noinline block: suspend (T) -> Unit
) = subscribe(scope) {
safely(onException) {
block(it)
}
}
/**
* Use [subscribeSafelyWithoutExceptions], but all exceptions inside of [safely] will be skipped
*/
inline fun <T> Flow<T>.subscribeSafelyWithoutExceptions(
scope: CoroutineScope,
noinline block: suspend (T) -> Unit
) = subscribeSafely(
scope,
{},
block
)

View File

@@ -0,0 +1,8 @@
@file:Suppress("NOTHING_TO_INLINE")
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.merge
inline operator fun <T> Flow<T>.plus(other: Flow<T>) = merge(this, other)

View File

@@ -21,3 +21,10 @@ suspend inline fun <T> safely(
onException(e)
}
}
/**
* Shortcut for [safely] without exception handler (instead of this you will receive null as a result)
*/
suspend inline fun <T> safelyWithoutExceptions(
noinline block: suspend CoroutineScope.() -> T
): T? = safely({ null }, block)

View File

@@ -0,0 +1 @@
<manifest package="dev.inmo.micro_utils.coroutines"/>

View File

@@ -0,0 +1,10 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
suspend inline fun <T> doInUI(noinline block: suspend CoroutineScope.() -> T) = withContext(
Dispatchers.Main,
block
)

38
defaultAndroidSettings Normal file
View File

@@ -0,0 +1,38 @@
android {
compileSdkVersion "$android_compileSdkVersion".toInteger()
buildToolsVersion "$android_buildToolsVersion"
defaultConfig {
minSdkVersion "$android_minSdkVersion".toInteger()
targetSdkVersion "$android_compileSdkVersion".toInteger()
versionCode "${android_code_version}".toInteger()
versionName "$version"
}
buildTypes {
release {
minifyEnabled false
}
debug {
debuggable true
}
}
packagingOptions {
exclude 'META-INF/kotlinx-serialization-runtime.kotlin_module'
exclude 'META-INF/kotlinx-serialization-cbor.kotlin_module'
exclude 'META-INF/kotlinx-serialization-properties.kotlin_module'
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
}

134
dokka/build.gradle Normal file
View File

@@ -0,0 +1,134 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
id "org.jetbrains.dokka"
}
repositories {
mavenLocal()
jcenter()
google()
mavenCentral()
}
kotlin {
jvm()
js(BOTH) {
browser()
nodejs()
}
android {}
sourceSets {
commonMain {
dependencies {
implementation kotlin('stdlib')
project.parent.subprojects.forEach {
if (
it != project
&& it.hasProperty("kotlin")
&& it.kotlin.sourceSets.any { it.name.contains("commonMain") }
&& it.kotlin.sourceSets.any { it.name.contains("jsMain") }
&& it.kotlin.sourceSets.any { it.name.contains("jvmMain") }
&& it.kotlin.sourceSets.any { it.name.contains("androidMain") }
) {
api it
}
}
}
}
jsMain {
dependencies {
implementation kotlin('stdlib')
project.parent.subprojects.forEach {
if (
it != project
&& it.hasProperty("kotlin")
&& it.kotlin.sourceSets.any { it.name.contains("commonMain") }
&& it.kotlin.sourceSets.any { it.name.contains("jsMain") }
) {
api it
}
}
}
}
jvmMain {
dependencies {
implementation kotlin('stdlib')
project.parent.subprojects.forEach {
if (
it != project
&& it.hasProperty("kotlin")
&& it.kotlin.sourceSets.any { it.name.contains("commonMain") }
&& it.kotlin.sourceSets.any { it.name.contains("jvmMain") }
) {
api it
}
}
}
}
androidMain {
dependencies {
implementation kotlin('stdlib')
project.parent.subprojects.forEach {
if (
it != project
&& it.hasProperty("kotlin")
&& it.kotlin.sourceSets.any { it.name.contains("commonMain") }
&& it.kotlin.sourceSets.any { it.name.contains("androidMain") }
) {
api it
}
}
}
}
}
}
private List<SourceDirectorySet> findSourcesWithName(String... approximateNames) {
return parent.subprojects
.findAll { it != project && it.hasProperty("kotlin") }
.collectMany { it.kotlin.sourceSets }
.findAll { sourceSet ->
approximateNames.any { nameToFilter ->
sourceSet.name.contains(nameToFilter)
}
}.collect { it.kotlin }
}
tasks.dokkaHtml {
dokkaSourceSets {
configureEach {
skipDeprecated.set(true)
sourceLink {
localDirectory.set(file("./"))
remoteUrl.set(new URL("https://github.com/InsanusMokrassar/MicroUtils/blob/master/"))
remoteLineSuffix.set("#L")
}
}
named("commonMain") {
sourceRoots.setFrom(findSourcesWithName("commonMain"))
}
named("jsMain") {
sourceRoots.setFrom(findSourcesWithName("jsMain", "commonMain"))
}
named("jvmMain") {
sourceRoots.setFrom(findSourcesWithName("jvmMain", "commonMain"))
}
named("androidMain") {
sourceRoots.setFrom(findSourcesWithName("androidMain", "commonMain"))
}
}
}
apply from: "$defaultAndroidSettingsPresetPath"

View File

@@ -0,0 +1 @@
<manifest package="dev.inmo.dokka"/>

View File

@@ -10,17 +10,21 @@ allprojects {
}
internalProject = { name ->
if (releaseMode) {
"$group:$name:$version"
} else {
projectByName("$name")
}
// if (releaseMode) {
// "$group:$name:$version"
// } else {
// projectByName("$name")
// }
projectByName("$name")
}
releaseMode = (project.hasProperty('RELEASE_MODE') && project.property('RELEASE_MODE') == "true") || System.getenv('RELEASE_MODE') == "true"
mppProjectWithSerializationPresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerialization"
mppJavaProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJavaProject"
mppAndroidProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppAndroidProject"
defaultAndroidSettingsPresetPath = "${rootProject.projectDir.absolutePath}/defaultAndroidSettings"
publishGradlePath = "${rootProject.projectDir.absolutePath}/publish.gradle"
publishMavenPath = "${rootProject.projectDir.absolutePath}/maven.publish.gradle"

View File

@@ -1,8 +1,11 @@
private String getCurrentVersionChangelog() {
OutputStream changelogDataOS = new ByteArrayOutputStream()
exec {
commandLine 'chmod', "+x", './changelog_parser.sh'
}
exec {
standardOutput = changelogDataOS
commandLine './changelog_info_retriever', "$library_version", 'CHANGELOG.md'
commandLine './changelog_parser.sh', "${project.version}", 'CHANGELOG.md'
}
return changelogDataOS.toString().trim()
@@ -18,9 +21,9 @@ if (new File(projectDir, "secret.gradle").exists()) {
owner "InsanusMokrassar"
repo "MicroUtils"
tagName "$library_version"
releaseName "$library_version"
targetCommitish "$library_version"
tagName "${project.version}"
releaseName "${project.version}"
targetCommitish "${project.version}"
body getCurrentVersionChangelog()
}

View File

@@ -3,20 +3,42 @@ org.gradle.parallel=true
kotlin.js.generate.externals=true
kotlin.incremental=true
kotlin.incremental.js=true
android.useAndroidX=true
android.enableJetifier=true
kotlin_version=1.4.10
kotlin_coroutines_version=1.3.9
kotlin_serialisation_core_version=1.0.0
kotlin_exposed_version=0.27.1
kotlin_version=1.4.20
kotlin_coroutines_version=1.4.2
kotlin_serialisation_core_version=1.0.1
kotlin_exposed_version=0.28.1
ktor_version=1.4.1
ktor_version=1.4.2
klockVersion=1.12.1
klockVersion=2.0.0
gradle_bintray_plugin_version=1.8.5
github_release_plugin_version=2.2.12
uuidVersion=0.2.2
uuidVersion=0.2.3
# ANDROID
core_ktx_version=1.3.2
androidx_recycler_version=1.1.0
appcompat_version=1.2.0
android_minSdkVersion=19
android_compileSdkVersion=30
android_buildToolsVersion=30.0.2
dexcount_version=2.0.0-RC1
junit_version=4.12
test_ext_junit_version=1.1.2
espresso_core=3.3.0
# Dokka
dokka_version=1.4.10.2
# Project data
group=dev.inmo
version=0.1.1
version=0.4.8
android_code_version=12

View File

@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@@ -1,6 +1,7 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
}
apply from: "$mppProjectWithSerializationPresetPath"

View File

@@ -4,7 +4,8 @@ import dev.inmo.micro_utils.coroutines.safely
import dev.inmo.micro_utils.ktor.common.*
import io.ktor.client.HttpClient
import io.ktor.client.features.websocket.ws
import io.ktor.http.cio.websocket.*
import io.ktor.http.cio.websocket.Frame
import io.ktor.http.cio.websocket.readBytes
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.channelFlow
import kotlinx.serialization.DeserializationStrategy
@@ -63,10 +64,11 @@ inline fun <T> HttpClient.createStandardWebsocketFlow(
inline fun <T> HttpClient.createStandardWebsocketFlow(
url: String,
crossinline checkReconnection: (Throwable?) -> Boolean = { true },
deserializer: DeserializationStrategy<T>
deserializer: DeserializationStrategy<T>,
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
) = createStandardWebsocketFlow(
url,
checkReconnection
) {
standardKtorSerialFormat.decodeDefault(deserializer, it)
serialFormat.decodeDefault(deserializer, it)
}

View File

@@ -8,27 +8,57 @@ import kotlinx.serialization.*
typealias BodyPair<T> = Pair<SerializationStrategy<T>, T>
class UnifiedRequester(
private val client: HttpClient = HttpClient(),
private val serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
) {
suspend fun <ResultType> uniget(
url: String,
resultDeserializer: DeserializationStrategy<ResultType>
): ResultType = client.get<StandardKtorSerialInputData>(
url
).let {
serialFormat.decodeDefault(resultDeserializer, it)
}
fun <T> encodeUrlQueryValue(
serializationStrategy: SerializationStrategy<T>,
value: T
) = serialFormat.encodeHex(
serializationStrategy,
value
)
suspend fun <BodyType, ResultType> unipost(
url: String,
bodyInfo: BodyPair<BodyType>,
resultDeserializer: DeserializationStrategy<ResultType>
) = client.post<StandardKtorSerialInputData>(url) {
body = serialFormat.encodeDefault(bodyInfo.first, bodyInfo.second)
}.let {
serialFormat.decodeDefault(resultDeserializer, it)
}
fun <T> createStandardWebsocketFlow(
url: String,
checkReconnection: (Throwable?) -> Boolean = { true },
deserializer: DeserializationStrategy<T>
) = client.createStandardWebsocketFlow(url, checkReconnection, deserializer, serialFormat)
}
val defaultRequester = UnifiedRequester()
suspend fun <ResultType> HttpClient.uniget(
url: String,
resultDeserializer: DeserializationStrategy<ResultType>
) = get<StandardKtorSerialInputData>(
url
).let {
standardKtorSerialFormat.decodeDefault(resultDeserializer, it)
}
) = defaultRequester.uniget(url, resultDeserializer)
fun <T> SerializationStrategy<T>.encodeUrlQueryValue(value: T) = standardKtorSerialFormat.encodeHex(
this,
value
)
fun <T> SerializationStrategy<T>.encodeUrlQueryValue(value: T) = defaultRequester.encodeUrlQueryValue(this, value)
suspend fun <BodyType, ResultType> HttpClient.unipost(
url: String,
bodyInfo: BodyPair<BodyType>,
resultDeserializer: DeserializationStrategy<ResultType>
) = post<StandardKtorSerialInputData>(url) {
body = standardKtorSerialFormat.encodeDefault(bodyInfo.first, bodyInfo.second)
}.let {
standardKtorSerialFormat.decodeDefault(resultDeserializer, it)
}
) = defaultRequester.unipost(url, bodyInfo, resultDeserializer)

View File

@@ -0,0 +1 @@
<manifest package="dev.inmo.micro_utils.ktor.client"/>

View File

@@ -1,6 +1,7 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
}
apply from: "$mppProjectWithSerializationPresetPath"

View File

@@ -3,16 +3,28 @@ package dev.inmo.micro_utils.ktor.common
typealias QueryParam = Pair<String, String?>
typealias QueryParams = Map<String, String?>
/**
* Create query part which includes key=value pairs separated with &
*/
val QueryParams.asUrlQuery: String
get() = keys.joinToString("&") { "${it}${get(it) ?.let { value -> "=$value" }}" }
/**
* Create query part which includes key=value pairs separated with &
*/
val List<QueryParam>.asUrlQuery: String
get() = joinToString("&") { (key, value) -> "${key}${value ?.let { _ -> "=$value" }}" }
/**
* Create query part which includes key=value pairs separated with & and attach to receiver
*/
fun String.includeQueryParams(
queryParams: QueryParams
): String = "$this${if(queryParams.isNotEmpty()) "${if (contains("?")) "&" else "?"}${queryParams.asUrlQuery}" else ""}"
/**
* Create query part which includes key=value pairs separated with & and attach to receiver
*/
fun String.includeQueryParams(
queryParams: List<QueryParam>
): String = "$this${if (contains("?")) "&" else "?"}${queryParams.asUrlQuery}"

View File

@@ -0,0 +1 @@
<manifest package="dev.inmo.micro_utils.ktor.common"/>

View File

@@ -22,23 +22,22 @@ fun <T> Route.includeWebsocketHandling(
converter: (T) -> StandardKtorSerialInputData
) {
webSocket(suburl) {
// println("connected")
safely {
flow.collect {
send(converter(it))
}
}
// println("disconnected")
}
}
fun <T> Route.includeWebsocketHandling(
suburl: String,
flow: Flow<T>,
serializer: SerializationStrategy<T>
serializer: SerializationStrategy<T>,
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
) = includeWebsocketHandling(
suburl,
flow
) {
standardKtorSerialFormat.encodeDefault(serializer, it)
serialFormat.encodeDefault(serializer, it)
}

View File

@@ -3,13 +3,88 @@ package dev.inmo.micro_utils.ktor.server
import dev.inmo.micro_utils.coroutines.safely
import dev.inmo.micro_utils.ktor.common.*
import io.ktor.application.ApplicationCall
import io.ktor.application.call
import io.ktor.http.ContentType
import io.ktor.http.HttpStatusCode
import io.ktor.request.receive
import io.ktor.response.respond
import io.ktor.response.respondBytes
import io.ktor.util.toByteArray
import io.ktor.routing.Route
import io.ktor.util.pipeline.PipelineContext
import kotlinx.coroutines.flow.Flow
import kotlinx.serialization.*
class UnifiedRouter(
private val serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
private val serialFormatContentType: ContentType = standardKtorSerialFormatContentType
) {
fun <T> Route.includeWebsocketHandling(
suburl: String,
flow: Flow<T>,
serializer: SerializationStrategy<T>
) = includeWebsocketHandling(suburl, flow, serializer, serialFormat)
suspend fun <T> PipelineContext<*, ApplicationCall>.unianswer(
answerSerializer: SerializationStrategy<T>,
answer: T
) {
call.respondBytes (
serialFormat.encodeDefault(answerSerializer, answer),
serialFormatContentType
)
}
suspend fun <T> PipelineContext<*, ApplicationCall>.uniload(
deserializer: DeserializationStrategy<T>
) = safely {
serialFormat.decodeDefault(
deserializer,
call.receive()
)
}
suspend fun PipelineContext<*, ApplicationCall>.getParameterOrSendError(
field: String
) = call.parameters[field].also {
if (it == null) {
call.respond(HttpStatusCode.BadRequest, "Request must contains $field")
}
}
fun PipelineContext<*, ApplicationCall>.getQueryParameter(
field: String
) = call.request.queryParameters[field]
suspend fun PipelineContext<*, ApplicationCall>.getQueryParameterOrSendError(
field: String
) = getQueryParameter(field).also {
if (it == null) {
call.respond(HttpStatusCode.BadRequest, "Request query parameters must contains $field")
}
}
fun <T> PipelineContext<*, ApplicationCall>.decodeUrlQueryValue(
field: String,
deserializer: DeserializationStrategy<T>
) = getQueryParameter(field) ?.let {
serialFormat.decodeHex(
deserializer,
it
)
}
suspend fun <T> PipelineContext<*, ApplicationCall>.decodeUrlQueryValueOrSendError(
field: String,
deserializer: DeserializationStrategy<T>
) = decodeUrlQueryValue(field, deserializer).also {
if (it == null) {
call.respond(HttpStatusCode.BadRequest, "Request query parameters must contains $field")
}
}
}
val defaultUnifiedRouter = UnifiedRouter()
suspend fun <T> ApplicationCall.unianswer(
answerSerializer: SerializationStrategy<T>,
answer: T

View File

@@ -1,60 +0,0 @@
apply plugin: 'maven-publish'
task javadocsJar(type: Jar) {
classifier = 'javadoc'
}
afterEvaluate {
project.publishing.publications.all {
// rename artifacts
groupId "${project.group}"
if (it.name.contains('kotlinMultiplatform')) {
artifactId = "${project.name}"
} else {
artifactId = "${project.name}-$name"
}
}
}
publishing {
publications.all {
artifact javadocsJar
pom {
description = ""
name = "${project.name}"
url = "https://git.inmo.dev/InsanusMokrassar/MicroUtils_mirror"
scm {
developerConnection = "scm:git:[fetch=]ssh://git@git.inmo.dev:8322/InsanusMokrassar/MicroUtils_mirror.git[push=]ssh://git@git.inmo.dev:8322/InsanusMokrassar/MicroUtils_mirror.git"
url = "ssh://git@git.inmo.dev:8322/InsanusMokrassar/MicroUtils_mirror.git"
}
developers {
developer {
id = "InsanusMokrassar"
name = "Aleksei Ovsiannikov"
email = "ovsyannikov.alexey95@gmail.com"
}
developer {
id = "000Sanya"
name = "Syrov Aleksandr"
email = "000sanya.000sanya@gmail.com"
}
}
licenses {
license {
name = "Apache Software License 2.0"
url = "https://git.inmo.dev/InsanusMokrassar/MicroUtils_mirror/src/master/LICENSE"
}
}
}
}
}

View File

@@ -1,6 +1,7 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
}
apply from: "$mppProjectWithSerializationPresetPath"

View File

@@ -0,0 +1 @@
<manifest package="dev.inmo.micro_utils.mime_types"/>

26
mppAndroidProject Normal file
View File

@@ -0,0 +1,26 @@
project.version = "$version"
project.group = "$group"
apply from: "$publishGradlePath"
kotlin {
android {
publishAllLibraryVariants()
}
sourceSets {
commonMain {
dependencies {
implementation kotlin('stdlib')
}
}
commonTest {
dependencies {
implementation kotlin('test-common')
implementation kotlin('test-annotations-common')
}
}
}
}
apply from: "$defaultAndroidSettingsPresetPath"

View File

@@ -3,13 +3,6 @@ project.group = "$group"
apply from: "$publishGradlePath"
repositories {
jcenter()
mavenCentral()
maven { url "https://kotlin.bintray.com/kotlinx" }
mavenLocal()
}
kotlin {
jvm()

View File

@@ -3,19 +3,15 @@ project.group = "$group"
apply from: "$publishGradlePath"
repositories {
jcenter()
mavenCentral()
maven { url "https://kotlin.bintray.com/kotlinx" }
mavenLocal()
}
kotlin {
jvm()
js (BOTH) {
browser()
nodejs()
}
android {
publishAllLibraryVariants()
}
sourceSets {
commonMain {
@@ -41,5 +37,14 @@ kotlin {
implementation kotlin('test-junit')
}
}
androidTest {
dependencies {
implementation kotlin('test-junit')
implementation "androidx.test.ext:junit:$test_ext_junit_version"
implementation "androidx.test.espresso:espresso-core:$espresso_core"
}
}
}
}
apply from: "$defaultAndroidSettingsPresetPath"

View File

@@ -1,6 +1,7 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
}
apply from: "$mppProjectWithSerializationPresetPath"

View File

@@ -22,12 +22,27 @@ interface Pagination {
val size: Int
}
/**
* Logical shortcut for comparison that page is 0
*/
inline val Pagination.isFirstPage
get() = page == 0
/**
* First number in index of objects. It can be used as offset for databases or other data sources
*/
val Pagination.firstIndex: Int
get() = page * size
/**
* Last number in index of objects. In fact, one [Pagination] object represent data in next range:
*
* [[firstIndex], [lastIndex]]; That means, that for [Pagination] with [Pagination.size] == 10 and [Pagination.page] == 1
* you will retrieve [Pagination.firstIndex] == 10 and [Pagination.lastIndex] == 19. Here [Pagination.lastIndexExclusive] == 20
*/
val Pagination.lastIndexExclusive: Int
get() = firstIndex + size
/**
* Last number in index of objects. In fact, one [Pagination] object represent data in next range:
*
@@ -35,7 +50,7 @@ val Pagination.firstIndex: Int
* you will retrieve [Pagination.firstIndex] == 10 and [Pagination.lastIndex] == 19.
*/
val Pagination.lastIndex: Int
get() = firstIndex + size - 1
get() = lastIndexExclusive - 1
/**
* Calculates pages count for given [datasetSize]
@@ -43,6 +58,11 @@ val Pagination.lastIndex: Int
fun calculatePagesNumber(datasetSize: Long, pageSize: Int): Int {
return ceil(datasetSize.toDouble() / pageSize).toInt()
}
/**
* Calculates pages count for given [datasetSize]. As a fact, it is shortcut for [calculatePagesNumber]
* @return calculated page number which can be correctly used in [PaginationResult] as [PaginationResult.page] value
*/
fun calculatePagesNumber(pageSize: Int, datasetSize: Long): Int = calculatePagesNumber(datasetSize, pageSize)
/**
* Calculates pages count for given [datasetSize]
*/

View File

@@ -14,6 +14,8 @@ inline fun FirstPagePagination(size: Int = defaultMediumPageSize) =
size = size
)
val emptyPagination = Pagination(0, 0)
@Suppress("NOTHING_TO_INLINE")
inline fun Pagination.nextPage() =
SimplePagination(
@@ -27,7 +29,21 @@ data class SimplePagination(
override val size: Int
) : Pagination
/**
* Factory for [SimplePagination]
*/
fun Pagination(
page: Int,
size: Int
) = SimplePagination(page, size)
/**
* @param firstIndex Inclusive first index of pagination
* @param lastIndex INCLUSIVE last index of pagination (last index of object covered by result [SimplePagination])
*/
fun PaginationByIndexes(
firstIndex: Int,
lastIndex: Int
) = maxOf(0, (lastIndex - firstIndex + 1)).let { size ->
Pagination(calculatePage(firstIndex, size), size)
}

View File

@@ -21,7 +21,12 @@ fun <T> Iterable<T>.paginate(with: Pagination): PaginationResult<T> {
}
fun <T> List<T>.paginate(with: Pagination): PaginationResult<T> {
return subList(with.firstIndex, with.lastIndex + 1).createPaginationResult(
val firstIndex = maxOf(with.firstIndex, 0)
val lastIndex = minOf(with.lastIndexExclusive, size)
if (firstIndex > lastIndex) {
return emptyPaginationResult()
}
return subList(firstIndex, lastIndex).createPaginationResult(
with,
size.toLong()
)

View File

@@ -0,0 +1,41 @@
package dev.inmo.micro_utils.pagination.utils
import dev.inmo.micro_utils.pagination.*
class PaginatedIterator<T>(
pageSize: Int,
private val countGetter: () -> Long,
private val paginationResultGetter: Pagination.() -> PaginationResult<T>
) : Iterator<T> {
private var pagination = FirstPagePagination(pageSize)
private val currentStack = mutableListOf<T>()
override fun hasNext(): Boolean = currentStack.isNotEmpty() || (countGetter() < pagination.lastIndexExclusive)
override fun next(): T {
if (currentStack.isEmpty()) {
val resultPagination = paginationResultGetter.invoke(pagination)
currentStack.addAll(resultPagination.results)
require(currentStack.isNotEmpty()) { "There is no elements left" }
pagination = resultPagination.nextPage()
}
return currentStack.removeFirst()
}
}
class PaginatedIterable<T>(
private val pageSize: Int,
private val countGetter: () -> Long,
private val paginationResultGetter: Pagination.() -> PaginationResult<T>
) : Iterable<T> {
override fun iterator(): Iterator<T> = PaginatedIterator(pageSize, countGetter, paginationResultGetter)
}
/**
* Will make iterable using incoming [countGetter] and [paginationResultGetter]
*/
@Suppress("NOTHING_TO_INLINE")
inline fun <T> makeIterable(
noinline countGetter: () -> Long,
pageSize: Int = defaultMediumPageSize,
noinline paginationResultGetter: Pagination.() -> PaginationResult<T>
): Iterable<T> = PaginatedIterable(pageSize, countGetter, paginationResultGetter)

View File

@@ -0,0 +1,28 @@
package dev.inmo.micro_utils.pagination.utils
import dev.inmo.micro_utils.pagination.*
/**
* Example:
*
* * `|__f__l_______________________|` will be transformed to `|_______________________f__l__|`
* * `|__f__l_|` will be transformed to `|__f__l_|`
*
* @return Reversed version of this [Pagination]
*/
fun Pagination.reverse(datasetSize: Long): SimplePagination {
val pagesNumber = calculatePagesNumber(size, datasetSize)
val newPage = pagesNumber - page - 1
return when {
page < 0 || page >= pagesNumber -> emptyPagination
else -> Pagination(
newPage,
size
)
}
}
/**
* Shortcut for [reverse]
*/
fun Pagination.reverse(objectsCount: Int) = reverse(objectsCount.toLong())

View File

@@ -0,0 +1,24 @@
package dev.inmo.micro_utils.pagination.utils
import dev.inmo.micro_utils.pagination.*
import kotlin.test.Test
import kotlin.test.assertEquals
class PaginationReversingTests {
@Test
fun testThatCommonCaseWorksOk() {
val pageSize = 3
val collectionSize = 9
assertEquals(Pagination(-1, pageSize).reverse(collectionSize), Pagination(0, 0))
val middleFirstIndex = collectionSize / 2 - pageSize / 2
val middleLastIndex = middleFirstIndex + pageSize - 1
assertEquals(
PaginationByIndexes(middleFirstIndex, middleLastIndex).reverse(collectionSize),
PaginationByIndexes(middleFirstIndex, middleLastIndex)
)
assertEquals(Pagination(calculatePagesNumber(collectionSize, pageSize), pageSize).reverse(collectionSize), Pagination(0, 0))
}
}

View File

@@ -0,0 +1 @@
<manifest package="dev.inmo.micro_utils.pagination.common"/>

View File

@@ -1,6 +1,7 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
}
apply from: "$mppProjectWithSerializationPresetPath"

View File

@@ -0,0 +1 @@
<manifest package="dev.inmo.micro_utils.pagination.ktor.common"/>

View File

@@ -1 +1 @@
{"bintrayConfig":{"repo":"MicroUtils","packageName":"${project.name}","packageVcs":"https://github.com/InsanusMokrassar/MicroUtils","autoPublish":true,"overridePublish":true},"licenses":[{"id":"Apache-2.0","title":"Apache Software License 2.0","url":"https://git.inmo.dev/InsanusMokrassar/MicroUtils_mirror/src/master/LICENSE"}],"mavenConfig":{"name":"${project.name}","description":"","url":"https://git.inmo.dev/InsanusMokrassar/MicroUtils_mirror","vcsUrl":"ssh://git@git.inmo.dev:8322/InsanusMokrassar/MicroUtils_mirror.git","developers":[{"id":"InsanusMokrassar","name":"Aleksei Ovsiannikov","eMail":"ovsyannikov.alexey95@gmail.com"},{"id":"000Sanya","name":"Syrov Aleksandr","eMail":"000sanya.000sanya@gmail.com"}]},"type":"Multiplatform"}
{"bintrayConfig":{"repo":"MicroUtils","packageName":"${project.name}","packageVcs":"https://github.com/InsanusMokrassar/MicroUtils","autoPublish":true,"overridePublish":true},"licenses":[{"id":"Apache-2.0","title":"Apache Software License 2.0","url":"https://git.inmo.dev/InsanusMokrassar/MicroUtils_mirror/src/master/LICENSE"}],"mavenConfig":{"name":"${project.name}","description":"","url":"https://git.inmo.dev/InsanusMokrassar/MicroUtils_mirror","vcsUrl":"ssh://git@git.inmo.dev:8322/InsanusMokrassar/MicroUtils_mirror.git","developers":[{"id":"InsanusMokrassar","name":"Aleksei Ovsiannikov","eMail":"ovsyannikov.alexey95@gmail.com"},{"id":"000Sanya","name":"Syrov Aleksandr","eMail":"000sanya.000sanya@gmail.com"}]}}

View File

@@ -1,59 +1,76 @@
apply plugin: 'com.jfrog.bintray'
apply plugin: 'maven-publish'
apply from: "$publishMavenPath"
bintray {
user = project.hasProperty('BINTRAY_USER') ? project.property('BINTRAY_USER') : System.getenv('BINTRAY_USER')
key = project.hasProperty('BINTRAY_KEY') ? project.property('BINTRAY_KEY') : System.getenv('BINTRAY_KEY')
filesSpec {
from "${buildDir}/publications/"
eachFile {
String directorySubname = it.getFile().parentFile.name
if (it.getName() == "module.json") {
if (directorySubname == "kotlinMultiplatform") {
it.setPath("${project.name}/${project.version}/${project.name}-${project.version}.module")
} else {
it.setPath("${project.name}-${directorySubname}/${project.version}/${project.name}-${directorySubname}-${project.version}.module")
}
} else {
if (directorySubname == "kotlinMultiplatform" && it.getName() == "pom-default.xml") {
it.setPath("${project.name}/${project.version}/${project.name}-${project.version}.pom")
} else {
it.exclude()
}
}
}
into "${project.group}".replace(".", "/")
}
publish = true
override = true
pkg {
repo = "MicroUtils"
name = "${project.name}"
vcsUrl = "https://github.com/InsanusMokrassar/MicroUtils"
licenses = ["Apache-2.0"]
version {
name = "${project.version}"
released = new Date()
vcsTag = "${project.version}"
gpg {
sign = true
passphrase = project.hasProperty('signing.gnupg.passphrase') ? project.property('signing.gnupg.passphrase') : System.getenv('signing.gnupg.passphrase')
}
}
}
task javadocsJar(type: Jar) {
classifier = 'javadoc'
}
task sourceJar (type : Jar) {
classifier = 'sources'
}
bintrayUpload.doFirst {
publications = publishing.publications.collect {
afterEvaluate {
project.publishing.publications.all {
// rename artifacts
groupId "${project.group}"
if (it.name.contains('kotlinMultiplatform')) {
null
artifactId = "${project.name}"
artifact sourceJar
} else {
it.name
artifactId = "${project.name}-$name"
}
} - null
}
}
bintrayUpload.dependsOn publishToMavenLocal
publishing {
publications.all {
artifact javadocsJar
pom {
description = ""
name = "${project.name}"
url = "https://git.inmo.dev/InsanusMokrassar/MicroUtils_mirror"
scm {
developerConnection = "scm:git:[fetch=]ssh://git@git.inmo.dev:8322/InsanusMokrassar/MicroUtils_mirror.git[push=]ssh://git@git.inmo.dev:8322/InsanusMokrassar/MicroUtils_mirror.git"
url = "ssh://git@git.inmo.dev:8322/InsanusMokrassar/MicroUtils_mirror.git"
}
developers {
developer {
id = "InsanusMokrassar"
name = "Aleksei Ovsiannikov"
email = "ovsyannikov.alexey95@gmail.com"
}
developer {
id = "000Sanya"
name = "Syrov Aleksandr"
email = "000sanya.000sanya@gmail.com"
}
}
licenses {
license {
name = "Apache Software License 2.0"
url = "https://git.inmo.dev/InsanusMokrassar/MicroUtils_mirror/src/master/LICENSE"
}
}
}
repositories {
maven {
name = "bintray"
url = uri("https://api.bintray.com/maven/${project.hasProperty('BINTRAY_USER') ? project.property('BINTRAY_USER') : System.getenv('BINTRAY_USER')}/MicroUtils/${project.name}/;publish=1;override=1")
credentials {
username = project.hasProperty('BINTRAY_USER') ? project.property('BINTRAY_USER') : System.getenv('BINTRAY_USER')
password = project.hasProperty('BINTRAY_KEY') ? project.property('BINTRAY_KEY') : System.getenv('BINTRAY_KEY')
}
}
}
}
}

View File

@@ -1,6 +1,7 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
}
apply from: "$mppProjectWithSerializationPresetPath"
@@ -15,5 +16,18 @@ kotlin {
api "com.benasher44:uuid:$uuidVersion"
}
}
jvmMain {
dependencies {
api internalProject("micro_utils.common")
}
}
androidMain {
dependencies {
api "androidx.core:core-ktx:$core_ktx_version"
api internalProject("micro_utils.common")
api internalProject("micro_utils.coroutines")
}
}
}
}

View File

@@ -0,0 +1,21 @@
package dev.inmo.micro_utils.repos
interface MapperRepo<FromKey, FromValue, ToKey, ToValue> {
suspend fun FromKey.toOutKey() = this as ToKey
suspend fun FromValue.toOutValue() = this as ToValue
suspend fun ToKey.toInnerKey() = this as FromKey
suspend fun ToValue.toInnerValue() = this as FromValue
}
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> mapper(
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
) = object : MapperRepo<FromKey, FromValue, ToKey, ToValue> {
override suspend fun FromKey.toOutKey(): ToKey = keyFromToTo()
override suspend fun FromValue.toOutValue(): ToValue = valueFromToTo()
override suspend fun ToKey.toInnerKey(): FromKey = keyToToFrom()
override suspend fun ToValue.toInnerValue(): FromValue = valueToToFrom()
}

View File

@@ -1,30 +1,110 @@
package dev.inmo.micro_utils.repos
import dev.inmo.micro_utils.pagination.Pagination
import dev.inmo.micro_utils.pagination.PaginationResult
import dev.inmo.micro_utils.pagination.*
import kotlinx.coroutines.flow.Flow
interface ReadOneToManyKeyValueRepo<Key, Value> : Repo {
suspend fun get(k: Key, pagination: Pagination, reversed: Boolean = false): PaginationResult<Value>
suspend fun keys(pagination: Pagination, reversed: Boolean = false): PaginationResult<Key>
suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean = false): PaginationResult<Key>
suspend fun contains(k: Key): Boolean
suspend fun contains(k: Key, v: Value): Boolean
suspend fun count(k: Key): Long
suspend fun count(): Long
suspend fun getAll(k: Key, reversed: Boolean = false): List<Value> = mutableListOf<Value>().also { list ->
doWithPagination {
get(k, it).also {
list.addAll(it.results)
}.nextPageIfNotEmpty()
}
if (reversed) {
list.reverse()
}
}
/**
* WARNING!!! THIS METHOD PROBABLY IS NOT EFFICIENT, USE WITH CAUTION
*/
suspend fun getAll(reverseLists: Boolean = false): Map<Key, List<Value>> = mutableMapOf<Key, List<Value>>().also { map ->
doWithPagination {
keys(it).also { paginationResult ->
paginationResult.results.forEach { k ->
map[k] = getAll(k, reverseLists)
}
}.nextPageIfNotEmpty()
}
}
}
@Deprecated("Renamed", ReplaceWith("ReadOneToManyKeyValueRepo", "dev.inmo.micro_utils.repos.ReadOneToManyKeyValueRepo"))
typealias OneToManyReadKeyValueRepo<Key, Value> = ReadOneToManyKeyValueRepo<Key, Value>
typealias ReadKeyValuesRepo<Key,Value> = ReadOneToManyKeyValueRepo<Key, Value>
interface WriteOneToManyKeyValueRepo<Key, Value> : Repo {
val onNewValue: Flow<Pair<Key, Value>>
val onValueRemoved: Flow<Pair<Key, Value>>
val onDataCleared: Flow<Key>
suspend fun add(k: Key, v: Value)
suspend fun remove(k: Key, v: Value)
suspend fun clear(k: Key)
}
@Deprecated("Renamed", ReplaceWith("WriteOneToManyKeyValueRepo", "dev.inmo.micro_utils.repos.WriteOneToManyKeyValueRepo"))
typealias OneToManyWriteKeyValueRepo<Key, Value> = WriteOneToManyKeyValueRepo<Key, Value>
suspend fun add(toAdd: Map<Key, List<Value>>)
interface OneToManyKeyValueRepo<Key, Value> : ReadOneToManyKeyValueRepo<Key, Value>, WriteOneToManyKeyValueRepo<Key, Value>
suspend fun remove(toRemove: Map<Key, List<Value>>)
suspend fun clear(k: Key)
suspend fun set(toSet: Map<Key, List<Value>>) {
toSet.keys.forEach { key -> clear(key) }
add(toSet)
}
}
typealias WriteKeyValuesRepo<Key,Value> = WriteOneToManyKeyValueRepo<Key, Value>
suspend inline fun <Key, Value, REPO : WriteOneToManyKeyValueRepo<Key, Value>> REPO.add(
keysAndValues: List<Pair<Key, List<Value>>>
) = add(keysAndValues.toMap())
suspend inline fun <Key, Value, REPO : WriteOneToManyKeyValueRepo<Key, Value>> REPO.add(
vararg keysAndValues: Pair<Key, List<Value>>
) = add(keysAndValues.toMap())
suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.add(
k: Key, v: List<Value>
) = add(mapOf(k to v))
suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.add(
k: Key, vararg v: Value
) = add(k, v.toList())
suspend inline fun <Key, Value, REPO : WriteOneToManyKeyValueRepo<Key, Value>> REPO.set(
keysAndValues: List<Pair<Key, List<Value>>>
) = set(keysAndValues.toMap())
suspend inline fun <Key, Value, REPO : WriteOneToManyKeyValueRepo<Key, Value>> REPO.set(
vararg keysAndValues: Pair<Key, List<Value>>
) = set(keysAndValues.toMap())
suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.set(
k: Key, v: List<Value>
) = set(mapOf(k to v))
suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.set(
k: Key, vararg v: Value
) = set(k, v.toList())
interface OneToManyKeyValueRepo<Key, Value> : ReadOneToManyKeyValueRepo<Key, Value>, WriteOneToManyKeyValueRepo<Key, Value>
typealias KeyValuesRepo<Key,Value> = OneToManyKeyValueRepo<Key, Value>
suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.remove(
keysAndValues: List<Pair<Key, List<Value>>>
) = remove(keysAndValues.toMap())
suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.remove(
vararg keysAndValues: Pair<Key, List<Value>>
) = remove(keysAndValues.toMap())
suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.remove(
k: Key,
v: List<Value>
) = remove(mapOf(k to v))
suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.remove(
k: Key,
vararg v: Value
) = remove(k, v.toList())

View File

@@ -10,6 +10,7 @@ interface ReadStandardCRUDRepo<ObjectType, IdType> : Repo {
suspend fun contains(id: IdType): Boolean
suspend fun count(): Long
}
typealias ReadCRUDRepo<ObjectType, IdType> = ReadStandardCRUDRepo<ObjectType, IdType>
typealias UpdatedValuePair<IdType, ValueType> = Pair<IdType, ValueType>
val <IdType> UpdatedValuePair<IdType, *>.id
@@ -27,6 +28,7 @@ interface WriteStandardCRUDRepo<ObjectType, IdType, InputValueType> : Repo {
suspend fun update(values: List<UpdatedValuePair<IdType, InputValueType>>): List<ObjectType>
suspend fun deleteById(ids: List<IdType>)
}
typealias WriteCRUDRepo<ObjectType, IdType, InputValueType> = WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>
suspend fun <ObjectType, IdType, InputValueType> WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>.create(
vararg values: InputValueType
@@ -39,4 +41,5 @@ suspend fun <ObjectType, IdType, InputValueType> WriteStandardCRUDRepo<ObjectTyp
) = deleteById(ids.toList())
interface StandardCRUDRepo<ObjectType, IdType, InputValueType> : ReadStandardCRUDRepo<ObjectType, IdType>,
WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>
WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>
typealias CRUDRepo<ObjectType, IdType, InputValueType> = StandardCRUDRepo<ObjectType, IdType, InputValueType>

View File

@@ -8,16 +8,32 @@ interface ReadStandardKeyValueRepo<Key, Value> : Repo {
suspend fun get(k: Key): Value?
suspend fun values(pagination: Pagination, reversed: Boolean = false): PaginationResult<Value>
suspend fun keys(pagination: Pagination, reversed: Boolean = false): PaginationResult<Key>
suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean = false): PaginationResult<Key>
suspend fun contains(key: Key): Boolean
suspend fun count(): Long
}
typealias ReadKeyValueRepo<Key,Value> = ReadStandardKeyValueRepo<Key, Value>
interface WriteStandardKeyValueRepo<Key, Value> : Repo {
val onNewValue: Flow<Pair<Key, Value>>
val onValueRemoved: Flow<Key>
suspend fun set(k: Key, v: Value)
suspend fun unset(k: Key)
suspend fun set(toSet: Map<Key, Value>)
suspend fun unset(toUnset: List<Key>)
}
typealias WriteKeyValueRepo<Key,Value> = WriteStandardKeyValueRepo<Key, Value>
interface StandardKeyValueRepo<Key, Value> : ReadStandardKeyValueRepo<Key, Value>, WriteStandardKeyValueRepo<Key, Value>
suspend inline fun <Key, Value> WriteStandardKeyValueRepo<Key, Value>.set(
vararg toSet: Pair<Key, Value>
) = set(toSet.toMap())
suspend inline fun <Key, Value> WriteStandardKeyValueRepo<Key, Value>.set(
k: Key, v: Value
) = set(k to v)
suspend inline fun <Key, Value> WriteStandardKeyValueRepo<Key, Value>.unset(
vararg k: Key
) = unset(k.toList())
interface StandardKeyValueRepo<Key, Value> : ReadStandardKeyValueRepo<Key, Value>, WriteStandardKeyValueRepo<Key, Value>
typealias KeyValueRepo<Key,Value> = StandardKeyValueRepo<Key, Value>

View File

@@ -0,0 +1,146 @@
package dev.inmo.micro_utils.repos.mappers
import dev.inmo.micro_utils.pagination.Pagination
import dev.inmo.micro_utils.pagination.PaginationResult
import dev.inmo.micro_utils.repos.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
open class MapperReadStandardKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
private val to: ReadStandardKeyValueRepo<ToKey, ToValue>,
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
) : ReadStandardKeyValueRepo<FromKey, FromValue>, MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper {
override suspend fun get(k: FromKey): FromValue? = to.get(
k.toOutKey()
) ?.toInnerValue()
override suspend fun values(
pagination: Pagination,
reversed: Boolean
): PaginationResult<FromValue> = to.values(
pagination,
reversed
).let {
PaginationResult(
it.page,
it.pagesNumber,
it.results.map { it.toInnerValue() },
it.size
)
}
override suspend fun keys(
pagination: Pagination,
reversed: Boolean
): PaginationResult<FromKey> = to.keys(
pagination,
reversed
).let {
PaginationResult(
it.page,
it.pagesNumber,
it.results.map { it.toInnerKey() },
it.size
)
}
override suspend fun keys(
v: FromValue,
pagination: Pagination,
reversed: Boolean
): PaginationResult<FromKey> = to.keys(
v.toOutValue(),
pagination,
reversed
).let {
PaginationResult(
it.page,
it.pagesNumber,
it.results.map { it.toInnerKey() },
it.size
)
}
override suspend fun contains(key: FromKey): Boolean = to.contains(
key.toOutKey()
)
override suspend fun count(): Long = to.count()
}
@Suppress("NOTHING_TO_INLINE")
inline fun <FromKey, FromValue, ToKey, ToValue> ReadStandardKeyValueRepo<ToKey, ToValue>.withMapper(
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
): ReadStandardKeyValueRepo<FromKey, FromValue> = MapperReadStandardKeyValueRepo(this, mapper)
@Suppress("NOTHING_TO_INLINE")
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> ReadStandardKeyValueRepo<ToKey, ToValue>.withMapper(
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
): ReadStandardKeyValueRepo<FromKey, FromValue> = withMapper(
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
)
open class MapperWriteStandardKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
private val to: WriteStandardKeyValueRepo<ToKey, ToValue>,
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
) : WriteStandardKeyValueRepo<FromKey, FromValue>, MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper {
override val onNewValue: Flow<Pair<FromKey, FromValue>> = to.onNewValue.map { (k, v) ->
k.toInnerKey() to v.toInnerValue()
}
override val onValueRemoved: Flow<FromKey> = to.onValueRemoved.map { k ->
k.toInnerKey()
}
override suspend fun set(toSet: Map<FromKey, FromValue>) = to.set(
toSet.map { (k, v) ->
k.toOutKey() to v.toOutValue()
}.toMap()
)
override suspend fun unset(toUnset: List<FromKey>) = to.unset(
toUnset.map { k ->
k.toOutKey()
}
)
}
@Suppress("NOTHING_TO_INLINE")
inline fun <FromKey, FromValue, ToKey, ToValue> WriteStandardKeyValueRepo<ToKey, ToValue>.withMapper(
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
): WriteStandardKeyValueRepo<FromKey, FromValue> = MapperWriteStandardKeyValueRepo(this, mapper)
@Suppress("NOTHING_TO_INLINE")
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> WriteStandardKeyValueRepo<ToKey, ToValue>.withMapper(
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
): WriteStandardKeyValueRepo<FromKey, FromValue> = withMapper(
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
)
open class MapperStandardKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
private val to: StandardKeyValueRepo<ToKey, ToValue>,
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
) : StandardKeyValueRepo<FromKey, FromValue>,
MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper,
ReadStandardKeyValueRepo<FromKey, FromValue> by MapperReadStandardKeyValueRepo(to, mapper),
WriteStandardKeyValueRepo<FromKey, FromValue> by MapperWriteStandardKeyValueRepo(to, mapper)
@Suppress("NOTHING_TO_INLINE")
inline fun <FromKey, FromValue, ToKey, ToValue> StandardKeyValueRepo<ToKey, ToValue>.withMapper(
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
): StandardKeyValueRepo<FromKey, FromValue> = MapperStandardKeyValueRepo(this, mapper)
@Suppress("NOTHING_TO_INLINE")
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> StandardKeyValueRepo<ToKey, ToValue>.withMapper(
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
): StandardKeyValueRepo<FromKey, FromValue> = withMapper(
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
)

View File

@@ -0,0 +1,155 @@
package dev.inmo.micro_utils.repos.mappers
import dev.inmo.micro_utils.pagination.Pagination
import dev.inmo.micro_utils.pagination.PaginationResult
import dev.inmo.micro_utils.repos.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
open class MapperReadOneToManyKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
private val to: ReadOneToManyKeyValueRepo<ToKey, ToValue>,
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
) : ReadOneToManyKeyValueRepo<FromKey, FromValue>, MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper {
override suspend fun get(
k: FromKey,
pagination: Pagination,
reversed: Boolean
): PaginationResult<FromValue> = to.get(
k.toOutKey(),
pagination,
reversed
).let {
PaginationResult(
it.page,
it.pagesNumber,
it.results.map { it.toInnerValue() },
it.size
)
}
override suspend fun keys(
pagination: Pagination,
reversed: Boolean
): PaginationResult<FromKey> = to.keys(
pagination,
reversed
).let {
PaginationResult(
it.page,
it.pagesNumber,
it.results.map { it.toInnerKey() },
it.size
)
}
override suspend fun keys(
v: FromValue,
pagination: Pagination,
reversed: Boolean
): PaginationResult<FromKey> = to.keys(
v.toOutValue(),
pagination,
reversed
).let {
PaginationResult(
it.page,
it.pagesNumber,
it.results.map { it.toInnerKey() },
it.size
)
}
override suspend fun contains(k: FromKey): Boolean = to.contains(k.toOutKey())
override suspend fun contains(k: FromKey, v: FromValue): Boolean = to.contains(k.toOutKey(), v.toOutValue())
override suspend fun count(): Long = to.count()
override suspend fun count(k: FromKey): Long = to.count(k.toOutKey())
}
@Suppress("NOTHING_TO_INLINE")
inline fun <FromKey, FromValue, ToKey, ToValue> ReadOneToManyKeyValueRepo<ToKey, ToValue>.withMapper(
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
): ReadOneToManyKeyValueRepo<FromKey, FromValue> = MapperReadOneToManyKeyValueRepo(this, mapper)
@Suppress("NOTHING_TO_INLINE")
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> ReadOneToManyKeyValueRepo<ToKey, ToValue>.withMapper(
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
): ReadOneToManyKeyValueRepo<FromKey, FromValue> = withMapper(
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
)
open class MapperWriteOneToManyKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
private val to: WriteOneToManyKeyValueRepo<ToKey, ToValue>,
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
) : WriteOneToManyKeyValueRepo<FromKey, FromValue>, MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper {
override val onNewValue: Flow<Pair<FromKey, FromValue>> = to.onNewValue.map { (k, v) ->
k.toInnerKey() to v.toInnerValue()
}
override val onValueRemoved: Flow<Pair<FromKey, FromValue>> = to.onValueRemoved.map { (k, v) ->
k.toInnerKey() to v.toInnerValue()
}
override val onDataCleared: Flow<FromKey> = to.onDataCleared.map { k ->
k.toInnerKey()
}
override suspend fun add(toAdd: Map<FromKey, List<FromValue>>) = to.add(
toAdd.map { (k, v) ->
k.toOutKey() to v.map { it.toOutValue() }
}.toMap()
)
override suspend fun remove(toRemove: Map<FromKey, List<FromValue>>) = to.remove(
toRemove.map { (k, v) ->
k.toOutKey() to v.map { it.toOutValue() }
}.toMap()
)
override suspend fun set(toSet: Map<FromKey, List<FromValue>>) {
to.set(
toSet.map { (k, vs) -> k.toOutKey() to vs.map { v -> v.toOutValue() } }.toMap()
)
}
override suspend fun clear(k: FromKey) = to.clear(k.toOutKey())
}
@Suppress("NOTHING_TO_INLINE")
inline fun <FromKey, FromValue, ToKey, ToValue> WriteOneToManyKeyValueRepo<ToKey, ToValue>.withMapper(
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
): WriteOneToManyKeyValueRepo<FromKey, FromValue> = MapperWriteOneToManyKeyValueRepo(this, mapper)
@Suppress("NOTHING_TO_INLINE")
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> WriteOneToManyKeyValueRepo<ToKey, ToValue>.withMapper(
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
): WriteOneToManyKeyValueRepo<FromKey, FromValue> = withMapper(
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
)
open class MapperOneToManyKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
private val to: OneToManyKeyValueRepo<ToKey, ToValue>,
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
) : OneToManyKeyValueRepo<FromKey, FromValue>,
MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper,
ReadOneToManyKeyValueRepo<FromKey, FromValue> by MapperReadOneToManyKeyValueRepo(to, mapper),
WriteOneToManyKeyValueRepo<FromKey, FromValue> by MapperWriteOneToManyKeyValueRepo(to, mapper)
@Suppress("NOTHING_TO_INLINE")
inline fun <FromKey, FromValue, ToKey, ToValue> OneToManyKeyValueRepo<ToKey, ToValue>.withMapper(
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
): OneToManyKeyValueRepo<FromKey, FromValue> = MapperOneToManyKeyValueRepo(this, mapper)
@Suppress("NOTHING_TO_INLINE")
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> OneToManyKeyValueRepo<ToKey, ToValue>.withMapper(
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
): OneToManyKeyValueRepo<FromKey, FromValue> = withMapper(
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
)

View File

@@ -1,7 +1,7 @@
package dev.inmo.micro_utils.repos.pagination
import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.repos.*
import dev.inmo.micro_utils.repos.ReadStandardKeyValueRepo
suspend inline fun <Key, Value, REPO : ReadStandardKeyValueRepo<Key, Value>> REPO.doForAll(
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")

View File

@@ -1,7 +1,7 @@
package dev.inmo.micro_utils.repos.pagination
import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.repos.*
import dev.inmo.micro_utils.repos.ReadOneToManyKeyValueRepo
suspend inline fun <Key, Value, REPO : ReadOneToManyKeyValueRepo<Key, Value>> REPO.doForAll(
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")

View File

@@ -0,0 +1,13 @@
package dev.inmo.micro_utils.repos.versions
import dev.inmo.micro_utils.repos.StandardKeyValueRepo
import dev.inmo.micro_utils.repos.set
class KeyValueBasedVersionsRepoProxy<T>(
private val keyValueStore: StandardKeyValueRepo<String, Int>,
override val database: T
) : StandardVersionsRepoProxy<T> {
override suspend fun getTableVersion(tableName: String): Int? = keyValueStore.get(tableName)
override suspend fun updateTableVersion(tableName: String, version: Int) { keyValueStore.set(tableName, version) }
}

View File

@@ -0,0 +1,36 @@
package dev.inmo.micro_utils.repos.versions
import dev.inmo.micro_utils.repos.Repo
interface StandardVersionsRepoProxy<T> : Repo {
val database: T
suspend fun getTableVersion(tableName: String): Int?
suspend fun updateTableVersion(tableName: String, version: Int)
}
class StandardVersionsRepo<T>(
private val proxy: StandardVersionsRepoProxy<T>
) : VersionsRepo<T> {
override suspend fun setTableVersion(
tableName: String,
version: Int,
onCreate: suspend T.() -> Unit,
onUpdate: suspend T.(from: Int, to: Int) -> Unit
) {
var savedVersion = proxy.getTableVersion(tableName)
if (savedVersion == null) {
proxy.database.onCreate()
proxy.updateTableVersion(tableName, version)
} else {
while (savedVersion != null && savedVersion < version) {
val newVersion = savedVersion + 1
proxy.database.onUpdate(savedVersion, newVersion)
proxy.updateTableVersion(tableName, newVersion)
savedVersion = newVersion
}
}
}
}

View File

@@ -0,0 +1,31 @@
package dev.inmo.micro_utils.repos.versions
import dev.inmo.micro_utils.repos.Repo
/**
* This interface has been created due to requirement to work with different versions of databases and make some
* migrations between versions
*
* @param T It is a type of database, which will be used by this repo to retrieve current table version and update it
*/
interface VersionsRepo<T> : Repo {
/**
* By default, instance of this interface will check that version of table with name [tableName] is less than
* [version] or is absent
*
* * In case if [tableName] didn't found, will be called [onCreate] and version of table will be set up to [version]
* * In case if [tableName] have version less than parameter [version], it will increase version one-by-one
* until database version will be equal to [version]
*
* @param version Current version of table
* @param onCreate This callback will be called in case when table have no information about table
* @param onUpdate This callback will be called after **iterative** changing of version. It is expected that parameter
* "to" will always be greater than "from"
*/
suspend fun setTableVersion(
tableName: String,
version: Int,
onCreate: suspend T.() -> Unit = {},
onUpdate: suspend T.(from: Int, to: Int) -> Unit = { _, _ ->}
)
}

View File

@@ -0,0 +1,171 @@
package dev.inmo.micro_utils.repos
import dev.inmo.micro_utils.common.Warning
import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.pagination.utils.reverse
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import java.io.File
import java.nio.file.FileSystems
import java.nio.file.Path
import java.nio.file.StandardWatchEventKinds.*
private inline val String.isAbsolute
get() = startsWith(File.separator)
class FileReadStandardKeyValueRepo(
private val folder: File
) : ReadStandardKeyValueRepo<String, File> {
init {
folder.mkdirs()
}
override suspend fun get(k: String): File? {
val file = File(folder, k)
if (file.exists()) {
return file
}
return null
}
override suspend fun values(pagination: Pagination, reversed: Boolean): PaginationResult<File> {
val count = count()
val resultPagination = if (reversed) pagination.reverse(count) else pagination
val filesPaths = folder.list() ?.copyOfRange(resultPagination.firstIndex, resultPagination.lastIndex) ?: return emptyPaginationResult()
if (reversed) {
filesPaths.reverse()
}
return filesPaths.map { File(folder, it) }.createPaginationResult(
resultPagination,
count
)
}
override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult<String> {
val count = count()
val resultPagination = if (reversed) pagination.reverse(count) else pagination
val filesPaths = folder.list() ?.copyOfRange(resultPagination.firstIndex, resultPagination.lastIndex) ?: return emptyPaginationResult()
if (reversed) {
filesPaths.reverse()
}
return filesPaths.toList().createPaginationResult(
resultPagination,
count
)
}
override suspend fun keys(
v: File,
pagination: Pagination,
reversed: Boolean
): PaginationResult<String> {
val resultPagination = if (reversed) pagination.reverse(1L) else pagination
return if (resultPagination.isFirstPage) {
val fileSubpath = v.absolutePath.removePrefix(folder.absolutePath)
if (fileSubpath == v.absolutePath) {
emptyList()
} else {
listOf(fileSubpath)
}
} else {
emptyList()
}.createPaginationResult(resultPagination, 1L)
}
override suspend fun contains(key: String): Boolean {
return File(folder, key).exists()
}
override suspend fun count(): Long = folder.list() ?.size ?.toLong() ?: 0L
}
/**
* Files watching will not correctly works on Android with version of API lower than API 26
*/
@Warning("Files watching will not correctly works on Android Platform with version of API lower than API 26")
class FileWriteStandardKeyValueRepo(
private val folder: File,
filesChangedProcessingScope: CoroutineScope? = null
) : WriteStandardKeyValueRepo<String, File> {
private val _onNewValue = MutableSharedFlow<Pair<String, File>>()
override val onNewValue: Flow<Pair<String, File>> = _onNewValue.asSharedFlow()
private val _onValueRemoved = MutableSharedFlow<String>()
override val onValueRemoved: Flow<String> = _onValueRemoved.asSharedFlow()
init {
folder.mkdirs()
filesChangedProcessingScope ?.let {
it.launch {
try {
val folderPath = folder.toPath()
while (isActive) {
val key = try {
folderPath.register(FileSystems.getDefault().newWatchService(), ENTRY_CREATE, ENTRY_MODIFY, ENTRY_DELETE)
} catch (e: Exception) {
// add verbose way to show that file watching is not working
return@launch
}
for (event in key.pollEvents()) {
val relativeFilePath = (event.context() as? Path) ?: continue
val file = relativeFilePath.toFile()
val relativePath = file.toRelativeString(folder)
when (event.kind()) {
ENTRY_CREATE, ENTRY_MODIFY -> {
launch {
_onNewValue.emit(relativePath to file)
}
}
ENTRY_DELETE -> {
launch {
_onValueRemoved.emit(relativePath)
}
}
}
}
if (key.isValid || folder.exists()) {
continue
}
break
}
} catch (e: Throwable) {
// add verbose way to notify that this functionality is disabled
}
}
}
}
override suspend fun set(toSet: Map<String, File>) {
supervisorScope {
toSet.map { (filename, fileSource) ->
launch {
val file = File(folder, filename)
file.delete()
fileSource.copyTo(file, overwrite = true)
_onNewValue.emit(filename to file)
}
}
}.joinAll()
}
override suspend fun unset(toUnset: List<String>) {
toUnset.forEach {
val file = File(folder, it)
if (file.exists()) {
file.delete()
_onValueRemoved.emit(it)
}
}
}
}
@Warning("Files watching will not correctly works on Android Platform with version of API lower than API 26")
class FileStandardKeyValueRepo(
folder: File,
filesChangedProcessingScope: CoroutineScope? = null
) : StandardKeyValueRepo<String, File>,
WriteStandardKeyValueRepo<String, File> by FileWriteStandardKeyValueRepo(folder, filesChangedProcessingScope),
ReadStandardKeyValueRepo<String, File> by FileReadStandardKeyValueRepo(folder)

View File

@@ -0,0 +1 @@
<manifest package="dev.inmo.micro_utils.repos.common"/>

View File

@@ -0,0 +1,46 @@
package dev.inmo.micro_utils.repos
sealed class ColumnType(
typeName: String,
nullable: Boolean
) {
open val asType: String = "$typeName${if (!nullable) " not null" else ""}"
sealed class Text(
nullable: Boolean
) : ColumnType("text", nullable) {
object NULLABLE : Text(true)
object NOT_NULLABLE : Text(false)
}
sealed class Numeric(
typeName: String,
autoincrement: Boolean = false,
primaryKey: Boolean = false,
nullable: Boolean = false
) : ColumnType(
typeName,
nullable
) {
override val asType: String = "${super.asType}${if (primaryKey) " primary key" else ""}${if (autoincrement) " autoincrement" else ""}"
class INTEGER(
autoincrement: Boolean = false,
primaryKey: Boolean = false,
nullable: Boolean = false
) : Numeric(
"integer",
autoincrement,
primaryKey,
nullable
)
class DOUBLE(autoincrement: Boolean = false, primaryKey: Boolean = false, nullable: Boolean = false) : Numeric(
"double",
autoincrement,
primaryKey,
nullable
)
}
override fun toString(): String {
return asType
}
}

View File

@@ -0,0 +1,8 @@
package dev.inmo.micro_utils.repos
import androidx.core.content.contentValuesOf
@Suppress("UNCHECKED_CAST", "SimplifiableCall")
fun contentValuesOfNotNull(vararg pairs: Pair<String, Any?>?) = contentValuesOf(
*(pairs.filter { it != null } as List<Pair<String, Any?>>).toTypedArray()
)

View File

@@ -0,0 +1,21 @@
package dev.inmo.micro_utils.repos
import android.database.Cursor
inline fun <T> Cursor.map(
block: (Cursor) -> T
): List<T> {
val result = mutableListOf<T>()
if (moveToFirst()) {
do {
result.add(block(this))
} while (moveToNext())
}
return result
}
fun Cursor.firstOrNull(): Cursor? = if (moveToFirst()) {
this
} else {
null
}

View File

@@ -0,0 +1,6 @@
package dev.inmo.micro_utils.repos
import kotlinx.coroutines.Dispatchers
import kotlin.coroutines.CoroutineContext
val DatabaseCoroutineContext: CoroutineContext = Dispatchers.IO

View File

@@ -0,0 +1,64 @@
package dev.inmo.micro_utils.repos
import android.database.Cursor
import android.database.sqlite.SQLiteDatabase
fun createTableQuery(
tableName: String,
vararg columnsToTypes: Pair<String, ColumnType>
) = "create table $tableName (${columnsToTypes.joinToString(", ") { "${it.first} ${it.second}" }});"
fun SQLiteDatabase.createTable(
tableName: String,
vararg columnsToTypes: Pair<String, ColumnType>,
onInit: (SQLiteDatabase.() -> Unit)? = null
): Boolean {
val existing = rawQuery("SELECT name FROM sqlite_master WHERE type='table' AND name='$tableName'", null).use {
it.count > 0
}
return if (existing) {
false
// TODO:: add upgrade opportunity
} else {
execSQL(createTableQuery(tableName, *columnsToTypes))
onInit ?.invoke(this)
true
}
}
fun Cursor.getString(columnName: String) = getString(
getColumnIndex(columnName)
)
fun Cursor.getLong(columnName: String) = getLong(
getColumnIndex(columnName)
)
fun Cursor.getInt(columnName: String) = getInt(
getColumnIndex(columnName)
)
fun Cursor.getDouble(columnName: String) = getDouble(
getColumnIndex(columnName)
)
fun SQLiteDatabase.select(
table: String,
columns: Array<String>? = null,
selection: String? = null,
selectionArgs: Array<String>? = null,
groupBy: String? = null,
having: String? = null,
orderBy: String? = null,
limit: String? = null
) = query(
table, columns, selection, selectionArgs, groupBy, having, orderBy, limit
)
fun makePlaceholders(count: Int): String {
return (0 until count).joinToString { "?" }
}
fun makeStringPlaceholders(count: Int): String {
return (0 until count).joinToString { "\"?\"" }
}

View File

@@ -0,0 +1,8 @@
package dev.inmo.micro_utils.repos
import dev.inmo.micro_utils.pagination.Pagination
import dev.inmo.micro_utils.pagination.firstIndex
fun limitClause(size: Long, since: Long? = null) = "${since ?.let { "$it, " } ?: ""}$size"
fun limitClause(size: Int, since: Int? = null) = limitClause(size.toLong(), since ?.toLong())
fun Pagination.limitClause() = limitClause(size, firstIndex)

View File

@@ -0,0 +1,87 @@
package dev.inmo.micro_utils.repos
import android.database.sqlite.SQLiteDatabase
import dev.inmo.micro_utils.coroutines.safely
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import java.util.concurrent.Executors
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.coroutineContext
private object ContextsPool {
private val contexts = mutableListOf<CoroutineContext>()
private val mutex = Mutex(locked = false)
private val freeContexts = mutableListOf<CoroutineContext>()
suspend fun acquireContext(): CoroutineContext {
return mutex.withLock {
freeContexts.removeFirstOrNull() ?: Executors.newSingleThreadExecutor().asCoroutineDispatcher().also {
contexts.add(it)
}
}
}
suspend fun freeContext(context: CoroutineContext) {
return mutex.withLock {
if (context in contexts && context !in freeContexts) {
freeContexts.add(context)
}
}
}
suspend fun <T> use(block: suspend (CoroutineContext) -> T): T = acquireContext().let {
try {
block(it)
} finally {
freeContext(it)
}
}
}
class TransactionContext(
val databaseContext: CoroutineContext
): CoroutineContext.Element {
override val key: CoroutineContext.Key<TransactionContext> = TransactionContext
companion object : CoroutineContext.Key<TransactionContext>
}
suspend fun <T> SQLiteDatabase.transaction(block: suspend SQLiteDatabase.() -> T): T {
return coroutineContext[TransactionContext] ?.let {
withContext(it.databaseContext) {
block()
}
} ?: ContextsPool.use { context ->
withContext(TransactionContext(context) + context) {
beginTransaction()
safely(
{
endTransaction()
throw it
}
) {
block().also {
setTransactionSuccessful()
endTransaction()
}
}
}
}
}
inline fun <T> SQLiteDatabase.inlineTransaction(block: SQLiteDatabase.() -> T): T {
return when {
inTransaction() -> block()
else -> {
beginTransaction()
try {
block().also {
setTransactionSuccessful()
}
} finally {
endTransaction()
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More