Compare commits

..

253 Commits

Author SHA1 Message Date
4a454f3d67 add opportunity to use translation with default locale 2023-12-12 23:45:22 +06:00
151aa1863d update gradle wrapper 2023-12-12 21:41:53 +06:00
3bf6896296 get back translations with android and java resources packages 2023-12-12 21:41:09 +06:00
0d01561476 optimizations and improvements in resources 2023-12-12 21:39:58 +06:00
f6ded92251 init resources module 2023-12-12 20:58:08 +06:00
d01b735cc6 start 0.20.21 2023-12-12 20:21:37 +06:00
6c12001080 Merge pull request #358 from InsanusMokrassar/0.20.20
0.20.20
2023-12-12 00:12:34 +06:00
1afbf03606 improvements in AbstractExposedCRUDRepo 2023-12-12 00:01:58 +06:00
f6ef5c61c5 start 0.20.20 2023-12-11 23:54:25 +06:00
c18fee8107 update gradle publishing scripts
one more potential ix of publishing scripts

drop new configs of publish scripts

revert publish scripts and update gradle properties
2023-12-09 20:38:44 +06:00
d9df7a4384 Revert gradle wrapper version 2023-12-09 01:38:45 +06:00
87c2230e8e revert xmx 2500 2023-12-08 23:42:26 +06:00
da7eb6de0a change xmx 2023-12-08 21:34:05 +06:00
ed3815118f update jvmargs of gradle 2023-12-08 19:34:47 +06:00
be726f42bd Merge pull request #356 from InsanusMokrassar/0.20.19
update publishing scripts
2023-12-08 16:06:22 +06:00
a91006132f update publishing scripts 2023-12-08 16:06:07 +06:00
9a9f741a0b Merge pull request #355 from InsanusMokrassar/0.20.19
0.20.19
2023-12-08 16:03:05 +06:00
5028f130e9 update dependencies 2023-12-08 15:58:16 +06:00
77fa019651 start 0.20.19 2023-12-08 15:56:44 +06:00
9715da9384 Merge pull request #353 from InsanusMokrassar/0.20.18
0.20.18
2023-12-04 16:13:33 +06:00
f6d5035c1a small refactor in SpecialMutableStateFlow 2023-12-04 15:44:06 +06:00
43e782ab6f SpecialMutableStateFlow : MutableStateFlow 2023-12-04 15:37:02 +06:00
f3f9920bfb deprecate FlowState 2023-12-04 15:08:52 +06:00
2bfd615812 start 0.20.18 2023-12-04 15:01:42 +06:00
ebfacb3659 Merge pull request #352 from InsanusMokrassar/0.20.17
0.20.17
2023-12-01 02:51:00 +06:00
c71d557eec update dependencies 2023-12-01 02:46:15 +06:00
e0398cef21 start 0.10.17 2023-12-01 02:44:44 +06:00
f91599e9c6 Merge pull request #348 from InsanusMokrassar/0.20.16
0.20.16
2023-11-30 01:54:31 +06:00
f8f9f93c97 update changelog, rename SpecialMutableStateFlow file and update gradle wrapper 2023-11-30 01:54:04 +06:00
a8a5340d8b kdocs 2023-11-30 01:27:14 +06:00
871b27f37d initialization fixes 2023-11-30 01:22:43 +06:00
6f174cae1d fixes 2023-11-30 01:05:01 +06:00
22d7ac3e22 FlowState 2023-11-29 23:54:27 +06:00
9b30c3a155 small matrix improvements 2023-11-29 20:33:06 +06:00
915bac64b1 small fixes in SpecialStateFlow 2023-11-29 20:26:23 +06:00
9d2b50e55d SpecialStateFlow 2023-11-29 17:13:21 +06:00
bde100f63d start 0.20.16 2023-11-29 17:12:37 +06:00
05b035a13d Merge pull request #345 from InsanusMokrassar/0.20.15
0.20.15
2023-11-24 23:47:02 +06:00
eefb56bed7 Update libs.versions.toml 2023-11-24 20:14:32 +06:00
fcc0dc4189 update dependencies 2023-11-24 16:58:50 +06:00
47ff20317f start 0.20.15 2023-11-24 16:46:22 +06:00
1558b9103d Merge pull request #343 from InsanusMokrassar/0.20.14
0.20.14
2023-11-18 23:27:06 +06:00
7a78742162 update kslog 2023-11-18 18:11:07 +06:00
c01e240f66 update dependencies 2023-11-18 15:40:12 +06:00
fef4fcbac6 start 0.20.14 2023-11-18 15:37:19 +06:00
5ab18bce4b Merge pull request #335 from InsanusMokrassar/0.20.13
0.20.13
2023-11-11 16:37:11 +06:00
24aec7271a update dependencies and changelog 2023-11-11 16:30:59 +06:00
9b19a2cb95 Update libs.versions.toml 2023-11-10 04:29:38 +06:00
efdd7b8a57 Update libs.versions.toml 2023-11-10 04:28:54 +06:00
6df4cc9c3b start 0.20.13 2023-11-06 21:12:28 +06:00
b9d93db0f5 Merge pull request #334 from InsanusMokrassar/0.20.12
0.20.12
2023-11-02 19:16:56 +06:00
d7ee45ca64 update github workflows 2023-11-02 19:01:00 +06:00
d7c31b1b22 update and fix build 2023-11-02 19:00:35 +06:00
7d6794a358 start 0.20.12 2023-11-02 18:26:15 +06:00
473eb87346 Merge pull request #330 from InsanusMokrassar/0.20.11
0.20.11
2023-11-01 12:38:44 +06:00
8b18b07790 Update CHANGELOG.md 2023-11-01 12:37:30 +06:00
ab3c80a5ec update dependencies 2023-10-31 17:53:56 +06:00
075b93ecd6 SmartRWLocker now will wait first unlock of write mutex for acquiring read 2023-10-26 12:28:24 +06:00
f6d0f72e49 start 0.20.11 2023-10-26 12:28:04 +06:00
fcda3af862 Merge pull request #329 from InsanusMokrassar/0.20.10
0.20.10
2023-10-26 10:51:45 +06:00
d5c7a589b1 update dependencies 2023-10-26 10:48:36 +06:00
86e099ed25 start 0.20.10 2023-10-26 10:37:46 +06:00
ebd7befe73 Merge pull request #324 from InsanusMokrassar/0.20.9
0.20.9
2023-10-24 16:25:05 +06:00
b8c7e581a1 add support of linuxArm64 target 2023-10-20 21:53:27 +06:00
8281259179 start 0.20.9 2023-10-17 23:34:53 +06:00
d3e06b07df Update libs.versions.toml 2023-10-14 22:53:02 +06:00
4967018418 Merge pull request #322 from InsanusMokrassar/0.20.8
0.20.8
2023-10-14 21:01:45 +06:00
537a3c38fa update dependencies 2023-10-14 16:22:00 +06:00
0124957833 start 0.20.8 2023-10-14 16:08:51 +06:00
f0420e2d61 Merge pull request #312 from InsanusMokrassar/0.20.7
0.20.7
2023-10-09 18:16:58 +06:00
7090566041 fill changelog 2023-10-09 13:58:17 +06:00
f0d5035cd0 update dependencies 2023-10-09 13:53:44 +06:00
0fb9b8dc30 update compose version 2023-09-27 17:59:09 +06:00
eef6e81134 Update libs.versions.toml 2023-09-26 01:28:36 +06:00
1593159a3f Update libs.versions.toml 2023-09-26 01:27:20 +06:00
3da9eb9dbe temporal update 2023-09-22 14:14:23 +06:00
f17613f3fb update dependencies 2023-09-12 14:11:12 +06:00
14337ccb46 start 0.20.7 2023-09-10 17:09:08 +06:00
1a3913b09c Merge pull request #311 from InsanusMokrassar/0.20.6
0.20.6
2023-09-08 04:20:58 +06:00
039aed2747 improvements in ExposedReadKeyValuesRepo 2023-09-08 02:59:53 +06:00
173991e3cb start 0.20.6 2023-09-08 02:55:25 +06:00
8b3f8cab01 Merge pull request #310 from InsanusMokrassar/0.20.5
0.20.5
2023-09-07 20:09:58 +06:00
2a20d24589 fixes in SmartRWLocker 2023-09-07 16:57:59 +06:00
53c2d552ec start 0.20.5 2023-09-07 16:57:08 +06:00
af11c1a83d Merge pull request #307 from InsanusMokrassar/0.20.4
0.20.4
2023-09-06 19:17:17 +06:00
a65cf1481c fixes in build 2023-09-06 19:15:09 +06:00
0318716236 update kslog version 2023-09-06 19:08:32 +06:00
88eb4b3342 update dependencies 2023-09-06 18:58:10 +06:00
4810d1ef6a start 0.20.4 2023-09-06 18:55:48 +06:00
f2a9514d89 Merge pull request #306 from InsanusMokrassar/renovate/jb.dokka
Update dependency org.jetbrains.dokka:dokka-gradle-plugin to v1.9.0
2023-09-02 04:15:10 +06:00
925ba6ac24 Update build.gradle 2023-09-02 04:04:04 +06:00
renovate[bot]
ef407268a2 Update dependency org.jetbrains.dokka:dokka-gradle-plugin to v1.9.0 2023-09-01 21:04:25 +00:00
cd6c4bbe38 Merge pull request #304 from InsanusMokrassar/0.20.3
0.20.3
2023-09-02 03:03:57 +06:00
c058e18408 update smart rw locker tests 2023-09-01 20:11:04 +06:00
6d3ca565ca update ktor 2023-09-01 19:41:49 +06:00
236a7b4fd2 Update libs.versions.toml 2023-09-01 04:33:36 +06:00
e1f387dbf7 update dependencies and make a lot of fixes in repos and smart lockers/semaphors 2023-08-31 01:37:35 +06:00
3d113dd31e fixes in locks of caches repos 2023-08-29 15:06:55 +06:00
e0e57f0336 start 0.20.3 2023-08-29 14:59:57 +06:00
e775b58d41 Merge pull request #298 from InsanusMokrassar/0.20.2
0.20.2
2023-08-24 20:10:06 +06:00
5b070a8478 add small kdoc for map repos 2023-08-24 17:15:29 +06:00
8a61193500 update korlibs 2023-08-24 14:29:15 +06:00
fad522b8fe Update CHANGELOG.md 2023-08-24 03:00:08 +06:00
0acac205af fix build 2023-08-24 02:17:27 +06:00
069d4c61b7 update android target 2023-08-23 14:52:09 +06:00
7c113f5700 Update libs.versions.toml 2023-08-23 13:27:23 +06:00
dfdaf4225b start 0.20.2 and all main repos uses 'SmartRWLocker' 2023-08-23 03:26:54 +06:00
bd39ab2467 Merge pull request #295 from InsanusMokrassar/0.20.1
0.20.1
2023-08-12 22:47:03 +06:00
6ce1eb3f2d add tests for smartrwlocker 2023-08-12 22:37:35 +06:00
ce7d4fe9a2 fix of #293 2023-08-12 22:08:12 +06:00
a65bb2f419 add number picker, set picker and small text field 2023-08-11 01:51:40 +06:00
cbc868448b start 0.20.1 2023-08-11 00:58:09 +06:00
9c336a0b56 Update CHANGELOG.md 2023-08-09 02:43:53 +06:00
0f0d09399e Update CHANGELOG.md 2023-08-09 02:42:28 +06:00
e13a1162a9 Merge pull request #294 from InsanusMokrassar/0.20.0
0.20.0
2023-08-09 00:32:03 +06:00
57ebed903f done migration 2023-08-08 18:45:36 +06:00
4478193d8a update dependencies 2023-08-05 13:49:45 +06:00
ee948395e3 start 0.20.0 2023-08-02 13:03:40 +06:00
0616b051ae Merge pull request #291 from InsanusMokrassar/0.19.9
0.19.9
2023-07-29 17:37:38 +06:00
4d155d0505 update koin 2023-07-29 16:37:25 +06:00
a169e733d9 fill changelog 2023-07-29 16:31:50 +06:00
f081e237c8 improvements in startup plugin 2023-07-29 16:30:17 +06:00
f412d387fa start 0.19.9 2023-07-29 16:05:24 +06:00
67354b43e2 Merge pull request #287 from InsanusMokrassar/0.19.8
0.19.8
2023-07-27 01:33:49 +06:00
39135a4000 skipStartInvalidate 2023-07-27 00:35:01 +06:00
eaa014cebd update dependencies 2023-07-27 00:30:26 +06:00
856e657f81 fixes in KeyValueRepo.clear 2023-07-23 13:47:20 +06:00
3a609e5b66 start 0.19.8 2023-07-23 13:26:39 +06:00
d0022dd599 Merge pull request #281 from InsanusMokrassar/0.19.7
0.19.7
2023-06-30 14:37:36 +06:00
7ba6eed453 Update CHANGELOG.md 2023-06-30 12:04:42 +06:00
beeb6ecc0a Update CHANGELOG.md 2023-06-30 12:03:17 +06:00
7cdc17a714 Update libs.versions.toml 2023-06-30 12:02:20 +06:00
4765a950a9 start 0.19.7 2023-06-30 12:01:21 +06:00
65e8137e08 Merge pull request #279 from InsanusMokrassar/0.19.6
0.19.6
2023-06-29 20:27:19 +06:00
ae546dd9ad update dependencies 2023-06-29 18:38:58 +06:00
8110c42be0 update compose 2023-06-27 16:53:10 +06:00
bd2b5ae5fc add versions plugin 2023-06-27 16:46:45 +06:00
2ddfffa6a9 start 0.19.6 2023-06-27 16:46:45 +06:00
a4b54e861d Merge pull request #277 from InsanusMokrassar/0.19.5
0.19.5
2023-06-20 20:22:33 +06:00
c6785f1a4f fixes in types generation 2023-06-20 20:20:47 +06:00
83fe621c56 start 0.19.5 2023-06-20 20:16:24 +06:00
b3a93e17eb Merge pull request #275 from InsanusMokrassar/0.19.4
0.19.4
2023-06-19 16:25:43 +06:00
546a391af3 dependencies update 2023-06-19 16:19:14 +06:00
786cf9bd8b fix in koin generator 2023-06-19 13:52:41 +06:00
dfd6fe062d start 0.19.4 2023-06-19 13:45:29 +06:00
b6ef818613 Merge pull request #274 from InsanusMokrassar/0.19.3
0.19.3
2023-06-19 08:45:45 +06:00
b0f9e9c30a generic-oriented koin definitions 2023-06-19 00:32:45 +06:00
7e5c88ddc3 koin generator improvements 2023-06-18 23:38:36 +06:00
9824c3e00f start 0.19.3 2023-06-18 23:11:50 +06:00
9171d5ed11 update dokka version and return js kdocs 2023-06-09 12:01:11 +06:00
a1830ebb82 Merge pull request #269 from InsanusMokrassar/0.19.2
0.19.2
2023-06-02 13:02:38 +06:00
22d2a3d9bf Update CHANGELOG.md 2023-06-02 12:47:45 +06:00
c9b97fc965 Update libs.versions.toml 2023-06-02 12:46:56 +06:00
51f85becd5 Merge pull request #267 from InsanusMokrassar/0.19.2
0.19.2
2023-06-02 12:39:27 +06:00
a8a281cfb4 downgrade kotlin-poet version 2023-06-02 12:19:02 +06:00
ddd1304949 update dependencies 2023-06-02 12:17:21 +06:00
86d70b6c02 start 0.19.2 2023-06-02 12:11:49 +06:00
a22bdb39e7 Merge pull request #261 from InsanusMokrassar/0.19.1
update kotlin poet (0.19.1)
2023-05-29 17:35:29 +06:00
7ae4d5ef95 update kotlin poet 2023-05-29 17:31:40 +06:00
a2038cbefa Merge pull request #259 from InsanusMokrassar/0.19.1
0.19.1
2023-05-29 17:28:19 +06:00
992091eade Update CHANGELOG.md 2023-05-29 17:27:57 +06:00
e3bfead0c5 Update libs.versions.toml 2023-05-29 10:15:19 +06:00
0de96141fd start 0.19.1 2023-05-29 10:14:35 +06:00
fa18d15c3c Merge pull request #257 from InsanusMokrassar/0.19.0
0.19.0
2023-05-26 20:17:30 +06:00
ea9dbf2371 Koin.get and Scope.get with opportunity to get dependency by its type and definition 2023-05-25 21:12:21 +06:00
d34e3ec7a9 update dependencies 2023-05-25 21:08:12 +06:00
c8833a36af start 0.19.0 2023-05-25 20:58:05 +06:00
a067cb0c0f Merge pull request #256 from InsanusMokrassar/0.18.4
0.18.4
2023-05-14 16:51:57 +06:00
999c8327bd lazyInject 2023-05-14 16:36:24 +06:00
c2ec73c70a start 0.18.4 2023-05-14 16:28:03 +06:00
702f782fc1 Merge pull request #254 from InsanusMokrassar/0.18.3
0.18.3
2023-05-12 14:30:37 +06:00
25dbcaaf83 update dependencies 2023-05-12 00:51:38 +06:00
b00d454a24 start 0.18.3 2023-05-12 00:26:16 +06:00
dbc921d56d Merge pull request #250 from InsanusMokrassar/0.18.2
0.18.2
2023-05-10 10:19:47 +06:00
438fefa7a3 fixes 2023-05-09 15:09:50 +06:00
5d74eac814 Update libs.versions.toml 2023-05-09 01:41:25 +06:00
9fb62e1e25 start 0.18.2 and make json of Startup customizable 2023-05-09 01:38:04 +06:00
3e366ea73b Update libs.versions.toml 2023-05-06 01:54:15 +06:00
0ff895bffa Update gradle.properties 2023-05-06 01:53:33 +06:00
c5bb120280 Merge pull request #248 from InsanusMokrassar/0.18.1
0.18.1
2023-05-05 22:26:53 +06:00
4b26a92b37 improve old diff API 2023-05-05 12:01:29 +06:00
0a8453b4d2 add MapDiff#reversed and MutableMap#applyDiff(MapDiff) 2023-05-05 11:58:27 +06:00
c9872a61b6 now applyDiff will return its receiver 2023-05-05 11:50:23 +06:00
149a1aa278 fix in new applyDiff 2023-05-05 11:33:22 +06:00
13d0e1b682 add MapDiff utils 2023-05-04 21:12:26 +06:00
6f9be2a9f8 start 0.18.1 and add SmartMutex 2023-05-03 23:47:05 +06:00
93ba98d993 Merge pull request #246 from InsanusMokrassar/revert-245-renovate/ksp
Revert "Update ksp to v1.8.21-1.0.11"
2023-04-28 00:34:20 +06:00
940ee3df06 Revert "Update ksp to v1.8.21-1.0.11" 2023-04-28 00:34:10 +06:00
2e7bab10fd Merge pull request #245 from InsanusMokrassar/renovate/ksp
Update ksp to v1.8.21-1.0.11
2023-04-28 00:14:25 +06:00
renovate[bot]
3ed70a37ea Update ksp to v1.8.21-1.0.11 2023-04-27 18:10:41 +00:00
fe8f80b9d9 Merge pull request #243 from InsanusMokrassar/0.18.0
0.18.0
2023-04-27 17:08:42 +06:00
d81fb32fb9 remove previous deprecations 2023-04-27 16:29:08 +06:00
2877b5532c Revert "Get back some functional of "trying to update up to gradle wrapper 8.1.1""
This reverts commit b938b21395.
2023-04-27 16:24:58 +06:00
b938b21395 Get back some functional of "trying to update up to gradle wrapper 8.1.1" 2023-04-27 16:24:37 +06:00
58836359cc Revert "trying to update up to gradle wrapper 8.1.1"
This reverts commit 5edb0e1331.
2023-04-27 15:00:43 +06:00
5edb0e1331 trying to update up to gradle wrapper 8.1.1 2023-04-27 14:15:45 +06:00
0f0d0b5d58 update android fragments dependency 2023-04-27 12:16:48 +06:00
46c1887cbe remove usage of cache updates flows in autorecached repos 2023-04-27 12:06:50 +06:00
5f231c2212 ReadKeyValuesRepo#removeWithValue 2023-04-25 19:32:43 +06:00
4e97ce86aa add defauult value to kvCache in fullyCached 2023-04-25 17:39:14 +06:00
315a7cb29e Rename full caching factories functions to fullyCached 2023-04-25 17:23:47 +06:00
aa7cc503f2 improvements in query parameters getting 2023-04-25 13:14:12 +06:00
4bbe7e5a80 update build tools version 2023-04-25 12:23:53 +06:00
d9c05f38d2 start 0.18.0 2023-04-25 12:23:08 +06:00
cd0c4c9650 Merge pull request #237 from InsanusMokrassar/renovate/android-gradle
Update dependency com.android.tools.build:gradle to v7.4.2
2023-04-25 00:38:47 +06:00
fc3407f104 Merge pull request #241 from InsanusMokrassar/0.17.8
0.17.8
2023-04-19 20:16:36 +06:00
3a5544206b update ktor 2023-04-19 19:46:39 +06:00
e17e2f7fb8 start 0.17.8 2023-04-19 19:45:58 +06:00
renovate[bot]
d32c95f143 Update dependency com.android.tools.build:gradle to v7.4.2 2023-04-18 18:12:52 +00: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
297 changed files with 5894 additions and 1511 deletions

View File

@@ -8,7 +8,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-java@v1
with:
java-version: 11
java-version: 17
- name: Rewrite version
run: |
branch="`echo "${{ github.ref }}" | grep -o "[^/]*$"`"

View File

@@ -10,7 +10,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-java@v1
with:
java-version: 11
java-version: 17
- name: Build
run: ./gradlew build && ./gradlew dokkaHtml
- name: Publish KDocs

View File

@@ -1,5 +1,336 @@
# Changelog
## 0.20.21
* `Resources`:
* Inited
## 0.20.20
* `Repos`:
* `Exposed`:
* Add opportunity for setup flows in `AbstractExposedCRUDRepo`
## 0.20.19
* `Versions`:
* `Ktor`: `2.3.6` -> `2.3.7`
## 0.20.18
* `Coroutines`:
* `SpecialMutableStateFlow` now extends `MutableStateFlow`
* `Compose`:
* Deprecate `FlowState` due to its complexity in fixes
## 0.20.17
* `Versions`:
* `Serialization`: `1.6.1` -> `1.6.2`
## 0.20.16
* `Versions`:
* `Exposed`: `0.44.1` -> `0.45.0`
* `Coroutines`:
* Add `SpecialMutableStateFlow`
* `Compose`:
* Add `FlowState`
## 0.20.15
* `Versions`:
* `Kotlin`: `1.9.20` -> `1.9.21`
* `KSLog`: `1.3.0` -> `1.3.1`
* `Compose`: `1.5.10` -> `1.5.11`
## 0.20.14
* `Versions`:
* `Serialization`: `1.6.0` -> `1.6.1`
* `KSLog`: `1.2.4` -> `1.3.0`
## 0.20.13
* `Versions`:
* `Ktor`: `2.3.5` -> `2.3.6`
* `UUID`: `0.8.1` -> `0.8.2`
## 0.20.12
**It is experimental migration onto new gradle version. Be careful in use of this version**
**This update have JDK 17 in `compatibility` and `target` versions**
## 0.20.11
* `Versions`:
* `Kotlin`: `1.9.20-RC2` -> `1.9.20`
* `Exposed`: `0.44.0` -> `0.44.1`
* `Compose`: `1.5.10-rc02` -> `1.5.10`
* `Coroutines`:
* `SmartRWLocker` now will wait first unlock of write mutex for acquiring read
## 0.20.10
* `Versions`:
* `Kotlin`: `1.9.20-RC` -> `1.9.20-RC1`
* `KSLog`: `1.2.1` -> `1.2.2`
* `Compose`: `1.5.10-rc01` -> `1.5.10-rc02`
* `RecyclerView`: `1.3.1` -> `1.3.2`
## 0.20.9
* Most of common modules now supports `linuxArm64` target
## 0.20.8
**THIS VERSION CONTAINS UPDATES OF DEPENDENCIES UP TO RC VERSIONS. USE WITH CAUTION**
* `Versions`:
* `Kotlin`: `1.9.20-Beta2` -> `1.9.20-RC`
* `Compose`: `1.5.10-beta02` -> `1.5.10-rc01`
## 0.20.7
**THIS VERSION CONTAINS UPDATES OF DEPENDENCIES UP TO BETA VERSIONS. USE WITH CAUTION**
* `Versions`:
* `Kotlin`: `1.9.10` -> `1.9.20-Beta2`
* `Compose`: `1.5.1` -> `1.5.10-beta02`
* `Exposed`: `0.43.0` -> `0.44.0`
* `Ktor`: `2.3.4` -> `2.3.5`
* `Koin`: `3.4.3` -> `3.5.0`
* `Okio`: `3.5.0` -> `3.6.0`
* `Android Core`: `1.10.1` -> `1.12.0`
* `Android Compose Material`: `1.1.1` -> `1.1.2`
## 0.20.6
* `Repos`:
* `Exposed`
* Fixes in exposed key-values repos
## 0.20.5
* `Coroutines`:
* Fixes in `SmartRWLocker`
## 0.20.4
* `Versions`:
* `Kotlin`: `1.9.0` -> `1.9.10`
* `KSLog`: `1.2.0` -> `1.2.1`
* `Compose`: `1.5.0` -> `1.5.1`
* `UUID`: `0.8.0` -> `0.8.1`
## 0.20.3
* `Versions`:
* `Compose`: `1.4.3` -> `1.5.0`
* `Exposed`: `0.42.1` -> `0.43.0`
* `Ktor`: `2.3.3` -> `2.3.4`
* `Repos`:
* `Cache`:
* Fixes in locks of caches
## 0.20.2
* All main repos uses `SmartRWLocker`
* `Versions`:
* `Serialization`: `1.5.1` -> `1.6.0`
* `Exposed`: `0.42.0` -> `0.42.1`
* `Korlibs`: `4.0.9` -> `4.0.10`
* `Androis SDK`: `33` -> `34`
## 0.20.1
* `SmallTextField`:
* Module is initialized
* `Pickers`:
* Module is initialized
* `Coroutines`:
* Add `SmartSemaphore`
* Add `SmartRWLocker`
## 0.20.0
* `Versions`:
* `Kotlin`: `1.8.22` -> `1.9.0`
* `KSLog`: `1.1.1` -> `1.2.0`
* `Exposed`: `0.41.1` -> `0.42.0`
* `UUID`: `0.7.1` -> `0.8.0`
* `Korlibs`: `4.0.3` -> `4.0.9`
* `Ktor`: `2.3.2` -> `2.3.3`
* `Okio`: `3.4.0` -> `3.5.0`
## 0.19.9
* `Versions`:
* `Koin`: `3.4.2` -> `3.4.3`
* `Startup`:
* Now it is possible to start application in synchronous way
## 0.19.8
* `Versions`:
* `Coroutines`: `1.7.2` -> `1.7.3`
* `Kotlin`: `1.8.20` -> `1.8.22`
* `Compose`: `1.4.1` -> `1.4.3`
* `Okio`: `3.3.0` -> `3.4.0`
* `RecyclerView`: `1.3.0` -> `1.3.1`
* `Fragment`: `1.6.0` -> `1.6.1`
* `Repos`:
* Fixes In `KeyValueRepo.clear()` of almost all inheritors of `KeyValueRepo`
* `Cache`:
* All full caches got `skipStartInvalidate` property. By default, this property is `false` and fully caching repos
will be automatically invalidated on start of their work
## 0.19.7
* `Versions`:
* `Coroutines`: `1.7.1` -> `1.7.2`
## 0.19.6
* `Versions`:
* `Coroutines`: `1.6.4` -> `1.7.1`
* `Ktor`: `2.3.1` -> `2.3.2`
* `Compose`: `1.4.0` -> `1.4.1`
## 0.19.5
* `Repos`:
* `Generator`:
* Fixes in new type generation
## 0.19.4
* `Versions`:
* `Koin`: `3.4.1` -> `3.4.2`
* `Android Fragments`: `1.5.7` -> `1.6.0`
* `Koin`
* `Generator`
* Fixes in new generic generator part
## 0.19.3
* `Koin`
* `Generator`
* New getter methods now available with opportunity to use parameters
* Old notation `*Single` and `*Factory` is deprecated since this release. With old
will be generated new `single*` and `factory*` notations for new generations
* Add opportunity to use generic-oriented koin definitions
## 0.19.2
* `Versions`:
* `Ktor`: `2.3.0` -> `2.3.1`
* `Koin`: `3.4.0` -> `3.4.1`
* `Uuid`: `0.7.0` -> `0.7.1`
## 0.19.1
* `Versions`:
* `Korlibs`: `4.0.1` -> `4.0.3`
* `Kotlin Poet`: `1.13.2` -> `1.14.0`
## 0.19.0
* `Versions`:
* `Korlibs`: `3.4.0` -> `4.0.1`
## 0.18.4
* `Koin`:
* New extension `lazyInject`
## 0.18.3
* `Versions`:
* `Serialization`: `1.5.0` -> `1.5.1`
* `Android Cor Ktx`: `1.10.0` -> `1.10.1`
## 0.18.2
* `Startup`:
* Now internal `Json` is fully customizable
## 0.18.1
* `Common`:
* Add `MapDiff`
* `Coroutines`:
* Add `SmartMutex`
## 0.18.0
**ALL PREVIOUSLY DEPRECATED FUNCTIONALITY HAVE BEEN REMOVED**
* `Versions`:
* `Android Fragments`: `1.5.6` -> `1.5.7`
* `Ktor`:
* `Server`:
* Now it is possible to take query parameters as list
* `Repos`:
* `Common`:
* New `WriteKeyValuesRepo.removeWithValue`
* `Cache`:
* Rename full caching factories functions to `fullyCached`
## 0.17.8
* `Versions`:
* `Ktor`: `2.2.4` -> `2.3.0`
## 0.17.7
* `Versions`:
* `Android CoreKtx`: `1.9.0` -> `1.10.0`
* `Startup`:
* Add support of `linuxX64` and `mingwX64` platforms
## 0.17.6
* `Versions`:
* `Kotlin`: `1.8.10` -> `1.8.20`
* `KSLog`: `1.0.0` -> `1.1.1`
* `Compose`: `1.3.1` -> `1.4.0`
* `Koin`: `3.3.2` -> `3.4.0`
* `RecyclerView`: `1.2.1` -> `1.3.0`
* `Fragment`: `1.5.5` -> `1.5.6`
* Experimentally (`!!!`) add `linuxX64` and `mingwX64` targets
## 0.17.5
* `Common`:
* Conversations of number primitives with bounds care
* `Repos`:
* `Common`:
* By default, `getAll` for repos will take all the size of repo as page size
* New extension for all built-in repos `maxPagePagination`
* All the repos got `getAll` functions
## 0.17.4
* `Serialization`:
* `Mapper`:
* Module inited
* `Versions`:
* `Compose`: `1.3.1-rc02` -> `1.3.1`
## 0.17.3
* `Common`:
* Add `fixed` extensions for `Float` and `Double`
* New function `emptyDiff`
* Now you may pass custom `comparisonFun` to all `diff` functions
## 0.17.2
* `FSM`:
* `DefaultStatesManager.onUpdateContextsConflictResolver` and `DefaultStatesManager.onStartContextsConflictResolver` now return `false` by default
## 0.17.1
* **Hotfix** for absence of jvm dependencies in android modules

View File

@@ -0,0 +1 @@
<manifest/>

View File

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

View File

@@ -0,0 +1 @@
<manifest/>

View File

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

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 {
androidMain {
dependencies {
api project(":micro_utils.android.smalltextfield")
}
}
}
}

View File

@@ -0,0 +1 @@
<manifest/>

View File

@@ -0,0 +1,27 @@
package dev.inmo.micro_utils.android.pickers
import androidx.compose.animation.core.*
internal suspend fun Animatable<Float, AnimationVector1D>.fling(
initialVelocity: Float,
animationSpec: DecayAnimationSpec<Float>,
adjustTarget: ((Float) -> Float)?,
block: (Animatable<Float, AnimationVector1D>.() -> Unit)? = null,
): AnimationResult<Float, AnimationVector1D> {
val targetValue = animationSpec.calculateTargetValue(value, initialVelocity)
val adjustedTarget = adjustTarget?.invoke(targetValue)
return if (adjustedTarget != null) {
animateTo(
targetValue = adjustedTarget,
initialVelocity = initialVelocity,
block = block
)
} else {
animateDecay(
initialVelocity = initialVelocity,
animationSpec = animationSpec,
block = block,
)
}
}

View File

@@ -0,0 +1,222 @@
package dev.inmo.micro_utils.android.pickers
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.exponentialDecay
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.ContentAlpha
import androidx.compose.material.IconButton
import androidx.compose.material.ProvideTextStyle
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.KeyboardArrowDown
import androidx.compose.material.icons.filled.KeyboardArrowUp
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.PointerInputScope
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.ExperimentalTextApi
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.center
import androidx.compose.ui.unit.dp
import dev.inmo.micro_utils.android.smalltextfield.SmallTextField
import kotlinx.coroutines.launch
import kotlin.math.abs
import kotlin.math.absoluteValue
import kotlin.math.roundToInt
private inline fun PointerInputScope.checkContains(offset: Offset): Boolean {
return ((size.center.x - offset.x).absoluteValue < size.width / 2) && ((size.center.y - offset.y).absoluteValue < size.height / 2)
}
// src: https://gist.github.com/vganin/a9a84653a9f48a2d669910fbd48e32d5
@OptIn(ExperimentalTextApi::class, ExperimentalComposeUiApi::class)
@Composable
fun NumberPicker(
number: Int,
modifier: Modifier = Modifier,
range: IntRange? = null,
textStyle: TextStyle = LocalTextStyle.current,
arrowsColor: Color = MaterialTheme.colorScheme.primary,
allowUseManualInput: Boolean = true,
onStateChanged: (Int) -> Unit = {},
) {
val coroutineScope = rememberCoroutineScope()
val numbersColumnHeight = 36.dp
val halvedNumbersColumnHeight = numbersColumnHeight / 2
val halvedNumbersColumnHeightPx = with(LocalDensity.current) { halvedNumbersColumnHeight.toPx() }
fun animatedStateValue(offset: Float): Int = number - (offset / halvedNumbersColumnHeightPx).toInt()
val animatedOffset = remember { Animatable(0f) }.apply {
if (range != null) {
val offsetRange = remember(number, range) {
val value = number
val first = -(range.last - value) * halvedNumbersColumnHeightPx
val last = -(range.first - value) * halvedNumbersColumnHeightPx
first..last
}
updateBounds(offsetRange.start, offsetRange.endInclusive)
}
}
val coercedAnimatedOffset = animatedOffset.value % halvedNumbersColumnHeightPx
val animatedStateValue = animatedStateValue(animatedOffset.value)
val disabledArrowsColor = arrowsColor.copy(alpha = ContentAlpha.disabled)
val inputFieldShown = if (allowUseManualInput) {
remember { mutableStateOf(false) }
} else {
null
}
Column(
modifier = modifier
.wrapContentSize()
.draggable(
orientation = Orientation.Vertical,
state = rememberDraggableState { deltaY ->
if (inputFieldShown ?.value != true) {
coroutineScope.launch {
animatedOffset.snapTo(animatedOffset.value + deltaY)
}
}
},
onDragStopped = { velocity ->
if (inputFieldShown ?.value != true) {
coroutineScope.launch {
val endValue = animatedOffset.fling(
initialVelocity = velocity,
animationSpec = exponentialDecay(frictionMultiplier = 20f),
adjustTarget = { target ->
val coercedTarget = target % halvedNumbersColumnHeightPx
val coercedAnchors =
listOf(-halvedNumbersColumnHeightPx, 0f, halvedNumbersColumnHeightPx)
val coercedPoint = coercedAnchors.minByOrNull { abs(it - coercedTarget) }!!
val base =
halvedNumbersColumnHeightPx * (target / halvedNumbersColumnHeightPx).toInt()
coercedPoint + base
}
).endState.value
onStateChanged(animatedStateValue(endValue))
animatedOffset.snapTo(0f)
}
}
}
),
horizontalAlignment = Alignment.CenterHorizontally
) {
val spacing = 4.dp
val upEnabled = range == null || range.first < number
IconButton(
{
onStateChanged(number - 1)
inputFieldShown ?.value = false
},
enabled = upEnabled
) {
Icon(Icons.Default.KeyboardArrowUp, "", tint = if (upEnabled) arrowsColor else disabledArrowsColor)
}
Spacer(modifier = Modifier.height(spacing))
Box(
modifier = Modifier
.offset { IntOffset(x = 0, y = coercedAnimatedOffset.roundToInt()) },
contentAlignment = Alignment.Center
) {
val baseLabelModifier = Modifier.align(Alignment.Center)
ProvideTextStyle(textStyle) {
Text(
text = (animatedStateValue - 1).toString(),
modifier = baseLabelModifier
.offset(y = -halvedNumbersColumnHeight)
.alpha(coercedAnimatedOffset / halvedNumbersColumnHeightPx)
)
if (inputFieldShown ?.value == true) {
val currentValue = remember { mutableStateOf(number.toString()) }
val focusRequester = remember { FocusRequester() }
SmallTextField(
currentValue.value,
{
val asDigit = it.toIntOrNull()
when {
(asDigit == null && it.isEmpty()) -> currentValue.value = (range ?.first ?: 0).toString()
(asDigit != null && (range == null || asDigit in range)) -> currentValue.value = it
else -> { /* do nothing */ }
}
},
baseLabelModifier.focusRequester(focusRequester).width(IntrinsicSize.Min).pointerInput(number) {
detectTapGestures {
if (!checkContains(it)) {
currentValue.value.toIntOrNull() ?.let(onStateChanged)
inputFieldShown.value = false
}
}
},
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Number
),
keyboardActions = KeyboardActions {
currentValue.value.toIntOrNull() ?.let(onStateChanged)
inputFieldShown.value = false
},
singleLine = true,
textStyle = textStyle
)
LaunchedEffect(Unit) {
focusRequester.requestFocus()
}
} else {
Text(
text = animatedStateValue.toString(),
modifier = baseLabelModifier
.alpha(1 - abs(coercedAnimatedOffset) / halvedNumbersColumnHeightPx)
.clickable {
if (inputFieldShown ?.value == false) {
inputFieldShown.value = true
}
}
)
}
Text(
text = (animatedStateValue + 1).toString(),
modifier = baseLabelModifier
.offset(y = halvedNumbersColumnHeight)
.alpha(-coercedAnimatedOffset / halvedNumbersColumnHeightPx)
)
}
}
Spacer(modifier = Modifier.height(spacing))
val downEnabled = range == null || range.last > number
IconButton(
{
onStateChanged(number + 1)
inputFieldShown ?.value = false
},
enabled = downEnabled
) {
Icon(Icons.Default.KeyboardArrowDown, "", tint = if (downEnabled) arrowsColor else disabledArrowsColor)
}
}
}

View File

@@ -0,0 +1,156 @@
package dev.inmo.micro_utils.android.pickers
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.exponentialDecay
import androidx.compose.foundation.gestures.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.material.ContentAlpha
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.KeyboardArrowDown
import androidx.compose.material.icons.filled.KeyboardArrowUp
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.ExperimentalTextApi
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch
import kotlin.math.*
@OptIn(ExperimentalTextApi::class, ExperimentalComposeUiApi::class)
@Composable
fun <T> SetPicker(
current: T,
dataList: List<T>,
modifier: Modifier = Modifier,
textStyle: TextStyle = LocalTextStyle.current,
arrowsColor: Color = MaterialTheme.colorScheme.primary,
dataToString: @Composable (T) -> String = { it.toString() },
onStateChanged: (T) -> Unit = {},
) {
val coroutineScope = rememberCoroutineScope()
val numbersColumnHeight = 8.dp + with(LocalDensity.current) {
textStyle.lineHeight.toDp()
}
val numbersColumnHeightPx = with(LocalDensity.current) { numbersColumnHeight.toPx() }
val halvedNumbersColumnHeight = numbersColumnHeight / 2
val halvedNumbersColumnHeightPx = with(LocalDensity.current) { halvedNumbersColumnHeight.toPx() }
val index = dataList.indexOfFirst { it === current }.takeIf { it > -1 } ?: dataList.indexOf(current)
val lastIndex = dataList.size - 1
fun animatedStateValue(offset: Float): Int = index - (offset / halvedNumbersColumnHeightPx).toInt()
val animatedOffset = remember { Animatable(0f) }.apply {
val offsetRange = remember(index, lastIndex) {
val value = index
val first = -(lastIndex - value) * halvedNumbersColumnHeightPx
val last = value * halvedNumbersColumnHeightPx
first..last
}
updateBounds(offsetRange.start, offsetRange.endInclusive)
}
val indexAnimatedOffset = if (animatedOffset.value > 0) {
(index - floor(animatedOffset.value / halvedNumbersColumnHeightPx).toInt())
} else {
(index - ceil(animatedOffset.value / halvedNumbersColumnHeightPx).toInt())
}
val coercedAnimatedOffset = animatedOffset.value % halvedNumbersColumnHeightPx
val boxOffset = (indexAnimatedOffset * halvedNumbersColumnHeightPx) - coercedAnimatedOffset
val disabledArrowsColor = arrowsColor.copy(alpha = ContentAlpha.disabled)
val scrollState = rememberScrollState()
Column(
modifier = modifier
.wrapContentSize()
.draggable(
orientation = Orientation.Vertical,
state = rememberDraggableState { deltaY ->
coroutineScope.launch {
animatedOffset.snapTo(animatedOffset.value + deltaY)
}
},
onDragStopped = { velocity ->
coroutineScope.launch {
val endValue = animatedOffset.fling(
initialVelocity = velocity,
animationSpec = exponentialDecay(frictionMultiplier = 20f),
adjustTarget = { target ->
val coercedTarget = target % halvedNumbersColumnHeightPx
val coercedAnchors =
listOf(-halvedNumbersColumnHeightPx, 0f, halvedNumbersColumnHeightPx)
val coercedPoint = coercedAnchors.minByOrNull { abs(it - coercedTarget) }!!
val base =
halvedNumbersColumnHeightPx * (target / halvedNumbersColumnHeightPx).toInt()
coercedPoint + base
}
).endState.value
onStateChanged(dataList.elementAt(animatedStateValue(endValue)))
animatedOffset.snapTo(0f)
}
}
),
horizontalAlignment = Alignment.CenterHorizontally
) {
val spacing = 4.dp
val upEnabled = index > 0
IconButton(
{
onStateChanged(dataList.elementAt(index - 1))
},
enabled = upEnabled
) {
Icon(Icons.Default.KeyboardArrowUp, "", tint = if (upEnabled) arrowsColor else disabledArrowsColor)
}
Spacer(modifier = Modifier.height(spacing))
Box(
modifier = Modifier,
contentAlignment = Alignment.Center
) {
ProvideTextStyle(textStyle) {
dataList.forEachIndexed { i, t ->
val alpha = when {
i == indexAnimatedOffset - 1 -> coercedAnimatedOffset / halvedNumbersColumnHeightPx
i == indexAnimatedOffset -> 1 - (abs(coercedAnimatedOffset) / halvedNumbersColumnHeightPx)
i == indexAnimatedOffset + 1 -> -coercedAnimatedOffset / halvedNumbersColumnHeightPx
else -> return@forEachIndexed
}
val offset = when {
i == indexAnimatedOffset - 1 && coercedAnimatedOffset > 0 -> coercedAnimatedOffset - halvedNumbersColumnHeightPx
i == indexAnimatedOffset -> coercedAnimatedOffset
i == indexAnimatedOffset + 1 && coercedAnimatedOffset < 0 -> coercedAnimatedOffset + halvedNumbersColumnHeightPx
else -> return@forEachIndexed
}
Text(
text = dataToString(t),
modifier = Modifier
.alpha(alpha)
.offset(y = with(LocalDensity.current) { offset.toDp() })
)
}
}
}
Spacer(modifier = Modifier.height(spacing))
val downEnabled = index < lastIndex
IconButton(
{
onStateChanged(dataList.elementAt(index + 1))
},
enabled = downEnabled
) {
Icon(Icons.Default.KeyboardArrowDown, "", tint = if (downEnabled) arrowsColor else disabledArrowsColor)
}
}
}

View File

@@ -0,0 +1 @@
<manifest/>

View File

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

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 {
androidMain {
dependencies {
api libs.android.compose.material3
}
}
}
}

View File

@@ -0,0 +1 @@
<manifest/>

View File

@@ -0,0 +1,66 @@
package dev.inmo.micro_utils.android.smalltextfield
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.text.selection.LocalTextSelectionColors
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.takeOrElse
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.VisualTransformation
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SmallTextField(
value: String,
onValueChange: (String) -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
readOnly: Boolean = false,
textStyle: TextStyle = LocalTextStyle.current,
textColor: Color = textStyle.color.takeOrElse {
LocalContentColor.current
},
visualTransformation: VisualTransformation = VisualTransformation.None,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
keyboardActions: KeyboardActions = KeyboardActions.Default,
singleLine: Boolean = false,
maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE,
minLines: Int = 1,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
) {
BasicTextField(
value = value,
modifier = modifier,
onValueChange = onValueChange,
enabled = enabled,
readOnly = readOnly,
textStyle = textStyle.copy(
color = textColor
),
visualTransformation = visualTransformation,
keyboardOptions = keyboardOptions,
keyboardActions = keyboardActions,
interactionSource = interactionSource,
singleLine = singleLine,
maxLines = maxLines,
minLines = minLines,
cursorBrush = SolidColor(
textStyle.color.takeOrElse {
LocalContentColor.current
}
)
)
}

View File

@@ -17,12 +17,17 @@ buildscript {
}
}
plugins {
alias(libs.plugins.versions)
}
allprojects {
repositories {
mavenLocal()
mavenCentral()
google()
maven { url "https://maven.pkg.jetbrains.space/public/p/compose/dev" }
maven { url "https://nexus.inmo.dev/repository/maven-releases/" }
}
// temporal crutch until legacy tests will be stabled or legacy target will be removed
@@ -37,3 +42,4 @@ allprojects {
apply from: "./extensions.gradle"
apply from: "./github_release.gradle"
apply from: "./versions_plugin_setup.gradle"

View File

@@ -4,7 +4,7 @@ plugins {
id "com.android.library"
}
apply from: "$mppProjectWithSerializationPresetPath"
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64ProjectPresetPath"
kotlin {
sourceSets {
@@ -20,5 +20,21 @@ kotlin {
}
dependsOn jvmMain
}
linuxX64Main {
dependencies {
api libs.okio
}
}
mingwX64Main {
dependencies {
api libs.okio
}
}
linuxArm64Main {
dependencies {
api libs.okio
}
}
}
}

View File

@@ -0,0 +1 @@
<manifest/>

View File

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

View File

@@ -0,0 +1 @@
<manifest/>

View File

@@ -34,7 +34,11 @@ data class Diff<T> internal constructor(
*/
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>?>>,
@@ -43,14 +47,14 @@ private inline fun <T> performChanges(
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 {
@@ -104,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
@@ -132,7 +136,7 @@ fun <T> Iterable<T>.calculateDiff(
}
when {
oldObject === newObject || (oldObject == newObject && !strictComparison) -> {
comparisonFun(oldObject, newObject) -> {
changedObjects.addAll(potentiallyChangedObjects.map {
@Suppress("UNCHECKED_CAST")
it as Pair<IndexedValue<T>, IndexedValue<T>>
@@ -143,23 +147,49 @@ fun <T> Iterable<T>.calculateDiff(
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())
}
/**
* 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)
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)
/**
@@ -169,6 +199,23 @@ inline fun <T> Iterable<T>.calculateStrictDiff(
other: Iterable<T>
) = calculateDiff(other, strictComparison = true)
/**
* Applies [diff] to [this] [MutableList]
*/
fun <T> MutableList<T>.applyDiff(
diff: Diff<T>
) {
for (i in diff.removed.indices.sortedDescending()) {
removeAt(diff.removed[i].index)
}
diff.added.forEach { (i, t) ->
add(i, t)
}
diff.replaced.forEach { (_, new) ->
set(new.index, new.value)
}
}
/**
* This method call [calculateDiff] with strict mode [strictComparison] and then apply differences to [this]
* mutable list
@@ -177,13 +224,26 @@ 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)
}
applyDiff(it)
}
/**
* This method call [calculateDiff] and then apply differences to [this]
* mutable list
*/
fun <T> MutableList<T>.applyDiff(
source: Iterable<T>,
comparisonFun: (T?, T?) -> Boolean
): Diff<T> = calculateDiff(source, comparisonFun).also {
applyDiff(it)
}
/**
* Reverse [this] [Diff]. Result will contain [Diff.added] on [Diff.removed] (and vice-verse), all the
* [Diff.replaced] values will be reversed too
*/
fun <T> Diff<T>.reversed() = Diff(
removed = added,
replaced = replaced.map { it.second to it.first },
added = removed
)

View File

@@ -0,0 +1,135 @@
package dev.inmo.micro_utils.common
/**
* Contains diff based on the comparison of objects with the same [K].
*
* @param removed Contains map with keys removed from parent map
* @param changed Contains map with keys values changed new map in comparison with old one
* @param added Contains map with new keys and values
*/
data class MapDiff<K, V> @Warning(warning) constructor(
val removed: Map<K, V>,
val changed: Map<K, Pair<V, V>>,
val added: Map<K, V>
) {
fun isEmpty() = removed.isEmpty() && changed.isEmpty() && added.isEmpty()
inline fun isNotEmpty() = !isEmpty()
companion object {
private const val warning = "This feature can be changed without any warranties. Use with caution and only in case you know what you are doing"
fun <K, V> empty() = MapDiff<K, V>(emptyMap(), emptyMap(), emptyMap())
}
}
private inline fun <K, V> createCompareFun(
strictComparison: Boolean
): (K, V, V) -> Boolean = if (strictComparison) {
{ _, first, second -> first === second }
} else {
{ _, first, second -> first == second }
}
/**
* Compare [this] [Map] with the [other] one in principle when [other] is newer than [this]
*
* @param compareFun Will be used to determine changed values
*/
fun <K, V> Map<K, V>.diff(
other: Map<K, V>,
compareFun: (K, V, V) -> Boolean
): MapDiff<K, V> {
val removed: Map<K, V> = (keys - other.keys).associateWith {
getValue(it)
}
val added: Map<K, V> = (other.keys - keys).associateWith {
other.getValue(it)
}
val changed = keys.intersect(other.keys).mapNotNull {
val old = getValue(it)
val new = other.getValue(it)
if (compareFun(it, old, new)) {
return@mapNotNull null
} else {
it to (old to new)
}
}.toMap()
return MapDiff(
removed,
changed,
added
)
}
/**
* Compare [this] [Map] with the [other] one in principle when [other] is newer than [this]
*
* @param strictComparison If true, will use strict (===) comparison for the values' comparison. Otherwise, standard
* `equals` will be used
*/
fun <K, V> Map<K, V>.diff(
other: Map<K, V>,
strictComparison: Boolean = false
): MapDiff<K, V> = diff(
other,
compareFun = createCompareFun(strictComparison)
)
/**
* Will apply [mapDiff] to [this] [MutableMap]
*/
fun <K, V> MutableMap<K, V>.applyDiff(
mapDiff: MapDiff<K, V>
) {
mapDiff.apply {
removed.keys.forEach { remove(it) }
changed.forEach { (k, oldNew) ->
put(k, oldNew.second)
}
added.forEach { (k, new) ->
put(k, new)
}
}
}
/**
* Will apply changes with [from] map into [this] one
*
* @param compareFun Will be used to determine changed values
*
* @return [MapDiff] applied to [this] [MutableMap]
*/
fun <K, V> MutableMap<K, V>.applyDiff(
from: Map<K, V>,
compareFun: (K, V, V) -> Boolean
): MapDiff<K, V> {
return diff(from, compareFun).also {
applyDiff(it)
}
}
/**
* Will apply changes with [from] map into [this] one
*
* @param strictComparison If true, will use strict (===) comparison for the values' comparison. Otherwise, standard
* `equals` will be used
*
* @return [MapDiff] applied to [this] [MutableMap]
*/
fun <K, V> MutableMap<K, V>.applyDiff(
from: Map<K, V>,
strictComparison: Boolean = false
): MapDiff<K, V> = applyDiff(
from,
compareFun = createCompareFun(strictComparison)
)
/**
* Reverse [this] [MapDiff]. Result will contain [MapDiff.added] on [MapDiff.removed] (and vice-verse), all the
* [MapDiff.changed] values will be reversed too
*/
fun <K, V> MapDiff<K, V>.reversed(): MapDiff<K, V> = MapDiff(
removed = added,
changed = changed.mapValues { (_, oldNew) -> oldNew.second to oldNew.first },
added = removed
)

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,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

@@ -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,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,25 @@
package dev.inmo.micro_utils.common
import kotlinx.cinterop.*
import platform.posix.snprintf
import platform.posix.sprintf
@OptIn(ExperimentalForeignApi::class)
actual fun Float.fixed(signs: Int): Float {
return memScoped {
val buff = allocArray<ByteVar>(Float.SIZE_BYTES * 2)
sprintf(buff, "%.${signs}f", this@fixed)
buff.toKString().toFloat()
}
}
@OptIn(ExperimentalForeignApi::class)
actual fun Double.fixed(signs: Int): Double {
return memScoped {
val buff = allocArray<ByteVar>(Double.SIZE_BYTES * 2)
sprintf(buff, "%.${signs}f", this@fixed)
buff.toKString().toDouble()
}
}

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,25 @@
package dev.inmo.micro_utils.common
import kotlinx.cinterop.*
import platform.posix.snprintf
import platform.posix.sprintf
@OptIn(ExperimentalForeignApi::class)
actual fun Float.fixed(signs: Int): Float {
return memScoped {
val buff = allocArray<ByteVar>(Float.SIZE_BYTES * 2)
sprintf(buff, "%.${signs}f", this@fixed)
buff.toKString().toFloat()
}
}
@OptIn(ExperimentalForeignApi::class)
actual fun Double.fixed(signs: Int): Double {
return memScoped {
val buff = allocArray<ByteVar>(Double.SIZE_BYTES * 2)
sprintf(buff, "%.${signs}f", this@fixed)
buff.toKString().toDouble()
}
}

View File

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

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,25 @@
package dev.inmo.micro_utils.common
import kotlinx.cinterop.*
import platform.posix.snprintf
import platform.posix.sprintf
@OptIn(ExperimentalForeignApi::class)
actual fun Float.fixed(signs: Int): Float {
return memScoped {
val buff = allocArray<ByteVar>(Float.SIZE_BYTES * 2)
sprintf(buff, "%.${signs}f", this@fixed)
buff.toKString().toFloat()
}
}
@OptIn(ExperimentalForeignApi::class)
actual fun Double.fixed(signs: Int): Double {
return memScoped {
val buff = allocArray<ByteVar>(Double.SIZE_BYTES * 2)
sprintf(buff, "%.${signs}f", this@fixed)
buff.toKString().toDouble()
}
}

View File

@@ -4,7 +4,7 @@ plugins {
id "com.android.library"
}
apply from: "$mppProjectWithSerializationPresetPath"
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64ProjectPresetPath"
kotlin {
sourceSets {

View File

@@ -0,0 +1 @@
<manifest/>

View File

@@ -0,0 +1,46 @@
package dev.inmo.micro_utils.coroutines.compose
import androidx.compose.runtime.MutableState
import dev.inmo.micro_utils.coroutines.SpecialMutableStateFlow
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
/**
* This type works like [MutableState], [kotlinx.coroutines.flow.StateFlow] and [kotlinx.coroutines.flow.MutableSharedFlow].
* Based on [SpecialMutableStateFlow]
*/
@Deprecated("Will be removed soon")
class FlowState<T>(
initial: T,
internalScope: CoroutineScope = CoroutineScope(Dispatchers.Default)
) : MutableState<T>,
SpecialMutableStateFlow<T>(initial, internalScope) {
private var internalValue: T = initial
override var value: T
get() = internalValue
set(value) {
internalValue = value
tryEmit(value)
}
override fun onChangeWithoutSync(value: T) {
internalValue = value
super.onChangeWithoutSync(value)
}
override fun component1(): T = value
override fun component2(): (T) -> Unit = { tryEmit(it) }
override fun tryEmit(value: T): Boolean {
internalValue = value
return super.tryEmit(value)
}
override suspend fun emit(value: T) {
internalValue = value
super.emit(value)
}
}
//fun <T> MutableState<T>.asFlowState(scope: CoroutineScope = CoroutineScope(Dispatchers.Main)) = FlowState(this, scope)

View File

@@ -1,21 +0,0 @@
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

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

View File

@@ -0,0 +1 @@
<manifest/>

View File

@@ -0,0 +1,136 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.isActive
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
/**
* It is interface which will work like classic [Mutex], but in difference have [lockStateFlow] for listening of the
* [SmartMutex] state.
*
* There is [Mutable] and [Immutable] realizations. In case you are owner and manager current state of lock, you need
* [Mutable] [SmartMutex]. Otherwise, [Immutable].
*
* Any [Mutable] [SmartMutex] may produce its [Immutable] variant which will contains [lockStateFlow] equal to its
* [Mutable] creator
*/
sealed interface SmartMutex {
val lockStateFlow: StateFlow<Boolean>
/**
* * True - locked
* * False - unlocked
*/
val isLocked: Boolean
get() = lockStateFlow.value
/**
* Immutable variant of [SmartMutex]. In fact will depend on the owner of [lockStateFlow]
*/
class Immutable(override val lockStateFlow: StateFlow<Boolean>) : SmartMutex
/**
* Mutable variant of [SmartMutex]. With that variant you may [lock] and [unlock]. Besides, you may create
* [Immutable] variant of [this] instance with [immutable] factory
*
* @param locked Preset state of [isLocked] and its internal [_lockStateFlow]
*/
class Mutable(locked: Boolean = false) : SmartMutex {
private val _lockStateFlow = MutableStateFlow<Boolean>(locked)
override val lockStateFlow: StateFlow<Boolean> = _lockStateFlow.asStateFlow()
private val internalChangesMutex = Mutex()
fun immutable() = Immutable(lockStateFlow)
/**
* Holds call until this [SmartMutex] will be re-locked. That means that while [isLocked] == true, [holds] will
* wait for [isLocked] == false and then try to lock
*/
suspend fun lock() {
do {
waitUnlock()
val shouldContinue = internalChangesMutex.withLock {
if (_lockStateFlow.value) {
true
} else {
_lockStateFlow.value = true
false
}
}
} while (shouldContinue && currentCoroutineContext().isActive)
}
/**
* Will try to lock this [SmartMutex] immediataly
*
* @return True if lock was successful. False otherwise
*/
suspend fun tryLock(): Boolean {
return if (!_lockStateFlow.value) {
internalChangesMutex.withLock {
if (!_lockStateFlow.value) {
_lockStateFlow.value = true
true
} else {
false
}
}
} else {
false
}
}
/**
* If [isLocked] == true - will change it to false and return true. If current call will not unlock this
* [SmartMutex] - false
*/
suspend fun unlock(): Boolean {
return if (_lockStateFlow.value) {
internalChangesMutex.withLock {
if (_lockStateFlow.value) {
_lockStateFlow.value = false
true
} else {
false
}
}
} else {
false
}
}
}
}
/**
* Will call [SmartMutex.Mutable.lock], then execute [action] and return the result after [SmartMutex.Mutable.unlock]
*/
@OptIn(ExperimentalContracts::class)
suspend inline fun <T> SmartMutex.Mutable.withLock(action: () -> T): T {
contract {
callsInPlace(action, InvocationKind.EXACTLY_ONCE)
}
lock()
try {
return action()
} finally {
unlock()
}
}
/**
* Will wait until the [SmartMutex.lockStateFlow] of [this] instance will be false.
*
* Anyway, after the end of this block there are no any guaranties that [SmartMutex.isLocked] == false due to the fact
* that some other parties may lock it again
*/
suspend fun SmartMutex.waitUnlock() = lockStateFlow.first { !it }

View File

@@ -0,0 +1,105 @@
package dev.inmo.micro_utils.coroutines
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
/**
* Composite mutex which works with next rules:
*
* * [acquireRead] require to [writeMutex] be free. Then it will take one lock from [readSemaphore]
* * [releaseRead] will just free up one permit in [readSemaphore]
* * [lockWrite] will lock [writeMutex] and then await while all [readSemaphore] will be freed
* * [unlockWrite] will just unlock [writeMutex]
*/
class SmartRWLocker(private val readPermits: Int = Int.MAX_VALUE, writeIsLocked: Boolean = false) {
private val _readSemaphore = SmartSemaphore.Mutable(permits = readPermits, acquiredPermits = 0)
private val _writeMutex = SmartMutex.Mutable(locked = writeIsLocked)
val readSemaphore: SmartSemaphore.Immutable = _readSemaphore.immutable()
val writeMutex: SmartMutex.Immutable = _writeMutex.immutable()
/**
* Do lock in [readSemaphore] inside of [writeMutex] locking
*/
suspend fun acquireRead() {
_writeMutex.waitUnlock()
_readSemaphore.acquire()
}
/**
* Release one read permit in [readSemaphore]
*/
suspend fun releaseRead(): Boolean {
return _readSemaphore.release()
}
/**
* Locking [writeMutex] and wait while all [readSemaphore] permits will be freed
*/
suspend fun lockWrite() {
_writeMutex.lock()
_readSemaphore.acquire(readPermits)
}
/**
* Unlock [writeMutex]
*/
suspend fun unlockWrite(): Boolean {
return _writeMutex.unlock().also {
if (it) {
_readSemaphore.release(readPermits)
}
}
}
}
/**
* Will call [SmartSemaphore.Mutable.lock], then execute [action] and return the result after [SmartSemaphore.Mutable.unlock]
*/
@OptIn(ExperimentalContracts::class)
suspend inline fun <T> SmartRWLocker.withReadAcquire(action: () -> T): T {
contract {
callsInPlace(action, InvocationKind.EXACTLY_ONCE)
}
acquireRead()
try {
return action()
} finally {
releaseRead()
}
}
/**
* Will wait until the [SmartSemaphore.permitsStateFlow] of [this] instance will have [permits] count free permits.
*
* Anyway, after the end of this block there are no any guaranties that [SmartSemaphore.freePermits] >= [permits] due to
* the fact that some other parties may lock it again
*/
suspend fun SmartRWLocker.waitReadRelease(permits: Int = 1) = readSemaphore.waitRelease(permits)
/**
* Will call [SmartMutex.Mutable.lock], then execute [action] and return the result after [SmartMutex.Mutable.unlock]
*/
@OptIn(ExperimentalContracts::class)
suspend inline fun <T> SmartRWLocker.withWriteLock(action: () -> T): T {
contract {
callsInPlace(action, InvocationKind.EXACTLY_ONCE)
}
lockWrite()
try {
return action()
} finally {
unlockWrite()
}
}
/**
* Will wait until the [SmartMutex.lockStateFlow] of [this] instance will be false.
*
* Anyway, after the end of this block there are no any guaranties that [SmartMutex.isLocked] == false due to the fact
* that some other parties may lock it again
*/
suspend fun SmartRWLocker.waitWriteUnlock() = writeMutex.waitUnlock()

View File

@@ -0,0 +1,168 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.isActive
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withLock
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
/**
* It is interface which will work like classic [Semaphore], but in difference have [permitsStateFlow] for listening of the
* [SmartSemaphore] state.
*
* There is [Mutable] and [Immutable] realizations. In case you are owner and manager current state of lock, you need
* [Mutable] [SmartSemaphore]. Otherwise, [Immutable].
*
* Any [Mutable] [SmartSemaphore] may produce its [Immutable] variant which will contains [permitsStateFlow] equal to its
* [Mutable] creator
*/
sealed interface SmartSemaphore {
val permitsStateFlow: StateFlow<Int>
/**
* * True - locked
* * False - unlocked
*/
val freePermits: Int
get() = permitsStateFlow.value
/**
* Immutable variant of [SmartSemaphore]. In fact will depend on the owner of [permitsStateFlow]
*/
class Immutable(override val permitsStateFlow: StateFlow<Int>) : SmartSemaphore
/**
* Mutable variant of [SmartSemaphore]. With that variant you may [lock] and [unlock]. Besides, you may create
* [Immutable] variant of [this] instance with [immutable] factory
*
* @param locked Preset state of [freePermits] and its internal [_freePermitsStateFlow]
*/
class Mutable(private val permits: Int, acquiredPermits: Int = 0) : SmartSemaphore {
private val _freePermitsStateFlow = MutableStateFlow<Int>(permits - acquiredPermits)
override val permitsStateFlow: StateFlow<Int> = _freePermitsStateFlow.asStateFlow()
private val internalChangesMutex = Mutex(false)
fun immutable() = Immutable(permitsStateFlow)
private fun checkedPermits(permits: Int) = permits.coerceIn(1 .. this.permits)
/**
* Holds call until this [SmartSemaphore] will be re-locked. That means that current method will
*/
suspend fun acquire(permits: Int = 1) {
var acquiredPermits = 0
val checkedPermits = checkedPermits(permits)
try {
do {
val shouldContinue = internalChangesMutex.withLock {
val requiredPermits = checkedPermits - acquiredPermits
val acquiring = minOf(freePermits, requiredPermits).takeIf { it > 0 } ?: return@withLock true
acquiredPermits += acquiring
_freePermitsStateFlow.value -= acquiring
acquiredPermits != checkedPermits
}
if (shouldContinue) {
waitRelease()
}
} while (shouldContinue && currentCoroutineContext().isActive)
} catch (e: Throwable) {
release(acquiredPermits)
throw e
}
}
/**
* Holds call until this [SmartSemaphore] will be re-locked. That means that while [freePermits] == true, [holds] will
* wait for [freePermits] == false and then try to lock
*/
suspend fun acquireByOne(permits: Int = 1) {
val checkedPermits = checkedPermits(permits)
do {
waitRelease(checkedPermits)
val shouldContinue = internalChangesMutex.withLock {
if (_freePermitsStateFlow.value < checkedPermits) {
true
} else {
_freePermitsStateFlow.value -= checkedPermits
false
}
}
} while (shouldContinue && currentCoroutineContext().isActive)
}
/**
* Will try to lock this [SmartSemaphore] immediataly
*
* @return True if lock was successful. False otherwise
*/
suspend fun tryAcquire(permits: Int = 1): Boolean {
val checkedPermits = checkedPermits(permits)
return if (_freePermitsStateFlow.value < checkedPermits) {
internalChangesMutex.withLock {
if (_freePermitsStateFlow.value < checkedPermits) {
_freePermitsStateFlow.value -= checkedPermits
true
} else {
false
}
}
} else {
false
}
}
/**
* If [freePermits] == true - will change it to false and return true. If current call will not unlock this
* [SmartSemaphore] - false
*/
suspend fun release(permits: Int = 1): Boolean {
val checkedPermits = checkedPermits(permits)
return if (_freePermitsStateFlow.value < this.permits) {
internalChangesMutex.withLock {
if (_freePermitsStateFlow.value < this.permits) {
_freePermitsStateFlow.value = minOf(_freePermitsStateFlow.value + checkedPermits, this.permits)
true
} else {
false
}
}
} else {
false
}
}
}
}
/**
* Will call [SmartSemaphore.Mutable.lock], then execute [action] and return the result after [SmartSemaphore.Mutable.unlock]
*/
@OptIn(ExperimentalContracts::class)
suspend inline fun <T> SmartSemaphore.Mutable.withAcquire(permits: Int = 1, action: () -> T): T {
contract {
callsInPlace(action, InvocationKind.EXACTLY_ONCE)
}
acquire(permits)
try {
return action()
} finally {
release(permits)
}
}
/**
* Will wait until the [SmartSemaphore.permitsStateFlow] of [this] instance will have [permits] count free permits.
*
* Anyway, after the end of this block there are no any guaranties that [SmartSemaphore.freePermits] >= [permits] due to
* the fact that some other parties may lock it again
*/
suspend fun SmartSemaphore.waitRelease(permits: Int = 1) = permitsStateFlow.first { it >= permits }

View File

@@ -0,0 +1,86 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.InternalCoroutinesApi
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.internal.SynchronizedObject
import kotlinx.coroutines.internal.synchronized
/**
* Works like [StateFlow], but guarantee that latest value update will always be delivered to
* each active subscriber
*/
open class SpecialMutableStateFlow<T>(
initialValue: T,
internalScope: CoroutineScope = CoroutineScope(Dispatchers.Default)
) : MutableStateFlow<T>, FlowCollector<T>, MutableSharedFlow<T> {
@OptIn(InternalCoroutinesApi::class)
private val syncObject = SynchronizedObject()
protected val internalSharedFlow: MutableSharedFlow<T> = MutableSharedFlow(
replay = 0,
extraBufferCapacity = 2,
onBufferOverflow = BufferOverflow.DROP_OLDEST
)
protected val publicSharedFlow: MutableSharedFlow<T> = MutableSharedFlow(
replay = 1,
extraBufferCapacity = 1,
onBufferOverflow = BufferOverflow.DROP_OLDEST
)
protected var _value: T = initialValue
override var value: T
get() = _value
set(value) {
doOnChangeAction(value)
}
protected val job = internalSharedFlow.subscribe(internalScope) {
doOnChangeAction(it)
}
override val replayCache: List<T>
get() = publicSharedFlow.replayCache
override val subscriptionCount: StateFlow<Int>
get() = publicSharedFlow.subscriptionCount
@OptIn(InternalCoroutinesApi::class)
override fun compareAndSet(expect: T, update: T): Boolean {
return synchronized(syncObject) {
if (expect == _value && update != _value) {
doOnChangeAction(update)
}
expect == _value
}
}
protected open fun onChangeWithoutSync(value: T) {
_value = value
publicSharedFlow.tryEmit(value)
}
@OptIn(InternalCoroutinesApi::class)
protected open fun doOnChangeAction(value: T) {
synchronized(syncObject) {
if (_value != value) {
onChangeWithoutSync(value)
}
}
}
@ExperimentalCoroutinesApi
override fun resetReplayCache() = publicSharedFlow.resetReplayCache()
override fun tryEmit(value: T): Boolean {
return internalSharedFlow.tryEmit(value)
}
override suspend fun emit(value: T) {
internalSharedFlow.emit(value)
}
override suspend fun collect(collector: FlowCollector<T>) = publicSharedFlow.collect(collector)
}

View File

@@ -0,0 +1,151 @@
import dev.inmo.micro_utils.coroutines.*
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
class SmartRWLockerTests {
@Test
fun compositeTest() {
val locker = SmartRWLocker()
val readAndWriteWorkers = 10
runTest {
var started = 0
var done = 0
val doneMutex = Mutex()
val readWorkers = (0 until readAndWriteWorkers).map {
launch(start = CoroutineStart.LAZY) {
locker.withReadAcquire {
doneMutex.withLock {
started++
}
delay(100L)
doneMutex.withLock {
done++
}
}
}
}
var doneWrites = 0
val writeWorkers = (0 until readAndWriteWorkers).map {
launch(start = CoroutineStart.LAZY) {
locker.withWriteLock {
assertTrue(done == readAndWriteWorkers || started == 0)
delay(10L)
doneWrites++
}
}
}
readWorkers.forEach { it.start() }
writeWorkers.forEach { it.start() }
readWorkers.joinAll()
writeWorkers.joinAll()
assertEquals(expected = readAndWriteWorkers, actual = done)
assertEquals(expected = readAndWriteWorkers, actual = doneWrites)
}
}
@Test
fun simpleWithWriteLockTest() {
val locker = SmartRWLocker()
runTest {
locker.withWriteLock {
assertEquals(0, locker.readSemaphore.freePermits)
assertEquals(true, locker.writeMutex.isLocked)
}
assertEquals(Int.MAX_VALUE, locker.readSemaphore.freePermits)
assertEquals(false, locker.writeMutex.isLocked)
}
}
@Test
fun failureWithWriteLockTest() {
val locker = SmartRWLocker()
val exception = IllegalArgumentException()
try {
runTest {
val subscope = kotlinx.coroutines.CoroutineScope(this.coroutineContext)
var happenException: Throwable? = null
try {
locker.withWriteLock {
val checkFunction = fun (): Deferred<Unit> {
return subscope.async {
assertEquals(0, locker.readSemaphore.freePermits)
assertEquals(true, locker.writeMutex.isLocked)
throw exception
}
}
doInDefault {
assertEquals(0, locker.readSemaphore.freePermits)
assertEquals(true, locker.writeMutex.isLocked)
checkFunction().await()
}
}
} catch (e: Exception) {
happenException = e
}
if (exception != happenException) {
assertEquals(exception, happenException ?.cause)
}
assertEquals(Int.MAX_VALUE, locker.readSemaphore.freePermits)
assertEquals(false, locker.writeMutex.isLocked)
}
} catch (e: Exception) {
assertEquals(exception, e)
}
}
@Test
fun simpleWithReadAcquireTest() {
val locker = SmartRWLocker()
runTest {
locker.withReadAcquire {
assertEquals(Int.MAX_VALUE - 1, locker.readSemaphore.freePermits)
assertEquals(false, locker.writeMutex.isLocked)
locker.withReadAcquire {
assertEquals(Int.MAX_VALUE - 2, locker.readSemaphore.freePermits)
assertEquals(false, locker.writeMutex.isLocked)
}
}
assertEquals(Int.MAX_VALUE, locker.readSemaphore.freePermits)
assertEquals(false, locker.writeMutex.isLocked)
}
}
@Test
fun simple2WithWriteLockTest() {
val locker = SmartRWLocker()
val unlockDelay = 1000L // 1 sec
var unlocked: Boolean = false
runTest {
launch {
locker.withReadAcquire {
delay(unlockDelay)
}
unlocked = true
}
locker.readSemaphore.permitsStateFlow.first { it == Int.MAX_VALUE - 1 }
assertEquals(false, unlocked)
locker.withWriteLock {
assertEquals(true, unlocked)
assertEquals(0, locker.readSemaphore.freePermits)
assertEquals(true, locker.writeMutex.isLocked)
}
assertEquals(Int.MAX_VALUE, locker.readSemaphore.freePermits)
assertEquals(false, locker.writeMutex.isLocked)
}
}
}

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

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

View File

@@ -4,13 +4,14 @@ plugins {
id "com.android.library"
}
apply from: "$mppProjectWithSerializationPresetPath"
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64ProjectPresetPath"
kotlin {
sourceSets {
commonMain {
dependencies {
api project(":micro_utils.common")
api libs.krypto
}
}
jsMain {

View File

@@ -0,0 +1 @@
<manifest/>

View File

@@ -1,6 +1,8 @@
package dev.inmo.micro_utils.crypto
import korlibs.crypto.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

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

View File

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

View File

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

View File

@@ -9,6 +9,7 @@ android {
targetSdkVersion libs.versions.android.props.compileSdk.get().toInteger()
versionCode "${android_code_version}".toInteger()
versionName "$version"
namespace "${project.group}.${project.name}"
}
buildTypes {
release {
@@ -26,11 +27,7 @@ android {
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
}

View File

@@ -13,10 +13,10 @@ repositories {
kotlin {
jvm()
// js(IR) {
// browser()
// nodejs()
// }
js(IR) {
browser()
nodejs()
}
android {}
sourceSets {
@@ -26,44 +26,44 @@ kotlin {
project.parent.subprojects.forEach {
if (
it != project
&& it.hasProperty("kotlin")
&& it.kotlin.sourceSets.any { it.name.contains("commonMain") }
// && it.kotlin.sourceSets.any { it.name.contains("jsMain") }
&& it.kotlin.sourceSets.any { it.name.contains("jvmMain") }
&& it.kotlin.sourceSets.any { it.name.contains("androidMain") }
it != project
&& it.hasProperty("kotlin")
&& it.kotlin.sourceSets.any { it.name.contains("commonMain") }
&& it.kotlin.sourceSets.any { it.name.contains("jsMain") }
&& it.kotlin.sourceSets.any { it.name.contains("jvmMain") }
&& it.kotlin.sourceSets.any { it.name.contains("androidMain") }
) {
api it
}
}
}
}
// jsMain {
// dependencies {
// implementation kotlin('stdlib')
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')
project.parent.subprojects.forEach {
if (
it != project
&& it.hasProperty("kotlin")
&& it.kotlin.sourceSets.any { it.name.contains("commonMain") }
&& it.kotlin.sourceSets.any { it.name.contains("jvmMain") }
it != project
&& it.hasProperty("kotlin")
&& it.kotlin.sourceSets.any { it.name.contains("commonMain") }
&& it.kotlin.sourceSets.any { it.name.contains("jvmMain") }
) {
api it
}
@@ -76,10 +76,10 @@ kotlin {
project.parent.subprojects.forEach {
if (
it != project
&& it.hasProperty("kotlin")
&& it.kotlin.sourceSets.any { it.name.contains("commonMain") }
&& it.kotlin.sourceSets.any { it.name.contains("androidMain") }
it != project
&& it.hasProperty("kotlin")
&& it.kotlin.sourceSets.any { it.name.contains("commonMain") }
&& it.kotlin.sourceSets.any { it.name.contains("androidMain") }
) {
api it
}
@@ -106,7 +106,7 @@ tasks.dokkaHtml {
skipDeprecated.set(true)
sourceLink {
localDirectory.set(file("./"))
localDirectory.set(file("../"))
remoteUrl.set(new URL("https://github.com/InsanusMokrassar/MicroUtils/blob/master/"))
remoteLineSuffix.set("#L")
}
@@ -116,9 +116,9 @@ tasks.dokkaHtml {
sourceRoots.setFrom(findSourcesWithName("commonMain"))
}
// named("jsMain") {
// sourceRoots.setFrom(findSourcesWithName("jsMain", "commonMain"))
// }
named("jsMain") {
sourceRoots.setFrom(findSourcesWithName("jsMain"))
}
named("jvmMain") {
sourceRoots.setFrom(findSourcesWithName("jvmMain"))

View File

@@ -0,0 +1 @@
<manifest/>

View File

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

View File

@@ -19,11 +19,19 @@ allprojects {
}
releaseMode = (project.hasProperty('RELEASE_MODE') && project.property('RELEASE_MODE') == "true") || System.getenv('RELEASE_MODE') == "true"
// String compilerPluginVersionFromProperties = (String) project.properties["compose.kotlinCompilerPluginVersion"]
// String compilerPluginVersionFromLibrariesVersions = libs.versions.compose.kotlin.get()
// composePluginKotlinVersion = compilerPluginVersionFromProperties
// if (compilerPluginVersionFromProperties == null) {
// composePluginKotlinVersion = compilerPluginVersionFromLibrariesVersions
// }
mppProjectWithSerializationPresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerialization.gradle"
mppProjectWithSerializationPresetPath = "${rootProject.projectDir.absolutePath}/mppJvmJsAndroidProject.gradle"
mppProjectWithSerializationAndComposePresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerializationAndCompose.gradle"
mppJavaProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJavaProject.gradle"
mppJsAndJavaProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJsAndJavaProject.gradle"
mppJvmJsLinuxMingwProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJvmJsLinuxMingwProject.gradle"
mppJvmJsLinuxMingwLinuxArm64ProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJvmJsLinuxMingwLinuxArm64Project.gradle"
mppJvmJsAndroidLinuxMingwLinuxArm64ProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJvmJsAndroidLinuxMingwLinuxArm64Project.gradle"
mppAndroidProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppAndroidProject.gradle"
defaultAndroidSettingsPresetPath = "${rootProject.projectDir.absolutePath}/defaultAndroidSettings.gradle"

View File

@@ -4,7 +4,7 @@ plugins {
id "com.android.library"
}
apply from: "$mppProjectWithSerializationPresetPath"
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64ProjectPresetPath"
kotlin {
sourceSets {

View File

@@ -0,0 +1 @@
<manifest/>

View File

@@ -48,8 +48,8 @@ interface DefaultStatesManagerRepo<T : State> {
*/
open class DefaultStatesManager<T : State>(
protected val repo: DefaultStatesManagerRepo<T> = InMemoryDefaultStatesManagerRepo(),
protected val onStartContextsConflictResolver: suspend (current: T, new: T) -> Boolean = { _, _ -> true },
protected val onUpdateContextsConflictResolver: suspend (old: T, new: T, currentNew: T) -> Boolean = { _, _, _ -> true }
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()

View File

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

View File

@@ -4,7 +4,7 @@ plugins {
id "com.android.library"
}
apply from: "$mppProjectWithSerializationPresetPath"
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64ProjectPresetPath"
kotlin {
sourceSets {

View File

@@ -0,0 +1 @@
<manifest/>

View File

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

View File

@@ -3,9 +3,10 @@ org.gradle.parallel=true
kotlin.js.generate.externals=true
kotlin.incremental=true
kotlin.incremental.js=true
#kotlin.experimental.tryK2=true
android.useAndroidX=true
android.enableJetifier=true
org.gradle.jvmargs=-Xmx2g
org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=2g
# JS NPM
@@ -14,5 +15,5 @@ crypto_js_version=4.1.1
# Project data
group=dev.inmo
version=0.17.1
android_code_version=183
version=0.20.21
android_code_version=227

View File

@@ -1,40 +1,45 @@
[versions]
kt = "1.8.10"
kt-serialization = "1.5.0"
kt-coroutines = "1.6.4"
kt = "1.9.21"
kt-serialization = "1.6.2"
kt-coroutines = "1.7.3"
kslog = "1.0.0"
kslog = "1.3.1"
jb-compose = "1.3.1-rc01"
jb-exposed = "0.41.1"
jb-dokka = "1.7.20"
jb-compose = "1.5.11"
jb-exposed = "0.45.0"
jb-dokka = "1.9.10"
klock = "3.4.0"
uuid = "0.7.0"
korlibs = "4.0.10"
uuid = "0.8.2"
ktor = "2.2.4"
ktor = "2.3.7"
gh-release = "2.4.1"
koin = "3.3.2"
koin = "3.5.0"
ksp = "1.8.10-1.0.9"
kotlin-poet = "1.12.0"
okio = "3.6.0"
android-gradle = "7.3.1"
ksp = "1.9.21-1.0.15"
kotlin-poet = "1.15.3"
versions = "0.50.0"
android-gradle = "8.2.0"
dexcount = "4.0.0"
android-coreKtx = "1.9.0"
android-recyclerView = "1.2.1"
android-coreKtx = "1.12.0"
android-recyclerView = "1.3.2"
android-appCompat = "1.6.1"
android-fragment = "1.5.5"
android-fragment = "1.6.2"
android-espresso = "3.5.1"
android-test = "1.1.5"
android-compose-material3 = "1.1.2"
android-props-minSdk = "21"
android-props-compileSdk = "33"
android-props-buildTools = "33.0.1"
android-props-compileSdk = "34"
android-props-buildTools = "34.0.0"
[libraries]
@@ -67,7 +72,8 @@ ktor-server-content-negotiation = { module = "io.ktor:ktor-server-content-negoti
kslog = { module = "dev.inmo:kslog", version.ref = "kslog" }
klock = { module = "com.soywiz.korlibs.klock:klock", version.ref = "klock" }
klock = { module = "com.soywiz.korlibs.klock:klock", version.ref = "korlibs" }
krypto = { module = "com.soywiz.korlibs.krypto:krypto", version.ref = "korlibs" }
uuid = { module = "com.benasher44:uuid", version.ref = "uuid" }
koin = { module = "io.insert-koin:koin-core", version.ref = "koin" }
@@ -78,6 +84,7 @@ jb-exposed = { module = "org.jetbrains.exposed:exposed-core", version.ref = "jb-
android-coreKtx = { module = "androidx.core:core-ktx", version.ref = "android-coreKtx" }
android-recyclerView = { module = "androidx.recyclerview:recyclerview", version.ref = "android-recyclerView" }
android-appCompat-resources = { module = "androidx.appcompat:appcompat-resources", version.ref = "android-appCompat" }
android-compose-material3 = { module = "androidx.compose.material3:material3", version.ref = "android-compose-material3" }
android-fragment = { module = "androidx.fragment:fragment", version.ref = "android-fragment" }
android-espresso = { module = "androidx.test.espresso:espresso-core", version.ref = "android-espresso" }
android-test-junit = { module = "androidx.test.ext:junit", version.ref = "android-test" }
@@ -91,6 +98,8 @@ kt-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref
kotlin-poet = { module = "com.squareup:kotlinpoet-ksp", version.ref = "kotlin-poet" }
ksp = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "ksp" }
okio = { module = "com.squareup.okio:okio", version.ref = "okio" }
# Buildscript
buildscript-kt-gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kt" }
@@ -104,3 +113,5 @@ buildscript-android-dexcount = { module = "com.getkeepsafe.dexcount:dexcount-gra
[plugins]
jb-compose = { id = "org.jetbrains.compose", version.ref = "jb-compose" }
versions = { id = "com.github.ben-manes.versions", version.ref = "versions" }

View File

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

View File

@@ -2,11 +2,11 @@ apply plugin: 'maven-publish'
task javadocJar(type: Jar) {
from javadoc
classifier = 'javadoc'
archiveClassifier = 'javadoc'
}
task sourcesJar(type: Jar) {
from sourceSets.main.allSource
classifier = 'sources'
archiveClassifier = 'sources'
}
publishing {
@@ -68,18 +68,14 @@ publishing {
}
}
if (project.hasProperty('GITEA_TOKEN') || System.getenv('GITEA_TOKEN') != null) {
if ((project.hasProperty('INMONEXUS_USER') || System.getenv('INMONEXUS_USER') != null) && (project.hasProperty('INMONEXUS_PASSWORD') || System.getenv('INMONEXUS_PASSWORD') != null)) {
maven {
name = "Gitea"
url = uri("https://git.inmo.dev/api/packages/InsanusMokrassar/maven")
name = "InmoNexus"
url = uri("https://nexus.inmo.dev/repository/maven-releases/")
credentials(HttpHeaderCredentials) {
name = "Authorization"
value = project.hasProperty('GITEA_TOKEN') ? project.property('GITEA_TOKEN') : System.getenv('GITEA_TOKEN')
}
authentication {
header(HttpHeaderAuthentication)
credentials {
username = project.hasProperty('INMONEXUS_USER') ? project.property('INMONEXUS_USER') : System.getenv('INMONEXUS_USER')
password = project.hasProperty('INMONEXUS_PASSWORD') ? project.property('INMONEXUS_PASSWORD') : System.getenv('INMONEXUS_PASSWORD')
}
}
@@ -115,4 +111,27 @@ if (project.hasProperty("signing.gnupg.keyName")) {
dependsOn(it)
}
}
// Workaround to make android sign operations depend on signing tasks
project.getTasks().withType(AbstractPublishToMaven.class).configureEach {
def signingTasks = project.getTasks().withType(Sign.class)
mustRunAfter(signingTasks)
}
// Workaround to make test tasks use sign
project.getTasks().withType(Sign.class).configureEach { signTask ->
def withoutSign = (signTask.name.startsWith("sign") ? signTask.name.minus("sign") : signTask.name)
def pubName = withoutSign.endsWith("Publication") ? withoutSign.substring(0, withoutSign.length() - "Publication".length()) : withoutSign
// These tasks only exist for native targets, hence findByName() to avoid trying to find them for other targets
// Task ':linkDebugTest<platform>' uses this output of task ':sign<platform>Publication' without declaring an explicit or implicit dependency
def debugTestTask = tasks.findByName("linkDebugTest$pubName")
if (debugTestTask != null) {
signTask.mustRunAfter(debugTestTask)
}
// Task ':compileTestKotlin<platform>' uses this output of task ':sign<platform>Publication' without declaring an explicit or implicit dependency
def testTask = tasks.findByName("compileTestKotlin$pubName")
if (testTask != null) {
signTask.mustRunAfter(testTask)
}
}
}

View File

@@ -1 +1 @@
{"licenses":[{"id":"Apache-2.0","title":"Apache Software License 2.0","url":"https://github.com/InsanusMokrassar/MicroUtils/blob/master/LICENSE"}],"mavenConfig":{"name":"${project.name}","description":"It is set of projects with micro tools for avoiding of routines coding","url":"https://github.com/InsanusMokrassar/MicroUtils/","vcsUrl":"https://github.com/InsanusMokrassar/MicroUtils.git","developers":[{"id":"InsanusMokrassar","name":"Aleksei Ovsiannikov","eMail":"ovsyannikov.alexey95@gmail.com"},{"id":"000Sanya","name":"Syrov Aleksandr","eMail":"000sanya.000sanya@gmail.com"}],"repositories":[{"name":"GithubPackages","url":"https://maven.pkg.github.com/InsanusMokrassar/MicroUtils"},{"name":"Gitea","url":"https://git.inmo.dev/api/packages/InsanusMokrassar/maven","credsType":{"type":"dev.inmo.kmppscriptbuilder.core.models.MavenPublishingRepository.CredentialsType.HttpHeaderCredentials","headerName":"Authorization","headerValueProperty":"GITEA_TOKEN"}},{"name":"sonatype","url":"https://oss.sonatype.org/service/local/staging/deploy/maven2/"}],"gpgSigning":{"type":"dev.inmo.kmppscriptbuilder.core.models.GpgSigning.Optional"}},"type":"JVM"}
{"licenses":[{"id":"Apache-2.0","title":"Apache Software License 2.0","url":"https://github.com/InsanusMokrassar/MicroUtils/blob/master/LICENSE"}],"mavenConfig":{"name":"${project.name}","description":"It is set of projects with micro tools for avoiding of routines coding","url":"https://github.com/InsanusMokrassar/MicroUtils/","vcsUrl":"https://github.com/InsanusMokrassar/MicroUtils.git","developers":[{"id":"InsanusMokrassar","name":"Aleksei Ovsiannikov","eMail":"ovsyannikov.alexey95@gmail.com"},{"id":"000Sanya","name":"Syrov Aleksandr","eMail":"000sanya.000sanya@gmail.com"}],"repositories":[{"name":"GithubPackages","url":"https://maven.pkg.github.com/InsanusMokrassar/MicroUtils"},{"name":"InmoNexus","url":"https://nexus.inmo.dev/repository/maven-releases/"},{"name":"sonatype","url":"https://oss.sonatype.org/service/local/staging/deploy/maven2/"}],"gpgSigning":{"type":"dev.inmo.kmppscriptbuilder.core.models.GpgSigning.Optional"}},"type":"JVM"}

View File

@@ -13,3 +13,8 @@ dependencies {
api libs.kotlin.poet
api libs.ksp
}
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}

View File

@@ -9,19 +9,28 @@ import com.google.devtools.ksp.processing.Resolver
import com.google.devtools.ksp.processing.SymbolProcessor
import com.google.devtools.ksp.symbol.KSAnnotated
import com.google.devtools.ksp.symbol.KSFile
import com.google.devtools.ksp.symbol.Modifier
import com.squareup.kotlinpoet.AnnotationSpec
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.CodeBlock
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.FunSpec
import com.squareup.kotlinpoet.KModifier
import com.squareup.kotlinpoet.ParameterSpec
import com.squareup.kotlinpoet.ParameterizedTypeName
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import com.squareup.kotlinpoet.PropertySpec
import com.squareup.kotlinpoet.TypeName
import com.squareup.kotlinpoet.TypeVariableName
import com.squareup.kotlinpoet.asTypeName
import com.squareup.kotlinpoet.ksp.toClassName
import com.squareup.kotlinpoet.ksp.toTypeName
import com.squareup.kotlinpoet.ksp.writeTo
import dev.inmo.micro_utils.koin.annotations.GenerateGenericKoinDefinition
import dev.inmo.micro_utils.koin.annotations.GenerateKoinDefinition
import org.koin.core.Koin
import org.koin.core.module.Module
import org.koin.core.parameter.ParametersDefinition
import org.koin.core.scope.Scope
import java.io.File
import kotlin.reflect.KClass
@@ -32,11 +41,190 @@ class Processor(
private val definitionClassName = ClassName("org.koin.core.definition", "Definition")
private val koinDefinitionClassName = ClassName("org.koin.core.definition", "KoinDefinition")
private fun FileSpec.Builder.addCodeForType(
targetType: TypeName,
name: String,
nullable: Boolean,
generateSingle: Boolean,
generateFactory: Boolean,
) {
val targetTypeAsGenericType = (targetType as? TypeVariableName) ?.copy(reified = true)
fun addGetterProperty(
receiver: KClass<*>
) {
addProperty(
PropertySpec.builder(
name,
targetType,
).apply {
addKdoc(
"""
@return Definition by key "${name}"
""".trimIndent()
)
getter(
FunSpec.getterBuilder().apply {
targetTypeAsGenericType ?.let {
addModifiers(KModifier.INLINE)
}
addCode(
"return " + (if (nullable) {
"getOrNull"
} else {
"get"
}) + "(named(\"${name}\"))"
)
}.build()
)
targetTypeAsGenericType ?.let {
addTypeVariable(it)
}
receiver(receiver)
}.build()
)
}
if (targetTypeAsGenericType == null) {
addGetterProperty(Scope::class)
addGetterProperty(Koin::class)
}
val parametersDefinitionClassName = ClassName(
"org.koin.core.parameter",
"ParametersDefinition"
)
fun addGetterMethod(
receiver: KClass<*>
) {
addFunction(
FunSpec.builder(
name
).apply {
addKdoc(
"""
@return Definition by key "${name}" with [parameters]
""".trimIndent()
)
receiver(receiver)
addParameter(
ParameterSpec(
"parameters",
parametersDefinitionClassName.let {
if (targetTypeAsGenericType != null) {
it.copy(nullable = true)
} else {
it
}
},
KModifier.NOINLINE
).toBuilder().apply {
if (targetTypeAsGenericType != null) {
defaultValue("null")
}
}.build()
)
addModifiers(KModifier.INLINE)
targetTypeAsGenericType ?.let {
addTypeVariable(it)
returns(it.copy(nullable = nullable))
} ?: returns(targetType)
addCode(
"return " + (if (nullable) {
"getOrNull"
} else {
"get"
}) + "(named(\"${name}\"), parameters)"
)
}.build()
)
}
addGetterMethod(Scope::class)
addGetterMethod(Koin::class)
fun FunSpec.Builder.addDefinitionParameter() {
val definitionModifiers = if (targetTypeAsGenericType == null) {
arrayOf()
} else {
arrayOf(KModifier.NOINLINE)
}
addParameter(
ParameterSpec.builder(
"definition",
definitionClassName.parameterizedBy(targetType.copy(nullable = false)),
*definitionModifiers
).build()
)
}
if (generateSingle) {
fun FunSpec.Builder.configure() {
addKdoc(
"""
Will register [definition] with [org.koin.core.module.Module.single] and key "${name}"
""".trimIndent()
)
receiver(Module::class)
addParameter(
ParameterSpec.builder(
"createdAtStart",
Boolean::class
).apply {
defaultValue("false")
}.build()
)
addDefinitionParameter()
returns(koinDefinitionClassName.parameterizedBy(targetType.copy(nullable = false)))
addCode(
"return single(named(\"${name}\"), createdAtStart = createdAtStart, definition = definition)"
)
targetTypeAsGenericType ?.let {
addTypeVariable(it)
addModifiers(KModifier.INLINE)
}
}
val actualSingleName = "single${name.replaceFirstChar { it.uppercase() }}"
addFunction(
FunSpec.builder(actualSingleName).apply { configure() }.build()
)
}
if (generateFactory) {
fun FunSpec.Builder.configure() {
addKdoc(
"""
Will register [definition] with [org.koin.core.module.Module.factory] and key "${name}"
""".trimIndent()
)
receiver(Module::class)
addDefinitionParameter()
returns(koinDefinitionClassName.parameterizedBy(targetType.copy(nullable = false)))
addCode(
"return factory(named(\"${name}\"), definition = definition)"
)
targetTypeAsGenericType ?.let {
addTypeVariable(it)
addModifiers(KModifier.INLINE)
}
}
val actualFactoryName = "factory${name.replaceFirstChar { it.uppercase() }}"
addFunction(
FunSpec.builder(actualFactoryName).apply { configure() }.build()
)
}
addImport("org.koin.core.qualifier", "named")
}
@OptIn(KspExperimental::class)
override fun process(resolver: Resolver): List<KSAnnotated> {
resolver.getSymbolsWithAnnotation(
(resolver.getSymbolsWithAnnotation(
GenerateKoinDefinition::class.qualifiedName!!
).filterIsInstance<KSFile>().forEach { ksFile ->
) + resolver.getSymbolsWithAnnotation(
GenerateGenericKoinDefinition::class.qualifiedName!!
)).filterIsInstance<KSFile>().forEach { ksFile ->
FileSpec.builder(
ksFile.packageName.asString(),
"GeneratedDefinitions${ksFile.fileName.removeSuffix(".kt")}"
@@ -72,92 +260,12 @@ class Processor(
}.copy(
nullable = it.nullable
)
fun addGetterProperty(
receiver: KClass<*>
) {
addProperty(
PropertySpec.builder(
it.name,
targetType,
).apply {
addKdoc(
"""
@return Definition by key "${it.name}"
""".trimIndent()
)
getter(
FunSpec.getterBuilder().apply {
addCode(
"return " + (if (it.nullable) {
"getOrNull"
} else {
"get"
}) + "(named(\"${it.name}\"))"
)
}.build()
)
receiver(receiver)
}.build()
)
}
addGetterProperty(Scope::class)
addGetterProperty(Koin::class)
if (it.generateSingle) {
addFunction(
FunSpec.builder("${it.name}Single").apply {
addKdoc(
"""
Will register [definition] with [org.koin.core.module.Module.single] and key "${it.name}"
""".trimIndent()
)
receiver(Module::class)
addParameter(
ParameterSpec.builder(
"createdAtStart",
Boolean::class
).apply {
defaultValue("false")
}.build()
)
addParameter(
ParameterSpec.builder(
"definition",
definitionClassName.parameterizedBy(targetType.copy(nullable = false))
).build()
)
returns(koinDefinitionClassName.parameterizedBy(targetType.copy(nullable = false)))
addCode(
"return single(named(\"${it.name}\"), createdAtStart = createdAtStart, definition = definition)"
)
}.build()
)
}
if (it.generateFactory) {
addFunction(
FunSpec.builder("${it.name}Factory").apply {
addKdoc(
"""
Will register [definition] with [org.koin.core.module.Module.factory] and key "${it.name}"
""".trimIndent()
)
receiver(Module::class)
addParameter(
ParameterSpec.builder(
"definition",
definitionClassName.parameterizedBy(targetType.copy(nullable = false))
).build()
)
returns(koinDefinitionClassName.parameterizedBy(targetType.copy(nullable = false)))
addCode(
"return factory(named(\"${it.name}\"), definition = definition)"
)
}.build()
)
}
addImport("org.koin.core.qualifier", "named")
addCodeForType(targetType, it.name, it.nullable, it.generateSingle, it.generateFactory)
}
ksFile.getAnnotationsByType(GenerateGenericKoinDefinition::class).forEach {
val targetType = TypeVariableName("T", Any::class)
addCodeForType(targetType, it.name, it.nullable, it.generateSingle, it.generateFactory)
}
}.build().let {
File(

View File

@@ -0,0 +1 @@
<manifest/>

View File

@@ -3,12 +3,15 @@
// ORIGINAL FILE: Test.kt
package dev.inmo.micro_utils.koin.generator.test
import kotlin.Any
import kotlin.Boolean
import kotlin.Deprecated
import kotlin.String
import org.koin.core.Koin
import org.koin.core.definition.Definition
import org.koin.core.definition.KoinDefinition
import org.koin.core.module.Module
import org.koin.core.parameter.ParametersDefinition
import org.koin.core.qualifier.named
import org.koin.core.scope.Scope
@@ -24,15 +27,98 @@ public val Scope.sampleInfo: Test<String>
public val Koin.sampleInfo: Test<String>
get() = get(named("sampleInfo"))
/**
* @return Definition by key "sampleInfo" with [parameters]
*/
public inline fun Scope.sampleInfo(noinline parameters: ParametersDefinition): Test<String> =
get(named("sampleInfo"), parameters)
/**
* @return Definition by key "sampleInfo" with [parameters]
*/
public inline fun Koin.sampleInfo(noinline parameters: ParametersDefinition): Test<String> =
get(named("sampleInfo"), parameters)
/**
* Will register [definition] with [org.koin.core.module.Module.single] and key "sampleInfo"
*/
@Deprecated(
"This definition is old style and should not be used anymore. Use singleSampleInfo instead",
ReplaceWith("singleSampleInfo"),
)
public fun Module.sampleInfoSingle(createdAtStart: Boolean = false,
definition: Definition<Test<String>>): KoinDefinition<Test<String>> =
single(named("sampleInfo"), createdAtStart = createdAtStart, definition = definition)
/**
* Will register [definition] with [org.koin.core.module.Module.single] and key "sampleInfo"
*/
public fun Module.singleSampleInfo(createdAtStart: Boolean = false,
definition: Definition<Test<String>>): KoinDefinition<Test<String>> =
single(named("sampleInfo"), createdAtStart = createdAtStart, definition = definition)
/**
* Will register [definition] with [org.koin.core.module.Module.factory] and key "sampleInfo"
*/
@Deprecated(
"This definition is old style and should not be used anymore. Use factorySampleInfo instead",
ReplaceWith("factorySampleInfo"),
)
public fun Module.sampleInfoFactory(definition: Definition<Test<String>>):
KoinDefinition<Test<String>> = factory(named("sampleInfo"), definition = definition)
/**
* Will register [definition] with [org.koin.core.module.Module.factory] and key "sampleInfo"
*/
public fun Module.factorySampleInfo(definition: Definition<Test<String>>):
KoinDefinition<Test<String>> = factory(named("sampleInfo"), definition = definition)
/**
* @return Definition by key "test" with [parameters]
*/
public inline fun <reified T : Any> Scope.test(noinline parameters: ParametersDefinition? = null): T
= get(named("test"), parameters)
/**
* @return Definition by key "test" with [parameters]
*/
public inline fun <reified T : Any> Koin.test(noinline parameters: ParametersDefinition? = null): T
= get(named("test"), parameters)
/**
* Will register [definition] with [org.koin.core.module.Module.single] and key "test"
*/
public inline fun <reified T : Any> Module.singleTest(createdAtStart: Boolean = false, noinline
definition: Definition<T>): KoinDefinition<T> = single(named("test"), createdAtStart =
createdAtStart, definition = definition)
/**
* Will register [definition] with [org.koin.core.module.Module.factory] and key "test"
*/
public inline fun <reified T : Any> Module.factoryTest(noinline definition: Definition<T>):
KoinDefinition<T> = factory(named("test"), definition = definition)
/**
* @return Definition by key "testNullable" with [parameters]
*/
public inline fun <reified T : Any> Scope.testNullable(noinline parameters: ParametersDefinition? =
null): T? = getOrNull(named("testNullable"), parameters)
/**
* @return Definition by key "testNullable" with [parameters]
*/
public inline fun <reified T : Any> Koin.testNullable(noinline parameters: ParametersDefinition? =
null): T? = getOrNull(named("testNullable"), parameters)
/**
* Will register [definition] with [org.koin.core.module.Module.single] and key "testNullable"
*/
public inline fun <reified T : Any> Module.singleTestNullable(createdAtStart: Boolean = false,
noinline definition: Definition<T>): KoinDefinition<T> = single(named("testNullable"),
createdAtStart = createdAtStart, definition = definition)
/**
* Will register [definition] with [org.koin.core.module.Module.factory] and key "testNullable"
*/
public inline fun <reified T : Any> Module.factoryTestNullable(noinline definition: Definition<T>):
KoinDefinition<T> = factory(named("testNullable"), definition = definition)

View File

@@ -1,6 +1,9 @@
@file:GenerateKoinDefinition("sampleInfo", Test::class, String::class, nullable = false)
@file:GenerateGenericKoinDefinition("test", nullable = false)
@file:GenerateGenericKoinDefinition("testNullable", nullable = true)
package dev.inmo.micro_utils.koin.generator.test
import dev.inmo.micro_utils.koin.annotations.GenerateGenericKoinDefinition
import dev.inmo.micro_utils.koin.annotations.GenerateKoinDefinition
import org.koin.core.Koin

View File

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

View File

@@ -0,0 +1 @@
<manifest/>

View File

@@ -0,0 +1,40 @@
package dev.inmo.micro_utils.koin
import org.koin.core.Koin
import org.koin.core.definition.BeanDefinition
import org.koin.core.definition.KoinDefinition
import org.koin.core.instance.InstanceFactory
import org.koin.core.parameter.ParametersDefinition
import org.koin.core.scope.Scope
fun <T> Koin.get(definition: BeanDefinition<T>, parameters: ParametersDefinition? = null): T = get(
definition.primaryType,
definition.qualifier,
parameters
)
fun <T> Koin.get(definition: InstanceFactory<T>, parameters: ParametersDefinition? = null): T = get(
definition.beanDefinition,
parameters
)
fun <T> Koin.get(definition: KoinDefinition<T>, parameters: ParametersDefinition? = null): T = get(
definition.factory,
parameters
)
fun <T> Scope.get(definition: BeanDefinition<T>, parameters: ParametersDefinition? = null): T = get(
definition.primaryType,
definition.qualifier,
parameters
)
fun <T> Scope.get(definition: InstanceFactory<T>, parameters: ParametersDefinition? = null): T = get(
definition.beanDefinition,
parameters
)
fun <T> Scope.get(definition: KoinDefinition<T>, parameters: ParametersDefinition? = null): T = get(
definition.factory,
parameters
)

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