mirror of
				https://github.com/InsanusMokrassar/MicroUtils.git
				synced 2025-10-29 19:20:26 +00:00 
			
		
		
		
	Compare commits
	
		
			1 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 3e24cf1768 | 
							
								
								
									
										2
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							| @@ -8,7 +8,7 @@ jobs: | |||||||
|       - uses: actions/checkout@v2 |       - uses: actions/checkout@v2 | ||||||
|       - uses: actions/setup-java@v1 |       - uses: actions/setup-java@v1 | ||||||
|         with: |         with: | ||||||
|           java-version: 17 |           java-version: 11 | ||||||
|       - name: Rewrite version |       - name: Rewrite version | ||||||
|         run: | |         run: | | ||||||
|           branch="`echo "${{ github.ref }}" | grep -o "[^/]*$"`" |           branch="`echo "${{ github.ref }}" | grep -o "[^/]*$"`" | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								.github/workflows/dokka_push.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/dokka_push.yml
									
									
									
									
										vendored
									
									
								
							| @@ -10,7 +10,7 @@ jobs: | |||||||
|       - uses: actions/checkout@v2 |       - uses: actions/checkout@v2 | ||||||
|       - uses: actions/setup-java@v1 |       - uses: actions/setup-java@v1 | ||||||
|         with: |         with: | ||||||
|           java-version: 17 |           java-version: 11 | ||||||
|       - name: Build |       - name: Build | ||||||
|         run: ./gradlew build && ./gradlew dokkaHtml |         run: ./gradlew build && ./gradlew dokkaHtml | ||||||
|       - name: Publish KDocs |       - name: Publish KDocs | ||||||
|   | |||||||
							
								
								
									
										577
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										577
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -1,582 +1,9 @@ | |||||||
| # Changelog | # Changelog | ||||||
|  |  | ||||||
| ## 0.20.39 |  | ||||||
|  |  | ||||||
| * `Versions`: |  | ||||||
|     * `Kotlin`: `1.9.22` -> `1.9.23` |  | ||||||
|     * `Korlibs`: `5.3.2` -> `5.4.0` |  | ||||||
|     * `Okio`: `3.8.0` -> `3.9.0` |  | ||||||
|     * `Compose`: `1.6.0` -> `1.6.1` |  | ||||||
|     * `ComposeMaterial3`: `1.2.0` -> `1.2.1` |  | ||||||
|  |  | ||||||
| ## 0.20.38 |  | ||||||
|  |  | ||||||
| * `Versions`: |  | ||||||
|     * `Ktor`: `2.3.8` -> `2.3.9` |  | ||||||
|  |  | ||||||
| ## 0.20.37 |  | ||||||
|  |  | ||||||
| * `Versions`: |  | ||||||
|     * `Compose`: `1.5.12` -> `1.6.0` |  | ||||||
|     * `Exposed`: `0.47.0` -> `0.48.0` |  | ||||||
|  |  | ||||||
| ## 0.20.36 |  | ||||||
|  |  | ||||||
| * `Versions`: |  | ||||||
|     * `Serialization`: `1.6.2` -> `1.6.3` |  | ||||||
|     * `Korlibs`: `5.3.1` -> `5.3.2` |  | ||||||
| * `Repos`: |  | ||||||
|     * `Cache`: |  | ||||||
|         * Improve work and functionality of `actualizeAll` and subsequent functions |  | ||||||
|         * All internal repos `invalidate`/`actualizeAll` now use common `actualizeAll` functions |  | ||||||
|  |  | ||||||
| ## 0.20.35 |  | ||||||
|  |  | ||||||
| * `Versions`: |  | ||||||
|     * `Coroutines`: `1.7.3` -> `1.8.0` |  | ||||||
|     * `Material3`: `1.1.2` -> `1.2.0` |  | ||||||
|  |  | ||||||
| ## 0.20.34 |  | ||||||
|  |  | ||||||
| * `Repos`: |  | ||||||
|     * `Common`: |  | ||||||
|         * Improve default `set` realization of `KeyValuesRepo` |  | ||||||
|  |  | ||||||
| ## 0.20.33 |  | ||||||
|  |  | ||||||
| * `Colors` |  | ||||||
|     * `Common`: |  | ||||||
|         * Add opportunity to use `HEXAColor` with `ahex` colors |  | ||||||
|  |  | ||||||
| ## 0.20.32 |  | ||||||
|  |  | ||||||
| * `Versions`: |  | ||||||
|     * `Okio`: `3.7.0` -> `3.8.0` |  | ||||||
| * `Resources`: |  | ||||||
|     * Make `StringResource` serializable |  | ||||||
|     * Add several variants of builder usages |  | ||||||
|  |  | ||||||
| ## 0.20.31 |  | ||||||
|  |  | ||||||
| * `Versions`: |  | ||||||
|     * `Ktor`: `2.3.7` -> `2.3.8` |  | ||||||
|  |  | ||||||
| ## 0.20.30 |  | ||||||
|  |  | ||||||
| * `Versions`: |  | ||||||
|     * `Exposed`: `0.46.0` -> `0.47.0` |  | ||||||
|  |  | ||||||
| ## 0.20.29 |  | ||||||
|  |  | ||||||
| * `Versions`: |  | ||||||
|     * `Kotlin`: `1.9.21` -> `1.9.22` |  | ||||||
|     * `Compose`: `1.5.11` -> `1.5.12` |  | ||||||
|     * `Korlibs`: `5.3.0` -> `5.3.1` |  | ||||||
|  |  | ||||||
| ## 0.20.28 |  | ||||||
|  |  | ||||||
| * `Versions`: |  | ||||||
|     * `Kotlin`: `1.9.22` -> `1.9.21` (downgrade) |  | ||||||
|     * `Compose`: `1.6.0-dev13691` -> `1.5.11` (downgrade) |  | ||||||
|  |  | ||||||
| ## 0.20.27 |  | ||||||
|  |  | ||||||
| * `Versions`: |  | ||||||
|     * `Kotlin`: `1.9.21` -> `1.9.22` |  | ||||||
|     * `Compose`: `1.5.11` -> `1.6.0-dev13691` |  | ||||||
|  |  | ||||||
| ## 0.20.26 |  | ||||||
|  |  | ||||||
| * `Versions`: |  | ||||||
|     * `Exposed`: `0.45.0` -> `0.46.0`. **This update brinds new api deprecations in exposed** |  | ||||||
| * `Resources`: |  | ||||||
|     * Add opportunity to get default translation by passing `null` as `IetfLang` argument |  | ||||||
|     * Add several useful extensions to get translations in `JS` target |  | ||||||
|  |  | ||||||
| ## 0.20.25 |  | ||||||
|  |  | ||||||
| * `Colors`: |  | ||||||
|     * `Common`: |  | ||||||
|         * Module inited |  | ||||||
|  |  | ||||||
| ## 0.20.24 |  | ||||||
|  |  | ||||||
| **Since this version depdendencies of klock and krypto replaced with `com.soywiz.korge:korlibs-time` and `com.soywiz.korge:korlibs-crypto`** |  | ||||||
|  |  | ||||||
| * `Versions`: |  | ||||||
|     * `Klock` (since now `KorlibsTime`): `4.0.10` -> `5.3.0` |  | ||||||
|     * `Krypto` (since now `KorlibsCrypto`): `4.0.10` -> `5.3.0` |  | ||||||
| * `Serialization`: |  | ||||||
|     * `Mapper`: |  | ||||||
|         * `Mapper` pass decoder into callback of deserialization strategy |  | ||||||
|         * `Mapper` pass encoder into callback of serialization strategy |  | ||||||
|  |  | ||||||
| ## 0.20.23 |  | ||||||
|  |  | ||||||
| * `Versions`: |  | ||||||
|     * `Koin`: `3.5.0` -> `3.5.3` |  | ||||||
|     * `Okio`: `3.6.0` -> `3.7.0` |  | ||||||
| * `LanguageCodes`: |  | ||||||
|     * Fixes in intermediate language codes (like `Chinese.Hans`) |  | ||||||
|     * Rename `IetfLanguageCode` to `IetfLang` |  | ||||||
|         * Rename all subsequent functions (including serializer) |  | ||||||
|     * New lazy properties `knownLanguageCodesMap`, `knownLanguageCodesMapByLowerCasedKeys` and several others |  | ||||||
|  |  | ||||||
| ## 0.20.22 |  | ||||||
|  |  | ||||||
| * `Common`: |  | ||||||
|     * Add opportunity to create own `Diff` with base constructor |  | ||||||
|  |  | ||||||
| ## 0.20.21 |  | ||||||
|  |  | ||||||
| * `Resources`: |  | ||||||
|     * Inited |  | ||||||
|  |  | ||||||
| ## 0.20.20 |  | ||||||
|  |  | ||||||
| * `Repos`: |  | ||||||
|     * `Exposed`: |  | ||||||
|         * Add opportunity for setup flows in `AbstractExposedCRUDRepo` |  | ||||||
|  |  | ||||||
| ## 0.20.19 |  | ||||||
|  |  | ||||||
| * `Versions`: |  | ||||||
|     * `Ktor`: `2.3.6` -> `2.3.7` |  | ||||||
|  |  | ||||||
| ## 0.20.18 |  | ||||||
|  |  | ||||||
| * `Coroutines`: |  | ||||||
|     * `SpecialMutableStateFlow` now extends `MutableStateFlow` |  | ||||||
|     * `Compose`: |  | ||||||
|         * Deprecate `FlowState` due to its complexity in fixes |  | ||||||
|  |  | ||||||
| ## 0.20.17 |  | ||||||
|  |  | ||||||
| * `Versions`: |  | ||||||
|     * `Serialization`: `1.6.1` -> `1.6.2` |  | ||||||
|  |  | ||||||
| ## 0.20.16 |  | ||||||
|  |  | ||||||
| * `Versions`: |  | ||||||
|     * `Exposed`: `0.44.1` -> `0.45.0` |  | ||||||
| * `Coroutines`: |  | ||||||
|     * Add `SpecialMutableStateFlow` |  | ||||||
|     * `Compose`: |  | ||||||
|         * Add `FlowState` |  | ||||||
|  |  | ||||||
| ## 0.20.15 |  | ||||||
|  |  | ||||||
| * `Versions`: |  | ||||||
|     * `Kotlin`: `1.9.20` -> `1.9.21` |  | ||||||
|     * `KSLog`: `1.3.0` -> `1.3.1` |  | ||||||
|     * `Compose`: `1.5.10` -> `1.5.11` |  | ||||||
|  |  | ||||||
| ## 0.20.14 |  | ||||||
|  |  | ||||||
| * `Versions`: |  | ||||||
|     * `Serialization`: `1.6.0` -> `1.6.1` |  | ||||||
|     * `KSLog`: `1.2.4` -> `1.3.0` |  | ||||||
|  |  | ||||||
| ## 0.20.13 |  | ||||||
|  |  | ||||||
| * `Versions`: |  | ||||||
|     * `Ktor`: `2.3.5` -> `2.3.6` |  | ||||||
|     * `UUID`: `0.8.1` -> `0.8.2` |  | ||||||
|  |  | ||||||
| ## 0.20.12 |  | ||||||
|  |  | ||||||
| **It is experimental migration onto new gradle version. Be careful in use of this version** |  | ||||||
|  |  | ||||||
| **This update have JDK 17 in `compatibility` and `target` versions** |  | ||||||
|  |  | ||||||
| ## 0.20.11 |  | ||||||
|  |  | ||||||
| * `Versions`: |  | ||||||
|     * `Kotlin`: `1.9.20-RC2` -> `1.9.20` |  | ||||||
|     * `Exposed`: `0.44.0` -> `0.44.1` |  | ||||||
|     * `Compose`: `1.5.10-rc02` -> `1.5.10` |  | ||||||
| * `Coroutines`: |  | ||||||
|     * `SmartRWLocker` now will wait first unlock of write mutex for acquiring read |  | ||||||
|  |  | ||||||
| ## 0.20.10 |  | ||||||
|  |  | ||||||
| * `Versions`: |  | ||||||
|     * `Kotlin`: `1.9.20-RC` -> `1.9.20-RC1` |  | ||||||
|     * `KSLog`: `1.2.1` -> `1.2.2` |  | ||||||
|     * `Compose`: `1.5.10-rc01` -> `1.5.10-rc02` |  | ||||||
|     * `RecyclerView`: `1.3.1` -> `1.3.2` |  | ||||||
|  |  | ||||||
| ## 0.20.9 |  | ||||||
|  |  | ||||||
| * Most of common modules now supports `linuxArm64` target |  | ||||||
|  |  | ||||||
| ## 0.20.8 |  | ||||||
|  |  | ||||||
| **THIS VERSION CONTAINS UPDATES OF DEPENDENCIES UP TO RC VERSIONS. USE WITH CAUTION** |  | ||||||
|  |  | ||||||
| * `Versions`: |  | ||||||
|     * `Kotlin`: `1.9.20-Beta2` -> `1.9.20-RC` |  | ||||||
|     * `Compose`: `1.5.10-beta02` -> `1.5.10-rc01` |  | ||||||
|  |  | ||||||
| ## 0.20.7 |  | ||||||
|  |  | ||||||
| **THIS VERSION CONTAINS UPDATES OF DEPENDENCIES UP TO BETA VERSIONS. USE WITH CAUTION** |  | ||||||
|  |  | ||||||
| * `Versions`: |  | ||||||
|     * `Kotlin`: `1.9.10` -> `1.9.20-Beta2` |  | ||||||
|     * `Compose`: `1.5.1` -> `1.5.10-beta02` |  | ||||||
|     * `Exposed`: `0.43.0` -> `0.44.0` |  | ||||||
|     * `Ktor`: `2.3.4` -> `2.3.5` |  | ||||||
|     * `Koin`: `3.4.3` -> `3.5.0` |  | ||||||
|     * `Okio`: `3.5.0` -> `3.6.0` |  | ||||||
|     * `Android Core`: `1.10.1` -> `1.12.0` |  | ||||||
|     * `Android Compose Material`: `1.1.1` -> `1.1.2` |  | ||||||
|  |  | ||||||
| ## 0.20.6 |  | ||||||
|  |  | ||||||
| * `Repos`: |  | ||||||
|     * `Exposed` |  | ||||||
|         * Fixes in exposed key-values repos |  | ||||||
|  |  | ||||||
| ## 0.20.5 |  | ||||||
|  |  | ||||||
| * `Coroutines`: |  | ||||||
|     * Fixes in `SmartRWLocker` |  | ||||||
|  |  | ||||||
| ## 0.20.4 |  | ||||||
|  |  | ||||||
| * `Versions`: |  | ||||||
|     * `Kotlin`: `1.9.0` -> `1.9.10` |  | ||||||
|     * `KSLog`: `1.2.0` -> `1.2.1` |  | ||||||
|     * `Compose`: `1.5.0` -> `1.5.1` |  | ||||||
|     * `UUID`: `0.8.0` -> `0.8.1` |  | ||||||
|  |  | ||||||
| ## 0.20.3 |  | ||||||
|  |  | ||||||
| * `Versions`: |  | ||||||
|     * `Compose`: `1.4.3` -> `1.5.0` |  | ||||||
|     * `Exposed`: `0.42.1` -> `0.43.0` |  | ||||||
|     * `Ktor`: `2.3.3` -> `2.3.4` |  | ||||||
| * `Repos`: |  | ||||||
|     * `Cache`: |  | ||||||
|         * Fixes in locks of caches |  | ||||||
|  |  | ||||||
| ## 0.20.2 |  | ||||||
|  |  | ||||||
| * All main repos uses `SmartRWLocker` |  | ||||||
| * `Versions`: |  | ||||||
|     * `Serialization`: `1.5.1` -> `1.6.0` |  | ||||||
|     * `Exposed`: `0.42.0` -> `0.42.1` |  | ||||||
|     * `Korlibs`: `4.0.9` -> `4.0.10` |  | ||||||
| * `Androis SDK`: `33` -> `34` |  | ||||||
|  |  | ||||||
| ## 0.20.1 |  | ||||||
|  |  | ||||||
| * `SmallTextField`: |  | ||||||
|     * Module is initialized |  | ||||||
| * `Pickers`: |  | ||||||
|     * Module is initialized |  | ||||||
| * `Coroutines`: |  | ||||||
|     * Add `SmartSemaphore` |  | ||||||
|     * Add `SmartRWLocker` |  | ||||||
|  |  | ||||||
| ## 0.20.0 |  | ||||||
|  |  | ||||||
| * `Versions`: |  | ||||||
|     * `Kotlin`: `1.8.22` -> `1.9.0` |  | ||||||
|     * `KSLog`: `1.1.1` -> `1.2.0` |  | ||||||
|     * `Exposed`: `0.41.1` -> `0.42.0` |  | ||||||
|     * `UUID`: `0.7.1` -> `0.8.0` |  | ||||||
|     * `Korlibs`: `4.0.3` -> `4.0.9` |  | ||||||
|     * `Ktor`: `2.3.2` -> `2.3.3` |  | ||||||
|     * `Okio`: `3.4.0` -> `3.5.0` |  | ||||||
|  |  | ||||||
| ## 0.19.9 |  | ||||||
|  |  | ||||||
| * `Versions`: |  | ||||||
|     * `Koin`: `3.4.2` -> `3.4.3` |  | ||||||
| * `Startup`: |  | ||||||
|     * Now it is possible to start application in synchronous way |  | ||||||
|  |  | ||||||
| ## 0.19.8 |  | ||||||
|  |  | ||||||
| * `Versions`: |  | ||||||
|     * `Coroutines`: `1.7.2` -> `1.7.3` |  | ||||||
|     * `Kotlin`: `1.8.20` -> `1.8.22` |  | ||||||
|     * `Compose`: `1.4.1` -> `1.4.3` |  | ||||||
|     * `Okio`: `3.3.0` -> `3.4.0` |  | ||||||
|     * `RecyclerView`: `1.3.0` -> `1.3.1` |  | ||||||
|     * `Fragment`: `1.6.0` -> `1.6.1` |  | ||||||
| * `Repos`: |  | ||||||
|     * Fixes In `KeyValueRepo.clear()` of almost all inheritors of `KeyValueRepo` |  | ||||||
|     * `Cache`: |  | ||||||
|         * All full caches got `skipStartInvalidate` property. By default, this property is `false` and fully caching repos |  | ||||||
|           will be automatically invalidated on start of their work |  | ||||||
|  |  | ||||||
| ## 0.19.7 |  | ||||||
|  |  | ||||||
| * `Versions`: |  | ||||||
|     * `Coroutines`: `1.7.1` -> `1.7.2` |  | ||||||
|  |  | ||||||
| ## 0.19.6 |  | ||||||
|  |  | ||||||
| * `Versions`: |  | ||||||
|     * `Coroutines`: `1.6.4` -> `1.7.1` |  | ||||||
|     * `Ktor`: `2.3.1` -> `2.3.2` |  | ||||||
|     * `Compose`: `1.4.0` -> `1.4.1` |  | ||||||
|  |  | ||||||
| ## 0.19.5 |  | ||||||
|  |  | ||||||
| * `Repos`: |  | ||||||
|     * `Generator`: |  | ||||||
|         * Fixes in new type generation |  | ||||||
|  |  | ||||||
| ## 0.19.4 |  | ||||||
|  |  | ||||||
| * `Versions`: |  | ||||||
|     * `Koin`: `3.4.1` -> `3.4.2` |  | ||||||
|     * `Android Fragments`: `1.5.7` -> `1.6.0` |  | ||||||
| * `Koin` |  | ||||||
|     * `Generator` |  | ||||||
|         * Fixes in new generic generator part |  | ||||||
|  |  | ||||||
| ## 0.19.3 |  | ||||||
|  |  | ||||||
| * `Koin` |  | ||||||
|     * `Generator` |  | ||||||
|         * New getter methods now available with opportunity to use parameters |  | ||||||
|         * Old notation `*Single` and `*Factory` is deprecated since this release. With old |  | ||||||
|           will be generated new `single*` and `factory*` notations for new generations |  | ||||||
|         * Add opportunity to use generic-oriented koin definitions |  | ||||||
|  |  | ||||||
| ## 0.19.2 |  | ||||||
|  |  | ||||||
| * `Versions`: |  | ||||||
|     * `Ktor`: `2.3.0` -> `2.3.1` |  | ||||||
|     * `Koin`: `3.4.0` -> `3.4.1` |  | ||||||
|     * `Uuid`: `0.7.0` -> `0.7.1` |  | ||||||
|  |  | ||||||
| ## 0.19.1 |  | ||||||
|  |  | ||||||
| * `Versions`: |  | ||||||
|     * `Korlibs`: `4.0.1` -> `4.0.3` |  | ||||||
|     * `Kotlin Poet`: `1.13.2` -> `1.14.0` |  | ||||||
|  |  | ||||||
| ## 0.19.0 |  | ||||||
|  |  | ||||||
| * `Versions`: |  | ||||||
|     * `Korlibs`: `3.4.0` -> `4.0.1` |  | ||||||
|  |  | ||||||
| ## 0.18.4 |  | ||||||
|  |  | ||||||
| * `Koin`: |  | ||||||
|     * New extension `lazyInject` |  | ||||||
|  |  | ||||||
| ## 0.18.3 |  | ||||||
|  |  | ||||||
| * `Versions`: |  | ||||||
|     * `Serialization`: `1.5.0` -> `1.5.1` |  | ||||||
|     * `Android Cor Ktx`: `1.10.0` -> `1.10.1` |  | ||||||
|  |  | ||||||
| ## 0.18.2 |  | ||||||
|  |  | ||||||
| * `Startup`: |  | ||||||
|     * Now internal `Json` is fully customizable |  | ||||||
|  |  | ||||||
| ## 0.18.1 |  | ||||||
|  |  | ||||||
| * `Common`: |  | ||||||
|     * Add `MapDiff` |  | ||||||
| * `Coroutines`: |  | ||||||
|     * Add `SmartMutex` |  | ||||||
|  |  | ||||||
| ## 0.18.0 |  | ||||||
|  |  | ||||||
| **ALL PREVIOUSLY DEPRECATED FUNCTIONALITY HAVE BEEN REMOVED** |  | ||||||
|  |  | ||||||
| * `Versions`: |  | ||||||
|     * `Android Fragments`: `1.5.6` -> `1.5.7` |  | ||||||
| * `Ktor`: |  | ||||||
|     * `Server`: |  | ||||||
|         * Now it is possible to take query parameters as list |  | ||||||
| * `Repos`: |  | ||||||
|     * `Common`: |  | ||||||
|         * New `WriteKeyValuesRepo.removeWithValue` |  | ||||||
|     * `Cache`: |  | ||||||
|         * Rename full caching factories functions to `fullyCached` |  | ||||||
|  |  | ||||||
| ## 0.17.8 |  | ||||||
|  |  | ||||||
| * `Versions`: |  | ||||||
|     * `Ktor`: `2.2.4` -> `2.3.0` |  | ||||||
|  |  | ||||||
| ## 0.17.7 |  | ||||||
|  |  | ||||||
| * `Versions`: |  | ||||||
|     * `Android CoreKtx`: `1.9.0` -> `1.10.0` |  | ||||||
| * `Startup`: |  | ||||||
|     * Add support of `linuxX64` and `mingwX64` platforms |  | ||||||
|  |  | ||||||
| ## 0.17.6 |  | ||||||
|  |  | ||||||
| * `Versions`: |  | ||||||
|     * `Kotlin`: `1.8.10` -> `1.8.20` |  | ||||||
|     * `KSLog`: `1.0.0` -> `1.1.1` |  | ||||||
|     * `Compose`: `1.3.1` -> `1.4.0` |  | ||||||
|     * `Koin`: `3.3.2` -> `3.4.0` |  | ||||||
|     * `RecyclerView`: `1.2.1` -> `1.3.0` |  | ||||||
|     * `Fragment`: `1.5.5` -> `1.5.6` |  | ||||||
| * Experimentally (`!!!`) add `linuxX64` and `mingwX64` targets |  | ||||||
|  |  | ||||||
| ## 0.17.5 |  | ||||||
|  |  | ||||||
| * `Common`: |  | ||||||
|     * Conversations of number primitives with bounds care |  | ||||||
| * `Repos`: |  | ||||||
|     * `Common`: |  | ||||||
|         * By default, `getAll` for repos will take all the size of repo as page size |  | ||||||
|         * New extension for all built-in repos `maxPagePagination` |  | ||||||
|     * All the repos got `getAll` functions |  | ||||||
|  |  | ||||||
| ## 0.17.4 |  | ||||||
|  |  | ||||||
| * `Serialization`: |  | ||||||
|     * `Mapper`: |  | ||||||
|         * Module inited |  | ||||||
| * `Versions`: |  | ||||||
|     * `Compose`: `1.3.1-rc02` -> `1.3.1` |  | ||||||
|  |  | ||||||
| ## 0.17.3 |  | ||||||
|  |  | ||||||
| * `Common`: |  | ||||||
|     * Add `fixed` extensions for `Float` and `Double` |  | ||||||
|     * New function `emptyDiff` |  | ||||||
|     * Now you may pass custom `comparisonFun` to all `diff` functions |  | ||||||
|  |  | ||||||
| ## 0.17.2 |  | ||||||
|  |  | ||||||
| * `FSM`: |  | ||||||
|     * `DefaultStatesManager.onUpdateContextsConflictResolver` and `DefaultStatesManager.onStartContextsConflictResolver` now return `false` by default |  | ||||||
|  |  | ||||||
| ## 0.17.1 |  | ||||||
|  |  | ||||||
| * **Hotfix** for absence of jvm dependencies in android modules |  | ||||||
| * `Versions`: |  | ||||||
|     * `Ktor`: `2.2.3` -> `2.2.4` |  | ||||||
|  |  | ||||||
| ## 0.17.0 |  | ||||||
|  |  | ||||||
| * `Versions`: |  | ||||||
|     * `Kotlin`: `1.7.20` -> `1.8.10` |  | ||||||
|     * `Serialization`: `1.4.1` -> `1.5.0` |  | ||||||
|     * `KSLog`: `0.5.4` -> `1.0.0` |  | ||||||
|     * `AppCompat`: `1.6.0` -> `1.6.1` |  | ||||||
|  |  | ||||||
| ## 0.16.13 |  | ||||||
|  |  | ||||||
| * `Repos`: |  | ||||||
|     * `Generator`: |  | ||||||
|         * Module has been created |  | ||||||
|  |  | ||||||
| ## 0.16.12 |  | ||||||
|  |  | ||||||
| * `Repos`: |  | ||||||
|     * `Exposed`: |  | ||||||
|         * `CommonExposedRepo.selectByIds` uses `foldRight` by default instead of raw foreach |  | ||||||
| * `Koin`: |  | ||||||
|     * `Generator`: |  | ||||||
|         * Module has been created |  | ||||||
|  |  | ||||||
| ## 0.16.11 |  | ||||||
|  |  | ||||||
| * `LanguageCodes`: |  | ||||||
|     * In android and JVM targets now available `toJavaLocale` and from Java `Locale` conversations from/to |  | ||||||
|       `IetfLanguageCode` |  | ||||||
|  |  | ||||||
| ## 0.16.10 |  | ||||||
|  |  | ||||||
| * `Repos`: |  | ||||||
|     * `Cache`: |  | ||||||
|         * New transformer type: `ReadCRUDFromKeyValueRepo` |  | ||||||
|         * New transformer type: `ReadKeyValueFromCRUDRepo` |  | ||||||
| * `Pagination`: |  | ||||||
|     * New `paginate` extensions with `reversed` support for `List`/`Set` |  | ||||||
|  |  | ||||||
| ## 0.16.9 |  | ||||||
|  |  | ||||||
| * `Versions`: |  | ||||||
|     * `Koin`: `3.2.2` -> `3.3.2` |  | ||||||
|     * `AppCompat`: `1.5.1` -> `1.6.0` |  | ||||||
| * `Ktor`: |  | ||||||
|     * `Client` |  | ||||||
|         * `HttpResponse.bodyOrNull` now retrieve callback to check if body should be received or null |  | ||||||
|         * New extension `HttpResponse.bodyOrNullOnNoContent` |  | ||||||
|  |  | ||||||
| ## 0.16.8 |  | ||||||
|  |  | ||||||
| * `Versions`: |  | ||||||
|     * `Ktor`: `2.2.2` -> `2.2.3` |  | ||||||
| * `Ktor`: |  | ||||||
|     * `Client` |  | ||||||
|         * Fixes in `HttpClient.uniUpload` |  | ||||||
|     * `Server` |  | ||||||
|         * Fixes in `PartData.FileItem.download` |  | ||||||
| * `Repos`: |  | ||||||
|     * `Cache`: |  | ||||||
|         * New type of caches: `FallbackCacheRepo` |  | ||||||
|         * Fixes in `Write*` variants of cached repos |  | ||||||
|         * New type `ActionWrapper` |  | ||||||
|         * New `AutoRecache*` classes for all types of repos as `FallbackCacheRepo`s |  | ||||||
|     * `Common`: |  | ||||||
|         * New transformations for key-value and key-values vice-verse |  | ||||||
|         * Fixes in `FileReadKeyValueRepo` |  | ||||||
|  |  | ||||||
| ## 0.16.7 |  | ||||||
|  |  | ||||||
| * `Common`: |  | ||||||
|     * New extensions `ifTrue`/`ifFalse`/`alsoIfTrue`/`alsoIfFalse`/`letIfTrue`/`letIfFalse` |  | ||||||
|     * `Diff` now is serializable |  | ||||||
|     * Add `IndexedValue` serializer |  | ||||||
|     * `repeatOnFailure` extending: now you may pass any lambda to check if continue to try/do something |  | ||||||
|     * `Compose`: |  | ||||||
|         * New extension `MutableState.asState` |  | ||||||
| * `Coroutines`: |  | ||||||
|     * `Compose`: |  | ||||||
|         * All the `Flow` conversations to compose `State`/`MutableState`/`SnapshotStateList`/`List` got several new |  | ||||||
|         parameters |  | ||||||
|         * `Flow.toMutableState` now is deprecated in favor to `asMutableComposeState` |  | ||||||
| * `Repos`: |  | ||||||
|     * `Cache`: |  | ||||||
|         * New type `FullCacheRepo` |  | ||||||
|         * New type `CommonCacheRepo` |  | ||||||
|         * `CacheRepo` got `invalidate` method. It will fully reload `FullCacheRepo` and just clear `CommonCacheRepo` |  | ||||||
|         * New extensions `KVCache.actualizeAll` |  | ||||||
|  |  | ||||||
| ## 0.16.6 |  | ||||||
|  |  | ||||||
| * `Startup`: |  | ||||||
|     * `Launcher`: |  | ||||||
|         * Improvements in `StartLauncherPlugin#start` methods |  | ||||||
|         * Add opportunity to pass second argument on `JVM` platform as log level |  | ||||||
| * `Repos`: |  | ||||||
|     * `Ktor`: |  | ||||||
|         * `Client`: |  | ||||||
|             * All clients repos got opportunity to customize their flows |  | ||||||
|     * `Exposed`: |  | ||||||
|         * Extensions `eqOrIsNull` and `neqOrIsNotNull` for `Column` |  | ||||||
|  |  | ||||||
| ## 0.16.5 |  | ||||||
|  |  | ||||||
| * `Versions`: |  | ||||||
|     * `Ktor`: `2.2.1` -> `2.2.2` |  | ||||||
|  |  | ||||||
| ## 0.16.4 |  | ||||||
|  |  | ||||||
| * `Coroutines`: |  | ||||||
|     * Create `launchInCurrentThread` |  | ||||||
|  |  | ||||||
| ## 0.16.3 | ## 0.16.3 | ||||||
|  |  | ||||||
|  | * `Coroutines`: | ||||||
|  |     * Create `launchInCurrentThread` | ||||||
| * `Startup`: | * `Startup`: | ||||||
|     * `Launcher`: |     * `Launcher`: | ||||||
|         * All starting API have been moved into `StartLauncherPlugin` and do not require serialize/deserialize cycle for now |         * All starting API have been moved into `StartLauncherPlugin` and do not require serialize/deserialize cycle for now | ||||||
|   | |||||||
| @@ -1 +0,0 @@ | |||||||
| <manifest/> |  | ||||||
							
								
								
									
										1
									
								
								android/alerts/common/src/main/AndroidManifest.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								android/alerts/common/src/main/AndroidManifest.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | <manifest package="dev.inmo.micro_utils.android.alerts.common"/> | ||||||
| @@ -1 +0,0 @@ | |||||||
| <manifest/> |  | ||||||
							
								
								
									
										1
									
								
								android/alerts/recyclerview/src/main/AndroidManifest.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								android/alerts/recyclerview/src/main/AndroidManifest.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | <manifest package="dev.inmo.micro_utils.android.alerts.recyclerview"/> | ||||||
| @@ -1,18 +0,0 @@ | |||||||
| 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") |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1 +0,0 @@ | |||||||
| <manifest/> |  | ||||||
| @@ -1,27 +0,0 @@ | |||||||
| package dev.inmo.micro_utils.android.pickers |  | ||||||
|  |  | ||||||
| import androidx.compose.animation.core.* |  | ||||||
|  |  | ||||||
| internal suspend fun Animatable<Float, AnimationVector1D>.fling( |  | ||||||
|     initialVelocity: Float, |  | ||||||
|     animationSpec: DecayAnimationSpec<Float>, |  | ||||||
|     adjustTarget: ((Float) -> Float)?, |  | ||||||
|     block: (Animatable<Float, AnimationVector1D>.() -> Unit)? = null, |  | ||||||
| ): AnimationResult<Float, AnimationVector1D> { |  | ||||||
|     val targetValue = animationSpec.calculateTargetValue(value, initialVelocity) |  | ||||||
|     val adjustedTarget = adjustTarget?.invoke(targetValue) |  | ||||||
|  |  | ||||||
|     return if (adjustedTarget != null) { |  | ||||||
|         animateTo( |  | ||||||
|             targetValue = adjustedTarget, |  | ||||||
|             initialVelocity = initialVelocity, |  | ||||||
|             block = block |  | ||||||
|         ) |  | ||||||
|     } else { |  | ||||||
|         animateDecay( |  | ||||||
|             initialVelocity = initialVelocity, |  | ||||||
|             animationSpec = animationSpec, |  | ||||||
|             block = block, |  | ||||||
|         ) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,222 +0,0 @@ | |||||||
| package dev.inmo.micro_utils.android.pickers |  | ||||||
|  |  | ||||||
| import androidx.compose.animation.core.Animatable |  | ||||||
| import androidx.compose.animation.core.exponentialDecay |  | ||||||
| import androidx.compose.foundation.clickable |  | ||||||
| import androidx.compose.foundation.gestures.* |  | ||||||
| import androidx.compose.foundation.layout.* |  | ||||||
| import androidx.compose.foundation.text.KeyboardActions |  | ||||||
| import androidx.compose.foundation.text.KeyboardOptions |  | ||||||
| import androidx.compose.material.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) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| @@ -1,156 +0,0 @@ | |||||||
| package dev.inmo.micro_utils.android.pickers |  | ||||||
|  |  | ||||||
| import androidx.compose.animation.core.Animatable |  | ||||||
| import androidx.compose.animation.core.exponentialDecay |  | ||||||
| import androidx.compose.foundation.gestures.* |  | ||||||
| import androidx.compose.foundation.layout.* |  | ||||||
| import androidx.compose.foundation.rememberScrollState |  | ||||||
| import androidx.compose.material.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) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1 +0,0 @@ | |||||||
| <manifest/> |  | ||||||
							
								
								
									
										1
									
								
								android/recyclerview/src/main/AndroidManifest.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								android/recyclerview/src/main/AndroidManifest.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | <manifest package="dev.inmo.micro_utils.android.recyclerview"/> | ||||||
| @@ -1,18 +0,0 @@ | |||||||
| 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 |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1 +0,0 @@ | |||||||
| <manifest/> |  | ||||||
| @@ -1,66 +0,0 @@ | |||||||
| package dev.inmo.micro_utils.android.smalltextfield |  | ||||||
|  |  | ||||||
| import androidx.compose.foundation.interaction.MutableInteractionSource |  | ||||||
| import androidx.compose.foundation.layout.Box |  | ||||||
| import androidx.compose.foundation.layout.Row |  | ||||||
| import androidx.compose.foundation.layout.defaultMinSize |  | ||||||
| import androidx.compose.foundation.text.BasicTextField |  | ||||||
| import androidx.compose.foundation.text.KeyboardActions |  | ||||||
| import androidx.compose.foundation.text.KeyboardOptions |  | ||||||
| import androidx.compose.foundation.text.selection.LocalTextSelectionColors |  | ||||||
| import androidx.compose.material3.* |  | ||||||
| import androidx.compose.runtime.Composable |  | ||||||
| import androidx.compose.runtime.CompositionLocalProvider |  | ||||||
| import androidx.compose.runtime.remember |  | ||||||
| import androidx.compose.ui.Alignment |  | ||||||
| import androidx.compose.ui.Modifier |  | ||||||
| import androidx.compose.ui.graphics.Color |  | ||||||
| import androidx.compose.ui.graphics.Shape |  | ||||||
| import androidx.compose.ui.graphics.SolidColor |  | ||||||
| import androidx.compose.ui.graphics.takeOrElse |  | ||||||
| import androidx.compose.ui.text.TextStyle |  | ||||||
| import androidx.compose.ui.text.input.VisualTransformation |  | ||||||
|  |  | ||||||
| @OptIn(ExperimentalMaterial3Api::class) |  | ||||||
| @Composable |  | ||||||
| fun SmallTextField( |  | ||||||
|     value: String, |  | ||||||
|     onValueChange: (String) -> Unit, |  | ||||||
|     modifier: Modifier = Modifier, |  | ||||||
|     enabled: Boolean = true, |  | ||||||
|     readOnly: Boolean = false, |  | ||||||
|     textStyle: TextStyle = LocalTextStyle.current, |  | ||||||
|     textColor: Color = textStyle.color.takeOrElse { |  | ||||||
|         LocalContentColor.current |  | ||||||
|     }, |  | ||||||
|     visualTransformation: VisualTransformation = VisualTransformation.None, |  | ||||||
|     keyboardOptions: KeyboardOptions = KeyboardOptions.Default, |  | ||||||
|     keyboardActions: KeyboardActions = KeyboardActions.Default, |  | ||||||
|     singleLine: Boolean = false, |  | ||||||
|     maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE, |  | ||||||
|     minLines: Int = 1, |  | ||||||
|     interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, |  | ||||||
| ) { |  | ||||||
|     BasicTextField( |  | ||||||
|         value = value, |  | ||||||
|         modifier = modifier, |  | ||||||
|         onValueChange = onValueChange, |  | ||||||
|         enabled = enabled, |  | ||||||
|         readOnly = readOnly, |  | ||||||
|         textStyle = textStyle.copy( |  | ||||||
|             color = textColor |  | ||||||
|         ), |  | ||||||
|         visualTransformation = visualTransformation, |  | ||||||
|         keyboardOptions = keyboardOptions, |  | ||||||
|         keyboardActions = keyboardActions, |  | ||||||
|         interactionSource = interactionSource, |  | ||||||
|         singleLine = singleLine, |  | ||||||
|         maxLines = maxLines, |  | ||||||
|         minLines = minLines, |  | ||||||
|         cursorBrush = SolidColor( |  | ||||||
|             textStyle.color.takeOrElse { |  | ||||||
|                 LocalContentColor.current |  | ||||||
|             } |  | ||||||
|         ) |  | ||||||
|     ) |  | ||||||
| } |  | ||||||
| @@ -9,7 +9,6 @@ buildscript { | |||||||
|     dependencies { |     dependencies { | ||||||
|         classpath libs.buildscript.kt.gradle |         classpath libs.buildscript.kt.gradle | ||||||
|         classpath libs.buildscript.kt.serialization |         classpath libs.buildscript.kt.serialization | ||||||
|         classpath libs.buildscript.kt.ksp |  | ||||||
|         classpath libs.buildscript.jb.dokka |         classpath libs.buildscript.jb.dokka | ||||||
|         classpath libs.buildscript.gh.release |         classpath libs.buildscript.gh.release | ||||||
|         classpath libs.buildscript.android.gradle |         classpath libs.buildscript.android.gradle | ||||||
| @@ -17,17 +16,12 @@ buildscript { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| plugins { |  | ||||||
|     alias(libs.plugins.versions) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| allprojects { | allprojects { | ||||||
|     repositories { |     repositories { | ||||||
|         mavenLocal() |         mavenLocal() | ||||||
|         mavenCentral() |         mavenCentral() | ||||||
|         google() |         google() | ||||||
|         maven { url "https://maven.pkg.jetbrains.space/public/p/compose/dev" } |         maven { url "https://maven.pkg.jetbrains.space/public/p/compose/dev" } | ||||||
|         maven { url "https://nexus.inmo.dev/repository/maven-releases/" } |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // temporal crutch until legacy tests will be stabled or legacy target will be removed |     // temporal crutch until legacy tests will be stabled or legacy target will be removed | ||||||
| @@ -42,4 +36,3 @@ allprojects { | |||||||
|  |  | ||||||
| apply from: "./extensions.gradle" | apply from: "./extensions.gradle" | ||||||
| apply from: "./github_release.gradle" | apply from: "./github_release.gradle" | ||||||
| apply from: "./versions_plugin_setup.gradle" |  | ||||||
|   | |||||||
| @@ -1,7 +0,0 @@ | |||||||
| plugins { |  | ||||||
|     id "org.jetbrains.kotlin.multiplatform" |  | ||||||
|     id "org.jetbrains.kotlin.plugin.serialization" |  | ||||||
|     id "com.android.library" |  | ||||||
| } |  | ||||||
|  |  | ||||||
| apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64ProjectPresetPath" |  | ||||||
| @@ -1,174 +0,0 @@ | |||||||
| package dev.inmo.micro_utils.colors.common |  | ||||||
|  |  | ||||||
| import kotlinx.serialization.Serializable |  | ||||||
| import kotlin.jvm.JvmInline |  | ||||||
| import kotlin.math.floor |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Wrapper for RGBA colors. Receiving [UInt] in main constructor. Each part in main constructor |  | ||||||
|  * configured with `0x00 - 0xff` range. Examples: |  | ||||||
|  * |  | ||||||
|  * * Red: `0xff0000ffu` |  | ||||||
|  * * Red (0.5 capacity): `0xff000088u` |  | ||||||
|  * |  | ||||||
|  * Anyway it is recommended to use |  | ||||||
|  * |  | ||||||
|  * @param hexaUInt rgba [UInt] in format `0xFFEEBBAA` where FF - red, EE - green, BB - blue` and AA - alpha |  | ||||||
|  */ |  | ||||||
| @Serializable |  | ||||||
| @JvmInline |  | ||||||
| value class HEXAColor ( |  | ||||||
|     val hexaUInt: UInt |  | ||||||
| ) : Comparable<HEXAColor> { |  | ||||||
|     /** |  | ||||||
|      * @returns [hexaUInt] as a string with format `#FFEEBBAA` where FF - red, EE - green, BB - blue and AA - alpha |  | ||||||
|      */ |  | ||||||
|     val hexa: String |  | ||||||
|         get() = "#${hexaUInt.toString(16).padStart(8, '0')}" |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * @returns [hexaUInt] as a string with format `#FFEEBB` where FF - red, EE - green and BB - blue |  | ||||||
|      */ |  | ||||||
|     val hex: String |  | ||||||
|         get() = hexa.take(7) |  | ||||||
|     /** |  | ||||||
|      * @returns [hexaUInt] as a string with format `#AAFFEEBB` where AA - alpha, FF - red, EE - green and BB - blue |  | ||||||
|      */ |  | ||||||
|     val ahex: String |  | ||||||
|         get() = "#${a.toString(16).padStart(2, '2')}${hex.drop(1)}" |  | ||||||
|     val rgba: String |  | ||||||
|         get() = "rgba($r,$g,$b,${aOfOne.toString().take(5)})" |  | ||||||
|     val rgb: String |  | ||||||
|         get() = "rgb($r,$g,$b)" |  | ||||||
|     val shortHex: String |  | ||||||
|         get() = "#${r.shortPart()}${g.shortPart()}${b.shortPart()}" |  | ||||||
|     val shortHexa: String |  | ||||||
|         get() = "$shortHex${a.shortPart()}" |  | ||||||
|     val rgbUInt: UInt |  | ||||||
|         get() = (hexaUInt / 256u) |  | ||||||
|     val rgbInt: Int |  | ||||||
|         get() = rgbUInt.toInt() |  | ||||||
|     val ahexUInt |  | ||||||
|         get() = (a * 0x1000000).toUInt() + rgbUInt |  | ||||||
|  |  | ||||||
|     val r: Int |  | ||||||
|         get() = ((hexaUInt and 0xff000000u) / 0x1000000u).toInt() |  | ||||||
|     val g: Int |  | ||||||
|         get() = ((hexaUInt and 0x00ff0000u) / 0x10000u).toInt() |  | ||||||
|     val b: Int |  | ||||||
|         get() = ((hexaUInt and 0x0000ff00u) / 0x100u).toInt() |  | ||||||
|     val a: Int |  | ||||||
|         get() = ((hexaUInt and 0x000000ffu)).toInt() |  | ||||||
|     val aOfOne: Float |  | ||||||
|         get() = a.toFloat() / (0xff) |  | ||||||
|     init { |  | ||||||
|         require(hexaUInt in 0u ..0xffffffffu) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     constructor(r: Int, g: Int, b: Int, a: Int) : this( |  | ||||||
|         ((r * 0x1000000).toLong() + g * 0x10000 + b * 0x100 + a).toUInt() |  | ||||||
|     ) { |  | ||||||
|         require(r in 0 ..0xff) |  | ||||||
|         require(g in 0 ..0xff) |  | ||||||
|         require(b in 0 ..0xff) |  | ||||||
|         require(a in 0 ..0xff) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     constructor(r: Int, g: Int, b: Int, aOfOne: Float = 1f) : this( |  | ||||||
|         r = r, g = g, b = b, a = (aOfOne * 0xff).toInt() |  | ||||||
|     ) |  | ||||||
|  |  | ||||||
|     override fun toString(): String { |  | ||||||
|         return hexa |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun compareTo(other: HEXAColor): Int = (hexaUInt - other.hexaUInt).coerceIn(Int.MIN_VALUE.toUInt(), Int.MAX_VALUE.toLong().toUInt()).toInt() |  | ||||||
|  |  | ||||||
|     fun copy( |  | ||||||
|         r: Int = this.r, |  | ||||||
|         g: Int = this.g, |  | ||||||
|         b: Int = this.b, |  | ||||||
|         aOfOne: Float = this.aOfOne |  | ||||||
|     ) = HEXAColor(r = r, g = g, b = b, aOfOne = aOfOne) |  | ||||||
|     fun copy( |  | ||||||
|         r: Int = this.r, |  | ||||||
|         g: Int = this.g, |  | ||||||
|         b: Int = this.b, |  | ||||||
|         a: Int |  | ||||||
|     ) = HEXAColor(r = r, g = g, b = b, a = a) |  | ||||||
|  |  | ||||||
|     companion object { |  | ||||||
|         /** |  | ||||||
|          * Parsing color from [color] |  | ||||||
|          * |  | ||||||
|          * Supported formats samples (on Red color based): |  | ||||||
|          * |  | ||||||
|          * * `#f00` |  | ||||||
|          * * `#f00f` |  | ||||||
|          * * `#ff0000` |  | ||||||
|          * * `#ff0000ff` |  | ||||||
|          * * `rgb(255, 0, 0)` |  | ||||||
|          * * `rgba(255, 0, 0, 1)` |  | ||||||
|          */ |  | ||||||
|         fun parseStringColor(color: String): HEXAColor = when { |  | ||||||
|             color.startsWith("#") -> color.removePrefix("#").let { color -> |  | ||||||
|                 when (color.length) { |  | ||||||
|                     3 -> color.map { "$it$it" }.joinToString(separator = "", postfix = "ff") |  | ||||||
|                     4 -> color.take(3).map { "$it$it" }.joinToString(separator = "", postfix = color.takeLast(1).let { "${it}0" }) |  | ||||||
|                     6 -> "${color}ff" |  | ||||||
|                     8 -> color |  | ||||||
|                     else -> error("Malfurmed color string: $color. It is expected that color started with # will contains 3, 6 or 8 valuable parts") |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             color.startsWith("rgb(") -> color |  | ||||||
|                 .removePrefix("rgb(") |  | ||||||
|                 .removeSuffix(")") |  | ||||||
|                 .replace(Regex("\\s"), "") |  | ||||||
|                 .split(",") |  | ||||||
|                 .joinToString("", postfix = "ff") { |  | ||||||
|                     it.toInt().toString(16).padStart(2, '0') |  | ||||||
|                 } |  | ||||||
|             color.startsWith("rgba(") -> color |  | ||||||
|                 .removePrefix("rgba(") |  | ||||||
|                 .removeSuffix(")") |  | ||||||
|                 .replace(Regex("\\s"), "") |  | ||||||
|                 .split(",").let { |  | ||||||
|                     it.take(3).map { it.toInt().toString(16).padStart(2, '0') } + (it.last().toFloat() * 0xff).toInt().toString(16).padStart(2, '0') |  | ||||||
|                 } |  | ||||||
|                 .joinToString("") |  | ||||||
|             else -> color |  | ||||||
|         }.lowercase().toUInt(16).let(::HEXAColor) |  | ||||||
|  |  | ||||||
|         /** |  | ||||||
|          * Creates [HEXAColor] from [uint] presume it is in format `0xFFEEBBAA` where FF - red, EE - green, BB - blue` and AA - alpha |  | ||||||
|          */ |  | ||||||
|         fun fromHexa(uint: UInt) = HEXAColor(uint) |  | ||||||
|  |  | ||||||
|         /** |  | ||||||
|          * Creates [HEXAColor] from [uint] presume it is in format `0xAAFFEEBB` where AA - alpha, FF - red, EE - green and BB - blue` |  | ||||||
|          */ |  | ||||||
|         fun fromAhex(uint: UInt) = HEXAColor( |  | ||||||
|             a = ((uint and 0xff000000u) / 0x1000000u).toInt(), |  | ||||||
|             r = ((uint and 0x00ff0000u) / 0x10000u).toInt(), |  | ||||||
|             g = ((uint and 0x0000ff00u) / 0x100u).toInt(), |  | ||||||
|             b = ((uint and 0x000000ffu)).toInt() |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|         /** |  | ||||||
|          * Parsing color from [color] |  | ||||||
|          * |  | ||||||
|          * Supported formats samples (on Red color based): |  | ||||||
|          * |  | ||||||
|          * * `#f00` |  | ||||||
|          * * `#ff0000` |  | ||||||
|          * * `#ff0000ff` |  | ||||||
|          * * `rgb(255, 0, 0)` |  | ||||||
|          * * `rgba(255, 0, 0, 1)` |  | ||||||
|          */ |  | ||||||
|         operator fun invoke(color: String) = parseStringColor(color) |  | ||||||
|  |  | ||||||
|         private fun Int.shortPart(): String { |  | ||||||
|             return (floor(toFloat() / 16)).toInt().toString(16) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,209 +0,0 @@ | |||||||
| package dev.inmo.micro_utils.colors.common |  | ||||||
|  |  | ||||||
| import kotlin.math.floor |  | ||||||
| import kotlin.test.Test |  | ||||||
| import kotlin.test.assertEquals |  | ||||||
| import kotlin.test.assertTrue |  | ||||||
|  |  | ||||||
| class HexColorTests { |  | ||||||
|     val alphaRgbaPrecision = 5 |  | ||||||
|     class TestColor( |  | ||||||
|         val color: HEXAColor, |  | ||||||
|         val shortHex: String, |  | ||||||
|         val shortHexa: String, |  | ||||||
|         val hex: String, |  | ||||||
|         val hexa: String, |  | ||||||
|         val ahex: String, |  | ||||||
|         val ahexUInt: UInt, |  | ||||||
|         val rgbUInt: UInt, |  | ||||||
|         val rgb: String, |  | ||||||
|         val rgba: String, |  | ||||||
|         val r: Int, |  | ||||||
|         val g: Int, |  | ||||||
|         val b: Int, |  | ||||||
|         val a: Int, |  | ||||||
|         vararg val additionalRGBAVariants: String |  | ||||||
|     ) |  | ||||||
|     val testColors: List<TestColor> |  | ||||||
|         get() = listOf( |  | ||||||
|             TestColor( |  | ||||||
|                 color = HEXAColor(hexaUInt = 0xff0000ffu), |  | ||||||
|                 shortHex = "#f00", |  | ||||||
|                 shortHexa = "#f00f", |  | ||||||
|                 hex = "#ff0000", |  | ||||||
|                 hexa = "#ff0000ff", |  | ||||||
|                 ahex = "#ffff0000", |  | ||||||
|                 ahexUInt = 0xffff0000u, |  | ||||||
|                 rgbUInt = 0xff0000u, |  | ||||||
|                 rgb = "rgb(255,0,0)", |  | ||||||
|                 rgba = "rgba(255,0,0,1.0)", |  | ||||||
|                 r = 0xff, |  | ||||||
|                 g = 0x00, |  | ||||||
|                 b = 0x00, |  | ||||||
|                 a = 0xff, |  | ||||||
|                 "rgba(255,0,0,1)", |  | ||||||
|             ), |  | ||||||
|             TestColor( |  | ||||||
|                 color = HEXAColor(hexaUInt = 0x00ff00ffu), |  | ||||||
|                 shortHex = "#0f0", |  | ||||||
|                 shortHexa = "#0f0f", |  | ||||||
|                 hex = "#00ff00", |  | ||||||
|                 hexa = "#00ff00ff", |  | ||||||
|                 ahex = "#ff00ff00", |  | ||||||
|                 ahexUInt = 0xff00ff00u, |  | ||||||
|                 rgbUInt = 0x00ff00u, |  | ||||||
|                 rgb = "rgb(0,255,0)", |  | ||||||
|                 rgba = "rgba(0,255,0,1.0)", |  | ||||||
|                 r = 0x00, |  | ||||||
|                 g = 0xff, |  | ||||||
|                 b = 0x00, |  | ||||||
|                 a = 0xff, |  | ||||||
|                 "rgba(0,255,0,1)" |  | ||||||
|             ), |  | ||||||
|             TestColor( |  | ||||||
|                 color = HEXAColor(0x0000ffffu), |  | ||||||
|                 shortHex = "#00f", |  | ||||||
|                 shortHexa = "#00ff", |  | ||||||
|                 hex = "#0000ff", |  | ||||||
|                 hexa = "#0000ffff", |  | ||||||
|                 ahex = "#ff0000ff", |  | ||||||
|                 ahexUInt = 0xff0000ffu, |  | ||||||
|                 rgbUInt = 0x0000ffu, |  | ||||||
|                 rgb = "rgb(0,0,255)", |  | ||||||
|                 rgba = "rgba(0,0,255,1.0)", |  | ||||||
|                 r = 0x00, |  | ||||||
|                 g = 0x00, |  | ||||||
|                 b = 0xff, |  | ||||||
|                 a = 0xff, |  | ||||||
|                 "rgba(0,0,255,1)" |  | ||||||
|             ), |  | ||||||
|             TestColor( |  | ||||||
|                 color = HEXAColor(0xff000088u), |  | ||||||
|                 shortHex = "#f00", |  | ||||||
|                 shortHexa = "#f008", |  | ||||||
|                 hex = "#ff0000", |  | ||||||
|                 hexa = "#ff000088", |  | ||||||
|                 ahex = "#88ff0000", |  | ||||||
|                 ahexUInt = 0x88ff0000u, |  | ||||||
|                 rgbUInt = 0xff0000u, |  | ||||||
|                 rgb = "rgb(255,0,0)", |  | ||||||
|                 rgba = "rgba(255,0,0,0.533)", |  | ||||||
|                 r = 0xff, |  | ||||||
|                 g = 0x00, |  | ||||||
|                 b = 0x00, |  | ||||||
|                 a = 0x88, |  | ||||||
|             ), |  | ||||||
|             TestColor( |  | ||||||
|                 color = HEXAColor(0x00ff0088u), |  | ||||||
|                 shortHex = "#0f0", |  | ||||||
|                 shortHexa = "#0f08", |  | ||||||
|                 hex = "#00ff00", |  | ||||||
|                 hexa = "#00ff0088", |  | ||||||
|                 ahex = "#8800ff00", |  | ||||||
|                 ahexUInt = 0x8800ff00u, |  | ||||||
|                 rgbUInt = 0x00ff00u, |  | ||||||
|                 rgb = "rgb(0,255,0)", |  | ||||||
|                 rgba = "rgba(0,255,0,0.533)", |  | ||||||
|                 r = 0x00, |  | ||||||
|                 g = 0xff, |  | ||||||
|                 b = 0x00, |  | ||||||
|                 a = 0x88, |  | ||||||
|             ), |  | ||||||
|             TestColor( |  | ||||||
|                 color = HEXAColor(0x0000ff88u), |  | ||||||
|                 shortHex = "#00f", |  | ||||||
|                 shortHexa = "#00f8", |  | ||||||
|                 hex = "#0000ff", |  | ||||||
|                 hexa = "#0000ff88", |  | ||||||
|                 ahex = "#880000ff", |  | ||||||
|                 ahexUInt = 0x880000ffu, |  | ||||||
|                 rgbUInt = 0x0000ffu, |  | ||||||
|                 rgb = "rgb(0,0,255)", |  | ||||||
|                 rgba = "rgba(0,0,255,0.533)", |  | ||||||
|                 r = 0x00, |  | ||||||
|                 g = 0x00, |  | ||||||
|                 b = 0xff, |  | ||||||
|                 a = 0x88, |  | ||||||
|             ), |  | ||||||
|             TestColor( |  | ||||||
|                 color = HEXAColor(0xff000022u), |  | ||||||
|                 shortHex = "#f00", |  | ||||||
|                 shortHexa = "#f002", |  | ||||||
|                 hex = "#ff0000", |  | ||||||
|                 hexa = "#ff000022", |  | ||||||
|                 ahex = "#22ff0000", |  | ||||||
|                 ahexUInt = 0x22ff0000u, |  | ||||||
|                 rgbUInt = 0xff0000u, |  | ||||||
|                 rgb = "rgb(255,0,0)", |  | ||||||
|                 rgba = "rgba(255,0,0,0.133)", |  | ||||||
|                 r = 0xff, |  | ||||||
|                 g = 0x00, |  | ||||||
|                 b = 0x00, |  | ||||||
|                 a = 0x22, |  | ||||||
|             ), |  | ||||||
|             TestColor( |  | ||||||
|                 color = HEXAColor(0x00ff0022u), |  | ||||||
|                 shortHex = "#0f0", |  | ||||||
|                 shortHexa = "#0f02", |  | ||||||
|                 hex = "#00ff00", |  | ||||||
|                 hexa = "#00ff0022", |  | ||||||
|                 ahex = "#2200ff00", |  | ||||||
|                 ahexUInt = 0x2200ff00u, |  | ||||||
|                 rgbUInt = 0x00ff00u, |  | ||||||
|                 rgb = "rgb(0,255,0)", |  | ||||||
|                 rgba = "rgba(0,255,0,0.133)", |  | ||||||
|                 r = 0x00, |  | ||||||
|                 g = 0xff, |  | ||||||
|                 b = 0x00, |  | ||||||
|                 a = 0x22, |  | ||||||
|             ), |  | ||||||
|             TestColor( |  | ||||||
|                 color = HEXAColor(0x0000ff22u), |  | ||||||
|                 shortHex = "#00f", |  | ||||||
|                 shortHexa = "#00f2", |  | ||||||
|                 hex = "#0000ff", |  | ||||||
|                 hexa = "#0000ff22", |  | ||||||
|                 ahex = "#220000ff", |  | ||||||
|                 ahexUInt = 0x220000ffu, |  | ||||||
|                 rgbUInt = 0x0000ffu, |  | ||||||
|                 rgb = "rgb(0,0,255)", |  | ||||||
|                 rgba = "rgba(0,0,255,0.133)", |  | ||||||
|                 r = 0x00, |  | ||||||
|                 g = 0x00, |  | ||||||
|                 b = 0xff, |  | ||||||
|                 a = 0x22, |  | ||||||
|             ), |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|     @Test |  | ||||||
|     fun baseTest() { |  | ||||||
|         testColors.forEach { |  | ||||||
|             assertEquals(it.hex, it.color.hex) |  | ||||||
|             assertEquals(it.hexa, it.color.hexa) |  | ||||||
|             assertEquals(it.ahex, it.color.ahex) |  | ||||||
|             assertEquals(it.rgbUInt, it.color.rgbUInt) |  | ||||||
|             assertEquals(it.ahexUInt, it.color.ahexUInt) |  | ||||||
|             assertEquals(it.shortHex, it.color.shortHex) |  | ||||||
|             assertEquals(it.shortHexa, it.color.shortHexa) |  | ||||||
|             assertEquals(it.rgb, it.color.rgb) |  | ||||||
|             assertTrue(it.rgba == it.color.rgba || it.color.rgba in it.additionalRGBAVariants) |  | ||||||
|             assertEquals(it.r, it.color.r) |  | ||||||
|             assertEquals(it.g, it.color.g) |  | ||||||
|             assertEquals(it.b, it.color.b) |  | ||||||
|             assertEquals(it.a, it.color.a) |  | ||||||
|             assertEquals(it.color, HEXAColor.fromAhex(it.ahexUInt)) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Test |  | ||||||
|     fun testHexParseColor() { |  | ||||||
|         testColors.forEach { |  | ||||||
|             assertEquals(it.color.copy(aOfOne = 1f), HEXAColor.parseStringColor(it.hex)) |  | ||||||
|             assertEquals(it.color, HEXAColor.parseStringColor(it.hexa)) |  | ||||||
|             assertEquals(it.color.copy(aOfOne = 1f), HEXAColor.parseStringColor(it.rgb)) |  | ||||||
|             assertTrue(it.color.hexaUInt.toInt() - HEXAColor.parseStringColor(it.rgba).hexaUInt.toInt() in -0x1 .. 0x1, ) |  | ||||||
|             assertEquals(it.color.copy(aOfOne = 1f), HEXAColor.parseStringColor(it.shortHex)) |  | ||||||
|             assertEquals(it.color.copy(a = floor(it.color.a.toFloat() / 16).toInt() * 0x10), HEXAColor.parseStringColor(it.shortHexa)) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -4,7 +4,7 @@ plugins { | |||||||
|     id "com.android.library" |     id "com.android.library" | ||||||
| } | } | ||||||
|  |  | ||||||
| apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64ProjectPresetPath" | apply from: "$mppProjectWithSerializationPresetPath" | ||||||
|  |  | ||||||
| kotlin { | kotlin { | ||||||
|     sourceSets { |     sourceSets { | ||||||
| @@ -18,23 +18,6 @@ kotlin { | |||||||
|                 api project(":micro_utils.coroutines") |                 api project(":micro_utils.coroutines") | ||||||
|                 api libs.android.fragment |                 api libs.android.fragment | ||||||
|             } |             } | ||||||
|             dependsOn jvmMain |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         linuxX64Main { |  | ||||||
|             dependencies { |  | ||||||
|                 api libs.okio |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         mingwX64Main { |  | ||||||
|             dependencies { |  | ||||||
|                 api libs.okio |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         linuxArm64Main { |  | ||||||
|             dependencies { |  | ||||||
|                 api libs.okio |  | ||||||
|             } |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1 +0,0 @@ | |||||||
| <manifest/> |  | ||||||
| @@ -1,10 +0,0 @@ | |||||||
| package dev.inmo.micro_utils.common.compose |  | ||||||
|  |  | ||||||
| import androidx.compose.runtime.MutableState |  | ||||||
| import androidx.compose.runtime.State |  | ||||||
| import androidx.compose.runtime.derivedStateOf |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Converts current [MutableState] to immutable [State] using [derivedStateOf] |  | ||||||
|  */ |  | ||||||
| fun <T> MutableState<T>.asState(): State<T> = derivedStateOf { this.value } |  | ||||||
							
								
								
									
										1
									
								
								common/compose/src/main/AndroidManifest.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								common/compose/src/main/AndroidManifest.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | <manifest package="dev.inmo.micro_utils.common.compose"/> | ||||||
| @@ -1 +0,0 @@ | |||||||
| <manifest/> |  | ||||||
| @@ -2,8 +2,6 @@ | |||||||
|  |  | ||||||
| package dev.inmo.micro_utils.common | package dev.inmo.micro_utils.common | ||||||
|  |  | ||||||
| import kotlinx.serialization.Serializable |  | ||||||
|  |  | ||||||
| private inline fun <T> getObject( | private inline fun <T> getObject( | ||||||
|     additional: MutableList<T>, |     additional: MutableList<T>, | ||||||
|     iterator: Iterator<T> |     iterator: Iterator<T> | ||||||
| @@ -26,23 +24,14 @@ private inline fun <T> getObject( | |||||||
|  * |  * | ||||||
|  * @see calculateDiff |  * @see calculateDiff | ||||||
|  */ |  */ | ||||||
| @Serializable | data class Diff<T> internal constructor( | ||||||
| data class Diff<T> @Warning(warning) constructor( |     val removed: List<IndexedValue<T>>, | ||||||
|     val removed: List<@Serializable(IndexedValueSerializer::class) IndexedValue<T>>, |  | ||||||
|     /** |     /** | ||||||
|      * Old-New values pairs |      * Old-New values pairs | ||||||
|      */ |      */ | ||||||
|     val replaced: List<Pair<@Serializable(IndexedValueSerializer::class) IndexedValue<T>, @Serializable(IndexedValueSerializer::class) IndexedValue<T>>>, |     val replaced: List<Pair<IndexedValue<T>, IndexedValue<T>>>, | ||||||
|     val added: List<@Serializable(IndexedValueSerializer::class) IndexedValue<T>> |     val added: List<IndexedValue<T>> | ||||||
| ) { | ) | ||||||
|     fun isEmpty(): Boolean = removed.isEmpty() && replaced.isEmpty() && added.isEmpty() |  | ||||||
|  |  | ||||||
|     companion object { |  | ||||||
|         private const val warning = "This feature can be changed without any warranties. Use with caution and only in case you know what you are doing" |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| fun <T> emptyDiff(): Diff<T> = Diff(emptyList(), emptyList(), emptyList()) |  | ||||||
|  |  | ||||||
| private inline fun <T> performChanges( | private inline fun <T> performChanges( | ||||||
|     potentialChanges: MutableList<Pair<IndexedValue<T>?, IndexedValue<T>?>>, |     potentialChanges: MutableList<Pair<IndexedValue<T>?, IndexedValue<T>?>>, | ||||||
| @@ -51,14 +40,14 @@ private inline fun <T> performChanges( | |||||||
|     changedList: MutableList<Pair<IndexedValue<T>, IndexedValue<T>>>, |     changedList: MutableList<Pair<IndexedValue<T>, IndexedValue<T>>>, | ||||||
|     removedList: MutableList<IndexedValue<T>>, |     removedList: MutableList<IndexedValue<T>>, | ||||||
|     addedList: MutableList<IndexedValue<T>>, |     addedList: MutableList<IndexedValue<T>>, | ||||||
|     comparisonFun: (T?, T?) -> Boolean |     strictComparison: Boolean | ||||||
| ) { | ) { | ||||||
|     var i = -1 |     var i = -1 | ||||||
|     val (oldObject, newObject) = potentialChanges.lastOrNull() ?: return |     val (oldObject, newObject) = potentialChanges.lastOrNull() ?: return | ||||||
|     for ((old, new) in potentialChanges.take(potentialChanges.size - 1)) { |     for ((old, new) in potentialChanges.take(potentialChanges.size - 1)) { | ||||||
|         i++ |         i++ | ||||||
|         val oldOneEqualToNewObject = comparisonFun(old ?.value, newObject ?.value) |         val oldOneEqualToNewObject = old ?.value === newObject ?.value || (old ?.value == newObject ?.value && !strictComparison) | ||||||
|         val newOneEqualToOldObject = comparisonFun(new ?.value, oldObject ?.value) |         val newOneEqualToOldObject = new ?.value === oldObject ?.value || (new ?.value == oldObject ?.value && !strictComparison) | ||||||
|         if (oldOneEqualToNewObject || newOneEqualToOldObject) { |         if (oldOneEqualToNewObject || newOneEqualToOldObject) { | ||||||
|             changedList.addAll( |             changedList.addAll( | ||||||
|                 potentialChanges.take(i).mapNotNull { |                 potentialChanges.take(i).mapNotNull { | ||||||
| @@ -112,7 +101,7 @@ private inline fun <T> performChanges( | |||||||
|  */ |  */ | ||||||
| fun <T> Iterable<T>.calculateDiff( | fun <T> Iterable<T>.calculateDiff( | ||||||
|     other: Iterable<T>, |     other: Iterable<T>, | ||||||
|     comparisonFun: (T?, T?) -> Boolean |     strictComparison: Boolean = false | ||||||
| ): Diff<T> { | ): Diff<T> { | ||||||
|     var i = -1 |     var i = -1 | ||||||
|     var j = -1 |     var j = -1 | ||||||
| @@ -140,7 +129,7 @@ fun <T> Iterable<T>.calculateDiff( | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         when { |         when { | ||||||
|             comparisonFun(oldObject, newObject) -> { |             oldObject === newObject || (oldObject == newObject && !strictComparison) -> { | ||||||
|                 changedObjects.addAll(potentiallyChangedObjects.map { |                 changedObjects.addAll(potentiallyChangedObjects.map { | ||||||
|                     @Suppress("UNCHECKED_CAST") |                     @Suppress("UNCHECKED_CAST") | ||||||
|                     it as Pair<IndexedValue<T>, IndexedValue<T>> |                     it as Pair<IndexedValue<T>, IndexedValue<T>> | ||||||
| @@ -151,49 +140,23 @@ fun <T> Iterable<T>.calculateDiff( | |||||||
|                 potentiallyChangedObjects.add(oldObject ?.let { IndexedValue(i, oldObject) } to newObject ?.let { IndexedValue(j, newObject) }) |                 potentiallyChangedObjects.add(oldObject ?.let { IndexedValue(i, oldObject) } to newObject ?.let { IndexedValue(j, newObject) }) | ||||||
|                 val previousOldsAdditionsSize = additionalInOld.size |                 val previousOldsAdditionsSize = additionalInOld.size | ||||||
|                 val previousNewsAdditionsSize = additionalInNew.size |                 val previousNewsAdditionsSize = additionalInNew.size | ||||||
|                 performChanges(potentiallyChangedObjects, additionalInOld, additionalInNew, changedObjects, removedObjects, addedObjects, comparisonFun) |                 performChanges(potentiallyChangedObjects, additionalInOld, additionalInNew, changedObjects, removedObjects, addedObjects, strictComparison) | ||||||
|                 i -= (additionalInOld.size - previousOldsAdditionsSize) |                 i -= (additionalInOld.size - previousOldsAdditionsSize) | ||||||
|                 j -= (additionalInNew.size - previousNewsAdditionsSize) |                 j -= (additionalInNew.size - previousNewsAdditionsSize) | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     potentiallyChangedObjects.add(null to null) |     potentiallyChangedObjects.add(null to null) | ||||||
|     performChanges(potentiallyChangedObjects, additionalInOld, additionalInNew, changedObjects, removedObjects, addedObjects, comparisonFun) |     performChanges(potentiallyChangedObjects, additionalInOld, additionalInNew, changedObjects, removedObjects, addedObjects, strictComparison) | ||||||
|  |  | ||||||
|     return Diff(removedObjects.toList(), changedObjects.toList(), addedObjects.toList()) |     return Diff(removedObjects.toList(), changedObjects.toList(), addedObjects.toList()) | ||||||
| } | } | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Calculating [Diff] object |  | ||||||
|  * |  | ||||||
|  * @param strictComparison If this parameter set to true, objects which are not equal by links will be used as different |  | ||||||
|  * objects. For example, in case of two "Example" string they will be equal by value, but CAN be different by links |  | ||||||
|  */ |  | ||||||
| fun <T> Iterable<T>.calculateDiff( |  | ||||||
|     other: Iterable<T>, |  | ||||||
|     strictComparison: Boolean = false |  | ||||||
| ): Diff<T> = calculateDiff( |  | ||||||
|     other, |  | ||||||
|     comparisonFun = if (strictComparison) { |  | ||||||
|         { t1, t2 -> |  | ||||||
|             t1 === t2 |  | ||||||
|         } |  | ||||||
|     } else { |  | ||||||
|         { t1, t2 -> |  | ||||||
|             t1 === t2 || t1 == t2 // small optimization for cases when t1 and t2 are the same - comparison will be faster potentially |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| ) |  | ||||||
| inline fun <T> Iterable<T>.diff( | inline fun <T> Iterable<T>.diff( | ||||||
|     other: Iterable<T>, |     other: Iterable<T>, | ||||||
|     strictComparison: Boolean = false |     strictComparison: Boolean = false | ||||||
| ): Diff<T> = calculateDiff(other, strictComparison) | ): Diff<T> = calculateDiff(other, strictComparison) | ||||||
| inline fun <T> Iterable<T>.diff( |  | ||||||
|     other: Iterable<T>, |  | ||||||
|     noinline comparisonFun: (T?, T?) -> Boolean |  | ||||||
| ): Diff<T> = calculateDiff(other, comparisonFun) |  | ||||||
|  |  | ||||||
| inline fun <T> Diff(old: Iterable<T>, new: Iterable<T>) = old.calculateDiff(new, strictComparison = false) | inline fun <T> Diff(old: Iterable<T>, new: Iterable<T>) = old.calculateDiff(new) | ||||||
| inline fun <T> StrictDiff(old: Iterable<T>, new: Iterable<T>) = old.calculateDiff(new, true) | inline fun <T> StrictDiff(old: Iterable<T>, new: Iterable<T>) = old.calculateDiff(new, true) | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -203,23 +166,6 @@ inline fun <T> Iterable<T>.calculateStrictDiff( | |||||||
|     other: Iterable<T> |     other: Iterable<T> | ||||||
| ) = calculateDiff(other, strictComparison = true) | ) = calculateDiff(other, strictComparison = true) | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Applies [diff] to [this] [MutableList] |  | ||||||
|  */ |  | ||||||
| fun <T> MutableList<T>.applyDiff( |  | ||||||
|     diff: Diff<T> |  | ||||||
| ) { |  | ||||||
|     for (i in diff.removed.indices.sortedDescending()) { |  | ||||||
|         removeAt(diff.removed[i].index) |  | ||||||
|     } |  | ||||||
|     diff.added.forEach { (i, t) -> |  | ||||||
|         add(i, t) |  | ||||||
|     } |  | ||||||
|     diff.replaced.forEach { (_, new) -> |  | ||||||
|         set(new.index, new.value) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * This method call [calculateDiff] with strict mode [strictComparison] and then apply differences to [this] |  * This method call [calculateDiff] with strict mode [strictComparison] and then apply differences to [this] | ||||||
|  * mutable list |  * mutable list | ||||||
| @@ -228,26 +174,13 @@ fun <T> MutableList<T>.applyDiff( | |||||||
|     source: Iterable<T>, |     source: Iterable<T>, | ||||||
|     strictComparison: Boolean = false |     strictComparison: Boolean = false | ||||||
| ): Diff<T> = calculateDiff(source, strictComparison).also { | ): Diff<T> = calculateDiff(source, strictComparison).also { | ||||||
|     applyDiff(it) |     for (i in it.removed.indices.sortedDescending()) { | ||||||
|  |         removeAt(it.removed[i].index) | ||||||
|  |     } | ||||||
|  |     it.added.forEach { (i, t) -> | ||||||
|  |         add(i, t) | ||||||
|  |     } | ||||||
|  |     it.replaced.forEach { (_, new) -> | ||||||
|  |         set(new.index, new.value) | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * This method call [calculateDiff] and then apply differences to [this] |  | ||||||
|  * mutable list |  | ||||||
|  */ |  | ||||||
| fun <T> MutableList<T>.applyDiff( |  | ||||||
|     source: Iterable<T>, |  | ||||||
|     comparisonFun: (T?, T?) -> Boolean |  | ||||||
| ): Diff<T> = calculateDiff(source, comparisonFun).also { |  | ||||||
|     applyDiff(it) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Reverse [this] [Diff]. Result will contain [Diff.added] on [Diff.removed] (and vice-verse), all the |  | ||||||
|  * [Diff.replaced] values will be reversed too |  | ||||||
|  */ |  | ||||||
| fun <T> Diff<T>.reversed() = Diff( |  | ||||||
|     removed = added, |  | ||||||
|     replaced = replaced.map { it.second to it.first }, |  | ||||||
|     added = removed |  | ||||||
| ) |  | ||||||
|   | |||||||
| @@ -1,43 +0,0 @@ | |||||||
| package dev.inmo.micro_utils.common |  | ||||||
|  |  | ||||||
| inline fun <T> Boolean.letIfTrue(block: () -> T): T? { |  | ||||||
|     return if (this) { |  | ||||||
|         block() |  | ||||||
|     } else { |  | ||||||
|         null |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| inline fun <T> Boolean.letIfFalse(block: () -> T): T? { |  | ||||||
|     return if (this) { |  | ||||||
|         null |  | ||||||
|     } else { |  | ||||||
|         block() |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| inline fun Boolean.alsoIfTrue(block: () -> Unit): Boolean { |  | ||||||
|     letIfTrue(block) |  | ||||||
|     return this |  | ||||||
| } |  | ||||||
|  |  | ||||||
| inline fun Boolean.alsoIfFalse(block: () -> Unit): Boolean { |  | ||||||
|     letIfFalse(block) |  | ||||||
|     return this |  | ||||||
| } |  | ||||||
|  |  | ||||||
| inline fun <T> Boolean.ifTrue(block: () -> T): T? { |  | ||||||
|     return if (this) { |  | ||||||
|         block() |  | ||||||
|     } else { |  | ||||||
|         null |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| inline fun <T> Boolean.ifFalse(block: () -> T): T? { |  | ||||||
|     return if (this) { |  | ||||||
|         null |  | ||||||
|     } else { |  | ||||||
|         block() |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,30 +0,0 @@ | |||||||
| package dev.inmo.micro_utils.common |  | ||||||
|  |  | ||||||
| import kotlinx.serialization.KSerializer |  | ||||||
| import kotlinx.serialization.Serializer |  | ||||||
| import kotlinx.serialization.builtins.PairSerializer |  | ||||||
| import kotlinx.serialization.builtins.serializer |  | ||||||
| import kotlinx.serialization.descriptors.SerialDescriptor |  | ||||||
| import kotlinx.serialization.encoding.Decoder |  | ||||||
| import kotlinx.serialization.encoding.Encoder |  | ||||||
|  |  | ||||||
| class IndexedValueSerializer<T>(private val subSerializer: KSerializer<T>) : KSerializer<IndexedValue<T>> { |  | ||||||
|     private val originalSerializer = PairSerializer(Int.serializer(), subSerializer) |  | ||||||
|     override val descriptor: SerialDescriptor |  | ||||||
|         get() = originalSerializer.descriptor |  | ||||||
|  |  | ||||||
|     override fun deserialize(decoder: Decoder): IndexedValue<T> { |  | ||||||
|         val pair = originalSerializer.deserialize(decoder) |  | ||||||
|         return IndexedValue( |  | ||||||
|             pair.first, |  | ||||||
|             pair.second |  | ||||||
|         ) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun serialize(encoder: Encoder, value: IndexedValue<T>) { |  | ||||||
|         originalSerializer.serialize( |  | ||||||
|             encoder, |  | ||||||
|             Pair(value.index, value.value) |  | ||||||
|         ) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,135 +0,0 @@ | |||||||
| package dev.inmo.micro_utils.common |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Contains diff based on the comparison of objects with the same [K]. |  | ||||||
|  * |  | ||||||
|  * @param removed Contains map with keys removed from parent map |  | ||||||
|  * @param changed Contains map with keys values changed new map in comparison with old one |  | ||||||
|  * @param added Contains map with new keys and values |  | ||||||
|  */ |  | ||||||
| data class MapDiff<K, V> @Warning(warning) constructor( |  | ||||||
|     val removed: Map<K, V>, |  | ||||||
|     val changed: Map<K, Pair<V, V>>, |  | ||||||
|     val added: Map<K, V> |  | ||||||
| ) { |  | ||||||
|     fun isEmpty() = removed.isEmpty() && changed.isEmpty() && added.isEmpty() |  | ||||||
|     inline fun isNotEmpty() = !isEmpty() |  | ||||||
|  |  | ||||||
|     companion object { |  | ||||||
|         private const val warning = "This feature can be changed without any warranties. Use with caution and only in case you know what you are doing" |  | ||||||
|         fun <K, V> empty() = MapDiff<K, V>(emptyMap(), emptyMap(), emptyMap()) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| private inline fun <K, V> createCompareFun( |  | ||||||
|     strictComparison: Boolean |  | ||||||
| ): (K, V, V) -> Boolean = if (strictComparison) { |  | ||||||
|     { _, first, second -> first === second } |  | ||||||
| } else { |  | ||||||
|     { _, first, second -> first == second } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Compare [this] [Map] with the [other] one in principle when [other] is newer than [this] |  | ||||||
|  * |  | ||||||
|  * @param compareFun Will be used to determine changed values |  | ||||||
|  */ |  | ||||||
| fun <K, V> Map<K, V>.diff( |  | ||||||
|     other: Map<K, V>, |  | ||||||
|     compareFun: (K, V, V) -> Boolean |  | ||||||
| ): MapDiff<K, V> { |  | ||||||
|     val removed: Map<K, V> = (keys - other.keys).associateWith { |  | ||||||
|         getValue(it) |  | ||||||
|     } |  | ||||||
|     val added: Map<K, V> = (other.keys - keys).associateWith { |  | ||||||
|         other.getValue(it) |  | ||||||
|     } |  | ||||||
|     val changed = keys.intersect(other.keys).mapNotNull { |  | ||||||
|         val old = getValue(it) |  | ||||||
|         val new = other.getValue(it) |  | ||||||
|         if (compareFun(it, old, new)) { |  | ||||||
|             return@mapNotNull null |  | ||||||
|         } else { |  | ||||||
|             it to (old to new) |  | ||||||
|         } |  | ||||||
|     }.toMap() |  | ||||||
|  |  | ||||||
|     return MapDiff( |  | ||||||
|         removed, |  | ||||||
|         changed, |  | ||||||
|         added |  | ||||||
|     ) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Compare [this] [Map] with the [other] one in principle when [other] is newer than [this] |  | ||||||
|  * |  | ||||||
|  * @param strictComparison If true, will use strict (===) comparison for the values' comparison. Otherwise, standard |  | ||||||
|  * `equals` will be used |  | ||||||
|  */ |  | ||||||
| fun <K, V> Map<K, V>.diff( |  | ||||||
|     other: Map<K, V>, |  | ||||||
|     strictComparison: Boolean = false |  | ||||||
| ): MapDiff<K, V> = diff( |  | ||||||
|     other, |  | ||||||
|     compareFun = createCompareFun(strictComparison) |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Will apply [mapDiff] to [this] [MutableMap] |  | ||||||
|  */ |  | ||||||
| fun <K, V> MutableMap<K, V>.applyDiff( |  | ||||||
|     mapDiff: MapDiff<K, V> |  | ||||||
| ) { |  | ||||||
|     mapDiff.apply { |  | ||||||
|         removed.keys.forEach { remove(it) } |  | ||||||
|         changed.forEach { (k, oldNew) -> |  | ||||||
|             put(k, oldNew.second) |  | ||||||
|         } |  | ||||||
|         added.forEach { (k, new) -> |  | ||||||
|             put(k, new) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Will apply changes with [from] map into [this] one |  | ||||||
|  * |  | ||||||
|  * @param compareFun Will be used to determine changed values |  | ||||||
|  * |  | ||||||
|  * @return [MapDiff] applied to [this] [MutableMap] |  | ||||||
|  */ |  | ||||||
| fun <K, V> MutableMap<K, V>.applyDiff( |  | ||||||
|     from: Map<K, V>, |  | ||||||
|     compareFun: (K, V, V) -> Boolean |  | ||||||
| ): MapDiff<K, V> { |  | ||||||
|     return diff(from, compareFun).also { |  | ||||||
|         applyDiff(it) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Will apply changes with [from] map into [this] one |  | ||||||
|  * |  | ||||||
|  * @param strictComparison If true, will use strict (===) comparison for the values' comparison. Otherwise, standard |  | ||||||
|  * `equals` will be used |  | ||||||
|  * |  | ||||||
|  * @return [MapDiff] applied to [this] [MutableMap] |  | ||||||
|  */ |  | ||||||
| fun <K, V> MutableMap<K, V>.applyDiff( |  | ||||||
|     from: Map<K, V>, |  | ||||||
|     strictComparison: Boolean = false |  | ||||||
| ): MapDiff<K, V> = applyDiff( |  | ||||||
|     from, |  | ||||||
|     compareFun = createCompareFun(strictComparison) |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Reverse [this] [MapDiff]. Result will contain [MapDiff.added] on [MapDiff.removed] (and vice-verse), all the |  | ||||||
|  * [MapDiff.changed] values will be reversed too |  | ||||||
|  */ |  | ||||||
| fun <K, V> MapDiff<K, V>.reversed(): MapDiff<K, V> = MapDiff( |  | ||||||
|     removed = added, |  | ||||||
|     changed = changed.mapValues { (_, oldNew) -> oldNew.second to oldNew.first }, |  | ||||||
|     added = removed |  | ||||||
| ) |  | ||||||
| @@ -1,28 +0,0 @@ | |||||||
| package dev.inmo.micro_utils.common |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Convert [this] [Long] to [Int] with bounds of [Int.MIN_VALUE] and [Int.MAX_VALUE] |  | ||||||
|  */ |  | ||||||
| fun Long.toCoercedInt(): Int = coerceIn(Int.MIN_VALUE.toLong(), Int.MAX_VALUE.toLong()).toInt() |  | ||||||
| /** |  | ||||||
|  * Convert [this] [Long] to [Short] with bounds of [Short.MIN_VALUE] and [Short.MAX_VALUE] |  | ||||||
|  */ |  | ||||||
| fun Long.toCoercedShort(): Short = coerceIn(Short.MIN_VALUE.toLong(), Short.MAX_VALUE.toLong()).toShort() |  | ||||||
| /** |  | ||||||
|  * Convert [this] [Long] to [Byte] with bounds of [Byte.MIN_VALUE] and [Byte.MAX_VALUE] |  | ||||||
|  */ |  | ||||||
| fun Long.toCoercedByte(): Byte = coerceIn(Byte.MIN_VALUE.toLong(), Byte.MAX_VALUE.toLong()).toByte() |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Convert [this] [Int] to [Short] with bounds of [Short.MIN_VALUE] and [Short.MAX_VALUE] |  | ||||||
|  */ |  | ||||||
| fun Int.toCoercedShort(): Short = coerceIn(Short.MIN_VALUE.toInt(), Short.MAX_VALUE.toInt()).toShort() |  | ||||||
| /** |  | ||||||
|  * Convert [this] [Int] to [Byte] with bounds of [Byte.MIN_VALUE] and [Byte.MAX_VALUE] |  | ||||||
|  */ |  | ||||||
| fun Int.toCoercedByte(): Byte = coerceIn(Byte.MIN_VALUE.toInt(), Byte.MAX_VALUE.toInt()).toByte() |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Convert [this] [Short] to [Byte] with bounds of [Byte.MIN_VALUE] and [Byte.MAX_VALUE] |  | ||||||
|  */ |  | ||||||
| fun Short.toCoercedByte(): Byte = coerceIn(Byte.MIN_VALUE.toShort(), Byte.MAX_VALUE.toShort()).toByte() |  | ||||||
| @@ -1,27 +1,5 @@ | |||||||
| package dev.inmo.micro_utils.common | package dev.inmo.micro_utils.common | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Executes the given [action] until getting of successful result specified number of [times]. |  | ||||||
|  * |  | ||||||
|  * A zero-based index of current iteration is passed as a parameter to [action]. |  | ||||||
|  */ |  | ||||||
| inline fun <R> repeatOnFailure( |  | ||||||
|     onFailure: (Throwable) -> Boolean, |  | ||||||
|     action: () -> R |  | ||||||
| ): Result<R> { |  | ||||||
|     do { |  | ||||||
|         runCatching { |  | ||||||
|             action() |  | ||||||
|         }.onFailure { |  | ||||||
|             if (!onFailure(it)) { |  | ||||||
|                 return Result.failure(it) |  | ||||||
|             } |  | ||||||
|         }.onSuccess { |  | ||||||
|             return Result.success(it) |  | ||||||
|         } |  | ||||||
|     } while (true) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Executes the given [action] until getting of successful result specified number of [times]. |  * Executes the given [action] until getting of successful result specified number of [times]. | ||||||
|  * |  * | ||||||
| @@ -32,23 +10,12 @@ inline fun <R> repeatOnFailure( | |||||||
|     onEachFailure: (Throwable) -> Unit = {}, |     onEachFailure: (Throwable) -> Unit = {}, | ||||||
|     action: (Int) -> R |     action: (Int) -> R | ||||||
| ): Optional<R> { | ): Optional<R> { | ||||||
|     var i = 0 |     repeat(times) { | ||||||
|     val result = repeatOnFailure( |         runCatching { | ||||||
|         { |             action(it) | ||||||
|             onEachFailure(it) |         }.onFailure(onEachFailure).onSuccess { | ||||||
|             if (i < times) { |             return Optional.presented(it) | ||||||
|                 i++ |  | ||||||
|                 true |  | ||||||
|             } else { |  | ||||||
|                 false |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     ) { |     return Optional.absent() | ||||||
|         action(i) |  | ||||||
|     } |  | ||||||
|     return if (result.isSuccess) { |  | ||||||
|         Optional.presented(result.getOrThrow()) |  | ||||||
|     } else { |  | ||||||
|         Optional.absent() |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,6 +0,0 @@ | |||||||
| package dev.inmo.micro_utils.common |  | ||||||
|  |  | ||||||
| val FixedSignsRange = 0 .. 100 |  | ||||||
|  |  | ||||||
| expect fun Float.fixed(signs: Int): Float |  | ||||||
| expect fun Double.fixed(signs: Int): Double |  | ||||||
| @@ -1,4 +0,0 @@ | |||||||
| package dev.inmo.micro_utils.common |  | ||||||
|  |  | ||||||
| actual fun Float.fixed(signs: Int): Float = this.asDynamic().toFixed(signs.coerceIn(FixedSignsRange)).unsafeCast<String>().toFloat() |  | ||||||
| actual fun Double.fixed(signs: Int): Double = this.asDynamic().toFixed(signs.coerceIn(FixedSignsRange)).unsafeCast<String>().toDouble() |  | ||||||
| @@ -1,12 +0,0 @@ | |||||||
| package dev.inmo.micro_utils.common |  | ||||||
|  |  | ||||||
| import java.math.BigDecimal |  | ||||||
| import java.math.RoundingMode |  | ||||||
|  |  | ||||||
| actual fun Float.fixed(signs: Int): Float = BigDecimal.valueOf(this.toDouble()) |  | ||||||
|     .setScale(signs.coerceIn(FixedSignsRange), RoundingMode.HALF_UP) |  | ||||||
|     .toFloat(); |  | ||||||
|  |  | ||||||
| actual fun Double.fixed(signs: Int): Double = BigDecimal.valueOf(this) |  | ||||||
|     .setScale(signs.coerceIn(FixedSignsRange), RoundingMode.HALF_UP) |  | ||||||
|     .toDouble(); |  | ||||||
| @@ -1,36 +0,0 @@ | |||||||
| package dev.inmo.micro_utils.common |  | ||||||
|  |  | ||||||
| import okio.FileSystem |  | ||||||
| import okio.Path |  | ||||||
| import okio.use |  | ||||||
|  |  | ||||||
| actual typealias MPPFile = Path |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * @suppress |  | ||||||
|  */ |  | ||||||
| actual val MPPFile.filename: FileName |  | ||||||
|     get() = FileName(toString()) |  | ||||||
| /** |  | ||||||
|  * @suppress |  | ||||||
|  */ |  | ||||||
| actual val MPPFile.filesize: Long |  | ||||||
|     get() = FileSystem.SYSTEM.openReadOnly(this).use { |  | ||||||
|         it.size() |  | ||||||
|     } |  | ||||||
| /** |  | ||||||
|  * @suppress |  | ||||||
|  */ |  | ||||||
| actual val MPPFile.bytesAllocatorSync: ByteArrayAllocator |  | ||||||
|     get() = { |  | ||||||
|         FileSystem.SYSTEM.read(this) { |  | ||||||
|             readByteArray() |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| /** |  | ||||||
|  * @suppress |  | ||||||
|  */ |  | ||||||
| actual val MPPFile.bytesAllocator: SuspendByteArrayAllocator |  | ||||||
|     get() = { |  | ||||||
|         bytesAllocatorSync() |  | ||||||
|     } |  | ||||||
| @@ -1,25 +0,0 @@ | |||||||
| package dev.inmo.micro_utils.common |  | ||||||
|  |  | ||||||
| import kotlinx.cinterop.* |  | ||||||
| import platform.posix.snprintf |  | ||||||
| import platform.posix.sprintf |  | ||||||
|  |  | ||||||
| @OptIn(ExperimentalForeignApi::class) |  | ||||||
| actual fun Float.fixed(signs: Int): Float { |  | ||||||
|     return memScoped { |  | ||||||
|         val buff = allocArray<ByteVar>(Float.SIZE_BYTES * 2) |  | ||||||
|  |  | ||||||
|         sprintf(buff, "%.${signs}f", this@fixed) |  | ||||||
|         buff.toKString().toFloat() |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| @OptIn(ExperimentalForeignApi::class) |  | ||||||
| actual fun Double.fixed(signs: Int): Double { |  | ||||||
|     return memScoped { |  | ||||||
|         val buff = allocArray<ByteVar>(Double.SIZE_BYTES * 2) |  | ||||||
|  |  | ||||||
|         sprintf(buff, "%.${signs}f", this@fixed) |  | ||||||
|         buff.toKString().toDouble() |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,36 +0,0 @@ | |||||||
| package dev.inmo.micro_utils.common |  | ||||||
|  |  | ||||||
| import okio.FileSystem |  | ||||||
| import okio.Path |  | ||||||
| import okio.use |  | ||||||
|  |  | ||||||
| actual typealias MPPFile = Path |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * @suppress |  | ||||||
|  */ |  | ||||||
| actual val MPPFile.filename: FileName |  | ||||||
|     get() = FileName(toString()) |  | ||||||
| /** |  | ||||||
|  * @suppress |  | ||||||
|  */ |  | ||||||
| actual val MPPFile.filesize: Long |  | ||||||
|     get() = FileSystem.SYSTEM.openReadOnly(this).use { |  | ||||||
|         it.size() |  | ||||||
|     } |  | ||||||
| /** |  | ||||||
|  * @suppress |  | ||||||
|  */ |  | ||||||
| actual val MPPFile.bytesAllocatorSync: ByteArrayAllocator |  | ||||||
|     get() = { |  | ||||||
|         FileSystem.SYSTEM.read(this) { |  | ||||||
|             readByteArray() |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| /** |  | ||||||
|  * @suppress |  | ||||||
|  */ |  | ||||||
| actual val MPPFile.bytesAllocator: SuspendByteArrayAllocator |  | ||||||
|     get() = { |  | ||||||
|         bytesAllocatorSync() |  | ||||||
|     } |  | ||||||
| @@ -1,25 +0,0 @@ | |||||||
| package dev.inmo.micro_utils.common |  | ||||||
|  |  | ||||||
| import kotlinx.cinterop.* |  | ||||||
| import platform.posix.snprintf |  | ||||||
| import platform.posix.sprintf |  | ||||||
|  |  | ||||||
| @OptIn(ExperimentalForeignApi::class) |  | ||||||
| actual fun Float.fixed(signs: Int): Float { |  | ||||||
|     return memScoped { |  | ||||||
|         val buff = allocArray<ByteVar>(Float.SIZE_BYTES * 2) |  | ||||||
|  |  | ||||||
|         sprintf(buff, "%.${signs}f", this@fixed) |  | ||||||
|         buff.toKString().toFloat() |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| @OptIn(ExperimentalForeignApi::class) |  | ||||||
| actual fun Double.fixed(signs: Int): Double { |  | ||||||
|     return memScoped { |  | ||||||
|         val buff = allocArray<ByteVar>(Double.SIZE_BYTES * 2) |  | ||||||
|  |  | ||||||
|         sprintf(buff, "%.${signs}f", this@fixed) |  | ||||||
|         buff.toKString().toDouble() |  | ||||||
|     } |  | ||||||
| } |  | ||||||
							
								
								
									
										1
									
								
								common/src/main/AndroidManifest.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								common/src/main/AndroidManifest.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | <manifest package="dev.inmo.micro_utils.common"/> | ||||||
| @@ -1,36 +0,0 @@ | |||||||
| package dev.inmo.micro_utils.common |  | ||||||
|  |  | ||||||
| import okio.FileSystem |  | ||||||
| import okio.Path |  | ||||||
| import okio.use |  | ||||||
|  |  | ||||||
| actual typealias MPPFile = Path |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * @suppress |  | ||||||
|  */ |  | ||||||
| actual val MPPFile.filename: FileName |  | ||||||
|     get() = FileName(toString()) |  | ||||||
| /** |  | ||||||
|  * @suppress |  | ||||||
|  */ |  | ||||||
| actual val MPPFile.filesize: Long |  | ||||||
|     get() = FileSystem.SYSTEM.openReadOnly(this).use { |  | ||||||
|         it.size() |  | ||||||
|     } |  | ||||||
| /** |  | ||||||
|  * @suppress |  | ||||||
|  */ |  | ||||||
| actual val MPPFile.bytesAllocatorSync: ByteArrayAllocator |  | ||||||
|     get() = { |  | ||||||
|         FileSystem.SYSTEM.read(this) { |  | ||||||
|             readByteArray() |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| /** |  | ||||||
|  * @suppress |  | ||||||
|  */ |  | ||||||
| actual val MPPFile.bytesAllocator: SuspendByteArrayAllocator |  | ||||||
|     get() = { |  | ||||||
|         bytesAllocatorSync() |  | ||||||
|     } |  | ||||||
| @@ -1,25 +0,0 @@ | |||||||
| package dev.inmo.micro_utils.common |  | ||||||
|  |  | ||||||
| import kotlinx.cinterop.* |  | ||||||
| import platform.posix.snprintf |  | ||||||
| import platform.posix.sprintf |  | ||||||
|  |  | ||||||
| @OptIn(ExperimentalForeignApi::class) |  | ||||||
| actual fun Float.fixed(signs: Int): Float { |  | ||||||
|     return memScoped { |  | ||||||
|         val buff = allocArray<ByteVar>(Float.SIZE_BYTES * 2) |  | ||||||
|  |  | ||||||
|         sprintf(buff, "%.${signs}f", this@fixed) |  | ||||||
|         buff.toKString().toFloat() |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| @OptIn(ExperimentalForeignApi::class) |  | ||||||
| actual fun Double.fixed(signs: Int): Double { |  | ||||||
|     return memScoped { |  | ||||||
|         val buff = allocArray<ByteVar>(Double.SIZE_BYTES * 2) |  | ||||||
|  |  | ||||||
|         sprintf(buff, "%.${signs}f", this@fixed) |  | ||||||
|         buff.toKString().toDouble() |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -4,7 +4,7 @@ plugins { | |||||||
|     id "com.android.library" |     id "com.android.library" | ||||||
| } | } | ||||||
|  |  | ||||||
| apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64ProjectPresetPath" | apply from: "$mppProjectWithSerializationPresetPath" | ||||||
|  |  | ||||||
| kotlin { | kotlin { | ||||||
|     sourceSets { |     sourceSets { | ||||||
|   | |||||||
| @@ -1 +0,0 @@ | |||||||
| <manifest/> |  | ||||||
| @@ -3,58 +3,24 @@ package dev.inmo.micro_utils.coroutines.compose | |||||||
| import androidx.compose.runtime.* | import androidx.compose.runtime.* | ||||||
| import androidx.compose.runtime.snapshots.SnapshotStateList | import androidx.compose.runtime.snapshots.SnapshotStateList | ||||||
| import dev.inmo.micro_utils.common.applyDiff | import dev.inmo.micro_utils.common.applyDiff | ||||||
| import dev.inmo.micro_utils.coroutines.ExceptionHandler |  | ||||||
| import dev.inmo.micro_utils.coroutines.defaultSafelyWithoutExceptionHandlerWithNull |  | ||||||
| import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions | import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions | ||||||
| import kotlinx.coroutines.CoroutineScope | import kotlinx.coroutines.CoroutineScope | ||||||
| import kotlinx.coroutines.Dispatchers |  | ||||||
| import kotlinx.coroutines.flow.Flow | import kotlinx.coroutines.flow.Flow | ||||||
| import kotlinx.coroutines.flow.StateFlow | import kotlinx.coroutines.flow.StateFlow | ||||||
| import kotlinx.coroutines.withContext |  | ||||||
| import kotlin.coroutines.CoroutineContext |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Each value of [this] [Flow] will trigger [applyDiff] to the result [SnapshotStateList] |  | ||||||
|  * |  | ||||||
|  * @param scope Will be used to [subscribeSafelyWithoutExceptions] on [this] to update returned [SnapshotStateList] |  | ||||||
|  * @param useContextOnChange Will be used to change context inside of [subscribeSafelyWithoutExceptions] to ensure that |  | ||||||
|  * change will happen in the required [CoroutineContext]. [Dispatchers.Main] by default |  | ||||||
|  * @param onException Will be passed to the [subscribeSafelyWithoutExceptions] as uncaught exceptions handler |  | ||||||
|  */ |  | ||||||
| @Suppress("NOTHING_TO_INLINE") | @Suppress("NOTHING_TO_INLINE") | ||||||
| inline fun <reified T> Flow<List<T>>.asMutableComposeListState( | inline fun <reified T> Flow<List<T>>.asMutableComposeListState( | ||||||
|     scope: CoroutineScope, |     scope: CoroutineScope | ||||||
|     useContextOnChange: CoroutineContext? = Dispatchers.Main, |  | ||||||
|     noinline onException: ExceptionHandler<List<T>?> = defaultSafelyWithoutExceptionHandlerWithNull, |  | ||||||
| ): SnapshotStateList<T> { | ): SnapshotStateList<T> { | ||||||
|     val state = mutableStateListOf<T>() |     val state = mutableStateListOf<T>() | ||||||
|     val changeBlock: suspend (List<T>) -> Unit = useContextOnChange ?.let { |     subscribeSafelyWithoutExceptions(scope) { | ||||||
|         { |  | ||||||
|             withContext(useContextOnChange) { |  | ||||||
|         state.applyDiff(it) |         state.applyDiff(it) | ||||||
|     } |     } | ||||||
|         } |  | ||||||
|     } ?: { |  | ||||||
|         state.applyDiff(it) |  | ||||||
|     } |  | ||||||
|     subscribeSafelyWithoutExceptions(scope, onException, changeBlock) |  | ||||||
|     return state |     return state | ||||||
| } | } | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * In fact, it is just classcast of [asMutableComposeListState] to [List] |  | ||||||
|  * |  | ||||||
|  * @param scope Will be used to [subscribeSafelyWithoutExceptions] on [this] to update returned [List] |  | ||||||
|  * @param useContextOnChange Will be used to change context inside of [subscribeSafelyWithoutExceptions] to ensure that |  | ||||||
|  * change will happen in the required [CoroutineContext]. [Dispatchers.Main] by default |  | ||||||
|  * @param onException Will be passed to the [subscribeSafelyWithoutExceptions] as uncaught exceptions handler |  | ||||||
|  * |  | ||||||
|  * @return Changing in time [List] which follow [Flow] values |  | ||||||
|  */ |  | ||||||
| @Suppress("NOTHING_TO_INLINE") | @Suppress("NOTHING_TO_INLINE") | ||||||
| inline fun <reified T> Flow<List<T>>.asComposeList( | inline fun <reified T> Flow<List<T>>.asComposeList( | ||||||
|     scope: CoroutineScope, |     scope: CoroutineScope | ||||||
|     useContextOnChange: CoroutineContext? = Dispatchers.Main, | ): List<T> = asMutableComposeListState(scope) | ||||||
|     noinline onException: ExceptionHandler<List<T>?> = defaultSafelyWithoutExceptionHandlerWithNull, |  | ||||||
| ): List<T> = asMutableComposeListState(scope, useContextOnChange, onException) |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,94 +1,35 @@ | |||||||
| package dev.inmo.micro_utils.coroutines.compose | package dev.inmo.micro_utils.coroutines.compose | ||||||
|  |  | ||||||
| import androidx.compose.runtime.* | import androidx.compose.runtime.* | ||||||
| import dev.inmo.micro_utils.common.compose.asState |  | ||||||
| import dev.inmo.micro_utils.coroutines.ExceptionHandler |  | ||||||
| import dev.inmo.micro_utils.coroutines.defaultSafelyWithoutExceptionHandlerWithNull |  | ||||||
| import dev.inmo.micro_utils.coroutines.doInUI |  | ||||||
| import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions | import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions | ||||||
| import kotlinx.coroutines.CoroutineScope | import kotlinx.coroutines.CoroutineScope | ||||||
| import kotlinx.coroutines.Dispatchers |  | ||||||
| import kotlinx.coroutines.flow.Flow | import kotlinx.coroutines.flow.Flow | ||||||
| import kotlinx.coroutines.flow.StateFlow | import kotlinx.coroutines.flow.StateFlow | ||||||
| import kotlinx.coroutines.withContext |  | ||||||
| import kotlin.coroutines.CoroutineContext |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Will map [this] [Flow] as [MutableState]. Returned [MutableState] WILL NOT change source [Flow] |  | ||||||
|  * |  | ||||||
|  * @param initial First value which will be passed to the result [MutableState] |  | ||||||
|  * @param scope Will be used to [subscribeSafelyWithoutExceptions] on [this] to update returned [MutableState] |  | ||||||
|  * @param useContextOnChange Will be used to change context inside of [subscribeSafelyWithoutExceptions] to ensure that |  | ||||||
|  * change will happen in the required [CoroutineContext]. [Dispatchers.Main] by default |  | ||||||
|  * @param onException Will be passed to the [subscribeSafelyWithoutExceptions] as uncaught exceptions handler |  | ||||||
|  */ |  | ||||||
| fun <T> Flow<T>.asMutableComposeState( | fun <T> Flow<T>.asMutableComposeState( | ||||||
|     initial: T, |     initial: T, | ||||||
|     scope: CoroutineScope, |     scope: CoroutineScope | ||||||
|     useContextOnChange: CoroutineContext? = Dispatchers.Main, |  | ||||||
|     onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull, |  | ||||||
| ): MutableState<T> { | ): MutableState<T> { | ||||||
|     val state = mutableStateOf(initial) |     val state = mutableStateOf(initial) | ||||||
|     val changeBlock: suspend (T) -> Unit = useContextOnChange ?.let { |     subscribeSafelyWithoutExceptions(scope) { state.value = it } | ||||||
|         { |  | ||||||
|             withContext(useContextOnChange) { |  | ||||||
|                 state.value = it |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } ?: { |  | ||||||
|         state.value = it |  | ||||||
|     } |  | ||||||
|     subscribeSafelyWithoutExceptions(scope, onException, block = changeBlock) |  | ||||||
|     return state |     return state | ||||||
| } | } | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Will map [this] [StateFlow] as [MutableState]. Returned [MutableState] WILL NOT change source [StateFlow]. |  | ||||||
|  * This conversation will pass its [StateFlow.value] as the first value |  | ||||||
|  * |  | ||||||
|  * @param scope Will be used to [subscribeSafelyWithoutExceptions] on [this] to update returned [MutableState] |  | ||||||
|  * @param useContextOnChange Will be used to change context inside of [subscribeSafelyWithoutExceptions] to ensure that |  | ||||||
|  * change will happen in the required [CoroutineContext]. [Dispatchers.Main] by default |  | ||||||
|  * @param onException Will be passed to the [subscribeSafelyWithoutExceptions] as uncaught exceptions handler |  | ||||||
|  */ |  | ||||||
| @Suppress("NOTHING_TO_INLINE") | @Suppress("NOTHING_TO_INLINE") | ||||||
| inline fun <T> StateFlow<T>.asMutableComposeState( | inline fun <T> StateFlow<T>.asMutableComposeState( | ||||||
|     scope: CoroutineScope, |     scope: CoroutineScope | ||||||
|     useContextOnChange: CoroutineContext? = Dispatchers.Main, | ): MutableState<T> = asMutableComposeState(value, scope) | ||||||
|     noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull, |  | ||||||
| ): MutableState<T> = asMutableComposeState(value, scope, useContextOnChange, onException) |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Will create [MutableState] using [asMutableComposeState] and use [asState] to convert it as immutable state |  | ||||||
|  * |  | ||||||
|  * @param initial First value which will be passed to the result [State] |  | ||||||
|  * @param scope Will be used to [subscribeSafelyWithoutExceptions] on [this] to update returned [State] |  | ||||||
|  * @param useContextOnChange Will be used to change context inside of [subscribeSafelyWithoutExceptions] to ensure that |  | ||||||
|  * change will happen in the required [CoroutineContext]. [Dispatchers.Main] by default |  | ||||||
|  * @param onException Will be passed to the [subscribeSafelyWithoutExceptions] as uncaught exceptions handler |  | ||||||
|  */ |  | ||||||
| fun <T> Flow<T>.asComposeState( | fun <T> Flow<T>.asComposeState( | ||||||
|     initial: T, |     initial: T, | ||||||
|     scope: CoroutineScope, |     scope: CoroutineScope | ||||||
|     useContextOnChange: CoroutineContext? = Dispatchers.Main, |  | ||||||
|     onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull, |  | ||||||
| ): State<T> { | ): State<T> { | ||||||
|     val state = asMutableComposeState(initial, scope, useContextOnChange, onException) |     val state = asMutableComposeState(initial, scope) | ||||||
|     return state.asState() |     return derivedStateOf { state.value } | ||||||
| } | } | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Will map [this] [StateFlow] as [State]. This conversation will pass its [StateFlow.value] as the first value |  | ||||||
|  * |  | ||||||
|  * @param scope Will be used to [subscribeSafelyWithoutExceptions] on [this] to update returned [State] |  | ||||||
|  * @param useContextOnChange Will be used to change context inside of [subscribeSafelyWithoutExceptions] to ensure that |  | ||||||
|  * change will happen in the required [CoroutineContext]. [Dispatchers.Main] by default |  | ||||||
|  * @param onException Will be passed to the [subscribeSafelyWithoutExceptions] as uncaught exceptions handler |  | ||||||
|  */ |  | ||||||
| @Suppress("NOTHING_TO_INLINE") | @Suppress("NOTHING_TO_INLINE") | ||||||
| inline fun <T> StateFlow<T>.asComposeState( | inline fun <T> StateFlow<T>.asComposeState( | ||||||
|     scope: CoroutineScope, |     scope: CoroutineScope | ||||||
|     useContextOnChange: CoroutineContext? = Dispatchers.Main, | ): State<T> = asComposeState(value, scope) | ||||||
|     noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull, |  | ||||||
| ): State<T> = asComposeState(value, scope, useContextOnChange, onException) |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,46 +0,0 @@ | |||||||
| 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) |  | ||||||
| @@ -0,0 +1,23 @@ | |||||||
|  | package dev.inmo.micro_utils.coroutines.compose | ||||||
|  |  | ||||||
|  | import androidx.compose.runtime.MutableState | ||||||
|  | import androidx.compose.runtime.mutableStateOf | ||||||
|  | import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions | ||||||
|  | import kotlinx.coroutines.CoroutineScope | ||||||
|  | import kotlinx.coroutines.flow.Flow | ||||||
|  | import kotlinx.coroutines.flow.StateFlow | ||||||
|  |  | ||||||
|  | fun <T> Flow<T>.toMutableState( | ||||||
|  |     initial: T, | ||||||
|  |     scope: CoroutineScope | ||||||
|  | ): MutableState<T> { | ||||||
|  |     val state = mutableStateOf(initial) | ||||||
|  |     subscribeSafelyWithoutExceptions(scope) { state.value = it } | ||||||
|  |     return state | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @Suppress("NOTHING_TO_INLINE") | ||||||
|  | inline fun <T> StateFlow<T>.toMutableState( | ||||||
|  |     scope: CoroutineScope | ||||||
|  | ): MutableState<T> = toMutableState(value, scope) | ||||||
|  |  | ||||||
							
								
								
									
										1
									
								
								coroutines/compose/src/main/AndroidManifest.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								coroutines/compose/src/main/AndroidManifest.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | <manifest package="dev.inmo.micro_utils.coroutines.compose"/> | ||||||
| @@ -1 +0,0 @@ | |||||||
| <manifest/> |  | ||||||
| @@ -1,136 +0,0 @@ | |||||||
| 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 } |  | ||||||
| @@ -1,105 +0,0 @@ | |||||||
| 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() |  | ||||||
| @@ -1,168 +0,0 @@ | |||||||
| 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 } |  | ||||||
| @@ -1,86 +0,0 @@ | |||||||
| 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) |  | ||||||
| } |  | ||||||
| @@ -1,151 +0,0 @@ | |||||||
| 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) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,26 +0,0 @@ | |||||||
| 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 |  | ||||||
| } |  | ||||||
							
								
								
									
										1
									
								
								coroutines/src/main/AndroidManifest.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								coroutines/src/main/AndroidManifest.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | <manifest package="dev.inmo.micro_utils.coroutines"/> | ||||||
| @@ -4,14 +4,13 @@ plugins { | |||||||
|     id "com.android.library" |     id "com.android.library" | ||||||
| } | } | ||||||
|  |  | ||||||
| apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64ProjectPresetPath" | apply from: "$mppProjectWithSerializationPresetPath" | ||||||
|  |  | ||||||
| kotlin { | kotlin { | ||||||
|     sourceSets { |     sourceSets { | ||||||
|         commonMain { |         commonMain { | ||||||
|             dependencies { |             dependencies { | ||||||
|                 api project(":micro_utils.common") |                 api project(":micro_utils.common") | ||||||
|                 api libs.krypto |  | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         jsMain { |         jsMain { | ||||||
|   | |||||||
| @@ -1 +0,0 @@ | |||||||
| <manifest/> |  | ||||||
| @@ -1,8 +1,6 @@ | |||||||
| package dev.inmo.micro_utils.crypto | package dev.inmo.micro_utils.crypto | ||||||
|  |  | ||||||
| import korlibs.crypto.md5 |  | ||||||
|  |  | ||||||
| typealias MD5 = String | typealias MD5 = String | ||||||
|  |  | ||||||
| fun SourceBytes.md5(): MD5 = md5().hexLower | expect fun SourceBytes.md5(): MD5 | ||||||
| fun SourceString.md5(): MD5 = encodeToByteArray().md5().hexLower | fun SourceString.md5(): MD5 = encodeToByteArray().md5() | ||||||
|   | |||||||
| @@ -0,0 +1,6 @@ | |||||||
|  | package dev.inmo.micro_utils.crypto | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @suppress | ||||||
|  |  */ | ||||||
|  | actual fun SourceBytes.md5(): MD5 = CryptoJS.MD5(decodeToString()) | ||||||
| @@ -0,0 +1,12 @@ | |||||||
|  | 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) | ||||||
							
								
								
									
										1
									
								
								crypto/src/main/AndroidManifest.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								crypto/src/main/AndroidManifest.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | <manifest package="dev.inmo.micro_utils.crypto"/> | ||||||
| @@ -1,5 +1,30 @@ | |||||||
| apply plugin: 'com.getkeepsafe.dexcount' | apply plugin: 'com.getkeepsafe.dexcount' | ||||||
|  |  | ||||||
|  | ext { | ||||||
|  |     jvmKotlinFolderFile = { | ||||||
|  |         String sep = File.separator | ||||||
|  |         return new File("${project.projectDir}${sep}src${sep}jvmMain${sep}kotlin") | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     enableIncludingJvmCodeInAndroidPart = { | ||||||
|  |         File jvmKotlinFolder = jvmKotlinFolderFile() | ||||||
|  |         if (jvmKotlinFolder.exists()) { | ||||||
|  |             android.sourceSets.main.java.srcDirs += jvmKotlinFolder.path | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     disableIncludingJvmCodeInAndroidPart = { | ||||||
|  |         File jvmKotlinFolder = jvmKotlinFolderFile() | ||||||
|  |         String[] oldDirs = android.sourceSets.main.java.srcDirs | ||||||
|  |         android.sourceSets.main.java.srcDirs = [] | ||||||
|  |         for (oldDir in oldDirs) { | ||||||
|  |             if (oldDir != jvmKotlinFolder.path) { | ||||||
|  |                 android.sourceSets.main.java.srcDirs += oldDir | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| android { | android { | ||||||
|     compileSdkVersion libs.versions.android.props.compileSdk.get().toInteger() |     compileSdkVersion libs.versions.android.props.compileSdk.get().toInteger() | ||||||
|     buildToolsVersion libs.versions.android.props.buildTools.get() |     buildToolsVersion libs.versions.android.props.buildTools.get() | ||||||
| @@ -9,7 +34,6 @@ android { | |||||||
|         targetSdkVersion libs.versions.android.props.compileSdk.get().toInteger() |         targetSdkVersion libs.versions.android.props.compileSdk.get().toInteger() | ||||||
|         versionCode "${android_code_version}".toInteger() |         versionCode "${android_code_version}".toInteger() | ||||||
|         versionName "$version" |         versionName "$version" | ||||||
|         namespace "${project.group}.${project.name}" |  | ||||||
|     } |     } | ||||||
|     buildTypes { |     buildTypes { | ||||||
|         release { |         release { | ||||||
| @@ -27,7 +51,17 @@ android { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     compileOptions { |     compileOptions { | ||||||
|         sourceCompatibility JavaVersion.VERSION_17 |         sourceCompatibility JavaVersion.VERSION_1_8 | ||||||
|         targetCompatibility JavaVersion.VERSION_17 |         targetCompatibility JavaVersion.VERSION_1_8 | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     kotlinOptions { | ||||||
|  |         jvmTarget = JavaVersion.VERSION_1_8.toString() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sourceSets { | ||||||
|  |         String sep = File.separator | ||||||
|  |         main.java.srcDirs += "src${sep}main${sep}kotlin" | ||||||
|  |         enableIncludingJvmCodeInAndroidPart() | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -13,10 +13,10 @@ repositories { | |||||||
|  |  | ||||||
| kotlin { | kotlin { | ||||||
|     jvm() |     jvm() | ||||||
|     js(IR) { | //    js(IR) { | ||||||
|         browser() | //        browser() | ||||||
|         nodejs() | //        nodejs() | ||||||
|     } | //    } | ||||||
|     android {} |     android {} | ||||||
|  |  | ||||||
|     sourceSets { |     sourceSets { | ||||||
| @@ -29,7 +29,7 @@ kotlin { | |||||||
|                         it != project |                         it != project | ||||||
|                         && it.hasProperty("kotlin") |                         && it.hasProperty("kotlin") | ||||||
|                         && it.kotlin.sourceSets.any { it.name.contains("commonMain") } |                         && it.kotlin.sourceSets.any { it.name.contains("commonMain") } | ||||||
|                                     && it.kotlin.sourceSets.any { it.name.contains("jsMain") } | //                        && it.kotlin.sourceSets.any { it.name.contains("jsMain") } | ||||||
|                         && it.kotlin.sourceSets.any { it.name.contains("jvmMain") } |                         && it.kotlin.sourceSets.any { it.name.contains("jvmMain") } | ||||||
|                         && it.kotlin.sourceSets.any { it.name.contains("androidMain") } |                         && it.kotlin.sourceSets.any { it.name.contains("androidMain") } | ||||||
|                     ) { |                     ) { | ||||||
| @@ -38,22 +38,22 @@ kotlin { | |||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         jsMain { | //        jsMain { | ||||||
|             dependencies { | //            dependencies { | ||||||
|                 implementation kotlin('stdlib') | //                implementation kotlin('stdlib') | ||||||
|  |  | ||||||
|                 project.parent.subprojects.forEach { | //                project.parent.subprojects.forEach { | ||||||
|                     if ( | //                    if ( | ||||||
|                             it != project | //                        it != project | ||||||
|                                     && it.hasProperty("kotlin") | //                        && it.hasProperty("kotlin") | ||||||
|                                     && it.kotlin.sourceSets.any { it.name.contains("commonMain") } | //                        && it.kotlin.sourceSets.any { it.name.contains("commonMain") } | ||||||
|                                     && it.kotlin.sourceSets.any { it.name.contains("jsMain") } | //                        && it.kotlin.sourceSets.any { it.name.contains("jsMain") } | ||||||
|                     ) { | //                    ) { | ||||||
|                         api it | //                        api it | ||||||
|                     } | //                    } | ||||||
|                 } | //                } | ||||||
|             } | //            } | ||||||
|         } | //        } | ||||||
|         jvmMain { |         jvmMain { | ||||||
|             dependencies { |             dependencies { | ||||||
|                 implementation kotlin('stdlib') |                 implementation kotlin('stdlib') | ||||||
| @@ -106,7 +106,7 @@ tasks.dokkaHtml { | |||||||
|             skipDeprecated.set(true) |             skipDeprecated.set(true) | ||||||
|  |  | ||||||
|             sourceLink { |             sourceLink { | ||||||
|                 localDirectory.set(file("../")) |                 localDirectory.set(file("./")) | ||||||
|                 remoteUrl.set(new URL("https://github.com/InsanusMokrassar/MicroUtils/blob/master/")) |                 remoteUrl.set(new URL("https://github.com/InsanusMokrassar/MicroUtils/blob/master/")) | ||||||
|                 remoteLineSuffix.set("#L") |                 remoteLineSuffix.set("#L") | ||||||
|             } |             } | ||||||
| @@ -116,9 +116,9 @@ tasks.dokkaHtml { | |||||||
|             sourceRoots.setFrom(findSourcesWithName("commonMain")) |             sourceRoots.setFrom(findSourcesWithName("commonMain")) | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         named("jsMain") { | //        named("jsMain") { | ||||||
|             sourceRoots.setFrom(findSourcesWithName("jsMain")) | //            sourceRoots.setFrom(findSourcesWithName("jsMain", "commonMain")) | ||||||
|         } | //        } | ||||||
|  |  | ||||||
|         named("jvmMain") { |         named("jvmMain") { | ||||||
|             sourceRoots.setFrom(findSourcesWithName("jvmMain")) |             sourceRoots.setFrom(findSourcesWithName("jvmMain")) | ||||||
|   | |||||||
| @@ -1 +0,0 @@ | |||||||
| <manifest/> |  | ||||||
							
								
								
									
										1
									
								
								dokka/src/main/AndroidManifest.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								dokka/src/main/AndroidManifest.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | <manifest package="dev.inmo.dokka"/> | ||||||
| @@ -19,24 +19,16 @@ allprojects { | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         releaseMode = (project.hasProperty('RELEASE_MODE') && project.property('RELEASE_MODE') == "true") || System.getenv('RELEASE_MODE') == "true" |         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}/mppJvmJsAndroidProject.gradle" |         mppProjectWithSerializationPresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerialization.gradle" | ||||||
|         mppProjectWithSerializationAndComposePresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerializationAndCompose.gradle" |         mppProjectWithSerializationAndComposePresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerializationAndCompose.gradle" | ||||||
|         mppJavaProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJavaProject.gradle" |         mppJavaProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJavaProject.gradle" | ||||||
|         mppJvmJsLinuxMingwProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJvmJsLinuxMingwProject.gradle" |         mppJsAndJavaProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJsAndJavaProject.gradle" | ||||||
|         mppJvmJsLinuxMingwLinuxArm64ProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJvmJsLinuxMingwLinuxArm64Project.gradle" |  | ||||||
|         mppJvmJsAndroidLinuxMingwLinuxArm64ProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJvmJsAndroidLinuxMingwLinuxArm64Project.gradle" |  | ||||||
|         mppAndroidProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppAndroidProject.gradle" |         mppAndroidProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppAndroidProject.gradle" | ||||||
|  |  | ||||||
|         defaultAndroidSettingsPresetPath = "${rootProject.projectDir.absolutePath}/defaultAndroidSettings.gradle" |         defaultAndroidSettingsPresetPath = "${rootProject.projectDir.absolutePath}/defaultAndroidSettings.gradle" | ||||||
|  |  | ||||||
|         publishGradlePath = "${rootProject.projectDir.absolutePath}/publish.gradle" |         publishGradlePath = "${rootProject.projectDir.absolutePath}/publish.gradle" | ||||||
|         publishJvmOnlyPath = "${rootProject.projectDir.absolutePath}/jvm.publish.gradle" |         publishMavenPath = "${rootProject.projectDir.absolutePath}/maven.publish.gradle" | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ plugins { | |||||||
|     id "com.android.library" |     id "com.android.library" | ||||||
| } | } | ||||||
|  |  | ||||||
| apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64ProjectPresetPath" | apply from: "$mppProjectWithSerializationPresetPath" | ||||||
|  |  | ||||||
| kotlin { | kotlin { | ||||||
|     sourceSets { |     sourceSets { | ||||||
|   | |||||||
| @@ -1 +0,0 @@ | |||||||
| <manifest/> |  | ||||||
| @@ -48,8 +48,8 @@ interface DefaultStatesManagerRepo<T : State> { | |||||||
|  */ |  */ | ||||||
| open class DefaultStatesManager<T : State>( | open class DefaultStatesManager<T : State>( | ||||||
|     protected val repo: DefaultStatesManagerRepo<T> = InMemoryDefaultStatesManagerRepo(), |     protected val repo: DefaultStatesManagerRepo<T> = InMemoryDefaultStatesManagerRepo(), | ||||||
|     protected val onStartContextsConflictResolver: suspend (current: T, new: T) -> Boolean = { _, _ -> false }, |     protected val onStartContextsConflictResolver: suspend (current: T, new: T) -> Boolean = { _, _ -> true }, | ||||||
|     protected val onUpdateContextsConflictResolver: suspend (old: T, new: T, currentNew: T) -> Boolean = { _, _, _ -> false } |     protected val onUpdateContextsConflictResolver: suspend (old: T, new: T, currentNew: T) -> Boolean = { _, _, _ -> true } | ||||||
| ) : StatesManager<T> { | ) : StatesManager<T> { | ||||||
|     protected val _onChainStateUpdated = MutableSharedFlow<Pair<T, T>>(0) |     protected val _onChainStateUpdated = MutableSharedFlow<Pair<T, T>>(0) | ||||||
|     override val onChainStateUpdated: Flow<Pair<T, T>> = _onChainStateUpdated.asSharedFlow() |     override val onChainStateUpdated: Flow<Pair<T, T>> = _onChainStateUpdated.asSharedFlow() | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								fsm/common/src/main/AndroidManifest.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								fsm/common/src/main/AndroidManifest.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | <manifest package="dev.inmo.micro_utils.fsm.common"/> | ||||||
| @@ -4,7 +4,7 @@ plugins { | |||||||
|     id "com.android.library" |     id "com.android.library" | ||||||
| } | } | ||||||
|  |  | ||||||
| apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64ProjectPresetPath" | apply from: "$mppProjectWithSerializationPresetPath" | ||||||
|  |  | ||||||
| kotlin { | kotlin { | ||||||
|     sourceSets { |     sourceSets { | ||||||
|   | |||||||
| @@ -1 +0,0 @@ | |||||||
| <manifest/> |  | ||||||
							
								
								
									
										1
									
								
								fsm/repos/common/src/main/AndroidManifest.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								fsm/repos/common/src/main/AndroidManifest.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | <manifest package="dev.inmo.micro_utils.fsm.repos.common"/> | ||||||
| @@ -18,13 +18,13 @@ if (new File(projectDir, "secret.gradle").exists()) { | |||||||
|     githubRelease { |     githubRelease { | ||||||
|         token "${project.property('GITHUB_RELEASE_TOKEN')}" |         token "${project.property('GITHUB_RELEASE_TOKEN')}" | ||||||
|  |  | ||||||
|         owner = "InsanusMokrassar" |         owner "InsanusMokrassar" | ||||||
|         repo = "MicroUtils" |         repo "MicroUtils" | ||||||
|  |  | ||||||
|         tagName = "v${project.version}" |         tagName "v${project.version}" | ||||||
|         releaseName = "${project.version}" |         releaseName "${project.version}" | ||||||
|         targetCommitish = "${project.version}" |         targetCommitish "${project.version}" | ||||||
|  |  | ||||||
|         body = getCurrentVersionChangelog() |         body getCurrentVersionChangelog() | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -3,10 +3,9 @@ org.gradle.parallel=true | |||||||
| kotlin.js.generate.externals=true | kotlin.js.generate.externals=true | ||||||
| kotlin.incremental=true | kotlin.incremental=true | ||||||
| kotlin.incremental.js=true | kotlin.incremental.js=true | ||||||
| #kotlin.experimental.tryK2=true |  | ||||||
| android.useAndroidX=true | android.useAndroidX=true | ||||||
| android.enableJetifier=true | android.enableJetifier=true | ||||||
| org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=2g | org.gradle.jvmargs=-Xmx2g | ||||||
|  |  | ||||||
| # JS NPM | # JS NPM | ||||||
|  |  | ||||||
| @@ -15,5 +14,5 @@ crypto_js_version=4.1.1 | |||||||
| # Project data | # Project data | ||||||
|  |  | ||||||
| group=dev.inmo | group=dev.inmo | ||||||
| version=0.20.39 | version=0.16.3 | ||||||
| android_code_version=245 | android_code_version=171 | ||||||
|   | |||||||
| @@ -1,45 +1,37 @@ | |||||||
| [versions] | [versions] | ||||||
|  |  | ||||||
| kt = "1.9.23" | kt = "1.7.20" | ||||||
| kt-serialization = "1.6.3" | kt-serialization = "1.4.1" | ||||||
| kt-coroutines = "1.8.0" | kt-coroutines = "1.6.4" | ||||||
|  |  | ||||||
| kslog = "1.3.2" | kslog = "0.5.4" | ||||||
|  |  | ||||||
| jb-compose = "1.6.1" | jb-compose = "1.2.2" | ||||||
| jb-exposed = "0.48.0" | jb-exposed = "0.41.1" | ||||||
| jb-dokka = "1.9.20" | jb-dokka = "1.7.20" | ||||||
|  |  | ||||||
| korlibs = "5.4.0" | klock = "3.4.0" | ||||||
| uuid = "0.8.2" | uuid = "0.6.0" | ||||||
|  |  | ||||||
| ktor = "2.3.9" | ktor = "2.2.1" | ||||||
|  |  | ||||||
| gh-release = "2.5.2" | gh-release = "2.4.1" | ||||||
|  |  | ||||||
| koin = "3.5.3" | koin = "3.2.2" | ||||||
|  |  | ||||||
| okio = "3.9.0" | android-gradle = "7.3.0" | ||||||
|  | dexcount = "3.1.0" | ||||||
|  |  | ||||||
| ksp = "1.9.23-1.0.19" | android-coreKtx = "1.9.0" | ||||||
| kotlin-poet = "1.16.0" | android-recyclerView = "1.2.1" | ||||||
|  | android-appCompat = "1.5.1" | ||||||
| versions = "0.51.0" | android-fragment = "1.5.5" | ||||||
|  | android-espresso = "3.4.0" | ||||||
| android-gradle = "8.3.0" | android-test = "1.1.3" | ||||||
| dexcount = "4.0.0" |  | ||||||
|  |  | ||||||
| android-coreKtx = "1.12.0" |  | ||||||
| android-recyclerView = "1.3.2" |  | ||||||
| android-appCompat = "1.6.1" |  | ||||||
| android-fragment = "1.6.2" |  | ||||||
| android-espresso = "3.5.1" |  | ||||||
| android-test = "1.1.5" |  | ||||||
| android-compose-material3 = "1.2.1" |  | ||||||
|  |  | ||||||
| android-props-minSdk = "21" | android-props-minSdk = "21" | ||||||
| android-props-compileSdk = "34" | android-props-compileSdk = "33" | ||||||
| android-props-buildTools = "34.0.0" | android-props-buildTools = "33.0.0" | ||||||
|  |  | ||||||
| [libraries] | [libraries] | ||||||
|  |  | ||||||
| @@ -72,8 +64,7 @@ ktor-server-content-negotiation = { module = "io.ktor:ktor-server-content-negoti | |||||||
|  |  | ||||||
| kslog = { module = "dev.inmo:kslog", version.ref = "kslog" } | kslog = { module = "dev.inmo:kslog", version.ref = "kslog" } | ||||||
|  |  | ||||||
| klock = { module = "com.soywiz.korge:korlibs-time", version.ref = "korlibs" } | klock = { module = "com.soywiz.korlibs.klock:klock", version.ref = "klock" } | ||||||
| krypto = { module = "com.soywiz.korge:korlibs-crypto", version.ref = "korlibs" } |  | ||||||
| uuid = { module = "com.benasher44:uuid", version.ref = "uuid" } | uuid = { module = "com.benasher44:uuid", version.ref = "uuid" } | ||||||
| koin = { module = "io.insert-koin:koin-core", version.ref = "koin" } | koin = { module = "io.insert-koin:koin-core", version.ref = "koin" } | ||||||
|  |  | ||||||
| @@ -84,7 +75,6 @@ jb-exposed = { module = "org.jetbrains.exposed:exposed-core", version.ref = "jb- | |||||||
| android-coreKtx = { module = "androidx.core:core-ktx", version.ref = "android-coreKtx" } | android-coreKtx = { module = "androidx.core:core-ktx", version.ref = "android-coreKtx" } | ||||||
| android-recyclerView = { module = "androidx.recyclerview:recyclerview", version.ref = "android-recyclerView" } | android-recyclerView = { module = "androidx.recyclerview:recyclerview", version.ref = "android-recyclerView" } | ||||||
| android-appCompat-resources = { module = "androidx.appcompat:appcompat-resources", version.ref = "android-appCompat" } | 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-fragment = { module = "androidx.fragment:fragment", version.ref = "android-fragment" } | ||||||
| android-espresso = { module = "androidx.test.espresso:espresso-core", version.ref = "android-espresso" } | android-espresso = { module = "androidx.test.espresso:espresso-core", version.ref = "android-espresso" } | ||||||
| android-test-junit = { module = "androidx.test.ext:junit", version.ref = "android-test" } | android-test-junit = { module = "androidx.test.ext:junit", version.ref = "android-test" } | ||||||
| @@ -93,18 +83,9 @@ android-test-junit = { module = "androidx.test.ext:junit", version.ref = "androi | |||||||
| kt-test-js = { module = "org.jetbrains.kotlin:kotlin-test-js", version.ref = "kt" } | kt-test-js = { module = "org.jetbrains.kotlin:kotlin-test-js", version.ref = "kt" } | ||||||
| kt-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kt" } | kt-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kt" } | ||||||
|  |  | ||||||
| # ksp dependencies |  | ||||||
|  |  | ||||||
| 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" } | buildscript-kt-gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kt" } | ||||||
| buildscript-kt-serialization = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kt" } | buildscript-kt-serialization = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kt" } | ||||||
| buildscript-kt-ksp = { module = "com.google.devtools.ksp:symbol-processing-gradle-plugin", version.ref = "ksp" } |  | ||||||
| buildscript-jb-dokka = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "jb-dokka" } | buildscript-jb-dokka = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "jb-dokka" } | ||||||
| buildscript-gh-release = { module = "com.github.breadmoirai:github-release", version.ref = "gh-release" } | buildscript-gh-release = { module = "com.github.breadmoirai:github-release", version.ref = "gh-release" } | ||||||
| buildscript-android-gradle = { module = "com.android.tools.build:gradle", version.ref = "android-gradle" } | buildscript-android-gradle = { module = "com.android.tools.build:gradle", version.ref = "android-gradle" } | ||||||
| @@ -113,5 +94,3 @@ buildscript-android-dexcount = { module = "com.getkeepsafe.dexcount:dexcount-gra | |||||||
| [plugins] | [plugins] | ||||||
|  |  | ||||||
| jb-compose = { id = "org.jetbrains.compose", version.ref = "jb-compose" } | jb-compose = { id = "org.jetbrains.compose", version.ref = "jb-compose" } | ||||||
|  |  | ||||||
| versions = { id = "com.github.ben-manes.versions", version.ref = "versions" } |  | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							| @@ -1,5 +1,5 @@ | |||||||
| distributionBase=GRADLE_USER_HOME | distributionBase=GRADLE_USER_HOME | ||||||
| distributionPath=wrapper/dists | distributionPath=wrapper/dists | ||||||
| distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip | ||||||
| zipStoreBase=GRADLE_USER_HOME | zipStoreBase=GRADLE_USER_HOME | ||||||
| zipStorePath=wrapper/dists | zipStorePath=wrapper/dists | ||||||
|   | |||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user