Compare commits

...

77 Commits

Author SHA1 Message Date
renovate[bot]
7551253e0b Update dependency org.jetbrains.kotlinx:kotlinx-browser-wasm-js to v0.5.0 2025-09-08 13:38:13 +00:00
9822ff321b Merge pull request #607 from InsanusMokrassar/0.26.4
0.26.4
2025-09-02 01:33:50 +06:00
b485d485ef MPPFilePathSeparator 2025-09-01 22:40:26 +06:00
0b3d445109 start 0.26.4 2025-09-01 22:34:32 +06:00
d7e48940bc Merge pull request #606 from InsanusMokrassar/0.26.3
0.26.3
2025-08-20 19:17:26 +06:00
1049eb0fe7 update dependencies 2025-08-20 19:15:24 +06:00
c871ef5635 start 0.26.3 2025-08-20 18:39:59 +06:00
7edfcb20c4 Merge pull request #593 from InsanusMokrassar/0.26.2
0.26.2
2025-07-31 15:22:34 +06:00
7a1438a2c0 update dependencies 2025-07-31 15:11:42 +06:00
2af8cba8cd rename SpecialMutableStateFlow to MutableRedeliverStateFlow 2025-07-29 17:53:47 +06:00
27d74c0a62 start 0.26.2 2025-07-29 17:46:31 +06:00
f86d1bfe06 Merge pull request #591 from InsanusMokrassar/0.26.1
0.26.1
2025-07-21 23:05:46 +06:00
7cc5972ff7 update dependencies 2025-07-21 22:58:00 +06:00
3bbf978b00 fixes in actorAsync 2025-07-21 22:02:30 +06:00
ed36467600 add opportunity to pass logger in subscribe async 2025-07-21 21:53:03 +06:00
dd0de327fc start 0.26.1 2025-07-21 21:37:50 +06:00
dccd3ce8fd Merge pull request #589 from InsanusMokrassar/0.26.0
0.26.0
2025-07-11 16:46:21 +06:00
fa45e7b696 fix of kspCommonMainKotlinMetadata call 2025-07-11 16:39:08 +06:00
57f009e8aa try to fix build 2025-07-11 14:40:53 +06:00
04b633a5ea fill changelog 2025-07-10 23:44:11 +06:00
20d42b05bb fixed issue with ksp (with https://github.com/google/ksp/issues/2491) 2025-07-10 19:22:08 +06:00
91ba50f1ff start updates 2025-07-10 18:21:57 +06:00
f4476c99f9 start 0.26.0 2025-07-10 17:59:06 +06:00
50f3f586ab fixes 2025-06-18 15:00:02 +06:00
36a2d7ec8e small improvement of InfinityPagedComponent and add opportunity to set publishing type 2025-06-18 14:31:01 +06:00
4890b5833e Merge pull request #587 from InsanusMokrassar/0.25.8
0.25.8
2025-06-17 15:47:54 +06:00
e20ab89688 improvements in InfinityPagedComponent 2025-06-17 15:45:05 +06:00
e557ba8184 start 0.25.8 2025-06-17 15:31:03 +06:00
8540e21d5a Merge pull request #584 from InsanusMokrassar/0.25.7
0.25.7
2025-06-10 23:38:07 +06:00
76c04a8506 update dependencies 2025-06-10 23:29:26 +06:00
128632770e start 0.25.7 2025-06-10 23:19:46 +06:00
31e0800e81 Update build.gradle 2025-06-08 20:41:05 +06:00
00ca96eec8 add nmcp 2025-06-08 18:51:53 +06:00
077ef2c639 fix of repositories order in build.gradle 2025-06-02 19:44:16 +06:00
e3ea7be0e7 one more improvement 2025-06-02 11:47:38 +06:00
05fd1c2b14 one more improvement 2025-06-02 11:45:30 +06:00
affcffe270 small improvement in uploadSonatypePublicationmain.kts 2025-06-02 11:44:09 +06:00
62930231e4 first version of uploadSonatypePublicationmain.kts 2025-06-02 11:42:45 +06:00
ad651631ec experiments time 2025-06-02 08:34:09 +06:00
cf1c8f13db add publish_all_script 2025-06-01 23:16:50 +06:00
9acc69b897 Revert "add keepa-live for uploading script parts"
This reverts commit 2ed8443e28.
2025-05-18 19:15:58 +06:00
9bc7cbdb50 add allowing of connection and keep-alive headers 2025-05-18 18:35:31 +06:00
2ed8443e28 add keepa-live for uploading script parts 2025-05-17 15:59:53 +06:00
94f598c2b4 Merge pull request #578 from InsanusMokrassar/0.25.6
0.25.6
2025-05-16 14:45:34 +06:00
d83d30af06 actualize kslog, changelog and gradle.properties 2025-05-16 14:44:39 +06:00
284e763f0d update publish gradle scripts 2025-05-16 14:31:22 +06:00
3bfa172533 update github_release.gradle to allow it to publish with env instead of secret.gradle 2025-05-16 12:39:24 +06:00
b5b1fd6d5f rewrite publishing scripts with using of publication scripts generator 2025-05-16 00:53:12 +06:00
05e0d9b7d2 add clatch for uploading of publication 2025-05-15 22:18:43 +06:00
1ae1f8dee2 Revert "trying to add new plugin for publishing"
This reverts commit 1bf479a0b7.
2025-05-15 21:17:56 +06:00
1bf479a0b7 trying to add new plugin for publishing 2025-05-14 20:47:09 +06:00
9d04d49628 update dependencies and try to use new publishing 2025-05-13 23:41:27 +06:00
3de324519b start 0.25.6 2025-05-13 22:49:03 +06:00
4be90d0ea5 Merge pull request #567 from InsanusMokrassar/0.25.5
0.25.5
2025-04-14 10:24:01 +06:00
041f35ed6c add templates declaration in README.md 2025-04-14 10:09:33 +06:00
9c463d0338 add kdocs to koin common module 2025-04-14 10:03:26 +06:00
5ec0a46089 add KDocs for actor and safeActor 2025-04-14 09:53:24 +06:00
979d0ee4ca new model of templating 2025-04-14 09:43:07 +06:00
448686b399 Merge pull request #572 from 000Sanya/0.25.5
fix test running for wasm/js node
2025-04-14 08:54:53 +06:00
nullsanya
0ff149a3d3 fix test running for wasm/js node 2025-04-14 12:44:00 +10:00
50a90d6e96 update dependencies 2025-04-11 22:45:54 +06:00
5f93706d91 Small performance optimization of MutableMap.applyDiff 2025-04-11 22:40:55 +06:00
39415550f5 Merge pull request #568 from 000Sanya/0.25.5
fix test running for wasm/js browser
2025-04-11 22:40:47 +06:00
nullsanya
4586ad65b5 fix test running for wasm/js browser 2025-04-05 12:45:51 +10:00
8c5678e26f pre-fix of tests for wasmjs 2025-04-04 21:46:36 +06:00
5dff373d5f refactor: rename templates and optimize wasmJsMain blocks 2025-04-04 20:29:40 +06:00
e17868a085 Merge pull request #566 from 000Sanya/master
implement support of wasm/js for browser
2025-04-04 20:24:44 +06:00
97bb6d0936 start 0.25.5 2025-04-04 20:06:44 +06:00
nullsanya
54576d8dec implement support of wasm/js for browser 2025-04-03 21:25:26 +10:00
282bb24c71 Merge pull request #563 from InsanusMokrassar/0.25.4
0.25.4
2025-04-02 23:48:54 +06:00
b1a96b6ecb fix transactions dsl 2025-04-02 16:52:08 +06:00
66dac2086c update koin 2025-04-02 16:09:40 +06:00
e269d0d206 fix in transactions 2025-04-02 16:05:33 +06:00
5d95c3eb9c update dependencies 2025-03-31 11:30:56 +06:00
26650e9b6c add SmartKeyRWLocker.withWriteLocks extension with vararg keys 2025-03-27 20:54:41 +06:00
7339dd8354 start 0.25.4 2025-03-27 20:53:29 +06:00
8ae983971a Merge pull request #561 from InsanusMokrassar/0.25.3
0.25.3
2025-03-25 17:55:58 +06:00
130 changed files with 1687 additions and 742 deletions

2
.gitignore vendored
View File

@@ -1,4 +1,5 @@
.idea
.vscode
.kotlin
out/*
*.iml
@@ -9,6 +10,7 @@ settings.xml
.gradle/
build/
out/
bin/
secret.gradle
local.properties

View File

@@ -1,5 +1,92 @@
# Changelog
## 0.26.4
* `Common`:
* Add expect/actual `MPPFilePathSeparator`
* Fix `FileName` realization to take care about system file path separator
## 0.26.3
* `Versions`:
* `Kotlin`: `2.2.0` -> `2.2.10`
* `KSP`: `2.2.0-2.0.2` -> `2.2.10-2.0.2`
* `Android CoreKTX`: `1.16.0` -> `1.17.0`
* `Android Fragment`: `1.8.8` -> `1.8.9`
## 0.26.2
* `Versions`:
* `Ktor`: `3.2.2` -> `3.2.3`
* `Okio`: `3.15.0` -> `3.16.0`
* `Coroutines`:
* Rename `SpecialMutableStateFlow` to `MutableRedeliverStateFlow`
## 0.26.1
* `Versions`:
* `Compose`: `1.8.1` -> `1.8.2`
* `Ktor`: `3.2.1` -> `3.2.2`
* `Coroutines`:
* Add opportunity to pass logger in subscribe async
## 0.26.0
**WARNING!!! SINCE THIS VERSION IF YOU WANT TO USE SOME OF KSP MODULES, SET `ksp.useKSP2=false` IN YOUR `gradle.properties`** (see [gh issue 2491](https://github.com/google/ksp/issues/2491))
* `Versions`:
* `Kotlin`: `2.1.21` -> `2.2.0`
* `Serialization`: `1.8.1` -> `1.9.0`
* `KSLog`: `1.4.2` -> `1.5.0`
* `Ktor`: `3.1.3` -> `3.2.1`
* `Koin`: `4.0.4` -> `4.1.0`
* `Okio`: `3.12.0` -> `3.15.0`
* `KSP`: `2.1.20-1.0.31` -> `2.2.0-2.0.2`
* `kotlin-poet`: `1.18.1` -> `2.2.0`
## 0.25.8
* `Pagination`:
* `Compose`:
* New function `rememberInfinityPagedComponentContext` to create `InfinityPagedComponentContext`
* New variants of `InfinityPagedComponent` component
## 0.25.7
* `Versions`:
* `Compose`: `1.8.0` -> `1.8.1`
* `Xerial SQLite`: `3.49.1.0` -> `3.50.1.0`
* `Okio`: `3.11.0` -> `3.12.0`
* `Android AppCompat`: `1.7.0` -> `1.7.1`
* `Android Fragment`: `1.8.6` -> `1.8.8`
## 0.25.6
* `Versions`:
* `Kotlin`: `2.1.20` -> `2.1.21`
* `KSLog`: `1.4.1` -> `1.4.2`
* `Compose`: `1.7.3` -> `1.8.0`
* `Okio`: `3.10.2` -> `3.11.0`
## 0.25.5
* `Versions`:
* `Exposed`: `0.60.0` -> `0.61.0`
* `Serialization`: `1.8.0` -> `1.8.1`
* `Coroutines`: `1.10.1` -> `1.10.2`
* `Common`:
* Small performance optimization of `MutableMap.applyDiff`
## 0.25.4
* `Versions`:
* `Ktor`: `3.1.1` -> `3.1.2`
* `Koin`: `4.0.2` -> `4.0.4`
* `Coroutines`:
* Add `SmartKeyRWLocker.withWriteLocks` extension with vararg keys
* `Transactions`:
* Fix order of rollback actions calling
## 0.25.3
* `Coroutines`:

View File

@@ -35,3 +35,51 @@ Most of complex modules are built with next hierarchy:
* `common` part contains routes which are common for clients and servers
* `client` submodule contains clients which are usually using `UnifiedRequester` to make requests using routes from `ktor/common` module and some internal logic of requests
* `server` submodule (in most cases `JVM`-only) contains some extensions for `Route` instances which usually will give opportunity to proxy internet requests from `ktor/client` realization to some proxy object
## Gradle Templates
All templates can be used by applying them in your project's build.gradle files using the `apply from` directive. For example:
```gradle
apply from: "$defaultProject"
```
In the sample has been used `defaultProject.gradle` as a basic template.
The project includes a collection of Gradle templates to simplify project setup and configuration. These templates are located in the `gradle/templates` directory and can be used to quickly set up different types of projects:
### Project Setup Templates
* `defaultProject.gradle` (usage `apply from: "$defaultProject"`) - Basic project configuration
* `defaultProjectWithSerialization.gradle` (usage `apply from: "$defaultProjectWithSerialization"`) - Project configuration with Kotlin Serialization support
* `mppJavaProject.gradle` (usage `apply from: "$mppJavaProject"`) - Multiplatform project with Java support
* `mppAndroidProject.gradle` (usage `apply from: "$mppAndroidProject"`) - Multiplatform project with Android support
### Multiplatform Configuration Templates
* `enableMPPAndroid.gradle` (usage `apply from: "$enableMPPAndroid"`) - Enable Android target in multiplatform project
* `enableMPPJs.gradle` (usage `apply from: "$enableMPPJs"`) - Enable JavaScript target in multiplatform project
* `enableMPPJvm.gradle` (usage `apply from: "$enableMPPJvm"`) - Enable JVM target in multiplatform project
* `enableMPPNativeArm64.gradle` (usage `apply from: "$enableMPPNativeArm64"`) - Enable ARM64 native target
* `enableMPPNativeX64.gradle` (usage `apply from: "$enableMPPNativeX64"`) - Enable x64 native target
* `enableMPPWasmJs.gradle` (usage `apply from: "$enableMPPWasmJs"`) - Enable WebAssembly JavaScript target
### Compose Integration Templates
* `addCompose.gradle` (usage `apply from: "$addCompose"`) - Basic Compose configuration
* `addComposeForAndroid.gradle` (usage `apply from: "$addComposeForAndroid"`) - Compose configuration for Android
* `addComposeForDesktop.gradle` (usage `apply from: "$addComposeForDesktop"`) - Compose configuration for Desktop
* `addComposeForJs.gradle` (usage `apply from: "$addComposeForJs"`) - Compose configuration for JavaScript
### Publishing Templates
* `publish.gradle` (usage `apply from: "$publish"`) - General publishing configuration
* `publish_jvm.gradle` (usage `apply from: "$publish_jvm"`) - JVM-specific publishing configuration
* `publish.kpsb` and `publish_jvm.kpsb` (usage `apply from: "$publish_kpsb"` and `apply from: "$publish_jvm_kpsb"`) - Publishing configuration for Kotlin Multiplatform and JVM
### Combined Project Templates
* `mppJvmJsWasmJsLinuxMingwProject.gradle` (usage `apply from: "$mppJvmJsWasmJsLinuxMingwProject"`) - Multiplatform project for JVM, JS, Wasm, Linux, and MinGW
* `mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project.gradle` (usage `apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project"`) - Multiplatform project with additional Android and ARM64 support
* `mppComposeJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project.gradle` (usage `apply from: "$mppComposeJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project"`) - Multiplatform project with Compose support
* `mppProjectWithSerializationAndCompose.gradle` (usage `apply from: "$mppProjectWithSerializationAndCompose"`) - Multiplatform project with both Serialization and Compose support

View File

@@ -19,15 +19,30 @@ buildscript {
plugins {
alias(libs.plugins.versions)
alias(libs.plugins.nmcp.aggregation)
}
if ((project.hasProperty('SONATYPE_USER') || System.getenv('SONATYPE_USER') != null) && (project.hasProperty('SONATYPE_PASSWORD') || System.getenv('SONATYPE_PASSWORD') != null)) {
nmcpAggregation {
centralPortal {
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')
validationTimeout = Duration.ofHours(4)
publishingType = System.getenv('PUBLISHING_TYPE') != "" ? System.getenv('PUBLISHING_TYPE') : "USER_MANAGED"
}
publishAllProjectsProbablyBreakingProjectIsolation()
}
}
allprojects {
repositories {
mavenLocal()
mavenCentral()
google()
maven { url "https://maven.pkg.jetbrains.space/public/p/compose/dev" }
maven { url "https://nexus.inmo.dev/repository/maven-releases/" }
mavenLocal()
}
}

View File

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

View File

@@ -4,4 +4,4 @@ plugins {
id "com.android.library"
}
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64Project"
apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project"

View File

@@ -4,7 +4,7 @@ plugins {
id "com.android.library"
}
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64Project"
apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project"
kotlin {
sourceSets {
@@ -29,5 +29,12 @@ kotlin {
api libs.okio
}
}
wasmJsMain {
dependencies {
api libs.kotlinx.browser
api libs.kt.coroutines
}
}
}
}

View File

@@ -6,7 +6,7 @@ plugins {
alias(libs.plugins.kt.jb.compose)
}
apply from: "$mppComposeJvmJsAndroidLinuxMingwLinuxArm64Project"
apply from: "$mppComposeJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project"
kotlin {
sourceSets {

View File

@@ -2,11 +2,9 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.runComposeUiTest
import dev.inmo.micro_utils.common.compose.LoadableComponent
import dev.inmo.micro_utils.coroutines.SpecialMutableStateFlow
import kotlinx.coroutines.flow.MutableSharedFlow
import dev.inmo.micro_utils.coroutines.MutableRedeliverStateFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.firstOrNull
import org.jetbrains.annotations.TestOnly
import kotlin.test.Test
import kotlin.test.assertTrue
@@ -16,8 +14,8 @@ class LoadableComponentTests {
@Test
@TestOnly
fun testSimpleLoad() = runComposeUiTest {
val loadingFlow = SpecialMutableStateFlow<Int>(0)
val loadedFlow = SpecialMutableStateFlow<Int>(0)
val loadingFlow = MutableRedeliverStateFlow<Int>(0)
val loadedFlow = MutableRedeliverStateFlow<Int>(0)
setContent {
LoadableComponent<Int>({
loadingFlow.filter { it == 1 }.first()

View File

@@ -7,7 +7,7 @@ import kotlin.jvm.JvmInline
@JvmInline
value class FileName(val string: String) {
val name: String
get() = withoutSlashAtTheEnd.takeLastWhile { it != '/' }
get() = withoutSlashAtTheEnd.takeLastWhile { it != MPPFilePathSeparator }
val extension: String
get() = name.takeLastWhile { it != '.' }
val nameWithoutExtension: String
@@ -18,7 +18,7 @@ value class FileName(val string: String) {
} ?: filename
}
val withoutSlashAtTheEnd: String
get() = string.dropLastWhile { it == '/' }
get() = string.dropLastWhile { it == MPPFilePathSeparator }
override fun toString(): String = string
}
@@ -26,6 +26,7 @@ value class FileName(val string: String) {
expect class MPPFile
expect val MPPFile.filename: FileName
expect val MPPFilePathSeparator: Char
expect val MPPFile.filesize: Long
expect val MPPFile.bytesAllocatorSync: ByteArrayAllocator
expect val MPPFile.bytesAllocator: SuspendByteArrayAllocator

View File

@@ -82,13 +82,11 @@ fun <K, V> MutableMap<K, V>.applyDiff(
mapDiff: MapDiff<K, V>
) {
mapDiff.apply {
removed.keys.forEach { remove(it) }
keys.removeAll(removed.keys)
changed.forEach { (k, oldNew) ->
put(k, oldNew.second)
}
added.forEach { (k, new) ->
put(k, new)
}
putAll(added)
}
}

View File

@@ -35,6 +35,10 @@ private suspend fun MPPFile.dirtyReadBytes(): ByteArray = readBytesPromise().awa
*/
actual val MPPFile.filename: FileName
get() = FileName(name)
actual val MPPFilePathSeparator: Char
get() = '/'
/**
* @suppress
*/

View File

@@ -14,6 +14,10 @@ actual typealias MPPFile = File
*/
actual val MPPFile.filename: FileName
get() = FileName(name)
actual val MPPFilePathSeparator: Char
get() = File.separatorChar
/**
* @suppress
*/

View File

@@ -11,6 +11,10 @@ actual typealias MPPFile = Path
*/
actual val MPPFile.filename: FileName
get() = FileName(toString())
actual val MPPFilePathSeparator: Char = Path.DIRECTORY_SEPARATOR.first()
/**
* @suppress
*/

View File

@@ -0,0 +1,15 @@
package dev.inmo.micro_utils.common
import org.khronos.webgl.*
fun DataView.toByteArray() = ByteArray(this.byteLength) {
getInt8(it)
}
fun ArrayBuffer.toByteArray() = Int8Array(this).toByteArray()
fun ByteArray.toDataView() = DataView(ArrayBuffer(size)).also {
forEachIndexed { i, byte -> it.setInt8(i, byte) }
}
fun ByteArray.toArrayBuffer() = toDataView().buffer

View File

@@ -0,0 +1,13 @@
package dev.inmo.micro_utils.common
import kotlinx.browser.window
fun copyToClipboard(text: String): Boolean {
return runCatching {
window.navigator.clipboard.writeText(
text
)
}.onFailure {
it.printStackTrace()
}.isSuccess
}

View File

@@ -0,0 +1,31 @@
package dev.inmo.micro_utils.common
import kotlinx.browser.window
import kotlinx.coroutines.await
import org.w3c.fetch.Response
import org.w3c.files.Blob
import org.w3c.files.BlobPropertyBag
external class ClipboardItem(data: JsAny?) : JsAny
fun createBlobData(blob: Blob): JsAny = js("""({[blob.type]: blob})""")
inline fun Blob.convertToClipboardItem(): ClipboardItem {
return ClipboardItem(createBlobData(this))
}
suspend fun copyImageURLToClipboard(imageUrl: String): Boolean {
return runCatching {
val response = window.fetch(imageUrl).await<Response>()
val blob = response.blob().await<Blob>()
val data = arrayOf(
Blob(
arrayOf(blob).toJsArray().unsafeCast(),
BlobPropertyBag("image/png")
).convertToClipboardItem()
).toJsArray()
window.navigator.clipboard.write(data.unsafeCast())
}.onFailure {
it.printStackTrace()
}.isSuccess
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,62 @@
package dev.inmo.micro_utils.common
import kotlinx.coroutines.await
import org.khronos.webgl.ArrayBuffer
import org.w3c.dom.ErrorEvent
import org.w3c.files.*
import kotlin.js.Promise
/**
* @suppress
*/
actual typealias MPPFile = File
private fun createJsError(message: String): JsAny = js("Error(message)")
fun MPPFile.readBytesPromise() = Promise { success, failure ->
val reader = FileReader()
reader.onload = {
success((reader.result as ArrayBuffer))
}
reader.onerror = {
val message = (it as ErrorEvent).message
failure(createJsError(message))
}
reader.readAsArrayBuffer(this)
}
fun MPPFile.readBytes(): ByteArray {
val reader = FileReaderSync()
return reader.readAsArrayBuffer(this).toByteArray()
}
private suspend fun MPPFile.dirtyReadBytes(): ByteArray = readBytesPromise().await<ArrayBuffer>().toByteArray()
/**
* @suppress
*/
actual val MPPFile.filename: FileName
get() = FileName(name)
actual val MPPFilePathSeparator: Char
get() = '/'
/**
* @suppress
*/
actual val MPPFile.filesize: Long
get() = jsNumberToBigInt(size).toLong()
/**
* @suppress
*/
@Warning("That is not optimized version of bytes allocator. Use asyncBytesAllocator everywhere you can")
actual val MPPFile.bytesAllocatorSync: ByteArrayAllocator
get() = ::readBytes
/**
* @suppress
*/
@Warning("That is not optimized version of bytes allocator. Use asyncBytesAllocator everywhere you can")
actual val MPPFile.bytesAllocator: SuspendByteArrayAllocator
get() = ::dirtyReadBytes
private fun jsNumberToBigInt(number: JsNumber): JsBigInt = js("BigInt(number)")

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,11 @@
package dev.inmo.micro_utils.common
actual fun Float.fixed(signs: Int): Float {
return jsToFixed(toDouble().toJsNumber(), signs.coerceIn(FixedSignsRange)).toString().toFloat()
}
actual fun Double.fixed(signs: Int): Double {
return jsToFixed(toJsNumber(), signs.coerceIn(FixedSignsRange)).toString().toDouble()
}
private fun jsToFixed(number: JsNumber, signs: Int): JsString = js("number.toFixed(signs)")

View File

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

View File

@@ -6,7 +6,7 @@ plugins {
alias(libs.plugins.kt.jb.compose)
}
apply from: "$mppComposeJvmJsAndroidLinuxMingwLinuxArm64Project"
apply from: "$mppComposeJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project"
kotlin {
sourceSets {

View File

@@ -3,7 +3,7 @@ package dev.inmo.micro_utils.coroutines.compose
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.remember
import dev.inmo.micro_utils.coroutines.SpecialMutableStateFlow
import dev.inmo.micro_utils.coroutines.MutableRedeliverStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.debounce
@@ -16,7 +16,7 @@ import org.jetbrains.compose.web.css.StyleSheet
* to add `Style(stylesheet)` on every compose function call
*/
object StyleSheetsAggregator {
private val _stylesFlow = SpecialMutableStateFlow<Set<CSSRulesHolder>>(emptySet())
private val _stylesFlow = MutableRedeliverStateFlow<Set<CSSRulesHolder>>(emptySet())
val stylesFlow: StateFlow<Set<CSSRulesHolder>> = _stylesFlow.asStateFlow()
@Composable

View File

@@ -2,7 +2,7 @@ import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.test.*
import dev.inmo.micro_utils.coroutines.SpecialMutableStateFlow
import dev.inmo.micro_utils.coroutines.MutableRedeliverStateFlow
import org.jetbrains.annotations.TestOnly
import kotlin.test.Test
@@ -11,7 +11,7 @@ class FlowStateTests {
@Test
@TestOnly
fun simpleTest() = runComposeUiTest {
val flowState = SpecialMutableStateFlow(0)
val flowState = MutableRedeliverStateFlow(0)
setContent {
Button({ flowState.value++ }) { Text("Click") }
Text(flowState.collectAsState().value.toString())

View File

@@ -4,6 +4,15 @@ import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.consumeAsFlow
/**
* Creates an actor using coroutines that processes incoming messages of type [T].
* An actor is a computational entity that processes messages sequentially in response to messages it receives.
*
* @param T The type of messages this actor will process
* @param channelCapacity The capacity of the [Channel] used for message delibery to the actor. Defaults to [Channel.UNLIMITED]
* @param block The processing logic to be applied to each received message
* @return A [Channel] that can be used to send messages to this actor or cancel it
*/
fun <T> CoroutineScope.actor(
channelCapacity: Int = Channel.UNLIMITED,
block: suspend (T) -> Unit
@@ -13,6 +22,16 @@ fun <T> CoroutineScope.actor(
return channel
}
/**
* Creates a safe actor that catches and handles exceptions during message processing.
* This variant wraps the processing logic in a safety mechanism to prevent actor failure due to exceptions.
*
* @param T The type of messages this actor will process
* @param channelCapacity The capacity of the [Channel] used for message processing. Defaults to [Channel.UNLIMITED]
* @param onException Handler for exceptions that occur during message processing. Defaults to [defaultSafelyExceptionHandler]
* @param block The processing logic to be applied to each received message
* @return A [Channel] that can be used to send messages to this actor
*/
inline fun <T> CoroutineScope.safeActor(
channelCapacity: Int = Channel.UNLIMITED,
noinline onException: ExceptionHandler<Unit> = defaultSafelyExceptionHandler,

View File

@@ -1,5 +1,6 @@
package dev.inmo.micro_utils.coroutines
import dev.inmo.kslog.common.KSLog
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.consumeAsFlow
@@ -7,13 +8,15 @@ import kotlinx.coroutines.flow.consumeAsFlow
fun <T> CoroutineScope.actorAsync(
channelCapacity: Int = Channel.UNLIMITED,
markerFactory: suspend (T) -> Any? = { null },
logger: KSLog = KSLog,
block: suspend (T) -> Unit
): Channel<T> {
val channel = Channel<T>(channelCapacity)
channel.consumeAsFlow().subscribeAsync(this, markerFactory, block)
channel.consumeAsFlow().subscribeAsync(this, markerFactory, logger, block)
return channel
}
@Deprecated("Use standard actosAsync instead", ReplaceWith("actorAsync(channelCapacity, markerFactory, block = block)", "dev.inmo.micro_utils.coroutines.actorAsync"))
inline fun <T> CoroutineScope.safeActorAsync(
channelCapacity: Int = Channel.UNLIMITED,
noinline onException: ExceptionHandler<Unit> = defaultSafelyExceptionHandler,

View File

@@ -1,5 +1,6 @@
package dev.inmo.micro_utils.coroutines
import dev.inmo.kslog.common.KSLog
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.*
import kotlinx.coroutines.flow.*
@@ -8,6 +9,7 @@ import kotlinx.coroutines.sync.withLock
private class SubscribeAsyncReceiver<T>(
val scope: CoroutineScope,
val logger: KSLog,
output: suspend SubscribeAsyncReceiver<T>.(T) -> Unit
) {
private val dataChannel: Channel<T> = Channel(Channel.UNLIMITED)
@@ -15,7 +17,7 @@ private class SubscribeAsyncReceiver<T>(
get() = dataChannel
init {
scope.launchLoggingDropExceptions {
scope.launchLoggingDropExceptions(logger = logger) {
for (data in dataChannel) {
output(data)
}
@@ -33,13 +35,16 @@ private data class AsyncSubscriptionCommandData<T, M>(
val scope: CoroutineScope,
val markerFactory: suspend (T) -> M,
val block: suspend (T) -> Unit,
val logger: KSLog,
val onEmpty: suspend (M) -> Unit
) : AsyncSubscriptionCommand<T, M> {
override suspend fun invoke(markersMap: MutableMap<M, SubscribeAsyncReceiver<T>>) {
val marker = markerFactory(data)
markersMap.getOrPut(marker) {
SubscribeAsyncReceiver(scope.LinkedSupervisorScope()) {
safelyWithoutExceptions { block(it) }
SubscribeAsyncReceiver(scope.LinkedSupervisorScope(), logger) {
runCatchingLogging(logger = logger) {
block(it)
}
if (isEmpty()) {
onEmpty(marker)
}
@@ -63,6 +68,7 @@ private data class AsyncSubscriptionCommandClearReceiver<T, M>(
fun <T, M> Flow<T>.subscribeAsync(
scope: CoroutineScope,
markerFactory: suspend (T) -> M,
logger: KSLog = KSLog,
block: suspend (T) -> Unit
): Job {
val subscope = scope.LinkedSupervisorScope()
@@ -71,8 +77,14 @@ fun <T, M> Flow<T>.subscribeAsync(
it.invoke(markersMap)
}
val job = subscribeLoggingDropExceptions(subscope) { data ->
val dataCommand = AsyncSubscriptionCommandData(data, subscope, markerFactory, block) { marker ->
val job = subscribeLoggingDropExceptions(subscope, logger = logger) { data ->
val dataCommand = AsyncSubscriptionCommandData(
data = data,
scope = subscope,
markerFactory = markerFactory,
block = block,
logger = logger
) { marker ->
actor.send(
AsyncSubscriptionCommandClearReceiver(marker)
)
@@ -85,17 +97,20 @@ fun <T, M> Flow<T>.subscribeAsync(
return job
}
@Deprecated("Renamed", ReplaceWith("subscribeLoggingDropExceptionsAsync(scope, markerFactory, block = block)", "dev.inmo.micro_utils.coroutines.subscribeLoggingDropExceptionsAsync"))
fun <T, M> Flow<T>.subscribeSafelyAsync(
scope: CoroutineScope,
markerFactory: suspend (T) -> M,
onException: ExceptionHandler<Unit> = defaultSafelyExceptionHandler,
logger: KSLog = KSLog,
block: suspend (T) -> Unit
) = subscribeAsync(scope, markerFactory) {
) = subscribeAsync(scope, markerFactory, logger) {
safely(onException) {
block(it)
}
}
@Deprecated("Renamed", ReplaceWith("subscribeLoggingDropExceptionsAsync(scope, markerFactory, block = block)", "dev.inmo.micro_utils.coroutines.subscribeLoggingDropExceptionsAsync"))
fun <T, M> Flow<T>.subscribeSafelyWithoutExceptionsAsync(
scope: CoroutineScope,
markerFactory: suspend (T) -> M,
@@ -107,11 +122,22 @@ fun <T, M> Flow<T>.subscribeSafelyWithoutExceptionsAsync(
}
}
fun <T, M> Flow<T>.subscribeLoggingDropExceptionsAsync(
scope: CoroutineScope,
markerFactory: suspend (T) -> M,
logger: KSLog = KSLog,
block: suspend (T) -> Unit
) = subscribeAsync(scope, markerFactory, logger) {
block(it)
}
@Deprecated("Renamed", ReplaceWith("subscribeLoggingDropExceptionsAsync(scope, markerFactory, logger, block = block)", "dev.inmo.micro_utils.coroutines.subscribeLoggingDropExceptionsAsync"))
fun <T, M> Flow<T>.subscribeSafelySkippingExceptionsAsync(
scope: CoroutineScope,
markerFactory: suspend (T) -> M,
logger: KSLog = KSLog,
block: suspend (T) -> Unit
) = subscribeAsync(scope, markerFactory) {
) = subscribeAsync(scope, markerFactory, logger) {
safelyWithoutExceptions({ /* do nothing */}) {
block(it)
}

View File

@@ -1,7 +1,5 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.InternalCoroutinesApi
import kotlinx.coroutines.channels.BufferOverflow
@@ -11,13 +9,12 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.internal.SynchronizedObject
import kotlinx.coroutines.internal.synchronized
import kotlin.coroutines.CoroutineContext
/**
* Works like [StateFlow], but guarantee that latest value update will always be delivered to
* each active subscriber
*/
open class SpecialMutableStateFlow<T>(
open class MutableRedeliverStateFlow<T>(
initialValue: T
) : MutableStateFlow<T>, FlowCollector<T>, MutableSharedFlow<T> {
@OptIn(InternalCoroutinesApi::class)
@@ -68,3 +65,6 @@ open class SpecialMutableStateFlow<T>(
override suspend fun collect(collector: FlowCollector<T>) = sharingFlow.collect(collector)
}
@Deprecated("Renamed to MutableRedeliverStateFlow", ReplaceWith("MutableRedeliverStateFlow<T>"))
typealias SpecialMutableStateFlow<T> = MutableRedeliverStateFlow<T>

View File

@@ -221,4 +221,6 @@ suspend inline fun <T, R> SmartKeyRWLocker<T>.withWriteLocks(keys: Iterable<T>,
unlockWrite(it)
}
}
}
}
suspend inline fun <T, R> SmartKeyRWLocker<T>.withWriteLocks(vararg keys: T, action: () -> R): R = withWriteLocks(keys.asIterable(), action)

View File

@@ -1,7 +1,6 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.first
@@ -44,7 +43,7 @@ sealed interface SmartMutex {
* @param locked Preset state of [isLocked] and its internal [_lockStateFlow]
*/
class Mutable(locked: Boolean = false) : SmartMutex {
private val _lockStateFlow = SpecialMutableStateFlow<Boolean>(locked)
private val _lockStateFlow = MutableRedeliverStateFlow<Boolean>(locked)
override val lockStateFlow: StateFlow<Boolean> = _lockStateFlow.asStateFlow()
private val internalChangesMutex = Mutex()

View File

@@ -1,7 +1,6 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.first
@@ -47,7 +46,7 @@ sealed interface SmartSemaphore {
*/
class Mutable(permits: Int, acquiredPermits: Int = 0) : SmartSemaphore {
override val maxPermits: Int = permits
private val _freePermitsStateFlow = SpecialMutableStateFlow<Int>(permits - acquiredPermits)
private val _freePermitsStateFlow = MutableRedeliverStateFlow<Int>(permits - acquiredPermits)
override val permitsStateFlow: StateFlow<Int> = _freePermitsStateFlow.asStateFlow()
private val internalChangesMutex = Mutex(false)

View File

@@ -1,33 +1,31 @@
import dev.inmo.micro_utils.coroutines.SpecialMutableStateFlow
import dev.inmo.micro_utils.coroutines.asDeferred
import dev.inmo.micro_utils.coroutines.MutableRedeliverStateFlow
import dev.inmo.micro_utils.coroutines.subscribe
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
class SpecialMutableStateFlowTests {
@Test
fun simpleTest() = runTest {
val specialMutableStateFlow = SpecialMutableStateFlow(0)
specialMutableStateFlow.value = 1
specialMutableStateFlow.first { it == 1 }
assertEquals(1, specialMutableStateFlow.value)
val mutableRedeliverStateFlow = MutableRedeliverStateFlow(0)
mutableRedeliverStateFlow.value = 1
mutableRedeliverStateFlow.first { it == 1 }
assertEquals(1, mutableRedeliverStateFlow.value)
}
@Test
fun specialTest() = runTest {
val specialMutableStateFlow = SpecialMutableStateFlow(0)
val mutableRedeliverStateFlow = MutableRedeliverStateFlow(0)
lateinit var subscriberJob: Job
subscriberJob = specialMutableStateFlow.subscribe(this) {
subscriberJob = mutableRedeliverStateFlow.subscribe(this) {
when (it) {
1 -> specialMutableStateFlow.value = 2
1 -> mutableRedeliverStateFlow.value = 2
2 -> subscriberJob.cancel()
}
}
specialMutableStateFlow.value = 1
mutableRedeliverStateFlow.value = 1
subscriberJob.join()
assertEquals(2, specialMutableStateFlow.value)
assertEquals(2, mutableRedeliverStateFlow.value)
}
}

View File

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

View File

@@ -30,3 +30,13 @@ allprojects {
}
}
}
tasks.register("getPublishableModules") {
doLast {
rootProject.subprojects.each { project ->
if (project.plugins.hasPlugin('maven-publish')) {
println(":${project.name}")
}
}
}
}

View File

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

View File

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

View File

@@ -11,12 +11,20 @@ private String getCurrentVersionChangelog() {
return changelogDataOS.toString().trim()
}
if (new File(projectDir, "secret.gradle").exists()) {
apply from: './secret.gradle'
def githubTokenVariableName = "GITHUB_RELEASE_TOKEN"
def githubTokenVariableFromEnv = System.getenv(githubTokenVariableName)
def secretFile = new File(projectDir, "secret.gradle")
if (secretFile.exists() || project.hasProperty(githubTokenVariableName) || (githubTokenVariableFromEnv != "" && githubTokenVariableFromEnv != null)) {
if (secretFile.exists()) {
apply from: './secret.gradle'
}
apply plugin: "com.github.breadmoirai.github-release"
def githubReleaseToken = project.hasProperty(githubTokenVariableName) ? project.property(githubTokenVariableName).toString() : githubTokenVariableFromEnv
githubRelease {
token "${project.property('GITHUB_RELEASE_TOKEN')}"
token githubReleaseToken
owner = "InsanusMokrassar"
repo = "MicroUtils"

View File

@@ -8,6 +8,9 @@ android.useAndroidX=true
android.enableJetifier=true
org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=2g
# https://github.com/google/ksp/issues/2491
ksp.useKSP2=false
# JS NPM
crypto_js_version=4.1.1
@@ -15,5 +18,5 @@ crypto_js_version=4.1.1
# Project data
group=dev.inmo
version=0.25.3
android_code_version=293
version=0.26.4
android_code_version=303

View File

@@ -0,0 +1,7 @@
config.set({
client: {
mocha: {
timeout: 240000
}
}
});

View File

@@ -1,46 +1,49 @@
[versions]
kt = "2.1.20"
kt-serialization = "1.8.0"
kt-coroutines = "1.10.1"
kt = "2.2.10"
kt-serialization = "1.9.0"
kt-coroutines = "1.10.2"
kslog = "1.4.1"
kotlinx-browser = "0.5.0"
jb-compose = "1.7.3"
jb-exposed = "0.60.0"
kslog = "1.5.0"
jb-compose = "1.8.2"
jb-exposed = "0.61.0"
jb-dokka = "2.0.0"
sqlite = "3.49.1.0"
sqlite = "3.50.1.0"
korlibs = "5.4.0"
uuid = "0.8.4"
ktor = "3.1.1"
ktor = "3.2.3"
gh-release = "2.5.2"
koin = "4.0.2"
koin = "4.1.0"
okio = "3.10.2"
okio = "3.16.0"
ksp = "2.1.20-1.0.31"
kotlin-poet = "1.18.1"
ksp = "2.2.10-2.0.2"
kotlin-poet = "2.2.0"
versions = "0.51.0"
versions = "0.52.0"
nmcp = "1.1.0"
android-gradle = "8.7.+"
android-gradle = "8.10.+"
dexcount = "4.0.0"
android-coreKtx = "1.15.0"
android-coreKtx = "1.17.0"
android-recyclerView = "1.4.0"
android-appCompat = "1.7.0"
android-fragment = "1.8.6"
android-espresso = "3.6.1"
android-test = "1.2.1"
android-appCompat = "1.7.1"
android-fragment = "1.8.9"
android-espresso = "3.7.0"
android-test = "1.3.0"
android-props-minSdk = "21"
android-props-compileSdk = "35"
android-props-buildTools = "35.0.0"
android-props-compileSdk = "36"
android-props-buildTools = "36.0.0"
[libraries]
@@ -55,6 +58,7 @@ kt-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-and
kt-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kt-coroutines" }
kt-coroutines-debug = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-debug", version.ref = "kt-coroutines" }
kotlinx-browser = { module = "org.jetbrains.kotlinx:kotlinx-browser-wasm-js", version.ref = "kotlinx-browser" }
ktor-io = { module = "io.ktor:ktor-io", version.ref = "ktor" }
ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }
@@ -120,3 +124,4 @@ jb-compose = { id = "org.jetbrains.compose", version.ref = "jb-compose" }
kt-jb-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kt" }
versions = { id = "com.github.ben-manes.versions", version.ref = "versions" }
nmcp-aggregation = { id = "com.gradleup.nmcp.aggregation", version.ref = "nmcp" }

View File

@@ -0,0 +1,9 @@
kotlin {
sourceSets {
commonMain {
dependencies {
implementation compose.runtime
}
}
}
}

View File

@@ -0,0 +1,9 @@
kotlin {
sourceSets {
androidUnitTest {
dependencies {
implementation compose.uiTest
}
}
}
}

View File

@@ -0,0 +1,15 @@
kotlin {
sourceSets {
jvmMain {
dependencies {
implementation compose.desktop.currentOs
}
}
jvmTest {
dependencies {
implementation kotlin('test-junit')
implementation compose.uiTest
}
}
}
}

View File

@@ -0,0 +1,9 @@
kotlin {
sourceSets {
jsMain {
dependencies {
implementation compose.web.core
}
}
}
}

View File

@@ -0,0 +1,24 @@
project.version = "$version"
project.group = "$group"
kotlin {
sourceSets {
commonMain {
dependencies {
implementation kotlin('stdlib')
}
}
commonTest {
dependencies {
implementation kotlin('test-common')
implementation kotlin('test-annotations-common')
implementation libs.kt.coroutines.test
}
}
}
}
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}

View File

@@ -0,0 +1,25 @@
project.version = "$version"
project.group = "$group"
kotlin {
sourceSets {
commonMain {
dependencies {
implementation kotlin('stdlib')
api libs.kt.serialization
}
}
commonTest {
dependencies {
implementation kotlin('test-common')
implementation kotlin('test-annotations-common')
implementation libs.kt.coroutines.test
}
}
}
}
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}

View File

@@ -0,0 +1,31 @@
kotlin {
androidTarget {
publishAllLibraryVariants()
compilations.all {
kotlinOptions {
jvmTarget = "17"
}
}
}
sourceSets {
androidUnitTest {
dependencies {
implementation kotlin('test-junit')
implementation libs.android.test.junit
implementation libs.android.espresso
}
}
androidInstrumentedTest {
dependencies {
implementation kotlin('test-junit')
implementation libs.android.test.junit
implementation libs.android.espresso
}
}
androidMain.dependsOn jvmMain
}
}
apply from: "$defaultAndroidSettings"

View File

@@ -0,0 +1,30 @@
kotlin {
js (IR) {
browser {
testTask {
useMocha {
timeout = "240000"
}
}
}
nodejs {
testTask {
useMocha {
timeout = "240000"
}
}
}
}
sourceSets {
jsMain {
dependencies {
}
}
jsTest {
dependencies {
implementation kotlin('test-js')
}
}
}
}

View File

@@ -0,0 +1,21 @@
kotlin {
jvm {
compilations.main {
kotlinOptions {
jvmTarget = "17"
}
}
}
sourceSets {
jvmMain {
dependencies {
}
}
jvmTest {
dependencies {
implementation kotlin('test-junit')
}
}
}
}

View File

@@ -0,0 +1,8 @@
kotlin {
linuxArm64()
sourceSets {
nativeMain.dependsOn commonMain
linuxArm64Main.dependsOn nativeMain
}
}

View File

@@ -0,0 +1,10 @@
kotlin {
linuxX64()
mingwX64()
sourceSets {
nativeMain.dependsOn commonMain
linuxX64Main.dependsOn nativeMain
mingwX64Main.dependsOn nativeMain
}
}

View File

@@ -0,0 +1,27 @@
kotlin {
wasmJs {
browser {
testTask {
useKarma {
useChromeHeadless()
useConfigDirectory(rootProject.relativeProjectPath("gradle/karma.config.d"))
}
}
}
nodejs {
testTask {
timeout = Duration.ofSeconds(240)
nodeJsArgs.add("--unhandled-rejections=warn")
nodeJsArgs.add("--trace-warnings")
}
}
}
sourceSets {
wasmJsTest {
dependencies {
implementation kotlin('test-wasm-js')
}
}
}
}

View File

@@ -1,37 +1,6 @@
project.version = "$version"
project.group = "$group"
apply from: "$defaultProject"
apply from: "$enableMPPJvm"
apply from: "$enableMPPJs"
apply from: "$enableMPPWasmJs"
apply from: "$enableMPPAndroid"
apply from: "$publish"
kotlin {
androidTarget {
publishAllLibraryVariants()
compilations.all {
kotlinOptions {
jvmTarget = "17"
}
}
}
sourceSets {
commonMain {
dependencies {
implementation kotlin('stdlib')
}
}
commonTest {
dependencies {
implementation kotlin('test-common')
implementation kotlin('test-annotations-common')
implementation libs.kt.coroutines.test
}
}
}
}
apply from: "$defaultAndroidSettings"
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}

View File

@@ -1,108 +0,0 @@
project.version = "$version"
project.group = "$group"
apply from: "$publish"
kotlin {
jvm {
compilations.main {
kotlinOptions {
jvmTarget = "17"
}
}
}
js (IR) {
browser {
testTask {
useMocha {
timeout = "240000"
}
}
}
nodejs {
testTask {
useMocha {
timeout = "240000"
}
}
}
}
androidTarget {
publishAllLibraryVariants()
compilations.all {
kotlinOptions {
jvmTarget = "17"
}
}
}
linuxX64()
mingwX64()
linuxArm64()
sourceSets {
commonMain {
dependencies {
implementation kotlin('stdlib')
implementation compose.runtime
api libs.kt.serialization
}
}
commonTest {
dependencies {
implementation kotlin('test-common')
implementation kotlin('test-annotations-common')
implementation libs.kt.coroutines.test
}
}
androidUnitTest {
dependencies {
implementation kotlin('test-junit')
implementation libs.android.test.junit
implementation libs.android.espresso
implementation compose.uiTest
}
}
androidInstrumentedTest {
dependencies {
implementation kotlin('test-junit')
implementation libs.android.test.junit
implementation libs.android.espresso
}
}
jvmMain {
dependencies {
implementation compose.desktop.currentOs
}
}
jvmTest {
dependencies {
implementation kotlin('test-junit')
implementation compose.uiTest
}
}
jsMain {
dependencies {
implementation compose.web.core
}
}
jsTest {
dependencies {
implementation kotlin('test-js')
}
}
nativeMain.dependsOn commonMain
linuxX64Main.dependsOn nativeMain
mingwX64Main.dependsOn nativeMain
linuxArm64Main.dependsOn nativeMain
androidMain.dependsOn jvmMain
}
}
apply from: "$defaultAndroidSettings"
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}

View File

@@ -0,0 +1,12 @@
apply from: "$defaultProjectWithSerialization"
apply from: "$enableMPPJvm"
apply from: "$enableMPPJs"
apply from: "$enableMPPWasmJs"
apply from: "$enableMPPAndroid"
apply from: "$enableMPPNativeX64"
apply from: "$enableMPPNativeArm64"
apply from: "$addCompose"
apply from: "$addComposeForAndroid"
apply from: "$addComposeForDesktop"
apply from: "$addComposeForJs"
apply from: "$publish"

View File

@@ -1,40 +1,3 @@
project.version = "$version"
project.group = "$group"
apply from: "$defaultProject"
apply from: "$enableMPPJvm"
apply from: "$publish"
kotlin {
jvm {
compilations.main {
kotlinOptions {
jvmTarget = "17"
}
}
}
sourceSets {
commonMain {
dependencies {
implementation kotlin('stdlib')
}
}
commonTest {
dependencies {
implementation kotlin('test-common')
implementation kotlin('test-annotations-common')
implementation libs.kt.coroutines.test
}
}
jvmTest {
dependencies {
implementation kotlin('test-junit')
}
}
}
}
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}

View File

@@ -1,88 +0,0 @@
project.version = "$version"
project.group = "$group"
apply from: "$publish"
kotlin {
jvm {
compilations.main {
kotlinOptions {
jvmTarget = "17"
}
}
}
js (IR) {
browser {
testTask {
useMocha {
timeout = "240000"
}
}
}
nodejs {
testTask {
useMocha {
timeout = "240000"
}
}
}
}
androidTarget {
publishAllLibraryVariants()
compilations.all {
kotlinOptions {
jvmTarget = "17"
}
}
}
linuxX64()
mingwX64()
linuxArm64()
sourceSets {
commonMain {
dependencies {
implementation kotlin('stdlib')
api libs.kt.serialization
}
}
commonTest {
dependencies {
implementation kotlin('test')
implementation kotlin('test-annotations-common')
implementation libs.kt.coroutines.test
}
}
androidUnitTest {
dependencies {
implementation kotlin('test-junit')
implementation libs.android.test.junit
implementation libs.android.espresso
}
}
jvmTest {
dependencies {
implementation kotlin('test-junit')
}
}
jsTest {
dependencies {
implementation kotlin('test-js')
}
}
nativeMain.dependsOn commonMain
linuxX64Main.dependsOn nativeMain
mingwX64Main.dependsOn nativeMain
linuxArm64Main.dependsOn nativeMain
androidMain.dependsOn jvmMain
}
}
apply from: "$defaultAndroidSettings"
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}

View File

@@ -1,70 +0,0 @@
project.version = "$version"
project.group = "$group"
apply from: "$publish"
kotlin {
jvm {
compilations.main {
kotlinOptions {
jvmTarget = "17"
}
}
}
js (IR) {
browser {
testTask {
useMocha {
timeout = "240000"
}
}
}
nodejs {
testTask {
useMocha {
timeout = "240000"
}
}
}
}
linuxX64()
mingwX64()
sourceSets {
commonMain {
dependencies {
implementation kotlin('stdlib')
api libs.kt.serialization
}
}
commonTest {
dependencies {
implementation kotlin('test-common')
implementation kotlin('test-annotations-common')
implementation libs.kt.coroutines.test
}
}
jvmTest {
dependencies {
implementation kotlin('test-junit')
}
}
jsTest {
dependencies {
implementation kotlin('test-js')
}
}
nativeMain.dependsOn commonMain
linuxX64Main.dependsOn nativeMain
mingwX64Main.dependsOn nativeMain
androidMain.dependsOn jvmMain
}
}
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}

View File

@@ -0,0 +1,8 @@
apply from: "$defaultProjectWithSerialization"
apply from: "$enableMPPJvm"
apply from: "$enableMPPJs"
apply from: "$enableMPPWasmJs"
apply from: "$enableMPPAndroid"
apply from: "$enableMPPNativeX64"
apply from: "$enableMPPNativeArm64"
apply from: "$publish"

View File

@@ -0,0 +1,6 @@
apply from: "$defaultProjectWithSerialization"
apply from: "$enableMPPJvm"
apply from: "$enableMPPJs"
apply from: "$enableMPPWasmJs"
apply from: "$enableMPPNativeX64"
apply from: "$publish"

View File

@@ -1,98 +1,10 @@
project.version = "$version"
project.group = "$group"
apply from: "$defaultProjectWithSerialization"
apply from: "$enableMPPJvm"
apply from: "$enableMPPJs"
apply from: "$enableMPPWasmJs"
apply from: "$enableMPPAndroid"
apply from: "$addCompose"
apply from: "$addComposeForAndroid"
apply from: "$addComposeForDesktop"
apply from: "$addComposeForJs"
apply from: "$publish"
kotlin {
jvm {
compilations.main {
kotlinOptions {
jvmTarget = "17"
}
}
}
js (IR) {
browser {
testTask {
useMocha {
timeout = "240000"
}
}
}
nodejs {
testTask {
useMocha {
timeout = "240000"
}
}
}
}
androidTarget {
publishAllLibraryVariants()
compilations.all {
kotlinOptions {
jvmTarget = "17"
}
}
}
sourceSets {
commonMain {
dependencies {
implementation kotlin('stdlib')
implementation compose.runtime
api libs.kt.serialization
}
}
commonTest {
dependencies {
implementation kotlin('test-common')
implementation kotlin('test-annotations-common')
implementation libs.kt.coroutines.test
}
}
jvmMain {
dependencies {
implementation compose.desktop.currentOs
}
}
jvmTest {
dependencies {
implementation kotlin('test-junit')
implementation compose.uiTest
}
}
jsMain {
dependencies {
implementation compose.web.core
}
}
jsTest {
dependencies {
implementation kotlin('test-js')
}
}
androidUnitTest {
dependencies {
implementation kotlin('test-junit')
implementation libs.android.test.junit
implementation libs.android.espresso
implementation compose.uiTest
}
}
}
}
apply from: "$defaultAndroidSettings"
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
//compose {
// if (composePluginKotlinVersion != null && !composePluginKotlinVersion.isEmpty()) {
// kotlinCompilerPlugin.set(composePluginKotlinVersion)
// }
//}

View File

@@ -1,7 +1,3 @@
if (ext.getProperties()["do_publish"] == false) {
return
}
apply plugin: 'maven-publish'
task javadocsJar(type: Jar) {
@@ -23,72 +19,64 @@ publishing {
}
developers {
developer {
id = "InsanusMokrassar"
name = "Aleksei Ovsiannikov"
email = "ovsyannikov.alexey95@gmail.com"
}
developer {
id = "InsanusMokrassar"
name = "Aleksei Ovsiannikov"
email = "ovsyannikov.alexey95@gmail.com"
}
developer {
id = "000Sanya"
name = "Syrov Aleksandr"
email = "000sanya.000sanya@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"
}
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')
}
}
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('INMONEXUS_USER') || System.getenv('INMONEXUS_USER') != null) && (project.hasProperty('INMONEXUS_PASSWORD') || System.getenv('INMONEXUS_PASSWORD') != null)) {
maven {
name = "InmoNexus"
url = uri("https://nexus.inmo.dev/repository/maven-releases/")
credentials {
username = project.hasProperty('INMONEXUS_USER') ? project.property('INMONEXUS_USER') : System.getenv('INMONEXUS_USER')
password = project.hasProperty('INMONEXUS_PASSWORD') ? project.property('INMONEXUS_PASSWORD') : System.getenv('INMONEXUS_PASSWORD')
}
}
if ((project.hasProperty('INMONEXUS_USER') || System.getenv('INMONEXUS_USER') != null) && (project.hasProperty('INMONEXUS_PASSWORD') || System.getenv('INMONEXUS_PASSWORD') != null)) {
maven {
name = "InmoNexus"
url = uri("https://nexus.inmo.dev/repository/maven-releases/")
credentials {
username = project.hasProperty('INMONEXUS_USER') ? project.property('INMONEXUS_USER') : System.getenv('INMONEXUS_USER')
password = project.hasProperty('INMONEXUS_PASSWORD') ? project.property('INMONEXUS_PASSWORD') : System.getenv('INMONEXUS_PASSWORD')
}
}
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('SONATYPE_USER') || System.getenv('SONATYPE_USER') != null) && (project.hasProperty('SONATYPE_PASSWORD') || System.getenv('SONATYPE_PASSWORD') != null)) {
maven {
name = "sonatype"
url = uri("https://ossrh-staging-api.central.sonatype.com/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'

View File

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

View File

@@ -1,5 +1,6 @@
apply plugin: 'maven-publish'
task javadocJar(type: Jar) {
from javadoc
archiveClassifier = 'javadoc'
@@ -55,42 +56,39 @@ publishing {
}
}
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')
}
}
}
}
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('INMONEXUS_USER') || System.getenv('INMONEXUS_USER') != null) && (project.hasProperty('INMONEXUS_PASSWORD') || System.getenv('INMONEXUS_PASSWORD') != null)) {
maven {
name = "InmoNexus"
url = uri("https://nexus.inmo.dev/repository/maven-releases/")
credentials {
username = project.hasProperty('INMONEXUS_USER') ? project.property('INMONEXUS_USER') : System.getenv('INMONEXUS_USER')
password = project.hasProperty('INMONEXUS_PASSWORD') ? project.property('INMONEXUS_PASSWORD') : System.getenv('INMONEXUS_PASSWORD')
}
}
}
}
if ((project.hasProperty('INMONEXUS_USER') || System.getenv('INMONEXUS_USER') != null) && (project.hasProperty('INMONEXUS_PASSWORD') || System.getenv('INMONEXUS_PASSWORD') != null)) {
maven {
name = "InmoNexus"
url = uri("https://nexus.inmo.dev/repository/maven-releases/")
credentials {
username = project.hasProperty('INMONEXUS_USER') ? project.property('INMONEXUS_USER') : System.getenv('INMONEXUS_USER')
password = project.hasProperty('INMONEXUS_PASSWORD') ? project.property('INMONEXUS_PASSWORD') : System.getenv('INMONEXUS_PASSWORD')
}
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('SONATYPE_USER') || System.getenv('SONATYPE_USER') != null) && (project.hasProperty('SONATYPE_PASSWORD') || System.getenv('SONATYPE_PASSWORD') != null)) {
maven {
name = "sonatype"
url = uri("https://ossrh-staging-api.central.sonatype.com/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')
}
}
}
@@ -99,13 +97,13 @@ publishing {
if (project.hasProperty("signing.gnupg.keyName")) {
apply plugin: 'signing'
signing {
useGpgCmd()
sign publishing.publications
}
task signAll {
tasks.withType(Sign).forEach {
dependsOn(it)

View File

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

View File

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

View File

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

View File

@@ -10,6 +10,7 @@ repositories {
dependencies {
api project(":micro_utils.koin")
api project(":micro_utils.ksp.generator")
api libs.kotlin.poet
api libs.ksp
}

View File

@@ -26,6 +26,7 @@ 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_ksp.generator.safeClassName
import dev.inmo.micro_utils.koin.annotations.GenerateGenericKoinDefinition
import dev.inmo.micro_utils.koin.annotations.GenerateKoinDefinition
import org.koin.core.Koin
@@ -237,15 +238,7 @@ class Processor(
""".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 type = safeClassName { it.type }
val targetType = runCatching {
type.parameterizedBy(*(it.typeArgs.takeIf { it.isNotEmpty() } ?.map { it.asTypeName() } ?.toTypedArray() ?: return@runCatching type))
}.getOrElse { e ->

View File

@@ -5,7 +5,7 @@ plugins {
id "com.google.devtools.ksp"
}
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64Project"
apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project"
kotlin {

View File

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

View File

@@ -4,8 +4,14 @@ 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 :)
* Declares a factory with a random qualifier in the Koin module.
* This is useful when you need to declare multiple factory definitions of the same type
* but want them to be uniquely identifiable without manually specifying qualifiers.
* Unlike singles, factories create a new instance each time they are requested.
*
* @param T The type of instance to be created by the factory
* @param definition The definition function that creates new instances
* @return A Koin definition for the factory with a random qualifier
*/
inline fun <reified T : Any> Module.factoryWithRandomQualifier(
noinline definition: Definition<T>

View File

@@ -4,6 +4,16 @@ import org.koin.core.definition.Definition
import org.koin.core.module.Module
import org.koin.core.qualifier.StringQualifier
/**
* Declares a factory with a string qualifier in the Koin module.
* This is a convenience function that wraps the string qualifier in a [StringQualifier].
* Unlike singles, factories create a new instance each time they are requested.
*
* @param T The type of instance to be created by the factory
* @param qualifier The string value to be used as a qualifier
* @param definition The definition function that creates new instances
* @return A Koin definition for the factory with the specified string qualifier
*/
inline fun <reified T : Any> Module.factory(
qualifier: String,
noinline definition: Definition<T>

View File

@@ -3,6 +3,21 @@ package dev.inmo.micro_utils.koin
import org.koin.core.Koin
import org.koin.core.scope.Scope
/**
* Retrieves all instances of type [T] from the current [Scope] and returns them as a distinct list.
* This function is useful when you want to avoid duplicate instances of the same type.
*
* @param T The type of instances to retrieve
* @return A list of distinct instances of type [T]
*/
inline fun <reified T : Any> Scope.getAllDistinct() = getAll<T>().distinct()
/**
* Retrieves all instances of type [T] from the [Koin] container and returns them as a distinct list.
* This function is useful when you want to avoid duplicate instances of the same type.
*
* @param T The type of instances to retrieve
* @return A list of distinct instances of type [T]
*/
inline fun <reified T : Any> Koin.getAllDistinct() = getAll<T>().distinct()

View File

@@ -3,6 +3,23 @@ package dev.inmo.micro_utils.koin
import org.koin.core.Koin
import org.koin.core.scope.Scope
/**
* Retrieves the first available instance of type [T] from the current scope.
* This is useful when you need any instance of a type and don't care which one.
*
* @param T The type of instance to retrieve
* @return The first available instance of type [T]
* @throws NoSuchElementException if no instances of type [T] are available
*/
inline fun <reified T : Any> Scope.getAny() = getAll<T>().first()
/**
* Retrieves the first available instance of type [T] from the Koin container.
* This is useful when you need any instance of a type and don't care which one.
*
* @param T The type of instance to retrieve
* @return The first available instance of type [T]
* @throws NoSuchElementException if no instances of type [T] are available
*/
inline fun <reified T : Any> Koin.getAny() = getAll<T>().first()

View File

@@ -7,33 +7,81 @@ import org.koin.core.instance.InstanceFactory
import org.koin.core.parameter.ParametersDefinition
import org.koin.core.scope.Scope
/**
* Retrieves an instance of type [T] from the Koin container using a [BeanDefinition].
*
* @param T The type of instance to retrieve
* @param definition The bean definition to use for instance retrieval
* @param parameters Optional parameters to pass to the instance constructor
* @return An instance of type [T]
*/
fun <T> Koin.get(definition: BeanDefinition<T>, parameters: ParametersDefinition? = null): T = get(
definition.primaryType,
definition.qualifier,
parameters
)
/**
* Retrieves an instance of type [T] from the Koin container using an [InstanceFactory].
*
* @param T The type of instance to retrieve
* @param definition The instance factory to use for instance retrieval
* @param parameters Optional parameters to pass to the instance constructor
* @return An instance of type [T]
*/
fun <T> Koin.get(definition: InstanceFactory<T>, parameters: ParametersDefinition? = null): T = get(
definition.beanDefinition,
parameters
)
/**
* Retrieves an instance of type [T] from the Koin container using a [KoinDefinition].
*
* @param T The type of instance to retrieve
* @param definition The Koin definition to use for instance retrieval
* @param parameters Optional parameters to pass to the instance constructor
* @return An instance of type [T]
*/
fun <T> Koin.get(definition: KoinDefinition<T>, parameters: ParametersDefinition? = null): T = get(
definition.factory,
parameters
)
/**
* Retrieves an instance of type [T] from the current scope using a [BeanDefinition].
*
* @param T The type of instance to retrieve
* @param definition The bean definition to use for instance retrieval
* @param parameters Optional parameters to pass to the instance constructor
* @return An instance of type [T]
*/
fun <T> Scope.get(definition: BeanDefinition<T>, parameters: ParametersDefinition? = null): T = get(
definition.primaryType,
definition.qualifier,
parameters
)
/**
* Retrieves an instance of type [T] from the current scope using an [InstanceFactory].
*
* @param T The type of instance to retrieve
* @param definition The instance factory to use for instance retrieval
* @param parameters Optional parameters to pass to the instance constructor
* @return An instance of type [T]
*/
fun <T> Scope.get(definition: InstanceFactory<T>, parameters: ParametersDefinition? = null): T = get(
definition.beanDefinition,
parameters
)
/**
* Retrieves an instance of type [T] from the current scope using a [KoinDefinition].
*
* @param T The type of instance to retrieve
* @param definition The Koin definition to use for instance retrieval
* @param parameters Optional parameters to pass to the instance constructor
* @return An instance of type [T]
*/
fun <T> Scope.get(definition: KoinDefinition<T>, parameters: ParametersDefinition? = null): T = get(
definition.factory,
parameters

View File

@@ -3,4 +3,10 @@ package dev.inmo.micro_utils.koin
import com.benasher44.uuid.uuid4
import org.koin.core.qualifier.StringQualifier
/**
* Creates a [StringQualifier] with a random string value.
*
* @param randomFun A function that generates a random string. By default, it uses UUID4 string representation.
* @return A [StringQualifier] with a randomly generated string value
*/
fun RandomQualifier(randomFun: () -> String = { uuid4().toString() }) = StringQualifier(randomFun())

View File

@@ -4,8 +4,14 @@ 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 :)
* Declares a single instance with a random qualifier in the Koin module.
* This is useful when you need to declare multiple instances of the same type
* but want them to be uniquely identifiable without manually specifying qualifiers.
*
* @param T The type of instance to be created
* @param createdAtStart Whether the instance should be created when the Koin module starts (default: false)
* @param definition The definition function that creates the instance
* @return A Koin definition for the single instance with a random qualifier
*/
inline fun <reified T : Any> Module.singleWithRandomQualifier(
createdAtStart: Boolean = false,

View File

@@ -4,6 +4,16 @@ import org.koin.core.definition.Definition
import org.koin.core.module.Module
import org.koin.core.qualifier.StringQualifier
/**
* Declares a single instance with a string qualifier in the Koin module.
* This is a convenience function that wraps the string qualifier in a [StringQualifier].
*
* @param T The type of instance to be created
* @param qualifier The string value to be used as a qualifier
* @param createdAtStart Whether the instance should be created when the Koin module starts (default: false)
* @param definition The definition function that creates the instance
* @return A Koin definition for the single instance with the specified string qualifier
*/
inline fun <reified T : Any> Module.single(
qualifier: String,
createdAtStart: Boolean = false,

View File

@@ -4,4 +4,4 @@ plugins {
id "com.android.library"
}
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64Project"
apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project"

View File

@@ -7,7 +7,7 @@ plugins {
ext.do_publish = false
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64Project"
apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project"
kotlin {

View File

@@ -0,0 +1,23 @@
package dev.inmo.micro_ksp.generator
import com.google.devtools.ksp.KSTypeNotPresentException
import com.google.devtools.ksp.KspExperimental
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.asTypeName
import kotlin.reflect.KClass
@Suppress("NOTHING_TO_INLINE")
@OptIn(KspExperimental::class)
inline fun safeClassName(classnameGetter: () -> KClass<*>) = runCatching {
classnameGetter().asTypeName()
}.getOrElse { e ->
if (e is KSTypeNotPresentException) {
ClassName(
e.ksType.declaration.packageName.asString(),
e.ksType.declaration.qualifiedName ?.asString() ?.replaceFirst(e.ksType.declaration.packageName.asString(), "")
?: e.ksType.declaration.simpleName.asString()
)
} else {
throw e
}
}

View File

@@ -4,4 +4,4 @@ plugins {
id "com.android.library"
}
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64Project"
apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project"

View File

@@ -7,7 +7,7 @@ plugins {
ext.do_publish = false
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64Project"
apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project"
kotlin {

View File

@@ -4,4 +4,4 @@ plugins {
id "com.android.library"
}
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64Project"
apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project"

View File

@@ -7,7 +7,7 @@ plugins {
ext.do_publish = false
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64Project"
apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project"
kotlin {

View File

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

View File

@@ -0,0 +1,63 @@
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.launchLoggingDropExceptions
import dev.inmo.micro_utils.ktor.common.TemporalFileId
import io.ktor.client.HttpClient
import io.ktor.client.content.*
import kotlinx.coroutines.*
import org.w3c.xhr.*
import org.w3c.xhr.XMLHttpRequest.Companion.DONE
suspend fun tempUpload(
fullTempUploadDraftPath: String,
file: MPPFile,
onUpload: ProgressListener
): TemporalFileId {
val formData = FormData()
val answer = CompletableDeferred<TemporalFileId>(currentCoroutineContext().job)
val subscope = CoroutineScope(currentCoroutineContext().LinkedSupervisorJob())
formData.append(
"data",
file
)
val request = XMLHttpRequest()
request.responseType = XMLHttpRequestResponseType.TEXT
request.upload.onprogress = {
subscope.launchLoggingDropExceptions { onUpload.onProgress(it.loaded.toString().toLong(), it.total.toString().toLong()) }
}
request.onload = {
if (request.status == 200.toShort()) {
answer.complete(TemporalFileId(request.responseText))
} else {
answer.completeExceptionally(Exception("Something went wrong: $it"))
}
}
request.onerror = {
answer.completeExceptionally(Exception("Something went wrong: $it"))
}
request.open("POST", fullTempUploadDraftPath, true)
request.send(formData)
answer.invokeOnCompletion {
runCatching {
if (request.readyState != DONE) {
request.abort()
}
}
}
return answer.await().also {
subscope.cancel()
}
}
actual suspend fun HttpClient.tempUpload(
fullTempUploadDraftPath: String,
file: MPPFile,
onUpload: ProgressListener
): TemporalFileId = dev.inmo.micro_utils.ktor.client.tempUpload(fullTempUploadDraftPath, file, onUpload)

View File

@@ -0,0 +1,97 @@
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.launchLoggingDropExceptions
import io.ktor.client.HttpClient
import io.ktor.client.content.*
import io.ktor.http.Headers
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.cancel
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.job
import kotlinx.io.readByteArray
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.StringFormat
import kotlinx.serialization.encodeToString
import org.khronos.webgl.toInt8Array
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: ProgressListener
): 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(v.inputAllocator().readByteArray().toInt8Array()).toJsArray().unsafeCast()),
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.launchLoggingDropExceptions { onUpload.onProgress(it.loaded.toString().toLong(), it.total.toString().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()
}
}

View File

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

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