mirror of
				https://github.com/InsanusMokrassar/MicroUtils.git
				synced 2025-11-04 06:00:22 +00:00 
			
		
		
		
	Compare commits
	
		
			303 Commits
		
	
	
		
			v0.12.10
			...
			revert-245
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 940ee3df06 | |||
| 2e7bab10fd | |||
| 
						 | 
					3ed70a37ea | ||
| fe8f80b9d9 | |||
| d81fb32fb9 | |||
| 2877b5532c | |||
| b938b21395 | |||
| 58836359cc | |||
| 5edb0e1331 | |||
| 0f0d0b5d58 | |||
| 46c1887cbe | |||
| 5f231c2212 | |||
| 4e97ce86aa | |||
| 315a7cb29e | |||
| aa7cc503f2 | |||
| 4bbe7e5a80 | |||
| d9c05f38d2 | |||
| cd0c4c9650 | |||
| fc3407f104 | |||
| 3a5544206b | |||
| e17e2f7fb8 | |||
| 
						 | 
					d32c95f143 | ||
| 6d8a8ab018 | |||
| a7dce8fa78 | |||
| ca73ff8e19 | |||
| d01ad10d7d | |||
| 81041ee43c | |||
| 
						 | 
					6e004c2ae4 | ||
| 0e2fac5b22 | |||
| 269da7f155 | |||
| 3cb6b73ee0 | |||
| a938ee1efb | |||
| 6ea5e2e5a6 | |||
| 617dfb54e0 | |||
| d23e005985 | |||
| e5207f5bc5 | |||
| c96cea8db0 | |||
| 0a8e71d76a | |||
| cf1fd32b08 | |||
| cc4224ea1f | |||
| f4c148bc58 | |||
| 022297ad3f | |||
| 5180d6fc3e | |||
| eeebbff70d | |||
| afc6aeea15 | |||
| 486515eddd | |||
| 0e21699cd1 | |||
| f1678ef7cf | |||
| cea65fc76e | |||
| c9e320b72a | |||
| 555956087d | |||
| b3f468f901 | |||
| f5f7511781 | |||
| 4be1d93f60 | |||
| 7d684608ef | |||
| 2c7fd320eb | |||
| 88ee82e1c6 | |||
| d6402c624e | |||
| 8b9c93bc10 | |||
| 4f5e261d01 | |||
| cf455aebe6 | |||
| 1380d5f8e1 | |||
| 5f65260bfe | |||
| 11f0dcfc01 | |||
| 555b7886a4 | |||
| 3707a6c0ce | |||
| 4c8d92b4c3 | |||
| 8bbd33c896 | |||
| ac33a3580f | |||
| a64a32fbe6 | |||
| 9493e97a38 | |||
| 88bd770260 | |||
| a7bd33b7bf | |||
| 73c724a2e5 | |||
| d8cf3c6726 | |||
| 15dee238b5 | |||
| c70626734e | |||
| 5a765ea1bc | |||
| 8215f9d2c6 | |||
| d2e6d2ec80 | |||
| 3718181e8f | |||
| 0d825cf424 | |||
| 28a804d988 | |||
| 9e4bb9d678 | |||
| 9c40d7da3d | |||
| 2b76ad0aa9 | |||
| e4b619e050 | |||
| 36c09feaf2 | |||
| 2d68321503 | |||
| 85455ab21c | |||
| 18d63eb980 | |||
| 2e429e9704 | |||
| f4af28059b | |||
| c1476bd075 | |||
| 16c720fddd | |||
| 8b4b4a5eca | |||
| 32e6e5b7e2 | |||
| a9f7fd8e32 | |||
| 95be1a26f2 | |||
| ef9b31aee0 | |||
| df3c01ff0a | |||
| 4704c5a33d | |||
| 225c06550a | |||
| f0987614c6 | |||
| 269c2876f3 | |||
| 168d6acf7c | |||
| a5f718e257 | |||
| 4f68459582 | |||
| 442db122cf | |||
| 580d757be2 | |||
| 47b0f6d2d8 | |||
| 3f6f6ebc2b | |||
| 2645ea29d6 | |||
| 79f2041565 | |||
| 4a7567f288 | |||
| 8a890ed6ed | |||
| 3d90df6897 | |||
| 681c13144a | |||
| b64f2e6d32 | |||
| 428eabb1bd | |||
| 2162e83bce | |||
| 6142022283 | |||
| e6d9c8250f | |||
| 46178e723b | |||
| 605f55acd2 | |||
| 0f8b69aa60 | |||
| 551d8ec480 | |||
| fc48446ec4 | |||
| 3644b83ac6 | |||
| cd73791b6f | |||
| 03de71df2e | |||
| 83d5d3faf4 | |||
| 0c8bec4c89 | |||
| 7fc93817c1 | |||
| d0a00031a1 | |||
| 6ebc5aa0c2 | |||
| 8a6b4bb49e | |||
| 20799b9a3e | |||
| ec3afc615c | |||
| da692ccfc3 | |||
| 53b89f3a18 | |||
| 58cded28d3 | |||
| 592c5f3732 | |||
| f44a78a5f5 | |||
| e0bdd5dfdc | |||
| 99c0f06b72 | |||
| 66fc6df3d7 | |||
| a36425a905 | |||
| d920fee6d4 | |||
| 23590be5de | |||
| 94acc3c93b | |||
| 5616326a3b | |||
| 7601860c5c | |||
| 8b43d785cc | |||
| b62d3a0b7d | |||
| fad73c7213 | |||
| 2403c7c2b0 | |||
| fa090bf920 | |||
| a83ee86340 | |||
| 204955bcce | |||
| ee56e9543a | |||
| 96fdff6ffd | |||
| 58b007cbb3 | |||
| 4f0c139889 | |||
| c584c24fce | |||
| 85e5cee154 | |||
| 5af91981f1 | |||
| 2fe4f08059 | |||
| 83796f345a | |||
| c1e21364a6 | |||
| 067d9d0d3b | |||
| 03f527d83e | |||
| ced05a4586 | |||
| 43fe06206a | |||
| 023657558e | |||
| 9b0b726c80 | |||
| 4ee67321c4 | |||
| 59f1f2e59b | |||
| 0766d48b7c | |||
| e18903b9e9 | |||
| d0eecdead2 | |||
| cc4a83a033 | |||
| 1cf911bbde | |||
| 36d73d5023 | |||
| c395242e3e | |||
| cd9cd7cc5d | |||
| acbb8a0c07 | |||
| b9d8528599 | |||
| 4971326eca | |||
| 09d1047260 | |||
| 02dbd493c2 | |||
| b17931e7bd | |||
| 2a4570eafc | |||
| c9514d3a6d | |||
| 072805efc7 | |||
| 369ff26627 | |||
| c5abbbbd2d | |||
| d974639f1e | |||
| 26efde316b | |||
| fafe50f80a | |||
| 41504469db | |||
| 03b3ddd98b | |||
| 89d919f2be | |||
| b53cfd5504 | |||
| 31022733ac | |||
| f9a8c39879 | |||
| a812c2dd2f | |||
| 217e977f0d | |||
| 04c301d1ac | |||
| 7f0c425389 | |||
| 1ede1c423b | |||
| 7cb064896a | |||
| 0c5e2862ca | |||
| 30d4507f54 | |||
| b6c6d89455 | |||
| 9d218ee534 | |||
| 11116d8cab | |||
| 58d754bbde | |||
| 8f25c123dd | |||
| 76e214fc08 | |||
| 2b5380f8d6 | |||
| 844a129094 | |||
| a3090cca9d | |||
| b7b5159e9c | |||
| 0f8bc2c950 | |||
| 69f5c49f45 | |||
| 9b308e6fb8 | |||
| 3e3f91128b | |||
| 0d1aae0ef7 | |||
| 4d022f0480 | |||
| 153e20d00e | |||
| a9a8171dd6 | |||
| bf5c3b59b2 | |||
| 607c432bdb | |||
| ae5c010770 | |||
| d5e432437f | |||
| 9f99ebce01 | |||
| 64ee899b84 | |||
| e0e0c1658b | |||
| 2c586f667c | |||
| 64164ef6c1 | |||
| 22343c0731 | |||
| f4ec1a4c60 | |||
| c1c33cceb1 | |||
| a3e975b2ba | |||
| 06e705a687 | |||
| d56eb6c867 | |||
| 9cbca864e3 | |||
| abb4378694 | |||
| 0eb698d9a4 | |||
| 15ea9f2093 | |||
| d47aca0923 | |||
| 1ac50e9959 | |||
| 6adfbe3a96 | |||
| 59f36e62e9 | |||
| 54af116009 | |||
| 38fbec8e3b | |||
| babbfc55e4 | |||
| 2511e18d69 | |||
| 29658c70a0 | |||
| 96311ee43d | |||
| bd33b09052 | |||
| 8055003b47 | |||
| 1257492f85 | |||
| 1107b7f4ef | |||
| a1a1171240 | |||
| 46c02e5df1 | |||
| 2e9efc57de | |||
| acecadef17 | |||
| 19394b5e69 | |||
| de999e197f | |||
| 9d95687d3c | |||
| aa9dfb4ab8 | |||
| 9c5b44efb3 | |||
| ac587a67e6 | |||
| 59428140a8 | |||
| 60bdb59d71 | |||
| be52871de8 | |||
| b7934cf357 | |||
| dbfbeef90a | |||
| 00943c9cdf | |||
| 8745c6a16a | |||
| 433ba4b58f | |||
| d40376e524 | |||
| a2982f88f5 | |||
| 1642f7abd9 | |||
| a10d2184ff | |||
| 522435f096 | |||
| 79b30290c0 | |||
| f8b8626859 | |||
| b061b85a08 | |||
| 3870db1c88 | |||
| 1be1070eb4 | |||
| 2696e663cf | |||
| 1e1f7df86d | |||
| 1d8ded8fd3 | |||
| 197825123a | |||
| 422b2e6db1 | |||
| 1973e0b5bf | |||
| 8258cf93a9 | |||
| 1d49bd5947 | |||
| 44317d1519 | |||
| 48e08fcc69 | 
							
								
								
									
										14
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										14
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							@@ -9,9 +9,6 @@ jobs:
 | 
			
		||||
      - uses: actions/setup-java@v1
 | 
			
		||||
        with:
 | 
			
		||||
          java-version: 11
 | 
			
		||||
      - name: Fix android 32.0.0 dx
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
        run: cd /usr/local/lib/android/sdk/build-tools/32.0.0/ && mv d8 dx && cd lib  && mv d8.jar dx.jar
 | 
			
		||||
      - name: Rewrite version
 | 
			
		||||
        run: |
 | 
			
		||||
          branch="`echo "${{ github.ref }}" | grep -o "[^/]*$"`"
 | 
			
		||||
@@ -20,9 +17,8 @@ jobs:
 | 
			
		||||
          mv gradle.properties.tmp gradle.properties
 | 
			
		||||
      - name: Build
 | 
			
		||||
        run: ./gradlew build
 | 
			
		||||
#      - name: Publish
 | 
			
		||||
#        continue-on-error: true
 | 
			
		||||
#        run: ./gradlew --no-parallel publishAllPublicationsToGithubPackagesRepository
 | 
			
		||||
#        env:
 | 
			
		||||
#          GITHUBPACKAGES_USER: ${{ github.actor }}
 | 
			
		||||
#          GITHUBPACKAGES_PASSWORD: ${{ secrets.GITHUB_TOKEN }}
 | 
			
		||||
      - name: Publish
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
        run: ./gradlew publishAllPublicationsToGiteaRepository
 | 
			
		||||
        env:
 | 
			
		||||
          GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										3
									
								
								.github/workflows/dokka_push.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.github/workflows/dokka_push.yml
									
									
									
									
										vendored
									
									
								
							@@ -11,9 +11,6 @@ jobs:
 | 
			
		||||
      - uses: actions/setup-java@v1
 | 
			
		||||
        with:
 | 
			
		||||
          java-version: 11
 | 
			
		||||
      - name: Fix android 32.0.0 dx
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
        run: cd /usr/local/lib/android/sdk/build-tools/32.0.0/ && mv d8 dx && cd lib  && mv d8.jar dx.jar
 | 
			
		||||
      - name: Build
 | 
			
		||||
        run: ./gradlew build && ./gradlew dokkaHtml
 | 
			
		||||
      - name: Publish KDocs
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +0,0 @@
 | 
			
		||||
job("Build and run tests") {
 | 
			
		||||
    container(displayName = "Run gradle build", image = "openjdk:11") {
 | 
			
		||||
        kotlinScript { api ->
 | 
			
		||||
            // here can be your complex logic
 | 
			
		||||
            api.gradlew("build")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										371
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										371
									
								
								CHANGELOG.md
									
									
									
									
									
								
							@@ -1,5 +1,376 @@
 | 
			
		||||
# Changelog
 | 
			
		||||
 | 
			
		||||
## 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
 | 
			
		||||
 | 
			
		||||
* `Startup`:
 | 
			
		||||
    * `Launcher`:
 | 
			
		||||
        * All starting API have been moved into `StartLauncherPlugin` and do not require serialize/deserialize cycle for now
 | 
			
		||||
 | 
			
		||||
## 0.16.2
 | 
			
		||||
 | 
			
		||||
* `Versions`:
 | 
			
		||||
    * `Compose`: `1.2.1` -> `1.2.2`
 | 
			
		||||
* `Startup`:
 | 
			
		||||
    * Module become available on `JS` target
 | 
			
		||||
 | 
			
		||||
## 0.16.1
 | 
			
		||||
 | 
			
		||||
* `Coroutines`:
 | 
			
		||||
    * New `runCatchingSafely`/`safelyWithResult` with receivers
 | 
			
		||||
* `SafeWrapper`:
 | 
			
		||||
    * Module inited
 | 
			
		||||
 | 
			
		||||
## 0.16.0
 | 
			
		||||
 | 
			
		||||
* `Versions`:
 | 
			
		||||
    * `Ktor`: `2.1.3` -> `2.2.1`
 | 
			
		||||
    * `Android Fragment`: `1.5.3` -> `1.5.5`
 | 
			
		||||
 | 
			
		||||
## 0.15.1
 | 
			
		||||
 | 
			
		||||
* `Startup`:
 | 
			
		||||
    * Inited :)
 | 
			
		||||
    * `Plugin`:
 | 
			
		||||
        * Inited :)
 | 
			
		||||
    * `Launcher`:
 | 
			
		||||
        * Inited :)
 | 
			
		||||
 | 
			
		||||
## 0.15.0
 | 
			
		||||
 | 
			
		||||
* `Repos`:
 | 
			
		||||
    * `CRUD`:
 | 
			
		||||
        * `Common`:
 | 
			
		||||
            * New method `ReadCRUDRepo#getIdsByPagination`
 | 
			
		||||
        * `Android`:
 | 
			
		||||
            * `AbstractAndroidCRUDRepo` got new abstract method `toId`
 | 
			
		||||
        * `Exposed`:
 | 
			
		||||
            * `CommonExposedRepo` new abstract property `asId`
 | 
			
		||||
        * `Ktor`:
 | 
			
		||||
            * `Client`:
 | 
			
		||||
                * `KtorReadCRUDRepoClient` now requires `paginationIdType`
 | 
			
		||||
* `LanguageCodes`:
 | 
			
		||||
    * Updates and fixes in generation
 | 
			
		||||
* `MimeTypes`:
 | 
			
		||||
    * Updates and fixes in generation
 | 
			
		||||
 | 
			
		||||
## 0.14.4
 | 
			
		||||
 | 
			
		||||
* `Common`:
 | 
			
		||||
    * `JVM`:
 | 
			
		||||
        * New extension `downloadToTempFile`
 | 
			
		||||
* `Ktor`:
 | 
			
		||||
    * `Server`:
 | 
			
		||||
        * Small fix in `handleUniUpload`
 | 
			
		||||
        * `ApplicationCall#uniloadMultipartFile` now uses `uniloadMultipart`
 | 
			
		||||
    * `Common`:
 | 
			
		||||
        * New extension `downloadToTempFile`
 | 
			
		||||
    * `Client`:
 | 
			
		||||
        * New extensions on top of `uniUpload`
 | 
			
		||||
 | 
			
		||||
## 0.14.3
 | 
			
		||||
 | 
			
		||||
* `Common`:
 | 
			
		||||
    * New type `Progress`
 | 
			
		||||
* `Ktor`:
 | 
			
		||||
    * `Client`:
 | 
			
		||||
        * New universal `uniUpload` extension for `HttpClient`
 | 
			
		||||
    * `Server`:
 | 
			
		||||
        * New universal `handleUniUpload` extension for `ApplicationCall`
 | 
			
		||||
        * Add extensions `download` and `downloadToTemporalFile`
 | 
			
		||||
 | 
			
		||||
## 0.14.2
 | 
			
		||||
 | 
			
		||||
* `Versions`:
 | 
			
		||||
    * `Exposed`: `0.40.1` -> `0.41.1`
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 0.14.1
 | 
			
		||||
 | 
			
		||||
* `Versions`:
 | 
			
		||||
    * `Klock`: `3.3.1` -> `3.4.0`
 | 
			
		||||
    * `UUID`: `0.5.0` -> `0.6.0`
 | 
			
		||||
 | 
			
		||||
## 0.14.0
 | 
			
		||||
 | 
			
		||||
**ALL DEPRECATIONS HAVE BEEN REMOVED**
 | 
			
		||||
 | 
			
		||||
* `Versions`:
 | 
			
		||||
    * `Kotlin`: `1.7.10` -> `1.7.20`
 | 
			
		||||
    * `Klock`: `3.3.0` -> `3.3.1`
 | 
			
		||||
    * `Compose`: `1.2.0` -> `1.2.1`
 | 
			
		||||
    * `Exposed`: `0.39.2` -> `0.40.1`
 | 
			
		||||
 | 
			
		||||
## 0.13.2
 | 
			
		||||
 | 
			
		||||
* `Versions`:
 | 
			
		||||
    * `Klock`: `3.1.0` -> `3.3.0`
 | 
			
		||||
    * `Ktor`: `2.1.2` -> `2.1.3`
 | 
			
		||||
 | 
			
		||||
## 0.13.1
 | 
			
		||||
 | 
			
		||||
* `Repos`:
 | 
			
		||||
    * `Exposed`:
 | 
			
		||||
      * `AbstractExposedWriteCRUDRepo#createAndInsertId` now is optional and returns nullable value
 | 
			
		||||
 | 
			
		||||
## 0.13.0
 | 
			
		||||
 | 
			
		||||
**ALL DEPRECATIONS HAVE BEEN REMOVED**
 | 
			
		||||
**A LOT OF KTOR METHODS RELATED TO UnifierRouter/UnifiedRequester HAVE BEEN REMOVED**
 | 
			
		||||
 | 
			
		||||
* `Repos`:
 | 
			
		||||
  * `Exposed`:
 | 
			
		||||
    * `AbstractExposedWriteCRUDRepo` got two new methods: `update` with `it` as `UpdateBuilder<Int>` and `createAndInsertId`
 | 
			
		||||
      * Old `update` method has been deprecated and not recommended to override anymore in realizations
 | 
			
		||||
      * Old `insert` method now is `open` instead of `abstract` and can be omitted
 | 
			
		||||
    * `AbstractExposedKeyValueRepo` got two new methods: `update` with `it` as `UpdateBuilder<Int>` and `insertKey`
 | 
			
		||||
      * Old `update` method has been deprecated and not recommended to override anymore
 | 
			
		||||
      * Old `insert` method now is `open` instead of `abstract` and can be omitted in realizations
 | 
			
		||||
 | 
			
		||||
## 0.12.17
 | 
			
		||||
 | 
			
		||||
* `Versions`:
 | 
			
		||||
    * `JB Compose`: `1.2.0-alpha01-dev774` -> `1.2.0-beta02`
 | 
			
		||||
    * `Ktor`: `2.1.1` -> `2.1.2`
 | 
			
		||||
    * `Koin`: `3.2.0` -> `3.2.2`
 | 
			
		||||
 | 
			
		||||
## 0.12.16
 | 
			
		||||
 | 
			
		||||
* `Coroutines`:
 | 
			
		||||
  * `Android`:
 | 
			
		||||
    * Add class `FlowOnHierarchyChangeListener`
 | 
			
		||||
    * Add `ViewGroup#setOnHierarchyChangeListenerRecursively(OnHierarchyChangeListener)`
 | 
			
		||||
 | 
			
		||||
## 0.12.15
 | 
			
		||||
 | 
			
		||||
* `Common`:
 | 
			
		||||
    * `applyDiff` will return `Diff` object since this release
 | 
			
		||||
    * `Android`:
 | 
			
		||||
      * New functions/extensions `findViewsByTag` and `findViewsByTagInActivity`
 | 
			
		||||
* `Coroutines`:
 | 
			
		||||
    * Add `Flow` extensions `flatMap`, `flatMapNotNull` and `flatten`
 | 
			
		||||
    * Add `Flow` extensions `takeNotNull` and `filterNotNull`
 | 
			
		||||
 | 
			
		||||
## 0.12.14
 | 
			
		||||
 | 
			
		||||
* `Versions`:
 | 
			
		||||
  * `Android CoreKTX`: `1.8.0` -> `1.9.0`
 | 
			
		||||
  * `Android AppCompat`: `1.4.2` -> `1.5.1`
 | 
			
		||||
  * Android Compile SDK: 32 -> 33
 | 
			
		||||
  * Android Build Tools: 32.0.0 -> 33.0.0
 | 
			
		||||
* `Common`:
 | 
			
		||||
  * `Android`:
 | 
			
		||||
    * Add `argumentOrNull`/`argumentOrThrow` delegates for fragments
 | 
			
		||||
* `Coroutines`:
 | 
			
		||||
  * Rewrite `awaitFirstWithDeferred` onto `CompletableDeferred` instead of coroutines suspending
 | 
			
		||||
 | 
			
		||||
## 0.12.13
 | 
			
		||||
 | 
			
		||||
* `Coroutines`:
 | 
			
		||||
  * Add opportunity to use markers in actors (solution of [#160](https://github.com/InsanusMokrassar/MicroUtils/issues/160))
 | 
			
		||||
* `Koin`:
 | 
			
		||||
  * Module inited :)
 | 
			
		||||
* `Repos`:
 | 
			
		||||
  * `Android`:
 | 
			
		||||
    * Add typealias `KeyValueSPRepo` and opportunity to create shared preferences `KeyValue` repo with `KeyValueStore(...)` (fix of [#155](https://github.com/InsanusMokrassar/MicroUtils/issues/155))
 | 
			
		||||
 | 
			
		||||
## 0.12.12
 | 
			
		||||
 | 
			
		||||
* `Common`:
 | 
			
		||||
  * `Compose`:
 | 
			
		||||
    * `JS`:
 | 
			
		||||
      * Add `SkeletonAnimation` stylesheet
 | 
			
		||||
 | 
			
		||||
## 0.12.11
 | 
			
		||||
 | 
			
		||||
* `Repos`:
 | 
			
		||||
    * `Cache`:
 | 
			
		||||
        * Override `KeyValue` cache method `values`
 | 
			
		||||
 | 
			
		||||
## 0.12.10
 | 
			
		||||
 | 
			
		||||
* `Repos`:
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,7 @@ buildscript {
 | 
			
		||||
    dependencies {
 | 
			
		||||
        classpath libs.buildscript.kt.gradle
 | 
			
		||||
        classpath libs.buildscript.kt.serialization
 | 
			
		||||
        classpath libs.buildscript.kt.ksp
 | 
			
		||||
        classpath libs.buildscript.jb.dokka
 | 
			
		||||
        classpath libs.buildscript.gh.release
 | 
			
		||||
        classpath libs.buildscript.android.gradle
 | 
			
		||||
@@ -22,6 +23,7 @@ allprojects {
 | 
			
		||||
        mavenCentral()
 | 
			
		||||
        google()
 | 
			
		||||
        maven { url "https://maven.pkg.jetbrains.space/public/p/compose/dev" }
 | 
			
		||||
        maven { url "https://git.inmo.dev/api/packages/InsanusMokrassar/maven" }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // temporal crutch until legacy tests will be stabled or legacy target will be removed
 | 
			
		||||
 
 | 
			
		||||
@@ -16,6 +16,19 @@ kotlin {
 | 
			
		||||
        androidMain {
 | 
			
		||||
            dependencies {
 | 
			
		||||
                api project(":micro_utils.coroutines")
 | 
			
		||||
                api libs.android.fragment
 | 
			
		||||
            }
 | 
			
		||||
            dependsOn jvmMain
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        linuxX64Main {
 | 
			
		||||
            dependencies {
 | 
			
		||||
                api libs.okio
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        mingwX64Main {
 | 
			
		||||
            dependencies {
 | 
			
		||||
                api libs.okio
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,10 @@
 | 
			
		||||
package dev.inmo.micro_utils.common.compose
 | 
			
		||||
 | 
			
		||||
import androidx.compose.runtime.MutableState
 | 
			
		||||
import androidx.compose.runtime.State
 | 
			
		||||
import androidx.compose.runtime.derivedStateOf
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Converts current [MutableState] to immutable [State] using [derivedStateOf]
 | 
			
		||||
 */
 | 
			
		||||
fun <T> MutableState<T>.asState(): State<T> = derivedStateOf { this.value }
 | 
			
		||||
@@ -0,0 +1,43 @@
 | 
			
		||||
package dev.inmo.micro_utils.common.compose
 | 
			
		||||
 | 
			
		||||
import org.jetbrains.compose.web.css.*
 | 
			
		||||
 | 
			
		||||
object SkeletonAnimation : StyleSheet() {
 | 
			
		||||
    val skeletonKeyFrames: CSSNamedKeyframes by keyframes {
 | 
			
		||||
        to { backgroundPosition("-20% 0") }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun CSSBuilder.includeSkeletonStyle(
 | 
			
		||||
        duration: CSSSizeValue<out CSSUnitTime> = 2.s,
 | 
			
		||||
        timingFunction: AnimationTimingFunction = AnimationTimingFunction.EaseInOut,
 | 
			
		||||
        iterationCount: Int? = null,
 | 
			
		||||
        direction: AnimationDirection = AnimationDirection.Normal,
 | 
			
		||||
        keyFrames: CSSNamedKeyframes = skeletonKeyFrames,
 | 
			
		||||
        hideChildren: Boolean = true,
 | 
			
		||||
        hideText: Boolean = hideChildren
 | 
			
		||||
    ) {
 | 
			
		||||
        backgroundImage("linear-gradient(110deg, rgb(236, 236, 236) 40%, rgb(245, 245, 245) 50%, rgb(236, 236, 236) 65%)")
 | 
			
		||||
        backgroundSize("200% 100%")
 | 
			
		||||
        backgroundPosition("180% 0")
 | 
			
		||||
        animation(keyFrames) {
 | 
			
		||||
            duration(duration)
 | 
			
		||||
            timingFunction(timingFunction)
 | 
			
		||||
            iterationCount(iterationCount)
 | 
			
		||||
            direction(direction)
 | 
			
		||||
        }
 | 
			
		||||
        if (hideText) {
 | 
			
		||||
            property("color", "${Color.transparent} !important")
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (hideChildren) {
 | 
			
		||||
            child(self, universal) style {
 | 
			
		||||
                property("visibility", "hidden")
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    val skeleton by style {
 | 
			
		||||
        includeSkeletonStyle()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -2,6 +2,8 @@
 | 
			
		||||
 | 
			
		||||
package dev.inmo.micro_utils.common
 | 
			
		||||
 | 
			
		||||
import kotlinx.serialization.Serializable
 | 
			
		||||
 | 
			
		||||
private inline fun <T> getObject(
 | 
			
		||||
    additional: MutableList<T>,
 | 
			
		||||
    iterator: Iterator<T>
 | 
			
		||||
@@ -14,16 +16,29 @@ private inline fun <T> getObject(
 | 
			
		||||
/**
 | 
			
		||||
 * Diff object which contains information about differences between two [Iterable]s
 | 
			
		||||
 *
 | 
			
		||||
 * See tests for more info
 | 
			
		||||
 *
 | 
			
		||||
 * @param removed The objects which has been presented in the old collection but absent in new one. Index here is the index in the old collection
 | 
			
		||||
 * @param added The object which appear in new collection only. Indexes here show the index in the new collection
 | 
			
		||||
 * @param replaced Pair of old-new changes. First object has been presented in the old collection on its
 | 
			
		||||
 * [IndexedValue.index] place, the second one is the object in new collection. Both have indexes due to the fact that in
 | 
			
		||||
 * case when some value has been replaced after adds or removes in original collection the object index will be changed
 | 
			
		||||
 *
 | 
			
		||||
 * @see calculateDiff
 | 
			
		||||
 */
 | 
			
		||||
@Serializable
 | 
			
		||||
data class Diff<T> internal constructor(
 | 
			
		||||
    val removed: List<IndexedValue<T>>,
 | 
			
		||||
    val removed: List<@Serializable(IndexedValueSerializer::class) IndexedValue<T>>,
 | 
			
		||||
    /**
 | 
			
		||||
     * Old-New values pairs
 | 
			
		||||
     */
 | 
			
		||||
    val replaced: List<Pair<IndexedValue<T>, IndexedValue<T>>>,
 | 
			
		||||
    val added: List<IndexedValue<T>>
 | 
			
		||||
)
 | 
			
		||||
    val replaced: List<Pair<@Serializable(IndexedValueSerializer::class) IndexedValue<T>, @Serializable(IndexedValueSerializer::class) IndexedValue<T>>>,
 | 
			
		||||
    val added: List<@Serializable(IndexedValueSerializer::class) IndexedValue<T>>
 | 
			
		||||
) {
 | 
			
		||||
    fun isEmpty(): Boolean = removed.isEmpty() && replaced.isEmpty() && added.isEmpty()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fun <T> emptyDiff(): Diff<T> = Diff(emptyList(), emptyList(), emptyList())
 | 
			
		||||
 | 
			
		||||
private inline fun <T> performChanges(
 | 
			
		||||
    potentialChanges: MutableList<Pair<IndexedValue<T>?, IndexedValue<T>?>>,
 | 
			
		||||
@@ -32,14 +47,14 @@ private inline fun <T> performChanges(
 | 
			
		||||
    changedList: MutableList<Pair<IndexedValue<T>, IndexedValue<T>>>,
 | 
			
		||||
    removedList: MutableList<IndexedValue<T>>,
 | 
			
		||||
    addedList: MutableList<IndexedValue<T>>,
 | 
			
		||||
    strictComparison: Boolean
 | 
			
		||||
    comparisonFun: (T?, T?) -> Boolean
 | 
			
		||||
) {
 | 
			
		||||
    var i = -1
 | 
			
		||||
    val (oldObject, newObject) = potentialChanges.lastOrNull() ?: return
 | 
			
		||||
    for ((old, new) in potentialChanges.take(potentialChanges.size - 1)) {
 | 
			
		||||
        i++
 | 
			
		||||
        val oldOneEqualToNewObject = old ?.value === newObject ?.value || (old ?.value == newObject ?.value && !strictComparison)
 | 
			
		||||
        val newOneEqualToOldObject = new ?.value === oldObject ?.value || (new ?.value == oldObject ?.value && !strictComparison)
 | 
			
		||||
        val oldOneEqualToNewObject = comparisonFun(old ?.value, newObject ?.value)
 | 
			
		||||
        val newOneEqualToOldObject = comparisonFun(new ?.value, oldObject ?.value)
 | 
			
		||||
        if (oldOneEqualToNewObject || newOneEqualToOldObject) {
 | 
			
		||||
            changedList.addAll(
 | 
			
		||||
                potentialChanges.take(i).mapNotNull {
 | 
			
		||||
@@ -93,7 +108,7 @@ private inline fun <T> performChanges(
 | 
			
		||||
 */
 | 
			
		||||
fun <T> Iterable<T>.calculateDiff(
 | 
			
		||||
    other: Iterable<T>,
 | 
			
		||||
    strictComparison: Boolean = false
 | 
			
		||||
    comparisonFun: (T?, T?) -> Boolean
 | 
			
		||||
): Diff<T> {
 | 
			
		||||
    var i = -1
 | 
			
		||||
    var j = -1
 | 
			
		||||
@@ -121,7 +136,7 @@ fun <T> Iterable<T>.calculateDiff(
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        when {
 | 
			
		||||
            oldObject === newObject || (oldObject == newObject && !strictComparison) -> {
 | 
			
		||||
            comparisonFun(oldObject, newObject) -> {
 | 
			
		||||
                changedObjects.addAll(potentiallyChangedObjects.map {
 | 
			
		||||
                    @Suppress("UNCHECKED_CAST")
 | 
			
		||||
                    it as Pair<IndexedValue<T>, IndexedValue<T>>
 | 
			
		||||
@@ -132,23 +147,49 @@ fun <T> Iterable<T>.calculateDiff(
 | 
			
		||||
                potentiallyChangedObjects.add(oldObject ?.let { IndexedValue(i, oldObject) } to newObject ?.let { IndexedValue(j, newObject) })
 | 
			
		||||
                val previousOldsAdditionsSize = additionalInOld.size
 | 
			
		||||
                val previousNewsAdditionsSize = additionalInNew.size
 | 
			
		||||
                performChanges(potentiallyChangedObjects, additionalInOld, additionalInNew, changedObjects, removedObjects, addedObjects, strictComparison)
 | 
			
		||||
                performChanges(potentiallyChangedObjects, additionalInOld, additionalInNew, changedObjects, removedObjects, addedObjects, comparisonFun)
 | 
			
		||||
                i -= (additionalInOld.size - previousOldsAdditionsSize)
 | 
			
		||||
                j -= (additionalInNew.size - previousNewsAdditionsSize)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    potentiallyChangedObjects.add(null to null)
 | 
			
		||||
    performChanges(potentiallyChangedObjects, additionalInOld, additionalInNew, changedObjects, removedObjects, addedObjects, strictComparison)
 | 
			
		||||
    performChanges(potentiallyChangedObjects, additionalInOld, additionalInNew, changedObjects, removedObjects, addedObjects, comparisonFun)
 | 
			
		||||
 | 
			
		||||
    return Diff(removedObjects.toList(), changedObjects.toList(), addedObjects.toList())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Calculating [Diff] object
 | 
			
		||||
 *
 | 
			
		||||
 * @param strictComparison If this parameter set to true, objects which are not equal by links will be used as different
 | 
			
		||||
 * objects. For example, in case of two "Example" string they will be equal by value, but CAN be different by links
 | 
			
		||||
 */
 | 
			
		||||
fun <T> Iterable<T>.calculateDiff(
 | 
			
		||||
    other: Iterable<T>,
 | 
			
		||||
    strictComparison: Boolean = false
 | 
			
		||||
): Diff<T> = calculateDiff(
 | 
			
		||||
    other,
 | 
			
		||||
    comparisonFun = if (strictComparison) {
 | 
			
		||||
        { t1, t2 ->
 | 
			
		||||
            t1 === t2
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        { t1, t2 ->
 | 
			
		||||
            t1 === t2 || t1 == t2 // small optimization for cases when t1 and t2 are the same - comparison will be faster potentially
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
inline fun <T> Iterable<T>.diff(
 | 
			
		||||
    other: Iterable<T>,
 | 
			
		||||
    strictComparison: Boolean = false
 | 
			
		||||
): Diff<T> = calculateDiff(other, strictComparison)
 | 
			
		||||
inline fun <T> Iterable<T>.diff(
 | 
			
		||||
    other: Iterable<T>,
 | 
			
		||||
    noinline comparisonFun: (T?, T?) -> Boolean
 | 
			
		||||
): Diff<T> = calculateDiff(other, comparisonFun)
 | 
			
		||||
 | 
			
		||||
inline fun <T> Diff(old: Iterable<T>, new: Iterable<T>) = old.calculateDiff(new)
 | 
			
		||||
inline fun <T> Diff(old: Iterable<T>, new: Iterable<T>) = old.calculateDiff(new, strictComparison = false)
 | 
			
		||||
inline fun <T> StrictDiff(old: Iterable<T>, new: Iterable<T>) = old.calculateDiff(new, true)
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -165,7 +206,26 @@ inline fun <T> Iterable<T>.calculateStrictDiff(
 | 
			
		||||
fun <T> MutableList<T>.applyDiff(
 | 
			
		||||
    source: Iterable<T>,
 | 
			
		||||
    strictComparison: Boolean = false
 | 
			
		||||
) = calculateDiff(source, strictComparison).let {
 | 
			
		||||
): Diff<T> = calculateDiff(source, strictComparison).also {
 | 
			
		||||
    for (i in it.removed.indices.sortedDescending()) {
 | 
			
		||||
        removeAt(it.removed[i].index)
 | 
			
		||||
    }
 | 
			
		||||
    it.added.forEach { (i, t) ->
 | 
			
		||||
        add(i, t)
 | 
			
		||||
    }
 | 
			
		||||
    it.replaced.forEach { (_, new) ->
 | 
			
		||||
        set(new.index, new.value)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This method call [calculateDiff] with strict mode [strictComparison] and then apply differences to [this]
 | 
			
		||||
 * mutable list
 | 
			
		||||
 */
 | 
			
		||||
fun <T> MutableList<T>.applyDiff(
 | 
			
		||||
    source: Iterable<T>,
 | 
			
		||||
    comparisonFun: (T?, T?) -> Boolean
 | 
			
		||||
): Diff<T> = calculateDiff(source, comparisonFun).also {
 | 
			
		||||
    for (i in it.removed.indices.sortedDescending()) {
 | 
			
		||||
        removeAt(it.removed[i].index)
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,43 @@
 | 
			
		||||
package dev.inmo.micro_utils.common
 | 
			
		||||
 | 
			
		||||
inline fun <T> Boolean.letIfTrue(block: () -> T): T? {
 | 
			
		||||
    return if (this) {
 | 
			
		||||
        block()
 | 
			
		||||
    } else {
 | 
			
		||||
        null
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
inline fun <T> Boolean.letIfFalse(block: () -> T): T? {
 | 
			
		||||
    return if (this) {
 | 
			
		||||
        null
 | 
			
		||||
    } else {
 | 
			
		||||
        block()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
inline fun Boolean.alsoIfTrue(block: () -> Unit): Boolean {
 | 
			
		||||
    letIfTrue(block)
 | 
			
		||||
    return this
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
inline fun Boolean.alsoIfFalse(block: () -> Unit): Boolean {
 | 
			
		||||
    letIfFalse(block)
 | 
			
		||||
    return this
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
inline fun <T> Boolean.ifTrue(block: () -> T): T? {
 | 
			
		||||
    return if (this) {
 | 
			
		||||
        block()
 | 
			
		||||
    } else {
 | 
			
		||||
        null
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
inline fun <T> Boolean.ifFalse(block: () -> T): T? {
 | 
			
		||||
    return if (this) {
 | 
			
		||||
        null
 | 
			
		||||
    } else {
 | 
			
		||||
        block()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,30 @@
 | 
			
		||||
package dev.inmo.micro_utils.common
 | 
			
		||||
 | 
			
		||||
import kotlinx.serialization.KSerializer
 | 
			
		||||
import kotlinx.serialization.Serializer
 | 
			
		||||
import kotlinx.serialization.builtins.PairSerializer
 | 
			
		||||
import kotlinx.serialization.builtins.serializer
 | 
			
		||||
import kotlinx.serialization.descriptors.SerialDescriptor
 | 
			
		||||
import kotlinx.serialization.encoding.Decoder
 | 
			
		||||
import kotlinx.serialization.encoding.Encoder
 | 
			
		||||
 | 
			
		||||
class IndexedValueSerializer<T>(private val subSerializer: KSerializer<T>) : KSerializer<IndexedValue<T>> {
 | 
			
		||||
    private val originalSerializer = PairSerializer(Int.serializer(), subSerializer)
 | 
			
		||||
    override val descriptor: SerialDescriptor
 | 
			
		||||
        get() = originalSerializer.descriptor
 | 
			
		||||
 | 
			
		||||
    override fun deserialize(decoder: Decoder): IndexedValue<T> {
 | 
			
		||||
        val pair = originalSerializer.deserialize(decoder)
 | 
			
		||||
        return IndexedValue(
 | 
			
		||||
            pair.first,
 | 
			
		||||
            pair.second
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun serialize(encoder: Encoder, value: IndexedValue<T>) {
 | 
			
		||||
        originalSerializer.serialize(
 | 
			
		||||
            encoder,
 | 
			
		||||
            Pair(value.index, value.value)
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,28 @@
 | 
			
		||||
package dev.inmo.micro_utils.common
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Convert [this] [Long] to [Int] with bounds of [Int.MIN_VALUE] and [Int.MAX_VALUE]
 | 
			
		||||
 */
 | 
			
		||||
fun Long.toCoercedInt(): Int = coerceIn(Int.MIN_VALUE.toLong(), Int.MAX_VALUE.toLong()).toInt()
 | 
			
		||||
/**
 | 
			
		||||
 * Convert [this] [Long] to [Short] with bounds of [Short.MIN_VALUE] and [Short.MAX_VALUE]
 | 
			
		||||
 */
 | 
			
		||||
fun Long.toCoercedShort(): Short = coerceIn(Short.MIN_VALUE.toLong(), Short.MAX_VALUE.toLong()).toShort()
 | 
			
		||||
/**
 | 
			
		||||
 * Convert [this] [Long] to [Byte] with bounds of [Byte.MIN_VALUE] and [Byte.MAX_VALUE]
 | 
			
		||||
 */
 | 
			
		||||
fun Long.toCoercedByte(): Byte = coerceIn(Byte.MIN_VALUE.toLong(), Byte.MAX_VALUE.toLong()).toByte()
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Convert [this] [Int] to [Short] with bounds of [Short.MIN_VALUE] and [Short.MAX_VALUE]
 | 
			
		||||
 */
 | 
			
		||||
fun Int.toCoercedShort(): Short = coerceIn(Short.MIN_VALUE.toInt(), Short.MAX_VALUE.toInt()).toShort()
 | 
			
		||||
/**
 | 
			
		||||
 * Convert [this] [Int] to [Byte] with bounds of [Byte.MIN_VALUE] and [Byte.MAX_VALUE]
 | 
			
		||||
 */
 | 
			
		||||
fun Int.toCoercedByte(): Byte = coerceIn(Byte.MIN_VALUE.toInt(), Byte.MAX_VALUE.toInt()).toByte()
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Convert [this] [Short] to [Byte] with bounds of [Byte.MIN_VALUE] and [Byte.MAX_VALUE]
 | 
			
		||||
 */
 | 
			
		||||
fun Short.toCoercedByte(): Byte = coerceIn(Byte.MIN_VALUE.toShort(), Byte.MAX_VALUE.toShort()).toByte()
 | 
			
		||||
@@ -0,0 +1,37 @@
 | 
			
		||||
package dev.inmo.micro_utils.common
 | 
			
		||||
 | 
			
		||||
import kotlinx.serialization.Serializable
 | 
			
		||||
import kotlin.jvm.JvmInline
 | 
			
		||||
 | 
			
		||||
@Serializable
 | 
			
		||||
@JvmInline
 | 
			
		||||
value class Progress private constructor(
 | 
			
		||||
    val of1: Double
 | 
			
		||||
) {
 | 
			
		||||
    val of1Float
 | 
			
		||||
        get() = of1.toFloat()
 | 
			
		||||
    val of100
 | 
			
		||||
        get() = of1 * 100
 | 
			
		||||
    val of100Float
 | 
			
		||||
        get() = of100.toFloat()
 | 
			
		||||
    val of100Int
 | 
			
		||||
        get() = of100.toInt()
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
        require(of1 in rangeOfValues) {
 | 
			
		||||
            "Progress main value should be in $rangeOfValues, but incoming value is $of1"
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        val rangeOfValues = 0.0 .. 1.0
 | 
			
		||||
 | 
			
		||||
        val START = Progress(rangeOfValues.start)
 | 
			
		||||
        val COMPLETED = Progress(rangeOfValues.endInclusive)
 | 
			
		||||
 | 
			
		||||
        operator fun invoke(of1: Double) = Progress(of1.coerceIn(rangeOfValues))
 | 
			
		||||
        operator fun invoke(part: Number, total: Number) = Progress(
 | 
			
		||||
            part.toDouble() / total.toDouble()
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,80 @@
 | 
			
		||||
@file:Suppress(
 | 
			
		||||
  "RemoveRedundantCallsOfConversionMethods",
 | 
			
		||||
  "RedundantVisibilityModifier",
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
package dev.inmo.micro_utils.common
 | 
			
		||||
 | 
			
		||||
import kotlin.Byte
 | 
			
		||||
import kotlin.Double
 | 
			
		||||
import kotlin.Float
 | 
			
		||||
import kotlin.Int
 | 
			
		||||
import kotlin.Long
 | 
			
		||||
import kotlin.Short
 | 
			
		||||
import kotlin.Suppress
 | 
			
		||||
 | 
			
		||||
public operator fun Progress.plus(other: Progress): Progress = Progress(of1 + other.of1)
 | 
			
		||||
 | 
			
		||||
public operator fun Progress.minus(other: Progress): Progress = Progress(of1 - other.of1)
 | 
			
		||||
 | 
			
		||||
public operator fun Progress.plus(i: Byte): Progress = Progress((of1 + i).toDouble())
 | 
			
		||||
 | 
			
		||||
public operator fun Progress.minus(i: Byte): Progress = Progress((of1 - i).toDouble())
 | 
			
		||||
 | 
			
		||||
public operator fun Progress.times(i: Byte): Progress = Progress((of1 * i).toDouble())
 | 
			
		||||
 | 
			
		||||
public operator fun Progress.div(i: Byte): Progress = Progress((of1 / i).toDouble())
 | 
			
		||||
 | 
			
		||||
public operator fun Progress.rem(i: Byte): Progress = Progress((of1 % i).toDouble())
 | 
			
		||||
 | 
			
		||||
public operator fun Progress.plus(i: Short): Progress = Progress((of1 + i).toDouble())
 | 
			
		||||
 | 
			
		||||
public operator fun Progress.minus(i: Short): Progress = Progress((of1 - i).toDouble())
 | 
			
		||||
 | 
			
		||||
public operator fun Progress.times(i: Short): Progress = Progress((of1 * i).toDouble())
 | 
			
		||||
 | 
			
		||||
public operator fun Progress.div(i: Short): Progress = Progress((of1 / i).toDouble())
 | 
			
		||||
 | 
			
		||||
public operator fun Progress.rem(i: Short): Progress = Progress((of1 % i).toDouble())
 | 
			
		||||
 | 
			
		||||
public operator fun Progress.plus(i: Int): Progress = Progress((of1 + i).toDouble())
 | 
			
		||||
 | 
			
		||||
public operator fun Progress.minus(i: Int): Progress = Progress((of1 - i).toDouble())
 | 
			
		||||
 | 
			
		||||
public operator fun Progress.times(i: Int): Progress = Progress((of1 * i).toDouble())
 | 
			
		||||
 | 
			
		||||
public operator fun Progress.div(i: Int): Progress = Progress((of1 / i).toDouble())
 | 
			
		||||
 | 
			
		||||
public operator fun Progress.rem(i: Int): Progress = Progress((of1 % i).toDouble())
 | 
			
		||||
 | 
			
		||||
public operator fun Progress.plus(i: Long): Progress = Progress((of1 + i).toDouble())
 | 
			
		||||
 | 
			
		||||
public operator fun Progress.minus(i: Long): Progress = Progress((of1 - i).toDouble())
 | 
			
		||||
 | 
			
		||||
public operator fun Progress.times(i: Long): Progress = Progress((of1 * i).toDouble())
 | 
			
		||||
 | 
			
		||||
public operator fun Progress.div(i: Long): Progress = Progress((of1 / i).toDouble())
 | 
			
		||||
 | 
			
		||||
public operator fun Progress.rem(i: Long): Progress = Progress((of1 % i).toDouble())
 | 
			
		||||
 | 
			
		||||
public operator fun Progress.plus(i: Float): Progress = Progress((of1 + i).toDouble())
 | 
			
		||||
 | 
			
		||||
public operator fun Progress.minus(i: Float): Progress = Progress((of1 - i).toDouble())
 | 
			
		||||
 | 
			
		||||
public operator fun Progress.times(i: Float): Progress = Progress((of1 * i).toDouble())
 | 
			
		||||
 | 
			
		||||
public operator fun Progress.div(i: Float): Progress = Progress((of1 / i).toDouble())
 | 
			
		||||
 | 
			
		||||
public operator fun Progress.rem(i: Float): Progress = Progress((of1 % i).toDouble())
 | 
			
		||||
 | 
			
		||||
public operator fun Progress.plus(i: Double): Progress = Progress((of1 + i).toDouble())
 | 
			
		||||
 | 
			
		||||
public operator fun Progress.minus(i: Double): Progress = Progress((of1 - i).toDouble())
 | 
			
		||||
 | 
			
		||||
public operator fun Progress.times(i: Double): Progress = Progress((of1 * i).toDouble())
 | 
			
		||||
 | 
			
		||||
public operator fun Progress.div(i: Double): Progress = Progress((of1 / i).toDouble())
 | 
			
		||||
 | 
			
		||||
public operator fun Progress.rem(i: Double): Progress = Progress((of1 % i).toDouble())
 | 
			
		||||
 | 
			
		||||
public operator fun Progress.compareTo(other: Progress): Int = (of1 - other.of1).toInt()
 | 
			
		||||
@@ -1,5 +1,27 @@
 | 
			
		||||
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].
 | 
			
		||||
 *
 | 
			
		||||
@@ -10,12 +32,23 @@ inline fun <R> repeatOnFailure(
 | 
			
		||||
    onEachFailure: (Throwable) -> Unit = {},
 | 
			
		||||
    action: (Int) -> R
 | 
			
		||||
): Optional<R> {
 | 
			
		||||
    repeat(times) {
 | 
			
		||||
        runCatching {
 | 
			
		||||
            action(it)
 | 
			
		||||
        }.onFailure(onEachFailure).onSuccess {
 | 
			
		||||
            return Optional.presented(it)
 | 
			
		||||
    var i = 0
 | 
			
		||||
    val result = repeatOnFailure(
 | 
			
		||||
        {
 | 
			
		||||
            onEachFailure(it)
 | 
			
		||||
            if (i < times) {
 | 
			
		||||
                i++
 | 
			
		||||
                true
 | 
			
		||||
            } else {
 | 
			
		||||
                false
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    return Optional.absent()
 | 
			
		||||
    ) {
 | 
			
		||||
        action(i)
 | 
			
		||||
    }
 | 
			
		||||
    return if (result.isSuccess) {
 | 
			
		||||
        Optional.presented(result.getOrThrow())
 | 
			
		||||
    } else {
 | 
			
		||||
        Optional.absent()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,6 @@
 | 
			
		||||
package dev.inmo.micro_utils.common
 | 
			
		||||
 | 
			
		||||
val FixedSignsRange = 0 .. 100
 | 
			
		||||
 | 
			
		||||
expect fun Float.fixed(signs: Int): Float
 | 
			
		||||
expect fun Double.fixed(signs: Int): Double
 | 
			
		||||
@@ -0,0 +1,4 @@
 | 
			
		||||
package dev.inmo.micro_utils.common
 | 
			
		||||
 | 
			
		||||
actual fun Float.fixed(signs: Int): Float = this.asDynamic().toFixed(signs.coerceIn(FixedSignsRange)).unsafeCast<String>().toFloat()
 | 
			
		||||
actual fun Double.fixed(signs: Int): Double = this.asDynamic().toFixed(signs.coerceIn(FixedSignsRange)).unsafeCast<String>().toDouble()
 | 
			
		||||
@@ -0,0 +1,20 @@
 | 
			
		||||
package dev.inmo.micro_utils.common
 | 
			
		||||
 | 
			
		||||
import java.io.File
 | 
			
		||||
import java.io.InputStream
 | 
			
		||||
import java.util.UUID
 | 
			
		||||
 | 
			
		||||
fun InputStream.downloadToTempFile(
 | 
			
		||||
    fileName: String = UUID.randomUUID().toString(),
 | 
			
		||||
    fileExtension: String? = ".temp",
 | 
			
		||||
    folder: File? = null
 | 
			
		||||
) = File.createTempFile(
 | 
			
		||||
    fileName,
 | 
			
		||||
    fileExtension,
 | 
			
		||||
    folder
 | 
			
		||||
).apply {
 | 
			
		||||
    outputStream().use {
 | 
			
		||||
        copyTo(it)
 | 
			
		||||
    }
 | 
			
		||||
    deleteOnExit()
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,12 @@
 | 
			
		||||
package dev.inmo.micro_utils.common
 | 
			
		||||
 | 
			
		||||
import java.math.BigDecimal
 | 
			
		||||
import java.math.RoundingMode
 | 
			
		||||
 | 
			
		||||
actual fun Float.fixed(signs: Int): Float = BigDecimal.valueOf(this.toDouble())
 | 
			
		||||
    .setScale(signs.coerceIn(FixedSignsRange), RoundingMode.HALF_UP)
 | 
			
		||||
    .toFloat();
 | 
			
		||||
 | 
			
		||||
actual fun Double.fixed(signs: Int): Double = BigDecimal.valueOf(this)
 | 
			
		||||
    .setScale(signs.coerceIn(FixedSignsRange), RoundingMode.HALF_UP)
 | 
			
		||||
    .toDouble();
 | 
			
		||||
							
								
								
									
										36
									
								
								common/src/linuxX64Main/kotlin/ActualMPPFile.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								common/src/linuxX64Main/kotlin/ActualMPPFile.kt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,36 @@
 | 
			
		||||
package dev.inmo.micro_utils.common
 | 
			
		||||
 | 
			
		||||
import okio.FileSystem
 | 
			
		||||
import okio.Path
 | 
			
		||||
import okio.use
 | 
			
		||||
 | 
			
		||||
actual typealias MPPFile = Path
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @suppress
 | 
			
		||||
 */
 | 
			
		||||
actual val MPPFile.filename: FileName
 | 
			
		||||
    get() = FileName(toString())
 | 
			
		||||
/**
 | 
			
		||||
 * @suppress
 | 
			
		||||
 */
 | 
			
		||||
actual val MPPFile.filesize: Long
 | 
			
		||||
    get() = FileSystem.SYSTEM.openReadOnly(this).use {
 | 
			
		||||
        it.size()
 | 
			
		||||
    }
 | 
			
		||||
/**
 | 
			
		||||
 * @suppress
 | 
			
		||||
 */
 | 
			
		||||
actual val MPPFile.bytesAllocatorSync: ByteArrayAllocator
 | 
			
		||||
    get() = {
 | 
			
		||||
        FileSystem.SYSTEM.read(this) {
 | 
			
		||||
            readByteArray()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
/**
 | 
			
		||||
 * @suppress
 | 
			
		||||
 */
 | 
			
		||||
actual val MPPFile.bytesAllocator: SuspendByteArrayAllocator
 | 
			
		||||
    get() = {
 | 
			
		||||
        bytesAllocatorSync()
 | 
			
		||||
    }
 | 
			
		||||
							
								
								
									
										26
									
								
								common/src/linuxX64Main/kotlin/fixed.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								common/src/linuxX64Main/kotlin/fixed.kt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
			
		||||
package dev.inmo.micro_utils.common
 | 
			
		||||
 | 
			
		||||
import kotlinx.cinterop.ByteVar
 | 
			
		||||
import kotlinx.cinterop.allocArray
 | 
			
		||||
import kotlinx.cinterop.memScoped
 | 
			
		||||
import kotlinx.cinterop.toKString
 | 
			
		||||
import platform.posix.snprintf
 | 
			
		||||
import platform.posix.sprintf
 | 
			
		||||
 | 
			
		||||
actual fun Float.fixed(signs: Int): Float {
 | 
			
		||||
    return memScoped {
 | 
			
		||||
        val buff = allocArray<ByteVar>(Float.SIZE_BYTES * 2)
 | 
			
		||||
 | 
			
		||||
        sprintf(buff, "%.${signs}f", this@fixed)
 | 
			
		||||
        buff.toKString().toFloat()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
actual fun Double.fixed(signs: Int): Double {
 | 
			
		||||
    return memScoped {
 | 
			
		||||
        val buff = allocArray<ByteVar>(Double.SIZE_BYTES * 2)
 | 
			
		||||
 | 
			
		||||
        sprintf(buff, "%.${signs}f", this@fixed)
 | 
			
		||||
        buff.toKString().toDouble()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,76 @@
 | 
			
		||||
package dev.inmo.micro_utils.common
 | 
			
		||||
 | 
			
		||||
import android.os.Bundle
 | 
			
		||||
import android.os.Parcelable
 | 
			
		||||
import androidx.fragment.app.Fragment
 | 
			
		||||
import java.io.Serializable
 | 
			
		||||
import kotlin.reflect.KProperty
 | 
			
		||||
 | 
			
		||||
object ArgumentPropertyNullableDelegate {
 | 
			
		||||
    operator fun <T: Any> getValue(thisRef: Fragment, property: KProperty<*>): T? {
 | 
			
		||||
        val arguments = thisRef.arguments ?: return null
 | 
			
		||||
        val key = property.name
 | 
			
		||||
        return when (property.getter.returnType.classifier) {
 | 
			
		||||
            // Scalars
 | 
			
		||||
            String::class -> arguments.getString(key)
 | 
			
		||||
            Boolean::class -> arguments.getBoolean(key)
 | 
			
		||||
            Byte::class -> arguments.getByte(key)
 | 
			
		||||
            Char::class -> arguments.getChar(key)
 | 
			
		||||
            Double::class -> arguments.getDouble(key)
 | 
			
		||||
            Float::class -> arguments.getFloat(key)
 | 
			
		||||
            Int::class -> arguments.getInt(key)
 | 
			
		||||
            Long::class -> arguments.getLong(key)
 | 
			
		||||
            Short::class -> arguments.getShort(key)
 | 
			
		||||
 | 
			
		||||
            // References
 | 
			
		||||
            Bundle::class -> arguments.getBundle(key)
 | 
			
		||||
            CharSequence::class -> arguments.getCharSequence(key)
 | 
			
		||||
            Parcelable::class -> arguments.getParcelable(key)
 | 
			
		||||
 | 
			
		||||
            // Scalar arrays
 | 
			
		||||
            BooleanArray::class -> arguments.getBooleanArray(key)
 | 
			
		||||
            ByteArray::class -> arguments.getByteArray(key)
 | 
			
		||||
            CharArray::class -> arguments.getCharArray(key)
 | 
			
		||||
            DoubleArray::class -> arguments.getDoubleArray(key)
 | 
			
		||||
            FloatArray::class -> arguments.getFloatArray(key)
 | 
			
		||||
            IntArray::class -> arguments.getIntArray(key)
 | 
			
		||||
            LongArray::class -> arguments.getLongArray(key)
 | 
			
		||||
            ShortArray::class -> arguments.getShortArray(key)
 | 
			
		||||
            Array::class -> {
 | 
			
		||||
                val componentType = property.returnType.classifier ?.javaClass ?.componentType!!
 | 
			
		||||
                @Suppress("UNCHECKED_CAST") // Checked by reflection.
 | 
			
		||||
                when {
 | 
			
		||||
                    Parcelable::class.java.isAssignableFrom(componentType) -> {
 | 
			
		||||
                        arguments.getParcelableArray(key)
 | 
			
		||||
                    }
 | 
			
		||||
                    String::class.java.isAssignableFrom(componentType) -> {
 | 
			
		||||
                        arguments.getStringArray(key)
 | 
			
		||||
                    }
 | 
			
		||||
                    CharSequence::class.java.isAssignableFrom(componentType) -> {
 | 
			
		||||
                        arguments.getCharSequenceArray(key)
 | 
			
		||||
                    }
 | 
			
		||||
                    Serializable::class.java.isAssignableFrom(componentType) -> {
 | 
			
		||||
                        arguments.getSerializable(key)
 | 
			
		||||
                    }
 | 
			
		||||
                    else -> {
 | 
			
		||||
                        val valueType = componentType.canonicalName
 | 
			
		||||
                        throw IllegalArgumentException(
 | 
			
		||||
                            "Illegal value array type $valueType for key \"$key\""
 | 
			
		||||
                        )
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            Serializable::class -> arguments.getSerializable(key)
 | 
			
		||||
            else -> null
 | 
			
		||||
        } as? T
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
object ArgumentPropertyNonNullableDelegate {
 | 
			
		||||
    operator fun <T: Any> getValue(thisRef: Fragment, property: KProperty<*>): T {
 | 
			
		||||
        return ArgumentPropertyNullableDelegate.getValue<T>(thisRef, property)!!
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fun argumentOrNull() = ArgumentPropertyNullableDelegate
 | 
			
		||||
fun argumentOrThrow() = ArgumentPropertyNonNullableDelegate
 | 
			
		||||
@@ -0,0 +1,61 @@
 | 
			
		||||
package dev.inmo.micro_utils.common
 | 
			
		||||
 | 
			
		||||
import android.app.Activity
 | 
			
		||||
import android.view.View
 | 
			
		||||
import android.view.ViewGroup
 | 
			
		||||
import androidx.core.view.children
 | 
			
		||||
import androidx.fragment.app.Fragment
 | 
			
		||||
 | 
			
		||||
fun findViewsByTag(viewGroup: ViewGroup, tag: Any?): List<View> {
 | 
			
		||||
    return viewGroup.children.flatMap {
 | 
			
		||||
        findViewsByTag(it, tag)
 | 
			
		||||
    }.toList()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fun findViewsByTag(viewGroup: ViewGroup, key: Int, tag: Any?): List<View> {
 | 
			
		||||
    return viewGroup.children.flatMap {
 | 
			
		||||
        findViewsByTag(it, key, tag)
 | 
			
		||||
    }.toList()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fun findViewsByTag(view: View, tag: Any?): List<View> {
 | 
			
		||||
    val result = mutableListOf<View>()
 | 
			
		||||
    if (view.tag == tag) {
 | 
			
		||||
        result.add(view)
 | 
			
		||||
    }
 | 
			
		||||
    if (view is ViewGroup) {
 | 
			
		||||
        result.addAll(findViewsByTag(view, tag))
 | 
			
		||||
    }
 | 
			
		||||
    return result.toList()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fun findViewsByTag(view: View, key: Int, tag: Any?): List<View> {
 | 
			
		||||
    val result = mutableListOf<View>()
 | 
			
		||||
    if (view.getTag(key) == tag) {
 | 
			
		||||
        result.add(view)
 | 
			
		||||
    }
 | 
			
		||||
    if (view is ViewGroup) {
 | 
			
		||||
        result.addAll(findViewsByTag(view, key, tag))
 | 
			
		||||
    }
 | 
			
		||||
    return result.toList()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fun Activity.findViewsByTag(tag: Any?) = rootView ?.let {
 | 
			
		||||
    findViewsByTag(it, tag)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fun Activity.findViewsByTag(key: Int, tag: Any?) = rootView ?.let {
 | 
			
		||||
    findViewsByTag(it, key, tag)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fun Fragment.findViewsByTag(tag: Any?) = view ?.let {
 | 
			
		||||
    findViewsByTag(it, tag)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fun Fragment.findViewsByTag(key: Int, tag: Any?) = view ?.let {
 | 
			
		||||
    findViewsByTag(it, key, tag)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fun Fragment.findViewsByTagInActivity(tag: Any?) = activity ?.findViewsByTag(tag)
 | 
			
		||||
 | 
			
		||||
fun Fragment.findViewsByTagInActivity(key: Int, tag: Any?) = activity ?.findViewsByTag(key, tag)
 | 
			
		||||
@@ -0,0 +1,7 @@
 | 
			
		||||
package dev.inmo.micro_utils.common
 | 
			
		||||
 | 
			
		||||
import android.app.Activity
 | 
			
		||||
import android.view.View
 | 
			
		||||
 | 
			
		||||
val Activity.rootView: View?
 | 
			
		||||
    get() = findViewById<View?>(android.R.id.content) ?.rootView ?: window.decorView.findViewById<View?>(android.R.id.content) ?.rootView
 | 
			
		||||
							
								
								
									
										36
									
								
								common/src/mingwX64Main/kotlin/ActualMPPFile.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								common/src/mingwX64Main/kotlin/ActualMPPFile.kt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,36 @@
 | 
			
		||||
package dev.inmo.micro_utils.common
 | 
			
		||||
 | 
			
		||||
import okio.FileSystem
 | 
			
		||||
import okio.Path
 | 
			
		||||
import okio.use
 | 
			
		||||
 | 
			
		||||
actual typealias MPPFile = Path
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @suppress
 | 
			
		||||
 */
 | 
			
		||||
actual val MPPFile.filename: FileName
 | 
			
		||||
    get() = FileName(toString())
 | 
			
		||||
/**
 | 
			
		||||
 * @suppress
 | 
			
		||||
 */
 | 
			
		||||
actual val MPPFile.filesize: Long
 | 
			
		||||
    get() = FileSystem.SYSTEM.openReadOnly(this).use {
 | 
			
		||||
        it.size()
 | 
			
		||||
    }
 | 
			
		||||
/**
 | 
			
		||||
 * @suppress
 | 
			
		||||
 */
 | 
			
		||||
actual val MPPFile.bytesAllocatorSync: ByteArrayAllocator
 | 
			
		||||
    get() = {
 | 
			
		||||
        FileSystem.SYSTEM.read(this) {
 | 
			
		||||
            readByteArray()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
/**
 | 
			
		||||
 * @suppress
 | 
			
		||||
 */
 | 
			
		||||
actual val MPPFile.bytesAllocator: SuspendByteArrayAllocator
 | 
			
		||||
    get() = {
 | 
			
		||||
        bytesAllocatorSync()
 | 
			
		||||
    }
 | 
			
		||||
							
								
								
									
										26
									
								
								common/src/mingwX64Main/kotlin/fixed.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								common/src/mingwX64Main/kotlin/fixed.kt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
			
		||||
package dev.inmo.micro_utils.common
 | 
			
		||||
 | 
			
		||||
import kotlinx.cinterop.ByteVar
 | 
			
		||||
import kotlinx.cinterop.allocArray
 | 
			
		||||
import kotlinx.cinterop.memScoped
 | 
			
		||||
import kotlinx.cinterop.toKString
 | 
			
		||||
import platform.posix.snprintf
 | 
			
		||||
import platform.posix.sprintf
 | 
			
		||||
 | 
			
		||||
actual fun Float.fixed(signs: Int): Float {
 | 
			
		||||
    return memScoped {
 | 
			
		||||
        val buff = allocArray<ByteVar>(Float.SIZE_BYTES * 2)
 | 
			
		||||
 | 
			
		||||
        sprintf(buff, "%.${signs}f", this@fixed)
 | 
			
		||||
        buff.toKString().toFloat()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
actual fun Double.fixed(signs: Int): Double {
 | 
			
		||||
    return memScoped {
 | 
			
		||||
        val buff = allocArray<ByteVar>(Double.SIZE_BYTES * 2)
 | 
			
		||||
 | 
			
		||||
        sprintf(buff, "%.${signs}f", this@fixed)
 | 
			
		||||
        buff.toKString().toDouble()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -22,6 +22,7 @@ kotlin {
 | 
			
		||||
            dependencies {
 | 
			
		||||
                api libs.kt.coroutines.android
 | 
			
		||||
            }
 | 
			
		||||
            dependsOn(jvmMain)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -3,24 +3,58 @@ package dev.inmo.micro_utils.coroutines.compose
 | 
			
		||||
import androidx.compose.runtime.*
 | 
			
		||||
import androidx.compose.runtime.snapshots.SnapshotStateList
 | 
			
		||||
import dev.inmo.micro_utils.common.applyDiff
 | 
			
		||||
import dev.inmo.micro_utils.coroutines.ExceptionHandler
 | 
			
		||||
import dev.inmo.micro_utils.coroutines.defaultSafelyWithoutExceptionHandlerWithNull
 | 
			
		||||
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
 | 
			
		||||
import kotlinx.coroutines.CoroutineScope
 | 
			
		||||
import kotlinx.coroutines.Dispatchers
 | 
			
		||||
import kotlinx.coroutines.flow.Flow
 | 
			
		||||
import kotlinx.coroutines.flow.StateFlow
 | 
			
		||||
import kotlinx.coroutines.withContext
 | 
			
		||||
import kotlin.coroutines.CoroutineContext
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Each value of [this] [Flow] will trigger [applyDiff] to the result [SnapshotStateList]
 | 
			
		||||
 *
 | 
			
		||||
 * @param scope Will be used to [subscribeSafelyWithoutExceptions] on [this] to update returned [SnapshotStateList]
 | 
			
		||||
 * @param useContextOnChange Will be used to change context inside of [subscribeSafelyWithoutExceptions] to ensure that
 | 
			
		||||
 * change will happen in the required [CoroutineContext]. [Dispatchers.Main] by default
 | 
			
		||||
 * @param onException Will be passed to the [subscribeSafelyWithoutExceptions] as uncaught exceptions handler
 | 
			
		||||
 */
 | 
			
		||||
@Suppress("NOTHING_TO_INLINE")
 | 
			
		||||
inline fun <reified T> Flow<List<T>>.asMutableComposeListState(
 | 
			
		||||
    scope: CoroutineScope
 | 
			
		||||
    scope: CoroutineScope,
 | 
			
		||||
    useContextOnChange: CoroutineContext? = Dispatchers.Main,
 | 
			
		||||
    noinline onException: ExceptionHandler<List<T>?> = defaultSafelyWithoutExceptionHandlerWithNull,
 | 
			
		||||
): SnapshotStateList<T> {
 | 
			
		||||
    val state = mutableStateListOf<T>()
 | 
			
		||||
    subscribeSafelyWithoutExceptions(scope) {
 | 
			
		||||
    val changeBlock: suspend (List<T>) -> Unit = useContextOnChange ?.let {
 | 
			
		||||
        {
 | 
			
		||||
            withContext(useContextOnChange) {
 | 
			
		||||
                state.applyDiff(it)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    } ?: {
 | 
			
		||||
        state.applyDiff(it)
 | 
			
		||||
    }
 | 
			
		||||
    subscribeSafelyWithoutExceptions(scope, onException, changeBlock)
 | 
			
		||||
    return state
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * In fact, it is just classcast of [asMutableComposeListState] to [List]
 | 
			
		||||
 *
 | 
			
		||||
 * @param scope Will be used to [subscribeSafelyWithoutExceptions] on [this] to update returned [List]
 | 
			
		||||
 * @param useContextOnChange Will be used to change context inside of [subscribeSafelyWithoutExceptions] to ensure that
 | 
			
		||||
 * change will happen in the required [CoroutineContext]. [Dispatchers.Main] by default
 | 
			
		||||
 * @param onException Will be passed to the [subscribeSafelyWithoutExceptions] as uncaught exceptions handler
 | 
			
		||||
 *
 | 
			
		||||
 * @return Changing in time [List] which follow [Flow] values
 | 
			
		||||
 */
 | 
			
		||||
@Suppress("NOTHING_TO_INLINE")
 | 
			
		||||
inline fun <reified T> Flow<List<T>>.asComposeList(
 | 
			
		||||
    scope: CoroutineScope
 | 
			
		||||
): List<T> = asMutableComposeListState(scope)
 | 
			
		||||
    scope: CoroutineScope,
 | 
			
		||||
    useContextOnChange: CoroutineContext? = Dispatchers.Main,
 | 
			
		||||
    noinline onException: ExceptionHandler<List<T>?> = defaultSafelyWithoutExceptionHandlerWithNull,
 | 
			
		||||
): List<T> = asMutableComposeListState(scope, useContextOnChange, onException)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,35 +1,94 @@
 | 
			
		||||
package dev.inmo.micro_utils.coroutines.compose
 | 
			
		||||
 | 
			
		||||
import androidx.compose.runtime.*
 | 
			
		||||
import dev.inmo.micro_utils.common.compose.asState
 | 
			
		||||
import dev.inmo.micro_utils.coroutines.ExceptionHandler
 | 
			
		||||
import dev.inmo.micro_utils.coroutines.defaultSafelyWithoutExceptionHandlerWithNull
 | 
			
		||||
import dev.inmo.micro_utils.coroutines.doInUI
 | 
			
		||||
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
 | 
			
		||||
import kotlinx.coroutines.CoroutineScope
 | 
			
		||||
import kotlinx.coroutines.Dispatchers
 | 
			
		||||
import kotlinx.coroutines.flow.Flow
 | 
			
		||||
import kotlinx.coroutines.flow.StateFlow
 | 
			
		||||
import kotlinx.coroutines.withContext
 | 
			
		||||
import kotlin.coroutines.CoroutineContext
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Will map [this] [Flow] as [MutableState]. Returned [MutableState] WILL NOT change source [Flow]
 | 
			
		||||
 *
 | 
			
		||||
 * @param initial First value which will be passed to the result [MutableState]
 | 
			
		||||
 * @param scope Will be used to [subscribeSafelyWithoutExceptions] on [this] to update returned [MutableState]
 | 
			
		||||
 * @param useContextOnChange Will be used to change context inside of [subscribeSafelyWithoutExceptions] to ensure that
 | 
			
		||||
 * change will happen in the required [CoroutineContext]. [Dispatchers.Main] by default
 | 
			
		||||
 * @param onException Will be passed to the [subscribeSafelyWithoutExceptions] as uncaught exceptions handler
 | 
			
		||||
 */
 | 
			
		||||
fun <T> Flow<T>.asMutableComposeState(
 | 
			
		||||
    initial: T,
 | 
			
		||||
    scope: CoroutineScope
 | 
			
		||||
    scope: CoroutineScope,
 | 
			
		||||
    useContextOnChange: CoroutineContext? = Dispatchers.Main,
 | 
			
		||||
    onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
 | 
			
		||||
): MutableState<T> {
 | 
			
		||||
    val state = mutableStateOf(initial)
 | 
			
		||||
    subscribeSafelyWithoutExceptions(scope) { state.value = it }
 | 
			
		||||
    val changeBlock: suspend (T) -> Unit = useContextOnChange ?.let {
 | 
			
		||||
        {
 | 
			
		||||
            withContext(useContextOnChange) {
 | 
			
		||||
                state.value = it
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    } ?: {
 | 
			
		||||
        state.value = it
 | 
			
		||||
    }
 | 
			
		||||
    subscribeSafelyWithoutExceptions(scope, onException, block = changeBlock)
 | 
			
		||||
    return state
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Will map [this] [StateFlow] as [MutableState]. Returned [MutableState] WILL NOT change source [StateFlow].
 | 
			
		||||
 * This conversation will pass its [StateFlow.value] as the first value
 | 
			
		||||
 *
 | 
			
		||||
 * @param scope Will be used to [subscribeSafelyWithoutExceptions] on [this] to update returned [MutableState]
 | 
			
		||||
 * @param useContextOnChange Will be used to change context inside of [subscribeSafelyWithoutExceptions] to ensure that
 | 
			
		||||
 * change will happen in the required [CoroutineContext]. [Dispatchers.Main] by default
 | 
			
		||||
 * @param onException Will be passed to the [subscribeSafelyWithoutExceptions] as uncaught exceptions handler
 | 
			
		||||
 */
 | 
			
		||||
@Suppress("NOTHING_TO_INLINE")
 | 
			
		||||
inline fun <T> StateFlow<T>.asMutableComposeState(
 | 
			
		||||
    scope: CoroutineScope
 | 
			
		||||
): MutableState<T> = asMutableComposeState(value, scope)
 | 
			
		||||
    scope: CoroutineScope,
 | 
			
		||||
    useContextOnChange: CoroutineContext? = Dispatchers.Main,
 | 
			
		||||
    noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
 | 
			
		||||
): MutableState<T> = asMutableComposeState(value, scope, useContextOnChange, onException)
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Will create [MutableState] using [asMutableComposeState] and use [asState] to convert it as immutable state
 | 
			
		||||
 *
 | 
			
		||||
 * @param initial First value which will be passed to the result [State]
 | 
			
		||||
 * @param scope Will be used to [subscribeSafelyWithoutExceptions] on [this] to update returned [State]
 | 
			
		||||
 * @param useContextOnChange Will be used to change context inside of [subscribeSafelyWithoutExceptions] to ensure that
 | 
			
		||||
 * change will happen in the required [CoroutineContext]. [Dispatchers.Main] by default
 | 
			
		||||
 * @param onException Will be passed to the [subscribeSafelyWithoutExceptions] as uncaught exceptions handler
 | 
			
		||||
 */
 | 
			
		||||
fun <T> Flow<T>.asComposeState(
 | 
			
		||||
    initial: T,
 | 
			
		||||
    scope: CoroutineScope
 | 
			
		||||
    scope: CoroutineScope,
 | 
			
		||||
    useContextOnChange: CoroutineContext? = Dispatchers.Main,
 | 
			
		||||
    onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
 | 
			
		||||
): State<T> {
 | 
			
		||||
    val state = asMutableComposeState(initial, scope)
 | 
			
		||||
    return derivedStateOf { state.value }
 | 
			
		||||
    val state = asMutableComposeState(initial, scope, useContextOnChange, onException)
 | 
			
		||||
    return state.asState()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Will map [this] [StateFlow] as [State]. This conversation will pass its [StateFlow.value] as the first value
 | 
			
		||||
 *
 | 
			
		||||
 * @param scope Will be used to [subscribeSafelyWithoutExceptions] on [this] to update returned [State]
 | 
			
		||||
 * @param useContextOnChange Will be used to change context inside of [subscribeSafelyWithoutExceptions] to ensure that
 | 
			
		||||
 * change will happen in the required [CoroutineContext]. [Dispatchers.Main] by default
 | 
			
		||||
 * @param onException Will be passed to the [subscribeSafelyWithoutExceptions] as uncaught exceptions handler
 | 
			
		||||
 */
 | 
			
		||||
@Suppress("NOTHING_TO_INLINE")
 | 
			
		||||
inline fun <T> StateFlow<T>.asComposeState(
 | 
			
		||||
    scope: CoroutineScope
 | 
			
		||||
): State<T> = asComposeState(value, scope)
 | 
			
		||||
    scope: CoroutineScope,
 | 
			
		||||
    useContextOnChange: CoroutineContext? = Dispatchers.Main,
 | 
			
		||||
    noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
 | 
			
		||||
): State<T> = asComposeState(value, scope, useContextOnChange, onException)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,23 +0,0 @@
 | 
			
		||||
package dev.inmo.micro_utils.coroutines.compose
 | 
			
		||||
 | 
			
		||||
import androidx.compose.runtime.MutableState
 | 
			
		||||
import androidx.compose.runtime.mutableStateOf
 | 
			
		||||
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
 | 
			
		||||
import kotlinx.coroutines.CoroutineScope
 | 
			
		||||
import kotlinx.coroutines.flow.Flow
 | 
			
		||||
import kotlinx.coroutines.flow.StateFlow
 | 
			
		||||
 | 
			
		||||
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,19 +1,15 @@
 | 
			
		||||
package dev.inmo.micro_utils.coroutines
 | 
			
		||||
 | 
			
		||||
import kotlinx.coroutines.CoroutineScope
 | 
			
		||||
import kotlinx.coroutines.*
 | 
			
		||||
import kotlinx.coroutines.channels.Channel
 | 
			
		||||
import kotlinx.coroutines.launch
 | 
			
		||||
import kotlinx.coroutines.flow.consumeAsFlow
 | 
			
		||||
 | 
			
		||||
fun <T> CoroutineScope.actor(
 | 
			
		||||
    channelCapacity: Int = Channel.UNLIMITED,
 | 
			
		||||
    block: suspend (T) -> Unit
 | 
			
		||||
): Channel<T> {
 | 
			
		||||
    val channel = Channel<T>(channelCapacity)
 | 
			
		||||
    launch {
 | 
			
		||||
        for (data in channel) {
 | 
			
		||||
            block(data)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    channel.consumeAsFlow().subscribe(this, block)
 | 
			
		||||
    return channel
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,30 @@
 | 
			
		||||
package dev.inmo.micro_utils.coroutines
 | 
			
		||||
 | 
			
		||||
import kotlinx.coroutines.*
 | 
			
		||||
import kotlinx.coroutines.channels.Channel
 | 
			
		||||
import kotlinx.coroutines.flow.consumeAsFlow
 | 
			
		||||
 | 
			
		||||
fun <T> CoroutineScope.actorAsync(
 | 
			
		||||
    channelCapacity: Int = Channel.UNLIMITED,
 | 
			
		||||
    markerFactory: suspend (T) -> Any? = { null },
 | 
			
		||||
    block: suspend (T) -> Unit
 | 
			
		||||
): Channel<T> {
 | 
			
		||||
    val channel = Channel<T>(channelCapacity)
 | 
			
		||||
    channel.consumeAsFlow().subscribeAsync(this, markerFactory, block)
 | 
			
		||||
    return channel
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
inline fun <T> CoroutineScope.safeActorAsync(
 | 
			
		||||
    channelCapacity: Int = Channel.UNLIMITED,
 | 
			
		||||
    noinline onException: ExceptionHandler<Unit> = defaultSafelyExceptionHandler,
 | 
			
		||||
    noinline markerFactory: suspend (T) -> Any? = { null },
 | 
			
		||||
    crossinline block: suspend (T) -> Unit
 | 
			
		||||
): Channel<T> = actorAsync(
 | 
			
		||||
    channelCapacity,
 | 
			
		||||
    markerFactory
 | 
			
		||||
) {
 | 
			
		||||
    safely(onException) {
 | 
			
		||||
        block(it)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -6,23 +6,19 @@ import kotlin.coroutines.*
 | 
			
		||||
suspend fun <T> Iterable<Deferred<T>>.awaitFirstWithDeferred(
 | 
			
		||||
    scope: CoroutineScope,
 | 
			
		||||
    cancelOnResult: Boolean = true
 | 
			
		||||
): Pair<Deferred<T>, T> = suspendCoroutine<Pair<Deferred<T>, T>> { continuation ->
 | 
			
		||||
    scope.launch(SupervisorJob()) {
 | 
			
		||||
        val scope = this
 | 
			
		||||
): Pair<Deferred<T>, T> {
 | 
			
		||||
    val resultDeferred = CompletableDeferred<Pair<Deferred<T>, T>>()
 | 
			
		||||
    val scope = scope.LinkedSupervisorScope()
 | 
			
		||||
    forEach {
 | 
			
		||||
        scope.launch {
 | 
			
		||||
                continuation.resume(it to it.await())
 | 
			
		||||
            resultDeferred.complete(it to it.await())
 | 
			
		||||
            scope.cancel()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    }
 | 
			
		||||
}.also {
 | 
			
		||||
    return resultDeferred.await().also {
 | 
			
		||||
        if (cancelOnResult) {
 | 
			
		||||
            forEach {
 | 
			
		||||
            try {
 | 
			
		||||
                it.cancel()
 | 
			
		||||
            } catch (e: IllegalStateException) {
 | 
			
		||||
                e.printStackTrace()
 | 
			
		||||
                runCatchingSafely { it.cancel() }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,39 @@
 | 
			
		||||
package dev.inmo.micro_utils.coroutines
 | 
			
		||||
 | 
			
		||||
import kotlinx.coroutines.flow.*
 | 
			
		||||
import kotlin.js.JsName
 | 
			
		||||
import kotlin.jvm.JvmName
 | 
			
		||||
 | 
			
		||||
inline fun <T, R> Flow<Flow<T>>.flatMap(
 | 
			
		||||
    crossinline mapper: suspend (T) -> R
 | 
			
		||||
) = flow {
 | 
			
		||||
    collect {
 | 
			
		||||
        it.collect {
 | 
			
		||||
            emit(mapper(it))
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@JsName("flatMapIterable")
 | 
			
		||||
@JvmName("flatMapIterable")
 | 
			
		||||
inline fun <T, R> Flow<Iterable<T>>.flatMap(
 | 
			
		||||
    crossinline mapper: suspend (T) -> R
 | 
			
		||||
) = map {
 | 
			
		||||
    it.asFlow()
 | 
			
		||||
}.flatMap(mapper)
 | 
			
		||||
 | 
			
		||||
inline fun <T, R> Flow<Flow<T>>.flatMapNotNull(
 | 
			
		||||
    crossinline mapper: suspend (T) -> R
 | 
			
		||||
) = flatMap(mapper).takeNotNull()
 | 
			
		||||
 | 
			
		||||
@JsName("flatMapNotNullIterable")
 | 
			
		||||
@JvmName("flatMapNotNullIterable")
 | 
			
		||||
inline fun <T, R> Flow<Iterable<T>>.flatMapNotNull(
 | 
			
		||||
    crossinline mapper: suspend (T) -> R
 | 
			
		||||
) = flatMap(mapper).takeNotNull()
 | 
			
		||||
 | 
			
		||||
fun <T> Flow<Flow<T>>.flatten() = flatMap { it }
 | 
			
		||||
 | 
			
		||||
@JsName("flattenIterable")
 | 
			
		||||
@JvmName("flattenIterable")
 | 
			
		||||
fun <T> Flow<Iterable<T>>.flatten() = flatMap { it }
 | 
			
		||||
@@ -0,0 +1,6 @@
 | 
			
		||||
package dev.inmo.micro_utils.coroutines
 | 
			
		||||
 | 
			
		||||
import kotlinx.coroutines.flow.*
 | 
			
		||||
 | 
			
		||||
fun <T> Flow<T>.takeNotNull() = mapNotNull { it }
 | 
			
		||||
fun <T> Flow<T>.filterNotNull() = takeNotNull()
 | 
			
		||||
@@ -115,10 +115,21 @@ suspend inline fun <T> runCatchingSafely(
 | 
			
		||||
    safely(onException, block)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
suspend inline fun <T, R> T.runCatchingSafely(
 | 
			
		||||
    noinline onException: ExceptionHandler<R> = defaultSafelyExceptionHandler,
 | 
			
		||||
    noinline block: suspend T.() -> R
 | 
			
		||||
): Result<R> = runCatching {
 | 
			
		||||
    safely(onException) { block() }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
suspend inline fun <T> safelyWithResult(
 | 
			
		||||
    noinline block: suspend CoroutineScope.() -> T
 | 
			
		||||
): Result<T> = runCatchingSafely(defaultSafelyExceptionHandler, block)
 | 
			
		||||
 | 
			
		||||
suspend inline fun <T, R> T.safelyWithResult(
 | 
			
		||||
    noinline block: suspend T.() -> R
 | 
			
		||||
): Result<R> = runCatchingSafely(defaultSafelyExceptionHandler, block)
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Use this handler in cases you wish to include handling of exceptions by [defaultSafelyWithoutExceptionHandler] and
 | 
			
		||||
 * returning null at one time
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,26 @@
 | 
			
		||||
package dev.inmo.micro_utils.coroutines
 | 
			
		||||
 | 
			
		||||
import kotlinx.coroutines.CancellationException
 | 
			
		||||
import kotlinx.coroutines.Job
 | 
			
		||||
import org.w3c.dom.Image
 | 
			
		||||
 | 
			
		||||
suspend fun preloadImage(src: String): Image {
 | 
			
		||||
    val image = Image()
 | 
			
		||||
    image.src = src
 | 
			
		||||
 | 
			
		||||
    val job = Job()
 | 
			
		||||
 | 
			
		||||
    image.addEventListener("load", {
 | 
			
		||||
        runCatching { job.complete() }
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    runCatchingSafely {
 | 
			
		||||
        job.join()
 | 
			
		||||
    }.onFailure {
 | 
			
		||||
        if (it is CancellationException) {
 | 
			
		||||
            image.src = ""
 | 
			
		||||
        }
 | 
			
		||||
    }.getOrThrow()
 | 
			
		||||
 | 
			
		||||
    return image
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,9 @@
 | 
			
		||||
package dev.inmo.micro_utils.coroutines
 | 
			
		||||
 | 
			
		||||
import kotlinx.coroutines.CoroutineScope
 | 
			
		||||
import kotlinx.coroutines.Dispatchers
 | 
			
		||||
 | 
			
		||||
fun <T> launchInCurrentThread(block: suspend CoroutineScope.() -> T): T {
 | 
			
		||||
    val scope = CoroutineScope(Dispatchers.Unconfined)
 | 
			
		||||
    return scope.launchSynchronously(block)
 | 
			
		||||
}
 | 
			
		||||
@@ -6,7 +6,7 @@ fun <T> CoroutineScope.launchSynchronously(block: suspend CoroutineScope.() -> T
 | 
			
		||||
    var result: Result<T>? = null
 | 
			
		||||
    val objectToSynchronize = Object()
 | 
			
		||||
    synchronized(objectToSynchronize) {
 | 
			
		||||
        launch {
 | 
			
		||||
        launch(start = CoroutineStart.UNDISPATCHED) {
 | 
			
		||||
            result = safelyWithResult(block)
 | 
			
		||||
        }.invokeOnCompletion {
 | 
			
		||||
            synchronized(objectToSynchronize) {
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,47 @@
 | 
			
		||||
package dev.inmo.micro_utils.coroutines
 | 
			
		||||
 | 
			
		||||
import kotlinx.coroutines.Dispatchers
 | 
			
		||||
import kotlinx.coroutines.delay
 | 
			
		||||
import kotlinx.coroutines.withContext
 | 
			
		||||
import kotlin.test.Test
 | 
			
		||||
import kotlin.test.assertEquals
 | 
			
		||||
 | 
			
		||||
class LaunchInCurrentThreadTests {
 | 
			
		||||
    @Test
 | 
			
		||||
    fun simpleTestThatLaunchInCurrentThreadWorks() {
 | 
			
		||||
        val expectedResult = 10
 | 
			
		||||
        val result = launchInCurrentThread {
 | 
			
		||||
            expectedResult
 | 
			
		||||
        }
 | 
			
		||||
        assertEquals(expectedResult, result)
 | 
			
		||||
    }
 | 
			
		||||
    @Test
 | 
			
		||||
    fun simpleTestThatSeveralLaunchInCurrentThreadWorks() {
 | 
			
		||||
        val testData = 0 until 100
 | 
			
		||||
 | 
			
		||||
        testData.forEach {
 | 
			
		||||
            val result = launchInCurrentThread {
 | 
			
		||||
                it
 | 
			
		||||
            }
 | 
			
		||||
            assertEquals(it, result)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    @Test
 | 
			
		||||
    fun simpleTestThatLaunchInCurrentThreadWillCorrectlyHandleSuspensionsWorks() {
 | 
			
		||||
        val testData = 0 until 100
 | 
			
		||||
 | 
			
		||||
        suspend fun test(data: Any): Any {
 | 
			
		||||
            return withContext(Dispatchers.Default) {
 | 
			
		||||
                delay(1)
 | 
			
		||||
                data
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        testData.forEach {
 | 
			
		||||
            val result = launchInCurrentThread {
 | 
			
		||||
                test(it)
 | 
			
		||||
            }
 | 
			
		||||
            assertEquals(it, result)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										50
									
								
								coroutines/src/main/kotlin/FlowOnHierarchyChangeListener.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								coroutines/src/main/kotlin/FlowOnHierarchyChangeListener.kt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,50 @@
 | 
			
		||||
package dev.inmo.micro_utils.coroutines
 | 
			
		||||
 | 
			
		||||
import android.view.View
 | 
			
		||||
import android.view.ViewGroup
 | 
			
		||||
import kotlinx.coroutines.flow.MutableSharedFlow
 | 
			
		||||
import kotlinx.coroutines.flow.asSharedFlow
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * [kotlinx.coroutines.flow.Flow]-based [android.view.ViewGroup.OnHierarchyChangeListener]
 | 
			
		||||
 *
 | 
			
		||||
 * @param recursive If set, any call of [onChildViewAdded] will check if child [View] is [ViewGroup] and subscribe to this
 | 
			
		||||
 * [ViewGroup] too
 | 
			
		||||
 * @param [_onChildViewAdded] Internal [MutableSharedFlow] which will be used to pass data to [onChildViewAdded] flow
 | 
			
		||||
 * @param [_onChildViewRemoved] Internal [MutableSharedFlow] which will be used to pass data to [onChildViewRemoved] flow
 | 
			
		||||
 */
 | 
			
		||||
class FlowOnHierarchyChangeListener(
 | 
			
		||||
    private val recursive: Boolean = false,
 | 
			
		||||
    private val _onChildViewAdded: MutableSharedFlow<Pair<View, View>> = MutableSharedFlow(extraBufferCapacity = Int.MAX_VALUE),
 | 
			
		||||
    private val _onChildViewRemoved: MutableSharedFlow<Pair<View, View>> = MutableSharedFlow(extraBufferCapacity = Int.MAX_VALUE)
 | 
			
		||||
) : ViewGroup.OnHierarchyChangeListener {
 | 
			
		||||
    val onChildViewAdded = _onChildViewAdded.asSharedFlow()
 | 
			
		||||
    val onChildViewRemoved = _onChildViewRemoved.asSharedFlow()
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Will emit data into [onChildViewAdded] flow. If [recursive] is true and [child] is [ViewGroup] will also
 | 
			
		||||
     * subscribe to [child] hierarchy changes.
 | 
			
		||||
     *
 | 
			
		||||
     * Due to the fact that this method is not suspendable, [FlowOnHierarchyChangeListener] will use
 | 
			
		||||
     * [MutableSharedFlow.tryEmit] to send data into [_onChildViewAdded]. That is why its default extraBufferCapacity is
 | 
			
		||||
     * [Int.MAX_VALUE]
 | 
			
		||||
     */
 | 
			
		||||
    override fun onChildViewAdded(parent: View, child: View) {
 | 
			
		||||
        _onChildViewAdded.tryEmit(parent to child)
 | 
			
		||||
 | 
			
		||||
        if (recursive && child is ViewGroup) {
 | 
			
		||||
            child.setOnHierarchyChangeListener(this)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Just emit data into [onChildViewRemoved]
 | 
			
		||||
     *
 | 
			
		||||
     * Due to the fact that this method is not suspendable, [FlowOnHierarchyChangeListener] will use
 | 
			
		||||
     * [MutableSharedFlow.tryEmit] to send data into [_onChildViewRemoved]. That is why its default extraBufferCapacity is
 | 
			
		||||
     * [Int.MAX_VALUE]
 | 
			
		||||
     */
 | 
			
		||||
    override fun onChildViewRemoved(parent: View, child: View) {
 | 
			
		||||
        _onChildViewRemoved.tryEmit(parent to child)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										17
									
								
								coroutines/src/main/kotlin/RecursiveHierarchySubscriber.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								coroutines/src/main/kotlin/RecursiveHierarchySubscriber.kt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
			
		||||
package dev.inmo.micro_utils.coroutines
 | 
			
		||||
 | 
			
		||||
import android.view.ViewGroup
 | 
			
		||||
import android.view.ViewGroup.OnHierarchyChangeListener
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Use [ViewGroup.setOnHierarchyChangeListener] recursively for all available [ViewGroup]s starting with [this].
 | 
			
		||||
 * This extension DO NOT guarantee that recursive subscription will happen after this method call
 | 
			
		||||
 */
 | 
			
		||||
fun ViewGroup.setOnHierarchyChangeListenerRecursively(
 | 
			
		||||
    listener: OnHierarchyChangeListener
 | 
			
		||||
) {
 | 
			
		||||
    setOnHierarchyChangeListener(listener)
 | 
			
		||||
    (0 until childCount).forEach {
 | 
			
		||||
        (getChildAt(it) as? ViewGroup) ?.setOnHierarchyChangeListenerRecursively(listener)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -11,6 +11,7 @@ kotlin {
 | 
			
		||||
        commonMain {
 | 
			
		||||
            dependencies {
 | 
			
		||||
                api project(":micro_utils.common")
 | 
			
		||||
                api libs.krypto
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        jsMain {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,8 @@
 | 
			
		||||
package dev.inmo.micro_utils.crypto
 | 
			
		||||
 | 
			
		||||
import com.soywiz.krypto.md5
 | 
			
		||||
 | 
			
		||||
typealias MD5 = String
 | 
			
		||||
 | 
			
		||||
expect fun SourceBytes.md5(): MD5
 | 
			
		||||
fun SourceString.md5(): MD5 = encodeToByteArray().md5()
 | 
			
		||||
fun SourceBytes.md5(): MD5 = md5().hexLower
 | 
			
		||||
fun SourceString.md5(): MD5 = encodeToByteArray().md5().hexLower
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +0,0 @@
 | 
			
		||||
package dev.inmo.micro_utils.crypto
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @suppress
 | 
			
		||||
 */
 | 
			
		||||
actual fun SourceBytes.md5(): MD5 = CryptoJS.MD5(decodeToString())
 | 
			
		||||
@@ -1,12 +0,0 @@
 | 
			
		||||
package dev.inmo.micro_utils.crypto
 | 
			
		||||
 | 
			
		||||
import java.math.BigInteger
 | 
			
		||||
import java.security.MessageDigest
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @suppress
 | 
			
		||||
 */
 | 
			
		||||
actual fun SourceBytes.md5(): MD5 = BigInteger(
 | 
			
		||||
    1,
 | 
			
		||||
    MessageDigest.getInstance("MD5").digest(this)
 | 
			
		||||
).toString(16)
 | 
			
		||||
@@ -1,30 +1,5 @@
 | 
			
		||||
apply plugin: 'com.getkeepsafe.dexcount'
 | 
			
		||||
 | 
			
		||||
ext {
 | 
			
		||||
    jvmKotlinFolderFile = {
 | 
			
		||||
        String sep = File.separator
 | 
			
		||||
        return new File("${project.projectDir}${sep}src${sep}jvmMain${sep}kotlin")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    enableIncludingJvmCodeInAndroidPart = {
 | 
			
		||||
        File jvmKotlinFolder = jvmKotlinFolderFile()
 | 
			
		||||
        if (jvmKotlinFolder.exists()) {
 | 
			
		||||
            android.sourceSets.main.java.srcDirs += jvmKotlinFolder.path
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    disableIncludingJvmCodeInAndroidPart = {
 | 
			
		||||
        File jvmKotlinFolder = jvmKotlinFolderFile()
 | 
			
		||||
        String[] oldDirs = android.sourceSets.main.java.srcDirs
 | 
			
		||||
        android.sourceSets.main.java.srcDirs = []
 | 
			
		||||
        for (oldDir in oldDirs) {
 | 
			
		||||
            if (oldDir != jvmKotlinFolder.path) {
 | 
			
		||||
                android.sourceSets.main.java.srcDirs += oldDir
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
android {
 | 
			
		||||
    compileSdkVersion libs.versions.android.props.compileSdk.get().toInteger()
 | 
			
		||||
    buildToolsVersion libs.versions.android.props.buildTools.get()
 | 
			
		||||
@@ -58,10 +33,4 @@ android {
 | 
			
		||||
    kotlinOptions {
 | 
			
		||||
        jvmTarget = JavaVersion.VERSION_1_8.toString()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    sourceSets {
 | 
			
		||||
        String sep = File.separator
 | 
			
		||||
        main.java.srcDirs += "src${sep}main${sep}kotlin"
 | 
			
		||||
        enableIncludingJvmCodeInAndroidPart()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -121,11 +121,11 @@ tasks.dokkaHtml {
 | 
			
		||||
//        }
 | 
			
		||||
 | 
			
		||||
        named("jvmMain") {
 | 
			
		||||
            sourceRoots.setFrom(findSourcesWithName("jvmMain", "commonMain"))
 | 
			
		||||
            sourceRoots.setFrom(findSourcesWithName("jvmMain"))
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        named("androidMain") {
 | 
			
		||||
            sourceRoots.setFrom(findSourcesWithName("androidMain", "commonMain"))
 | 
			
		||||
            sourceRoots.setFrom(findSourcesWithName("androidMain"))
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -23,11 +23,12 @@ allprojects {
 | 
			
		||||
        mppProjectWithSerializationPresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerialization.gradle"
 | 
			
		||||
        mppProjectWithSerializationAndComposePresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerializationAndCompose.gradle"
 | 
			
		||||
        mppJavaProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJavaProject.gradle"
 | 
			
		||||
        mppJvmJsLinuxMingwProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJvmJsLinuxMingwProject.gradle"
 | 
			
		||||
        mppAndroidProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppAndroidProject.gradle"
 | 
			
		||||
 | 
			
		||||
        defaultAndroidSettingsPresetPath = "${rootProject.projectDir.absolutePath}/defaultAndroidSettings.gradle"
 | 
			
		||||
 | 
			
		||||
        publishGradlePath = "${rootProject.projectDir.absolutePath}/publish.gradle"
 | 
			
		||||
        publishMavenPath = "${rootProject.projectDir.absolutePath}/maven.publish.gradle"
 | 
			
		||||
        publishJvmOnlyPath = "${rootProject.projectDir.absolutePath}/jvm.publish.gradle"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -48,8 +48,8 @@ interface DefaultStatesManagerRepo<T : State> {
 | 
			
		||||
 */
 | 
			
		||||
open class DefaultStatesManager<T : State>(
 | 
			
		||||
    protected val repo: DefaultStatesManagerRepo<T> = InMemoryDefaultStatesManagerRepo(),
 | 
			
		||||
    protected val onStartContextsConflictResolver: suspend (current: T, new: T) -> Boolean = { _, _ -> true },
 | 
			
		||||
    protected val onUpdateContextsConflictResolver: suspend (old: T, new: T, currentNew: T) -> Boolean = { _, _, _ -> true }
 | 
			
		||||
    protected val onStartContextsConflictResolver: suspend (current: T, new: T) -> Boolean = { _, _ -> false },
 | 
			
		||||
    protected val onUpdateContextsConflictResolver: suspend (old: T, new: T, currentNew: T) -> Boolean = { _, _, _ -> false }
 | 
			
		||||
) : StatesManager<T> {
 | 
			
		||||
    protected val _onChainStateUpdated = MutableSharedFlow<Pair<T, T>>(0)
 | 
			
		||||
    override val onChainStateUpdated: Flow<Pair<T, T>> = _onChainStateUpdated.asSharedFlow()
 | 
			
		||||
 
 | 
			
		||||
@@ -14,5 +14,5 @@ crypto_js_version=4.1.1
 | 
			
		||||
# Project data
 | 
			
		||||
 | 
			
		||||
group=dev.inmo
 | 
			
		||||
version=0.12.10
 | 
			
		||||
android_code_version=149
 | 
			
		||||
version=0.18.0
 | 
			
		||||
android_code_version=191
 | 
			
		||||
 
 | 
			
		||||
@@ -1,36 +1,47 @@
 | 
			
		||||
[versions]
 | 
			
		||||
 | 
			
		||||
kt = "1.7.10"
 | 
			
		||||
kt-serialization = "1.4.0"
 | 
			
		||||
kt = "1.8.20"
 | 
			
		||||
kt-serialization = "1.5.0"
 | 
			
		||||
kt-coroutines = "1.6.4"
 | 
			
		||||
 | 
			
		||||
jb-compose = "1.2.0-alpha01-dev774"
 | 
			
		||||
jb-exposed = "0.39.2"
 | 
			
		||||
jb-dokka = "1.7.10"
 | 
			
		||||
kslog = "1.1.1"
 | 
			
		||||
 | 
			
		||||
klock = "3.1.0"
 | 
			
		||||
uuid = "0.5.0"
 | 
			
		||||
jb-compose = "1.4.0"
 | 
			
		||||
jb-exposed = "0.41.1"
 | 
			
		||||
jb-dokka = "1.8.10"
 | 
			
		||||
 | 
			
		||||
ktor = "2.1.1"
 | 
			
		||||
korlibs = "3.4.0"
 | 
			
		||||
uuid = "0.7.0"
 | 
			
		||||
 | 
			
		||||
ktor = "2.3.0"
 | 
			
		||||
 | 
			
		||||
gh-release = "2.4.1"
 | 
			
		||||
 | 
			
		||||
android-gradle = "7.2.2"
 | 
			
		||||
dexcount = "3.1.0"
 | 
			
		||||
koin = "3.4.0"
 | 
			
		||||
 | 
			
		||||
android-coreKtx = "1.8.0"
 | 
			
		||||
android-recyclerView = "1.2.1"
 | 
			
		||||
android-appCompat = "1.4.2"
 | 
			
		||||
android-espresso = "3.4.0"
 | 
			
		||||
android-test = "1.1.3"
 | 
			
		||||
okio = "3.3.0"
 | 
			
		||||
 | 
			
		||||
ksp = "1.8.20-1.0.11"
 | 
			
		||||
kotlin-poet = "1.13.0"
 | 
			
		||||
 | 
			
		||||
android-gradle = "7.4.2"
 | 
			
		||||
dexcount = "4.0.0"
 | 
			
		||||
 | 
			
		||||
android-coreKtx = "1.10.0"
 | 
			
		||||
android-recyclerView = "1.3.0"
 | 
			
		||||
android-appCompat = "1.6.1"
 | 
			
		||||
android-fragment = "1.5.7"
 | 
			
		||||
android-espresso = "3.5.1"
 | 
			
		||||
android-test = "1.1.5"
 | 
			
		||||
 | 
			
		||||
android-props-minSdk = "21"
 | 
			
		||||
android-props-compileSdk = "32"
 | 
			
		||||
android-props-buildTools = "32.0.0"
 | 
			
		||||
android-props-compileSdk = "33"
 | 
			
		||||
android-props-buildTools = "33.0.2"
 | 
			
		||||
 | 
			
		||||
[libraries]
 | 
			
		||||
 | 
			
		||||
kt-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kt" }
 | 
			
		||||
kt-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kt" }
 | 
			
		||||
 | 
			
		||||
kt-serialization = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kt-serialization" }
 | 
			
		||||
kt-serialization-cbor = { module = "org.jetbrains.kotlinx:kotlinx-serialization-cbor", version.ref = "kt-serialization" }
 | 
			
		||||
@@ -56,9 +67,12 @@ ktor-server-websockets = { module = "io.ktor:ktor-server-websockets", version.re
 | 
			
		||||
ktor-server-statusPages = { module = "io.ktor:ktor-server-status-pages", version.ref = "ktor" }
 | 
			
		||||
ktor-server-content-negotiation = { module = "io.ktor:ktor-server-content-negotiation", version.ref = "ktor" }
 | 
			
		||||
 | 
			
		||||
kslog = { module = "dev.inmo:kslog", version.ref = "kslog" }
 | 
			
		||||
 | 
			
		||||
klock = { module = "com.soywiz.korlibs.klock:klock", version.ref = "klock" }
 | 
			
		||||
klock = { module = "com.soywiz.korlibs.klock:klock", version.ref = "korlibs" }
 | 
			
		||||
krypto = { module = "com.soywiz.korlibs.krypto:krypto", version.ref = "korlibs" }
 | 
			
		||||
uuid = { module = "com.benasher44:uuid", version.ref = "uuid" }
 | 
			
		||||
koin = { module = "io.insert-koin:koin-core", version.ref = "koin" }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
jb-exposed = { module = "org.jetbrains.exposed:exposed-core", version.ref = "jb-exposed" }
 | 
			
		||||
@@ -67,6 +81,7 @@ jb-exposed = { module = "org.jetbrains.exposed:exposed-core", version.ref = "jb-
 | 
			
		||||
android-coreKtx = { module = "androidx.core:core-ktx", version.ref = "android-coreKtx" }
 | 
			
		||||
android-recyclerView = { module = "androidx.recyclerview:recyclerview", version.ref = "android-recyclerView" }
 | 
			
		||||
android-appCompat-resources = { module = "androidx.appcompat:appcompat-resources", version.ref = "android-appCompat" }
 | 
			
		||||
android-fragment = { module = "androidx.fragment:fragment", version.ref = "android-fragment" }
 | 
			
		||||
android-espresso = { module = "androidx.test.espresso:espresso-core", version.ref = "android-espresso" }
 | 
			
		||||
android-test-junit = { module = "androidx.test.ext:junit", version.ref = "android-test" }
 | 
			
		||||
 | 
			
		||||
@@ -74,9 +89,18 @@ 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-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-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-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" }
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							@@ -1,5 +1,5 @@
 | 
			
		||||
distributionBase=GRADLE_USER_HOME
 | 
			
		||||
distributionPath=wrapper/dists
 | 
			
		||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
 | 
			
		||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-bin.zip
 | 
			
		||||
zipStoreBase=GRADLE_USER_HOME
 | 
			
		||||
zipStorePath=wrapper/dists
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										118
									
								
								jvm.publish.gradle
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								jvm.publish.gradle
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,118 @@
 | 
			
		||||
apply plugin: 'maven-publish'
 | 
			
		||||
 | 
			
		||||
task javadocJar(type: Jar) {
 | 
			
		||||
    from javadoc
 | 
			
		||||
    classifier = 'javadoc'
 | 
			
		||||
}
 | 
			
		||||
task sourcesJar(type: Jar) {
 | 
			
		||||
    from sourceSets.main.allSource
 | 
			
		||||
    classifier = 'sources'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
publishing {
 | 
			
		||||
    publications {
 | 
			
		||||
        maven(MavenPublication) {
 | 
			
		||||
            from components.java
 | 
			
		||||
 | 
			
		||||
            artifact javadocJar
 | 
			
		||||
            artifact sourcesJar
 | 
			
		||||
 | 
			
		||||
            pom {
 | 
			
		||||
                resolveStrategy = Closure.DELEGATE_FIRST
 | 
			
		||||
 | 
			
		||||
                description = "It is set of projects with micro tools for avoiding of routines coding"
 | 
			
		||||
                name = "${project.name}"
 | 
			
		||||
                url = "https://github.com/InsanusMokrassar/MicroUtils/"
 | 
			
		||||
 | 
			
		||||
                scm {
 | 
			
		||||
                    developerConnection = "scm:git:[fetch=]https://github.com/InsanusMokrassar/MicroUtils.git[push=]https://github.com/InsanusMokrassar/MicroUtils.git"
 | 
			
		||||
                    url = "https://github.com/InsanusMokrassar/MicroUtils.git"
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                developers {
 | 
			
		||||
                    
 | 
			
		||||
                        developer {
 | 
			
		||||
                            id = "InsanusMokrassar"
 | 
			
		||||
                            name = "Aleksei Ovsiannikov"
 | 
			
		||||
                            email = "ovsyannikov.alexey95@gmail.com"
 | 
			
		||||
                        }
 | 
			
		||||
                    
 | 
			
		||||
 | 
			
		||||
                        developer {
 | 
			
		||||
                            id = "000Sanya"
 | 
			
		||||
                            name = "Syrov Aleksandr"
 | 
			
		||||
                            email = "000sanya.000sanya@gmail.com"
 | 
			
		||||
                        }
 | 
			
		||||
                    
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                licenses {
 | 
			
		||||
                    
 | 
			
		||||
                        license {
 | 
			
		||||
                            name = "Apache Software License 2.0"
 | 
			
		||||
                            url = "https://github.com/InsanusMokrassar/MicroUtils/blob/master/LICENSE"
 | 
			
		||||
                        }
 | 
			
		||||
                    
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            repositories {
 | 
			
		||||
                if ((project.hasProperty('GITHUBPACKAGES_USER') || System.getenv('GITHUBPACKAGES_USER') != null) && (project.hasProperty('GITHUBPACKAGES_PASSWORD') || System.getenv('GITHUBPACKAGES_PASSWORD') != null)) {
 | 
			
		||||
                    maven {
 | 
			
		||||
                        name = "GithubPackages"
 | 
			
		||||
                        url = uri("https://maven.pkg.github.com/InsanusMokrassar/MicroUtils")
 | 
			
		||||
                
 | 
			
		||||
                        credentials {
 | 
			
		||||
                            username = project.hasProperty('GITHUBPACKAGES_USER') ? project.property('GITHUBPACKAGES_USER') : System.getenv('GITHUBPACKAGES_USER')
 | 
			
		||||
                            password = project.hasProperty('GITHUBPACKAGES_PASSWORD') ? project.property('GITHUBPACKAGES_PASSWORD') : System.getenv('GITHUBPACKAGES_PASSWORD')
 | 
			
		||||
                        }
 | 
			
		||||
                
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                if (project.hasProperty('GITEA_TOKEN') || System.getenv('GITEA_TOKEN') != null) {
 | 
			
		||||
                    maven {
 | 
			
		||||
                        name = "Gitea"
 | 
			
		||||
                        url = uri("https://git.inmo.dev/api/packages/InsanusMokrassar/maven")
 | 
			
		||||
                
 | 
			
		||||
                        credentials(HttpHeaderCredentials) {
 | 
			
		||||
                            name = "Authorization"
 | 
			
		||||
                            value = project.hasProperty('GITEA_TOKEN') ? project.property('GITEA_TOKEN') : System.getenv('GITEA_TOKEN')
 | 
			
		||||
                        }
 | 
			
		||||
                
 | 
			
		||||
                        authentication {
 | 
			
		||||
                            header(HttpHeaderAuthentication)
 | 
			
		||||
                        }
 | 
			
		||||
                
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                if ((project.hasProperty('SONATYPE_USER') || System.getenv('SONATYPE_USER') != null) && (project.hasProperty('SONATYPE_PASSWORD') || System.getenv('SONATYPE_PASSWORD') != null)) {
 | 
			
		||||
                    maven {
 | 
			
		||||
                        name = "sonatype"
 | 
			
		||||
                        url = uri("https://oss.sonatype.org/service/local/staging/deploy/maven2/")
 | 
			
		||||
                
 | 
			
		||||
                        credentials {
 | 
			
		||||
                            username = project.hasProperty('SONATYPE_USER') ? project.property('SONATYPE_USER') : System.getenv('SONATYPE_USER')
 | 
			
		||||
                            password = project.hasProperty('SONATYPE_PASSWORD') ? project.property('SONATYPE_PASSWORD') : System.getenv('SONATYPE_PASSWORD')
 | 
			
		||||
                        }
 | 
			
		||||
                
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
if (project.hasProperty("signing.gnupg.keyName")) {
 | 
			
		||||
    apply plugin: 'signing'
 | 
			
		||||
    
 | 
			
		||||
    signing {
 | 
			
		||||
        useGpgCmd()
 | 
			
		||||
    
 | 
			
		||||
        sign publishing.publications
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    task signAll {
 | 
			
		||||
        tasks.withType(Sign).forEach {
 | 
			
		||||
            dependsOn(it)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1
									
								
								jvm.publish.kpsb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								jvm.publish.kpsb
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
{"licenses":[{"id":"Apache-2.0","title":"Apache Software License 2.0","url":"https://github.com/InsanusMokrassar/MicroUtils/blob/master/LICENSE"}],"mavenConfig":{"name":"${project.name}","description":"It is set of projects with micro tools for avoiding of routines coding","url":"https://github.com/InsanusMokrassar/MicroUtils/","vcsUrl":"https://github.com/InsanusMokrassar/MicroUtils.git","developers":[{"id":"InsanusMokrassar","name":"Aleksei Ovsiannikov","eMail":"ovsyannikov.alexey95@gmail.com"},{"id":"000Sanya","name":"Syrov Aleksandr","eMail":"000sanya.000sanya@gmail.com"}],"repositories":[{"name":"GithubPackages","url":"https://maven.pkg.github.com/InsanusMokrassar/MicroUtils"},{"name":"Gitea","url":"https://git.inmo.dev/api/packages/InsanusMokrassar/maven","credsType":{"type":"dev.inmo.kmppscriptbuilder.core.models.MavenPublishingRepository.CredentialsType.HttpHeaderCredentials","headerName":"Authorization","headerValueProperty":"GITEA_TOKEN"}},{"name":"sonatype","url":"https://oss.sonatype.org/service/local/staging/deploy/maven2/"}],"gpgSigning":{"type":"dev.inmo.kmppscriptbuilder.core.models.GpgSigning.Optional"}},"type":"JVM"}
 | 
			
		||||
							
								
								
									
										28
									
								
								koin/build.gradle
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								koin/build.gradle
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
			
		||||
plugins {
 | 
			
		||||
    id "org.jetbrains.kotlin.multiplatform"
 | 
			
		||||
    id "org.jetbrains.kotlin.plugin.serialization"
 | 
			
		||||
    id "com.android.library"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
apply from: "$mppProjectWithSerializationPresetPath"
 | 
			
		||||
 | 
			
		||||
kotlin {
 | 
			
		||||
    sourceSets {
 | 
			
		||||
        commonMain {
 | 
			
		||||
            dependencies {
 | 
			
		||||
                api libs.koin
 | 
			
		||||
                api libs.uuid
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        jvmMain {
 | 
			
		||||
            dependencies {
 | 
			
		||||
                api libs.kt.reflect
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        androidMain {
 | 
			
		||||
            dependencies {
 | 
			
		||||
                api libs.kt.reflect
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										100
									
								
								koin/generator/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								koin/generator/README.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,100 @@
 | 
			
		||||
# Koin generator
 | 
			
		||||
 | 
			
		||||
It is Kotlin Symbol Processing generator for `Koin` module in `MicroUtils`.
 | 
			
		||||
 | 
			
		||||
1. [What may do this generator](#what-may-do-this-generator)
 | 
			
		||||
2. [How to add generator](#how-to-add-generator)
 | 
			
		||||
 | 
			
		||||
## What may do this generator
 | 
			
		||||
 | 
			
		||||
Let's imagine you want to have shortcuts in koin, to get something easily:
 | 
			
		||||
 | 
			
		||||
```kotlin
 | 
			
		||||
val koin: Koin// some initialization
 | 
			
		||||
 | 
			
		||||
val someUrl = koin.serverUrl
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
So, in that case you may mark containing file with next annotation (in the beginning of file):
 | 
			
		||||
 | 
			
		||||
```kotlin
 | 
			
		||||
@file:GenerateKoinDefinition("serverUrl", String::class, nullable = false)
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
If file is called like `Sample.kt`, will be generated file `GeneratedDefinitionsSample.kt` with next content:
 | 
			
		||||
 | 
			
		||||
```kotlin
 | 
			
		||||
public val Scope.serverUrl: String
 | 
			
		||||
  get() = get(named("serverUrl"))
 | 
			
		||||
 | 
			
		||||
public val Koin.serverUrl: String
 | 
			
		||||
  get() = get(named("serverUrl"))
 | 
			
		||||
 | 
			
		||||
public fun Module.serverUrlSingle(createdAtStart: Boolean = false,
 | 
			
		||||
    definition: Definition<String>): KoinDefinition<String> =
 | 
			
		||||
    single(named("serverUrl"), createdAtStart = createdAtStart, definition = definition)
 | 
			
		||||
 | 
			
		||||
public fun Module.serverUrlFactory(definition: Definition<String>):
 | 
			
		||||
    KoinDefinition<String> = factory(named("serverUrl"), definition = definition)
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Besides, you may use the generics:
 | 
			
		||||
 | 
			
		||||
```kotlin
 | 
			
		||||
@file:GenerateKoinDefinition("sampleInfo", Sample::class, G1::class, G2::class, nullable = false)
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Will generate:
 | 
			
		||||
 | 
			
		||||
```kotlin
 | 
			
		||||
public val Scope.sampleInfo: Sample<G1, G2>
 | 
			
		||||
  get() = get(named("sampleInfo"))
 | 
			
		||||
 | 
			
		||||
public val Koin.sampleInfo: Sample<G1, G2>
 | 
			
		||||
  get() = get(named("sampleInfo"))
 | 
			
		||||
 | 
			
		||||
public fun Module.sampleInfoSingle(createdAtStart: Boolean = false,
 | 
			
		||||
    definition: Definition<Sample<G1, G2>>): KoinDefinition<Sample<G1, G2>> =
 | 
			
		||||
    single(named("sampleInfo"), createdAtStart = createdAtStart, definition = definition)
 | 
			
		||||
 | 
			
		||||
public fun Module.sampleInfoFactory(definition: Definition<Sample<G1, G2>>):
 | 
			
		||||
    KoinDefinition<Sample<G1, G2>> = factory(named("sampleInfo"), definition = definition)
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
In case you wish not to generate single:
 | 
			
		||||
 | 
			
		||||
```kotlin
 | 
			
		||||
@file:GenerateKoinDefinition("sampleInfo", Sample::class, G1::class, G2::class, nullable = false, generateSingle = false)
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
And you will take next code:
 | 
			
		||||
 | 
			
		||||
```kotlin
 | 
			
		||||
public val Scope.sampleInfo: Sample<G1, G2>
 | 
			
		||||
  get() = get(named("sampleInfo"))
 | 
			
		||||
 | 
			
		||||
public val Koin.sampleInfo: Sample<G1, G2>
 | 
			
		||||
  get() = get(named("sampleInfo"))
 | 
			
		||||
 | 
			
		||||
public fun Module.sampleInfoFactory(definition: Definition<Sample<G1, G2>>):
 | 
			
		||||
    KoinDefinition<Sample<G1, G2>> = factory(named("sampleInfo"), definition = definition)
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## How to add generator
 | 
			
		||||
 | 
			
		||||
**Note: $ksp_version in the samples above is equal to supported `ksp` version presented in `/gradle/libs.versions.toml` of project**
 | 
			
		||||
 | 
			
		||||
**Note: $microutils_version in the version of MicroUtils library in your project**
 | 
			
		||||
 | 
			
		||||
1. Add `classpath` in `build.gradle` (`classpath "com.google.devtools.ksp:symbol-processing-gradle-plugin:$ksp_version"`)
 | 
			
		||||
2. Add plugin to the plugins list of your module: `id "com.google.devtools.ksp"`
 | 
			
		||||
3. In `dependencies` block add to the required target/compile the dependency `dev.inmo:micro_utils.koin.generator:$microutils_version`:
 | 
			
		||||
   ```groovy
 | 
			
		||||
    dependencies {
 | 
			
		||||
        add("kspCommonMainMetadata", "dev.inmo:micro_utils.koin.generator:$microutils_version") // will work in commonMain of your multiplatform module
 | 
			
		||||
        add("kspJvm", "dev.inmo:micro_utils.koin.generator:$microutils_version") // will work in main of your JVM module
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    ksp { // this generator do not require any arguments and we should left `ksp` empty
 | 
			
		||||
    }
 | 
			
		||||
    ```
 | 
			
		||||
							
								
								
									
										15
									
								
								koin/generator/build.gradle
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								koin/generator/build.gradle
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
			
		||||
plugins {
 | 
			
		||||
    id "org.jetbrains.kotlin.jvm"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
apply from: "$publishJvmOnlyPath"
 | 
			
		||||
 | 
			
		||||
repositories {
 | 
			
		||||
    mavenCentral()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
dependencies {
 | 
			
		||||
    api project(":micro_utils.koin")
 | 
			
		||||
    api libs.kotlin.poet
 | 
			
		||||
    api libs.ksp
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										178
									
								
								koin/generator/src/main/kotlin/Processor.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										178
									
								
								koin/generator/src/main/kotlin/Processor.kt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,178 @@
 | 
			
		||||
package dev.inmo.micro_utils.koin.generator
 | 
			
		||||
 | 
			
		||||
import com.google.devtools.ksp.KSTypeNotPresentException
 | 
			
		||||
import com.google.devtools.ksp.KSTypesNotPresentException
 | 
			
		||||
import com.google.devtools.ksp.KspExperimental
 | 
			
		||||
import com.google.devtools.ksp.getAnnotationsByType
 | 
			
		||||
import com.google.devtools.ksp.processing.CodeGenerator
 | 
			
		||||
import com.google.devtools.ksp.processing.Resolver
 | 
			
		||||
import com.google.devtools.ksp.processing.SymbolProcessor
 | 
			
		||||
import com.google.devtools.ksp.symbol.KSAnnotated
 | 
			
		||||
import com.google.devtools.ksp.symbol.KSFile
 | 
			
		||||
import com.squareup.kotlinpoet.ClassName
 | 
			
		||||
import com.squareup.kotlinpoet.FileSpec
 | 
			
		||||
import com.squareup.kotlinpoet.FunSpec
 | 
			
		||||
import com.squareup.kotlinpoet.ParameterSpec
 | 
			
		||||
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
 | 
			
		||||
import com.squareup.kotlinpoet.PropertySpec
 | 
			
		||||
import com.squareup.kotlinpoet.asTypeName
 | 
			
		||||
import com.squareup.kotlinpoet.ksp.toClassName
 | 
			
		||||
import com.squareup.kotlinpoet.ksp.toTypeName
 | 
			
		||||
import com.squareup.kotlinpoet.ksp.writeTo
 | 
			
		||||
import dev.inmo.micro_utils.koin.annotations.GenerateKoinDefinition
 | 
			
		||||
import org.koin.core.Koin
 | 
			
		||||
import org.koin.core.module.Module
 | 
			
		||||
import org.koin.core.scope.Scope
 | 
			
		||||
import java.io.File
 | 
			
		||||
import kotlin.reflect.KClass
 | 
			
		||||
 | 
			
		||||
class Processor(
 | 
			
		||||
    private val codeGenerator: CodeGenerator
 | 
			
		||||
) : SymbolProcessor {
 | 
			
		||||
    private val definitionClassName = ClassName("org.koin.core.definition", "Definition")
 | 
			
		||||
    private val koinDefinitionClassName = ClassName("org.koin.core.definition", "KoinDefinition")
 | 
			
		||||
 | 
			
		||||
    @OptIn(KspExperimental::class)
 | 
			
		||||
    override fun process(resolver: Resolver): List<KSAnnotated> {
 | 
			
		||||
        resolver.getSymbolsWithAnnotation(
 | 
			
		||||
            GenerateKoinDefinition::class.qualifiedName!!
 | 
			
		||||
        ).filterIsInstance<KSFile>().forEach { ksFile ->
 | 
			
		||||
            FileSpec.builder(
 | 
			
		||||
                ksFile.packageName.asString(),
 | 
			
		||||
                "GeneratedDefinitions${ksFile.fileName.removeSuffix(".kt")}"
 | 
			
		||||
            ).apply {
 | 
			
		||||
                addFileComment(
 | 
			
		||||
                    """
 | 
			
		||||
                        THIS CODE HAVE BEEN GENERATED AUTOMATICALLY
 | 
			
		||||
                        TO REGENERATE IT JUST DELETE FILE
 | 
			
		||||
                        ORIGINAL FILE: ${ksFile.fileName}
 | 
			
		||||
                    """.trimIndent()
 | 
			
		||||
                )
 | 
			
		||||
                ksFile.getAnnotationsByType(GenerateKoinDefinition::class).forEach {
 | 
			
		||||
                    val type = runCatching {
 | 
			
		||||
                        it.type.asTypeName()
 | 
			
		||||
                    }.getOrElse { e ->
 | 
			
		||||
                        if (e is KSTypeNotPresentException) {
 | 
			
		||||
                            e.ksType.toClassName()
 | 
			
		||||
                        } else {
 | 
			
		||||
                            throw e
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    val targetType = runCatching {
 | 
			
		||||
                        type.parameterizedBy(*(it.typeArgs.takeIf { it.isNotEmpty() } ?.map { it.asTypeName() } ?.toTypedArray() ?: return@runCatching type))
 | 
			
		||||
                    }.getOrElse { e ->
 | 
			
		||||
                        when (e) {
 | 
			
		||||
                            is KSTypeNotPresentException -> e.ksType.toClassName()
 | 
			
		||||
                        }
 | 
			
		||||
                        if (e is KSTypesNotPresentException) {
 | 
			
		||||
                            type.parameterizedBy(*e.ksTypes.map { it.toTypeName() }.toTypedArray())
 | 
			
		||||
                        } else {
 | 
			
		||||
                            throw e
 | 
			
		||||
                        }
 | 
			
		||||
                    }.copy(
 | 
			
		||||
                        nullable = it.nullable
 | 
			
		||||
                    )
 | 
			
		||||
                    fun addGetterProperty(
 | 
			
		||||
                        receiver: KClass<*>
 | 
			
		||||
                    ) {
 | 
			
		||||
                        addProperty(
 | 
			
		||||
                            PropertySpec.builder(
 | 
			
		||||
                                it.name,
 | 
			
		||||
                                targetType,
 | 
			
		||||
                            ).apply {
 | 
			
		||||
                                addKdoc(
 | 
			
		||||
                                    """
 | 
			
		||||
                                        @return Definition by key "${it.name}"
 | 
			
		||||
                                    """.trimIndent()
 | 
			
		||||
                                )
 | 
			
		||||
                                getter(
 | 
			
		||||
                                    FunSpec.getterBuilder().apply {
 | 
			
		||||
                                        addCode(
 | 
			
		||||
                                            "return " + (if (it.nullable) {
 | 
			
		||||
                                                "getOrNull"
 | 
			
		||||
                                            } else {
 | 
			
		||||
                                                "get"
 | 
			
		||||
                                            }) + "(named(\"${it.name}\"))"
 | 
			
		||||
                                        )
 | 
			
		||||
                                    }.build()
 | 
			
		||||
                                )
 | 
			
		||||
                                receiver(receiver)
 | 
			
		||||
                            }.build()
 | 
			
		||||
                        )
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    addGetterProperty(Scope::class)
 | 
			
		||||
                    addGetterProperty(Koin::class)
 | 
			
		||||
 | 
			
		||||
                    if (it.generateSingle) {
 | 
			
		||||
                        addFunction(
 | 
			
		||||
                            FunSpec.builder("${it.name}Single").apply {
 | 
			
		||||
                                addKdoc(
 | 
			
		||||
                                    """
 | 
			
		||||
                                        Will register [definition] with [org.koin.core.module.Module.single] and key "${it.name}"
 | 
			
		||||
                                    """.trimIndent()
 | 
			
		||||
                                )
 | 
			
		||||
                                receiver(Module::class)
 | 
			
		||||
                                addParameter(
 | 
			
		||||
                                    ParameterSpec.builder(
 | 
			
		||||
                                        "createdAtStart",
 | 
			
		||||
                                        Boolean::class
 | 
			
		||||
                                    ).apply {
 | 
			
		||||
                                        defaultValue("false")
 | 
			
		||||
                                    }.build()
 | 
			
		||||
                                )
 | 
			
		||||
                                addParameter(
 | 
			
		||||
                                    ParameterSpec.builder(
 | 
			
		||||
                                        "definition",
 | 
			
		||||
                                        definitionClassName.parameterizedBy(targetType.copy(nullable = false))
 | 
			
		||||
                                    ).build()
 | 
			
		||||
                                )
 | 
			
		||||
                                returns(koinDefinitionClassName.parameterizedBy(targetType.copy(nullable = false)))
 | 
			
		||||
                                addCode(
 | 
			
		||||
                                    "return single(named(\"${it.name}\"), createdAtStart = createdAtStart, definition = definition)"
 | 
			
		||||
                                )
 | 
			
		||||
                            }.build()
 | 
			
		||||
                        )
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if (it.generateFactory) {
 | 
			
		||||
                        addFunction(
 | 
			
		||||
                            FunSpec.builder("${it.name}Factory").apply {
 | 
			
		||||
                                addKdoc(
 | 
			
		||||
                                    """
 | 
			
		||||
                                        Will register [definition] with [org.koin.core.module.Module.factory] and key "${it.name}"
 | 
			
		||||
                                    """.trimIndent()
 | 
			
		||||
                                )
 | 
			
		||||
                                receiver(Module::class)
 | 
			
		||||
                                addParameter(
 | 
			
		||||
                                    ParameterSpec.builder(
 | 
			
		||||
                                        "definition",
 | 
			
		||||
                                        definitionClassName.parameterizedBy(targetType.copy(nullable = false))
 | 
			
		||||
                                    ).build()
 | 
			
		||||
                                )
 | 
			
		||||
                                returns(koinDefinitionClassName.parameterizedBy(targetType.copy(nullable = false)))
 | 
			
		||||
                                addCode(
 | 
			
		||||
                                    "return factory(named(\"${it.name}\"), definition = definition)"
 | 
			
		||||
                                )
 | 
			
		||||
                            }.build()
 | 
			
		||||
                        )
 | 
			
		||||
                    }
 | 
			
		||||
                    addImport("org.koin.core.qualifier", "named")
 | 
			
		||||
                }
 | 
			
		||||
            }.build().let {
 | 
			
		||||
                File(
 | 
			
		||||
                    File(ksFile.filePath).parent,
 | 
			
		||||
                    "GeneratedDefinitions${ksFile.fileName}"
 | 
			
		||||
                ).takeIf { !it.exists() } ?.apply {
 | 
			
		||||
                    parentFile.mkdirs()
 | 
			
		||||
 | 
			
		||||
                    writer().use { writer ->
 | 
			
		||||
                        it.writeTo(writer)
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return emptyList()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										11
									
								
								koin/generator/src/main/kotlin/Provider.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								koin/generator/src/main/kotlin/Provider.kt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
package dev.inmo.micro_utils.koin.generator
 | 
			
		||||
 | 
			
		||||
import com.google.devtools.ksp.processing.SymbolProcessor
 | 
			
		||||
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
 | 
			
		||||
import com.google.devtools.ksp.processing.SymbolProcessorProvider
 | 
			
		||||
 | 
			
		||||
class Provider : SymbolProcessorProvider {
 | 
			
		||||
    override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor = Processor(
 | 
			
		||||
        environment.codeGenerator
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1 @@
 | 
			
		||||
dev.inmo.micro_utils.koin.generator.Provider
 | 
			
		||||
							
								
								
									
										27
									
								
								koin/generator/test/build.gradle
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								koin/generator/test/build.gradle
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
			
		||||
plugins {
 | 
			
		||||
    id "org.jetbrains.kotlin.multiplatform"
 | 
			
		||||
    id "org.jetbrains.kotlin.plugin.serialization"
 | 
			
		||||
    id "com.android.library"
 | 
			
		||||
    id "com.google.devtools.ksp"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
apply from: "$mppProjectWithSerializationPresetPath"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
kotlin {
 | 
			
		||||
    sourceSets {
 | 
			
		||||
        commonMain {
 | 
			
		||||
            dependencies {
 | 
			
		||||
                api project(":micro_utils.koin")
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
dependencies {
 | 
			
		||||
    add("kspCommonMainMetadata", project(":micro_utils.koin.generator"))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ksp {
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,38 @@
 | 
			
		||||
// THIS CODE HAVE BEEN GENERATED AUTOMATICALLY
 | 
			
		||||
// TO REGENERATE IT JUST DELETE FILE
 | 
			
		||||
// ORIGINAL FILE: Test.kt
 | 
			
		||||
package dev.inmo.micro_utils.koin.generator.test
 | 
			
		||||
 | 
			
		||||
import kotlin.Boolean
 | 
			
		||||
import kotlin.String
 | 
			
		||||
import org.koin.core.Koin
 | 
			
		||||
import org.koin.core.definition.Definition
 | 
			
		||||
import org.koin.core.definition.KoinDefinition
 | 
			
		||||
import org.koin.core.module.Module
 | 
			
		||||
import org.koin.core.qualifier.named
 | 
			
		||||
import org.koin.core.scope.Scope
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @return Definition by key "sampleInfo"
 | 
			
		||||
 */
 | 
			
		||||
public val Scope.sampleInfo: Test<String>
 | 
			
		||||
  get() = get(named("sampleInfo"))
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @return Definition by key "sampleInfo"
 | 
			
		||||
 */
 | 
			
		||||
public val Koin.sampleInfo: Test<String>
 | 
			
		||||
  get() = get(named("sampleInfo"))
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Will register [definition] with [org.koin.core.module.Module.single] and key "sampleInfo"
 | 
			
		||||
 */
 | 
			
		||||
public fun Module.sampleInfoSingle(createdAtStart: Boolean = false,
 | 
			
		||||
    definition: Definition<Test<String>>): KoinDefinition<Test<String>> =
 | 
			
		||||
    single(named("sampleInfo"), createdAtStart = createdAtStart, definition = definition)
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Will register [definition] with [org.koin.core.module.Module.factory] and key "sampleInfo"
 | 
			
		||||
 */
 | 
			
		||||
public fun Module.sampleInfoFactory(definition: Definition<Test<String>>):
 | 
			
		||||
    KoinDefinition<Test<String>> = factory(named("sampleInfo"), definition = definition)
 | 
			
		||||
							
								
								
									
										13
									
								
								koin/generator/test/src/commonMain/kotlin/Test.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								koin/generator/test/src/commonMain/kotlin/Test.kt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
@file:GenerateKoinDefinition("sampleInfo", Test::class, String::class, nullable = false)
 | 
			
		||||
package dev.inmo.micro_utils.koin.generator.test
 | 
			
		||||
 | 
			
		||||
import dev.inmo.micro_utils.koin.annotations.GenerateKoinDefinition
 | 
			
		||||
import org.koin.core.Koin
 | 
			
		||||
 | 
			
		||||
class Test<T>(
 | 
			
		||||
    koin: Koin
 | 
			
		||||
) {
 | 
			
		||||
    init {
 | 
			
		||||
        koin.sampleInfo
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1
									
								
								koin/generator/test/src/main/AndroidManifest.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								koin/generator/test/src/main/AndroidManifest.xml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
<manifest package="dev.inmo.micro_utils.koin.generator.test"/>
 | 
			
		||||
							
								
								
									
										12
									
								
								koin/src/commonMain/kotlin/FactoryWithRandomQualifier.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								koin/src/commonMain/kotlin/FactoryWithRandomQualifier.kt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
			
		||||
package dev.inmo.micro_utils.koin
 | 
			
		||||
 | 
			
		||||
import org.koin.core.definition.Definition
 | 
			
		||||
import org.koin.core.module.Module
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Will be useful in case you need to declare some singles with one type several types, but need to separate them and do
 | 
			
		||||
 * not care about how :)
 | 
			
		||||
 */
 | 
			
		||||
inline fun <reified T : Any> Module.factoryWithRandomQualifier(
 | 
			
		||||
    noinline definition: Definition<T>
 | 
			
		||||
) = factory(RandomQualifier(), definition)
 | 
			
		||||
							
								
								
									
										12
									
								
								koin/src/commonMain/kotlin/FactoryWithStringQualifier.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								koin/src/commonMain/kotlin/FactoryWithStringQualifier.kt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
			
		||||
package dev.inmo.micro_utils.koin
 | 
			
		||||
 | 
			
		||||
import org.koin.core.definition.Definition
 | 
			
		||||
import org.koin.core.module.Module
 | 
			
		||||
import org.koin.core.qualifier.StringQualifier
 | 
			
		||||
 | 
			
		||||
inline fun <reified T : Any> Module.factory(
 | 
			
		||||
    qualifier: String,
 | 
			
		||||
    noinline definition: Definition<T>
 | 
			
		||||
) = factory(StringQualifier(qualifier), definition)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										8
									
								
								koin/src/commonMain/kotlin/GetAllDistinct.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								koin/src/commonMain/kotlin/GetAllDistinct.kt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
			
		||||
package dev.inmo.micro_utils.koin
 | 
			
		||||
 | 
			
		||||
import org.koin.core.Koin
 | 
			
		||||
import org.koin.core.scope.Scope
 | 
			
		||||
 | 
			
		||||
inline fun <reified T : Any> Scope.getAllDistinct() = getAll<T>().distinct()
 | 
			
		||||
inline fun <reified T : Any> Koin.getAllDistinct() = getAll<T>().distinct()
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										8
									
								
								koin/src/commonMain/kotlin/GetAny.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								koin/src/commonMain/kotlin/GetAny.kt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
			
		||||
package dev.inmo.micro_utils.koin
 | 
			
		||||
 | 
			
		||||
import org.koin.core.Koin
 | 
			
		||||
import org.koin.core.scope.Scope
 | 
			
		||||
 | 
			
		||||
inline fun <reified T : Any> Scope.getAny() = getAll<T>().first()
 | 
			
		||||
inline fun <reified T : Any> Koin.getAny() = getAll<T>().first()
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										6
									
								
								koin/src/commonMain/kotlin/RandomQualifier.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								koin/src/commonMain/kotlin/RandomQualifier.kt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
			
		||||
package dev.inmo.micro_utils.koin
 | 
			
		||||
 | 
			
		||||
import com.benasher44.uuid.uuid4
 | 
			
		||||
import org.koin.core.qualifier.StringQualifier
 | 
			
		||||
 | 
			
		||||
fun RandomQualifier(randomFun: () -> String = { uuid4().toString() }) = StringQualifier(randomFun())
 | 
			
		||||
							
								
								
									
										13
									
								
								koin/src/commonMain/kotlin/SingleWithRandomQualifier.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								koin/src/commonMain/kotlin/SingleWithRandomQualifier.kt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
package dev.inmo.micro_utils.koin
 | 
			
		||||
 | 
			
		||||
import org.koin.core.definition.Definition
 | 
			
		||||
import org.koin.core.module.Module
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Will be useful in case you need to declare some singles with one type several types, but need to separate them and do
 | 
			
		||||
 * not care about how :)
 | 
			
		||||
 */
 | 
			
		||||
inline fun <reified T : Any> Module.singleWithRandomQualifier(
 | 
			
		||||
    createdAtStart: Boolean = false,
 | 
			
		||||
    noinline definition: Definition<T>
 | 
			
		||||
) = single(RandomQualifier(), createdAtStart, definition)
 | 
			
		||||
							
								
								
									
										12
									
								
								koin/src/commonMain/kotlin/SingleWithStringQualifier.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								koin/src/commonMain/kotlin/SingleWithStringQualifier.kt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
			
		||||
package dev.inmo.micro_utils.koin
 | 
			
		||||
 | 
			
		||||
import org.koin.core.definition.Definition
 | 
			
		||||
import org.koin.core.module.Module
 | 
			
		||||
import org.koin.core.qualifier.StringQualifier
 | 
			
		||||
 | 
			
		||||
inline fun <reified T : Any> Module.single(
 | 
			
		||||
    qualifier: String,
 | 
			
		||||
    createdAtStart: Boolean = false,
 | 
			
		||||
    noinline definition: Definition<T>
 | 
			
		||||
) = single(StringQualifier(qualifier), createdAtStart, definition)
 | 
			
		||||
 | 
			
		||||
@@ -0,0 +1,28 @@
 | 
			
		||||
package dev.inmo.micro_utils.koin.annotations
 | 
			
		||||
 | 
			
		||||
import kotlin.reflect.KClass
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Use this annotation to mark files near to which generator should place generated extensions for koin [org.koin.core.scope.Scope]
 | 
			
		||||
 * and [org.koin.core.Koin]
 | 
			
		||||
 *
 | 
			
		||||
 * @param name Name for definitions. This name will be available as extension for [org.koin.core.scope.Scope] and [org.koin.core.Koin]
 | 
			
		||||
 * @param type Type of extensions. It is base star-typed class
 | 
			
		||||
 * @param typeArgs Generic types for [type]. For example, if [type] == `Something::class` and [typeArgs] == `G1::class,
 | 
			
		||||
 * G2::class`, the result type will be `Something<G1, G2>`
 | 
			
		||||
 * @param nullable In case when true, extension will not throw error when definition has not been registered in koin
 | 
			
		||||
 * @param generateSingle Generate definition factory with [org.koin.core.module.Module.single]. You will be able to use
 | 
			
		||||
 * the extension [org.koin.core.module.Module].[name]Single(createdAtStart/* default false */) { /* your definition */ }
 | 
			
		||||
 * @param generateFactory Generate definition factory with [org.koin.core.module.Module.factory]. You will be able to use
 | 
			
		||||
 * the extension [org.koin.core.module.Module].[name]Factory { /* your definition */ }
 | 
			
		||||
 */
 | 
			
		||||
@Target(AnnotationTarget.FILE)
 | 
			
		||||
@Repeatable
 | 
			
		||||
annotation class GenerateKoinDefinition(
 | 
			
		||||
    val name: String,
 | 
			
		||||
    val type: KClass<*>,
 | 
			
		||||
    vararg val typeArgs: KClass<*>,
 | 
			
		||||
    val nullable: Boolean = true,
 | 
			
		||||
    val generateSingle: Boolean = true,
 | 
			
		||||
    val generateFactory: Boolean = true
 | 
			
		||||
)
 | 
			
		||||
							
								
								
									
										28
									
								
								koin/src/jvmMain/kotlin/FactoryWithBinds.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								koin/src/jvmMain/kotlin/FactoryWithBinds.kt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
			
		||||
package dev.inmo.micro_utils.koin
 | 
			
		||||
 | 
			
		||||
import org.koin.core.definition.Definition
 | 
			
		||||
import org.koin.core.definition.KoinDefinition
 | 
			
		||||
import org.koin.core.instance.InstanceFactory
 | 
			
		||||
import org.koin.core.module.Module
 | 
			
		||||
import org.koin.core.qualifier.Qualifier
 | 
			
		||||
import org.koin.dsl.binds
 | 
			
		||||
import kotlin.reflect.KClass
 | 
			
		||||
import kotlin.reflect.full.allSuperclasses
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
inline fun <reified T : Any> Module.factoryWithBinds(
 | 
			
		||||
    qualifier: Qualifier? = null,
 | 
			
		||||
    bindFilter: (KClass<*>) -> Boolean = { true },
 | 
			
		||||
    noinline definition: Definition<T>
 | 
			
		||||
): KoinDefinition<*> {
 | 
			
		||||
    return factory(qualifier, definition) binds (T::class.allSuperclasses.filter(bindFilter).toTypedArray())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
inline fun <reified T : Any> Module.factoryWithBinds(
 | 
			
		||||
    qualifier: String,
 | 
			
		||||
    bindFilter: (KClass<*>) -> Boolean = { true },
 | 
			
		||||
    noinline definition: Definition<T>
 | 
			
		||||
): KoinDefinition<*> {
 | 
			
		||||
    return factory(qualifier, definition) binds (T::class.allSuperclasses.filter(bindFilter).toTypedArray())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -0,0 +1,14 @@
 | 
			
		||||
package dev.inmo.micro_utils.koin
 | 
			
		||||
 | 
			
		||||
import org.koin.core.definition.Definition
 | 
			
		||||
import org.koin.core.definition.KoinDefinition
 | 
			
		||||
import org.koin.core.instance.InstanceFactory
 | 
			
		||||
import org.koin.core.module.Module
 | 
			
		||||
import kotlin.reflect.KClass
 | 
			
		||||
 | 
			
		||||
inline fun <reified T : Any> Module.factoryWithRandomQualifierAndBinds(
 | 
			
		||||
    bindFilter: (KClass<*>) -> Boolean = { true },
 | 
			
		||||
    noinline definition: Definition<T>
 | 
			
		||||
): KoinDefinition<*> {
 | 
			
		||||
    return factoryWithBinds(RandomQualifier(), bindFilter, definition)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										31
									
								
								koin/src/jvmMain/kotlin/SignleWithBinds.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								koin/src/jvmMain/kotlin/SignleWithBinds.kt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,31 @@
 | 
			
		||||
package dev.inmo.micro_utils.koin
 | 
			
		||||
 | 
			
		||||
import org.koin.core.definition.Definition
 | 
			
		||||
import org.koin.core.definition.KoinDefinition
 | 
			
		||||
import org.koin.core.instance.InstanceFactory
 | 
			
		||||
import org.koin.core.module.Module
 | 
			
		||||
import org.koin.core.qualifier.Qualifier
 | 
			
		||||
import org.koin.dsl.binds
 | 
			
		||||
import kotlin.reflect.KClass
 | 
			
		||||
import kotlin.reflect.full.allSuperclasses
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
inline fun <reified T : Any> Module.singleWithBinds(
 | 
			
		||||
    qualifier: Qualifier? = null,
 | 
			
		||||
    createdAtStart: Boolean = false,
 | 
			
		||||
    bindFilter: (KClass<*>) -> Boolean = { true },
 | 
			
		||||
    noinline definition: Definition<T>
 | 
			
		||||
): KoinDefinition<*> {
 | 
			
		||||
    return single(qualifier, createdAtStart, definition) binds (T::class.allSuperclasses.filter(bindFilter).toTypedArray())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
inline fun <reified T : Any> Module.singleWithBinds(
 | 
			
		||||
    qualifier: String,
 | 
			
		||||
    createdAtStart: Boolean = false,
 | 
			
		||||
    bindFilter: (KClass<*>) -> Boolean = { true },
 | 
			
		||||
    noinline definition: Definition<T>
 | 
			
		||||
): KoinDefinition<*> {
 | 
			
		||||
    return single(qualifier, createdAtStart, definition) binds (T::class.allSuperclasses.filter(bindFilter).toTypedArray())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										15
									
								
								koin/src/jvmMain/kotlin/SingleWithBindsAndRandomQualifier.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								koin/src/jvmMain/kotlin/SingleWithBindsAndRandomQualifier.kt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
			
		||||
package dev.inmo.micro_utils.koin
 | 
			
		||||
 | 
			
		||||
import org.koin.core.definition.Definition
 | 
			
		||||
import org.koin.core.definition.KoinDefinition
 | 
			
		||||
import org.koin.core.instance.InstanceFactory
 | 
			
		||||
import org.koin.core.module.Module
 | 
			
		||||
import kotlin.reflect.KClass
 | 
			
		||||
 | 
			
		||||
inline fun <reified T : Any> Module.singleWithRandomQualifierAndBinds(
 | 
			
		||||
    createdAtStart: Boolean = false,
 | 
			
		||||
    bindFilter: (KClass<*>) -> Boolean = { true },
 | 
			
		||||
    noinline definition: Definition<T>
 | 
			
		||||
): KoinDefinition<*> {
 | 
			
		||||
    return singleWithBinds(RandomQualifier(), createdAtStart, bindFilter, definition)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1
									
								
								koin/src/main/AndroidManifest.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								koin/src/main/AndroidManifest.xml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
<manifest package="dev.inmo.micro_utils.koin"/>
 | 
			
		||||
@@ -15,5 +15,20 @@ kotlin {
 | 
			
		||||
                api libs.ktor.client
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        androidMain {
 | 
			
		||||
            dependsOn jvmMain
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        linuxX64Main {
 | 
			
		||||
            dependencies {
 | 
			
		||||
                api internalProject("micro_utils.mime_types")
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        mingwX64Main {
 | 
			
		||||
            dependencies {
 | 
			
		||||
                api internalProject("micro_utils.mime_types")
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,10 @@ import io.ktor.client.call.body
 | 
			
		||||
import io.ktor.client.statement.HttpResponse
 | 
			
		||||
import io.ktor.http.HttpStatusCode
 | 
			
		||||
 | 
			
		||||
suspend inline fun <reified T : Any> HttpResponse.bodyOrNull() = takeIf {
 | 
			
		||||
    status == HttpStatusCode.OK
 | 
			
		||||
} ?.body<T>()
 | 
			
		||||
suspend inline fun <reified T : Any> HttpResponse.bodyOrNull(
 | 
			
		||||
    statusFilter: (HttpResponse) -> Boolean = { it.status == HttpStatusCode.OK }
 | 
			
		||||
) = takeIf(statusFilter) ?.body<T>()
 | 
			
		||||
 | 
			
		||||
suspend inline fun <reified T : Any> HttpResponse.bodyOrNullOnNoContent() = bodyOrNull<T> {
 | 
			
		||||
    it.status != HttpStatusCode.NoContent
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,82 +0,0 @@
 | 
			
		||||
package dev.inmo.micro_utils.ktor.client
 | 
			
		||||
 | 
			
		||||
import dev.inmo.micro_utils.coroutines.runCatchingSafely
 | 
			
		||||
import dev.inmo.micro_utils.coroutines.safely
 | 
			
		||||
import dev.inmo.micro_utils.ktor.common.*
 | 
			
		||||
import io.ktor.client.HttpClient
 | 
			
		||||
import io.ktor.client.plugins.pluginOrNull
 | 
			
		||||
import io.ktor.client.plugins.websocket.WebSockets
 | 
			
		||||
import io.ktor.client.plugins.websocket.ws
 | 
			
		||||
import io.ktor.client.request.HttpRequestBuilder
 | 
			
		||||
import io.ktor.websocket.Frame
 | 
			
		||||
import io.ktor.websocket.readBytes
 | 
			
		||||
import kotlinx.coroutines.flow.Flow
 | 
			
		||||
import kotlinx.coroutines.flow.channelFlow
 | 
			
		||||
import kotlinx.coroutines.isActive
 | 
			
		||||
import kotlinx.serialization.DeserializationStrategy
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @param checkReconnection This lambda will be called when it is required to reconnect to websocket to establish
 | 
			
		||||
 * connection. Must return true in case if must be reconnected. By default always reconnecting
 | 
			
		||||
 */
 | 
			
		||||
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
 | 
			
		||||
inline fun <T> HttpClient.createStandardWebsocketFlow(
 | 
			
		||||
    url: String,
 | 
			
		||||
    crossinline checkReconnection: suspend (Throwable?) -> Boolean = { true },
 | 
			
		||||
    noinline requestBuilder: HttpRequestBuilder.() -> Unit = {},
 | 
			
		||||
    crossinline conversation: suspend (StandardKtorSerialInputData) -> T
 | 
			
		||||
): Flow<T> {
 | 
			
		||||
    pluginOrNull(WebSockets) ?: error("Plugin $WebSockets must be installed for using createStandardWebsocketFlow")
 | 
			
		||||
 | 
			
		||||
    val correctedUrl = url.asCorrectWebSocketUrl
 | 
			
		||||
 | 
			
		||||
    return channelFlow {
 | 
			
		||||
        do {
 | 
			
		||||
            val reconnect = runCatchingSafely {
 | 
			
		||||
                ws(correctedUrl, requestBuilder) {
 | 
			
		||||
                    for (received in incoming) {
 | 
			
		||||
                        when (received) {
 | 
			
		||||
                            is Frame.Binary -> send(conversation(received.data))
 | 
			
		||||
                            else -> {
 | 
			
		||||
                                close()
 | 
			
		||||
                                return@ws
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                checkReconnection(null)
 | 
			
		||||
            }.getOrElse { e ->
 | 
			
		||||
                checkReconnection(e).also {
 | 
			
		||||
                    if (!it) {
 | 
			
		||||
                        close(e)
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        } while (reconnect && isActive)
 | 
			
		||||
 | 
			
		||||
        if (isActive) {
 | 
			
		||||
            safely {
 | 
			
		||||
                close()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @param checkReconnection This lambda will be called when it is required to reconnect to websocket to establish
 | 
			
		||||
 * connection. Must return true in case if must be reconnected. By default always reconnecting
 | 
			
		||||
 */
 | 
			
		||||
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
 | 
			
		||||
inline fun <T> HttpClient.createStandardWebsocketFlow(
 | 
			
		||||
    url: String,
 | 
			
		||||
    deserializer: DeserializationStrategy<T>,
 | 
			
		||||
    crossinline checkReconnection: suspend (Throwable?) -> Boolean = { true },
 | 
			
		||||
    serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
 | 
			
		||||
    noinline requestBuilder: HttpRequestBuilder.() -> Unit = {},
 | 
			
		||||
) = createStandardWebsocketFlow(
 | 
			
		||||
    url,
 | 
			
		||||
    checkReconnection,
 | 
			
		||||
    requestBuilder
 | 
			
		||||
) {
 | 
			
		||||
    serialFormat.decodeDefault(deserializer, it)
 | 
			
		||||
}
 | 
			
		||||
@@ -1,6 +1,10 @@
 | 
			
		||||
package dev.inmo.micro_utils.ktor.client
 | 
			
		||||
 | 
			
		||||
import dev.inmo.micro_utils.common.MPPFile
 | 
			
		||||
import dev.inmo.micro_utils.common.filesize
 | 
			
		||||
import dev.inmo.micro_utils.ktor.common.input
 | 
			
		||||
import io.ktor.client.request.forms.InputProvider
 | 
			
		||||
 | 
			
		||||
expect suspend fun MPPFile.inputProvider(): InputProvider
 | 
			
		||||
fun MPPFile.inputProvider(): InputProvider = InputProvider(filesize) {
 | 
			
		||||
    input()
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -19,7 +19,7 @@ import kotlinx.coroutines.isActive
 | 
			
		||||
 * connection. Must return true in case if must be reconnected. By default always reconnecting
 | 
			
		||||
 */
 | 
			
		||||
@Warning("This feature is internal and should not be used directly. It is can be changed without any notification and warranty on compile-time or other guaranties")
 | 
			
		||||
inline fun <reified T : Any> openBaseWebSocketFlow(
 | 
			
		||||
inline fun <T : Any> openBaseWebSocketFlow(
 | 
			
		||||
    noinline checkReconnection: suspend (Throwable?) -> Boolean = { true },
 | 
			
		||||
    noinline webSocketSessionRequest: suspend SendChannel<T>.() -> Unit
 | 
			
		||||
): Flow<T> {
 | 
			
		||||
@@ -57,7 +57,7 @@ inline fun <reified T : Any> HttpClient.openWebSocketFlow(
 | 
			
		||||
): Flow<T> {
 | 
			
		||||
    pluginOrNull(WebSockets) ?: error("Plugin $WebSockets must be installed for using createStandardWebsocketFlow")
 | 
			
		||||
 | 
			
		||||
    return openBaseWebSocketFlow<T>(checkReconnection) {
 | 
			
		||||
    return openBaseWebSocketFlow(checkReconnection) {
 | 
			
		||||
        val block: suspend DefaultClientWebSocketSession.() -> Unit = {
 | 
			
		||||
            while (isActive) {
 | 
			
		||||
                send(receiveDeserialized<T>())
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,3 @@
 | 
			
		||||
package dev.inmo.micro_utils.ktor.client
 | 
			
		||||
 | 
			
		||||
typealias OnUploadCallback = suspend (uploaded: Long, count: Long) -> Unit
 | 
			
		||||
@@ -1,260 +0,0 @@
 | 
			
		||||
package dev.inmo.micro_utils.ktor.client
 | 
			
		||||
 | 
			
		||||
import dev.inmo.micro_utils.common.MPPFile
 | 
			
		||||
import dev.inmo.micro_utils.common.filename
 | 
			
		||||
import dev.inmo.micro_utils.ktor.common.*
 | 
			
		||||
import io.ktor.client.HttpClient
 | 
			
		||||
import io.ktor.client.call.body
 | 
			
		||||
import io.ktor.client.request.*
 | 
			
		||||
import io.ktor.client.request.forms.*
 | 
			
		||||
import io.ktor.client.statement.readBytes
 | 
			
		||||
import io.ktor.http.*
 | 
			
		||||
import io.ktor.utils.io.core.ByteReadPacket
 | 
			
		||||
import kotlinx.serialization.*
 | 
			
		||||
 | 
			
		||||
@Deprecated("This class will be removed soon. It is now recommended to use built-in ktor features instead")
 | 
			
		||||
class UnifiedRequester(
 | 
			
		||||
    val client: HttpClient = HttpClient(),
 | 
			
		||||
    val serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
 | 
			
		||||
) {
 | 
			
		||||
    suspend fun <ResultType> uniget(
 | 
			
		||||
        url: String,
 | 
			
		||||
        resultDeserializer: DeserializationStrategy<ResultType>
 | 
			
		||||
    ): ResultType = client.uniget(url, resultDeserializer, serialFormat)
 | 
			
		||||
 | 
			
		||||
    fun <T> encodeUrlQueryValue(
 | 
			
		||||
        serializationStrategy: SerializationStrategy<T>,
 | 
			
		||||
        value: T
 | 
			
		||||
    ) = serializationStrategy.encodeUrlQueryValue(
 | 
			
		||||
        value,
 | 
			
		||||
        serialFormat
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    suspend fun <BodyType, ResultType> unipost(
 | 
			
		||||
        url: String,
 | 
			
		||||
        bodyInfo: Pair<SerializationStrategy<BodyType>, BodyType>,
 | 
			
		||||
        resultDeserializer: DeserializationStrategy<ResultType>
 | 
			
		||||
    ) = client.unipost(url, bodyInfo, resultDeserializer, serialFormat)
 | 
			
		||||
 | 
			
		||||
    suspend fun <ResultType> unimultipart(
 | 
			
		||||
        url: String,
 | 
			
		||||
        filename: String,
 | 
			
		||||
        inputProvider: InputProvider,
 | 
			
		||||
        resultDeserializer: DeserializationStrategy<ResultType>,
 | 
			
		||||
        mimetype: String = "*/*",
 | 
			
		||||
        additionalParametersBuilder: FormBuilder.() -> Unit = {},
 | 
			
		||||
        dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
 | 
			
		||||
        requestBuilder: HttpRequestBuilder.() -> Unit = {},
 | 
			
		||||
    ): ResultType = client.unimultipart(url, filename, inputProvider, resultDeserializer, mimetype, additionalParametersBuilder, dataHeadersBuilder, requestBuilder, serialFormat)
 | 
			
		||||
 | 
			
		||||
    suspend fun <BodyType, ResultType> unimultipart(
 | 
			
		||||
        url: String,
 | 
			
		||||
        filename: String,
 | 
			
		||||
        inputProvider: InputProvider,
 | 
			
		||||
        otherData: Pair<SerializationStrategy<BodyType>, BodyType>,
 | 
			
		||||
        resultDeserializer: DeserializationStrategy<ResultType>,
 | 
			
		||||
        mimetype: String = "*/*",
 | 
			
		||||
        additionalParametersBuilder: FormBuilder.() -> Unit = {},
 | 
			
		||||
        dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
 | 
			
		||||
        requestBuilder: HttpRequestBuilder.() -> Unit = {},
 | 
			
		||||
    ): ResultType = client.unimultipart(url, filename, otherData, inputProvider, resultDeserializer, mimetype, additionalParametersBuilder, dataHeadersBuilder, requestBuilder, serialFormat)
 | 
			
		||||
 | 
			
		||||
    suspend fun <ResultType> unimultipart(
 | 
			
		||||
        url: String,
 | 
			
		||||
        mppFile: MPPFile,
 | 
			
		||||
        resultDeserializer: DeserializationStrategy<ResultType>,
 | 
			
		||||
        mimetype: String = "*/*",
 | 
			
		||||
        additionalParametersBuilder: FormBuilder.() -> Unit = {},
 | 
			
		||||
        dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
 | 
			
		||||
        requestBuilder: HttpRequestBuilder.() -> Unit = {}
 | 
			
		||||
    ): ResultType = client.unimultipart(
 | 
			
		||||
        url, mppFile, resultDeserializer, mimetype, additionalParametersBuilder, dataHeadersBuilder, requestBuilder, serialFormat
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    suspend fun <BodyType, ResultType> unimultipart(
 | 
			
		||||
        url: String,
 | 
			
		||||
        mppFile: MPPFile,
 | 
			
		||||
        otherData: Pair<SerializationStrategy<BodyType>, BodyType>,
 | 
			
		||||
        resultDeserializer: DeserializationStrategy<ResultType>,
 | 
			
		||||
        mimetype: String = "*/*",
 | 
			
		||||
        additionalParametersBuilder: FormBuilder.() -> Unit = {},
 | 
			
		||||
        dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
 | 
			
		||||
        requestBuilder: HttpRequestBuilder.() -> Unit = {}
 | 
			
		||||
    ): ResultType = client.unimultipart(
 | 
			
		||||
        url, mppFile, otherData, resultDeserializer, mimetype, additionalParametersBuilder, dataHeadersBuilder, requestBuilder, serialFormat
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    fun <T> createStandardWebsocketFlow(
 | 
			
		||||
        url: String,
 | 
			
		||||
        checkReconnection: suspend (Throwable?) -> Boolean,
 | 
			
		||||
        deserializer: DeserializationStrategy<T>,
 | 
			
		||||
        requestBuilder: HttpRequestBuilder.() -> Unit = {},
 | 
			
		||||
    ) = client.createStandardWebsocketFlow(url, deserializer, checkReconnection, serialFormat, requestBuilder)
 | 
			
		||||
 | 
			
		||||
    fun <T> createStandardWebsocketFlow(
 | 
			
		||||
        url: String,
 | 
			
		||||
        deserializer: DeserializationStrategy<T>,
 | 
			
		||||
        requestBuilder: HttpRequestBuilder.() -> Unit = {},
 | 
			
		||||
    ) = createStandardWebsocketFlow(url, { true }, deserializer, requestBuilder)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Deprecated("This property will be removed soon. It is now recommended to use built-in ktor features instead")
 | 
			
		||||
val defaultRequester = UnifiedRequester()
 | 
			
		||||
 | 
			
		||||
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
 | 
			
		||||
suspend fun <ResultType> HttpClient.uniget(
 | 
			
		||||
    url: String,
 | 
			
		||||
    resultDeserializer: DeserializationStrategy<ResultType>,
 | 
			
		||||
    serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
 | 
			
		||||
) = get(url).let {
 | 
			
		||||
    serialFormat.decodeDefault(resultDeserializer, it.body<StandardKtorSerialInputData>())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
 | 
			
		||||
fun <T> SerializationStrategy<T>.encodeUrlQueryValue(
 | 
			
		||||
    value: T,
 | 
			
		||||
    serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
 | 
			
		||||
) = serialFormat.encodeHex(
 | 
			
		||||
    this,
 | 
			
		||||
    value
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
 | 
			
		||||
suspend fun <BodyType, ResultType> HttpClient.unipost(
 | 
			
		||||
    url: String,
 | 
			
		||||
    bodyInfo: Pair<SerializationStrategy<BodyType>, BodyType>,
 | 
			
		||||
    resultDeserializer: DeserializationStrategy<ResultType>,
 | 
			
		||||
    serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
 | 
			
		||||
) = post(url) {
 | 
			
		||||
    setBody(
 | 
			
		||||
        serialFormat.encodeDefault(bodyInfo.first, bodyInfo.second)
 | 
			
		||||
    )
 | 
			
		||||
}.let {
 | 
			
		||||
    serialFormat.decodeDefault(resultDeserializer, it.body<StandardKtorSerialInputData>())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
 | 
			
		||||
suspend fun <ResultType> HttpClient.unimultipart(
 | 
			
		||||
    url: String,
 | 
			
		||||
    filename: String,
 | 
			
		||||
    inputProvider: InputProvider,
 | 
			
		||||
    resultDeserializer: DeserializationStrategy<ResultType>,
 | 
			
		||||
    mimetype: String = "*/*",
 | 
			
		||||
    additionalParametersBuilder: FormBuilder.() -> Unit = {},
 | 
			
		||||
    dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
 | 
			
		||||
    requestBuilder: HttpRequestBuilder.() -> Unit = {},
 | 
			
		||||
    serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
 | 
			
		||||
): ResultType = submitFormWithBinaryData(
 | 
			
		||||
    url,
 | 
			
		||||
    formData = formData {
 | 
			
		||||
        append(
 | 
			
		||||
            "bytes",
 | 
			
		||||
            inputProvider,
 | 
			
		||||
            Headers.build {
 | 
			
		||||
                append(HttpHeaders.ContentType, mimetype)
 | 
			
		||||
                append(HttpHeaders.ContentDisposition, "filename=\"$filename\"")
 | 
			
		||||
                dataHeadersBuilder()
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
        additionalParametersBuilder()
 | 
			
		||||
    }
 | 
			
		||||
) {
 | 
			
		||||
    requestBuilder()
 | 
			
		||||
}.let { serialFormat.decodeDefault(resultDeserializer, it.body<StandardKtorSerialInputData>()) }
 | 
			
		||||
 | 
			
		||||
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
 | 
			
		||||
suspend fun <BodyType, ResultType> HttpClient.unimultipart(
 | 
			
		||||
    url: String,
 | 
			
		||||
    filename: String,
 | 
			
		||||
    otherData: Pair<SerializationStrategy<BodyType>, BodyType>,
 | 
			
		||||
    inputProvider: InputProvider,
 | 
			
		||||
    resultDeserializer: DeserializationStrategy<ResultType>,
 | 
			
		||||
    mimetype: String = "*/*",
 | 
			
		||||
    additionalParametersBuilder: FormBuilder.() -> Unit = {},
 | 
			
		||||
    dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
 | 
			
		||||
    requestBuilder: HttpRequestBuilder.() -> Unit = {},
 | 
			
		||||
    serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
 | 
			
		||||
): ResultType = unimultipart(
 | 
			
		||||
    url,
 | 
			
		||||
    filename,
 | 
			
		||||
    inputProvider,
 | 
			
		||||
    resultDeserializer,
 | 
			
		||||
    mimetype,
 | 
			
		||||
    additionalParametersBuilder = {
 | 
			
		||||
        val serialized = serialFormat.encodeDefault(otherData.first, otherData.second)
 | 
			
		||||
        append(
 | 
			
		||||
            "data",
 | 
			
		||||
            InputProvider(serialized.size.toLong()) {
 | 
			
		||||
                ByteReadPacket(serialized)
 | 
			
		||||
            },
 | 
			
		||||
            Headers.build {
 | 
			
		||||
                append(HttpHeaders.ContentType, ContentType.Application.Cbor.contentType)
 | 
			
		||||
                append(HttpHeaders.ContentDisposition, "filename=data.bytes")
 | 
			
		||||
                dataHeadersBuilder()
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
        additionalParametersBuilder()
 | 
			
		||||
    },
 | 
			
		||||
    dataHeadersBuilder,
 | 
			
		||||
    requestBuilder,
 | 
			
		||||
    serialFormat
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
 | 
			
		||||
suspend fun <ResultType> HttpClient.unimultipart(
 | 
			
		||||
    url: String,
 | 
			
		||||
    mppFile: MPPFile,
 | 
			
		||||
    resultDeserializer: DeserializationStrategy<ResultType>,
 | 
			
		||||
    mimetype: String = "*/*",
 | 
			
		||||
    additionalParametersBuilder: FormBuilder.() -> Unit = {},
 | 
			
		||||
    dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
 | 
			
		||||
    requestBuilder: HttpRequestBuilder.() -> Unit = {},
 | 
			
		||||
    serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
 | 
			
		||||
): ResultType = unimultipart(
 | 
			
		||||
    url,
 | 
			
		||||
    mppFile.filename.string,
 | 
			
		||||
    mppFile.inputProvider(),
 | 
			
		||||
    resultDeserializer,
 | 
			
		||||
    mimetype,
 | 
			
		||||
    additionalParametersBuilder,
 | 
			
		||||
    dataHeadersBuilder,
 | 
			
		||||
    requestBuilder,
 | 
			
		||||
    serialFormat
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
 | 
			
		||||
suspend fun <BodyType, ResultType> HttpClient.unimultipart(
 | 
			
		||||
    url: String,
 | 
			
		||||
    mppFile: MPPFile,
 | 
			
		||||
    otherData: Pair<SerializationStrategy<BodyType>, BodyType>,
 | 
			
		||||
    resultDeserializer: DeserializationStrategy<ResultType>,
 | 
			
		||||
    mimetype: String = "*/*",
 | 
			
		||||
    additionalParametersBuilder: FormBuilder.() -> Unit = {},
 | 
			
		||||
    dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
 | 
			
		||||
    requestBuilder: HttpRequestBuilder.() -> Unit = {},
 | 
			
		||||
    serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
 | 
			
		||||
): ResultType = unimultipart(
 | 
			
		||||
    url,
 | 
			
		||||
    mppFile,
 | 
			
		||||
    resultDeserializer,
 | 
			
		||||
    mimetype,
 | 
			
		||||
    additionalParametersBuilder = {
 | 
			
		||||
        val serialized = serialFormat.encodeDefault(otherData.first, otherData.second)
 | 
			
		||||
        append(
 | 
			
		||||
            "data",
 | 
			
		||||
            InputProvider(serialized.size.toLong()) {
 | 
			
		||||
                ByteReadPacket(serialized)
 | 
			
		||||
            },
 | 
			
		||||
            Headers.build {
 | 
			
		||||
                append(HttpHeaders.ContentType, ContentType.Application.Cbor.contentType)
 | 
			
		||||
                append(HttpHeaders.ContentDisposition, "filename=data.bytes")
 | 
			
		||||
                dataHeadersBuilder()
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
        additionalParametersBuilder()
 | 
			
		||||
    },
 | 
			
		||||
    dataHeadersBuilder,
 | 
			
		||||
    requestBuilder,
 | 
			
		||||
    serialFormat
 | 
			
		||||
)
 | 
			
		||||
@@ -7,13 +7,5 @@ import io.ktor.client.HttpClient
 | 
			
		||||
expect suspend fun HttpClient.tempUpload(
 | 
			
		||||
    fullTempUploadDraftPath: String,
 | 
			
		||||
    file: MPPFile,
 | 
			
		||||
    onUpload: (uploaded: Long, count: Long) -> Unit = { _, _ -> }
 | 
			
		||||
    onUpload: OnUploadCallback = { _, _ -> }
 | 
			
		||||
): TemporalFileId
 | 
			
		||||
 | 
			
		||||
suspend fun UnifiedRequester.tempUpload(
 | 
			
		||||
    fullTempUploadDraftPath: String,
 | 
			
		||||
    file: MPPFile,
 | 
			
		||||
    onUpload: (uploaded: Long, count: Long) -> Unit = { _, _ -> }
 | 
			
		||||
): TemporalFileId = client.tempUpload(
 | 
			
		||||
    fullTempUploadDraftPath, file, onUpload
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,105 @@
 | 
			
		||||
package dev.inmo.micro_utils.ktor.client
 | 
			
		||||
 | 
			
		||||
import dev.inmo.micro_utils.common.FileName
 | 
			
		||||
import dev.inmo.micro_utils.common.MPPFile
 | 
			
		||||
import dev.inmo.micro_utils.ktor.common.LambdaInputProvider
 | 
			
		||||
import io.ktor.client.HttpClient
 | 
			
		||||
import io.ktor.http.Headers
 | 
			
		||||
import io.ktor.utils.io.core.Input
 | 
			
		||||
import kotlinx.serialization.DeserializationStrategy
 | 
			
		||||
import kotlinx.serialization.StringFormat
 | 
			
		||||
import kotlinx.serialization.json.Json
 | 
			
		||||
 | 
			
		||||
data class UniUploadFileInfo(
 | 
			
		||||
    val fileName: FileName,
 | 
			
		||||
    val mimeType: String,
 | 
			
		||||
    val inputAllocator: LambdaInputProvider
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Will execute submitting of multipart data request
 | 
			
		||||
 *
 | 
			
		||||
 * @param data [Map] where keys will be used as names for multipart parts and values as values. If you will pass
 | 
			
		||||
 * [dev.inmo.micro_utils.common.MPPFile] (File from JS or JVM platform). Also you may pass [UniUploadFileInfo] as value
 | 
			
		||||
 * in case you wish to pass other source of multipart binary data than regular file
 | 
			
		||||
 *
 | 
			
		||||
 * @see dev.inmo.micro_utils.ktor.server.handleUniUpload
 | 
			
		||||
 */
 | 
			
		||||
expect suspend fun <T> HttpClient.uniUpload(
 | 
			
		||||
    url: String,
 | 
			
		||||
    data: Map<String, Any>,
 | 
			
		||||
    resultDeserializer: DeserializationStrategy<T>,
 | 
			
		||||
    headers: Headers = Headers.Empty,
 | 
			
		||||
    stringFormat: StringFormat = Json,
 | 
			
		||||
    onUpload: OnUploadCallback = { _, _ -> }
 | 
			
		||||
): T?
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Additional variant of [uniUpload] which will unify sending of some [MPPFile] with the server
 | 
			
		||||
 *
 | 
			
		||||
 * @see dev.inmo.micro_utils.ktor.server.uniloadMultipartFile
 | 
			
		||||
 */
 | 
			
		||||
suspend fun <T> HttpClient.uniUpload(
 | 
			
		||||
    url: String,
 | 
			
		||||
    file: MPPFile,
 | 
			
		||||
    resultDeserializer: DeserializationStrategy<T>,
 | 
			
		||||
    additionalData: Map<String, Any> = emptyMap(),
 | 
			
		||||
    headers: Headers = Headers.Empty,
 | 
			
		||||
    stringFormat: StringFormat = Json,
 | 
			
		||||
    onUpload: OnUploadCallback = { _, _ -> }
 | 
			
		||||
): T? = uniUpload(
 | 
			
		||||
    url,
 | 
			
		||||
    additionalData + ("bytes" to file),
 | 
			
		||||
    resultDeserializer,
 | 
			
		||||
    headers,
 | 
			
		||||
    stringFormat,
 | 
			
		||||
    onUpload
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Additional variant of [uniUpload] which will unify sending of some [UniUploadFileInfo] with the server
 | 
			
		||||
 *
 | 
			
		||||
 * @see dev.inmo.micro_utils.ktor.server.uniloadMultipartFile
 | 
			
		||||
 */
 | 
			
		||||
suspend fun <T> HttpClient.uniUpload(
 | 
			
		||||
    url: String,
 | 
			
		||||
    info: UniUploadFileInfo,
 | 
			
		||||
    resultDeserializer: DeserializationStrategy<T>,
 | 
			
		||||
    additionalData: Map<String, Any> = emptyMap(),
 | 
			
		||||
    headers: Headers = Headers.Empty,
 | 
			
		||||
    stringFormat: StringFormat = Json,
 | 
			
		||||
    onUpload: OnUploadCallback = { _, _ -> }
 | 
			
		||||
): T? = uniUpload(
 | 
			
		||||
    url,
 | 
			
		||||
    additionalData + ("bytes" to info),
 | 
			
		||||
    resultDeserializer,
 | 
			
		||||
    headers,
 | 
			
		||||
    stringFormat,
 | 
			
		||||
    onUpload
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Additional variant of [uniUpload] which will unify sending of some [UniUploadFileInfo] (built from [fileName],
 | 
			
		||||
 * [mimeType] and [inputAllocator]) with the server
 | 
			
		||||
 *
 | 
			
		||||
 * @see dev.inmo.micro_utils.ktor.server.uniloadMultipartFile
 | 
			
		||||
 */
 | 
			
		||||
suspend fun <T> HttpClient.uniUpload(
 | 
			
		||||
    url: String,
 | 
			
		||||
    fileName: FileName,
 | 
			
		||||
    mimeType: String,
 | 
			
		||||
    inputAllocator: LambdaInputProvider,
 | 
			
		||||
    resultDeserializer: DeserializationStrategy<T>,
 | 
			
		||||
    additionalData: Map<String, Any> = emptyMap(),
 | 
			
		||||
    headers: Headers = Headers.Empty,
 | 
			
		||||
    stringFormat: StringFormat = Json,
 | 
			
		||||
    onUpload: OnUploadCallback = { _, _ -> }
 | 
			
		||||
): T? = uniUpload(
 | 
			
		||||
    url,
 | 
			
		||||
    UniUploadFileInfo(fileName, mimeType, inputAllocator),
 | 
			
		||||
    resultDeserializer,
 | 
			
		||||
    additionalData,
 | 
			
		||||
    headers,
 | 
			
		||||
    stringFormat,
 | 
			
		||||
    onUpload
 | 
			
		||||
)
 | 
			
		||||
@@ -1,11 +0,0 @@
 | 
			
		||||
package dev.inmo.micro_utils.ktor.client
 | 
			
		||||
 | 
			
		||||
import dev.inmo.micro_utils.common.*
 | 
			
		||||
import io.ktor.client.request.forms.InputProvider
 | 
			
		||||
import io.ktor.utils.io.core.ByteReadPacket
 | 
			
		||||
 | 
			
		||||
actual suspend fun MPPFile.inputProvider(): InputProvider = bytes().let {
 | 
			
		||||
    InputProvider(it.size.toLong()) {
 | 
			
		||||
        ByteReadPacket(it)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,6 +1,8 @@
 | 
			
		||||
package dev.inmo.micro_utils.ktor.client
 | 
			
		||||
 | 
			
		||||
import dev.inmo.micro_utils.common.MPPFile
 | 
			
		||||
import dev.inmo.micro_utils.coroutines.LinkedSupervisorJob
 | 
			
		||||
import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions
 | 
			
		||||
import dev.inmo.micro_utils.ktor.common.TemporalFileId
 | 
			
		||||
import io.ktor.client.HttpClient
 | 
			
		||||
import kotlinx.coroutines.*
 | 
			
		||||
@@ -12,10 +14,11 @@ import org.w3c.xhr.XMLHttpRequest.Companion.DONE
 | 
			
		||||
suspend fun tempUpload(
 | 
			
		||||
    fullTempUploadDraftPath: String,
 | 
			
		||||
    file: MPPFile,
 | 
			
		||||
    onUpload: (Long, Long) -> Unit
 | 
			
		||||
    onUpload: OnUploadCallback
 | 
			
		||||
): TemporalFileId {
 | 
			
		||||
    val formData = FormData()
 | 
			
		||||
    val answer = CompletableDeferred<TemporalFileId>(currentCoroutineContext().job)
 | 
			
		||||
    val subscope = CoroutineScope(currentCoroutineContext().LinkedSupervisorJob())
 | 
			
		||||
 | 
			
		||||
    formData.append(
 | 
			
		||||
        "data",
 | 
			
		||||
@@ -25,7 +28,7 @@ suspend fun tempUpload(
 | 
			
		||||
    val request = XMLHttpRequest()
 | 
			
		||||
    request.responseType = XMLHttpRequestResponseType.TEXT
 | 
			
		||||
    request.upload.onprogress = {
 | 
			
		||||
        onUpload(it.loaded.toLong(), it.total.toLong())
 | 
			
		||||
        subscope.launchSafelyWithoutExceptions { onUpload(it.loaded.toLong(), it.total.toLong()) }
 | 
			
		||||
    }
 | 
			
		||||
    request.onload = {
 | 
			
		||||
        if (request.status == 200.toShort()) {
 | 
			
		||||
@@ -48,12 +51,14 @@ suspend fun tempUpload(
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return answer.await()
 | 
			
		||||
    return answer.await().also {
 | 
			
		||||
        subscope.cancel()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
actual suspend fun HttpClient.tempUpload(
 | 
			
		||||
    fullTempUploadDraftPath: String,
 | 
			
		||||
    file: MPPFile,
 | 
			
		||||
    onUpload: (uploaded: Long, count: Long) -> Unit
 | 
			
		||||
    onUpload: OnUploadCallback
 | 
			
		||||
): TemporalFileId = dev.inmo.micro_utils.ktor.client.tempUpload(fullTempUploadDraftPath, file, onUpload)
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,97 @@
 | 
			
		||||
package dev.inmo.micro_utils.ktor.client
 | 
			
		||||
 | 
			
		||||
import dev.inmo.micro_utils.common.MPPFile
 | 
			
		||||
import dev.inmo.micro_utils.common.Progress
 | 
			
		||||
import dev.inmo.micro_utils.coroutines.LinkedSupervisorJob
 | 
			
		||||
import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions
 | 
			
		||||
import io.ktor.client.HttpClient
 | 
			
		||||
import io.ktor.http.Headers
 | 
			
		||||
import io.ktor.utils.io.core.readBytes
 | 
			
		||||
import kotlinx.coroutines.CompletableDeferred
 | 
			
		||||
import kotlinx.coroutines.CoroutineScope
 | 
			
		||||
import kotlinx.coroutines.cancel
 | 
			
		||||
import kotlinx.coroutines.currentCoroutineContext
 | 
			
		||||
import kotlinx.coroutines.job
 | 
			
		||||
import kotlinx.serialization.DeserializationStrategy
 | 
			
		||||
import kotlinx.serialization.StringFormat
 | 
			
		||||
import kotlinx.serialization.encodeToString
 | 
			
		||||
import org.khronos.webgl.Int8Array
 | 
			
		||||
import org.w3c.files.Blob
 | 
			
		||||
import org.w3c.xhr.FormData
 | 
			
		||||
import org.w3c.xhr.TEXT
 | 
			
		||||
import org.w3c.xhr.XMLHttpRequest
 | 
			
		||||
import org.w3c.xhr.XMLHttpRequestResponseType
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Will execute submitting of multipart data request
 | 
			
		||||
 *
 | 
			
		||||
 * @param data [Map] where keys will be used as names for multipart parts and values as values. If you will pass
 | 
			
		||||
 * [dev.inmo.micro_utils.common.MPPFile] (File from JS or JVM platform). Also you may pass [UniUploadFileInfo] as value
 | 
			
		||||
 * in case you wish to pass other source of multipart binary data than regular file
 | 
			
		||||
 * @suppress
 | 
			
		||||
 */
 | 
			
		||||
actual suspend fun <T> HttpClient.uniUpload(
 | 
			
		||||
    url: String,
 | 
			
		||||
    data: Map<String, Any>,
 | 
			
		||||
    resultDeserializer: DeserializationStrategy<T>,
 | 
			
		||||
    headers: Headers,
 | 
			
		||||
    stringFormat: StringFormat,
 | 
			
		||||
    onUpload: OnUploadCallback
 | 
			
		||||
): T? {
 | 
			
		||||
    val formData = FormData()
 | 
			
		||||
    val answer = CompletableDeferred<T?>(currentCoroutineContext().job)
 | 
			
		||||
    val subscope = CoroutineScope(currentCoroutineContext().LinkedSupervisorJob())
 | 
			
		||||
 | 
			
		||||
    data.forEach { (k, v) ->
 | 
			
		||||
        when (v) {
 | 
			
		||||
            is MPPFile -> formData.append(
 | 
			
		||||
                k,
 | 
			
		||||
                v
 | 
			
		||||
            )
 | 
			
		||||
            is UniUploadFileInfo -> formData.append(
 | 
			
		||||
                k,
 | 
			
		||||
                Blob(arrayOf(Int8Array(v.inputAllocator().readBytes().toTypedArray()))),
 | 
			
		||||
                v.fileName.name
 | 
			
		||||
            )
 | 
			
		||||
            else -> formData.append(
 | 
			
		||||
                k,
 | 
			
		||||
                stringFormat.encodeToString(v)
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    val request = XMLHttpRequest()
 | 
			
		||||
    headers.forEach { s, strings ->
 | 
			
		||||
        request.setRequestHeader(s, strings.joinToString())
 | 
			
		||||
    }
 | 
			
		||||
    request.responseType = XMLHttpRequestResponseType.TEXT
 | 
			
		||||
    request.upload.onprogress = {
 | 
			
		||||
        subscope.launchSafelyWithoutExceptions { onUpload(it.loaded.toLong(), it.total.toLong()) }
 | 
			
		||||
    }
 | 
			
		||||
    request.onload = {
 | 
			
		||||
        if (request.status == 200.toShort()) {
 | 
			
		||||
            answer.complete(
 | 
			
		||||
                stringFormat.decodeFromString(resultDeserializer, request.responseText)
 | 
			
		||||
            )
 | 
			
		||||
        } else {
 | 
			
		||||
            answer.completeExceptionally(Exception("Something went wrong: $it"))
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    request.onerror = {
 | 
			
		||||
        answer.completeExceptionally(Exception("Something went wrong: $it"))
 | 
			
		||||
    }
 | 
			
		||||
    request.open("POST", url, true)
 | 
			
		||||
    request.send(formData)
 | 
			
		||||
 | 
			
		||||
    answer.invokeOnCompletion {
 | 
			
		||||
        runCatching {
 | 
			
		||||
            if (request.readyState != XMLHttpRequest.DONE) {
 | 
			
		||||
                request.abort()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return answer.await().also {
 | 
			
		||||
        subscope.cancel()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -4,6 +4,6 @@ import dev.inmo.micro_utils.common.MPPFile
 | 
			
		||||
import io.ktor.client.request.forms.InputProvider
 | 
			
		||||
import io.ktor.utils.io.streams.asInput
 | 
			
		||||
 | 
			
		||||
actual suspend fun MPPFile.inputProvider(): InputProvider = InputProvider(length()) {
 | 
			
		||||
fun MPPFile.inputProviderSync(): InputProvider = InputProvider(length()) {
 | 
			
		||||
    inputStream().asInput()
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -18,7 +18,7 @@ internal val MPPFile.mimeType: String
 | 
			
		||||
actual suspend fun HttpClient.tempUpload(
 | 
			
		||||
    fullTempUploadDraftPath: String,
 | 
			
		||||
    file: MPPFile,
 | 
			
		||||
    onUpload: (Long, Long) -> Unit
 | 
			
		||||
    onUpload: OnUploadCallback
 | 
			
		||||
): TemporalFileId {
 | 
			
		||||
    val inputProvider = file.inputProvider()
 | 
			
		||||
    val fileId = submitFormWithBinaryData(
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,107 @@
 | 
			
		||||
package dev.inmo.micro_utils.ktor.client
 | 
			
		||||
 | 
			
		||||
import dev.inmo.micro_utils.common.Progress
 | 
			
		||||
import io.ktor.client.HttpClient
 | 
			
		||||
import io.ktor.client.engine.mergeHeaders
 | 
			
		||||
import io.ktor.client.plugins.onUpload
 | 
			
		||||
import io.ktor.client.request.HttpRequestBuilder
 | 
			
		||||
import io.ktor.client.request.forms.InputProvider
 | 
			
		||||
import io.ktor.client.request.forms.formData
 | 
			
		||||
import io.ktor.client.request.forms.submitForm
 | 
			
		||||
import io.ktor.client.request.forms.submitFormWithBinaryData
 | 
			
		||||
import io.ktor.client.request.headers
 | 
			
		||||
import io.ktor.client.statement.bodyAsText
 | 
			
		||||
import io.ktor.http.Headers
 | 
			
		||||
import io.ktor.http.HttpHeaders
 | 
			
		||||
import io.ktor.http.HttpStatusCode
 | 
			
		||||
import io.ktor.http.Parameters
 | 
			
		||||
import io.ktor.http.content.PartData
 | 
			
		||||
import kotlinx.serialization.DeserializationStrategy
 | 
			
		||||
import kotlinx.serialization.InternalSerializationApi
 | 
			
		||||
import kotlinx.serialization.SerializationStrategy
 | 
			
		||||
import kotlinx.serialization.StringFormat
 | 
			
		||||
import kotlinx.serialization.encodeToString
 | 
			
		||||
import kotlinx.serialization.serializer
 | 
			
		||||
import java.io.File
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Will execute submitting of multipart data request
 | 
			
		||||
 *
 | 
			
		||||
 * @param data [Map] where keys will be used as names for multipart parts and values as values. If you will pass
 | 
			
		||||
 * [dev.inmo.micro_utils.common.MPPFile] (File from JS or JVM platform). Also you may pass [UniUploadFileInfo] as value
 | 
			
		||||
 * in case you wish to pass other source of multipart binary data than regular file
 | 
			
		||||
 * @suppress
 | 
			
		||||
 */
 | 
			
		||||
@OptIn(InternalSerializationApi::class)
 | 
			
		||||
actual suspend fun <T> HttpClient.uniUpload(
 | 
			
		||||
    url: String,
 | 
			
		||||
    data: Map<String, Any>,
 | 
			
		||||
    resultDeserializer: DeserializationStrategy<T>,
 | 
			
		||||
    headers: Headers,
 | 
			
		||||
    stringFormat: StringFormat,
 | 
			
		||||
    onUpload: OnUploadCallback
 | 
			
		||||
): T? {
 | 
			
		||||
    val withBinary = data.values.any { it is File || it is UniUploadFileInfo }
 | 
			
		||||
 | 
			
		||||
    val formData = formData {
 | 
			
		||||
        for (k in data.keys) {
 | 
			
		||||
            val v = data[k] ?: continue
 | 
			
		||||
            when (v) {
 | 
			
		||||
                is File -> append(
 | 
			
		||||
                    k,
 | 
			
		||||
                    v.inputProviderSync(),
 | 
			
		||||
                    Headers.build {
 | 
			
		||||
                        append(HttpHeaders.ContentType, v.mimeType)
 | 
			
		||||
                        append(HttpHeaders.ContentDisposition, "filename=\"${v.name}\"")
 | 
			
		||||
                    }
 | 
			
		||||
                )
 | 
			
		||||
                is UniUploadFileInfo -> append(
 | 
			
		||||
                    k,
 | 
			
		||||
                    InputProvider(block = v.inputAllocator),
 | 
			
		||||
                    Headers.build {
 | 
			
		||||
                        append(HttpHeaders.ContentType, v.mimeType)
 | 
			
		||||
                        append(HttpHeaders.ContentDisposition, "filename=\"${v.fileName.name}\"")
 | 
			
		||||
                    }
 | 
			
		||||
                )
 | 
			
		||||
                else -> append(
 | 
			
		||||
                    k,
 | 
			
		||||
                    stringFormat.encodeToString(v::class.serializer() as SerializationStrategy<in Any>, v)
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    val requestBuilder: HttpRequestBuilder.() -> Unit = {
 | 
			
		||||
        headers {
 | 
			
		||||
            appendAll(headers)
 | 
			
		||||
        }
 | 
			
		||||
        onUpload { bytesSentTotal, contentLength ->
 | 
			
		||||
            onUpload(bytesSentTotal, contentLength)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    val response = if (withBinary) {
 | 
			
		||||
        submitFormWithBinaryData(
 | 
			
		||||
            url,
 | 
			
		||||
            formData,
 | 
			
		||||
            block = requestBuilder
 | 
			
		||||
        )
 | 
			
		||||
    } else {
 | 
			
		||||
        submitForm(
 | 
			
		||||
            url,
 | 
			
		||||
            Parameters.build {
 | 
			
		||||
                for (it in formData) {
 | 
			
		||||
                    val formItem = (it as PartData.FormItem)
 | 
			
		||||
                    append(it.name!!, it.value)
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            block = requestBuilder
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return if (response.status == HttpStatusCode.OK) {
 | 
			
		||||
        stringFormat.decodeFromString(resultDeserializer, response.bodyAsText())
 | 
			
		||||
    } else {
 | 
			
		||||
        null
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										40
									
								
								ktor/client/src/linuxX64Main/kotlin/ActualTemporalUpload.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								ktor/client/src/linuxX64Main/kotlin/ActualTemporalUpload.kt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,40 @@
 | 
			
		||||
package dev.inmo.micro_utils.ktor.client
 | 
			
		||||
 | 
			
		||||
import dev.inmo.micro_utils.common.MPPFile
 | 
			
		||||
import dev.inmo.micro_utils.common.filename
 | 
			
		||||
import dev.inmo.micro_utils.ktor.common.TemporalFileId
 | 
			
		||||
import dev.inmo.micro_utils.mime_types.getMimeTypeOrAny
 | 
			
		||||
import io.ktor.client.HttpClient
 | 
			
		||||
import io.ktor.client.plugins.onUpload
 | 
			
		||||
import io.ktor.client.request.forms.formData
 | 
			
		||||
import io.ktor.client.request.forms.submitFormWithBinaryData
 | 
			
		||||
import io.ktor.client.statement.bodyAsText
 | 
			
		||||
import io.ktor.http.Headers
 | 
			
		||||
import io.ktor.http.HttpHeaders
 | 
			
		||||
 | 
			
		||||
internal val MPPFile.mimeType: String
 | 
			
		||||
    get() = getMimeTypeOrAny(filename.extension).raw
 | 
			
		||||
 | 
			
		||||
actual suspend fun HttpClient.tempUpload(
 | 
			
		||||
    fullTempUploadDraftPath: String,
 | 
			
		||||
    file: MPPFile,
 | 
			
		||||
    onUpload: OnUploadCallback
 | 
			
		||||
): TemporalFileId {
 | 
			
		||||
    val inputProvider = file.inputProvider()
 | 
			
		||||
    val fileId = submitFormWithBinaryData(
 | 
			
		||||
        fullTempUploadDraftPath,
 | 
			
		||||
        formData = formData {
 | 
			
		||||
            append(
 | 
			
		||||
                "data",
 | 
			
		||||
                inputProvider,
 | 
			
		||||
                Headers.build {
 | 
			
		||||
                    append(HttpHeaders.ContentType, file.mimeType)
 | 
			
		||||
                    append(HttpHeaders.ContentDisposition, "filename=\"${file.filename.string}\"")
 | 
			
		||||
                }
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
    ) {
 | 
			
		||||
        onUpload(onUpload)
 | 
			
		||||
    }.bodyAsText()
 | 
			
		||||
    return TemporalFileId(fileId)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										107
									
								
								ktor/client/src/linuxX64Main/kotlin/ActualUniUpload.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								ktor/client/src/linuxX64Main/kotlin/ActualUniUpload.kt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,107 @@
 | 
			
		||||
package dev.inmo.micro_utils.ktor.client
 | 
			
		||||
 | 
			
		||||
import dev.inmo.micro_utils.common.MPPFile
 | 
			
		||||
import dev.inmo.micro_utils.common.Progress
 | 
			
		||||
import io.ktor.client.HttpClient
 | 
			
		||||
import io.ktor.client.engine.mergeHeaders
 | 
			
		||||
import io.ktor.client.plugins.onUpload
 | 
			
		||||
import io.ktor.client.request.HttpRequestBuilder
 | 
			
		||||
import io.ktor.client.request.forms.InputProvider
 | 
			
		||||
import io.ktor.client.request.forms.formData
 | 
			
		||||
import io.ktor.client.request.forms.submitForm
 | 
			
		||||
import io.ktor.client.request.forms.submitFormWithBinaryData
 | 
			
		||||
import io.ktor.client.request.headers
 | 
			
		||||
import io.ktor.client.statement.bodyAsText
 | 
			
		||||
import io.ktor.http.Headers
 | 
			
		||||
import io.ktor.http.HttpHeaders
 | 
			
		||||
import io.ktor.http.HttpStatusCode
 | 
			
		||||
import io.ktor.http.Parameters
 | 
			
		||||
import io.ktor.http.content.PartData
 | 
			
		||||
import kotlinx.serialization.DeserializationStrategy
 | 
			
		||||
import kotlinx.serialization.InternalSerializationApi
 | 
			
		||||
import kotlinx.serialization.SerializationStrategy
 | 
			
		||||
import kotlinx.serialization.StringFormat
 | 
			
		||||
import kotlinx.serialization.encodeToString
 | 
			
		||||
import kotlinx.serialization.serializer
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Will execute submitting of multipart data request
 | 
			
		||||
 *
 | 
			
		||||
 * @param data [Map] where keys will be used as names for multipart parts and values as values. If you will pass
 | 
			
		||||
 * [dev.inmo.micro_utils.common.MPPFile] (File from JS or JVM platform). Also you may pass [UniUploadFileInfo] as value
 | 
			
		||||
 * in case you wish to pass other source of multipart binary data than regular file
 | 
			
		||||
 * @suppress
 | 
			
		||||
 */
 | 
			
		||||
@OptIn(InternalSerializationApi::class)
 | 
			
		||||
actual suspend fun <T> HttpClient.uniUpload(
 | 
			
		||||
    url: String,
 | 
			
		||||
    data: Map<String, Any>,
 | 
			
		||||
    resultDeserializer: DeserializationStrategy<T>,
 | 
			
		||||
    headers: Headers,
 | 
			
		||||
    stringFormat: StringFormat,
 | 
			
		||||
    onUpload: OnUploadCallback
 | 
			
		||||
): T? {
 | 
			
		||||
    val withBinary = data.values.any { it is MPPFile || it is UniUploadFileInfo }
 | 
			
		||||
 | 
			
		||||
    val formData = formData {
 | 
			
		||||
        for (k in data.keys) {
 | 
			
		||||
            val v = data[k] ?: continue
 | 
			
		||||
            when (v) {
 | 
			
		||||
                is MPPFile -> append(
 | 
			
		||||
                    k,
 | 
			
		||||
                    v.inputProvider(),
 | 
			
		||||
                    Headers.build {
 | 
			
		||||
                        append(HttpHeaders.ContentType, v.mimeType)
 | 
			
		||||
                        append(HttpHeaders.ContentDisposition, "filename=\"${v.name}\"")
 | 
			
		||||
                    }
 | 
			
		||||
                )
 | 
			
		||||
                is UniUploadFileInfo -> append(
 | 
			
		||||
                    k,
 | 
			
		||||
                    InputProvider(block = v.inputAllocator),
 | 
			
		||||
                    Headers.build {
 | 
			
		||||
                        append(HttpHeaders.ContentType, v.mimeType)
 | 
			
		||||
                        append(HttpHeaders.ContentDisposition, "filename=\"${v.fileName.name}\"")
 | 
			
		||||
                    }
 | 
			
		||||
                )
 | 
			
		||||
                else -> append(
 | 
			
		||||
                    k,
 | 
			
		||||
                    stringFormat.encodeToString(v::class.serializer() as SerializationStrategy<in Any>, v)
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    val requestBuilder: HttpRequestBuilder.() -> Unit = {
 | 
			
		||||
        headers {
 | 
			
		||||
            appendAll(headers)
 | 
			
		||||
        }
 | 
			
		||||
        onUpload { bytesSentTotal, contentLength ->
 | 
			
		||||
            onUpload(bytesSentTotal, contentLength)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    val response = if (withBinary) {
 | 
			
		||||
        submitFormWithBinaryData(
 | 
			
		||||
            url,
 | 
			
		||||
            formData,
 | 
			
		||||
            block = requestBuilder
 | 
			
		||||
        )
 | 
			
		||||
    } else {
 | 
			
		||||
        submitForm(
 | 
			
		||||
            url,
 | 
			
		||||
            Parameters.build {
 | 
			
		||||
                for (it in formData) {
 | 
			
		||||
                    val formItem = (it as PartData.FormItem)
 | 
			
		||||
                    append(it.name!!, it.value)
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            block = requestBuilder
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return if (response.status == HttpStatusCode.OK) {
 | 
			
		||||
        stringFormat.decodeFromString(resultDeserializer, response.bodyAsText())
 | 
			
		||||
    } else {
 | 
			
		||||
        null
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										40
									
								
								ktor/client/src/mingwX64Main/kotlin/ActualTemporalUpload.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								ktor/client/src/mingwX64Main/kotlin/ActualTemporalUpload.kt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,40 @@
 | 
			
		||||
package dev.inmo.micro_utils.ktor.client
 | 
			
		||||
 | 
			
		||||
import dev.inmo.micro_utils.common.MPPFile
 | 
			
		||||
import dev.inmo.micro_utils.common.filename
 | 
			
		||||
import dev.inmo.micro_utils.ktor.common.TemporalFileId
 | 
			
		||||
import dev.inmo.micro_utils.mime_types.getMimeTypeOrAny
 | 
			
		||||
import io.ktor.client.HttpClient
 | 
			
		||||
import io.ktor.client.plugins.onUpload
 | 
			
		||||
import io.ktor.client.request.forms.formData
 | 
			
		||||
import io.ktor.client.request.forms.submitFormWithBinaryData
 | 
			
		||||
import io.ktor.client.statement.bodyAsText
 | 
			
		||||
import io.ktor.http.Headers
 | 
			
		||||
import io.ktor.http.HttpHeaders
 | 
			
		||||
 | 
			
		||||
internal val MPPFile.mimeType: String
 | 
			
		||||
    get() = getMimeTypeOrAny(filename.extension).raw
 | 
			
		||||
 | 
			
		||||
actual suspend fun HttpClient.tempUpload(
 | 
			
		||||
    fullTempUploadDraftPath: String,
 | 
			
		||||
    file: MPPFile,
 | 
			
		||||
    onUpload: OnUploadCallback
 | 
			
		||||
): TemporalFileId {
 | 
			
		||||
    val inputProvider = file.inputProvider()
 | 
			
		||||
    val fileId = submitFormWithBinaryData(
 | 
			
		||||
        fullTempUploadDraftPath,
 | 
			
		||||
        formData = formData {
 | 
			
		||||
            append(
 | 
			
		||||
                "data",
 | 
			
		||||
                inputProvider,
 | 
			
		||||
                Headers.build {
 | 
			
		||||
                    append(HttpHeaders.ContentType, file.mimeType)
 | 
			
		||||
                    append(HttpHeaders.ContentDisposition, "filename=\"${file.filename.string}\"")
 | 
			
		||||
                }
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
    ) {
 | 
			
		||||
        onUpload(onUpload)
 | 
			
		||||
    }.bodyAsText()
 | 
			
		||||
    return TemporalFileId(fileId)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										107
									
								
								ktor/client/src/mingwX64Main/kotlin/ActualUniUpload.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								ktor/client/src/mingwX64Main/kotlin/ActualUniUpload.kt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,107 @@
 | 
			
		||||
package dev.inmo.micro_utils.ktor.client
 | 
			
		||||
 | 
			
		||||
import dev.inmo.micro_utils.common.MPPFile
 | 
			
		||||
import dev.inmo.micro_utils.common.Progress
 | 
			
		||||
import io.ktor.client.HttpClient
 | 
			
		||||
import io.ktor.client.engine.mergeHeaders
 | 
			
		||||
import io.ktor.client.plugins.onUpload
 | 
			
		||||
import io.ktor.client.request.HttpRequestBuilder
 | 
			
		||||
import io.ktor.client.request.forms.InputProvider
 | 
			
		||||
import io.ktor.client.request.forms.formData
 | 
			
		||||
import io.ktor.client.request.forms.submitForm
 | 
			
		||||
import io.ktor.client.request.forms.submitFormWithBinaryData
 | 
			
		||||
import io.ktor.client.request.headers
 | 
			
		||||
import io.ktor.client.statement.bodyAsText
 | 
			
		||||
import io.ktor.http.Headers
 | 
			
		||||
import io.ktor.http.HttpHeaders
 | 
			
		||||
import io.ktor.http.HttpStatusCode
 | 
			
		||||
import io.ktor.http.Parameters
 | 
			
		||||
import io.ktor.http.content.PartData
 | 
			
		||||
import kotlinx.serialization.DeserializationStrategy
 | 
			
		||||
import kotlinx.serialization.InternalSerializationApi
 | 
			
		||||
import kotlinx.serialization.SerializationStrategy
 | 
			
		||||
import kotlinx.serialization.StringFormat
 | 
			
		||||
import kotlinx.serialization.encodeToString
 | 
			
		||||
import kotlinx.serialization.serializer
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Will execute submitting of multipart data request
 | 
			
		||||
 *
 | 
			
		||||
 * @param data [Map] where keys will be used as names for multipart parts and values as values. If you will pass
 | 
			
		||||
 * [dev.inmo.micro_utils.common.MPPFile] (File from JS or JVM platform). Also you may pass [UniUploadFileInfo] as value
 | 
			
		||||
 * in case you wish to pass other source of multipart binary data than regular file
 | 
			
		||||
 * @suppress
 | 
			
		||||
 */
 | 
			
		||||
@OptIn(InternalSerializationApi::class)
 | 
			
		||||
actual suspend fun <T> HttpClient.uniUpload(
 | 
			
		||||
    url: String,
 | 
			
		||||
    data: Map<String, Any>,
 | 
			
		||||
    resultDeserializer: DeserializationStrategy<T>,
 | 
			
		||||
    headers: Headers,
 | 
			
		||||
    stringFormat: StringFormat,
 | 
			
		||||
    onUpload: OnUploadCallback
 | 
			
		||||
): T? {
 | 
			
		||||
    val withBinary = data.values.any { it is MPPFile || it is UniUploadFileInfo }
 | 
			
		||||
 | 
			
		||||
    val formData = formData {
 | 
			
		||||
        for (k in data.keys) {
 | 
			
		||||
            val v = data[k] ?: continue
 | 
			
		||||
            when (v) {
 | 
			
		||||
                is MPPFile -> append(
 | 
			
		||||
                    k,
 | 
			
		||||
                    v.inputProvider(),
 | 
			
		||||
                    Headers.build {
 | 
			
		||||
                        append(HttpHeaders.ContentType, v.mimeType)
 | 
			
		||||
                        append(HttpHeaders.ContentDisposition, "filename=\"${v.name}\"")
 | 
			
		||||
                    }
 | 
			
		||||
                )
 | 
			
		||||
                is UniUploadFileInfo -> append(
 | 
			
		||||
                    k,
 | 
			
		||||
                    InputProvider(block = v.inputAllocator),
 | 
			
		||||
                    Headers.build {
 | 
			
		||||
                        append(HttpHeaders.ContentType, v.mimeType)
 | 
			
		||||
                        append(HttpHeaders.ContentDisposition, "filename=\"${v.fileName.name}\"")
 | 
			
		||||
                    }
 | 
			
		||||
                )
 | 
			
		||||
                else -> append(
 | 
			
		||||
                    k,
 | 
			
		||||
                    stringFormat.encodeToString(v::class.serializer() as SerializationStrategy<in Any>, v)
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    val requestBuilder: HttpRequestBuilder.() -> Unit = {
 | 
			
		||||
        headers {
 | 
			
		||||
            appendAll(headers)
 | 
			
		||||
        }
 | 
			
		||||
        onUpload { bytesSentTotal, contentLength ->
 | 
			
		||||
            onUpload(bytesSentTotal, contentLength)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    val response = if (withBinary) {
 | 
			
		||||
        submitFormWithBinaryData(
 | 
			
		||||
            url,
 | 
			
		||||
            formData,
 | 
			
		||||
            block = requestBuilder
 | 
			
		||||
        )
 | 
			
		||||
    } else {
 | 
			
		||||
        submitForm(
 | 
			
		||||
            url,
 | 
			
		||||
            Parameters.build {
 | 
			
		||||
                for (it in formData) {
 | 
			
		||||
                    val formItem = (it as PartData.FormItem)
 | 
			
		||||
                    append(it.name!!, it.value)
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            block = requestBuilder
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return if (response.status == HttpStatusCode.OK) {
 | 
			
		||||
        stringFormat.decodeFromString(resultDeserializer, response.bodyAsText())
 | 
			
		||||
    } else {
 | 
			
		||||
        null
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -17,5 +17,8 @@ kotlin {
 | 
			
		||||
                api libs.ktor.io
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        androidMain {
 | 
			
		||||
            dependsOn jvmMain
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,5 @@
 | 
			
		||||
package dev.inmo.micro_utils.ktor.common
 | 
			
		||||
 | 
			
		||||
import io.ktor.utils.io.core.Input
 | 
			
		||||
 | 
			
		||||
typealias LambdaInputProvider = () -> Input
 | 
			
		||||
@@ -0,0 +1,23 @@
 | 
			
		||||
package dev.inmo.micro_utils.ktor.common
 | 
			
		||||
 | 
			
		||||
import io.ktor.utils.io.core.Input
 | 
			
		||||
import io.ktor.utils.io.core.copyTo
 | 
			
		||||
import io.ktor.utils.io.streams.asOutput
 | 
			
		||||
import java.io.File
 | 
			
		||||
import java.io.InputStream
 | 
			
		||||
import java.util.UUID
 | 
			
		||||
 | 
			
		||||
fun Input.downloadToTempFile(
 | 
			
		||||
    fileName: String = UUID.randomUUID().toString(),
 | 
			
		||||
    fileExtension: String? = ".temp",
 | 
			
		||||
    folder: File? = null
 | 
			
		||||
) = File.createTempFile(
 | 
			
		||||
    fileName,
 | 
			
		||||
    fileExtension,
 | 
			
		||||
    folder
 | 
			
		||||
).apply {
 | 
			
		||||
    outputStream().use {
 | 
			
		||||
        copyTo(it.asOutput())
 | 
			
		||||
    }
 | 
			
		||||
    deleteOnExit()
 | 
			
		||||
}
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user