Compare commits

...

722 Commits

Author SHA1 Message Date
3a5544206b update ktor 2023-04-19 19:46:39 +06:00
e17e2f7fb8 start 0.17.8 2023-04-19 19:45:58 +06:00
6d8a8ab018 Merge pull request #233 from InsanusMokrassar/0.17.7
0.17.7
2023-04-18 19:37:06 +06:00
a7dce8fa78 update dependencies 2023-04-18 19:24:05 +06:00
ca73ff8e19 add support of native in startup 2023-04-18 19:20:39 +06:00
d01ad10d7d start 0.17.7 2023-04-18 19:20:39 +06:00
81041ee43c Merge pull request #232 from InsanusMokrassar/renovate/configure
Configure Renovate
2023-04-18 19:20:15 +06:00
renovate[bot]
6e004c2ae4 Add renovate.json 2023-04-18 08:23:10 +00:00
0e2fac5b22 Merge pull request #231 from InsanusMokrassar/0.17.6
0.17.6
2023-04-13 11:27:18 +06:00
269da7f155 update dependencies and fill changelog 2023-04-13 11:20:34 +06:00
3cb6b73ee0 add preloadImage 2023-04-07 01:17:04 +06:00
a938ee1efb simplify API of MPPFile.input 2023-04-04 01:26:18 +06:00
6ea5e2e5a6 update version 2023-04-03 22:54:57 +06:00
617dfb54e0 experimentally add linuxx64 and mingwx64 as target platforms 2023-04-03 22:35:41 +06:00
d23e005985 preview of files realization 2023-04-03 15:49:46 +06:00
e5207f5bc5 Revert "Revert "trying to add native support""
This reverts commit c96cea8db0.
2023-04-03 10:26:03 +06:00
c96cea8db0 Revert "trying to add native support"
This reverts commit 0a8e71d76a.
2023-04-03 10:22:53 +06:00
0a8e71d76a trying to add native support 2023-04-03 10:22:43 +06:00
cf1fd32b08 Merge pull request #230 from InsanusMokrassar/0.17.5
0.17.5
2023-03-10 22:30:32 +06:00
cc4224ea1f update kv and crud tests 2023-03-10 22:08:35 +06:00
f4c148bc58 add map type info in ktor read kv and crud repos 2023-03-10 21:41:01 +06:00
022297ad3f fixes in AutoRecacheReadKeyValueRepo 2023-03-10 19:37:36 +06:00
5180d6fc3e hotfixes 2023-03-10 18:43:43 +06:00
eeebbff70d getAll 2023-03-10 18:37:48 +06:00
afc6aeea15 fixes 2023-03-10 16:14:51 +06:00
486515eddd update dokka 2023-03-10 16:09:33 +06:00
0e21699cd1 several updates 2023-03-10 15:45:02 +06:00
f1678ef7cf start 0.17.5 2023-03-10 15:36:36 +06:00
cea65fc76e Merge pull request #229 from InsanusMokrassar/0.17.4
0.17.4
2023-03-09 23:23:55 +06:00
c9e320b72a update compose version 2023-03-09 23:22:33 +06:00
555956087d add android manifest to mapper module 2023-03-09 22:23:56 +06:00
b3f468f901 add docs to mapper serialization and allow to use them as supertypes 2023-03-09 22:21:22 +06:00
f5f7511781 add mapper serializer 2023-03-09 21:55:07 +06:00
4be1d93f60 start 0.17.4 2023-03-09 21:49:15 +06:00
7d684608ef Merge pull request #228 from InsanusMokrassar/0.17.3
0.17.3
2023-03-07 23:30:23 +06:00
2c7fd320eb fixed 2023-03-07 22:28:56 +06:00
88ee82e1c6 add Diff#isEmpty 2023-03-07 21:50:23 +06:00
d6402c624e optimization of nonstrict comparison 2023-03-07 19:14:43 +06:00
8b9c93bc10 diffs improvement 2023-03-07 19:12:12 +06:00
4f5e261d01 start 0.17.3 2023-03-07 18:59:34 +06:00
cf455aebe6 Merge pull request #227 from InsanusMokrassar/0.17.2
0.17.2
2023-03-02 21:57:49 +06:00
1380d5f8e1 fill changelog 2023-03-02 21:38:20 +06:00
5f65260bfe Update DefaultStatesManager.kt 2023-03-02 21:35:59 +06:00
11f0dcfc01 Update DefaultStatesManager.kt 2023-03-02 21:32:58 +06:00
555b7886a4 Update gradle.properties 2023-03-02 21:28:50 +06:00
3707a6c0ce Merge pull request #226 from InsanusMokrassar/0.17.1
0.17.1
2023-02-28 19:36:08 +06:00
4c8d92b4c3 update ktor 2023-02-28 19:32:32 +06:00
8bbd33c896 now all android modules depends on jvm 2023-02-28 19:08:39 +06:00
ac33a3580f start 0.17.1 2023-02-28 19:04:04 +06:00
a64a32fbe6 Merge pull request #225 from InsanusMokrassar/0.17.0
0.17.0
2023-02-28 12:15:41 +06:00
9493e97a38 remove redundant part from defaultAndroidSettings.gradle 2023-02-27 22:46:08 +06:00
88bd770260 fill dependencies updates changelog 2023-02-27 22:45:35 +06:00
a7bd33b7bf update kotlin and ksp versions 2023-02-27 17:53:37 +06:00
73c724a2e5 small cleanup in build scripts 2023-02-27 17:03:09 +06:00
d8cf3c6726 update dependencies and at least it is buildable 2023-02-27 16:56:39 +06:00
15dee238b5 start 0.17.0 2023-02-27 15:54:37 +06:00
c70626734e Merge pull request #224 from InsanusMokrassar/0.16.13
0.16.13
2023-02-27 15:51:20 +06:00
5a765ea1bc get back publishing signing 2023-02-27 15:43:52 +06:00
8215f9d2c6 temporal solution of generating problem 2023-02-26 14:37:41 +06:00
d2e6d2ec80 generators for models has been created 2023-02-25 19:56:12 +06:00
3718181e8f start 0.16.13 2023-02-25 18:44:07 +06:00
0d825cf424 Merge pull request #223 from InsanusMokrassar/0.16.12
0.16.12
2023-02-22 10:30:45 +06:00
28a804d988 improve start launcher plugin logs 2023-02-22 10:28:25 +06:00
9e4bb9d678 add readme to generator and add several fixes in processor 2023-02-22 10:20:15 +06:00
9c40d7da3d add generator for koin definitions 2023-02-22 01:34:42 +06:00
2b76ad0aa9 improvements in selectByIds of CommonExposedRepo 2023-02-20 11:55:14 +06:00
e4b619e050 start 0.16.12 2023-02-20 11:54:26 +06:00
36c09feaf2 Merge pull request #222 from InsanusMokrassar/0.16.11
0.16.11
2023-02-20 00:49:12 +06:00
2d68321503 Update build.gradle 2023-02-20 00:19:27 +06:00
85455ab21c improvements in language codes 2023-02-19 19:34:42 +06:00
18d63eb980 start 0.16.11 2023-02-19 19:20:03 +06:00
2e429e9704 Merge pull request #221 from InsanusMokrassar/0.16.10
0.16.10
2023-02-17 13:54:09 +06:00
f4af28059b improvements in ReadKeyValueFromCRUDRepo 2023-02-17 13:45:22 +06:00
c1476bd075 add ReadCRUDFromKeyValueRepo 2023-02-15 20:16:54 +06:00
16c720fddd start 0.16.10 2023-02-15 20:14:55 +06:00
8b4b4a5eca Merge pull request #220 from InsanusMokrassar/0.16.9
0.16.9
2023-02-13 16:09:55 +06:00
32e6e5b7e2 Update CHANGELOG.md 2023-02-13 16:09:44 +06:00
a9f7fd8e32 new extension HttpResponse.bodyOrNullOnNoContent 2023-02-10 15:17:08 +06:00
95be1a26f2 improvements in HttpResponse.bodyOrNull 2023-02-10 15:14:03 +06:00
ef9b31aee0 start 0.16.9 2023-02-05 18:17:20 +06:00
df3c01ff0a Merge pull request #219 from InsanusMokrassar/0.16.8
0.16.8
2023-02-04 01:46:43 +06:00
4704c5a33d Update CHANGELOG.md 2023-02-04 01:39:49 +06:00
225c06550a fixes in FileKeyValueRepo 2023-02-03 10:23:52 +06:00
f0987614c6 fixes in FileKeyValueRepo 2023-02-03 10:07:24 +06:00
269c2876f3 actualize all make it possible to disable clear on actualization 2023-02-02 20:07:31 +06:00
168d6acf7c actualizeAll now is overridable in auto recache repos 2023-02-02 20:05:25 +06:00
a5f718e257 add meppers for kv<->kvs repos 2023-02-02 12:29:00 +06:00
4f68459582 start add kv to kvs transformations 2023-02-02 10:15:37 +06:00
442db122cf remove redundant id getter in keyvalues autocached repo 2023-02-01 23:02:06 +06:00
580d757be2 fixes in download file and upgrade ktor version 2023-02-01 22:28:17 +06:00
47b0f6d2d8 fixes 2023-02-01 13:03:51 +06:00
3f6f6ebc2b realize invalidate method in fallback cache repos 2023-01-31 10:09:55 +06:00
2645ea29d6 Update ActionWrapper.kt 2023-01-31 00:11:45 +06:00
79f2041565 Update AutoRecacheReadCRUDRepo.kt 2023-01-30 23:59:48 +06:00
4a7567f288 Update AutoRecacheWriteCRUDRepo.kt 2023-01-30 23:59:19 +06:00
8a890ed6ed Update AutoRecacheReadKeyValueRepo.kt 2023-01-30 23:58:43 +06:00
3d90df6897 Update AutoRecacheWriteKeyValueRepo.kt 2023-01-30 23:58:11 +06:00
681c13144a Update AutoRecacheReadKeyValuesRepo.kt 2023-01-30 23:57:40 +06:00
b64f2e6d32 Update AutoRecacheWriteKeyValuesRepo.kt 2023-01-30 23:56:57 +06:00
428eabb1bd Create FallbackCacheRepo.kt 2023-01-30 23:54:32 +06:00
2162e83bce fixes in actualizeAll 2023-01-29 22:51:39 +06:00
6142022283 improve actualizeAll to lazily clear repo 2023-01-29 22:46:32 +06:00
e6d9c8250f add all autorecaches repos 2023-01-29 22:18:22 +06:00
46178e723b fixes 2023-01-29 20:54:32 +06:00
605f55acd2 fixes in Write* cache repos 2023-01-29 20:35:10 +06:00
0f8b69aa60 start 0.16.8 2023-01-29 20:35:10 +06:00
551d8ec480 Merge pull request #218 from InsanusMokrassar/0.16.7
0.16.7
2023-01-29 13:20:16 +06:00
fc48446ec4 temporarily remove fallback repo 2023-01-29 13:15:56 +06:00
3644b83ac6 fill changelog 2023-01-29 13:08:41 +06:00
cd73791b6f add docs to the MutableState.asState 2023-01-29 12:46:27 +06:00
03de71df2e add docs to as compose state 2023-01-29 12:43:51 +06:00
83d5d3faf4 improve flow as state functionality 2023-01-29 12:28:22 +06:00
0c8bec4c89 add extension actualizeAll 2023-01-27 15:15:27 +06:00
7fc93817c1 start to add fallback repos 2023-01-27 14:45:31 +06:00
d0a00031a1 Update CHANGELOG.md 2023-01-26 22:54:17 +06:00
6ebc5aa0c2 potential fix of state value change in asMutableComposeState 2023-01-23 15:21:47 +06:00
8a6b4bb49e add alsoIfTrue/alsoIfFalse/letIfTrue/letIfFalse 2023-01-22 23:01:06 +06:00
20799b9a3e add repeat on failure with callback 2023-01-22 22:43:26 +06:00
ec3afc615c add serializable diff 2023-01-20 13:29:45 +06:00
da692ccfc3 add ifTrue/ifFalse 2023-01-19 20:21:35 +06:00
53b89f3a18 start 0.16.7 2023-01-19 20:19:21 +06:00
58cded28d3 Merge pull request #217 from InsanusMokrassar/0.16.6
0.16.6
2023-01-18 22:28:44 +06:00
592c5f3732 Column extensions eqOrIsNull and neqOrIsNotNull 2023-01-14 20:17:57 +06:00
f44a78a5f5 small improvement of openBaseWebSocketFlow 2023-01-14 15:20:52 +06:00
e0bdd5dfdc improvements in microutils 2023-01-14 13:22:07 +06:00
99c0f06b72 repos update 2023-01-14 13:15:59 +06:00
66fc6df3d7 Improvements in 'StartLauncherPlugin#start' methods 2023-01-08 13:52:53 +06:00
a36425a905 start 0.16.6 2023-01-08 13:51:34 +06:00
d920fee6d4 Merge pull request #216 from InsanusMokrassar/0.16.5
0.16.5
2023-01-04 20:21:15 +06:00
23590be5de Update CHANGELOG.md 2023-01-04 20:20:25 +06:00
94acc3c93b Update libs.versions.toml 2023-01-04 08:56:46 +06:00
5616326a3b start 0.16.5 2023-01-04 08:50:19 +06:00
7601860c5c Merge pull request #214 from InsanusMokrassar/0.16.4
0.16.4
2022-12-27 19:03:35 +06:00
8b43d785cc update changelog 2022-12-27 18:51:01 +06:00
b62d3a0b7d launchInCurrentThread 2022-12-27 18:50:41 +06:00
fad73c7213 start 0.16.4 2022-12-27 18:49:04 +06:00
2403c7c2b0 Merge pull request #213 from InsanusMokrassar/0.16.3
0.16.3
2022-12-25 13:24:37 +06:00
fa090bf920 Update StartLauncherPlugin.kt 2022-12-25 10:29:16 +06:00
a83ee86340 improvements in startup launcher 2022-12-25 10:26:44 +06:00
204955bcce add template for startup and readme 2022-12-19 17:51:05 +06:00
ee56e9543a start 0.16.3 2022-12-17 17:27:15 +06:00
96fdff6ffd Merge pull request #212 from InsanusMokrassar/0.16.2
0.16.2
2022-12-16 13:59:47 +06:00
58b007cbb3 fill changelog 2022-12-16 13:59:24 +06:00
4f0c139889 remove redundant stop koin expect-actual 2022-12-16 11:58:18 +06:00
c584c24fce fixes and improvements 2022-12-15 15:07:10 +06:00
85e5cee154 replace createStartupPluginAndRegister 2022-12-15 14:59:07 +06:00
5af91981f1 upgrades and filling of README 2022-12-15 10:26:31 +06:00
2fe4f08059 update compose 2022-12-15 08:51:53 +06:00
83796f345a start fix startup 2022-12-14 22:26:23 +06:00
c1e21364a6 start 0.16.2 2022-12-14 21:40:18 +06:00
067d9d0d3b Merge pull request #211 from InsanusMokrassar/0.16.1
0.16.1
2022-12-09 19:58:54 +06:00
03f527d83e Update CHANGELOG.md 2022-12-09 19:46:58 +06:00
ced05a4586 improve default runCatchingSafely/safelyWithResult and add suspend variances of safe/unsafe SafeWrapper interface 2022-12-09 12:14:24 +06:00
43fe06206a add safe wrapper 2022-12-09 11:09:32 +06:00
023657558e start 0.16.1 2022-12-09 10:52:53 +06:00
9b0b726c80 Merge pull request #210 from InsanusMokrassar/0.16.0
0.16.0
2022-12-08 09:29:12 +06:00
4ee67321c4 fill changelog and update android dependencies 2022-12-08 09:26:59 +06:00
59f1f2e59b Update libs.versions.toml 2022-12-08 08:53:37 +06:00
0766d48b7c Update libs.versions.toml 2022-12-08 07:56:04 +06:00
e18903b9e9 Update gradle.properties 2022-12-08 07:54:53 +06:00
d0eecdead2 Update gradle.properties 2022-12-08 07:53:59 +06:00
cc4a83a033 Update gradle.properties 2022-12-08 07:53:08 +06:00
1cf911bbde Update build.gradle 2022-12-07 19:40:30 +06:00
36d73d5023 Update StartupLaunchingTests.kt 2022-12-07 11:39:45 +06:00
c395242e3e fixes in StartupLauncingTests 2022-12-07 10:50:51 +06:00
cd9cd7cc5d Merge pull request #208 from InsanusMokrassar/0.15.1
0.15.1
2022-12-07 09:43:48 +06:00
acbb8a0c07 complete prepare startup module 2022-12-06 13:15:08 +06:00
b9d8528599 complete kdocs for the startup module 2022-12-06 13:14:05 +06:00
4971326eca add kdocs for the config of startup 2022-12-06 13:08:37 +06:00
09d1047260 add kdocs to the startup module 2022-12-06 13:06:21 +06:00
02dbd493c2 add tests and make several replacements/improvements 2022-12-06 12:38:24 +06:00
b17931e7bd complete startup module 2022-12-05 22:31:15 +06:00
2a4570eafc Update ServerLauncher.kt 2022-12-05 21:52:36 +06:00
c9514d3a6d Create DefaultJson.kt 2022-12-05 21:47:00 +06:00
072805efc7 Update ServerLauncher.kt 2022-12-05 21:45:30 +06:00
369ff26627 initialize startup module 2022-12-05 21:19:38 +06:00
c5abbbbd2d start 0.15.1 2022-12-05 20:04:50 +06:00
d974639f1e fixes in source roots of dokka 2022-12-05 07:47:35 +06:00
26efde316b remove redundant fix of android 32 dx files from worflows 2022-12-05 07:09:31 +06:00
fafe50f80a suppress kdocs of uniupload in actual realizations 2022-12-05 07:00:31 +06:00
41504469db Revert "potential fix of dokka"
This reverts commit 31022733ac.
2022-12-05 06:57:29 +06:00
03b3ddd98b Revert "remove redundant dokka println"
This reverts commit b53cfd5504.
2022-12-05 06:57:27 +06:00
89d919f2be Merge pull request #207 from InsanusMokrassar/0.15.0
0.15.0
2022-12-04 19:26:14 +06:00
b53cfd5504 remove redundant dokka println 2022-12-04 19:03:44 +06:00
31022733ac potential fix of dokka 2022-12-04 19:00:16 +06:00
f9a8c39879 updates and fixes in mime types 2022-12-02 21:07:32 +06:00
a812c2dd2f refresh language codes 2022-12-02 20:03:29 +06:00
217e977f0d ids in crud repos 2022-12-02 13:46:06 +06:00
04c301d1ac upgrade version up to 0.15.0 2022-12-02 13:31:11 +06:00
7f0c425389 start 0.14.5 2022-12-02 13:04:44 +06:00
1ede1c423b Merge pull request #206 from InsanusMokrassar/0.14.4
0.14.4
2022-11-27 17:34:07 +06:00
7cb064896a fill changelog 2022-11-27 14:44:03 +06:00
0c5e2862ca improvements in ktor client-server files handling 2022-11-27 14:38:35 +06:00
30d4507f54 hotfix in handleUniUpload 2022-11-26 12:12:01 +06:00
b6c6d89455 Update gradle.properties 2022-11-25 10:16:31 +06:00
9d218ee534 Update UniloadMultipart.kt 2022-11-25 10:15:59 +06:00
11116d8cab Merge pull request #205 from InsanusMokrassar/0.14.3
0.14.3
2022-11-24 13:15:08 +06:00
58d754bbde handleUniUpload now is inline 2022-11-24 12:29:48 +06:00
8f25c123dd add download and downloadToTemporalFile extensions 2022-11-24 11:27:30 +06:00
76e214fc08 upfixes 2022-11-22 14:39:31 +06:00
2b5380f8d6 add server part of uniUpload, fill changelog 2022-11-22 13:38:36 +06:00
844a129094 complete universal uniupload 2022-11-22 12:57:43 +06:00
a3090cca9d add uniupload for JVM 2022-11-22 10:36:15 +06:00
b7b5159e9c Revert "Revert "start adding uniupload""
This reverts commit 0d1aae0ef7.
2022-11-22 09:51:10 +06:00
0f8bc2c950 fix in workflow build 2022-11-21 20:50:28 +06:00
69f5c49f45 small update of build workflows 2022-11-21 20:49:04 +06:00
9b308e6fb8 update workflow 2022-11-21 20:32:48 +06:00
3e3f91128b add dependency of common android repos onto jvm 2022-11-21 20:23:05 +06:00
0d1aae0ef7 Revert "start adding uniupload"
This reverts commit 4d022f0480.
2022-11-21 20:20:37 +06:00
4d022f0480 start adding uniupload 2022-11-21 13:39:11 +06:00
153e20d00e start 0.14.3 2022-11-21 12:49:04 +06:00
a9a8171dd6 add gitea repo 2022-11-16 21:35:18 +06:00
bf5c3b59b2 Merge pull request #204 from InsanusMokrassar/0.14.2
0.14.2
2022-11-15 10:02:11 +06:00
607c432bdb Update CHANGELOG.md 2022-11-15 10:01:58 +06:00
ae5c010770 Update libs.versions.toml 2022-11-15 09:54:18 +06:00
d5e432437f Update gradle.properties 2022-11-15 09:53:28 +06:00
9f99ebce01 Merge pull request #203 from InsanusMokrassar/0.14.1
0.14.1
2022-11-10 17:19:17 +06:00
64ee899b84 revert koin version change 2022-11-10 17:13:25 +06:00
e0e0c1658b revert kotlin versin version change 2022-11-10 17:11:45 +06:00
2c586f667c update version 2022-11-10 17:09:12 +06:00
64164ef6c1 update dependencies 2022-11-10 17:06:20 +06:00
22343c0731 Merge pull request #202 from InsanusMokrassar/0.14.0
0.14.0
2022-11-08 14:20:12 +06:00
f4ec1a4c60 add deprecations removing note 2022-11-08 14:19:48 +06:00
c1c33cceb1 remove deprecations 2022-11-08 14:17:45 +06:00
a3e975b2ba Update CHANGELOG.md 2022-11-08 14:13:36 +06:00
06e705a687 update klock 2022-11-08 13:12:39 +06:00
d56eb6c867 fixes 2022-11-08 12:47:59 +06:00
9cbca864e3 Update CHANGELOG.md 2022-11-08 08:00:18 +06:00
abb4378694 Update libs.versions.toml 2022-11-08 07:59:50 +06:00
0eb698d9a4 start 0.14.0 2022-11-08 07:57:31 +06:00
15ea9f2093 add documentation for KeyValue repo 2022-11-03 13:01:51 +06:00
d47aca0923 Merge pull request #201 from InsanusMokrassar/0.13.2
0.13.2
2022-10-30 22:35:07 +06:00
1ac50e9959 update several dependencies and fill changelog 2022-10-30 21:45:19 +06:00
6adfbe3a96 Update exposed and revert several dependencies updates 2022-10-24 23:52:04 +06:00
59f36e62e9 update dependencies 2022-10-22 14:25:46 +06:00
54af116009 start 0.13.2 2022-10-22 14:14:12 +06:00
38fbec8e3b Merge pull request #200 from InsanusMokrassar/0.13.1
0.13.1
2022-10-17 15:42:13 +06:00
babbfc55e4 update default of AbstractExposedWriteCRUDRepo 2022-10-17 15:31:27 +06:00
2511e18d69 AbstractExposedWriteCRUDRepo#createAndInsertId now is optional and returns nullable value 2022-10-17 14:42:53 +06:00
29658c70a0 start 0.13.1 2022-10-17 14:35:16 +06:00
96311ee43d Merge pull request #199 from InsanusMokrassar/0.13.0
0.13.0
2022-10-13 16:50:50 +06:00
bd33b09052 A LOT OF KTOR METHODS RELATED TO UnifierRouter/UnifiedRequester HAVE BEEN REMOVED 2022-10-11 13:24:39 +06:00
8055003b47 deprecations removing 2022-10-11 12:58:29 +06:00
1257492f85 changes in insert/update exposed methods and rewriting of read keyvalue(s) exposed repos to be extending abstract key value(s) repos 2022-10-11 12:55:25 +06:00
1107b7f4ef start 0.13.0 2022-10-11 12:07:49 +06:00
a1a1171240 start 0.12.18 2022-10-04 15:36:43 +06:00
46c02e5df1 Merge pull request #198 from InsanusMokrassar/0.12.17
0.12.17
2022-10-01 21:59:42 +06:00
2e9efc57de update dependencies 2022-10-01 21:56:47 +06:00
acecadef17 start 0.12.17 2022-10-01 21:41:34 +06:00
19394b5e69 Merge pull request #197 from InsanusMokrassar/0.12.16
0.12.16
2022-09-26 02:16:17 +06:00
de999e197f fix of setOnHierarchyChangeListenerRecursively 2022-09-26 01:10:10 +06:00
9d95687d3c FlowOnHierarchyChangeListener 2022-09-26 01:02:12 +06:00
aa9dfb4ab8 start 0.12.16 2022-09-26 00:47:32 +06:00
9c5b44efb3 Merge pull request #196 from InsanusMokrassar/0.12.15
0.12.15
2022-09-24 01:04:01 +06:00
ac587a67e6 findViewsByTag 2022-09-23 16:43:33 +06:00
59428140a8 applyDiff: Diff 2022-09-23 16:05:32 +06:00
60bdb59d71 fix version change 2022-09-23 12:41:57 +06:00
be52871de8 flows extensions 2022-09-23 12:41:07 +06:00
b7934cf357 flows extensions 2022-09-23 12:33:00 +06:00
dbfbeef90a start 0.12.15 2022-09-23 12:12:15 +06:00
00943c9cdf add docs for Diff class 2022-09-23 12:10:24 +06:00
8745c6a16a Merge pull request #195 from InsanusMokrassar/0.12.14
0.12.14
2022-09-23 00:19:12 +06:00
433ba4b58f argumentOrNull/argumentOrThrow 2022-09-22 23:39:08 +06:00
d40376e524 Update CHANGELOG.md 2022-09-22 21:51:40 +06:00
a2982f88f5 Update libs.versions.toml 2022-09-21 21:17:34 +06:00
1642f7abd9 improvements in AwaitFirst 2022-09-21 18:00:39 +06:00
a10d2184ff start 0.12.14 2022-09-21 17:53:57 +06:00
522435f096 Merge pull request #194 from InsanusMokrassar/0.12.13
make actor not async
2022-09-15 01:43:21 +06:00
79b30290c0 fix build 2022-09-15 01:19:05 +06:00
f8b8626859 make actor not async 2022-09-15 01:11:20 +06:00
b061b85a08 Merge pull request #193 from InsanusMokrassar/0.12.13
0.12.13
2022-09-14 23:27:59 +06:00
3870db1c88 fix of #155 and build 2022-09-14 22:47:57 +06:00
1be1070eb4 fix of #160 2022-09-14 22:32:00 +06:00
2696e663cf add RandomQualifier 2022-09-14 22:21:30 +06:00
1e1f7df86d create koin module 2022-09-14 22:14:20 +06:00
1d8ded8fd3 start 0.12.13 2022-09-14 21:42:29 +06:00
197825123a Merge pull request #192 from InsanusMokrassar/0.12.12
0.12.12
2022-09-13 22:08:52 +06:00
422b2e6db1 improve SkeletonAnimation 2022-09-13 22:01:02 +06:00
1973e0b5bf SkeletonAnimation 2022-09-13 01:30:36 +06:00
8258cf93a9 start 0.12.12 2022-09-12 21:03:07 +06:00
1d49bd5947 Merge pull request #191 from InsanusMokrassar/0.12.11
0.12.11
2022-09-08 23:46:55 +06:00
44317d1519 start 0.12.11 and add values in key value cache repo 2022-09-08 22:38:24 +06:00
48e08fcc69 Merge pull request #190 from InsanusMokrassar/0.12.10
0.12.10
2022-09-07 23:54:33 +06:00
1a3ce6e623 hotfix 2022-09-07 23:47:13 +06:00
fb122f3e70 0.12.10 2022-09-07 23:45:59 +06:00
7cca6039cc Merge pull request #189 from InsanusMokrassar/0.12.9
fix of cache filling without clearing of values
2022-09-07 22:16:50 +06:00
118e3dba39 fix of cache filling without clearing of values 2022-09-07 21:56:41 +06:00
87070710fa Merge pull request #188 from InsanusMokrassar/0.12.9
update korlibs
2022-09-07 21:14:26 +06:00
38499c3d4a update korlibs 2022-09-07 21:09:57 +06:00
5d754d968b Merge pull request #187 from InsanusMokrassar/0.12.9
0.12.9
2022-09-07 20:26:46 +06:00
d543d436bc Fixes in key values cache 2022-09-07 20:01:13 +06:00
12b54f99af update gradle wrapper 2022-09-07 19:49:29 +06:00
2a95d7e643 start 0.12.9 2022-09-07 19:48:59 +06:00
e3d3cacfa4 Merge pull request #186 from InsanusMokrassar/0.12.8
0.12.8
2022-09-07 00:57:17 +06:00
4b13491a0e Update CHANGELOG.md 2022-09-07 00:56:48 +06:00
85d516d1e9 Update libs.versions.toml 2022-09-06 23:44:02 +06:00
ac58b6a7e3 bodyOrNull/respondOrNoContent 2022-09-06 21:16:41 +06:00
2cc6126765 start 0.12.8 2022-09-06 21:15:01 +06:00
f94b085850 Merge pull request #185 from InsanusMokrassar/0.12.7
0.12.7
2022-09-05 18:35:56 +06:00
c9822a491b WriteCRUDCacheRepo subscribtions and changeResultsUnchecked(Pagination) 2022-09-05 15:07:33 +06:00
23b2d60295 start 0.12.7 2022-09-05 14:20:58 +06:00
f4bc9eed39 Merge pull request #184 from InsanusMokrassar/0.12.6
0.12.6
2022-08-31 04:10:33 +06:00
e310f188b0 fixed absence of image/* in known mime types 2022-08-31 03:01:06 +06:00
6c1571188c start 0.12.6 2022-08-31 02:59:29 +06:00
945d2fa284 Merge pull request #183 from InsanusMokrassar/0.12.5
0.12.5
2022-08-30 17:38:57 +06:00
020095f1ff Update CHANGELOG.md 2022-08-30 17:37:59 +06:00
b165a76e62 fixes in selectPaginated 2022-08-30 15:20:45 +06:00
03c8830672 start 0.12.5 2022-08-30 15:17:09 +06:00
38448da89b Merge pull request #182 from InsanusMokrassar/0.12.4
0.12.4
2022-08-23 13:02:19 +06:00
2ade5aff91 update dependencies 2022-08-23 12:57:16 +06:00
29bf6e80ec start 0.12.4 2022-08-23 12:55:52 +06:00
027e927e1b Merge pull request #181 from InsanusMokrassar/0.12.3
0.12.3
2022-08-22 01:44:17 +06:00
fa061f88e2 refactor 2022-08-22 01:42:04 +06:00
1d01b65b5f fix 2022-08-22 01:26:28 +06:00
2c2b364167 add default unoptimized realization of selectByIds 2022-08-22 01:25:07 +06:00
7c5fc9bf7c selectPaginated and abstract exposed keyvalue(s) repos 2022-08-22 01:11:09 +06:00
193d22ff20 start 0.12.3 2022-08-22 00:02:34 +06:00
0f3b553ba2 Merge pull request #180 from InsanusMokrassar/0.12.2
0.12.2
2022-08-19 16:25:24 +06:00
4eb1013446 Update CHANGELOG.md 2022-08-19 15:54:46 +06:00
be1b2563ec Update libs.versions.toml 2022-08-19 15:54:30 +06:00
1c9c2f1e70 Update libs.versions.toml 2022-08-19 02:00:29 +06:00
52dfded741 start 0.12.2 2022-08-19 01:59:09 +06:00
0a115e5cf4 Merge pull request #179 from InsanusMokrassar/0.12.1
0.12.1
2022-08-11 23:50:12 +06:00
c349af999b Update CHANGELOG.md 2022-08-11 23:36:01 +06:00
9a5709a34d Update libs.versions.toml 2022-08-11 22:52:34 +06:00
28d311160b start 0.12.1 2022-08-11 22:47:56 +06:00
1ca7081a40 Merge pull request #176 from InsanusMokrassar/0.12.0
0.12.0
2022-08-04 13:55:30 +06:00
5ce8ebe82c Merge branch 'master' into 0.12.0 2022-08-04 13:51:12 +06:00
a7a88b29b9 Merge pull request #178 from InsanusMokrassar/0.11.14
0.11.14
2022-08-04 13:48:18 +06:00
cd7b982385 update android version 2022-08-04 12:50:36 +06:00
ee59100075 fix of build 2022-08-04 12:36:23 +06:00
f808ac58ef Delete HmacSHA256.kt 2022-08-04 11:30:15 +06:00
f33ada5396 deprecations handling 2022-08-04 10:59:04 +06:00
984d781f2f fill changelog 2022-08-04 10:14:52 +06:00
dd33e1e8bc change version of kotlin 2022-08-04 10:05:29 +06:00
2950de29e5 fixes 2022-08-04 09:59:05 +06:00
50a8799f9d update serialization 2022-08-04 09:47:29 +06:00
bdb0ce6fc7 Update libs.versions.toml 2022-08-04 09:19:57 +06:00
18ec2bca96 Update libs.versions.toml 2022-08-03 10:09:38 +06:00
937ef48794 Update libs.versions.toml 2022-08-01 21:47:05 +06:00
6331f13e9a actualize changelog 2022-07-26 14:58:28 +06:00
5213a2ff8e add kdocs for pagination 2022-07-26 14:58:14 +06:00
087d7452fd encode by default pages number 2022-07-26 14:54:52 +06:00
703094c924 add several additional constructors for the pagination result 2022-07-26 13:56:47 +06:00
eea645c865 PaginationResult#objectsCount 2022-07-25 13:31:15 +06:00
324832a189 start 0.11.14 2022-07-25 13:14:55 +06:00
d55d735c51 Merge pull request #177 from InsanusMokrassar/0.11.13
0.11.13
2022-07-22 16:34:58 +06:00
e3ff1b9609 fill changelog 2022-07-22 16:31:33 +06:00
70c31966ca Update gradle-wrapper.properties 2022-07-18 14:33:04 +06:00
0e4188882f Update libs.versions.toml 2022-07-18 14:08:23 +06:00
6bf0ce92ba Update libs.versions.toml 2022-07-18 13:51:00 +06:00
d51bdc5086 Update gradle.properties 2022-07-18 13:48:09 +06:00
f04f262cee Update libs.versions.toml 2022-07-18 13:16:56 +06:00
0f172055ef Update gradle.properties 2022-07-18 13:11:02 +06:00
e5dd4363f1 Merge pull request #175 from InsanusMokrassar/0.11.12
0.11.12
2022-07-02 02:50:47 +06:00
a3a48bbaac fixes in file key value repo 2022-07-02 02:28:43 +06:00
5e716fb9a8 start 0.11.12 2022-07-02 02:26:58 +06:00
11a36153cc Merge pull request #174 from InsanusMokrassar/0.11.11
0.11.11: improvements of cache repos
2022-06-30 14:03:29 +06:00
8bee354f04 improvements of cache repos 2022-06-30 13:59:32 +06:00
f7dd2b5ce7 Merge pull request #173 from InsanusMokrassar/0.11.11
0.11.11
2022-06-30 13:41:43 +06:00
8ca10c00bb update dokka workflow 2022-06-30 13:41:12 +06:00
905c7e8eda deprecate hmacSha256 2022-06-30 13:19:37 +06:00
d4c5e849bf deprecate BodyPair 2022-06-30 13:15:19 +06:00
8250a2a021 start 0.11.11 2022-06-30 13:14:06 +06:00
01b3df7b8c Update github_release.gradle 2022-06-30 10:12:54 +06:00
daa6e4aff5 Merge pull request #172 from InsanusMokrassar/0.11.10
0.11.10
2022-06-30 02:56:14 +06:00
23bcb26a58 complete improvements in caches 2022-06-30 02:44:44 +06:00
e55f60c30b rename unlimited kv cache 2022-06-30 00:22:07 +06:00
0d0c16e16d add full cache repos 2022-06-29 23:53:49 +06:00
540d5cce7c start add full repos caches 2022-06-29 19:43:58 +06:00
a548b00979 update repos cache 2022-06-29 19:31:57 +06:00
4b7ca6d565 start 0.11.10 2022-06-29 19:29:38 +06:00
0473fa238c Merge pull request #171 from InsanusMokrassar/0.11.9
0.11.9
2022-06-29 01:56:03 +06:00
cfc7119697 update dependencies 2022-06-28 23:06:09 +06:00
22a6520d3e start 0.11.9 2022-06-28 23:01:32 +06:00
fb25e91191 start 0.12.0 2022-06-28 22:57:21 +06:00
c116b270b6 Merge pull request #170 from InsanusMokrassar/0.11.8
0.11.8
2022-06-28 14:18:11 +06:00
aa2d598689 fixes in FileKeyValueRepo 2022-06-28 14:13:04 +06:00
5ef3bb746b start 0.11.8 2022-06-28 14:12:12 +06:00
037616e271 Merge pull request #169 from InsanusMokrassar/0.11.7
0.11.7
2022-06-28 03:07:54 +06:00
abbea906f1 fixes 2022-06-28 01:46:24 +06:00
9132e216c9 add several extensions for MapperRepo 2022-06-28 01:43:57 +06:00
12a7e3c4af fixes 2022-06-28 01:06:46 +06:00
b40c093917 SimpleMapper, SimpleSuspendableMapper, mappers for CRUDRepo 2022-06-28 00:56:51 +06:00
7ac12455c8 add ReadCRUDRepo mappers 2022-06-28 00:19:25 +06:00
5043eec7a2 start 0.11.7 2022-06-27 23:35:10 +06:00
cf31f53e01 Merge pull request #168 from InsanusMokrassar/0.11.6
0.11.6
2022-06-24 00:19:34 +06:00
cd22d76fa7 fsms fixes 2022-06-23 18:47:59 +06:00
c8759843f7 start 0.11.6 2022-06-23 17:19:07 +06:00
781bbcc012 Merge pull request #167 from InsanusMokrassar/0.11.5
0.11.5
2022-06-23 12:50:31 +06:00
6da29c0686 add note about 0.11.4 2022-06-23 12:49:09 +06:00
fcdb6fc45a start and finish 0.11.5 -.- 2022-06-23 12:48:24 +06:00
e785a99bd7 Merge pull request #166 from InsanusMokrassar/0.11.4
0.11.4
2022-06-22 22:37:30 +06:00
121e513fdd fixes in tempUpload of js part 2022-06-22 22:36:06 +06:00
9cf01ab54f improve compose list state creation 2022-06-22 16:56:51 +06:00
c655107681 renames in compose states creation 2022-06-22 16:52:19 +06:00
91a5af6a9a fix of changelog 2022-06-22 16:10:58 +06:00
86e74c0a6f add extension toMutableListState 2022-06-22 16:09:52 +06:00
d8f01f21a0 start 0.11.4 2022-06-22 16:09:35 +06:00
9fb8626d8c Rewrite crud.yml 2022-06-16 15:51:22 +06:00
1c52e04cdb Merge pull request #164 from InsanusMokrassar/0.11.3
0.11.3
2022-06-14 23:59:49 +06:00
798128256e fixes in websockets 2022-06-14 23:48:10 +06:00
72c2df47fd start 0.11.3 2022-06-14 19:35:17 +06:00
c9c6d4c0c1 Merge pull request #163 from InsanusMokrassar/0.11.2
0.11.2
2022-06-13 23:21:14 +06:00
2f4f9f3003 fixes 2022-06-13 23:20:25 +06:00
22e8f8e5d6 start 0.11.2 2022-06-13 22:19:33 +06:00
04cf8c3d9a Merge pull request #162 from InsanusMokrassar/0.11.1
0.11.1
2022-06-12 16:32:05 +06:00
52198be543 fixes in tempupload 2022-06-12 14:26:41 +06:00
80fd5a489b add pagination type info in crud read repo client 2022-06-11 22:37:32 +06:00
b187043ee1 fixes in kv and kvs of ktor repos 2022-06-10 22:25:16 +06:00
8965752055 start 0.11.1 2022-06-10 22:23:45 +06:00
b796620267 Merge pull request #161 from InsanusMokrassar/0.11.0
0.11.0
2022-06-05 02:54:05 +06:00
62df81bb4e update uuid and partially fill changelog 2022-06-05 00:34:45 +06:00
0a8e0f6178 complete swagger templates 2022-06-04 17:12:05 +06:00
f705020aaa fixes in packages 2022-06-04 16:55:23 +06:00
78bd3b9853 replaces of new API of ktor 2022-06-04 16:52:01 +06:00
992b283597 add kv swagger template 2022-06-04 16:47:22 +06:00
8fc1ff1d59 fixes and filling of cru.yml for swagger 2022-06-04 16:18:48 +06:00
3bbde61f39 renames in crud repos and some little refactoring 2022-06-04 15:15:17 +06:00
8d955c4b9d renames in kevalues repos 2022-06-04 15:00:23 +06:00
18593c530b renames in key value repos 2022-06-04 14:42:16 +06:00
78903cd4eb complete improvements in repos ktor parts 2022-06-04 00:22:36 +06:00
eaa143f7d7 kv rework, fixes in map keyvalue repo, tests 2022-06-03 22:33:26 +06:00
bcb0e42fa2 add new started clients for crud 2022-06-03 17:52:58 +06:00
8eed435302 start improvements in ktor parts 2022-06-03 12:22:51 +06:00
0e4a63057f start 0.11.0 2022-06-02 23:50:06 +06:00
1af5faa440 Merge pull request #159 from InsanusMokrassar/0.10.8
0.10.8
2022-05-29 20:06:14 +06:00
7412217b0c add Element.isOverflow 2022-05-29 10:22:57 +06:00
3e749d75b7 start 0.10.8 2022-05-29 10:19:39 +06:00
846b5c87c9 Merge pull request #158 from InsanusMokrassar/0.10.7
0.10.7
2022-05-28 23:59:54 +06:00
7b5e84f80b doForAll and getForAll not suspend 2022-05-28 22:47:06 +06:00
27822a8a66 start 0.10.7 2022-05-28 18:48:51 +06:00
36f4e7ec37 Merge pull request #157 from InsanusMokrassar/0.10.6
0.10.6
2022-05-28 09:47:10 +06:00
187e84ad65 fill changelog and downgrade coroutines 2022-05-28 09:45:18 +06:00
195fe221c4 Update libs.versions.toml 2022-05-28 00:25:59 +06:00
6f9c19bbf6 fixes in ResizeObserver 2022-05-27 11:39:09 +06:00
65bdab4f7e ResizeObserver 2022-05-27 02:37:58 +06:00
c4d5fcfc22 start 0.10.6 2022-05-27 02:37:14 +06:00
43232afa62 Merge pull request #156 from InsanusMokrassar/0.10.5
0.10.5
2022-05-19 10:44:00 -04:00
18908a01d7 rewrite the way to use onStateHandlingErrorHandler inside of DefaultStatesMachine 2022-05-19 20:42:22 +06:00
b22fbfb3bc StateHandlingErrorHandler 2022-05-19 20:22:34 +06:00
57aaea88b6 Update gradle.properties 2022-05-17 12:38:15 +06:00
140949c5ea Update CHANGELOG.md 2022-05-17 12:32:59 +06:00
639241e0d3 Update libs.versions.toml 2022-05-17 12:31:50 +06:00
e0eb42bc2d update dexcount 2022-05-16 17:38:49 +06:00
7990b21cc5 add SharedPreferencesKeyValueRepo 2022-05-16 17:34:34 +06:00
2ba5c97709 start 0.10.5 2022-05-16 17:33:49 +06:00
33b7c85fc2 Merge pull request #154 from InsanusMokrassar/0.10.4
0.10.4
2022-05-12 17:24:13 +06:00
7f6c02ffdf update serialization 2022-05-12 17:04:53 +06:00
f368616e6f start 0.10.4 2022-05-12 17:03:59 +06:00
dd632f4203 Merge pull request #153 from InsanusMokrassar/0.10.3
0.10.3
2022-05-11 08:42:24 +06:00
a8f3ae501b Update CHANGELOG.md 2022-05-11 08:34:44 +06:00
ea76963ac2 Update libs.versions.toml 2022-05-11 08:33:55 +06:00
99dfa97958 Merge pull request #152 from InsanusMokrassar/0.10.3
0.10.3
2022-05-11 02:49:35 +06:00
f68270a5b3 fixes in AccumulatorFlow 2022-05-11 02:38:48 +06:00
542ed81034 start 0.10.3 2022-05-11 01:18:48 +06:00
404a11f5e7 Merge pull request #151 from InsanusMokrassar/0.10.2
0.10.2
2022-05-10 23:44:39 +06:00
411221070e Update CHANGELOG.md 2022-05-10 23:44:13 +06:00
25d35d0c76 Update compose 2022-05-10 15:03:04 +06:00
c72904d61c start 0.10.2 2022-05-10 15:00:56 +06:00
b94c9acd26 Merge pull request #150 from InsanusMokrassar/0.10.1
0.10.1
2022-04-29 23:20:27 +06:00
bdb4988569 fixes 2022-04-29 19:46:15 +06:00
1dafd1352a hotfix 2022-04-29 18:47:23 +06:00
31fbd3cad7 hmacSha256 and hex 2022-04-29 18:42:59 +06:00
848bc5ec10 Update gradle.properties 2022-04-28 23:45:57 +06:00
4d0ad826a0 Update libs.versions.toml 2022-04-28 23:32:35 +06:00
6955820fcc Merge pull request #149 from InsanusMokrassar/0.10.0
0.10.0
2022-04-28 20:30:52 +06:00
ef530624b9 remove redundant pluginManagement 2022-04-28 20:30:08 +06:00
9b9e7dd88f fixes in websockets 2022-04-28 19:43:52 +06:00
a13cc9e961 add error propagation for websockets when websockets has not been installed 2022-04-28 18:40:18 +06:00
0d2b923378 fix signature of createStandardWebsocketFlow 2022-04-28 18:16:14 +06:00
fba84c8ac8 optimize imports in ktor server 2022-04-27 18:30:28 +06:00
db10fe1b2c several fixes 2022-04-27 18:24:51 +06:00
175dd980f8 several warnings fixes 2022-04-27 15:15:03 +06:00
8364020671 migration onto new ktor and other dependencies 2022-04-27 14:39:21 +06:00
eba44cd394 update dependencies 2022-04-26 13:36:55 +06:00
b3bac8015a start 0.10.0 2022-04-26 13:35:00 +06:00
0b48afd251 Merge pull request #147 from InsanusMokrassar/0.9.24
0.9.24
2022-04-21 17:20:45 +06:00
19857930a4 small refactor in actual MPPFileInput 2022-04-21 16:45:44 +06:00
d0dbe3ed2f MPPFile#input 2022-04-21 16:12:58 +06:00
8b7e78b63a start 0.9.24 2022-04-21 16:12:21 +06:00
92a4ecb523 Merge pull request #146 from InsanusMokrassar/0.9.23
0.9.23
2022-04-20 23:46:06 +06:00
6a5ad4d728 Update CHANGELOG.md 2022-04-20 23:41:59 +06:00
be4aa8daac fixes in inheritance of interfaces 2022-04-20 23:14:06 +06:00
b5eac37782 improvements in ExposedCRUDRepo 2022-04-20 18:41:42 +06:00
b1ad3c5a39 start 0.9.23 2022-04-20 18:22:21 +06:00
ba16bad029 Merge pull request #145 from InsanusMokrassar/0.9.22
0.9.22
2022-04-19 13:55:13 +06:00
ca8ae4cd72 improve ktor server part 2022-04-19 11:16:04 +06:00
53d35d74b3 start 0.9.22 2022-04-19 11:14:50 +06:00
49c139e235 Merge pull request #144 from InsanusMokrassar/0.9.21
0.9.21
2022-04-17 08:56:59 +06:00
caf9c821f3 fixes in AbstractExposedWriteCRUDRepo 2022-04-15 00:49:32 +06:00
ca4c6db96f start 0.9.21 2022-04-15 00:44:28 +06:00
6b2298c752 Merge pull request #143 from InsanusMokrassar/0.9.20
0.9.20
2022-04-13 16:02:12 +06:00
a1bf43def9 upfixes 2022-04-13 16:01:13 +06:00
15e9254e00 fixes in OneToManyAndroidRepo and adding of CursorIterator 2022-04-13 15:54:48 +06:00
afe5a72c6f small fix of docs 2022-04-12 15:32:25 +06:00
750a8b9ecf start 0.9.20 2022-04-12 15:13:25 +06:00
27fc3f93e0 Merge pull request #142 from InsanusMokrassar/0.9.19
0.9.19
2022-04-08 01:16:13 +06:00
8166d4b99b Update CHANGELOG.md 2022-04-08 01:16:01 +06:00
b61d2ae2eb fixes in versions repo 2022-04-07 15:41:08 +06:00
4790fe0aea get back either serializer fun 2022-04-07 09:59:58 +06:00
bc37b11cee downgrade kotlin 2022-04-07 09:56:23 +06:00
223fed910f fix for column name of table version in exposed standard versions repo proxy 2022-04-07 00:43:55 +06:00
b85ab7b061 update coroutines 2022-04-06 11:47:06 +06:00
888dc299c9 update kotlin 2022-04-04 10:06:57 +06:00
e113dc28ed start 0.9.19 2022-04-04 10:05:56 +06:00
31e55d2307 Merge pull request #141 from InsanusMokrassar/0.9.18
0.9.18
2022-03-31 20:38:43 +06:00
e90645f248 Element#onActionOutside 2022-03-31 14:48:15 +06:00
4bb7ba2571 start 0.9.18 2022-03-31 14:31:04 +06:00
8d31c25bf8 Merge pull request #140 from InsanusMokrassar/0.9.17
0.9.17
2022-03-27 17:47:11 +06:00
c7ee1c28b2 fix of docs for DefaultStatesManager 2022-03-27 17:43:01 +06:00
99b09c8b28 add conflicts resolver for default states managers 2022-03-27 17:15:23 +06:00
a328c4425a Update CHANGELOG.md 2022-03-27 13:57:08 +06:00
c0f61ca896 Update libs.versions.toml 2022-03-26 19:11:44 +06:00
86e70c0961 js visibility of objects extensions 2022-03-26 11:01:40 +06:00
d87a3a039f start 0.9.17 2022-03-26 10:57:50 +06:00
6279a2c40a Merge pull request #139 from InsanusMokrassar/0.9.16
0.9.16
2022-03-18 00:10:25 +06:00
f377ebea88 add renderComposableAndLinkToRoot 2022-03-17 19:16:31 +06:00
7373fef964 add js onRemoved for nodes and different extensions for Composition 2022-03-17 14:56:14 +06:00
41ef86dbda start 0.9.16 2022-03-17 14:56:14 +06:00
7bc2b2336d Merge pull request #138 from InsanusMokrassar/0.9.15
0.9.15
2022-03-16 19:47:28 +06:00
0a615e6d78 cancel jobs of completed states in DefaultStatesMachine 2022-03-16 19:35:14 +06:00
8f928e16e1 FSM updates 2022-03-16 18:40:21 +06:00
72bc8da1e7 start 0.9.15 2022-03-16 17:59:29 +06:00
51e349a5db Merge pull request #137 from InsanusMokrassar/0.9.14
0.9.14 (upfixes)
2022-03-15 16:32:24 +06:00
3cbb19ba2c fixes and versions updates 2022-03-15 16:30:32 +06:00
ae3a5bf45d Merge pull request #136 from InsanusMokrassar/0.9.14
0.9.14
2022-03-15 16:22:35 +06:00
9c667f4b78 several fixes in actual temporal upload of js client 2022-03-15 15:37:41 +06:00
21195e1bcb temporal files uploading functionality 2022-03-15 15:33:45 +06:00
03117ac565 start 0.9.14 2022-03-15 15:31:58 +06:00
d13fbdf176 Merge pull request #135 from InsanusMokrassar/0.9.13
0.9.13
2022-03-13 17:14:23 +06:00
7cecc0e0b6 Update CHANGELOG.md 2022-03-13 14:08:01 +06:00
203e781f5d Update libs.versions.toml 2022-03-13 14:06:16 +06:00
3eb6cd77cd start 0.9.13 2022-03-13 14:05:24 +06:00
51855b2405 Merge pull request #134 from InsanusMokrassar/0.9.12
0.9.12
2022-03-12 01:46:23 +06:00
00cc26d874 fix in publication scripts 2022-03-12 01:38:13 +06:00
02520636ad hotfix in selectFile 2022-03-12 01:28:53 +06:00
76813fae8e hotfix in selectFile 2022-03-12 01:28:41 +06:00
4693483c2b rename selectFile with result MPPFile to selectFileOrThrow 2022-03-12 01:12:17 +06:00
6dbd12df59 fill changelog and rename selectOptionalFile to selectFileOrNull 2022-03-12 01:03:41 +06:00
9e84dc5031 add a set of tools for JS and Web Compose 2022-03-12 00:55:03 +06:00
8f790360bc start 0.9.12 2022-03-12 00:34:22 +06:00
808375cea6 Merge pull request #133 from InsanusMokrassar/0.9.11
0.9.11
2022-03-11 13:33:49 +06:00
d9c15db9de add compose submodule and add several functions in top of composition 2022-03-10 17:47:47 +06:00
cf2be8ed43 migration onto toml 2022-03-10 17:04:05 +06:00
97339c9b1d update wrapper version 2022-03-10 15:55:45 +06:00
eb35903de9 Update CHANGELOG.md 2022-03-10 15:20:42 +06:00
43e88e00da Update klock 2022-03-08 10:01:53 +06:00
1e51f9d77b Update CHANGELOG.md 2022-03-08 10:01:02 +06:00
dad738357c start 0.9.11 2022-03-08 10:00:19 +06:00
cb828ab3f2 Merge pull request #132 from InsanusMokrassar/0.9.10
0.9.10
2022-03-06 16:24:51 +06:00
aefaf4a8cc update klock (again) 2022-03-06 16:08:02 +06:00
b29f37a251 update klock 2022-03-04 20:20:17 +06:00
88854020ac extend functionality of websockets 2022-03-04 20:16:59 +06:00
14ebe01fc6 improvements in includeWebsocketHandling 2022-03-04 15:31:32 +06:00
771aed0f0f UnifiedRequester#createStandardWebsocketFlow(String,DeserializationStrategy) 2022-03-04 15:20:16 +06:00
16c57fcd6a start 0.9.10 2022-03-04 15:17:36 +06:00
75397a7ccb Merge pull request #131 from InsanusMokrassar/0.9.9
0.9.9
2022-02-24 14:44:13 +06:00
b05404f828 IntersectionObserver 2022-02-24 00:26:07 +06:00
edea942874 small refactor of operatons in applyDiff 2022-02-23 23:02:31 +06:00
8a8f568b9a add applyDiff in Diff Utils and update korlibs klock version 2022-02-23 22:55:56 +06:00
4dc8d30c52 start 0.9.9 2022-02-23 22:32:05 +06:00
cb4e08e823 Merge pull request #130 from InsanusMokrassar/0.9.8
0.9.8
2022-02-19 21:44:56 +06:00
c443bf4fa0 update dependencies 2022-02-19 16:15:36 +06:00
a6982de822 start 0.9.8 2022-02-19 16:06:45 +06:00
4f1a663e75 fixes in ExposedOneToManyKeyValueRepo and includeWebsocketHandling 2022-02-19 16:06:33 +06:00
dab262d626 start 0.9.7 2022-02-19 16:06:33 +06:00
87a3f61ca6 fix in ExposedOneToManyKeyValueRepo 2022-02-07 20:43:43 +06:00
506e937a68 start 0.9.6 2022-02-07 20:39:44 +06:00
5a037c76dd Merge pull request #126 from InsanusMokrassar/0.9.5
0.9.5
2022-02-01 23:30:26 +06:00
313f622f7e Update CHANGELOG.md 2022-02-01 14:27:07 +06:00
6cba1fe1a2 Update klock 2022-02-01 14:25:24 +06:00
fd2d0e80b7 start 0.9.5 2022-02-01 14:24:12 +06:00
96ab2e8aca Merge pull request #125 from InsanusMokrassar/0.9.4
0.9.4
2022-01-18 18:58:49 +06:00
0202988cae several fixes in optionallyReverse 2022-01-17 16:11:55 +06:00
d619d59947 Either updates 2022-01-17 15:56:48 +06:00
85b3e48d18 add optionallyReverse extensions 2022-01-17 15:38:23 +06:00
7a9b7d98a1 start 0.9.4 2022-01-14 13:22:04 +06:00
b212acfcaf Merge pull request #124 from InsanusMokrassar/0.9.3
0.9.3
2022-01-14 00:46:20 +06:00
3a45e5dc70 Update CHANGELOG.md 2022-01-14 00:37:03 +06:00
73190518d5 Update gradle.properties 2022-01-14 00:35:51 +06:00
03f78180dc Merge pull request #123 from InsanusMokrassar/0.9.2
0.9.2
2022-01-13 11:22:54 +06:00
1c0b8cf842 Update CHANGELOG.md 2022-01-13 10:20:35 +06:00
a1624ea2a9 Update gradle.properties 2022-01-13 10:19:08 +06:00
23a050cf1e Merge pull request #122 from InsanusMokrassar/0.9.1
0.9.1
2022-01-08 16:12:04 +06:00
916f2f96f4 typealiases for exposed one to many 2022-01-08 14:35:28 +06:00
00cc214754 repo exposed updates 2022-01-08 14:14:44 +06:00
b2e38f72b9 start 0.9.1 2022-01-08 14:12:57 +06:00
e7107d238d Update dokka_push.yml 2021-12-30 02:55:19 +06:00
ed9ebdbd1a Merge pull request #121 from InsanusMokrassar/0.9.0
0.9.0
2021-12-30 02:52:34 +06:00
e80676d3d2 Update packages_push.yml 2021-12-29 19:43:19 +06:00
02d02fa8f2 update android tools build 2021-12-29 19:24:25 +06:00
bd783fb74f update dokka 2021-12-29 17:44:45 +06:00
50386adf70 ignore kotlin-js-store folder 2021-12-28 21:19:47 +06:00
f4ee6c2890 update exposed and adapt to new version of kotlin serialization 2021-12-28 21:17:17 +06:00
d45aef9fe5 start 0.9.0 2021-12-28 09:58:59 +06:00
a56cd3dddd Merge pull request #120 from InsanusMokrassar/0.8.9
0.8.9
2021-12-27 17:02:16 +06:00
419e7070ee more fixes to god of fixes 2021-12-27 17:02:00 +06:00
612cf40b5f small hotfix 2021-12-27 16:00:07 +06:00
8b39882e83 fixes in DefaultUpdatableStatesMachine 2021-12-27 15:57:55 +06:00
e639ae172b Fixes in uniloadMultipart 2021-12-27 15:55:05 +06:00
d0446850ae start 0.8.9 2021-12-27 15:45:44 +06:00
c48465b90b Merge pull request #119 from InsanusMokrassar/0.8.8
0.8.8
2021-12-26 22:11:04 +06:00
f419fd03d2 small fix of performing state update for UpdatableStatesMachine 2021-12-26 22:00:26 +06:00
494812a660 one more fix 2021-12-26 21:25:12 +06:00
eb78f21eec fix FSMBuilder 2021-12-26 21:21:40 +06:00
4bda70268b small fix of style in DefaultUpdatableStatesMachine 2021-12-26 21:07:40 +06:00
f037ce4371 updates in FSM 2021-12-26 21:06:26 +06:00
3d2196e35d update dependencies (which possible) 2021-12-26 20:07:13 +06:00
a74f061b02 start 0.8.8 2021-12-26 20:02:18 +06:00
11ade14676 Merge pull request #118 from InsanusMokrassar/0.8.7
0.8.7
2021-12-15 01:22:42 +06:00
eb562d8784 add stream using for multipart 2021-12-14 22:03:29 +06:00
1ee5b4bfd4 hotfix for multipart 2021-12-14 21:13:11 +06:00
d97892080b add preview work with multipart 2021-12-14 18:01:41 +06:00
6f37125724 start 0.8.7 and make UnifiedRequester/UnifiedRouter fields are public 2021-12-14 13:25:58 +06:00
ed1baaade7 Merge pull request #117 from InsanusMokrassar/0.8.6
0.8.6
2021-12-08 15:57:43 +06:00
bb9669f8fd fill changelog 2021-12-08 11:45:01 +06:00
bdac715d48 remove crossinline where possible 2021-12-08 11:41:49 +06:00
acf4971298 downgrade ktor 2021-12-08 11:36:24 +06:00
249bc83a8c update ktor 2021-11-27 16:22:13 +06:00
0fbb92f03f start 0.8.6 2021-11-27 16:20:56 +06:00
ca27cb3f82 Merge pull request #116 from InsanusMokrassar/0.8.5
0.8.5
2021-11-27 01:00:18 +06:00
3a5771a0cc Update RepeatOnFailure.kt 2021-11-26 23:07:00 +06:00
527a2a91ac repeatOnFailure 2021-11-26 19:45:59 +06:00
6763e5c4c6 start 0.8.5 2021-11-26 19:32:52 +06:00
06918d8310 add mime types generator python script 2021-11-24 14:31:24 +06:00
89ccaa1b57 Merge pull request #115 from InsanusMokrassar/0.8.4
0.8.4
2021-11-19 14:02:38 +06:00
5d0bdb9bcf fix pagination 2021-11-19 13:58:59 +06:00
31fdcf74a5 ktor server createKtorServer extensions 2021-11-19 13:24:45 +06:00
afca09cc1d start 0.8.4 2021-11-19 13:18:37 +06:00
531d89d9db Merge pull request #114 from InsanusMokrassar/0.8.3
0.8.3
2021-11-19 09:49:35 +06:00
6bbbea0bc3 suppressions in Optional.kt 2021-11-18 23:03:24 +06:00
e337cd98c8 optional workaround 2021-11-17 21:31:35 +06:00
bcbab3b380 add Optional type 2021-11-17 17:38:41 +06:00
fb63de7568 intersect 2021-11-17 14:22:45 +06:00
aa45a4ab13 now Pagination is an ClosedRange 2021-11-17 14:07:11 +06:00
2af7e2f681 start 0.8.3 2021-11-17 14:01:44 +06:00
34fd9edce0 Merge pull request #113 from InsanusMokrassar/0.8.2
0.8.2
2021-11-12 13:23:39 +06:00
2a4cb8c5f9 improvements in FSM 2021-11-12 13:19:15 +06:00
50ea40bc3a add changelog for dependencies update 2021-11-12 13:14:57 +06:00
a77654052d Update gradle.properties 2021-11-11 21:20:17 +06:00
88aafce552 start 0.8.2 2021-11-11 20:31:04 +06:00
4e95d6bfff Merge pull request #112 from InsanusMokrassar/0.8.1
0.8.1
2021-11-09 14:04:11 +06:00
38d0e34fb5 updates in scripts and update core ktx 2021-11-09 14:03:51 +06:00
8fbc6b9041 update changelog 2021-11-09 13:43:46 +06:00
e8219d6cf4 start 0.8.1 2021-11-09 10:21:23 +06:00
6c20fc4ca6 Merge pull request #111 from InsanusMokrassar/0.8.0
0.8.0
2021-11-05 21:56:00 +06:00
85cd975492 preparations for release 2021-11-05 21:45:01 +06:00
1171a717fe optimize imports 2021-11-05 15:49:39 +06:00
bbe5320312 rework of FSM + 0.8.0 2021-11-05 15:43:05 +06:00
00acb9fddd add suppresses to the generated classes 2021-11-03 17:22:32 +06:00
de3d14dc41 solution of #109 2021-11-03 15:36:25 +06:00
67ff9cc9b3 update dependencies 2021-11-03 15:18:41 +06:00
af132103a0 start 0.7.5 and add either serializer 2021-11-02 12:43:59 +06:00
3b1124a804 fixes 2021-10-30 12:17:18 +06:00
f226c2dfd6 Merge pull request #106 from InsanusMokrassar/0.7.4
0.7.4
2021-10-29 13:57:58 +06:00
69d6e63846 getAllBy* 2021-10-29 13:51:59 +06:00
02c3d397ad solution of #104 2021-10-29 13:40:53 +06:00
67a1050646 solution for #105 2021-10-28 18:46:53 +06:00
8cd0775a6c update kdocs of Either 2021-10-28 18:42:05 +06:00
162294d6c6 either 2021-10-28 18:02:02 +06:00
c4dd19dd00 start 0.7.4 2021-10-28 17:50:28 +06:00
d2314422f1 Merge pull request #103 from InsanusMokrassar/0.7.3
0.7.3
2021-10-23 14:26:42 +06:00
6fedd6f859 update dependencies 2021-10-23 14:24:31 +06:00
e52b59665f start 0.7.3 2021-10-23 14:19:30 +06:00
cda9d09689 fixes for kdocs 2021-10-18 22:57:25 +06:00
c9237b3f00 Merge pull request #102 from InsanusMokrassar/0.7.2
0.7.2
2021-10-16 09:51:24 +06:00
18bba66c4a Update CHANGELOG.md 2021-10-16 08:56:38 +06:00
63418c4a8a Update gradle.properties 2021-10-16 08:55:44 +06:00
2e66c6f4e3 Merge pull request #101 from InsanusMokrassar/0.7.1
0.7.1
2021-10-13 15:11:26 +06:00
e9c5df4c13 upfill kdocs 2021-10-13 15:09:05 +06:00
bc7789ad2c add kdocs 2021-10-13 15:06:58 +06:00
e3da761249 Fill changelog 2021-10-13 14:38:50 +06:00
4082f65afa AccumulatorFlow 2021-10-13 13:26:39 +06:00
5d1cab075d Update gradle.properties 2021-10-12 21:03:47 +06:00
bcf67f7e59 Update gradle.properties 2021-10-12 20:56:00 +06:00
7d3b1f8e75 StatesMachine is interface 2021-10-06 13:56:58 +06:00
119a0588cc DefaultStatesManager 2021-10-06 13:30:25 +06:00
fab789d9c0 start rework of FSM states manager 2021-10-06 12:14:02 +06:00
ceba81c08f start 0.7.1 2021-10-06 11:51:55 +06:00
a061af0558 actualize changelog 2021-10-05 13:52:28 +06:00
c7a53846ad Merge pull request #100 from InsanusMokrassar/0.7.0
0.7.0 + migration back to klock
2021-10-05 13:51:41 +06:00
a683cccf0c 0.7.0 + migration back to klock 2021-10-05 13:46:23 +06:00
50d41e35c1 remove deprecations 2021-10-04 16:09:01 +06:00
aa0e831cea Merge pull request #99 from InsanusMokrassar/0.6.0
0.6.0
2021-10-04 15:57:02 +06:00
44e26ccb4f migration onto datetime 2021-10-04 15:54:43 +06:00
2a783f6e2b start 0.6.0 2021-10-03 18:58:15 +06:00
6058d6a724 update ktor 2021-10-01 16:12:26 +06:00
2e9c7eb5fa Merge pull request #98 from InsanusMokrassar/0.5.31
0.5.31
2021-10-01 15:42:24 +06:00
e75465ad10 update dependencies 2021-09-30 11:59:44 +06:00
de01ad54e9 start 0.5.31 2021-09-30 11:57:29 +06:00
eeea7ddbe3 Merge pull request #97 from InsanusMokrassar/0.5.30
0.5.30
2021-09-25 16:22:34 +06:00
e0b18bec05 update dependencies 2021-09-25 14:55:56 +06:00
410e89bba9 start 0.5.30 2021-09-25 14:45:56 +06:00
9ef19dc42b Merge pull request #96 from InsanusMokrassar/0.5.29
0.5.29
2021-09-23 13:45:55 +06:00
0337d1b82d add fix of 31.0.0 for kdocs workflow 2021-09-23 13:23:02 +06:00
f5bd4c5ccb update dependencies 2021-09-23 13:12:03 +06:00
630f9bc0d4 start 0.5.29 2021-09-23 13:10:44 +06:00
18b4ffece1 Update packages_push.yml 2021-09-22 20:18:14 +06:00
f64e1effa3 Delete build.yml 2021-09-22 20:17:04 +06:00
847fcbb488 Merge pull request #95 from InsanusMokrassar/0.5.28
0.5.28
2021-09-19 21:59:25 +06:00
88002ec8e7 set java toolchain version in all projects related to Java 2021-09-19 20:50:24 +06:00
7f8db6a29d update dependencies 2021-09-19 20:39:15 +06:00
b183b82443 start 0.5.28 2021-09-19 20:36:02 +06:00
5dad27de72 Merge pull request #94 from InsanusMokrassar/0.5.27
0.5.27
2021-09-15 21:12:34 +06:00
6b66084d0e update dependencies 2021-09-15 19:07:36 +06:00
50b56a7c39 start 0.5.27 2021-09-15 19:04:02 +06:00
7ab7d14471 Merge pull request #93 from InsanusMokrassar/0.5.26
0.5.26
2021-09-09 12:25:12 +06:00
bdcc179b7b protecteds in map repos instead of privates 2021-09-09 12:16:12 +06:00
55ffd4b46f start 0.5.26 2021-09-09 12:07:56 +06:00
7fc5ee70e1 Merge pull request #92 from InsanusMokrassar/0.5.25
0.5.25
2021-09-08 12:27:31 +06:00
a24a335743 TypedSerializer#plusAssign and TypedSerializer#minusAssign 2021-09-08 12:18:42 +06:00
ef9af71960 clamp deprecation and Iterable#diff 2021-09-08 12:10:32 +06:00
925702d315 MPPFile#withoutSlashAtTheEnd 2021-09-08 12:06:23 +06:00
d50dffec8c update dependencies 2021-09-08 12:01:02 +06:00
cef2081a13 start 0.5.25 2021-09-08 11:57:55 +06:00
06c8bde7c9 Merge pull request #91 from InsanusMokrassar/0.5.24
0.5.24
2021-09-04 14:59:13 +06:00
c9bbfa3820 update gradle config and fix build 2021-09-04 14:58:15 +06:00
eed7cfdc42 CoroutineScope with safely handler parameter 2021-09-04 14:46:12 +06:00
bd9b0d16ab update dependencies 2021-09-04 14:34:27 +06:00
ea6c33b497 start 0.5.24 2021-09-04 14:20:35 +06:00
dc80ade2fb Merge pull request #90 from InsanusMokrassar/0.5.23
0.5.23
2021-09-02 14:35:31 +06:00
401 changed files with 16527 additions and 4729 deletions

View File

@@ -1,4 +1,5 @@
name: Regular build
name: Build
on: [push]
jobs:
build:
@@ -7,6 +8,17 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-java@v1
with:
java-version: 1.8
java-version: 11
- name: Rewrite version
run: |
branch="`echo "${{ github.ref }}" | grep -o "[^/]*$"`"
cat gradle.properties | sed -e "s/^version=\([0-9\.]*\)/version=\1-branch_$branch-build${{ github.run_number }}/" > gradle.properties.tmp
rm gradle.properties
mv gradle.properties.tmp gradle.properties
- name: Build
run: ./gradlew build
- name: Publish
continue-on-error: true
run: ./gradlew publishAllPublicationsToGiteaRepository
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}

View File

@@ -10,9 +10,9 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-java@v1
with:
java-version: 1.8
java-version: 11
- name: Build
run: ./gradlew dokkaHtml
run: ./gradlew build && ./gradlew dokkaHtml
- name: Publish KDocs
uses: peaceiris/actions-gh-pages@v3
with:

View File

@@ -1,24 +0,0 @@
name: Publish package to GitHub Packages
on: [push]
jobs:
publishing:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Rewrite version
run: |
branch="`echo "${{ github.ref }}" | grep -o "[^/]*$"`"
cat gradle.properties | sed -e "s/^version=\([0-9\.]*\)/version=\1-branch_$branch-build${{ github.run_number }}/" > gradle.properties.tmp
rm gradle.properties
mv gradle.properties.tmp gradle.properties
- name: Build
run: ./gradlew build
- name: Publish
run: ./gradlew --no-parallel publishAllPublicationsToGithubPackagesRepository -x signJsPublication -x signJvmPublication -x signKotlinMultiplatformPublication -x signAndroidDebugPublication -x signAndroidReleasePublication -x signKotlinMultiplatformPublication
env:
GITHUBPACKAGES_USER: ${{ github.actor }}
GITHUBPACKAGES_PASSWORD: ${{ secrets.GITHUB_TOKEN }}

1
.gitignore vendored
View File

@@ -11,5 +11,6 @@ out/
secret.gradle
local.properties
kotlin-js-store
publishing.sh

View File

@@ -1,8 +0,0 @@
job("Build and run tests") {
container(displayName = "Run gradle build", image = "openjdk:11") {
kotlinScript { api ->
// here can be your complex logic
api.gradlew("build")
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -10,7 +10,7 @@ kotlin {
sourceSets {
androidMain {
dependencies {
api "androidx.appcompat:appcompat-resources:$appcompat_version"
api libs.android.appCompat.resources
}
}
}

View File

@@ -10,13 +10,13 @@ kotlin {
sourceSets {
commonMain {
dependencies {
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version"
api libs.kt.coroutines
api project(":micro_utils.common")
}
}
androidMain {
dependencies {
api "androidx.recyclerview:recyclerview:$androidx_recycler_version"
api libs.android.recyclerView
}
}
}

View File

@@ -7,12 +7,13 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:4.1.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_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"
classpath libs.buildscript.kt.gradle
classpath libs.buildscript.kt.serialization
classpath libs.buildscript.kt.ksp
classpath libs.buildscript.jb.dokka
classpath libs.buildscript.gh.release
classpath libs.buildscript.android.gradle
classpath libs.buildscript.android.dexcount
}
}
@@ -21,6 +22,8 @@ allprojects {
mavenLocal()
mavenCentral()
google()
maven { url "https://maven.pkg.jetbrains.space/public/p/compose/dev" }
maven { url "https://git.inmo.dev/api/packages/InsanusMokrassar/maven" }
}
// temporal crutch until legacy tests will be stabled or legacy target will be removed

View File

@@ -16,6 +16,19 @@ kotlin {
androidMain {
dependencies {
api project(":micro_utils.coroutines")
api libs.android.fragment
}
dependsOn jvmMain
}
linuxX64Main {
dependencies {
api libs.okio
}
}
mingwX64Main {
dependencies {
api libs.okio
}
}
}

View File

@@ -0,0 +1,18 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
alias(libs.plugins.jb.compose)
}
apply from: "$mppProjectWithSerializationAndComposePresetPath"
kotlin {
sourceSets {
commonMain {
dependencies {
api project(":micro_utils.common")
}
}
}
}

View File

@@ -0,0 +1,16 @@
package dev.inmo.micro_utils.common.compose
import androidx.compose.runtime.DisposableEffectResult
class DefaultDisposableEffectResult(
private val onDispose: () -> Unit
) : DisposableEffectResult {
override fun dispose() {
onDispose()
}
companion object {
val DoNothing = DefaultDisposableEffectResult {}
}
}

View File

@@ -0,0 +1,10 @@
package dev.inmo.micro_utils.common.compose
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.State
import androidx.compose.runtime.derivedStateOf
/**
* Converts current [MutableState] to immutable [State] using [derivedStateOf]
*/
fun <T> MutableState<T>.asState(): State<T> = derivedStateOf { this.value }

View File

@@ -0,0 +1,9 @@
package dev.inmo.micro_utils.common.compose
import androidx.compose.runtime.Composition
import dev.inmo.micro_utils.common.onRemoved
import org.w3c.dom.Element
fun Composition.linkWithElement(element: Element) {
element.onRemoved { dispose() }
}

View File

@@ -0,0 +1,10 @@
package dev.inmo.micro_utils.common.compose
import org.jetbrains.compose.web.attributes.ATarget
fun openLink(link: String, mode: ATarget = ATarget.Blank, features: String = "") = dev.inmo.micro_utils.common.openLink(
link,
mode.targetStr,
features
)

View File

@@ -0,0 +1,13 @@
package dev.inmo.micro_utils.common.compose
import androidx.compose.runtime.*
import org.jetbrains.compose.web.dom.DOMScope
import org.w3c.dom.Element
fun <TElement : Element> renderComposableAndLinkToRoot(
root: TElement,
monotonicFrameClock: MonotonicFrameClock = DefaultMonotonicFrameClock,
content: @Composable DOMScope<TElement>.() -> Unit
): Composition = org.jetbrains.compose.web.renderComposable(root, monotonicFrameClock, content).apply {
linkWithElement(root)
}

View File

@@ -0,0 +1,43 @@
package dev.inmo.micro_utils.common.compose
import org.jetbrains.compose.web.css.*
object SkeletonAnimation : StyleSheet() {
val skeletonKeyFrames: CSSNamedKeyframes by keyframes {
to { backgroundPosition("-20% 0") }
}
fun CSSBuilder.includeSkeletonStyle(
duration: CSSSizeValue<out CSSUnitTime> = 2.s,
timingFunction: AnimationTimingFunction = AnimationTimingFunction.EaseInOut,
iterationCount: Int? = null,
direction: AnimationDirection = AnimationDirection.Normal,
keyFrames: CSSNamedKeyframes = skeletonKeyFrames,
hideChildren: Boolean = true,
hideText: Boolean = hideChildren
) {
backgroundImage("linear-gradient(110deg, rgb(236, 236, 236) 40%, rgb(245, 245, 245) 50%, rgb(236, 236, 236) 65%)")
backgroundSize("200% 100%")
backgroundPosition("180% 0")
animation(keyFrames) {
duration(duration)
timingFunction(timingFunction)
iterationCount(iterationCount)
direction(direction)
}
if (hideText) {
property("color", "${Color.transparent} !important")
}
if (hideChildren) {
child(self, universal) style {
property("visibility", "hidden")
}
}
}
val skeleton by style {
includeSkeletonStyle()
}
}

View File

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

View File

@@ -1,3 +1,5 @@
@file:Suppress("OPT_IN_IS_NOT_ENABLED")
package dev.inmo.micro_utils.common
@RequiresOptIn(

View File

@@ -1,10 +0,0 @@
package dev.inmo.micro_utils.common
@Suppress("NOTHING_TO_INLINE")
inline fun <T : Comparable<T>> T.clamp(min: T, max: T): T {
return when {
this < min -> min
this > max -> max
else -> this
}
}

View File

@@ -2,6 +2,8 @@
package dev.inmo.micro_utils.common
import kotlinx.serialization.Serializable
private inline fun <T> getObject(
additional: MutableList<T>,
iterator: Iterator<T>
@@ -14,35 +16,49 @@ private inline fun <T> getObject(
/**
* Diff object which contains information about differences between two [Iterable]s
*
* See tests for more info
*
* @param removed The objects which has been presented in the old collection but absent in new one. Index here is the index in the old collection
* @param added The object which appear in new collection only. Indexes here show the index in the new collection
* @param replaced Pair of old-new changes. First object has been presented in the old collection on its
* [IndexedValue.index] place, the second one is the object in new collection. Both have indexes due to the fact that in
* case when some value has been replaced after adds or removes in original collection the object index will be changed
*
* @see calculateDiff
*/
@Serializable
data class Diff<T> internal constructor(
val removed: List<IndexedValue<T>>,
val removed: List<@Serializable(IndexedValueSerializer::class) IndexedValue<T>>,
/**
* Old-New values pairs
*/
val replaced: List<Pair<IndexedValue<T>, IndexedValue<T>>>,
val added: List<IndexedValue<T>>
)
val replaced: List<Pair<@Serializable(IndexedValueSerializer::class) IndexedValue<T>, @Serializable(IndexedValueSerializer::class) IndexedValue<T>>>,
val added: List<@Serializable(IndexedValueSerializer::class) IndexedValue<T>>
) {
fun isEmpty(): Boolean = removed.isEmpty() && replaced.isEmpty() && added.isEmpty()
}
fun <T> emptyDiff(): Diff<T> = Diff(emptyList(), emptyList(), emptyList())
private inline fun <T> performChanges(
potentialChanges: MutableList<Pair<IndexedValue<T>?, IndexedValue<T>?>>,
additionalsInOld: MutableList<T>,
additionalsInNew: MutableList<T>,
additionsInOld: MutableList<T>,
additionsInNew: MutableList<T>,
changedList: MutableList<Pair<IndexedValue<T>, IndexedValue<T>>>,
removedList: MutableList<IndexedValue<T>>,
addedList: MutableList<IndexedValue<T>>,
strictComparison: Boolean
comparisonFun: (T?, T?) -> 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)
val oldOneEqualToNewObject = comparisonFun(old ?.value, newObject ?.value)
val newOneEqualToOldObject = comparisonFun(new ?.value, oldObject ?.value)
if (oldOneEqualToNewObject || newOneEqualToOldObject) {
changedList.addAll(
potentialChanges.take(i).mapNotNull {
@Suppress("UNCHECKED_CAST")
if (it.first != null && it.second != null) it as Pair<IndexedValue<T>, IndexedValue<T>> else null
}
)
@@ -52,20 +68,20 @@ private inline fun <T> performChanges(
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) }
oldOne ?.let { additionsInOld.add(oldOne.value) }
}
if (newPotentials.size > 1) {
newPotentials.last().first ?.value ?.let { additionalsInOld.add(it) }
newPotentials.last().first ?.value ?.let { additionsInOld.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) }
newOne ?.let { additionsInNew.add(newOne.value) }
}
if (newPotentials.size > 1) {
newPotentials.last().second ?.value ?.let { additionalsInNew.add(it) }
newPotentials.last().second ?.value ?.let { additionsInNew.add(it) }
}
}
}
@@ -92,7 +108,7 @@ private inline fun <T> performChanges(
*/
fun <T> Iterable<T>.calculateDiff(
other: Iterable<T>,
strictComparison: Boolean = false
comparisonFun: (T?, T?) -> Boolean
): Diff<T> {
var i = -1
var j = -1
@@ -120,27 +136,60 @@ fun <T> Iterable<T>.calculateDiff(
}
when {
oldObject === newObject || (oldObject == newObject && !strictComparison) -> {
changedObjects.addAll(potentiallyChangedObjects.map { it as Pair<IndexedValue<T>, IndexedValue<T>> })
comparisonFun(oldObject, newObject) -> {
changedObjects.addAll(potentiallyChangedObjects.map {
@Suppress("UNCHECKED_CAST")
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)
performChanges(potentiallyChangedObjects, additionalInOld, additionalInNew, changedObjects, removedObjects, addedObjects, comparisonFun)
i -= (additionalInOld.size - previousOldsAdditionsSize)
j -= (additionalInNew.size - previousNewsAdditionsSize)
}
}
}
potentiallyChangedObjects.add(null to null)
performChanges(potentiallyChangedObjects, additionalInOld, additionalInNew, changedObjects, removedObjects, addedObjects, strictComparison)
performChanges(potentiallyChangedObjects, additionalInOld, additionalInNew, changedObjects, removedObjects, addedObjects, comparisonFun)
return Diff(removedObjects.toList(), changedObjects.toList(), addedObjects.toList())
}
inline fun <T> Diff(old: Iterable<T>, new: Iterable<T>) = old.calculateDiff(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> = calculateDiff(
other,
comparisonFun = if (strictComparison) {
{ t1, t2 ->
t1 === t2
}
} else {
{ t1, t2 ->
t1 === t2 || t1 == t2 // small optimization for cases when t1 and t2 are the same - comparison will be faster potentially
}
}
)
inline fun <T> Iterable<T>.diff(
other: Iterable<T>,
strictComparison: Boolean = false
): Diff<T> = calculateDiff(other, strictComparison)
inline fun <T> Iterable<T>.diff(
other: Iterable<T>,
noinline comparisonFun: (T?, T?) -> Boolean
): Diff<T> = calculateDiff(other, comparisonFun)
inline fun <T> Diff(old: Iterable<T>, new: Iterable<T>) = old.calculateDiff(new, strictComparison = false)
inline fun <T> StrictDiff(old: Iterable<T>, new: Iterable<T>) = old.calculateDiff(new, true)
/**
@@ -149,3 +198,41 @@ inline fun <T> StrictDiff(old: Iterable<T>, new: Iterable<T>) = old.calculateDif
inline fun <T> Iterable<T>.calculateStrictDiff(
other: Iterable<T>
) = calculateDiff(other, strictComparison = true)
/**
* This method call [calculateDiff] with strict mode [strictComparison] and then apply differences to [this]
* mutable list
*/
fun <T> MutableList<T>.applyDiff(
source: Iterable<T>,
strictComparison: Boolean = false
): Diff<T> = calculateDiff(source, strictComparison).also {
for (i in it.removed.indices.sortedDescending()) {
removeAt(it.removed[i].index)
}
it.added.forEach { (i, t) ->
add(i, t)
}
it.replaced.forEach { (_, new) ->
set(new.index, new.value)
}
}
/**
* This method call [calculateDiff] with strict mode [strictComparison] and then apply differences to [this]
* mutable list
*/
fun <T> MutableList<T>.applyDiff(
source: Iterable<T>,
comparisonFun: (T?, T?) -> Boolean
): Diff<T> = calculateDiff(source, comparisonFun).also {
for (i in it.removed.indices.sortedDescending()) {
removeAt(it.removed[i].index)
}
it.added.forEach { (i, t) ->
add(i, t)
}
it.replaced.forEach { (_, new) ->
set(new.index, new.value)
}
}

View File

@@ -0,0 +1,160 @@
@file:Suppress("unused", "NOTHING_TO_INLINE")
package dev.inmo.micro_utils.common
import kotlinx.serialization.*
import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.*
/**
* Realization of this interface will contains at least one not null - [optionalT1] or [optionalT2]
*
* @see EitherFirst
* @see EitherSecond
* @see Either.Companion.first
* @see Either.Companion.second
* @see Either.onFirst
* @see Either.onSecond
* @see Either.mapOnFirst
* @see Either.mapOnSecond
*/
@Serializable(EitherSerializer::class)
sealed interface Either<T1, T2> {
val optionalT1: Optional<T1>
val optionalT2: Optional<T2>
val t1OrNull: T1?
get() = optionalT1.dataOrNull()
val t2OrNull: T2?
get() = optionalT2.dataOrNull()
}
class EitherSerializer<T1, T2>(
t1Serializer: KSerializer<T1>,
t2Serializer: KSerializer<T2>,
) : KSerializer<Either<T1, T2>> {
@OptIn(InternalSerializationApi::class, ExperimentalSerializationApi::class)
override val descriptor: SerialDescriptor = buildSerialDescriptor(
"TypedSerializer",
SerialKind.CONTEXTUAL
) {
element("type", String.serializer().descriptor)
element("value", ContextualSerializer(Either::class).descriptor)
}
private val t1EitherSerializer = EitherFirst.serializer(t1Serializer, t2Serializer)
private val t2EitherSerializer = EitherSecond.serializer(t1Serializer, t2Serializer)
override fun deserialize(decoder: Decoder): Either<T1, T2> {
return decoder.decodeStructure(descriptor) {
var type: String? = null
lateinit var result: Either<T1, T2>
while (true) {
when (val index = decodeElementIndex(descriptor)) {
0 -> type = decodeStringElement(descriptor, 0)
1 -> {
result = when (type) {
"t1" -> decodeSerializableElement(
descriptor,
1,
t1EitherSerializer
)
"t2" -> decodeSerializableElement(
descriptor,
1,
t2EitherSerializer
)
else -> error("Unknown type of either: $type")
}
}
CompositeDecoder.DECODE_DONE -> break
else -> error("Unexpected index: $index")
}
}
result
}
}
override fun serialize(encoder: Encoder, value: Either<T1, T2>) {
encoder.encodeStructure(descriptor) {
when (value) {
is EitherFirst -> {
encodeStringElement(descriptor, 0, "t1")
encodeSerializableElement(descriptor, 1, t1EitherSerializer, value)
}
is EitherSecond -> {
encodeStringElement(descriptor, 0, "t2")
encodeSerializableElement(descriptor, 1, t2EitherSerializer, value)
}
}
}
}
}
/**
* This type [Either] will always have not nullable [optionalT1]
*/
@Serializable
data class EitherFirst<T1, T2>(
val t1: T1
) : Either<T1, T2> {
override val optionalT1: Optional<T1> = t1.optional
override val optionalT2: Optional<T2> = Optional.absent()
}
/**
* This type [Either] will always have not nullable [optionalT2]
*/
@Serializable
data class EitherSecond<T1, T2>(
val t2: T2
) : Either<T1, T2> {
override val optionalT1: Optional<T1> = Optional.absent()
override val optionalT2: Optional<T2> = t2.optional
}
/**
* @return New instance of [EitherFirst]
*/
inline fun <T1, T2> Either.Companion.first(t1: T1): Either<T1, T2> = EitherFirst(t1)
/**
* @return New instance of [EitherSecond]
*/
inline fun <T1, T2> Either.Companion.second(t2: T2): Either<T1, T2> = EitherSecond(t2)
/**
* Will call [block] in case when [this] is [EitherFirst]
*/
inline fun <T1, T2, E : Either<T1, T2>> E.onFirst(block: (T1) -> Unit): E {
optionalT1.onPresented(block)
return this
}
/**
* Will call [block] in case when [this] is [EitherSecond]
*/
inline fun <T1, T2, E : Either<T1, T2>> E.onSecond(block: (T2) -> Unit): E {
optionalT2.onPresented(block)
return this
}
/**
* @return Result of [block] if [this] is [EitherFirst]
*/
inline fun <T1, R> Either<T1, *>.mapOnFirst(block: (T1) -> R): R? {
return optionalT1.mapOnPresented(block)
}
/**
* @return Result of [block] if [this] is [EitherSecond]
*/
inline fun <T2, R> Either<*, T2>.mapOnSecond(block: (T2) -> R): R? {
return optionalT2.mapOnPresented(block)
}
inline fun <reified T1, reified T2> Any.either() = when (this) {
is T1 -> Either.first<T1, T2>(this)
is T2 -> Either.second<T1, T2>(this)
else -> error("Incorrect type of either argument $this")
}

View File

@@ -0,0 +1,43 @@
package dev.inmo.micro_utils.common
inline fun <T> Boolean.letIfTrue(block: () -> T): T? {
return if (this) {
block()
} else {
null
}
}
inline fun <T> Boolean.letIfFalse(block: () -> T): T? {
return if (this) {
null
} else {
block()
}
}
inline fun Boolean.alsoIfTrue(block: () -> Unit): Boolean {
letIfTrue(block)
return this
}
inline fun Boolean.alsoIfFalse(block: () -> Unit): Boolean {
letIfFalse(block)
return this
}
inline fun <T> Boolean.ifTrue(block: () -> T): T? {
return if (this) {
block()
} else {
null
}
}
inline fun <T> Boolean.ifFalse(block: () -> T): T? {
return if (this) {
null
} else {
block()
}
}

View File

@@ -0,0 +1,30 @@
package dev.inmo.micro_utils.common
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializer
import kotlinx.serialization.builtins.PairSerializer
import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
class IndexedValueSerializer<T>(private val subSerializer: KSerializer<T>) : KSerializer<IndexedValue<T>> {
private val originalSerializer = PairSerializer(Int.serializer(), subSerializer)
override val descriptor: SerialDescriptor
get() = originalSerializer.descriptor
override fun deserialize(decoder: Decoder): IndexedValue<T> {
val pair = originalSerializer.deserialize(decoder)
return IndexedValue(
pair.first,
pair.second
)
}
override fun serialize(encoder: Encoder, value: IndexedValue<T>) {
originalSerializer.serialize(
encoder,
Pair(value.index, value.value)
)
}
}

View File

@@ -1,10 +1,10 @@
package dev.inmo.micro_utils.common
inline fun <I, R> Iterable<I>.joinTo(
crossinline separatorFun: (I) -> R?,
separatorFun: (I) -> R?,
prefix: R? = null,
postfix: R? = null,
crossinline transform: (I) -> R?
transform: (I) -> R?
): List<R> {
val result = mutableListOf<R>()
val iterator = iterator()
@@ -29,11 +29,11 @@ inline fun <I, R> Iterable<I>.joinTo(
separator: R? = null,
prefix: R? = null,
postfix: R? = null,
crossinline transform: (I) -> R?
transform: (I) -> R?
): List<R> = joinTo({ separator }, prefix, postfix, transform)
inline fun <I> Iterable<I>.joinTo(
crossinline separatorFun: (I) -> I?,
separatorFun: (I) -> I?,
prefix: I? = null,
postfix: I? = null
): List<I> = joinTo<I, I>(separatorFun, prefix, postfix) { it }
@@ -45,15 +45,15 @@ inline fun <I> Iterable<I>.joinTo(
): List<I> = joinTo<I>({ separator }, prefix, postfix)
inline fun <I, reified R> Array<I>.joinTo(
crossinline separatorFun: (I) -> R?,
separatorFun: (I) -> R?,
prefix: R? = null,
postfix: R? = null,
crossinline transform: (I) -> R?
transform: (I) -> R?
): Array<R> = asIterable().joinTo(separatorFun, prefix, postfix, transform).toTypedArray()
inline fun <I, reified R> Array<I>.joinTo(
separator: R? = null,
prefix: R? = null,
postfix: R? = null,
crossinline transform: (I) -> R?
transform: (I) -> R?
): Array<R> = asIterable().joinTo(separator, prefix, postfix, transform).toTypedArray()

View File

@@ -7,7 +7,7 @@ import kotlin.jvm.JvmInline
@JvmInline
value class FileName(val string: String) {
val name: String
get() = string.takeLastWhile { it != '/' }
get() = withoutSlashAtTheEnd.takeLastWhile { it != '/' }
val extension: String
get() = name.takeLastWhile { it != '.' }
val nameWithoutExtension: String
@@ -17,15 +17,18 @@ value class FileName(val string: String) {
filename.substring(0, it)
} ?: filename
}
val withoutSlashAtTheEnd: String
get() = string.dropLastWhile { it == '/' }
override fun toString(): String = string
}
@PreviewFeature
expect class MPPFile
expect val MPPFile.filename: FileName
expect val MPPFile.filesize: Long
expect val MPPFile.bytesAllocatorSync: ByteArrayAllocator
expect val MPPFile.bytesAllocator: SuspendByteArrayAllocator
fun MPPFile.bytesSync() = bytesAllocatorSync()
suspend fun MPPFile.bytes() = bytesAllocator()

View File

@@ -0,0 +1,53 @@
package dev.inmo.micro_utils.common
import kotlin.jvm.JvmName
interface SimpleMapper<T1, T2> {
fun convertToT1(from: T2): T1
fun convertToT2(from: T1): T2
}
@JvmName("convertFromT2")
fun <T1, T2> SimpleMapper<T1, T2>.convert(from: T2) = convertToT1(from)
@JvmName("convertFromT1")
fun <T1, T2> SimpleMapper<T1, T2>.convert(from: T1) = convertToT2(from)
class SimpleMapperImpl<T1, T2>(
private val t1: (T2) -> T1,
private val t2: (T1) -> T2,
) : SimpleMapper<T1, T2> {
override fun convertToT1(from: T2): T1 = t1.invoke(from)
override fun convertToT2(from: T1): T2 = t2.invoke(from)
}
@Suppress("NOTHING_TO_INLINE")
inline fun <T1, T2> simpleMapper(
noinline t1: (T2) -> T1,
noinline t2: (T1) -> T2,
) = SimpleMapperImpl(t1, t2)
interface SimpleSuspendableMapper<T1, T2> {
suspend fun convertToT1(from: T2): T1
suspend fun convertToT2(from: T1): T2
}
@JvmName("convertFromT2")
suspend fun <T1, T2> SimpleSuspendableMapper<T1, T2>.convert(from: T2) = convertToT1(from)
@JvmName("convertFromT1")
suspend fun <T1, T2> SimpleSuspendableMapper<T1, T2>.convert(from: T1) = convertToT2(from)
class SimpleSuspendableMapperImpl<T1, T2>(
private val t1: suspend (T2) -> T1,
private val t2: suspend (T1) -> T2,
) : SimpleSuspendableMapper<T1, T2> {
override suspend fun convertToT1(from: T2): T1 = t1.invoke(from)
override suspend fun convertToT2(from: T1): T2 = t2.invoke(from)
}
@Suppress("NOTHING_TO_INLINE")
inline fun <T1, T2> simpleSuspendableMapper(
noinline t1: suspend (T2) -> T1,
noinline t2: suspend (T1) -> T2,
) = SimpleSuspendableMapperImpl(t1, t2)

View File

@@ -0,0 +1,97 @@
@file:Suppress("unused")
package dev.inmo.micro_utils.common
import kotlinx.serialization.Serializable
/**
* This type represents [T] as not only potentially nullable data, but also as a data which can not be presented. This
* type will be useful in cases when [T] is nullable and null as valuable data too in time of data absence should be
* presented by some third type.
*
* Let's imagine, you have nullable name in some database. In case when name is not nullable everything is clear - null
* will represent absence of row in the database. In case when name is nullable null will be a little bit dual-meaning,
* cause this null will say nothing about availability of the row (of course, it is exaggerated example)
*
* @see Optional.presented
* @see Optional.absent
* @see Optional.optional
* @see Optional.onPresented
* @see Optional.onAbsent
*/
@Serializable
data class Optional<T> internal constructor(
@Warning("It is unsafe to use this data directly")
val data: T?,
@Warning("It is unsafe to use this data directly")
val dataPresented: Boolean
) {
companion object {
/**
* Will create [Optional] with presented data
*/
fun <T> presented(data: T) = Optional(data, true)
/**
* Will create [Optional] without data
*/
fun <T> absent() = Optional<T>(null, false)
}
}
inline val <T> T.optional
get() = Optional.presented(this)
inline val <T : Any> T?.optionalOrAbsentIfNull
get() = if (this == null) {
Optional.absent<T>()
} else {
Optional.presented(this)
}
/**
* Will call [block] when data presented ([Optional.dataPresented] == true)
*/
inline fun <T> Optional<T>.onPresented(block: (T) -> Unit): Optional<T> = apply {
@OptIn(Warning::class)
if (dataPresented) { @Suppress("UNCHECKED_CAST") block(data as T) }
}
/**
* Will call [block] when data presented ([Optional.dataPresented] == true)
*/
inline fun <T, R> Optional<T>.mapOnPresented(block: (T) -> R): R? = run {
@OptIn(Warning::class)
if (dataPresented) { @Suppress("UNCHECKED_CAST") block(data as T) } else null
}
/**
* Will call [block] when data absent ([Optional.dataPresented] == false)
*/
inline fun <T> Optional<T>.onAbsent(block: () -> Unit): Optional<T> = apply {
@OptIn(Warning::class)
if (!dataPresented) { block() }
}
/**
* Will call [block] when data presented ([Optional.dataPresented] == true)
*/
inline fun <T, R> Optional<T>.mapOnAbsent(block: () -> R): R? = run {
@OptIn(Warning::class)
if (!dataPresented) { block() } else null
}
/**
* Returns [Optional.data] if [Optional.dataPresented] of [this] is true, or null otherwise
*/
fun <T> Optional<T>.dataOrNull() = @OptIn(Warning::class) if (dataPresented) @Suppress("UNCHECKED_CAST") (data as T) else null
/**
* Returns [Optional.data] if [Optional.dataPresented] of [this] is true, or throw [throwable] otherwise
*/
fun <T> Optional<T>.dataOrThrow(throwable: Throwable) = @OptIn(Warning::class) if (dataPresented) @Suppress("UNCHECKED_CAST") (data as T) else throw throwable
/**
* Returns [Optional.data] if [Optional.dataPresented] of [this] is true, or call [block] and returns the result of it
*/
inline fun <T> Optional<T>.dataOrElse(block: () -> T) = @OptIn(Warning::class) if (dataPresented) @Suppress("UNCHECKED_CAST") (data as T) else block()

View File

@@ -0,0 +1,28 @@
package dev.inmo.micro_utils.common
/**
* Convert [this] [Long] to [Int] with bounds of [Int.MIN_VALUE] and [Int.MAX_VALUE]
*/
fun Long.toCoercedInt(): Int = coerceIn(Int.MIN_VALUE.toLong(), Int.MAX_VALUE.toLong()).toInt()
/**
* Convert [this] [Long] to [Short] with bounds of [Short.MIN_VALUE] and [Short.MAX_VALUE]
*/
fun Long.toCoercedShort(): Short = coerceIn(Short.MIN_VALUE.toLong(), Short.MAX_VALUE.toLong()).toShort()
/**
* Convert [this] [Long] to [Byte] with bounds of [Byte.MIN_VALUE] and [Byte.MAX_VALUE]
*/
fun Long.toCoercedByte(): Byte = coerceIn(Byte.MIN_VALUE.toLong(), Byte.MAX_VALUE.toLong()).toByte()
/**
* Convert [this] [Int] to [Short] with bounds of [Short.MIN_VALUE] and [Short.MAX_VALUE]
*/
fun Int.toCoercedShort(): Short = coerceIn(Short.MIN_VALUE.toInt(), Short.MAX_VALUE.toInt()).toShort()
/**
* Convert [this] [Int] to [Byte] with bounds of [Byte.MIN_VALUE] and [Byte.MAX_VALUE]
*/
fun Int.toCoercedByte(): Byte = coerceIn(Byte.MIN_VALUE.toInt(), Byte.MAX_VALUE.toInt()).toByte()
/**
* Convert [this] [Short] to [Byte] with bounds of [Byte.MIN_VALUE] and [Byte.MAX_VALUE]
*/
fun Short.toCoercedByte(): Byte = coerceIn(Byte.MIN_VALUE.toShort(), Byte.MAX_VALUE.toShort()).toByte()

View File

@@ -0,0 +1,37 @@
package dev.inmo.micro_utils.common
import kotlinx.serialization.Serializable
import kotlin.jvm.JvmInline
@Serializable
@JvmInline
value class Progress private constructor(
val of1: Double
) {
val of1Float
get() = of1.toFloat()
val of100
get() = of1 * 100
val of100Float
get() = of100.toFloat()
val of100Int
get() = of100.toInt()
init {
require(of1 in rangeOfValues) {
"Progress main value should be in $rangeOfValues, but incoming value is $of1"
}
}
companion object {
val rangeOfValues = 0.0 .. 1.0
val START = Progress(rangeOfValues.start)
val COMPLETED = Progress(rangeOfValues.endInclusive)
operator fun invoke(of1: Double) = Progress(of1.coerceIn(rangeOfValues))
operator fun invoke(part: Number, total: Number) = Progress(
part.toDouble() / total.toDouble()
)
}
}

View File

@@ -0,0 +1,80 @@
@file:Suppress(
"RemoveRedundantCallsOfConversionMethods",
"RedundantVisibilityModifier",
)
package dev.inmo.micro_utils.common
import kotlin.Byte
import kotlin.Double
import kotlin.Float
import kotlin.Int
import kotlin.Long
import kotlin.Short
import kotlin.Suppress
public operator fun Progress.plus(other: Progress): Progress = Progress(of1 + other.of1)
public operator fun Progress.minus(other: Progress): Progress = Progress(of1 - other.of1)
public operator fun Progress.plus(i: Byte): Progress = Progress((of1 + i).toDouble())
public operator fun Progress.minus(i: Byte): Progress = Progress((of1 - i).toDouble())
public operator fun Progress.times(i: Byte): Progress = Progress((of1 * i).toDouble())
public operator fun Progress.div(i: Byte): Progress = Progress((of1 / i).toDouble())
public operator fun Progress.rem(i: Byte): Progress = Progress((of1 % i).toDouble())
public operator fun Progress.plus(i: Short): Progress = Progress((of1 + i).toDouble())
public operator fun Progress.minus(i: Short): Progress = Progress((of1 - i).toDouble())
public operator fun Progress.times(i: Short): Progress = Progress((of1 * i).toDouble())
public operator fun Progress.div(i: Short): Progress = Progress((of1 / i).toDouble())
public operator fun Progress.rem(i: Short): Progress = Progress((of1 % i).toDouble())
public operator fun Progress.plus(i: Int): Progress = Progress((of1 + i).toDouble())
public operator fun Progress.minus(i: Int): Progress = Progress((of1 - i).toDouble())
public operator fun Progress.times(i: Int): Progress = Progress((of1 * i).toDouble())
public operator fun Progress.div(i: Int): Progress = Progress((of1 / i).toDouble())
public operator fun Progress.rem(i: Int): Progress = Progress((of1 % i).toDouble())
public operator fun Progress.plus(i: Long): Progress = Progress((of1 + i).toDouble())
public operator fun Progress.minus(i: Long): Progress = Progress((of1 - i).toDouble())
public operator fun Progress.times(i: Long): Progress = Progress((of1 * i).toDouble())
public operator fun Progress.div(i: Long): Progress = Progress((of1 / i).toDouble())
public operator fun Progress.rem(i: Long): Progress = Progress((of1 % i).toDouble())
public operator fun Progress.plus(i: Float): Progress = Progress((of1 + i).toDouble())
public operator fun Progress.minus(i: Float): Progress = Progress((of1 - i).toDouble())
public operator fun Progress.times(i: Float): Progress = Progress((of1 * i).toDouble())
public operator fun Progress.div(i: Float): Progress = Progress((of1 / i).toDouble())
public operator fun Progress.rem(i: Float): Progress = Progress((of1 % i).toDouble())
public operator fun Progress.plus(i: Double): Progress = Progress((of1 + i).toDouble())
public operator fun Progress.minus(i: Double): Progress = Progress((of1 - i).toDouble())
public operator fun Progress.times(i: Double): Progress = Progress((of1 * i).toDouble())
public operator fun Progress.div(i: Double): Progress = Progress((of1 / i).toDouble())
public operator fun Progress.rem(i: Double): Progress = Progress((of1 % i).toDouble())
public operator fun Progress.compareTo(other: Progress): Int = (of1 - other.of1).toInt()

View File

@@ -0,0 +1,19 @@
package dev.inmo.micro_utils.common
fun <T : Comparable<T>> ClosedRange<T>.intersect(other: ClosedRange<T>): Pair<T, T>? = when {
start == other.start && endInclusive == other.endInclusive -> start to endInclusive
start > other.endInclusive || other.start > endInclusive -> null
else -> maxOf(start, other.start) to minOf(endInclusive, other.endInclusive)
}
fun IntRange.intersect(
other: IntRange
): IntRange? = (this as ClosedRange<Int>).intersect(other as ClosedRange<Int>) ?.let {
it.first .. it.second
}
fun LongRange.intersect(
other: LongRange
): LongRange? = (this as ClosedRange<Long>).intersect(other as ClosedRange<Long>) ?.let {
it.first .. it.second
}

View File

@@ -0,0 +1,54 @@
package dev.inmo.micro_utils.common
/**
* Executes the given [action] until getting of successful result specified number of [times].
*
* A zero-based index of current iteration is passed as a parameter to [action].
*/
inline fun <R> repeatOnFailure(
onFailure: (Throwable) -> Boolean,
action: () -> R
): Result<R> {
do {
runCatching {
action()
}.onFailure {
if (!onFailure(it)) {
return Result.failure(it)
}
}.onSuccess {
return Result.success(it)
}
} while (true)
}
/**
* Executes the given [action] until getting of successful result specified number of [times].
*
* A zero-based index of current iteration is passed as a parameter to [action].
*/
inline fun <R> repeatOnFailure(
times: Int,
onEachFailure: (Throwable) -> Unit = {},
action: (Int) -> R
): Optional<R> {
var i = 0
val result = repeatOnFailure(
{
onEachFailure(it)
if (i < times) {
i++
true
} else {
false
}
}
) {
action(i)
}
return if (result.isSuccess) {
Optional.presented(result.getOrThrow())
} else {
Optional.absent()
}
}

View File

@@ -0,0 +1,6 @@
package dev.inmo.micro_utils.common
val FixedSignsRange = 0 .. 100
expect fun Float.fixed(signs: Int): Float
expect fun Double.fixed(signs: Int): Double

View File

@@ -32,7 +32,7 @@ class DiffUtilsTests {
val withIndex = oldList.withIndex()
for (count in 1 .. (floor(oldList.size.toFloat() / 2).toInt())) {
for ((i, v) in withIndex) {
for ((i, _) in withIndex) {
if (i + count > oldList.lastIndex) {
continue
}
@@ -54,8 +54,8 @@ class DiffUtilsTests {
val oldList = (0 until 10).map { it.toString() }
val withIndex = oldList.withIndex()
for (step in 0 until oldList.size) {
for ((i, v) in withIndex) {
for (step in oldList.indices) {
for ((i, _) in withIndex) {
val mutable = oldList.toMutableList()
val changes = (
if (step == 0) i until oldList.size else (i until oldList.size step step)
@@ -73,4 +73,83 @@ class DiffUtilsTests {
}
}
}
@Test
fun testThatSimpleRemoveApplyWorks() {
val oldList = (0 until 10).toList()
val withIndex = oldList.withIndex()
for (count in 1 .. (floor(oldList.size.toFloat() / 2).toInt())) {
for ((i, _) in withIndex) {
if (i + count > oldList.lastIndex) {
continue
}
val removedSublist = oldList.subList(i, i + count)
val mutableOldList = oldList.toMutableList()
val targetList = oldList - removedSublist
mutableOldList.applyDiff(targetList)
assertEquals(
targetList,
mutableOldList
)
}
}
}
@Test
fun testThatSimpleAddApplyWorks() {
val oldList = (0 until 10).map { it.toString() }
val withIndex = oldList.withIndex()
for (count in 1 .. (floor(oldList.size.toFloat() / 2).toInt())) {
for ((i, _) 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)
val mutableOldList = oldList.toMutableList()
mutableOldList.applyDiff(mutable)
assertEquals(
mutable,
mutableOldList
)
}
}
}
@Test
fun testThatSimpleChangesApplyWorks() {
val oldList = (0 until 10).map { it.toString() }
val withIndex = oldList.withIndex()
for (step in oldList.indices) {
for ((i, _) in withIndex) {
val mutable = oldList.toMutableList()
val newList = if (step == 0) {
i until oldList.size
} else {
i until oldList.size step step
}
newList.forEach { index ->
IndexedValue(index, mutable[index]) to IndexedValue(index, "changed$index").also {
mutable[index] = it.value
}
}
val mutableOldList = oldList.toMutableList()
mutableOldList.applyDiff(mutable)
assertEquals(
mutable,
mutableOldList
)
}
}
}
}

View File

@@ -0,0 +1,61 @@
package dev.inmo.micro_utils.common
import kotlinx.browser.document
import org.w3c.dom.*
fun Node.onRemoved(block: () -> Unit): MutationObserver {
lateinit var observer: MutationObserver
observer = MutationObserver { _, _ ->
fun checkIfRemoved(node: Node): Boolean {
return node.parentNode != document && (node.parentNode ?.let { checkIfRemoved(it) } ?: true)
}
if (checkIfRemoved(this)) {
observer.disconnect()
block()
}
}
observer.observe(document, MutationObserverInit(childList = true, subtree = true))
return observer
}
fun Element.onVisibilityChanged(block: IntersectionObserverEntry.(Float, IntersectionObserver) -> Unit): IntersectionObserver {
var previousIntersectionRatio = -1f
val observer = IntersectionObserver { entries, observer ->
entries.forEach {
if (previousIntersectionRatio != it.intersectionRatio) {
previousIntersectionRatio = it.intersectionRatio.toFloat()
it.block(previousIntersectionRatio, observer)
}
}
}
observer.observe(this)
return observer
}
fun Element.onVisible(block: Element.(IntersectionObserver) -> Unit) {
var previous = -1f
onVisibilityChanged { intersectionRatio, observer ->
if (previous != intersectionRatio) {
if (intersectionRatio > 0 && previous == 0f) {
block(observer)
}
previous = intersectionRatio
}
}
}
fun Element.onInvisible(block: Element.(IntersectionObserver) -> Unit): IntersectionObserver {
var previous = -1f
return onVisibilityChanged { intersectionRatio, observer ->
if (previous != intersectionRatio) {
if (intersectionRatio == 0f && previous != 0f) {
block(observer)
}
previous = intersectionRatio
}
}
}

View File

@@ -0,0 +1,124 @@
package dev.inmo.micro_utils.common
import org.w3c.dom.DOMRectReadOnly
import org.w3c.dom.Element
external interface IntersectionObserverOptions {
/**
* An Element or Document object which is an ancestor of the intended target, whose bounding rectangle will be
* considered the viewport. Any part of the target not visible in the visible area of the root is not considered
* visible.
*/
var root: Element?
/**
* A string which specifies a set of offsets to add to the root's bounding_box when calculating intersections,
* effectively shrinking or growing the root for calculation purposes. The syntax is approximately the same as that
* for the CSS margin property; see The root element and root margin in Intersection Observer API for more
* information on how the margin works and the syntax. The default is "0px 0px 0px 0px".
*/
var rootMargin: String?
/**
* Either a single number or an array of numbers between 0.0 and 1.0, specifying a ratio of intersection area to
* total bounding box area for the observed target. A value of 0.0 means that even a single visible pixel counts as
* the target being visible. 1.0 means that the entire target element is visible. See Thresholds in Intersection
* Observer API for a more in-depth description of how thresholds are used. The default is a threshold of 0.0.
*/
var threshold: Array<Number>?
}
fun IntersectionObserverOptions(
block: IntersectionObserverOptions.() -> Unit = {}
): IntersectionObserverOptions = js("{}").unsafeCast<IntersectionObserverOptions>().apply(block)
external interface IntersectionObserverEntry {
/**
* Returns the bounds rectangle of the target element as a DOMRectReadOnly. The bounds are computed as described in
* the documentation for Element.getBoundingClientRect().
*/
val boundingClientRect: DOMRectReadOnly
/**
* Returns the ratio of the intersectionRect to the boundingClientRect.
*/
val intersectionRatio: Number
/**
* Returns a DOMRectReadOnly representing the target's visible area.
*/
val intersectionRect: DOMRectReadOnly
/**
* A Boolean value which is true if the target element intersects with the intersection observer's root. If this is
* true, then, the IntersectionObserverEntry describes a transition into a state of intersection; if it's false,
* then you know the transition is from intersecting to not-intersecting.
*/
val isIntersecting: Boolean
/**
* Returns a DOMRectReadOnly for the intersection observer's root.
*/
val rootBounds: DOMRectReadOnly
/**
* The Element whose intersection with the root changed.
*/
val target: Element
/**
* A DOMHighResTimeStamp indicating the time at which the intersection was recorded, relative to the
* IntersectionObserver's time origin.
*/
val time: Double
}
typealias IntersectionObserverCallback = (entries: Array<IntersectionObserverEntry>, observer: IntersectionObserver) -> Unit
/**
* This is just an implementation from [this commentary](https://youtrack.jetbrains.com/issue/KT-43157#focus=Comments-27-4498582.0-0)
* of Kotlin JS issue related to the absence of [Intersection Observer API](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver)
*/
external class IntersectionObserver(callback: IntersectionObserverCallback) {
constructor(callback: IntersectionObserverCallback, options: IntersectionObserverOptions)
/**
* The Element or Document whose bounds are used as the bounding box when testing for intersection. If no root value
* was passed to the constructor or its value is null, the top-level document's viewport is used.
*/
val root: Element
/**
* An offset rectangle applied to the root's bounding box when calculating intersections, effectively shrinking or
* growing the root for calculation purposes. The value returned by this property may not be the same as the one
* specified when calling the constructor as it may be changed to match internal requirements. Each offset can be
* expressed in pixels (px) or as a percentage (%). The default is "0px 0px 0px 0px".
*/
val rootMargin: String
/**
* A list of thresholds, sorted in increasing numeric order, where each threshold is a ratio of intersection area to
* bounding box area of an observed target. Notifications for a target are generated when any of the thresholds are
* crossed for that target. If no value was passed to the constructor, 0 is used.
*/
val thresholds: Array<Number>
/**
* Stops the IntersectionObserver object from observing any target.
*/
fun disconnect()
/**
* Tells the IntersectionObserver a target element to observe.
*/
fun observe(targetElement: Element)
/**
* Returns an array of IntersectionObserverEntry objects for all observed targets.
*/
fun takeRecords(): Array<IntersectionObserverEntry>
/**
* Tells the IntersectionObserver to stop observing a particular target element.
*/
fun unobserve(targetElement: Element)
}

View File

@@ -0,0 +1,12 @@
package dev.inmo.micro_utils.common
import org.w3c.dom.Element
inline val Element.isOverflowWidth
get() = scrollWidth > clientWidth
inline val Element.isOverflowHeight
get() = scrollHeight > clientHeight
inline val Element.isOverflow
get() = isOverflowHeight || isOverflowWidth

View File

@@ -2,10 +2,12 @@ package dev.inmo.micro_utils.common
import org.khronos.webgl.ArrayBuffer
import org.w3c.dom.ErrorEvent
import org.w3c.files.File
import org.w3c.files.FileReader
import org.w3c.files.*
import kotlin.js.Promise
/**
* @suppress
*/
actual typealias MPPFile = File
fun MPPFile.readBytesPromise() = Promise<ByteArray> { success, failure ->
@@ -21,12 +23,32 @@ fun MPPFile.readBytesPromise() = Promise<ByteArray> { success, failure ->
reader.readAsArrayBuffer(this)
}
fun MPPFile.readBytes(): ByteArray {
val reader = FileReaderSync()
return reader.readAsArrayBuffer(this).toByteArray()
}
private suspend fun MPPFile.dirtyReadBytes(): ByteArray = readBytesPromise().await()
/**
* @suppress
*/
actual val MPPFile.filename: FileName
get() = FileName(name)
/**
* @suppress
*/
actual val MPPFile.filesize: Long
get() = size.toLong()
/**
* @suppress
*/
@Warning("That is not optimized version of bytes allocator. Use asyncBytesAllocator everywhere you can")
actual val MPPFile.bytesAllocatorSync: ByteArrayAllocator
get() = ::readBytes
/**
* @suppress
*/
@Warning("That is not optimized version of bytes allocator. Use asyncBytesAllocator everywhere you can")
actual val MPPFile.bytesAllocator: SuspendByteArrayAllocator
get() = ::dirtyReadBytes

View File

@@ -0,0 +1,38 @@
package dev.inmo.micro_utils.common
import kotlinx.browser.document
import org.w3c.dom.*
import org.w3c.dom.events.Event
import org.w3c.dom.events.EventListener
fun Element.onActionOutside(type: String, options: dynamic = null, callback: (Event) -> Unit): EventListener {
lateinit var observer: MutationObserver
val listener = EventListener {
val elementsToCheck = mutableListOf<Element>(this@onActionOutside)
while (it.target != this@onActionOutside && elementsToCheck.isNotEmpty()) {
val childrenGettingElement = elementsToCheck.removeFirst()
for (i in 0 until childrenGettingElement.childElementCount) {
elementsToCheck.add(childrenGettingElement.children[i] ?: continue)
}
}
if (elementsToCheck.isEmpty()) {
callback(it)
}
}
if (options == null) {
document.addEventListener(type, listener)
} else {
document.addEventListener(type, listener, options)
}
observer = onRemoved {
if (options == null) {
document.removeEventListener(type, listener)
} else {
document.removeEventListener(type, listener, options)
}
observer.disconnect()
}
return listener
}
fun Element.onClickOutside(options: dynamic = null, callback: (Event) -> Unit) = onActionOutside("click", options, callback)

View File

@@ -0,0 +1,8 @@
package dev.inmo.micro_utils.common
import kotlinx.browser.window
fun openLink(link: String, target: String = "_blank", features: String = "") {
window.open(link, target, features) ?.focus()
}

View File

@@ -0,0 +1,58 @@
package dev.inmo.micro_utils.common
import org.w3c.dom.*
import kotlin.js.Json
import kotlin.js.json
external class ResizeObserver(
callback: (Array<ResizeObserverEntry>, ResizeObserver) -> Unit
) {
fun observe(target: Element, options: Json = definedExternally)
fun unobserve(target: Element)
fun disconnect()
}
external interface ResizeObserverSize {
val blockSize: Float
val inlineSize: Float
}
external interface ResizeObserverEntry {
val borderBoxSize: Array<ResizeObserverSize>
val contentBoxSize: Array<ResizeObserverSize>
val devicePixelContentBoxSize: Array<ResizeObserverSize>
val contentRect: DOMRectReadOnly
val target: Element
}
fun ResizeObserver.observe(target: Element, options: ResizeObserverObserveOptions) = observe(
target,
json(
"box" to options.box ?.name
)
)
class ResizeObserverObserveOptions(
val box: Box? = null
) {
sealed interface Box {
val name: String
object Content : Box {
override val name: String
get() = "content-box"
}
object Border : Box {
override val name: String
get() = "border-box"
}
object DevicePixelContent : Box {
override val name: String
get() = "device-pixel-content-box"
}
}
}

View File

@@ -0,0 +1,30 @@
package dev.inmo.micro_utils.common
import kotlinx.browser.document
import kotlinx.dom.createElement
import org.w3c.dom.HTMLElement
import org.w3c.dom.HTMLInputElement
import org.w3c.files.get
fun selectFile(
inputSetup: (HTMLInputElement) -> Unit = {},
onFailure: (Throwable) -> Unit = {},
onFile: (MPPFile) -> Unit
) {
(document.createElement("input") {
(this as HTMLInputElement).apply {
type = "file"
onchange = {
runCatching {
files ?.get(0) ?: error("File must not be null")
}.onSuccess {
onFile(it)
}.onFailure {
onFailure(it)
}
}
inputSetup(this)
}
} as HTMLElement).click()
}

View File

@@ -0,0 +1,14 @@
package dev.inmo.micro_utils.common
import kotlinx.browser.document
import org.w3c.dom.HTMLAnchorElement
fun triggerDownloadFile(filename: String, fileLink: String) {
val hiddenElement = document.createElement("a") as HTMLAnchorElement
hiddenElement.href = fileLink
hiddenElement.target = "_blank"
hiddenElement.download = filename
hiddenElement.click()
}

View File

@@ -0,0 +1,4 @@
package dev.inmo.micro_utils.common
actual fun Float.fixed(signs: Int): Float = this.asDynamic().toFixed(signs.coerceIn(FixedSignsRange)).unsafeCast<String>().toFloat()
actual fun Double.fixed(signs: Int): Double = this.asDynamic().toFixed(signs.coerceIn(FixedSignsRange)).unsafeCast<String>().toDouble()

View File

@@ -0,0 +1,20 @@
package dev.inmo.micro_utils.common
import java.io.File
import java.io.InputStream
import java.util.UUID
fun InputStream.downloadToTempFile(
fileName: String = UUID.randomUUID().toString(),
fileExtension: String? = ".temp",
folder: File? = null
) = File.createTempFile(
fileName,
fileExtension,
folder
).apply {
outputStream().use {
copyTo(it)
}
deleteOnExit()
}

View File

@@ -4,12 +4,29 @@ import dev.inmo.micro_utils.coroutines.doInIO
import dev.inmo.micro_utils.coroutines.doOutsideOfCoroutine
import java.io.File
/**
* @suppress
*/
actual typealias MPPFile = File
/**
* @suppress
*/
actual val MPPFile.filename: FileName
get() = FileName(name)
/**
* @suppress
*/
actual val MPPFile.filesize: Long
get() = length()
/**
* @suppress
*/
actual val MPPFile.bytesAllocatorSync: ByteArrayAllocator
get() = ::readBytes
/**
* @suppress
*/
actual val MPPFile.bytesAllocator: SuspendByteArrayAllocator
get() = {
doInIO {

View File

@@ -0,0 +1,12 @@
package dev.inmo.micro_utils.common
import java.math.BigDecimal
import java.math.RoundingMode
actual fun Float.fixed(signs: Int): Float = BigDecimal.valueOf(this.toDouble())
.setScale(signs.coerceIn(FixedSignsRange), RoundingMode.HALF_UP)
.toFloat();
actual fun Double.fixed(signs: Int): Double = BigDecimal.valueOf(this)
.setScale(signs.coerceIn(FixedSignsRange), RoundingMode.HALF_UP)
.toDouble();

View File

@@ -0,0 +1,36 @@
package dev.inmo.micro_utils.common
import okio.FileSystem
import okio.Path
import okio.use
actual typealias MPPFile = Path
/**
* @suppress
*/
actual val MPPFile.filename: FileName
get() = FileName(toString())
/**
* @suppress
*/
actual val MPPFile.filesize: Long
get() = FileSystem.SYSTEM.openReadOnly(this).use {
it.size()
}
/**
* @suppress
*/
actual val MPPFile.bytesAllocatorSync: ByteArrayAllocator
get() = {
FileSystem.SYSTEM.read(this) {
readByteArray()
}
}
/**
* @suppress
*/
actual val MPPFile.bytesAllocator: SuspendByteArrayAllocator
get() = {
bytesAllocatorSync()
}

View File

@@ -0,0 +1,26 @@
package dev.inmo.micro_utils.common
import kotlinx.cinterop.ByteVar
import kotlinx.cinterop.allocArray
import kotlinx.cinterop.memScoped
import kotlinx.cinterop.toKString
import platform.posix.snprintf
import platform.posix.sprintf
actual fun Float.fixed(signs: Int): Float {
return memScoped {
val buff = allocArray<ByteVar>(Float.SIZE_BYTES * 2)
sprintf(buff, "%.${signs}f", this@fixed)
buff.toKString().toFloat()
}
}
actual fun Double.fixed(signs: Int): Double {
return memScoped {
val buff = allocArray<ByteVar>(Double.SIZE_BYTES * 2)
sprintf(buff, "%.${signs}f", this@fixed)
buff.toKString().toDouble()
}
}

View File

@@ -0,0 +1,76 @@
package dev.inmo.micro_utils.common
import android.os.Bundle
import android.os.Parcelable
import androidx.fragment.app.Fragment
import java.io.Serializable
import kotlin.reflect.KProperty
object ArgumentPropertyNullableDelegate {
operator fun <T: Any> getValue(thisRef: Fragment, property: KProperty<*>): T? {
val arguments = thisRef.arguments ?: return null
val key = property.name
return when (property.getter.returnType.classifier) {
// Scalars
String::class -> arguments.getString(key)
Boolean::class -> arguments.getBoolean(key)
Byte::class -> arguments.getByte(key)
Char::class -> arguments.getChar(key)
Double::class -> arguments.getDouble(key)
Float::class -> arguments.getFloat(key)
Int::class -> arguments.getInt(key)
Long::class -> arguments.getLong(key)
Short::class -> arguments.getShort(key)
// References
Bundle::class -> arguments.getBundle(key)
CharSequence::class -> arguments.getCharSequence(key)
Parcelable::class -> arguments.getParcelable(key)
// Scalar arrays
BooleanArray::class -> arguments.getBooleanArray(key)
ByteArray::class -> arguments.getByteArray(key)
CharArray::class -> arguments.getCharArray(key)
DoubleArray::class -> arguments.getDoubleArray(key)
FloatArray::class -> arguments.getFloatArray(key)
IntArray::class -> arguments.getIntArray(key)
LongArray::class -> arguments.getLongArray(key)
ShortArray::class -> arguments.getShortArray(key)
Array::class -> {
val componentType = property.returnType.classifier ?.javaClass ?.componentType!!
@Suppress("UNCHECKED_CAST") // Checked by reflection.
when {
Parcelable::class.java.isAssignableFrom(componentType) -> {
arguments.getParcelableArray(key)
}
String::class.java.isAssignableFrom(componentType) -> {
arguments.getStringArray(key)
}
CharSequence::class.java.isAssignableFrom(componentType) -> {
arguments.getCharSequenceArray(key)
}
Serializable::class.java.isAssignableFrom(componentType) -> {
arguments.getSerializable(key)
}
else -> {
val valueType = componentType.canonicalName
throw IllegalArgumentException(
"Illegal value array type $valueType for key \"$key\""
)
}
}
}
Serializable::class -> arguments.getSerializable(key)
else -> null
} as? T
}
}
object ArgumentPropertyNonNullableDelegate {
operator fun <T: Any> getValue(thisRef: Fragment, property: KProperty<*>): T {
return ArgumentPropertyNullableDelegate.getValue<T>(thisRef, property)!!
}
}
fun argumentOrNull() = ArgumentPropertyNullableDelegate
fun argumentOrThrow() = ArgumentPropertyNonNullableDelegate

View File

@@ -0,0 +1,61 @@
package dev.inmo.micro_utils.common
import android.app.Activity
import android.view.View
import android.view.ViewGroup
import androidx.core.view.children
import androidx.fragment.app.Fragment
fun findViewsByTag(viewGroup: ViewGroup, tag: Any?): List<View> {
return viewGroup.children.flatMap {
findViewsByTag(it, tag)
}.toList()
}
fun findViewsByTag(viewGroup: ViewGroup, key: Int, tag: Any?): List<View> {
return viewGroup.children.flatMap {
findViewsByTag(it, key, tag)
}.toList()
}
fun findViewsByTag(view: View, tag: Any?): List<View> {
val result = mutableListOf<View>()
if (view.tag == tag) {
result.add(view)
}
if (view is ViewGroup) {
result.addAll(findViewsByTag(view, tag))
}
return result.toList()
}
fun findViewsByTag(view: View, key: Int, tag: Any?): List<View> {
val result = mutableListOf<View>()
if (view.getTag(key) == tag) {
result.add(view)
}
if (view is ViewGroup) {
result.addAll(findViewsByTag(view, key, tag))
}
return result.toList()
}
fun Activity.findViewsByTag(tag: Any?) = rootView ?.let {
findViewsByTag(it, tag)
}
fun Activity.findViewsByTag(key: Int, tag: Any?) = rootView ?.let {
findViewsByTag(it, key, tag)
}
fun Fragment.findViewsByTag(tag: Any?) = view ?.let {
findViewsByTag(it, tag)
}
fun Fragment.findViewsByTag(key: Int, tag: Any?) = view ?.let {
findViewsByTag(it, key, tag)
}
fun Fragment.findViewsByTagInActivity(tag: Any?) = activity ?.findViewsByTag(tag)
fun Fragment.findViewsByTagInActivity(key: Int, tag: Any?) = activity ?.findViewsByTag(key, tag)

View File

@@ -0,0 +1,7 @@
package dev.inmo.micro_utils.common
import android.app.Activity
import android.view.View
val Activity.rootView: View?
get() = findViewById<View?>(android.R.id.content) ?.rootView ?: window.decorView.findViewById<View?>(android.R.id.content) ?.rootView

View File

@@ -0,0 +1,36 @@
package dev.inmo.micro_utils.common
import okio.FileSystem
import okio.Path
import okio.use
actual typealias MPPFile = Path
/**
* @suppress
*/
actual val MPPFile.filename: FileName
get() = FileName(toString())
/**
* @suppress
*/
actual val MPPFile.filesize: Long
get() = FileSystem.SYSTEM.openReadOnly(this).use {
it.size()
}
/**
* @suppress
*/
actual val MPPFile.bytesAllocatorSync: ByteArrayAllocator
get() = {
FileSystem.SYSTEM.read(this) {
readByteArray()
}
}
/**
* @suppress
*/
actual val MPPFile.bytesAllocator: SuspendByteArrayAllocator
get() = {
bytesAllocatorSync()
}

View File

@@ -0,0 +1,26 @@
package dev.inmo.micro_utils.common
import kotlinx.cinterop.ByteVar
import kotlinx.cinterop.allocArray
import kotlinx.cinterop.memScoped
import kotlinx.cinterop.toKString
import platform.posix.snprintf
import platform.posix.sprintf
actual fun Float.fixed(signs: Int): Float {
return memScoped {
val buff = allocArray<ByteVar>(Float.SIZE_BYTES * 2)
sprintf(buff, "%.${signs}f", this@fixed)
buff.toKString().toFloat()
}
}
actual fun Double.fixed(signs: Int): Double {
return memScoped {
val buff = allocArray<ByteVar>(Double.SIZE_BYTES * 2)
sprintf(buff, "%.${signs}f", this@fixed)
buff.toKString().toDouble()
}
}

View File

@@ -10,13 +10,19 @@ kotlin {
sourceSets {
commonMain {
dependencies {
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version"
api libs.kt.coroutines
}
}
jsMain {
dependencies {
api project(":micro_utils.common")
}
}
androidMain {
dependencies {
api "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
api libs.kt.coroutines.android
}
dependsOn(jvmMain)
}
}
}

View File

@@ -0,0 +1,20 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
alias(libs.plugins.jb.compose)
}
apply from: "$mppProjectWithSerializationAndComposePresetPath"
kotlin {
sourceSets {
commonMain {
dependencies {
api libs.kt.coroutines
api project(":micro_utils.coroutines")
api project(":micro_utils.common.compose")
}
}
}
}

View File

@@ -0,0 +1,60 @@
package dev.inmo.micro_utils.coroutines.compose
import androidx.compose.runtime.*
import androidx.compose.runtime.snapshots.SnapshotStateList
import dev.inmo.micro_utils.common.applyDiff
import dev.inmo.micro_utils.coroutines.ExceptionHandler
import dev.inmo.micro_utils.coroutines.defaultSafelyWithoutExceptionHandlerWithNull
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.withContext
import kotlin.coroutines.CoroutineContext
/**
* Each value of [this] [Flow] will trigger [applyDiff] to the result [SnapshotStateList]
*
* @param scope Will be used to [subscribeSafelyWithoutExceptions] on [this] to update returned [SnapshotStateList]
* @param useContextOnChange Will be used to change context inside of [subscribeSafelyWithoutExceptions] to ensure that
* change will happen in the required [CoroutineContext]. [Dispatchers.Main] by default
* @param onException Will be passed to the [subscribeSafelyWithoutExceptions] as uncaught exceptions handler
*/
@Suppress("NOTHING_TO_INLINE")
inline fun <reified T> Flow<List<T>>.asMutableComposeListState(
scope: CoroutineScope,
useContextOnChange: CoroutineContext? = Dispatchers.Main,
noinline onException: ExceptionHandler<List<T>?> = defaultSafelyWithoutExceptionHandlerWithNull,
): SnapshotStateList<T> {
val state = mutableStateListOf<T>()
val changeBlock: suspend (List<T>) -> Unit = useContextOnChange ?.let {
{
withContext(useContextOnChange) {
state.applyDiff(it)
}
}
} ?: {
state.applyDiff(it)
}
subscribeSafelyWithoutExceptions(scope, onException, changeBlock)
return state
}
/**
* In fact, it is just classcast of [asMutableComposeListState] to [List]
*
* @param scope Will be used to [subscribeSafelyWithoutExceptions] on [this] to update returned [List]
* @param useContextOnChange Will be used to change context inside of [subscribeSafelyWithoutExceptions] to ensure that
* change will happen in the required [CoroutineContext]. [Dispatchers.Main] by default
* @param onException Will be passed to the [subscribeSafelyWithoutExceptions] as uncaught exceptions handler
*
* @return Changing in time [List] which follow [Flow] values
*/
@Suppress("NOTHING_TO_INLINE")
inline fun <reified T> Flow<List<T>>.asComposeList(
scope: CoroutineScope,
useContextOnChange: CoroutineContext? = Dispatchers.Main,
noinline onException: ExceptionHandler<List<T>?> = defaultSafelyWithoutExceptionHandlerWithNull,
): List<T> = asMutableComposeListState(scope, useContextOnChange, onException)

View File

@@ -0,0 +1,94 @@
package dev.inmo.micro_utils.coroutines.compose
import androidx.compose.runtime.*
import dev.inmo.micro_utils.common.compose.asState
import dev.inmo.micro_utils.coroutines.ExceptionHandler
import dev.inmo.micro_utils.coroutines.defaultSafelyWithoutExceptionHandlerWithNull
import dev.inmo.micro_utils.coroutines.doInUI
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.withContext
import kotlin.coroutines.CoroutineContext
/**
* Will map [this] [Flow] as [MutableState]. Returned [MutableState] WILL NOT change source [Flow]
*
* @param initial First value which will be passed to the result [MutableState]
* @param scope Will be used to [subscribeSafelyWithoutExceptions] on [this] to update returned [MutableState]
* @param useContextOnChange Will be used to change context inside of [subscribeSafelyWithoutExceptions] to ensure that
* change will happen in the required [CoroutineContext]. [Dispatchers.Main] by default
* @param onException Will be passed to the [subscribeSafelyWithoutExceptions] as uncaught exceptions handler
*/
fun <T> Flow<T>.asMutableComposeState(
initial: T,
scope: CoroutineScope,
useContextOnChange: CoroutineContext? = Dispatchers.Main,
onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
): MutableState<T> {
val state = mutableStateOf(initial)
val changeBlock: suspend (T) -> Unit = useContextOnChange ?.let {
{
withContext(useContextOnChange) {
state.value = it
}
}
} ?: {
state.value = it
}
subscribeSafelyWithoutExceptions(scope, onException, block = changeBlock)
return state
}
/**
* Will map [this] [StateFlow] as [MutableState]. Returned [MutableState] WILL NOT change source [StateFlow].
* This conversation will pass its [StateFlow.value] as the first value
*
* @param scope Will be used to [subscribeSafelyWithoutExceptions] on [this] to update returned [MutableState]
* @param useContextOnChange Will be used to change context inside of [subscribeSafelyWithoutExceptions] to ensure that
* change will happen in the required [CoroutineContext]. [Dispatchers.Main] by default
* @param onException Will be passed to the [subscribeSafelyWithoutExceptions] as uncaught exceptions handler
*/
@Suppress("NOTHING_TO_INLINE")
inline fun <T> StateFlow<T>.asMutableComposeState(
scope: CoroutineScope,
useContextOnChange: CoroutineContext? = Dispatchers.Main,
noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
): MutableState<T> = asMutableComposeState(value, scope, useContextOnChange, onException)
/**
* Will create [MutableState] using [asMutableComposeState] and use [asState] to convert it as immutable state
*
* @param initial First value which will be passed to the result [State]
* @param scope Will be used to [subscribeSafelyWithoutExceptions] on [this] to update returned [State]
* @param useContextOnChange Will be used to change context inside of [subscribeSafelyWithoutExceptions] to ensure that
* change will happen in the required [CoroutineContext]. [Dispatchers.Main] by default
* @param onException Will be passed to the [subscribeSafelyWithoutExceptions] as uncaught exceptions handler
*/
fun <T> Flow<T>.asComposeState(
initial: T,
scope: CoroutineScope,
useContextOnChange: CoroutineContext? = Dispatchers.Main,
onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
): State<T> {
val state = asMutableComposeState(initial, scope, useContextOnChange, onException)
return state.asState()
}
/**
* Will map [this] [StateFlow] as [State]. This conversation will pass its [StateFlow.value] as the first value
*
* @param scope Will be used to [subscribeSafelyWithoutExceptions] on [this] to update returned [State]
* @param useContextOnChange Will be used to change context inside of [subscribeSafelyWithoutExceptions] to ensure that
* change will happen in the required [CoroutineContext]. [Dispatchers.Main] by default
* @param onException Will be passed to the [subscribeSafelyWithoutExceptions] as uncaught exceptions handler
*/
@Suppress("NOTHING_TO_INLINE")
inline fun <T> StateFlow<T>.asComposeState(
scope: CoroutineScope,
useContextOnChange: CoroutineContext? = Dispatchers.Main,
noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
): State<T> = asComposeState(value, scope, useContextOnChange, onException)

View File

@@ -0,0 +1,21 @@
package dev.inmo.micro_utils.coroutines.compose
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
@Deprecated("Duplicated functionality", ReplaceWith("asMutableComposeState(initial, scope)", "dev.inmo.micro_utils.coroutines.compose.asMutableComposeState"))
fun <T> Flow<T>.toMutableState(
initial: T,
scope: CoroutineScope
): MutableState<T> = asMutableComposeState(initial, scope)
@Deprecated("Duplicated functionality", ReplaceWith("asMutableComposeState(scope)", "dev.inmo.micro_utils.coroutines.compose.asMutableComposeState"))
@Suppress("NOTHING_TO_INLINE")
inline fun <T> StateFlow<T>.toMutableState(
scope: CoroutineScope
): MutableState<T> = asMutableComposeState(scope)

View File

@@ -0,0 +1,14 @@
package dev.inmo.micro_utils.coroutines.compose
import androidx.compose.runtime.*
import kotlinx.coroutines.Job
import kotlinx.coroutines.job
import kotlin.coroutines.CoroutineContext
fun Composition.linkWithJob(job: Job) {
job.invokeOnCompletion {
this@linkWithJob.dispose()
}
}
fun Composition.linkWithContext(coroutineContext: CoroutineContext) = linkWithJob(coroutineContext.job)

View File

@@ -0,0 +1,26 @@
package dev.inmo.micro_utils.coroutines.compose
import androidx.compose.runtime.*
import dev.inmo.micro_utils.common.compose.linkWithElement
import kotlinx.coroutines.*
import org.jetbrains.compose.web.dom.DOMScope
import org.w3c.dom.Element
suspend fun <TElement : Element> renderComposableAndLinkToContext(
root: TElement,
monotonicFrameClock: MonotonicFrameClock = DefaultMonotonicFrameClock,
content: @Composable DOMScope<TElement>.() -> Unit
): Composition = org.jetbrains.compose.web.renderComposable(root, monotonicFrameClock, content).apply {
linkWithContext(
currentCoroutineContext()
)
}
suspend fun <TElement : Element> renderComposableAndLinkToContextAndRoot(
root: TElement,
monotonicFrameClock: MonotonicFrameClock = DefaultMonotonicFrameClock,
content: @Composable DOMScope<TElement>.() -> Unit
): Composition = org.jetbrains.compose.web.renderComposable(root, monotonicFrameClock, content).apply {
linkWithContext(currentCoroutineContext())
linkWithElement(root)
}

View File

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

View File

@@ -0,0 +1,100 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlin.coroutines.cancellation.CancellationException
private sealed interface AccumulatorFlowStep<T>
private data class DataRetrievedAccumulatorFlowStep<T>(val data: T) : AccumulatorFlowStep<T>
private data class SubscribeAccumulatorFlowStep<T>(val channel: Channel<T>) : AccumulatorFlowStep<T>
private data class UnsubscribeAccumulatorFlowStep<T>(val channel: Channel<T>) : AccumulatorFlowStep<T>
/**
* This [Flow] will have behaviour very similar to [SharedFlow], but there are several differences:
*
* * All unhandled by [FlowCollector] data will not be removed from [AccumulatorFlow] and will be sent to new
* [FlowCollector]s until anybody will handle it
* * Here there are an [activeData] where data [T] will be stored until somebody will handle it
*/
class AccumulatorFlow<T>(
sourceDataFlow: Flow<T>,
scope: CoroutineScope
) : AbstractFlow<T>() {
private val subscope = scope.LinkedSupervisorScope()
private val activeData = ArrayDeque<T>()
private val dataMutex = Mutex()
private val channelsForBroadcast = mutableListOf<Channel<T>>()
private val channelsMutex = Mutex()
private val steps = subscope.actor<AccumulatorFlowStep<T>> { step ->
when (step) {
is DataRetrievedAccumulatorFlowStep -> {
if (activeData.firstOrNull() === step.data) {
dataMutex.withLock {
activeData.removeFirst()
}
}
}
is SubscribeAccumulatorFlowStep -> channelsMutex.withLock {
channelsForBroadcast.add(step.channel)
dataMutex.withLock {
val dataToSend = activeData.toList()
safelyWithoutExceptions {
dataToSend.forEach { step.channel.send(it) }
}
}
}
is UnsubscribeAccumulatorFlowStep -> channelsMutex.withLock {
channelsForBroadcast.remove(step.channel)
}
}
}
private val subscriptionJob = sourceDataFlow.subscribeSafelyWithoutExceptions(subscope) {
dataMutex.withLock {
activeData.addLast(it)
}
channelsMutex.withLock {
channelsForBroadcast.forEach { channel ->
safelyWithResult {
channel.send(it)
}
}
}
}
override suspend fun collectSafely(collector: FlowCollector<T>) {
val channel = Channel<T>(Channel.UNLIMITED, BufferOverflow.SUSPEND)
steps.send(SubscribeAccumulatorFlowStep(channel))
val result = runCatchingSafely {
for (data in channel) {
val emitResult = runCatchingSafely {
collector.emit(data)
}
if (emitResult.isSuccess || emitResult.exceptionOrNull() is CancellationException) {
steps.send(DataRetrievedAccumulatorFlowStep(data))
}
emitResult.getOrThrow()
}
}
channel.cancel()
steps.send(UnsubscribeAccumulatorFlowStep(channel))
result.getOrThrow()
}
}
/**
* Creates [AccumulatorFlow] using [this] as base [Flow]
*/
fun <T> Flow<T>.accumulatorFlow(scope: CoroutineScope): Flow<T> {
return AccumulatorFlow(this, scope)
}
/**
* Creates [AccumulatorFlow] using [this] with [receiveAsFlow] to get
*/
fun <T> Channel<T>.accumulatorFlow(scope: CoroutineScope): Flow<T> {
return receiveAsFlow().accumulatorFlow(scope)
}

View File

@@ -1,19 +1,15 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.launch
import kotlinx.coroutines.flow.consumeAsFlow
fun <T> CoroutineScope.actor(
channelCapacity: Int = Channel.UNLIMITED,
block: suspend (T) -> Unit
): Channel<T> {
val channel = Channel<T>(channelCapacity)
launch {
for (data in channel) {
block(data)
}
}
channel.consumeAsFlow().subscribe(this, block)
return channel
}

View File

@@ -0,0 +1,30 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.consumeAsFlow
fun <T> CoroutineScope.actorAsync(
channelCapacity: Int = Channel.UNLIMITED,
markerFactory: suspend (T) -> Any? = { null },
block: suspend (T) -> Unit
): Channel<T> {
val channel = Channel<T>(channelCapacity)
channel.consumeAsFlow().subscribeAsync(this, markerFactory, block)
return channel
}
inline fun <T> CoroutineScope.safeActorAsync(
channelCapacity: Int = Channel.UNLIMITED,
noinline onException: ExceptionHandler<Unit> = defaultSafelyExceptionHandler,
noinline markerFactory: suspend (T) -> Any? = { null },
crossinline block: suspend (T) -> Unit
): Channel<T> = actorAsync(
channelCapacity,
markerFactory
) {
safely(onException) {
block(it)
}
}

View File

@@ -6,23 +6,19 @@ import kotlin.coroutines.*
suspend fun <T> Iterable<Deferred<T>>.awaitFirstWithDeferred(
scope: CoroutineScope,
cancelOnResult: Boolean = true
): Pair<Deferred<T>, T> = suspendCoroutine<Pair<Deferred<T>, T>> { continuation ->
scope.launch(SupervisorJob()) {
val scope = this
forEach {
scope.launch {
continuation.resume(it to it.await())
scope.cancel()
}
): Pair<Deferred<T>, T> {
val resultDeferred = CompletableDeferred<Pair<Deferred<T>, T>>()
val scope = scope.LinkedSupervisorScope()
forEach {
scope.launch {
resultDeferred.complete(it to it.await())
scope.cancel()
}
}
}.also {
if (cancelOnResult) {
forEach {
try {
it.cancel()
} catch (e: IllegalStateException) {
e.printStackTrace()
return resultDeferred.await().also {
if (cancelOnResult) {
forEach {
runCatchingSafely { it.cancel() }
}
}
}

View File

@@ -0,0 +1,39 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.flow.*
import kotlin.js.JsName
import kotlin.jvm.JvmName
inline fun <T, R> Flow<Flow<T>>.flatMap(
crossinline mapper: suspend (T) -> R
) = flow {
collect {
it.collect {
emit(mapper(it))
}
}
}
@JsName("flatMapIterable")
@JvmName("flatMapIterable")
inline fun <T, R> Flow<Iterable<T>>.flatMap(
crossinline mapper: suspend (T) -> R
) = map {
it.asFlow()
}.flatMap(mapper)
inline fun <T, R> Flow<Flow<T>>.flatMapNotNull(
crossinline mapper: suspend (T) -> R
) = flatMap(mapper).takeNotNull()
@JsName("flatMapNotNullIterable")
@JvmName("flatMapNotNullIterable")
inline fun <T, R> Flow<Iterable<T>>.flatMapNotNull(
crossinline mapper: suspend (T) -> R
) = flatMap(mapper).takeNotNull()
fun <T> Flow<Flow<T>>.flatten() = flatMap { it }
@JsName("flattenIterable")
@JvmName("flattenIterable")
fun <T> Flow<Iterable<T>>.flatten() = flatMap { it }

View File

@@ -0,0 +1,6 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.flow.*
fun <T> Flow<T>.takeNotNull() = mapNotNull { it }
fun <T> Flow<T>.filterNotNull() = takeNotNull()

View File

@@ -115,10 +115,21 @@ suspend inline fun <T> runCatchingSafely(
safely(onException, block)
}
suspend inline fun <T, R> T.runCatchingSafely(
noinline onException: ExceptionHandler<R> = defaultSafelyExceptionHandler,
noinline block: suspend T.() -> R
): Result<R> = runCatching {
safely(onException) { block() }
}
suspend inline fun <T> safelyWithResult(
noinline block: suspend CoroutineScope.() -> T
): Result<T> = runCatchingSafely(defaultSafelyExceptionHandler, block)
suspend inline fun <T, R> T.safelyWithResult(
noinline block: suspend T.() -> R
): Result<R> = runCatchingSafely(defaultSafelyExceptionHandler, block)
/**
* Use this handler in cases you wish to include handling of exceptions by [defaultSafelyWithoutExceptionHandler] and
* returning null at one time
@@ -147,3 +158,10 @@ suspend inline fun <T> runCatchingSafelyWithoutExceptions(
): Result<T?> = runCatching {
safelyWithoutExceptions(onException, block)
}
inline fun CoroutineScope(
context: CoroutineContext,
noinline defaultExceptionsHandler: ExceptionHandler<Unit>
) = CoroutineScope(
context + ContextSafelyExceptionHandler(defaultExceptionsHandler)
)

View File

@@ -0,0 +1,28 @@
package dev.inmo.micro_utils.coroutines
import dev.inmo.micro_utils.common.onRemoved
import dev.inmo.micro_utils.common.onVisibilityChanged
import kotlinx.coroutines.flow.*
import org.w3c.dom.Element
fun Element.visibilityFlow(): Flow<Boolean> = channelFlow {
var previousData: Boolean? = null
val observer = onVisibilityChanged { intersectionRatio, _ ->
val currentData = intersectionRatio > 0
if (currentData != previousData) {
trySend(currentData)
}
previousData = currentData
}
val removeObserver = onRemoved {
observer.disconnect()
close()
}
invokeOnClose {
observer.disconnect()
removeObserver.disconnect()
}
}

View File

@@ -0,0 +1,26 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Job
import org.w3c.dom.Image
suspend fun preloadImage(src: String): Image {
val image = Image()
image.src = src
val job = Job()
image.addEventListener("load", {
runCatching { job.complete() }
})
runCatchingSafely {
job.join()
}.onFailure {
if (it is CancellationException) {
image.src = ""
}
}.getOrThrow()
return image
}

View File

@@ -0,0 +1,42 @@
package dev.inmo.micro_utils.coroutines
import dev.inmo.micro_utils.common.MPPFile
import dev.inmo.micro_utils.common.selectFile
import kotlinx.coroutines.CompletableDeferred
import org.w3c.dom.HTMLInputElement
suspend fun selectFileOrThrow(
inputSetup: (HTMLInputElement) -> Unit = {}
): MPPFile {
val result = CompletableDeferred<MPPFile>()
selectFile(
inputSetup,
{
result.completeExceptionally(it)
}
) {
result.complete(it)
}
return result.await()
}
suspend fun selectFileOrNull(
inputSetup: (HTMLInputElement) -> Unit = {},
onFailure: (Throwable) -> Unit = {}
): MPPFile? {
val result = CompletableDeferred<MPPFile?>()
selectFile(
inputSetup,
{
result.complete(null)
onFailure(it)
}
) {
result.complete(it)
}
return result.await()
}

View File

@@ -0,0 +1,9 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
fun <T> launchInCurrentThread(block: suspend CoroutineScope.() -> T): T {
val scope = CoroutineScope(Dispatchers.Unconfined)
return scope.launchSynchronously(block)
}

View File

@@ -6,7 +6,7 @@ fun <T> CoroutineScope.launchSynchronously(block: suspend CoroutineScope.() -> T
var result: Result<T>? = null
val objectToSynchronize = Object()
synchronized(objectToSynchronize) {
launch {
launch(start = CoroutineStart.UNDISPATCHED) {
result = safelyWithResult(block)
}.invokeOnCompletion {
synchronized(objectToSynchronize) {

View File

@@ -0,0 +1,47 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext
import kotlin.test.Test
import kotlin.test.assertEquals
class LaunchInCurrentThreadTests {
@Test
fun simpleTestThatLaunchInCurrentThreadWorks() {
val expectedResult = 10
val result = launchInCurrentThread {
expectedResult
}
assertEquals(expectedResult, result)
}
@Test
fun simpleTestThatSeveralLaunchInCurrentThreadWorks() {
val testData = 0 until 100
testData.forEach {
val result = launchInCurrentThread {
it
}
assertEquals(it, result)
}
}
@Test
fun simpleTestThatLaunchInCurrentThreadWillCorrectlyHandleSuspensionsWorks() {
val testData = 0 until 100
suspend fun test(data: Any): Any {
return withContext(Dispatchers.Default) {
delay(1)
data
}
}
testData.forEach {
val result = launchInCurrentThread {
test(it)
}
assertEquals(it, result)
}
}
}

View File

@@ -0,0 +1,50 @@
package dev.inmo.micro_utils.coroutines
import android.view.View
import android.view.ViewGroup
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
/**
* [kotlinx.coroutines.flow.Flow]-based [android.view.ViewGroup.OnHierarchyChangeListener]
*
* @param recursive If set, any call of [onChildViewAdded] will check if child [View] is [ViewGroup] and subscribe to this
* [ViewGroup] too
* @param [_onChildViewAdded] Internal [MutableSharedFlow] which will be used to pass data to [onChildViewAdded] flow
* @param [_onChildViewRemoved] Internal [MutableSharedFlow] which will be used to pass data to [onChildViewRemoved] flow
*/
class FlowOnHierarchyChangeListener(
private val recursive: Boolean = false,
private val _onChildViewAdded: MutableSharedFlow<Pair<View, View>> = MutableSharedFlow(extraBufferCapacity = Int.MAX_VALUE),
private val _onChildViewRemoved: MutableSharedFlow<Pair<View, View>> = MutableSharedFlow(extraBufferCapacity = Int.MAX_VALUE)
) : ViewGroup.OnHierarchyChangeListener {
val onChildViewAdded = _onChildViewAdded.asSharedFlow()
val onChildViewRemoved = _onChildViewRemoved.asSharedFlow()
/**
* Will emit data into [onChildViewAdded] flow. If [recursive] is true and [child] is [ViewGroup] will also
* subscribe to [child] hierarchy changes.
*
* Due to the fact that this method is not suspendable, [FlowOnHierarchyChangeListener] will use
* [MutableSharedFlow.tryEmit] to send data into [_onChildViewAdded]. That is why its default extraBufferCapacity is
* [Int.MAX_VALUE]
*/
override fun onChildViewAdded(parent: View, child: View) {
_onChildViewAdded.tryEmit(parent to child)
if (recursive && child is ViewGroup) {
child.setOnHierarchyChangeListener(this)
}
}
/**
* Just emit data into [onChildViewRemoved]
*
* Due to the fact that this method is not suspendable, [FlowOnHierarchyChangeListener] will use
* [MutableSharedFlow.tryEmit] to send data into [_onChildViewRemoved]. That is why its default extraBufferCapacity is
* [Int.MAX_VALUE]
*/
override fun onChildViewRemoved(parent: View, child: View) {
_onChildViewRemoved.tryEmit(parent to child)
}
}

View File

@@ -0,0 +1,17 @@
package dev.inmo.micro_utils.coroutines
import android.view.ViewGroup
import android.view.ViewGroup.OnHierarchyChangeListener
/**
* Use [ViewGroup.setOnHierarchyChangeListener] recursively for all available [ViewGroup]s starting with [this].
* This extension DO NOT guarantee that recursive subscription will happen after this method call
*/
fun ViewGroup.setOnHierarchyChangeListenerRecursively(
listener: OnHierarchyChangeListener
) {
setOnHierarchyChangeListener(listener)
(0 until childCount).forEach {
(getChildAt(it) as? ViewGroup) ?.setOnHierarchyChangeListenerRecursively(listener)
}
}

View File

@@ -11,6 +11,7 @@ kotlin {
commonMain {
dependencies {
api project(":micro_utils.common")
api libs.krypto
}
}
jsMain {

View File

@@ -0,0 +1,15 @@
package dev.inmo.micro_utils.crypto
val HEX_ARRAY = "0123456789abcdef".toCharArray()
fun SourceBytes.hex(): String {
val hexChars = CharArray(size * 2)
for (j in indices) {
val v: Int = this[j].toInt() and 0xFF
hexChars[j * 2] = HEX_ARRAY[v ushr 4]
hexChars[j * 2 + 1] = HEX_ARRAY[v and 0x0F]
}
return hexChars.concatToString()
}
fun SourceString.hex(): String = encodeToByteArray().hex()

View File

@@ -1,6 +1,8 @@
package dev.inmo.micro_utils.crypto
import com.soywiz.krypto.md5
typealias MD5 = String
expect fun SourceBytes.md5(): MD5
fun SourceString.md5(): MD5 = encodeToByteArray().md5()
fun SourceBytes.md5(): MD5 = md5().hexLower
fun SourceString.md5(): MD5 = encodeToByteArray().md5().hexLower

View File

@@ -0,0 +1,15 @@
package dev.inmo.micro_utils.crypto
import kotlin.test.*
class Hex {
@Test
fun testSimpleHmacSHA256Message() {
val text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
val resultHex = text.hex()
assertEquals(
"4c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e73656374657475722061646970697363696e6720656c69742c2073656420646f20656975736d6f642074656d706f7220696e6369646964756e74207574206c61626f726520657420646f6c6f7265206d61676e6120616c697175612e20557420656e696d206164206d696e696d2076656e69616d2c2071756973206e6f737472756420657865726369746174696f6e20756c6c616d636f206c61626f726973206e69736920757420616c697175697020657820656120636f6d6d6f646f20636f6e7365717561742e2044756973206175746520697275726520646f6c6f7220696e20726570726568656e646572697420696e20766f6c7570746174652076656c697420657373652063696c6c756d20646f6c6f726520657520667567696174206e756c6c612070617269617475722e204578636570746575722073696e74206f6363616563617420637570696461746174206e6f6e2070726f6964656e742c2073756e7420696e2063756c706120717569206f666669636961206465736572756e74206d6f6c6c697420616e696d20696420657374206c61626f72756d2e",
resultHex
)
}
}

View File

@@ -1,3 +0,0 @@
package dev.inmo.micro_utils.crypto
actual fun SourceBytes.md5(): MD5 = CryptoJS.MD5(decodeToString())

View File

@@ -1,9 +0,0 @@
package dev.inmo.micro_utils.crypto
import java.math.BigInteger
import java.security.MessageDigest
actual fun SourceBytes.md5(): MD5 = BigInteger(
1,
MessageDigest.getInstance("MD5").digest(this)
).toString(16)

View File

@@ -1,37 +1,12 @@
apply plugin: 'com.getkeepsafe.dexcount'
ext {
jvmKotlinFolderFile = {
String sep = File.separator
return new File("${project.projectDir}${sep}src${sep}jvmMain${sep}kotlin")
}
enableIncludingJvmCodeInAndroidPart = {
File jvmKotlinFolder = jvmKotlinFolderFile()
if (jvmKotlinFolder.exists()) {
android.sourceSets.main.java.srcDirs += jvmKotlinFolder.path
}
}
disableIncludingJvmCodeInAndroidPart = {
File jvmKotlinFolder = jvmKotlinFolderFile()
String[] oldDirs = android.sourceSets.main.java.srcDirs
android.sourceSets.main.java.srcDirs = []
for (oldDir in oldDirs) {
if (oldDir != jvmKotlinFolder.path) {
android.sourceSets.main.java.srcDirs += oldDir
}
}
}
}
android {
compileSdkVersion "$android_compileSdkVersion".toInteger()
buildToolsVersion "$android_buildToolsVersion"
compileSdkVersion libs.versions.android.props.compileSdk.get().toInteger()
buildToolsVersion libs.versions.android.props.buildTools.get()
defaultConfig {
minSdkVersion "$android_minSdkVersion".toInteger()
targetSdkVersion "$android_compileSdkVersion".toInteger()
minSdkVersion libs.versions.android.props.minSdk.get().toInteger()
targetSdkVersion libs.versions.android.props.compileSdk.get().toInteger()
versionCode "${android_code_version}".toInteger()
versionName "$version"
}
@@ -58,10 +33,4 @@ android {
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
sourceSets {
String sep = File.separator
main.java.srcDirs += "src${sep}main${sep}kotlin"
enableIncludingJvmCodeInAndroidPart()
}
}

View File

@@ -13,10 +13,10 @@ repositories {
kotlin {
jvm()
js(IR) {
browser()
nodejs()
}
// js(IR) {
// browser()
// nodejs()
// }
android {}
sourceSets {
@@ -29,7 +29,7 @@ kotlin {
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("jsMain") }
&& it.kotlin.sourceSets.any { it.name.contains("jvmMain") }
&& it.kotlin.sourceSets.any { it.name.contains("androidMain") }
) {
@@ -38,22 +38,22 @@ kotlin {
}
}
}
jsMain {
dependencies {
implementation kotlin('stdlib')
// 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
}
}
}
}
// 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')
@@ -116,16 +116,16 @@ tasks.dokkaHtml {
sourceRoots.setFrom(findSourcesWithName("commonMain"))
}
named("jsMain") {
sourceRoots.setFrom(findSourcesWithName("jsMain", "commonMain"))
}
// named("jsMain") {
// sourceRoots.setFrom(findSourcesWithName("jsMain", "commonMain"))
// }
named("jvmMain") {
sourceRoots.setFrom(findSourcesWithName("jvmMain", "commonMain"))
sourceRoots.setFrom(findSourcesWithName("jvmMain"))
}
named("androidMain") {
sourceRoots.setFrom(findSourcesWithName("androidMain", "commonMain"))
sourceRoots.setFrom(findSourcesWithName("androidMain"))
}
}
}

View File

@@ -21,12 +21,14 @@ allprojects {
releaseMode = (project.hasProperty('RELEASE_MODE') && project.property('RELEASE_MODE') == "true") || System.getenv('RELEASE_MODE') == "true"
mppProjectWithSerializationPresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerialization.gradle"
mppProjectWithSerializationAndComposePresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerializationAndCompose.gradle"
mppJavaProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJavaProject.gradle"
mppJvmJsLinuxMingwProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJvmJsLinuxMingwProject.gradle"
mppAndroidProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppAndroidProject.gradle"
defaultAndroidSettingsPresetPath = "${rootProject.projectDir.absolutePath}/defaultAndroidSettings.gradle"
publishGradlePath = "${rootProject.projectDir.absolutePath}/publish.gradle"
publishMavenPath = "${rootProject.projectDir.absolutePath}/maven.publish.gradle"
publishJvmOnlyPath = "${rootProject.projectDir.absolutePath}/jvm.publish.gradle"
}
}

View File

@@ -10,6 +10,7 @@ kotlin {
sourceSets {
commonMain {
dependencies {
api project(":micro_utils.common")
api project(":micro_utils.coroutines")
}
}

View File

@@ -0,0 +1,68 @@
package dev.inmo.micro_utils.fsm.common
import kotlin.reflect.KClass
/**
* Define checkable holder which can be used to precheck that this handler may handle incoming [State]
*/
interface CheckableHandlerHolder<I : State, O : State> : StatesHandler<I, O> {
suspend fun checkHandleable(state: O): Boolean
}
/**
* Default realization of [StatesHandler]. It will incapsulate checking of [State] type in [checkHandleable] and class
* casting in [handleState]
*/
class CustomizableHandlerHolder<I : O, O : State>(
private val delegateTo: StatesHandler<I, O>,
private val filter: suspend (state: O) -> Boolean
) : CheckableHandlerHolder<I, O> {
/**
* Checks that [state] can be handled by [delegateTo]. Under the hood it will check exact equality of [state]
* [KClass] and use [KClass.isInstance] of [inputKlass] if [strict] == false
*/
override suspend fun checkHandleable(state: O) = filter(state)
/**
* Calls [delegateTo] method [StatesHandler.handleState] with [state] casted to [I]. Use [checkHandleable]
* to be sure that this [StatesHandlerHolder] will be able to handle [state]
*/
override suspend fun StatesMachine<in O>.handleState(state: I): O? {
return delegateTo.run { handleState(state) }
}
}
fun <I : O, O : State> CheckableHandlerHolder(
inputKlass: KClass<I>,
strict: Boolean = false,
delegateTo: StatesHandler<I, O>
) = CustomizableHandlerHolder(
StatesHandler<O, O> {
delegateTo.run { handleState(it as I) }
},
if (strict) {
{ it::class == inputKlass }
} else {
{ inputKlass.isInstance(it) }
}
)
inline fun <reified I : O, O : State> CheckableHandlerHolder(
strict: Boolean = false,
delegateTo: StatesHandler<I, O>
) = CheckableHandlerHolder(I::class, strict, delegateTo)
inline fun <reified I : O, O: State> StatesHandler<I, O>.holder(
strict: Boolean = true
) = CheckableHandlerHolder<I, O>(
I::class,
strict,
this
)
inline fun <I : O, O: State> StatesHandler<I, O>.holder(
noinline filter: suspend (state: State) -> Boolean
) = CustomizableHandlerHolder<O, O>(
{ this@holder.run { handleState(it as I) } },
filter
)

View File

@@ -1,15 +0,0 @@
package dev.inmo.micro_utils.fsm.common
import kotlin.reflect.KClass
class StateHandlerHolder<I : State>(
private val inputKlass: KClass<I>,
private val strict: Boolean = false,
private val delegateTo: StatesHandler<I>
) : StatesHandler<State> {
fun checkHandleable(state: State) = state::class == inputKlass || (!strict && inputKlass.isInstance(state))
override suspend fun StatesMachine.handleState(state: State): State? {
return delegateTo.run { handleState(state as I) }
}
}

View File

@@ -1,5 +1,12 @@
package dev.inmo.micro_utils.fsm.common
fun interface StatesHandler<I : State> {
suspend fun StatesMachine.handleState(state: I): State?
/**
* Default realization of states handler
*/
fun interface StatesHandler<I : State, O: State> {
/**
* Main handling of [state]. In case when this [state] leads to another [State] and [handleState] returns not null
* [State] it is assumed that chain is not completed.
*/
suspend fun StatesMachine<in O>.handleState(state: I): O?
}

View File

@@ -1,46 +1,146 @@
package dev.inmo.micro_utils.fsm.common
import dev.inmo.micro_utils.common.Optional
import dev.inmo.micro_utils.common.onPresented
import dev.inmo.micro_utils.coroutines.*
import dev.inmo.micro_utils.fsm.common.utils.StateHandlingErrorHandler
import dev.inmo.micro_utils.fsm.common.utils.defaultStateHandlingErrorHandler
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
private suspend fun <I : State> StatesMachine.launchStateHandling(
state: State,
handlers: List<StateHandlerHolder<out I>>
): State? {
return handlers.firstOrNull { it.checkHandleable(state) } ?.run {
handleState(state)
/**
* Default [StatesMachine] may [startChain] and use inside logic for handling [State]s. By default you may use
* [DefaultStatesMachine] or build it with [dev.inmo.micro_utils.fsm.common.dsl.buildFSM]. Implementers MUST NOT start
* handling until [start] method will be called
*/
interface StatesMachine<T : State> : StatesHandler<T, T> {
suspend fun launchStateHandling(
state: T,
handlers: List<CheckableHandlerHolder<in T, T>>,
onStateHandlingErrorHandler: StateHandlingErrorHandler<T>
): T? {
return runCatchingSafely {
handlers.firstOrNull { it.checkHandleable(state) } ?.run {
handleState(state)
}
}.getOrElse {
onStateHandlingErrorHandler(state, it)
}
}
suspend fun launchStateHandling(
state: T,
handlers: List<CheckableHandlerHolder<in T, T>>
): T? {
return launchStateHandling(state, handlers, defaultStateHandlingErrorHandler())
}
/**
* Starts handling of [State]s
*/
fun start(scope: CoroutineScope): Job
/**
* Start chain of [State]s witn [state]
*/
suspend fun startChain(state: T)
companion object {
/**
* Creates [DefaultStatesMachine]
*/
operator fun <T: State> invoke(
statesManager: StatesManager<T>,
handlers: List<CheckableHandlerHolder<in T, T>>,
onStateHandlingErrorHandler: StateHandlingErrorHandler<T> = defaultStateHandlingErrorHandler()
) = DefaultStatesMachine(statesManager, handlers, onStateHandlingErrorHandler)
}
}
class StatesMachine (
private val statesManager: StatesManager,
private val handlers: List<StateHandlerHolder<*>>
) : StatesHandler<State> {
override suspend fun StatesMachine.handleState(state: State): State? = launchStateHandling(state, handlers)
/**
* Default realization of [StatesMachine]. It uses [statesManager] for incapsulation of [State]s storing and contexts
* resolving, and uses [launchStateHandling] for [State] handling.
*
* This class suppose to be extended in case you wish some custom behaviour inside of [launchStateHandling], for example
*/
open class DefaultStatesMachine <T: State>(
protected val statesManager: StatesManager<T>,
protected val handlers: List<CheckableHandlerHolder<in T, T>>,
protected val onStateHandlingErrorHandler: StateHandlingErrorHandler<T> = defaultStateHandlingErrorHandler()
) : StatesMachine<T> {
/**
* Will call [launchStateHandling] for state handling
*/
override suspend fun StatesMachine<in T>.handleState(state: T): T? = launchStateHandling(state, handlers)
fun start(scope: CoroutineScope): Job = scope.launchSafelyWithoutExceptions {
val statePerformer: suspend (State) -> Unit = { state: State ->
val newState = launchStateHandling(state, handlers)
if (newState != null) {
statesManager.update(state, newState)
} else {
statesManager.endChain(state)
}
}
statesManager.onStartChain.subscribeSafelyWithoutExceptions(this) {
launch { statePerformer(it) }
}
statesManager.onChainStateUpdated.subscribeSafelyWithoutExceptions(this) {
launch { statePerformer(it.second) }
}
override suspend fun launchStateHandling(state: T, handlers: List<CheckableHandlerHolder<in T, T>>): T? {
return launchStateHandling(state, handlers, onStateHandlingErrorHandler)
}
statesManager.getActiveStates().forEach {
launch { statePerformer(it) }
/**
* This
*/
protected val statesJobs = mutableMapOf<T, Job>()
protected val statesJobsMutex = Mutex()
protected open suspend fun performUpdate(state: T) {
val newState = launchStateHandling(state, handlers)
if (newState == null) {
statesManager.endChain(state)
} else {
statesManager.update(state, newState)
}
}
suspend fun startChain(state: State) {
open suspend fun performStateUpdate(previousState: Optional<T>, actualState: T, scope: CoroutineScope) {
statesJobsMutex.withLock {
statesJobs[actualState] ?.cancel()
statesJobs[actualState] = scope.launch {
performUpdate(actualState)
}.also { job ->
job.invokeOnCompletion { _ ->
scope.launch {
statesJobsMutex.withLock {
if (statesJobs[actualState] == job) {
statesJobs.remove(actualState)
}
}
}
}
}
}
}
/**
* Launch handling of states. On [statesManager] [StatesManager.onStartChain],
* [statesManager] [StatesManager.onChainStateUpdated] will be called lambda with performing of state. If
* [launchStateHandling] will returns some [State] then [statesManager] [StatesManager.update] will be used, otherwise
* [StatesManager.endChain].
*/
override fun start(scope: CoroutineScope): Job = scope.launchSafelyWithoutExceptions {
(statesManager.getActiveStates().asFlow() + statesManager.onStartChain).subscribeSafelyWithoutExceptions(this) {
launch { performStateUpdate(Optional.absent(), it, scope.LinkedSupervisorScope()) }
}
statesManager.onChainStateUpdated.subscribeSafelyWithoutExceptions(this) {
launch { performStateUpdate(Optional.presented(it.first), it.second, scope.LinkedSupervisorScope()) }
}
statesManager.onEndChain.subscribeSafelyWithoutExceptions(this) { removedState ->
launch {
statesJobsMutex.withLock {
val stateInMap = statesJobs.keys.firstOrNull { stateInMap -> stateInMap == removedState }
if (stateInMap === removedState) {
statesJobs[stateInMap] ?.cancel()
}
}
}
}
}
/**
* Just calls [StatesManager.startChain] of [statesManager]
*/
override suspend fun startChain(state: T) {
statesManager.startChain(state)
}
}

View File

@@ -1,92 +1,30 @@
package dev.inmo.micro_utils.fsm.common
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.flow.Flow
interface StatesManager {
val onChainStateUpdated: Flow<Pair<State, State>>
val onStartChain: Flow<State>
val onEndChain: Flow<State>
interface StatesManager<T : State> {
val onChainStateUpdated: Flow<Pair<T, T>>
val onStartChain: Flow<T>
val onEndChain: Flow<T>
/**
* Must set current set using [State.context]
*/
suspend fun update(old: State, new: State)
suspend fun update(old: T, new: T)
/**
* Starts chain with [state] as first [State]. May returns false in case of [State.context] of [state] is already
* busy by the other [State]
*/
suspend fun startChain(state: State)
suspend fun startChain(state: T)
/**
* Ends chain with context from [state]. In case when [State.context] of [state] is absent, [state] should be just
* ignored
*/
suspend fun endChain(state: State)
suspend fun endChain(state: T)
suspend fun getActiveStates(): List<State>
suspend fun getActiveStates(): List<T>
}
/**
* @param onContextsConflictResolver Receive old [State], new one and the state currently placed on new [State.context]
* key. In case when this callback will returns true, the state placed on [State.context] of new will be replaced by
* new state by using [endChain] with that state
*/
class InMemoryStatesManager(
private val onContextsConflictResolver: suspend (old: State, new: State, currentNew: State) -> Boolean = { _, _, _ -> true }
) : StatesManager {
private val _onChainStateUpdated = MutableSharedFlow<Pair<State, State>>(0)
override val onChainStateUpdated: Flow<Pair<State, State>> = _onChainStateUpdated.asSharedFlow()
private val _onStartChain = MutableSharedFlow<State>(0)
override val onStartChain: Flow<State> = _onStartChain.asSharedFlow()
private val _onEndChain = MutableSharedFlow<State>(0)
override val onEndChain: Flow<State> = _onEndChain.asSharedFlow()
private val contextsToStates = mutableMapOf<Any, State>()
private val mapMutex = Mutex()
override suspend fun update(old: State, new: State) = mapMutex.withLock {
when {
contextsToStates[old.context] != old -> return@withLock
old.context == new.context || !contextsToStates.containsKey(new.context) -> {
contextsToStates[old.context] = new
_onChainStateUpdated.emit(old to new)
}
else -> {
val stateOnNewOneContext = contextsToStates.getValue(new.context)
if (onContextsConflictResolver(old, new, stateOnNewOneContext)) {
endChainWithoutLock(stateOnNewOneContext)
contextsToStates.remove(old.context)
contextsToStates[new.context] = new
_onChainStateUpdated.emit(old to new)
}
}
}
}
override suspend fun startChain(state: State) = mapMutex.withLock {
if (!contextsToStates.containsKey(state.context)) {
contextsToStates[state.context] = state
_onStartChain.emit(state)
}
}
private suspend fun endChainWithoutLock(state: State) {
if (contextsToStates[state.context] == state) {
contextsToStates.remove(state.context)
_onEndChain.emit(state)
}
}
override suspend fun endChain(state: State) {
mapMutex.withLock {
endChainWithoutLock(state)
}
}
override suspend fun getActiveStates(): List<State> = contextsToStates.values.toList()
}

View File

@@ -0,0 +1,74 @@
package dev.inmo.micro_utils.fsm.common
import dev.inmo.micro_utils.common.*
import dev.inmo.micro_utils.fsm.common.utils.StateHandlingErrorHandler
import dev.inmo.micro_utils.fsm.common.utils.defaultStateHandlingErrorHandler
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.withLock
/**
* This extender of [StatesMachine] interface declare one new function [updateChain]. Realizations of this interface
* must be able to perform update of chain in internal [StatesManager]
*/
interface UpdatableStatesMachine<T : State> : StatesMachine<T> {
/**
* Update chain with current state equal to [currentState] with [newState]. Behaviour of this update preforming
* in cases when [currentState] does not exist in [StatesManager] must be declared inside of realization of
* [StatesManager.update] function
*/
suspend fun updateChain(currentState: T, newState: T)
}
open class DefaultUpdatableStatesMachine<T : State>(
statesManager: StatesManager<T>,
handlers: List<CheckableHandlerHolder<in T, T>>,
onStateHandlingErrorHandler: StateHandlingErrorHandler<T> = defaultStateHandlingErrorHandler()
) : DefaultStatesMachine<T>(
statesManager,
handlers,
onStateHandlingErrorHandler
), UpdatableStatesMachine<T> {
protected val jobsStates = mutableMapOf<Job, T>()
/**
* Realization of this update will use the [Job] of [previousState] in [statesJobs] and [jobsStates] if
* [previousState] is [Optional.presented] and [shouldReplaceJob] has returned true for [previousState] and [actualState]. In
* other words, [Job] of [previousState] WILL NOT be replaced with the new one if they are "equal". Equality of
* states is solved in [shouldReplaceJob] and can be rewritten in subclasses
*/
override suspend fun performStateUpdate(previousState: Optional<T>, actualState: T, scope: CoroutineScope) {
statesJobsMutex.withLock {
if (shouldReplaceJob(previousState, actualState)) {
statesJobs[actualState] ?.cancel()
}
val job = previousState.mapOnPresented {
statesJobs.remove(it)
} ?.takeIf { it.isActive } ?: scope.launch {
performUpdate(actualState)
}.also { job ->
job.invokeOnCompletion { _ ->
scope.launch {
statesJobsMutex.withLock {
statesJobs.remove(
jobsStates[job] ?: return@withLock
)
jobsStates.remove(job)
}
}
}
}
jobsStates.remove(job)
statesJobs[actualState] = job
jobsStates[job] = actualState
}
}
/**
* Compare if [previous] potentially lead to the same behaviour with [new]
*/
protected open suspend fun shouldReplaceJob(previous: Optional<T>, new: T): Boolean = previous.dataOrNull() != new
override suspend fun updateChain(currentState: T, newState: T) {
statesManager.update(currentState, newState)
}
}

View File

@@ -1,35 +1,61 @@
package dev.inmo.micro_utils.fsm.common.dsl
import dev.inmo.micro_utils.fsm.common.*
import dev.inmo.micro_utils.fsm.common.managers.DefaultStatesManager
import dev.inmo.micro_utils.fsm.common.managers.InMemoryDefaultStatesManagerRepo
import kotlin.reflect.KClass
class FSMBuilder(
var statesManager: StatesManager = InMemoryStatesManager()
class FSMBuilder<T : State>(
var statesManager: StatesManager<T> = DefaultStatesManager(InMemoryDefaultStatesManagerRepo()),
val fsmBuilder: (statesManager: StatesManager<T>, states: List<CheckableHandlerHolder<T, T>>) -> StatesMachine<T> = { statesManager, states ->
StatesMachine(
statesManager,
states
)
},
var defaultStateHandler: StatesHandler<T, T>? = StatesHandler { null }
) {
private var states = mutableListOf<StateHandlerHolder<*>>()
private var states = mutableListOf<CheckableHandlerHolder<T, T>>()
fun <I : State> add(kClass: KClass<I>, handler: StatesHandler<I>) {
states.add(StateHandlerHolder(kClass, false, handler))
fun add(handler: CheckableHandlerHolder<T, T>) {
states.add(handler)
}
fun <I : State> addStrict(kClass: KClass<I>, handler: StatesHandler<I>) {
states.add(StateHandlerHolder(kClass, true, handler))
fun <I : T> add(kClass: KClass<I>, handler: StatesHandler<I, T>) {
add(CheckableHandlerHolder(kClass, false, handler))
}
fun build() = StatesMachine(
fun <I : T> add(filter: suspend (state: State) -> Boolean, handler: StatesHandler<I, T>) {
add(handler.holder(filter))
}
fun <I : T> addStrict(kClass: KClass<I>, handler: StatesHandler<I, T>) {
states.add(CheckableHandlerHolder(kClass, true, handler))
}
inline fun <reified I : T> onStateOrSubstate(handler: StatesHandler<I, T>) {
add(I::class, handler)
}
inline fun <reified I : T> strictlyOn(handler: StatesHandler<I, T>) {
addStrict(I::class, handler)
}
inline fun <reified I : T> doWhen(
noinline filter: suspend (state: State) -> Boolean,
handler: StatesHandler<I, T>
) {
add(filter, handler)
}
fun build() = fsmBuilder(
statesManager,
states.toList()
states.toList().let { list ->
defaultStateHandler ?.let { list + it.holder { true } } ?: list
}
)
}
inline fun <reified I : State> FSMBuilder.onStateOrSubstate(handler: StatesHandler<I>) {
add(I::class, handler)
}
inline fun <reified I : State> FSMBuilder.strictlyOn(handler: StatesHandler<I>) {
addStrict(I::class, handler)
}
fun buildFSM(
block: FSMBuilder.() -> Unit
): StatesMachine = FSMBuilder().apply(block).build()
fun <T : State> buildFSM(
block: FSMBuilder<T>.() -> Unit
): StatesMachine<T> = FSMBuilder<T>().apply(block).build()

View File

@@ -0,0 +1,118 @@
package dev.inmo.micro_utils.fsm.common.managers
import dev.inmo.micro_utils.fsm.common.State
import dev.inmo.micro_utils.fsm.common.StatesManager
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
/**
* Implement this repo if you want to use some custom repo for [DefaultStatesManager]
*/
interface DefaultStatesManagerRepo<T : State> {
/**
* Must save [state] as current state of chain with [State.context] of [state]
*/
suspend fun set(state: T)
/**
* Remove exactly [state]. In case if internally [State.context] is busy with different [State], that [State] should
* NOT be removed
*/
suspend fun removeState(state: T)
/**
* @return Current list of available and saved states
*/
suspend fun getStates(): List<T>
/**
* @return Current state by [context]
*/
suspend fun getContextState(context: Any): T?
/**
* @return Current state by [context]
*/
suspend fun contains(context: Any): Boolean = getContextState(context) != null
}
/**
* @param repo This repo will be used as repository for storing states. All operations with this repo will happen BEFORE
* any event will be sent to [onChainStateUpdated], [onStartChain] or [onEndChain]. By default, will be used
* [InMemoryDefaultStatesManagerRepo] or you may create custom [DefaultStatesManagerRepo] and pass as [repo] parameter
* @param onStartContextsConflictResolver Receive current [State] and the state passed with [startChain]. In case when
* this callback will return true, currently placed on the [State.context] [State] will be replaced by new state
* with [endChain] with current state
* @param onUpdateContextsConflictResolver Receive old [State], new one and the state currently placed on new [State.context]
* key. In case when this callback will returns true, the state placed on [State.context] of new will be replaced by
* new state by using [endChain] with that state
*/
open class DefaultStatesManager<T : State>(
protected val repo: DefaultStatesManagerRepo<T> = InMemoryDefaultStatesManagerRepo(),
protected val onStartContextsConflictResolver: suspend (current: T, new: T) -> Boolean = { _, _ -> false },
protected val onUpdateContextsConflictResolver: suspend (old: T, new: T, currentNew: T) -> Boolean = { _, _, _ -> false }
) : StatesManager<T> {
protected val _onChainStateUpdated = MutableSharedFlow<Pair<T, T>>(0)
override val onChainStateUpdated: Flow<Pair<T, T>> = _onChainStateUpdated.asSharedFlow()
protected val _onStartChain = MutableSharedFlow<T>(0)
override val onStartChain: Flow<T> = _onStartChain.asSharedFlow()
protected val _onEndChain = MutableSharedFlow<T>(0)
override val onEndChain: Flow<T> = _onEndChain.asSharedFlow()
protected val mapMutex = Mutex()
constructor(
repo: DefaultStatesManagerRepo<T>,
onContextsConflictResolver: suspend (old: T, new: T, currentNew: T) -> Boolean
) : this (
repo,
onUpdateContextsConflictResolver = onContextsConflictResolver
)
override suspend fun update(old: T, new: T) = mapMutex.withLock {
val stateByOldContext: T? = repo.getContextState(old.context)
when {
stateByOldContext != old -> return@withLock
stateByOldContext == null || old.context == new.context -> {
repo.removeState(old)
repo.set(new)
_onChainStateUpdated.emit(old to new)
}
else -> {
val stateOnNewOneContext = repo.getContextState(new.context)
if (stateOnNewOneContext == null || onUpdateContextsConflictResolver(old, new, stateOnNewOneContext)) {
stateOnNewOneContext ?.let { endChainWithoutLock(it) }
repo.removeState(old)
repo.set(new)
_onChainStateUpdated.emit(old to new)
}
}
}
}
override suspend fun startChain(state: T) = mapMutex.withLock {
val stateOnContext = repo.getContextState(state.context)
if (stateOnContext == null || onStartContextsConflictResolver(stateOnContext, state)) {
stateOnContext ?.let {
endChainWithoutLock(it)
}
repo.set(state)
_onStartChain.emit(state)
}
}
protected open suspend fun endChainWithoutLock(state: T) {
if (repo.getContextState(state.context) == state) {
repo.removeState(state)
_onEndChain.emit(state)
}
}
override suspend fun endChain(state: T) {
mapMutex.withLock {
endChainWithoutLock(state)
}
}
override suspend fun getActiveStates(): List<T> = repo.getStates()
}

View File

@@ -0,0 +1,25 @@
package dev.inmo.micro_utils.fsm.common.managers
import dev.inmo.micro_utils.fsm.common.State
/**
* Simple [DefaultStatesManagerRepo] for [DefaultStatesManager] which will store data in [map] and use primitive
* functionality
*/
class InMemoryDefaultStatesManagerRepo<T : State>(
private val map: MutableMap<Any, T> = mutableMapOf()
) : DefaultStatesManagerRepo<T> {
override suspend fun set(state: T) {
map[state.context] = state
}
override suspend fun removeState(state: T) {
map.remove(state.context)
}
override suspend fun getStates(): List<T> = map.values.toList()
override suspend fun getContextState(context: Any): T? = map[context]
override suspend fun contains(context: Any): Boolean = map.contains(context)
}

View File

@@ -0,0 +1,6 @@
package dev.inmo.micro_utils.fsm.common.utils
typealias StateHandlingErrorHandler<T> = suspend (T, Throwable) -> T?
val DefaultStateHandlingErrorHandler: StateHandlingErrorHandler<*> = { _, _ -> null }
inline fun <T> defaultStateHandlingErrorHandler(): StateHandlingErrorHandler<T> = DefaultStateHandlingErrorHandler as StateHandlingErrorHandler<T>

View File

@@ -1,6 +1,6 @@
import dev.inmo.micro_utils.fsm.common.*
import dev.inmo.micro_utils.fsm.common.dsl.buildFSM
import dev.inmo.micro_utils.fsm.common.dsl.strictlyOn
import dev.inmo.micro_utils.fsm.common.managers.DefaultStatesManager
import kotlinx.coroutines.*
sealed interface TrafficLightState : State {
@@ -25,9 +25,9 @@ class PlayableMain {
}
}
val statesManager = InMemoryStatesManager()
val statesManager = DefaultStatesManager<TrafficLightState>()
val machine = buildFSM {
val machine = buildFSM<TrafficLightState> {
strictlyOn<GreenCommon> {
delay(1000L)
YellowCommon(it.context).also(::println)

View File

@@ -0,0 +1,25 @@
package dev.inmo.micro_utils.fsm.repos.common
import dev.inmo.micro_utils.fsm.common.State
import dev.inmo.micro_utils.fsm.common.managers.DefaultStatesManagerRepo
import dev.inmo.micro_utils.repos.*
import dev.inmo.micro_utils.repos.pagination.getAll
class KeyValueBasedDefaultStatesManagerRepo<T : State>(
private val keyValueRepo: KeyValueRepo<Any, T>
) : DefaultStatesManagerRepo<T> {
override suspend fun set(state: T) {
keyValueRepo.set(state.context, state)
}
override suspend fun removeState(state: T) {
if (keyValueRepo.get(state.context) == state) {
keyValueRepo.unset(state.context)
}
}
override suspend fun getStates(): List<T> = keyValueRepo.getAll { keys(it) }.map { it.second }
override suspend fun getContextState(context: Any): T? = keyValueRepo.get(context)
override suspend fun contains(context: Any): Boolean = keyValueRepo.contains(context)
}

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