mirror of
https://github.com/InsanusMokrassar/MicroUtils.git
synced 2024-09-28 21:46:10 +00:00
Compare commits
No commits in common. "master" and "v0.20.29" have entirely different histories.
7
.gitignore
vendored
7
.gitignore
vendored
@ -1,5 +1,4 @@
|
||||
.idea
|
||||
.kotlin
|
||||
out/*
|
||||
*.iml
|
||||
target
|
||||
@ -15,9 +14,3 @@ local.properties
|
||||
kotlin-js-store
|
||||
|
||||
publishing.sh
|
||||
|
||||
local.*
|
||||
local/
|
||||
**/*.local.*
|
||||
|
||||
.kotlin/
|
||||
|
268
CHANGELOG.md
268
CHANGELOG.md
@ -1,273 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## 0.22.4
|
||||
|
||||
* `Versions`:
|
||||
* `Exposed`: `0.54.0` -> `0.55.0`
|
||||
* `SQLite`: `3.46.1.0` -> `3.46.1.2`
|
||||
|
||||
## 0.22.3
|
||||
|
||||
* `Versions`:
|
||||
* `Serialization`: `1.7.2` -> `1.7.3`
|
||||
* `Coroutines`: `1.8.1` -> `1.9.0`
|
||||
* `Compose`: `1.7.0-alpha03` -> `1.7.0-beta02`
|
||||
* `Koin`: `3.5.6` -> `4.0.0`
|
||||
* `Okio`: `3.9.0` -> `3.9.1`
|
||||
* `AndroidFragment`: `1.8.2` -> `1.8.3`
|
||||
* `androidx.compose.material3:material3` has been replaced with `org.jetbrains.compose.material3:material3`
|
||||
* `Common`:
|
||||
* `JS`:
|
||||
* Add several useful extensions
|
||||
* `Compose`:
|
||||
* `JS`:
|
||||
* Add several useful extensions
|
||||
|
||||
## 0.22.2
|
||||
|
||||
* `Versions`:
|
||||
* `Exposed`: `0.53.0` -> `0.54.0`
|
||||
* `SQLite`: `3.46.0.1` -> `3.46.1.0`
|
||||
|
||||
## 0.22.1
|
||||
|
||||
* `Versions`:
|
||||
* `Kotlin`: `2.0.10` -> `2.0.20`
|
||||
* `Serialization`: `1.7.1` -> `1.7.2`
|
||||
* `KSLog`: `1.3.5` -> `1.3.6`
|
||||
* `Compose`: `1.7.0-alpha02` -> `1.7.0-alpha03`
|
||||
* `Ktor`: `2.3.11` -> `2.3.12`
|
||||
|
||||
## 0.22.0
|
||||
|
||||
**THIS UPDATE CONTAINS BREAKING CHANGES ACCORDING TO UPDATE UP TO KOTLIN 2.0**
|
||||
|
||||
* `Versions`:
|
||||
* `Kotlin`: `1.9.23` -> `2.0.10`
|
||||
* `Serialization`: `1.6.3` -> `1.7.1`
|
||||
* `KSLog`: `1.3.4` -> `1.3.5`
|
||||
* `Compose`: `1.6.2` -> `1.7.0-alpha02`
|
||||
* `Exposed`: `0.50.1` -> `0.53.0`
|
||||
* `AndroidAppCompat`: `1.6.1` -> `1.7.0`
|
||||
* `AndroidFragment`: `1.7.1` -> `1.8.2`
|
||||
|
||||
## 0.21.6
|
||||
|
||||
* `KSP`:
|
||||
* `Sealed`:
|
||||
* Fixes in generation
|
||||
|
||||
## 0.21.5
|
||||
|
||||
* `KSP`:
|
||||
* Add utility functions `KSClassDeclaration.findSubClasses`
|
||||
* `Sealed`:
|
||||
* Improve generation
|
||||
|
||||
## 0.21.4
|
||||
|
||||
* `Common`:
|
||||
* `Compose`:
|
||||
* Add support of mingw, linux, arm64 targets
|
||||
* `Coroutines`:
|
||||
* `Compose`:
|
||||
* Add support of mingw, linux, arm64 targets
|
||||
* `Koin`:
|
||||
* Add support of mingw, linux, arm64 targets
|
||||
* `KSP`:
|
||||
* `ClassCasts`:
|
||||
* Add support of mingw, linux, arm64 targets
|
||||
* `Sealed`:
|
||||
* Add support of mingw, linux, arm64 targets
|
||||
|
||||
## 0.21.3
|
||||
|
||||
* `Colors`:
|
||||
* Added as a module. It should be used by default in case you wish all the API currently realized for `HEXAColor`
|
||||
* `Coroutines`:
|
||||
* Fix of [#374](https://github.com/InsanusMokrassar/MicroUtils/issues/374):
|
||||
* Add vararg variants of `awaitFirst`
|
||||
* Add `joinFirst`
|
||||
|
||||
## 0.21.2
|
||||
|
||||
* `KSP`:
|
||||
* `ClassCasts`:
|
||||
* Module has been initialized
|
||||
|
||||
## 0.21.1
|
||||
|
||||
* `KSP`:
|
||||
* Module has been initialized
|
||||
* `Generator`:
|
||||
* Module has been initialized
|
||||
* `Sealed`:
|
||||
* Module has been initialized
|
||||
|
||||
## 0.21.0
|
||||
|
||||
**THIS UPDATE CONTAINS BREAKING CHANGES IN `safely*`-ORIENTED FUNCTIONS**
|
||||
|
||||
* `Coroutines`:
|
||||
* **All `safely` functions lost their `supervisorScope` in favor to wrapping `runCatching`**
|
||||
* `runCatchingSafely` is the main handling function of all `safely` functions
|
||||
* `launchSafely*` and `asyncSafely*` blocks lost `CoroutineScope` as their receiver
|
||||
|
||||
## 0.20.52
|
||||
|
||||
* `Coroutines`:
|
||||
* Small rework of weak jobs: add `WeakScope` factory, rename old weal extensions and add kdocs
|
||||
|
||||
## 0.20.51
|
||||
|
||||
* `Versions`:
|
||||
* `Android Fragment`: `1.7.0` -> `1.7.1`
|
||||
* `Pagination`:
|
||||
* Add `Pagination.nextPageIfTrue` and `Pagination.thisPageIftrue` extensions to get the page according to condition
|
||||
pass status
|
||||
* Add `PaginationResult.nextPageIfNotEmptyOrLastPage` and `PaginationResult.thisPageIfNotEmptyOrLastPage`
|
||||
* Change all `doForAll` and `getAll` extensions fo pagination to work basing on `nextPageIfNotEmptyOrLastPage` and
|
||||
`thisPageIfNotEmptyOrLastPage`
|
||||
|
||||
## 0.20.50
|
||||
|
||||
* `Versions`:
|
||||
* `Coroutines`: `1.8.0` -> `1.8.1`
|
||||
* `KSLog`: `1.3.3` -> `1.3.4`
|
||||
* `Exposed`: `0.50.0` -> `0.50.1`
|
||||
* `Ktor`: `2.3.10` -> `2.3.11`
|
||||
* A lot of inline functions became common functions due to inline with only noinline callbacks in arguments leads to
|
||||
low performance
|
||||
* `Coroutines`:
|
||||
* `SmartMutex`, `SmartSemaphore` and `SmartRWLocker` as their user changed their state flow to `SpecialMutableStateFlow`
|
||||
|
||||
## 0.20.49
|
||||
|
||||
* `Repos`:
|
||||
* `Common`:
|
||||
* All `Repo`s get `diff` extensions
|
||||
* `KeyValueRepo` and `KeyValuesRepo` get `applyDiff` extension
|
||||
* Add new extensions `on*` flows for crud repos
|
||||
|
||||
## 0.20.48
|
||||
|
||||
* `Versions`:
|
||||
* `Android Core KTX`: `1.13.0` -> `1.13.1`
|
||||
* `AndroidX Fragment`: `1.6.2` -> `1.7.0`
|
||||
|
||||
## 0.20.47
|
||||
|
||||
* `Versions`:
|
||||
* `Exposed`: `0.49.0` -> `0.50.0`
|
||||
|
||||
## 0.20.46
|
||||
|
||||
* `Common`:
|
||||
* Now this repo depends on `klock`
|
||||
* Add new object-serializer `DateTimeSerializer` for `klock` serializer
|
||||
|
||||
## 0.20.45
|
||||
|
||||
* `Versions`:
|
||||
* `Android Core KTX`: `1.12.0` -> `1.13.0`
|
||||
|
||||
## 0.20.44
|
||||
|
||||
* `Versions`:
|
||||
* `Compose`: `1.6.1` -> `1.6.2`
|
||||
* `Koin`: `3.5.4` -> `3.5.6`
|
||||
|
||||
## 0.20.43
|
||||
|
||||
* `Versions`:
|
||||
* `Ktor`: `2.3.9` -> `2.3.10`
|
||||
* `Koin`: `3.5.3` -> `3.5.4`
|
||||
|
||||
## 0.20.42
|
||||
|
||||
* `Repos`:
|
||||
* `Generator`:
|
||||
* Improvements
|
||||
|
||||
## 0.20.41
|
||||
|
||||
* `Repos`:
|
||||
* `Exposed`:
|
||||
* `AbstractExposedKeyValueRepo`, `ExposedKeyValueRepo`, `AbstractExposedKeyValuesRepo`, `ExposedKeyValuesRepo` got opportunity to setup some part of their flows
|
||||
|
||||
## 0.20.40
|
||||
|
||||
* `Versions`:
|
||||
* `KSLog`: `1.3.2` -> `1.3.3`
|
||||
* `Exposed`: `0.48.0` -> `0.49.0`
|
||||
* `UUID`: `0.8.2` -> `0.8.4`
|
||||
|
||||
## 0.20.39
|
||||
|
||||
* `Versions`:
|
||||
* `Kotlin`: `1.9.22` -> `1.9.23`
|
||||
* `Korlibs`: `5.3.2` -> `5.4.0`
|
||||
* `Okio`: `3.8.0` -> `3.9.0`
|
||||
* `Compose`: `1.6.0` -> `1.6.1`
|
||||
* `ComposeMaterial3`: `1.2.0` -> `1.2.1`
|
||||
|
||||
## 0.20.38
|
||||
|
||||
* `Versions`:
|
||||
* `Ktor`: `2.3.8` -> `2.3.9`
|
||||
|
||||
## 0.20.37
|
||||
|
||||
* `Versions`:
|
||||
* `Compose`: `1.5.12` -> `1.6.0`
|
||||
* `Exposed`: `0.47.0` -> `0.48.0`
|
||||
|
||||
## 0.20.36
|
||||
|
||||
* `Versions`:
|
||||
* `Serialization`: `1.6.2` -> `1.6.3`
|
||||
* `Korlibs`: `5.3.1` -> `5.3.2`
|
||||
* `Repos`:
|
||||
* `Cache`:
|
||||
* Improve work and functionality of `actualizeAll` and subsequent functions
|
||||
* All internal repos `invalidate`/`actualizeAll` now use common `actualizeAll` functions
|
||||
|
||||
## 0.20.35
|
||||
|
||||
* `Versions`:
|
||||
* `Coroutines`: `1.7.3` -> `1.8.0`
|
||||
* `Material3`: `1.1.2` -> `1.2.0`
|
||||
|
||||
## 0.20.34
|
||||
|
||||
* `Repos`:
|
||||
* `Common`:
|
||||
* Improve default `set` realization of `KeyValuesRepo`
|
||||
|
||||
## 0.20.33
|
||||
|
||||
* `Colors`
|
||||
* `Common`:
|
||||
* Add opportunity to use `HEXAColor` with `ahex` colors
|
||||
|
||||
## 0.20.32
|
||||
|
||||
* `Versions`:
|
||||
* `Okio`: `3.7.0` -> `3.8.0`
|
||||
* `Resources`:
|
||||
* Make `StringResource` serializable
|
||||
* Add several variants of builder usages
|
||||
|
||||
## 0.20.31
|
||||
|
||||
* `Versions`:
|
||||
* `Ktor`: `2.3.7` -> `2.3.8`
|
||||
|
||||
## 0.20.30
|
||||
|
||||
* `Versions`:
|
||||
* `Exposed`: `0.46.0` -> `0.47.0`
|
||||
|
||||
## 0.20.29
|
||||
|
||||
* `Versions`:
|
||||
|
@ -4,7 +4,7 @@ plugins {
|
||||
id "com.android.library"
|
||||
}
|
||||
|
||||
apply from: "$mppAndroidProject"
|
||||
apply from: "$mppAndroidProjectPresetPath"
|
||||
|
||||
kotlin {
|
||||
sourceSets {
|
||||
|
@ -4,7 +4,7 @@ plugins {
|
||||
id "com.android.library"
|
||||
}
|
||||
|
||||
apply from: "$mppAndroidProject"
|
||||
apply from: "$mppAndroidProjectPresetPath"
|
||||
|
||||
kotlin {
|
||||
sourceSets {
|
||||
|
@ -3,10 +3,9 @@ plugins {
|
||||
id "org.jetbrains.kotlin.plugin.serialization"
|
||||
id "com.android.library"
|
||||
alias(libs.plugins.jb.compose)
|
||||
alias(libs.plugins.kt.jb.compose)
|
||||
}
|
||||
|
||||
apply from: "$mppProjectWithSerializationAndCompose"
|
||||
apply from: "$mppProjectWithSerializationAndComposePresetPath"
|
||||
|
||||
kotlin {
|
||||
sourceSets {
|
||||
|
@ -7,6 +7,9 @@ import androidx.compose.foundation.gestures.*
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.ContentAlpha
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.ProvideTextStyle
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.KeyboardArrowDown
|
||||
import androidx.compose.material.icons.filled.KeyboardArrowUp
|
||||
@ -72,7 +75,7 @@ fun NumberPicker(
|
||||
}
|
||||
val coercedAnimatedOffset = animatedOffset.value % halvedNumbersColumnHeightPx
|
||||
val animatedStateValue = animatedStateValue(animatedOffset.value)
|
||||
val disabledArrowsColor = arrowsColor.copy(alpha = 0f)
|
||||
val disabledArrowsColor = arrowsColor.copy(alpha = ContentAlpha.disabled)
|
||||
|
||||
val inputFieldShown = if (allowUseManualInput) {
|
||||
remember { mutableStateOf(false) }
|
||||
|
@ -5,6 +5,7 @@ import androidx.compose.animation.core.exponentialDecay
|
||||
import androidx.compose.foundation.gestures.*
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.material.ContentAlpha
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.KeyboardArrowDown
|
||||
import androidx.compose.material.icons.filled.KeyboardArrowUp
|
||||
@ -61,7 +62,9 @@ fun <T> SetPicker(
|
||||
(index - ceil(animatedOffset.value / halvedNumbersColumnHeightPx).toInt())
|
||||
}
|
||||
val coercedAnimatedOffset = animatedOffset.value % halvedNumbersColumnHeightPx
|
||||
val disabledArrowsColor = arrowsColor.copy(alpha = 0f)
|
||||
val boxOffset = (indexAnimatedOffset * halvedNumbersColumnHeightPx) - coercedAnimatedOffset
|
||||
val disabledArrowsColor = arrowsColor.copy(alpha = ContentAlpha.disabled)
|
||||
val scrollState = rememberScrollState()
|
||||
|
||||
Column(
|
||||
modifier = modifier
|
||||
|
@ -4,7 +4,7 @@ plugins {
|
||||
id "com.android.library"
|
||||
}
|
||||
|
||||
apply from: "$mppAndroidProject"
|
||||
apply from: "$mppAndroidProjectPresetPath"
|
||||
|
||||
kotlin {
|
||||
sourceSets {
|
||||
|
@ -3,16 +3,15 @@ plugins {
|
||||
id "org.jetbrains.kotlin.plugin.serialization"
|
||||
id "com.android.library"
|
||||
alias(libs.plugins.jb.compose)
|
||||
alias(libs.plugins.kt.jb.compose)
|
||||
}
|
||||
|
||||
apply from: "$mppProjectWithSerializationAndCompose"
|
||||
apply from: "$mppProjectWithSerializationAndComposePresetPath"
|
||||
|
||||
kotlin {
|
||||
sourceSets {
|
||||
androidMain {
|
||||
dependencies {
|
||||
api libs.jb.compose.material3
|
||||
api libs.android.compose.material3
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -29,6 +29,15 @@ allprojects {
|
||||
maven { url "https://maven.pkg.jetbrains.space/public/p/compose/dev" }
|
||||
maven { url "https://nexus.inmo.dev/repository/maven-releases/" }
|
||||
}
|
||||
|
||||
// temporal crutch until legacy tests will be stabled or legacy target will be removed
|
||||
if (it != rootProject.findProject("docs")) {
|
||||
tasks.whenTaskAdded { task ->
|
||||
if(task.name == "jsLegacyBrowserTest" || task.name == "jsLegacyNodeTest") {
|
||||
task.enabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
apply from: "./extensions.gradle"
|
||||
|
@ -1,17 +0,0 @@
|
||||
plugins {
|
||||
id "org.jetbrains.kotlin.multiplatform"
|
||||
id "org.jetbrains.kotlin.plugin.serialization"
|
||||
id "com.android.library"
|
||||
}
|
||||
|
||||
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64Project"
|
||||
|
||||
kotlin {
|
||||
sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
api project(":micro_utils.colors.common")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -4,4 +4,4 @@ plugins {
|
||||
id "com.android.library"
|
||||
}
|
||||
|
||||
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64Project"
|
||||
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64ProjectPresetPath"
|
||||
|
@ -12,30 +12,16 @@ import kotlin.math.floor
|
||||
* * Red (0.5 capacity): `0xff000088u`
|
||||
*
|
||||
* Anyway it is recommended to use
|
||||
*
|
||||
* @param hexaUInt rgba [UInt] in format `0xFFEEBBAA` where FF - red, EE - green, BB - blue` and AA - alpha
|
||||
*/
|
||||
@Serializable
|
||||
@JvmInline
|
||||
value class HEXAColor (
|
||||
val hexaUInt: UInt
|
||||
val uint: UInt
|
||||
) : Comparable<HEXAColor> {
|
||||
/**
|
||||
* @returns [hexaUInt] as a string with format `#FFEEBBAA` where FF - red, EE - green, BB - blue and AA - alpha
|
||||
*/
|
||||
val hexa: String
|
||||
get() = "#${hexaUInt.toString(16).padStart(8, '0')}"
|
||||
|
||||
/**
|
||||
* @returns [hexaUInt] as a string with format `#FFEEBB` where FF - red, EE - green and BB - blue
|
||||
*/
|
||||
get() = "#${uint.toString(16).padStart(8, '0')}"
|
||||
val hex: String
|
||||
get() = hexa.take(7)
|
||||
/**
|
||||
* @returns [hexaUInt] as a string with format `#AAFFEEBB` where AA - alpha, FF - red, EE - green and BB - blue
|
||||
*/
|
||||
val ahex: String
|
||||
get() = "#${a.toString(16).padStart(2, '2')}${hex.drop(1)}"
|
||||
val rgba: String
|
||||
get() = "rgba($r,$g,$b,${aOfOne.toString().take(5)})"
|
||||
val rgb: String
|
||||
@ -44,25 +30,21 @@ value class HEXAColor (
|
||||
get() = "#${r.shortPart()}${g.shortPart()}${b.shortPart()}"
|
||||
val shortHexa: String
|
||||
get() = "$shortHex${a.shortPart()}"
|
||||
val rgbUInt: UInt
|
||||
get() = (hexaUInt / 256u)
|
||||
val rgbInt: Int
|
||||
get() = rgbUInt.toInt()
|
||||
val ahexUInt
|
||||
get() = (a * 0x1000000).toUInt() + rgbUInt
|
||||
get() = (uint shr 2).toInt()
|
||||
|
||||
val r: Int
|
||||
get() = ((hexaUInt and 0xff000000u) / 0x1000000u).toInt()
|
||||
get() = ((uint and 0xff000000u) / 0x1000000u).toInt()
|
||||
val g: Int
|
||||
get() = ((hexaUInt and 0x00ff0000u) / 0x10000u).toInt()
|
||||
get() = ((uint and 0x00ff0000u) / 0x10000u).toInt()
|
||||
val b: Int
|
||||
get() = ((hexaUInt and 0x0000ff00u) / 0x100u).toInt()
|
||||
get() = ((uint and 0x0000ff00u) / 0x100u).toInt()
|
||||
val a: Int
|
||||
get() = ((hexaUInt and 0x000000ffu)).toInt()
|
||||
get() = ((uint and 0x000000ffu)).toInt()
|
||||
val aOfOne: Float
|
||||
get() = a.toFloat() / (0xff)
|
||||
init {
|
||||
require(hexaUInt in 0u ..0xffffffffu)
|
||||
require(uint in 0u ..0xffffffffu)
|
||||
}
|
||||
|
||||
constructor(r: Int, g: Int, b: Int, a: Int) : this(
|
||||
@ -82,7 +64,7 @@ value class HEXAColor (
|
||||
return hexa
|
||||
}
|
||||
|
||||
override fun compareTo(other: HEXAColor): Int = (hexaUInt - other.hexaUInt).coerceIn(Int.MIN_VALUE.toUInt(), Int.MAX_VALUE.toLong().toUInt()).toInt()
|
||||
override fun compareTo(other: HEXAColor): Int = (uint - other.uint).coerceIn(Int.MIN_VALUE.toUInt(), Int.MAX_VALUE.toLong().toUInt()).toInt()
|
||||
|
||||
fun copy(
|
||||
r: Int = this.r,
|
||||
@ -139,21 +121,6 @@ value class HEXAColor (
|
||||
else -> color
|
||||
}.lowercase().toUInt(16).let(::HEXAColor)
|
||||
|
||||
/**
|
||||
* Creates [HEXAColor] from [uint] presume it is in format `0xFFEEBBAA` where FF - red, EE - green, BB - blue` and AA - alpha
|
||||
*/
|
||||
fun fromHexa(uint: UInt) = HEXAColor(uint)
|
||||
|
||||
/**
|
||||
* Creates [HEXAColor] from [uint] presume it is in format `0xAAFFEEBB` where AA - alpha, FF - red, EE - green and BB - blue`
|
||||
*/
|
||||
fun fromAhex(uint: UInt) = HEXAColor(
|
||||
a = ((uint and 0xff000000u) / 0x1000000u).toInt(),
|
||||
r = ((uint and 0x00ff0000u) / 0x10000u).toInt(),
|
||||
g = ((uint and 0x0000ff00u) / 0x100u).toInt(),
|
||||
b = ((uint and 0x000000ffu)).toInt()
|
||||
)
|
||||
|
||||
/**
|
||||
* Parsing color from [color]
|
||||
*
|
||||
|
@ -13,9 +13,6 @@ class HexColorTests {
|
||||
val shortHexa: String,
|
||||
val hex: String,
|
||||
val hexa: String,
|
||||
val ahex: String,
|
||||
val ahexUInt: UInt,
|
||||
val rgbUInt: UInt,
|
||||
val rgb: String,
|
||||
val rgba: String,
|
||||
val r: Int,
|
||||
@ -27,14 +24,11 @@ class HexColorTests {
|
||||
val testColors: List<TestColor>
|
||||
get() = listOf(
|
||||
TestColor(
|
||||
color = HEXAColor(hexaUInt = 0xff0000ffu),
|
||||
color = HEXAColor(uint = 0xff0000ffu),
|
||||
shortHex = "#f00",
|
||||
shortHexa = "#f00f",
|
||||
hex = "#ff0000",
|
||||
hexa = "#ff0000ff",
|
||||
ahex = "#ffff0000",
|
||||
ahexUInt = 0xffff0000u,
|
||||
rgbUInt = 0xff0000u,
|
||||
rgb = "rgb(255,0,0)",
|
||||
rgba = "rgba(255,0,0,1.0)",
|
||||
r = 0xff,
|
||||
@ -44,14 +38,11 @@ class HexColorTests {
|
||||
"rgba(255,0,0,1)",
|
||||
),
|
||||
TestColor(
|
||||
color = HEXAColor(hexaUInt = 0x00ff00ffu),
|
||||
color = HEXAColor(uint = 0x00ff00ffu),
|
||||
shortHex = "#0f0",
|
||||
shortHexa = "#0f0f",
|
||||
hex = "#00ff00",
|
||||
hexa = "#00ff00ff",
|
||||
ahex = "#ff00ff00",
|
||||
ahexUInt = 0xff00ff00u,
|
||||
rgbUInt = 0x00ff00u,
|
||||
rgb = "rgb(0,255,0)",
|
||||
rgba = "rgba(0,255,0,1.0)",
|
||||
r = 0x00,
|
||||
@ -66,9 +57,6 @@ class HexColorTests {
|
||||
shortHexa = "#00ff",
|
||||
hex = "#0000ff",
|
||||
hexa = "#0000ffff",
|
||||
ahex = "#ff0000ff",
|
||||
ahexUInt = 0xff0000ffu,
|
||||
rgbUInt = 0x0000ffu,
|
||||
rgb = "rgb(0,0,255)",
|
||||
rgba = "rgba(0,0,255,1.0)",
|
||||
r = 0x00,
|
||||
@ -83,9 +71,6 @@ class HexColorTests {
|
||||
shortHexa = "#f008",
|
||||
hex = "#ff0000",
|
||||
hexa = "#ff000088",
|
||||
ahex = "#88ff0000",
|
||||
ahexUInt = 0x88ff0000u,
|
||||
rgbUInt = 0xff0000u,
|
||||
rgb = "rgb(255,0,0)",
|
||||
rgba = "rgba(255,0,0,0.533)",
|
||||
r = 0xff,
|
||||
@ -99,9 +84,6 @@ class HexColorTests {
|
||||
shortHexa = "#0f08",
|
||||
hex = "#00ff00",
|
||||
hexa = "#00ff0088",
|
||||
ahex = "#8800ff00",
|
||||
ahexUInt = 0x8800ff00u,
|
||||
rgbUInt = 0x00ff00u,
|
||||
rgb = "rgb(0,255,0)",
|
||||
rgba = "rgba(0,255,0,0.533)",
|
||||
r = 0x00,
|
||||
@ -115,9 +97,6 @@ class HexColorTests {
|
||||
shortHexa = "#00f8",
|
||||
hex = "#0000ff",
|
||||
hexa = "#0000ff88",
|
||||
ahex = "#880000ff",
|
||||
ahexUInt = 0x880000ffu,
|
||||
rgbUInt = 0x0000ffu,
|
||||
rgb = "rgb(0,0,255)",
|
||||
rgba = "rgba(0,0,255,0.533)",
|
||||
r = 0x00,
|
||||
@ -131,9 +110,6 @@ class HexColorTests {
|
||||
shortHexa = "#f002",
|
||||
hex = "#ff0000",
|
||||
hexa = "#ff000022",
|
||||
ahex = "#22ff0000",
|
||||
ahexUInt = 0x22ff0000u,
|
||||
rgbUInt = 0xff0000u,
|
||||
rgb = "rgb(255,0,0)",
|
||||
rgba = "rgba(255,0,0,0.133)",
|
||||
r = 0xff,
|
||||
@ -147,9 +123,6 @@ class HexColorTests {
|
||||
shortHexa = "#0f02",
|
||||
hex = "#00ff00",
|
||||
hexa = "#00ff0022",
|
||||
ahex = "#2200ff00",
|
||||
ahexUInt = 0x2200ff00u,
|
||||
rgbUInt = 0x00ff00u,
|
||||
rgb = "rgb(0,255,0)",
|
||||
rgba = "rgba(0,255,0,0.133)",
|
||||
r = 0x00,
|
||||
@ -163,9 +136,6 @@ class HexColorTests {
|
||||
shortHexa = "#00f2",
|
||||
hex = "#0000ff",
|
||||
hexa = "#0000ff22",
|
||||
ahex = "#220000ff",
|
||||
ahexUInt = 0x220000ffu,
|
||||
rgbUInt = 0x0000ffu,
|
||||
rgb = "rgb(0,0,255)",
|
||||
rgba = "rgba(0,0,255,0.133)",
|
||||
r = 0x00,
|
||||
@ -180,9 +150,6 @@ class HexColorTests {
|
||||
testColors.forEach {
|
||||
assertEquals(it.hex, it.color.hex)
|
||||
assertEquals(it.hexa, it.color.hexa)
|
||||
assertEquals(it.ahex, it.color.ahex)
|
||||
assertEquals(it.rgbUInt, it.color.rgbUInt)
|
||||
assertEquals(it.ahexUInt, it.color.ahexUInt)
|
||||
assertEquals(it.shortHex, it.color.shortHex)
|
||||
assertEquals(it.shortHexa, it.color.shortHexa)
|
||||
assertEquals(it.rgb, it.color.rgb)
|
||||
@ -191,7 +158,6 @@ class HexColorTests {
|
||||
assertEquals(it.g, it.color.g)
|
||||
assertEquals(it.b, it.color.b)
|
||||
assertEquals(it.a, it.color.a)
|
||||
assertEquals(it.color, HEXAColor.fromAhex(it.ahexUInt))
|
||||
}
|
||||
}
|
||||
|
||||
@ -201,7 +167,7 @@ class HexColorTests {
|
||||
assertEquals(it.color.copy(aOfOne = 1f), HEXAColor.parseStringColor(it.hex))
|
||||
assertEquals(it.color, HEXAColor.parseStringColor(it.hexa))
|
||||
assertEquals(it.color.copy(aOfOne = 1f), HEXAColor.parseStringColor(it.rgb))
|
||||
assertTrue(it.color.hexaUInt.toInt() - HEXAColor.parseStringColor(it.rgba).hexaUInt.toInt() in -0x1 .. 0x1, )
|
||||
assertTrue(it.color.uint.toInt() - HEXAColor.parseStringColor(it.rgba).uint.toInt() in -0x1 .. 0x1, )
|
||||
assertEquals(it.color.copy(aOfOne = 1f), HEXAColor.parseStringColor(it.shortHex))
|
||||
assertEquals(it.color.copy(a = floor(it.color.a.toFloat() / 16).toInt() * 0x10), HEXAColor.parseStringColor(it.shortHexa))
|
||||
}
|
||||
|
@ -1,298 +0,0 @@
|
||||
package dev.inmo.micro_utils.colors
|
||||
|
||||
import dev.inmo.micro_utils.colors.common.HEXAColor
|
||||
|
||||
val HEXAColor.Companion.aliceblue
|
||||
get() = HEXAColor(0xF0F8FFFFu)
|
||||
val HEXAColor.Companion.antiquewhite
|
||||
get() = HEXAColor(0xFAEBD7FFu)
|
||||
val HEXAColor.Companion.aqua
|
||||
get() = HEXAColor(0x00FFFFFFu)
|
||||
val HEXAColor.Companion.aquamarine
|
||||
get() = HEXAColor(0x7FFFD4FFu)
|
||||
val HEXAColor.Companion.azure
|
||||
get() = HEXAColor(0xF0FFFFFFu)
|
||||
val HEXAColor.Companion.beige
|
||||
get() = HEXAColor(0xF5F5DCFFu)
|
||||
val HEXAColor.Companion.bisque
|
||||
get() = HEXAColor(0xFFE4C4FFu)
|
||||
val HEXAColor.Companion.black
|
||||
get() = HEXAColor(0x000000FFu)
|
||||
val HEXAColor.Companion.blanchedalmond
|
||||
get() = HEXAColor(0xFFEBCDFFu)
|
||||
val HEXAColor.Companion.blue
|
||||
get() = HEXAColor(0x0000FFFFu)
|
||||
val HEXAColor.Companion.blueviolet
|
||||
get() = HEXAColor(0x8A2BE2FFu)
|
||||
val HEXAColor.Companion.brown
|
||||
get() = HEXAColor(0xA52A2AFFu)
|
||||
val HEXAColor.Companion.burlywood
|
||||
get() = HEXAColor(0xDEB887FFu)
|
||||
val HEXAColor.Companion.cadetblue
|
||||
get() = HEXAColor(0x5F9EA0FFu)
|
||||
val HEXAColor.Companion.chartreuse
|
||||
get() = HEXAColor(0x7FFF00FFu)
|
||||
val HEXAColor.Companion.chocolate
|
||||
get() = HEXAColor(0xD2691EFFu)
|
||||
val HEXAColor.Companion.coral
|
||||
get() = HEXAColor(0xFF7F50FFu)
|
||||
val HEXAColor.Companion.cornflowerblue
|
||||
get() = HEXAColor(0x6495EDFFu)
|
||||
val HEXAColor.Companion.cornsilk
|
||||
get() = HEXAColor(0xFFF8DCFFu)
|
||||
val HEXAColor.Companion.crimson
|
||||
get() = HEXAColor(0xDC143CFFu)
|
||||
val HEXAColor.Companion.cyan
|
||||
get() = HEXAColor(0x00FFFFFFu)
|
||||
val HEXAColor.Companion.darkblue
|
||||
get() = HEXAColor(0x00008BFFu)
|
||||
val HEXAColor.Companion.darkcyan
|
||||
get() = HEXAColor(0x008B8BFFu)
|
||||
val HEXAColor.Companion.darkgoldenrod
|
||||
get() = HEXAColor(0xB8860BFFu)
|
||||
val HEXAColor.Companion.darkgray
|
||||
get() = HEXAColor(0xA9A9A9FFu)
|
||||
val HEXAColor.Companion.darkgreen
|
||||
get() = HEXAColor(0x006400FFu)
|
||||
val HEXAColor.Companion.darkgrey
|
||||
get() = HEXAColor(0xA9A9A9FFu)
|
||||
val HEXAColor.Companion.darkkhaki
|
||||
get() = HEXAColor(0xBDB76BFFu)
|
||||
val HEXAColor.Companion.darkmagenta
|
||||
get() = HEXAColor(0x8B008BFFu)
|
||||
val HEXAColor.Companion.darkolivegreen
|
||||
get() = HEXAColor(0x556B2FFFu)
|
||||
val HEXAColor.Companion.darkorange
|
||||
get() = HEXAColor(0xFF8C00FFu)
|
||||
val HEXAColor.Companion.darkorchid
|
||||
get() = HEXAColor(0x9932CCFFu)
|
||||
val HEXAColor.Companion.darkred
|
||||
get() = HEXAColor(0x8B0000FFu)
|
||||
val HEXAColor.Companion.darksalmon
|
||||
get() = HEXAColor(0xE9967AFFu)
|
||||
val HEXAColor.Companion.darkseagreen
|
||||
get() = HEXAColor(0x8FBC8FFFu)
|
||||
val HEXAColor.Companion.darkslateblue
|
||||
get() = HEXAColor(0x483D8BFFu)
|
||||
val HEXAColor.Companion.darkslategray
|
||||
get() = HEXAColor(0x2F4F4FFFu)
|
||||
val HEXAColor.Companion.darkslategrey
|
||||
get() = HEXAColor(0x2F4F4FFFu)
|
||||
val HEXAColor.Companion.darkturquoise
|
||||
get() = HEXAColor(0x00CED1FFu)
|
||||
val HEXAColor.Companion.darkviolet
|
||||
get() = HEXAColor(0x9400D3FFu)
|
||||
val HEXAColor.Companion.deeppink
|
||||
get() = HEXAColor(0xFF1493FFu)
|
||||
val HEXAColor.Companion.deepskyblue
|
||||
get() = HEXAColor(0x00BFFFFFu)
|
||||
val HEXAColor.Companion.dimgray
|
||||
get() = HEXAColor(0x696969FFu)
|
||||
val HEXAColor.Companion.dimgrey
|
||||
get() = HEXAColor(0x696969FFu)
|
||||
val HEXAColor.Companion.dodgerblue
|
||||
get() = HEXAColor(0x1E90FFFFu)
|
||||
val HEXAColor.Companion.firebrick
|
||||
get() = HEXAColor(0xB22222FFu)
|
||||
val HEXAColor.Companion.floralwhite
|
||||
get() = HEXAColor(0xFFFAF0FFu)
|
||||
val HEXAColor.Companion.forestgreen
|
||||
get() = HEXAColor(0x228B22FFu)
|
||||
val HEXAColor.Companion.fuchsia
|
||||
get() = HEXAColor(0xFF00FFFFu)
|
||||
val HEXAColor.Companion.gainsboro
|
||||
get() = HEXAColor(0xDCDCDCFFu)
|
||||
val HEXAColor.Companion.ghostwhite
|
||||
get() = HEXAColor(0xF8F8FFFFu)
|
||||
val HEXAColor.Companion.gold
|
||||
get() = HEXAColor(0xFFD700FFu)
|
||||
val HEXAColor.Companion.goldenrod
|
||||
get() = HEXAColor(0xDAA520FFu)
|
||||
val HEXAColor.Companion.gray
|
||||
get() = HEXAColor(0x808080FFu)
|
||||
val HEXAColor.Companion.green
|
||||
get() = HEXAColor(0x008000FFu)
|
||||
val HEXAColor.Companion.greenyellow
|
||||
get() = HEXAColor(0xADFF2FFFu)
|
||||
val HEXAColor.Companion.grey
|
||||
get() = HEXAColor(0x808080FFu)
|
||||
val HEXAColor.Companion.honeydew
|
||||
get() = HEXAColor(0xF0FFF0FFu)
|
||||
val HEXAColor.Companion.hotpink
|
||||
get() = HEXAColor(0xFF69B4FFu)
|
||||
val HEXAColor.Companion.indianred
|
||||
get() = HEXAColor(0xCD5C5CFFu)
|
||||
val HEXAColor.Companion.indigo
|
||||
get() = HEXAColor(0x4B0082FFu)
|
||||
val HEXAColor.Companion.ivory
|
||||
get() = HEXAColor(0xFFFFF0FFu)
|
||||
val HEXAColor.Companion.khaki
|
||||
get() = HEXAColor(0xF0E68CFFu)
|
||||
val HEXAColor.Companion.lavender
|
||||
get() = HEXAColor(0xE6E6FAFFu)
|
||||
val HEXAColor.Companion.lavenderblush
|
||||
get() = HEXAColor(0xFFF0F5FFu)
|
||||
val HEXAColor.Companion.lawngreen
|
||||
get() = HEXAColor(0x7CFC00FFu)
|
||||
val HEXAColor.Companion.lemonchiffon
|
||||
get() = HEXAColor(0xFFFACDFFu)
|
||||
val HEXAColor.Companion.lightblue
|
||||
get() = HEXAColor(0xADD8E6FFu)
|
||||
val HEXAColor.Companion.lightcoral
|
||||
get() = HEXAColor(0xF08080FFu)
|
||||
val HEXAColor.Companion.lightcyan
|
||||
get() = HEXAColor(0xE0FFFFFFu)
|
||||
val HEXAColor.Companion.lightgoldenrodyellow
|
||||
get() = HEXAColor(0xFAFAD2FFu)
|
||||
val HEXAColor.Companion.lightgray
|
||||
get() = HEXAColor(0xD3D3D3FFu)
|
||||
val HEXAColor.Companion.lightgreen
|
||||
get() = HEXAColor(0x90EE90FFu)
|
||||
val HEXAColor.Companion.lightgrey
|
||||
get() = HEXAColor(0xD3D3D3FFu)
|
||||
val HEXAColor.Companion.lightpink
|
||||
get() = HEXAColor(0xFFB6C1FFu)
|
||||
val HEXAColor.Companion.lightsalmon
|
||||
get() = HEXAColor(0xFFA07AFFu)
|
||||
val HEXAColor.Companion.lightseagreen
|
||||
get() = HEXAColor(0x20B2AAFFu)
|
||||
val HEXAColor.Companion.lightskyblue
|
||||
get() = HEXAColor(0x87CEFAFFu)
|
||||
val HEXAColor.Companion.lightslategray
|
||||
get() = HEXAColor(0x778899FFu)
|
||||
val HEXAColor.Companion.lightslategrey
|
||||
get() = HEXAColor(0x778899FFu)
|
||||
val HEXAColor.Companion.lightsteelblue
|
||||
get() = HEXAColor(0xB0C4DEFFu)
|
||||
val HEXAColor.Companion.lightyellow
|
||||
get() = HEXAColor(0xFFFFE0FFu)
|
||||
val HEXAColor.Companion.lime
|
||||
get() = HEXAColor(0x00FF00FFu)
|
||||
val HEXAColor.Companion.limegreen
|
||||
get() = HEXAColor(0x32CD32FFu)
|
||||
val HEXAColor.Companion.linen
|
||||
get() = HEXAColor(0xFAF0E6FFu)
|
||||
val HEXAColor.Companion.magenta
|
||||
get() = HEXAColor(0xFF00FFFFu)
|
||||
val HEXAColor.Companion.maroon
|
||||
get() = HEXAColor(0x800000FFu)
|
||||
val HEXAColor.Companion.mediumaquamarine
|
||||
get() = HEXAColor(0x66CDAAFFu)
|
||||
val HEXAColor.Companion.mediumblue
|
||||
get() = HEXAColor(0x0000CDFFu)
|
||||
val HEXAColor.Companion.mediumorchid
|
||||
get() = HEXAColor(0xBA55D3FFu)
|
||||
val HEXAColor.Companion.mediumpurple
|
||||
get() = HEXAColor(0x9370DBFFu)
|
||||
val HEXAColor.Companion.mediumseagreen
|
||||
get() = HEXAColor(0x3CB371FFu)
|
||||
val HEXAColor.Companion.mediumslateblue
|
||||
get() = HEXAColor(0x7B68EEFFu)
|
||||
val HEXAColor.Companion.mediumspringgreen
|
||||
get() = HEXAColor(0x00FA9AFFu)
|
||||
val HEXAColor.Companion.mediumturquoise
|
||||
get() = HEXAColor(0x48D1CCFFu)
|
||||
val HEXAColor.Companion.mediumvioletred
|
||||
get() = HEXAColor(0xC71585FFu)
|
||||
val HEXAColor.Companion.midnightblue
|
||||
get() = HEXAColor(0x191970FFu)
|
||||
val HEXAColor.Companion.mintcream
|
||||
get() = HEXAColor(0xF5FFFAFFu)
|
||||
val HEXAColor.Companion.mistyrose
|
||||
get() = HEXAColor(0xFFE4E1FFu)
|
||||
val HEXAColor.Companion.moccasin
|
||||
get() = HEXAColor(0xFFE4B5FFu)
|
||||
val HEXAColor.Companion.navajowhite
|
||||
get() = HEXAColor(0xFFDEADFFu)
|
||||
val HEXAColor.Companion.navy
|
||||
get() = HEXAColor(0x000080FFu)
|
||||
val HEXAColor.Companion.oldlace
|
||||
get() = HEXAColor(0xFDF5E6FFu)
|
||||
val HEXAColor.Companion.olive
|
||||
get() = HEXAColor(0x808000FFu)
|
||||
val HEXAColor.Companion.olivedrab
|
||||
get() = HEXAColor(0x6B8E23FFu)
|
||||
val HEXAColor.Companion.orange
|
||||
get() = HEXAColor(0xFFA500FFu)
|
||||
val HEXAColor.Companion.orangered
|
||||
get() = HEXAColor(0xFF4500FFu)
|
||||
val HEXAColor.Companion.orchid
|
||||
get() = HEXAColor(0xDA70D6FFu)
|
||||
val HEXAColor.Companion.palegoldenrod
|
||||
get() = HEXAColor(0xEEE8AAFFu)
|
||||
val HEXAColor.Companion.palegreen
|
||||
get() = HEXAColor(0x98FB98FFu)
|
||||
val HEXAColor.Companion.paleturquoise
|
||||
get() = HEXAColor(0xAFEEEEFFu)
|
||||
val HEXAColor.Companion.palevioletred
|
||||
get() = HEXAColor(0xDB7093FFu)
|
||||
val HEXAColor.Companion.papayawhip
|
||||
get() = HEXAColor(0xFFEFD5FFu)
|
||||
val HEXAColor.Companion.peachpuff
|
||||
get() = HEXAColor(0xFFDAB9FFu)
|
||||
val HEXAColor.Companion.peru
|
||||
get() = HEXAColor(0xCD853FFFu)
|
||||
val HEXAColor.Companion.pink
|
||||
get() = HEXAColor(0xFFC0CBFFu)
|
||||
val HEXAColor.Companion.plum
|
||||
get() = HEXAColor(0xDDA0DDFFu)
|
||||
val HEXAColor.Companion.powderblue
|
||||
get() = HEXAColor(0xB0E0E6FFu)
|
||||
val HEXAColor.Companion.purple
|
||||
get() = HEXAColor(0x800080FFu)
|
||||
val HEXAColor.Companion.red
|
||||
get() = HEXAColor(0xFF0000FFu)
|
||||
val HEXAColor.Companion.rosybrown
|
||||
get() = HEXAColor(0xBC8F8FFFu)
|
||||
val HEXAColor.Companion.royalblue
|
||||
get() = HEXAColor(0x4169E1FFu)
|
||||
val HEXAColor.Companion.saddlebrown
|
||||
get() = HEXAColor(0x8B4513FFu)
|
||||
val HEXAColor.Companion.salmon
|
||||
get() = HEXAColor(0xFA8072FFu)
|
||||
val HEXAColor.Companion.sandybrown
|
||||
get() = HEXAColor(0xF4A460FFu)
|
||||
val HEXAColor.Companion.seagreen
|
||||
get() = HEXAColor(0x2E8B57FFu)
|
||||
val HEXAColor.Companion.seashell
|
||||
get() = HEXAColor(0xFFF5EEFFu)
|
||||
val HEXAColor.Companion.sienna
|
||||
get() = HEXAColor(0xA0522DFFu)
|
||||
val HEXAColor.Companion.silver
|
||||
get() = HEXAColor(0xC0C0C0FFu)
|
||||
val HEXAColor.Companion.skyblue
|
||||
get() = HEXAColor(0x87CEEBFFu)
|
||||
val HEXAColor.Companion.slateblue
|
||||
get() = HEXAColor(0x6A5ACDFFu)
|
||||
val HEXAColor.Companion.slategray
|
||||
get() = HEXAColor(0x708090FFu)
|
||||
val HEXAColor.Companion.slategrey
|
||||
get() = HEXAColor(0x708090FFu)
|
||||
val HEXAColor.Companion.snow
|
||||
get() = HEXAColor(0xFFFAFAFFu)
|
||||
val HEXAColor.Companion.springgreen
|
||||
get() = HEXAColor(0x00FF7FFFu)
|
||||
val HEXAColor.Companion.steelblue
|
||||
get() = HEXAColor(0x4682B4FFu)
|
||||
val HEXAColor.Companion.tan
|
||||
get() = HEXAColor(0xD2B48CFFu)
|
||||
val HEXAColor.Companion.teal
|
||||
get() = HEXAColor(0x008080FFu)
|
||||
val HEXAColor.Companion.thistle
|
||||
get() = HEXAColor(0xD8BFD8FFu)
|
||||
val HEXAColor.Companion.tomato
|
||||
get() = HEXAColor(0xFF6347FFu)
|
||||
val HEXAColor.Companion.turquoise
|
||||
get() = HEXAColor(0x40E0D0FFu)
|
||||
val HEXAColor.Companion.violet
|
||||
get() = HEXAColor(0xEE82EEFFu)
|
||||
val HEXAColor.Companion.wheat
|
||||
get() = HEXAColor(0xF5DEB3FFu)
|
||||
val HEXAColor.Companion.white
|
||||
get() = HEXAColor(0xFFFFFFFFu)
|
||||
val HEXAColor.Companion.whitesmoke
|
||||
get() = HEXAColor(0xF5F5F5FFu)
|
||||
val HEXAColor.Companion.yellow
|
||||
get() = HEXAColor(0xFFFF00FFu)
|
||||
val HEXAColor.Companion.yellowgreen
|
||||
get() = HEXAColor(0x9ACD32FFu)
|
@ -4,15 +4,10 @@ plugins {
|
||||
id "com.android.library"
|
||||
}
|
||||
|
||||
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64Project"
|
||||
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64ProjectPresetPath"
|
||||
|
||||
kotlin {
|
||||
sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
api libs.klock
|
||||
}
|
||||
}
|
||||
jvmMain {
|
||||
dependencies {
|
||||
api project(":micro_utils.coroutines")
|
||||
@ -20,11 +15,23 @@ kotlin {
|
||||
}
|
||||
androidMain {
|
||||
dependencies {
|
||||
api project(":micro_utils.coroutines")
|
||||
api libs.android.fragment
|
||||
}
|
||||
dependsOn jvmMain
|
||||
}
|
||||
|
||||
nativeMain {
|
||||
linuxX64Main {
|
||||
dependencies {
|
||||
api libs.okio
|
||||
}
|
||||
}
|
||||
mingwX64Main {
|
||||
dependencies {
|
||||
api libs.okio
|
||||
}
|
||||
}
|
||||
linuxArm64Main {
|
||||
dependencies {
|
||||
api libs.okio
|
||||
}
|
||||
|
@ -3,10 +3,9 @@ plugins {
|
||||
id "org.jetbrains.kotlin.plugin.serialization"
|
||||
id "com.android.library"
|
||||
alias(libs.plugins.jb.compose)
|
||||
alias(libs.plugins.kt.jb.compose)
|
||||
}
|
||||
|
||||
apply from: "$mppComposeJvmJsAndroidLinuxMingwLinuxArm64Project"
|
||||
apply from: "$mppProjectWithSerializationAndComposePresetPath"
|
||||
|
||||
kotlin {
|
||||
sourceSets {
|
||||
|
@ -1,19 +0,0 @@
|
||||
package dev.inmo.micro_utils.common.compose
|
||||
|
||||
import org.jetbrains.compose.web.dom.AttrBuilderContext
|
||||
import org.w3c.dom.Element
|
||||
|
||||
operator fun <T : Element> AttrBuilderContext<T>?.plus(
|
||||
other: AttrBuilderContext<T>?
|
||||
) = when (this) {
|
||||
null -> other ?: {}
|
||||
else -> when (other) {
|
||||
null -> this ?: {}
|
||||
else -> {
|
||||
{
|
||||
invoke(this)
|
||||
other(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
package dev.inmo.micro_utils.common.compose
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.DisposableEffectResult
|
||||
import androidx.compose.runtime.DisposableEffectScope
|
||||
import org.jetbrains.compose.web.attributes.AttrsScope
|
||||
import org.jetbrains.compose.web.dom.ElementScope
|
||||
import org.w3c.dom.Element
|
||||
|
||||
/**
|
||||
* This function must be called in the context of your tag content. It works like default [AttrsScope.ref],
|
||||
* but able to be used several times. Uses [DisposableEffect] under the hood
|
||||
*/
|
||||
@Composable
|
||||
fun <T : Element> ElementScope<T>.ref(
|
||||
block: DisposableEffectScope.(T) -> DisposableEffectResult
|
||||
) {
|
||||
DisposableEffect(0) {
|
||||
block(scopeElement)
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
package dev.inmo.micro_utils.common.compose
|
||||
|
||||
import org.jetbrains.compose.web.dom.AttrBuilderContext
|
||||
|
||||
fun tagClasses(vararg classnames: String): AttrBuilderContext<*> = {
|
||||
classes(*classnames)
|
||||
}
|
||||
|
||||
fun tagId(id: String): AttrBuilderContext<*> = {
|
||||
id(id)
|
||||
}
|
1
common/src/androidMain/AndroidManifest.xml
Normal file
1
common/src/androidMain/AndroidManifest.xml
Normal file
@ -0,0 +1 @@
|
||||
<manifest/>
|
@ -1,25 +0,0 @@
|
||||
package dev.inmo.micro_utils.common
|
||||
|
||||
import korlibs.time.DateTime
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.Serializer
|
||||
import kotlinx.serialization.builtins.serializer
|
||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
|
||||
/**
|
||||
* Serializes [DateTime] as its raw [DateTime.unixMillis] and deserializes in the same way
|
||||
*/
|
||||
object DateTimeSerializer : KSerializer<DateTime> {
|
||||
override val descriptor: SerialDescriptor
|
||||
get() = Double.serializer().descriptor
|
||||
|
||||
override fun deserialize(decoder: Decoder): DateTime {
|
||||
return DateTime(decoder.decodeDouble())
|
||||
}
|
||||
|
||||
override fun serialize(encoder: Encoder, value: DateTime) {
|
||||
encoder.encodeDouble(value.unixMillis)
|
||||
}
|
||||
}
|
@ -8,7 +8,7 @@ private inline fun <T> getObject(
|
||||
additional: MutableList<T>,
|
||||
iterator: Iterator<T>
|
||||
): T? = when {
|
||||
additional.isNotEmpty() -> additional.removeAt(0)
|
||||
additional.isNotEmpty() -> additional.removeFirst()
|
||||
iterator.hasNext() -> iterator.next()
|
||||
else -> null
|
||||
}
|
||||
|
@ -1,13 +0,0 @@
|
||||
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
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
package dev.inmo.micro_utils.common
|
||||
|
||||
import kotlinx.browser.window
|
||||
import org.w3c.files.Blob
|
||||
import org.w3c.files.BlobPropertyBag
|
||||
import kotlin.js.json
|
||||
|
||||
external class ClipboardItem(data: dynamic)
|
||||
|
||||
inline fun Blob.convertToClipboardItem(): ClipboardItem {
|
||||
val itemData: dynamic = json(this.type to this)
|
||||
return ClipboardItem(itemData)
|
||||
}
|
||||
|
||||
suspend fun copyImageURLToClipboard(imageUrl: String): Boolean {
|
||||
return runCatching {
|
||||
val response = window.fetch(imageUrl).await()
|
||||
val blob = response.blob().await()
|
||||
val data = arrayOf(
|
||||
Blob(
|
||||
arrayOf(blob),
|
||||
BlobPropertyBag("image/png")
|
||||
).convertToClipboardItem()
|
||||
).asDynamic()
|
||||
window.navigator.clipboard.write(data)
|
||||
}.onFailure {
|
||||
it.printStackTrace()
|
||||
}.isSuccess
|
||||
}
|
36
common/src/linuxX64Main/kotlin/ActualMPPFile.kt
Normal file
36
common/src/linuxX64Main/kotlin/ActualMPPFile.kt
Normal file
@ -0,0 +1,36 @@
|
||||
package dev.inmo.micro_utils.common
|
||||
|
||||
import okio.FileSystem
|
||||
import okio.Path
|
||||
import okio.use
|
||||
|
||||
actual typealias MPPFile = Path
|
||||
|
||||
/**
|
||||
* @suppress
|
||||
*/
|
||||
actual val MPPFile.filename: FileName
|
||||
get() = FileName(toString())
|
||||
/**
|
||||
* @suppress
|
||||
*/
|
||||
actual val MPPFile.filesize: Long
|
||||
get() = FileSystem.SYSTEM.openReadOnly(this).use {
|
||||
it.size()
|
||||
}
|
||||
/**
|
||||
* @suppress
|
||||
*/
|
||||
actual val MPPFile.bytesAllocatorSync: ByteArrayAllocator
|
||||
get() = {
|
||||
FileSystem.SYSTEM.read(this) {
|
||||
readByteArray()
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @suppress
|
||||
*/
|
||||
actual val MPPFile.bytesAllocator: SuspendByteArrayAllocator
|
||||
get() = {
|
||||
bytesAllocatorSync()
|
||||
}
|
25
common/src/linuxX64Main/kotlin/fixed.kt
Normal file
25
common/src/linuxX64Main/kotlin/fixed.kt
Normal file
@ -0,0 +1,25 @@
|
||||
package dev.inmo.micro_utils.common
|
||||
|
||||
import kotlinx.cinterop.*
|
||||
import platform.posix.snprintf
|
||||
import platform.posix.sprintf
|
||||
|
||||
@OptIn(ExperimentalForeignApi::class)
|
||||
actual fun Float.fixed(signs: Int): Float {
|
||||
return memScoped {
|
||||
val buff = allocArray<ByteVar>(Float.SIZE_BYTES * 2)
|
||||
|
||||
sprintf(buff, "%.${signs}f", this@fixed)
|
||||
buff.toKString().toFloat()
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalForeignApi::class)
|
||||
actual fun Double.fixed(signs: Int): Double {
|
||||
return memScoped {
|
||||
val buff = allocArray<ByteVar>(Double.SIZE_BYTES * 2)
|
||||
|
||||
sprintf(buff, "%.${signs}f", this@fixed)
|
||||
buff.toKString().toDouble()
|
||||
}
|
||||
}
|
36
common/src/mingwX64Main/kotlin/ActualMPPFile.kt
Normal file
36
common/src/mingwX64Main/kotlin/ActualMPPFile.kt
Normal file
@ -0,0 +1,36 @@
|
||||
package dev.inmo.micro_utils.common
|
||||
|
||||
import okio.FileSystem
|
||||
import okio.Path
|
||||
import okio.use
|
||||
|
||||
actual typealias MPPFile = Path
|
||||
|
||||
/**
|
||||
* @suppress
|
||||
*/
|
||||
actual val MPPFile.filename: FileName
|
||||
get() = FileName(toString())
|
||||
/**
|
||||
* @suppress
|
||||
*/
|
||||
actual val MPPFile.filesize: Long
|
||||
get() = FileSystem.SYSTEM.openReadOnly(this).use {
|
||||
it.size()
|
||||
}
|
||||
/**
|
||||
* @suppress
|
||||
*/
|
||||
actual val MPPFile.bytesAllocatorSync: ByteArrayAllocator
|
||||
get() = {
|
||||
FileSystem.SYSTEM.read(this) {
|
||||
readByteArray()
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @suppress
|
||||
*/
|
||||
actual val MPPFile.bytesAllocator: SuspendByteArrayAllocator
|
||||
get() = {
|
||||
bytesAllocatorSync()
|
||||
}
|
25
common/src/mingwX64Main/kotlin/fixed.kt
Normal file
25
common/src/mingwX64Main/kotlin/fixed.kt
Normal file
@ -0,0 +1,25 @@
|
||||
package dev.inmo.micro_utils.common
|
||||
|
||||
import kotlinx.cinterop.*
|
||||
import platform.posix.snprintf
|
||||
import platform.posix.sprintf
|
||||
|
||||
@OptIn(ExperimentalForeignApi::class)
|
||||
actual fun Float.fixed(signs: Int): Float {
|
||||
return memScoped {
|
||||
val buff = allocArray<ByteVar>(Float.SIZE_BYTES * 2)
|
||||
|
||||
sprintf(buff, "%.${signs}f", this@fixed)
|
||||
buff.toKString().toFloat()
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalForeignApi::class)
|
||||
actual fun Double.fixed(signs: Int): Double {
|
||||
return memScoped {
|
||||
val buff = allocArray<ByteVar>(Double.SIZE_BYTES * 2)
|
||||
|
||||
sprintf(buff, "%.${signs}f", this@fixed)
|
||||
buff.toKString().toDouble()
|
||||
}
|
||||
}
|
@ -4,7 +4,7 @@ plugins {
|
||||
id "com.android.library"
|
||||
}
|
||||
|
||||
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64Project"
|
||||
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64ProjectPresetPath"
|
||||
|
||||
kotlin {
|
||||
sourceSets {
|
||||
@ -22,6 +22,7 @@ kotlin {
|
||||
dependencies {
|
||||
api libs.kt.coroutines.android
|
||||
}
|
||||
dependsOn(jvmMain)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,10 +3,9 @@ plugins {
|
||||
id "org.jetbrains.kotlin.plugin.serialization"
|
||||
id "com.android.library"
|
||||
alias(libs.plugins.jb.compose)
|
||||
alias(libs.plugins.kt.jb.compose)
|
||||
}
|
||||
|
||||
apply from: "$mppComposeJvmJsAndroidLinuxMingwLinuxArm64Project"
|
||||
apply from: "$mppProjectWithSerializationAndComposePresetPath"
|
||||
|
||||
kotlin {
|
||||
sourceSets {
|
||||
|
1
coroutines/compose/src/androidMain/AndroidManifest.xml
Normal file
1
coroutines/compose/src/androidMain/AndroidManifest.xml
Normal file
@ -0,0 +1 @@
|
||||
<manifest/>
|
@ -0,0 +1,46 @@
|
||||
package dev.inmo.micro_utils.coroutines.compose
|
||||
|
||||
import androidx.compose.runtime.MutableState
|
||||
import dev.inmo.micro_utils.coroutines.SpecialMutableStateFlow
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
||||
/**
|
||||
* This type works like [MutableState], [kotlinx.coroutines.flow.StateFlow] and [kotlinx.coroutines.flow.MutableSharedFlow].
|
||||
* Based on [SpecialMutableStateFlow]
|
||||
*/
|
||||
@Deprecated("Will be removed soon")
|
||||
class FlowState<T>(
|
||||
initial: T,
|
||||
internalScope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
||||
) : MutableState<T>,
|
||||
SpecialMutableStateFlow<T>(initial, internalScope) {
|
||||
private var internalValue: T = initial
|
||||
override var value: T
|
||||
get() = internalValue
|
||||
set(value) {
|
||||
internalValue = value
|
||||
tryEmit(value)
|
||||
}
|
||||
|
||||
override fun onChangeWithoutSync(value: T) {
|
||||
internalValue = value
|
||||
super.onChangeWithoutSync(value)
|
||||
}
|
||||
|
||||
override fun component1(): T = value
|
||||
|
||||
override fun component2(): (T) -> Unit = { tryEmit(it) }
|
||||
|
||||
override fun tryEmit(value: T): Boolean {
|
||||
internalValue = value
|
||||
return super.tryEmit(value)
|
||||
}
|
||||
|
||||
override suspend fun emit(value: T) {
|
||||
internalValue = value
|
||||
super.emit(value)
|
||||
}
|
||||
}
|
||||
|
||||
//fun <T> MutableState<T>.asFlowState(scope: CoroutineScope = CoroutineScope(Dispatchers.Main)) = FlowState(this, scope)
|
@ -1,24 +0,0 @@
|
||||
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 org.jetbrains.annotations.TestOnly
|
||||
import kotlin.test.Test
|
||||
|
||||
class FlowStateTests {
|
||||
@OptIn(ExperimentalTestApi::class)
|
||||
@Test
|
||||
@TestOnly
|
||||
fun simpleTest() = runComposeUiTest {
|
||||
val flowState = SpecialMutableStateFlow(0)
|
||||
setContent {
|
||||
Button({ flowState.value++ }) { Text("Click") }
|
||||
Text(flowState.collectAsState().value.toString())
|
||||
}
|
||||
|
||||
onNodeWithText(0.toString()).assertExists()
|
||||
onNodeWithText("Click").performClick()
|
||||
onNodeWithText(1.toString()).assertExists()
|
||||
}
|
||||
}
|
@ -3,12 +3,6 @@ package dev.inmo.micro_utils.coroutines
|
||||
import kotlinx.coroutines.*
|
||||
import kotlin.coroutines.*
|
||||
|
||||
/**
|
||||
* Trying to [Deferred.await] on all [this] [Deferred]s. The first [Deferred] completed its work will interrupt all
|
||||
* others awaits and, if [cancelOnResult] passed as true (**by default**), will also cancel all the others [Deferred]s
|
||||
*
|
||||
* @param scope Will be used to create [CoroutineScope.LinkedSupervisorScope] and launch joining of all [Job]s in it
|
||||
*/
|
||||
suspend fun <T> Iterable<Deferred<T>>.awaitFirstWithDeferred(
|
||||
scope: CoroutineScope,
|
||||
cancelOnResult: Boolean = true
|
||||
@ -30,45 +24,10 @@ suspend fun <T> Iterable<Deferred<T>>.awaitFirstWithDeferred(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trying to [Deferred.await] on all [this] [Deferred]s. The first [Deferred] completed its work will interrupt all
|
||||
* others awaits and, if [cancelOnResult] passed as true (**by default**), will also cancel all the others [Deferred]s
|
||||
*
|
||||
* @param scope Will be used to create [CoroutineScope.LinkedSupervisorScope] and launch joining of all [Job]s in it
|
||||
*/
|
||||
suspend fun <T> Iterable<Deferred<T>>.awaitFirst(
|
||||
scope: CoroutineScope,
|
||||
cancelOnResult: Boolean = true
|
||||
): T = awaitFirstWithDeferred(scope, cancelOnResult).second
|
||||
|
||||
/**
|
||||
* Trying to [Deferred.await] on all [this] [Deferred]s. The first [Deferred] completed its work will interrupt all
|
||||
* others awaits and, if [cancelOthers] passed as true (**by default**), will also cancel all the others [Deferred]s
|
||||
*
|
||||
* Creates [CoroutineScope] using [coroutineContext] for internal purposes
|
||||
*/
|
||||
suspend fun <T> Iterable<Deferred<T>>.awaitFirst(
|
||||
cancelOthers: Boolean = true
|
||||
): T = awaitFirst(CoroutineScope(coroutineContext), cancelOthers)
|
||||
|
||||
/**
|
||||
* Trying to [Deferred.await] on all [deferreds]. The first [Deferred] completed its work will interrupt all
|
||||
* others awaits and, if [cancelOnResult] passed as true (**by default**), will also cancel all the others [deferreds]
|
||||
*
|
||||
* @param scope Will be used to create [CoroutineScope.LinkedSupervisorScope] and launch joining of all [Job]s in it
|
||||
*/
|
||||
suspend fun <T> awaitFirst(
|
||||
vararg deferreds: Deferred<T>,
|
||||
scope: CoroutineScope,
|
||||
cancelOnResult: Boolean = true
|
||||
): T = deferreds.toList().awaitFirstWithDeferred(scope, cancelOnResult).second
|
||||
/**
|
||||
* Trying to [Deferred.await] on all [deferreds]. The first [Deferred] completed its work will interrupt all
|
||||
* others awaits and, if [cancelOthers] passed as true (**by default**), will also cancel all the others [deferreds]
|
||||
*
|
||||
* Creates [CoroutineScope] using [coroutineContext] for internal purposes
|
||||
*/
|
||||
suspend fun <T> awaitFirst(
|
||||
vararg deferreds: Deferred<T>,
|
||||
cancelOthers: Boolean = true
|
||||
): T = awaitFirst(*deferreds, scope = CoroutineScope(coroutineContext), cancelOnResult = cancelOthers)
|
||||
|
@ -1,44 +0,0 @@
|
||||
package dev.inmo.micro_utils.coroutines
|
||||
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.coroutines.coroutineContext
|
||||
|
||||
typealias ExceptionHandler<T> = suspend (Throwable) -> T
|
||||
|
||||
/**
|
||||
* This instance will be used in all calls of [safely] where exception handler has not been passed
|
||||
*/
|
||||
var defaultSafelyExceptionHandler: ExceptionHandler<Nothing> = { throw it }
|
||||
|
||||
/**
|
||||
* This instance will be used in all calls of [safelyWithoutExceptions] as an exception handler for [safely] call
|
||||
*/
|
||||
var defaultSafelyWithoutExceptionHandler: ExceptionHandler<Unit> = {
|
||||
try {
|
||||
defaultSafelyExceptionHandler(it)
|
||||
} catch (e: Throwable) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This key can (and will) be used to get [ContextSafelyExceptionHandler] from [coroutineContext] of suspend functions
|
||||
* and in [ContextSafelyExceptionHandler] for defining of its [CoroutineContext.Element.key]
|
||||
*
|
||||
* @see safelyWithContextExceptionHandler
|
||||
* @see ContextSafelyExceptionHandler
|
||||
*/
|
||||
object ContextSafelyExceptionHandlerKey : CoroutineContext.Key<ContextSafelyExceptionHandler>
|
||||
|
||||
/**
|
||||
* [ExceptionHandler] wrapper which was created to make possible to use [handler] across all coroutines calls
|
||||
*
|
||||
* @see safelyWithContextExceptionHandler
|
||||
* @see ContextSafelyExceptionHandlerKey
|
||||
*/
|
||||
class ContextSafelyExceptionHandler(
|
||||
val handler: ExceptionHandler<Unit>
|
||||
) : CoroutineContext.Element {
|
||||
override val key: CoroutineContext.Key<*>
|
||||
get() = ContextSafelyExceptionHandlerKey
|
||||
}
|
@ -16,8 +16,8 @@ class DoWithFirstBuilder<T>(
|
||||
operator fun plus(block: suspend CoroutineScope.() -> T) {
|
||||
deferreds.add(scope.async(start = CoroutineStart.LAZY, block = block))
|
||||
}
|
||||
fun add(block: suspend CoroutineScope.() -> T) = plus(block)
|
||||
fun include(block: suspend CoroutineScope.() -> T) = plus(block)
|
||||
inline fun add(noinline block: suspend CoroutineScope.() -> T) = plus(block)
|
||||
inline fun include(noinline block: suspend CoroutineScope.() -> T) = plus(block)
|
||||
|
||||
fun build() = deferreds.toList()
|
||||
}
|
||||
|
@ -85,32 +85,32 @@ fun <T, M> Flow<T>.subscribeAsync(
|
||||
return job
|
||||
}
|
||||
|
||||
fun <T, M> Flow<T>.subscribeSafelyAsync(
|
||||
inline fun <T, M> Flow<T>.subscribeSafelyAsync(
|
||||
scope: CoroutineScope,
|
||||
markerFactory: suspend (T) -> M,
|
||||
onException: ExceptionHandler<Unit> = defaultSafelyExceptionHandler,
|
||||
block: suspend (T) -> Unit
|
||||
noinline markerFactory: suspend (T) -> M,
|
||||
noinline onException: ExceptionHandler<Unit> = defaultSafelyExceptionHandler,
|
||||
noinline block: suspend (T) -> Unit
|
||||
) = subscribeAsync(scope, markerFactory) {
|
||||
safely(onException) {
|
||||
block(it)
|
||||
}
|
||||
}
|
||||
|
||||
fun <T, M> Flow<T>.subscribeSafelyWithoutExceptionsAsync(
|
||||
inline fun <T, M> Flow<T>.subscribeSafelyWithoutExceptionsAsync(
|
||||
scope: CoroutineScope,
|
||||
markerFactory: suspend (T) -> M,
|
||||
onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
|
||||
block: suspend (T) -> Unit
|
||||
noinline markerFactory: suspend (T) -> M,
|
||||
noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
|
||||
noinline block: suspend (T) -> Unit
|
||||
) = subscribeAsync(scope, markerFactory) {
|
||||
safelyWithoutExceptions(onException) {
|
||||
block(it)
|
||||
}
|
||||
}
|
||||
|
||||
fun <T, M> Flow<T>.subscribeSafelySkippingExceptionsAsync(
|
||||
inline fun <T, M> Flow<T>.subscribeSafelySkippingExceptionsAsync(
|
||||
scope: CoroutineScope,
|
||||
markerFactory: suspend (T) -> M,
|
||||
block: suspend (T) -> Unit
|
||||
noinline markerFactory: suspend (T) -> M,
|
||||
noinline block: suspend (T) -> Unit
|
||||
) = subscribeAsync(scope, markerFactory) {
|
||||
safelyWithoutExceptions({ /* do nothing */}) {
|
||||
block(it)
|
||||
|
@ -4,58 +4,46 @@ import kotlinx.coroutines.*
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.coroutines.coroutineContext
|
||||
|
||||
typealias ExceptionHandler<T> = suspend (Throwable) -> T
|
||||
|
||||
/**
|
||||
* Launching [block] in [runCatching]. In case of failure, it will:
|
||||
*
|
||||
* * Try to get [ContextSafelyExceptionHandler] from current [coroutineContext] and call its
|
||||
* [ContextSafelyExceptionHandler.handler] invoke. **Thrown exception from its handler
|
||||
* will pass out of [runCatchingSafely]**
|
||||
* * Execute [onException] inside of new [runCatching] and return its result. Throws exception
|
||||
* will be caught by [runCatching] and wrapped in [Result]
|
||||
*
|
||||
* @return [Result] with result of [block] if no exceptions or [Result] from [onException] execution
|
||||
* This instance will be used in all calls of [safely] where exception handler has not been passed
|
||||
*/
|
||||
suspend inline fun <T> runCatchingSafely(
|
||||
onException: ExceptionHandler<T>,
|
||||
block: suspend () -> T
|
||||
): Result<T> {
|
||||
return runCatching {
|
||||
block()
|
||||
}.onFailure {
|
||||
coroutineContext[ContextSafelyExceptionHandlerKey] ?.handler ?.invoke(it)
|
||||
return runCatching {
|
||||
onException(it)
|
||||
}
|
||||
var defaultSafelyExceptionHandler: ExceptionHandler<Nothing> = { throw it }
|
||||
|
||||
/**
|
||||
* This instance will be used in all calls of [safelyWithoutExceptions] as an exception handler for [safely] call
|
||||
*/
|
||||
var defaultSafelyWithoutExceptionHandler: ExceptionHandler<Unit> = {
|
||||
try {
|
||||
defaultSafelyExceptionHandler(it)
|
||||
} catch (e: Throwable) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
suspend inline fun <T, R> R.runCatchingSafely(
|
||||
onException: ExceptionHandler<T>,
|
||||
block: suspend R.() -> T
|
||||
): Result<T> = runCatchingSafely<T>(onException) {
|
||||
block()
|
||||
}
|
||||
/**
|
||||
* This key can (and will) be used to get [ContextSafelyExceptionHandler] from [coroutineContext] of suspend functions
|
||||
* and in [ContextSafelyExceptionHandler] for defining of its [CoroutineContext.Element.key]
|
||||
*
|
||||
* @see safelyWithContextExceptionHandler
|
||||
* @see ContextSafelyExceptionHandler
|
||||
*/
|
||||
object ContextSafelyExceptionHandlerKey : CoroutineContext.Key<ContextSafelyExceptionHandler>
|
||||
|
||||
/**
|
||||
* Launching [runCatchingSafely] with [defaultSafelyExceptionHandler] as `onException` parameter
|
||||
* [ExceptionHandler] wrapper which was created to make possible to use [handler] across all coroutines calls
|
||||
*
|
||||
* @see safelyWithContextExceptionHandler
|
||||
* @see ContextSafelyExceptionHandlerKey
|
||||
*/
|
||||
suspend inline fun <T> runCatchingSafely(
|
||||
block: suspend () -> T
|
||||
): Result<T> = runCatchingSafely(defaultSafelyExceptionHandler, block)
|
||||
|
||||
suspend inline fun <T, R> R.runCatchingSafely(
|
||||
block: suspend R.() -> T
|
||||
): Result<T> = runCatchingSafely<T> {
|
||||
block()
|
||||
class ContextSafelyExceptionHandler(
|
||||
val handler: ExceptionHandler<Unit>
|
||||
) : CoroutineContext.Element {
|
||||
override val key: CoroutineContext.Key<*>
|
||||
get() = ContextSafelyExceptionHandlerKey
|
||||
}
|
||||
|
||||
//suspend inline fun <T, R> T.runCatchingSafely(
|
||||
// onException: ExceptionHandler<R>,
|
||||
// block: suspend T.() -> R
|
||||
//): Result<R> = runCatchingSafely(onException) {
|
||||
// block()
|
||||
//}
|
||||
|
||||
/**
|
||||
* @return [ContextSafelyExceptionHandler] from [coroutineContext] by key [ContextSafelyExceptionHandlerKey] if
|
||||
* exists
|
||||
@ -63,7 +51,7 @@ suspend inline fun <T, R> R.runCatchingSafely(
|
||||
* @see ContextSafelyExceptionHandler
|
||||
* @see ContextSafelyExceptionHandlerKey
|
||||
*/
|
||||
suspend fun contextSafelyExceptionHandler() = coroutineContext[ContextSafelyExceptionHandlerKey]
|
||||
suspend inline fun contextSafelyExceptionHandler() = coroutineContext[ContextSafelyExceptionHandlerKey]
|
||||
|
||||
/**
|
||||
* This method will set new [coroutineContext] with [ContextSafelyExceptionHandler]. In case if [coroutineContext]
|
||||
@ -76,7 +64,7 @@ suspend fun contextSafelyExceptionHandler() = coroutineContext[ContextSafelyExce
|
||||
suspend fun <T> safelyWithContextExceptionHandler(
|
||||
contextExceptionHandler: ExceptionHandler<Unit>,
|
||||
safelyExceptionHandler: ExceptionHandler<T> = defaultSafelyExceptionHandler,
|
||||
block: suspend () -> T
|
||||
block: suspend CoroutineScope.() -> T
|
||||
): T {
|
||||
val contextSafelyExceptionHandler = contextSafelyExceptionHandler() ?.handler ?.let { oldHandler ->
|
||||
ContextSafelyExceptionHandler {
|
||||
@ -90,35 +78,57 @@ suspend fun <T> safelyWithContextExceptionHandler(
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls [runCatchingSafely] and getting the result via [Result.getOrThrow]
|
||||
* It will run [block] inside of [supervisorScope] to avoid problems with catching of exceptions
|
||||
*
|
||||
* @see runCatchingSafely
|
||||
* Priorities of [ExceptionHandler]s:
|
||||
*
|
||||
* * [onException] In case if custom (will be used anyway if not [defaultSafelyExceptionHandler])
|
||||
* * [CoroutineContext.get] with [SafelyExceptionHandlerKey] as key
|
||||
* * [defaultSafelyExceptionHandler]
|
||||
*
|
||||
* Remember, that [ExceptionHandler] from [CoroutineContext.get] will be used anyway if it is available. After it will
|
||||
* be called [onException]
|
||||
*
|
||||
* @param [onException] Will be called when happen exception inside of [block]. By default will throw exception - this
|
||||
* exception will be available for catching
|
||||
*
|
||||
* @see defaultSafelyExceptionHandler
|
||||
* @see safelyWithoutExceptions
|
||||
* @see safelyWithContextExceptionHandler
|
||||
*/
|
||||
suspend inline fun <T> safely(
|
||||
onException: ExceptionHandler<T>,
|
||||
block: suspend () -> T
|
||||
): T = runCatchingSafely(onException, block).getOrThrow()
|
||||
noinline onException: ExceptionHandler<T> = defaultSafelyExceptionHandler,
|
||||
noinline block: suspend CoroutineScope.() -> T
|
||||
): T {
|
||||
return try {
|
||||
supervisorScope(block)
|
||||
} catch (e: Throwable) {
|
||||
coroutineContext[ContextSafelyExceptionHandlerKey] ?.handler ?.invoke(e)
|
||||
onException(e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls [safely] with passing of [defaultSafelyExceptionHandler] as `onException`
|
||||
*
|
||||
* @see runCatchingSafely
|
||||
*/
|
||||
suspend inline fun <T> safely(
|
||||
block: suspend () -> T
|
||||
): T = safely(defaultSafelyExceptionHandler, block)
|
||||
suspend inline fun <T, R> R.safely(
|
||||
block: suspend R.() -> T
|
||||
): T = safely<T> { block() }
|
||||
suspend inline fun <T> runCatchingSafely(
|
||||
noinline onException: ExceptionHandler<T> = defaultSafelyExceptionHandler,
|
||||
noinline block: suspend CoroutineScope.() -> T
|
||||
): Result<T> = runCatching {
|
||||
safely(onException, block)
|
||||
}
|
||||
|
||||
@Deprecated("Renamed", ReplaceWith("runCatchingSafely(block)", "dev.inmo.micro_utils.coroutines.runCatchingSafely"))
|
||||
suspend fun <T> safelyWithResult(
|
||||
block: suspend () -> T
|
||||
suspend inline fun <T, R> T.runCatchingSafely(
|
||||
noinline onException: ExceptionHandler<R> = defaultSafelyExceptionHandler,
|
||||
noinline block: suspend T.() -> R
|
||||
): Result<R> = runCatching {
|
||||
safely(onException) { block() }
|
||||
}
|
||||
|
||||
suspend inline fun <T> safelyWithResult(
|
||||
noinline block: suspend CoroutineScope.() -> T
|
||||
): Result<T> = runCatchingSafely(defaultSafelyExceptionHandler, block)
|
||||
@Deprecated("Renamed", ReplaceWith("this.runCatchingSafely(block)", "dev.inmo.micro_utils.coroutines.runCatchingSafely"))
|
||||
suspend fun <T, R> R.safelyWithResult(
|
||||
block: suspend R.() -> T
|
||||
): Result<T> = safelyWithResult<T> { block() }
|
||||
|
||||
suspend inline fun <T, R> T.safelyWithResult(
|
||||
noinline block: suspend T.() -> R
|
||||
): Result<R> = runCatchingSafely(defaultSafelyExceptionHandler, block)
|
||||
|
||||
/**
|
||||
* Use this handler in cases you wish to include handling of exceptions by [defaultSafelyWithoutExceptionHandler] and
|
||||
@ -137,23 +147,21 @@ val defaultSafelyWithoutExceptionHandlerWithNull: ExceptionHandler<Nothing?> = {
|
||||
* Shortcut for [safely] with exception handler, that as expected must return null in case of impossible creating of
|
||||
* result from exception (instead of throwing it, by default always returns null)
|
||||
*/
|
||||
suspend fun <T> safelyWithoutExceptions(
|
||||
onException: ExceptionHandler<T> = defaultSafelyExceptionHandler,
|
||||
block: suspend () -> T
|
||||
): T? = runCatchingSafely(onException, block).getOrNull()
|
||||
suspend inline fun <T> safelyWithoutExceptions(
|
||||
noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
|
||||
noinline block: suspend CoroutineScope.() -> T
|
||||
): T? = safely(onException, block)
|
||||
|
||||
suspend fun <T> runCatchingSafelyWithoutExceptions(
|
||||
onException: ExceptionHandler<T?> = defaultSafelyExceptionHandler,
|
||||
block: suspend () -> T
|
||||
): Result<T?> = runCatchingSafely(onException, block).let {
|
||||
if (it.isFailure) return Result.success<T?>(null)
|
||||
|
||||
it
|
||||
suspend inline fun <T> runCatchingSafelyWithoutExceptions(
|
||||
noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
|
||||
noinline block: suspend CoroutineScope.() -> T
|
||||
): Result<T?> = runCatching {
|
||||
safelyWithoutExceptions(onException, block)
|
||||
}
|
||||
|
||||
fun CoroutineScopeWithDefaultFallback(
|
||||
inline fun CoroutineScope(
|
||||
context: CoroutineContext,
|
||||
defaultExceptionsHandler: ExceptionHandler<Unit>
|
||||
noinline defaultExceptionsHandler: ExceptionHandler<Unit>
|
||||
) = CoroutineScope(
|
||||
context + ContextSafelyExceptionHandler(defaultExceptionsHandler)
|
||||
)
|
||||
|
@ -1,64 +0,0 @@
|
||||
package dev.inmo.micro_utils.coroutines
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
import kotlin.coroutines.*
|
||||
|
||||
/**
|
||||
* Trying to [Job.join] on all [this] [Job]s. The first [Job] completed its work will interrupt all others joins
|
||||
* and, if [cancelOthers] passed as true (**by default**), will also cancel all the others [Job]s
|
||||
*
|
||||
* @param scope Will be used to create [CoroutineScope.LinkedSupervisorScope] and launch joining of all [Job]s in it
|
||||
*/
|
||||
suspend fun Iterable<Job>.joinFirst(
|
||||
scope: CoroutineScope,
|
||||
cancelOthers: Boolean = true
|
||||
): Job {
|
||||
val resultDeferred = CompletableDeferred<Job>()
|
||||
val scope = scope.LinkedSupervisorScope()
|
||||
forEach {
|
||||
scope.launch {
|
||||
it.join()
|
||||
resultDeferred.complete(it)
|
||||
scope.cancel()
|
||||
}
|
||||
}
|
||||
return resultDeferred.await().also {
|
||||
if (cancelOthers) {
|
||||
forEach {
|
||||
runCatchingSafely { it.cancel() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Trying to [Job.join] on all [this] [Job]s. The first [Job] completed its work will interrupt all others joins
|
||||
* and, if [cancelOthers] passed as true (**by default**), will also cancel all the others [Job]s
|
||||
*
|
||||
* Creates [CoroutineScope] using [coroutineContext] for internal purposes
|
||||
*/
|
||||
suspend fun Iterable<Job>.joinFirst(
|
||||
cancelOthers: Boolean = true
|
||||
): Job = joinFirst(CoroutineScope(coroutineContext), cancelOthers)
|
||||
|
||||
/**
|
||||
* Trying to [Job.join] on all [jobs]. The first [Job] completed its work will interrupt all others joins
|
||||
* and, if [cancelOthers] passed as true (**by default**), will also cancel all the others [Job]s
|
||||
*
|
||||
* @param scope Will be used to create [CoroutineScope.LinkedSupervisorScope] and launch joining of all [Job]s in it
|
||||
*/
|
||||
suspend fun joinFirst(
|
||||
vararg jobs: Job,
|
||||
scope: CoroutineScope,
|
||||
cancelOthers: Boolean = true
|
||||
): Job = jobs.toList().joinFirst(scope, cancelOthers)
|
||||
|
||||
/**
|
||||
* Trying to [Job.join] on all [jobs]. The first [Job] completed its work will interrupt all others joins
|
||||
* and, if [cancelOthers] passed as true (**by default**), will also cancel all the others [Job]s
|
||||
*
|
||||
* Creates [CoroutineScope] using [coroutineContext] for internal purposes
|
||||
*/
|
||||
suspend fun joinFirst(
|
||||
vararg jobs: Job,
|
||||
cancelOthers: Boolean = true
|
||||
): Job = joinFirst(*jobs, scope = CoroutineScope(coroutineContext), cancelOthers = cancelOthers)
|
@ -4,46 +4,38 @@ import kotlinx.coroutines.*
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.coroutines.EmptyCoroutineContext
|
||||
|
||||
fun CoroutineScope.launchSafely(
|
||||
inline fun CoroutineScope.launchSafely(
|
||||
context: CoroutineContext = EmptyCoroutineContext,
|
||||
start: CoroutineStart = CoroutineStart.DEFAULT,
|
||||
onException: ExceptionHandler<Unit> = defaultSafelyExceptionHandler,
|
||||
block: suspend CoroutineScope.() -> Unit
|
||||
noinline onException: ExceptionHandler<Unit> = defaultSafelyExceptionHandler,
|
||||
noinline block: suspend CoroutineScope.() -> Unit
|
||||
) = launch(context, start) {
|
||||
runCatchingSafely(onException) {
|
||||
block()
|
||||
}
|
||||
safely(onException, block)
|
||||
}
|
||||
|
||||
fun CoroutineScope.launchSafelyWithoutExceptions(
|
||||
inline fun CoroutineScope.launchSafelyWithoutExceptions(
|
||||
context: CoroutineContext = EmptyCoroutineContext,
|
||||
start: CoroutineStart = CoroutineStart.DEFAULT,
|
||||
onException: ExceptionHandler<Unit?> = defaultSafelyWithoutExceptionHandlerWithNull,
|
||||
block: suspend CoroutineScope.() -> Unit
|
||||
noinline onException: ExceptionHandler<Unit?> = defaultSafelyWithoutExceptionHandlerWithNull,
|
||||
noinline block: suspend CoroutineScope.() -> Unit
|
||||
) = launch(context, start) {
|
||||
runCatchingSafelyWithoutExceptions(onException) {
|
||||
block()
|
||||
}
|
||||
safelyWithoutExceptions(onException, block)
|
||||
}
|
||||
|
||||
fun <T> CoroutineScope.asyncSafely(
|
||||
inline fun <T> CoroutineScope.asyncSafely(
|
||||
context: CoroutineContext = EmptyCoroutineContext,
|
||||
start: CoroutineStart = CoroutineStart.DEFAULT,
|
||||
onException: ExceptionHandler<T> = defaultSafelyExceptionHandler,
|
||||
block: suspend CoroutineScope.() -> T
|
||||
noinline onException: ExceptionHandler<T> = defaultSafelyExceptionHandler,
|
||||
noinline block: suspend CoroutineScope.() -> T
|
||||
) = async(context, start) {
|
||||
runCatchingSafely(onException) {
|
||||
block()
|
||||
}
|
||||
safely(onException, block)
|
||||
}
|
||||
|
||||
fun <T> CoroutineScope.asyncSafelyWithoutExceptions(
|
||||
inline fun <T> CoroutineScope.asyncSafelyWithoutExceptions(
|
||||
context: CoroutineContext = EmptyCoroutineContext,
|
||||
start: CoroutineStart = CoroutineStart.DEFAULT,
|
||||
onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
|
||||
block: suspend CoroutineScope.() -> T
|
||||
noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
|
||||
noinline block: suspend CoroutineScope.() -> T
|
||||
) = async(context, start) {
|
||||
runCatchingSafelyWithoutExceptions(onException) {
|
||||
block()
|
||||
}
|
||||
safelyWithoutExceptions(onException, block)
|
||||
}
|
||||
|
@ -44,7 +44,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 = MutableStateFlow<Boolean>(locked)
|
||||
override val lockStateFlow: StateFlow<Boolean> = _lockStateFlow.asStateFlow()
|
||||
|
||||
private val internalChangesMutex = Mutex()
|
||||
|
@ -13,7 +13,7 @@ import kotlin.contracts.contract
|
||||
* * [unlockWrite] will just unlock [writeMutex]
|
||||
*/
|
||||
class SmartRWLocker(private val readPermits: Int = Int.MAX_VALUE, writeIsLocked: Boolean = false) {
|
||||
private val _readSemaphore = SmartSemaphore.Mutable(permits = readPermits, acquiredPermits = if (writeIsLocked) readPermits else 0)
|
||||
private val _readSemaphore = SmartSemaphore.Mutable(permits = readPermits, acquiredPermits = 0)
|
||||
private val _writeMutex = SmartMutex.Mutable(locked = writeIsLocked)
|
||||
|
||||
val readSemaphore: SmartSemaphore.Immutable = _readSemaphore.immutable()
|
||||
|
@ -45,7 +45,7 @@ sealed interface SmartSemaphore {
|
||||
* @param locked Preset state of [freePermits] and its internal [_freePermitsStateFlow]
|
||||
*/
|
||||
class Mutable(private val permits: Int, acquiredPermits: Int = 0) : SmartSemaphore {
|
||||
private val _freePermitsStateFlow = SpecialMutableStateFlow<Int>(permits - acquiredPermits)
|
||||
private val _freePermitsStateFlow = MutableStateFlow<Int>(permits - acquiredPermits)
|
||||
override val permitsStateFlow: StateFlow<Int> = _freePermitsStateFlow.asStateFlow()
|
||||
|
||||
private val internalChangesMutex = Mutex(false)
|
||||
|
@ -11,60 +11,76 @@ 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>(
|
||||
initialValue: T
|
||||
initialValue: T,
|
||||
internalScope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
||||
) : MutableStateFlow<T>, FlowCollector<T>, MutableSharedFlow<T> {
|
||||
@OptIn(InternalCoroutinesApi::class)
|
||||
private val syncObject = SynchronizedObject()
|
||||
protected val sharingFlow: MutableSharedFlow<T> = MutableSharedFlow(
|
||||
protected val internalSharedFlow: MutableSharedFlow<T> = MutableSharedFlow(
|
||||
replay = 0,
|
||||
extraBufferCapacity = 2,
|
||||
onBufferOverflow = BufferOverflow.DROP_OLDEST
|
||||
)
|
||||
protected val publicSharedFlow: MutableSharedFlow<T> = MutableSharedFlow(
|
||||
replay = 1,
|
||||
extraBufferCapacity = 1,
|
||||
onBufferOverflow = BufferOverflow.DROP_OLDEST
|
||||
)
|
||||
|
||||
@OptIn(InternalCoroutinesApi::class)
|
||||
override var value: T = initialValue
|
||||
protected var _value: T = initialValue
|
||||
override var value: T
|
||||
get() = _value
|
||||
set(value) {
|
||||
synchronized(syncObject) {
|
||||
if (field != value) {
|
||||
field = value
|
||||
sharingFlow.tryEmit(value)
|
||||
}
|
||||
}
|
||||
doOnChangeAction(value)
|
||||
}
|
||||
|
||||
override val replayCache: List<T>
|
||||
get() = sharingFlow.replayCache
|
||||
override val subscriptionCount: StateFlow<Int>
|
||||
get() = sharingFlow.subscriptionCount
|
||||
|
||||
init {
|
||||
sharingFlow.tryEmit(initialValue)
|
||||
protected val job = internalSharedFlow.subscribe(internalScope) {
|
||||
doOnChangeAction(it)
|
||||
}
|
||||
|
||||
override val replayCache: List<T>
|
||||
get() = publicSharedFlow.replayCache
|
||||
override val subscriptionCount: StateFlow<Int>
|
||||
get() = publicSharedFlow.subscriptionCount
|
||||
|
||||
@OptIn(InternalCoroutinesApi::class)
|
||||
override fun compareAndSet(expect: T, update: T): Boolean {
|
||||
if (expect == value) {
|
||||
value = update
|
||||
return synchronized(syncObject) {
|
||||
if (expect == _value && update != _value) {
|
||||
doOnChangeAction(update)
|
||||
}
|
||||
expect == _value
|
||||
}
|
||||
}
|
||||
|
||||
protected open fun onChangeWithoutSync(value: T) {
|
||||
_value = value
|
||||
publicSharedFlow.tryEmit(value)
|
||||
}
|
||||
@OptIn(InternalCoroutinesApi::class)
|
||||
protected open fun doOnChangeAction(value: T) {
|
||||
synchronized(syncObject) {
|
||||
if (_value != value) {
|
||||
onChangeWithoutSync(value)
|
||||
}
|
||||
}
|
||||
return expect == value
|
||||
}
|
||||
|
||||
@ExperimentalCoroutinesApi
|
||||
override fun resetReplayCache() = sharingFlow.resetReplayCache()
|
||||
override fun resetReplayCache() = publicSharedFlow.resetReplayCache()
|
||||
|
||||
override fun tryEmit(value: T): Boolean {
|
||||
return compareAndSet(this.value, value)
|
||||
return internalSharedFlow.tryEmit(value)
|
||||
}
|
||||
|
||||
override suspend fun emit(value: T) {
|
||||
compareAndSet(this.value, value)
|
||||
internalSharedFlow.emit(value)
|
||||
}
|
||||
|
||||
override suspend fun collect(collector: FlowCollector<T>) = sharingFlow.collect(collector)
|
||||
override suspend fun collect(collector: FlowCollector<T>) = publicSharedFlow.collect(collector)
|
||||
}
|
||||
|
@ -10,12 +10,8 @@ fun CoroutineScope.LinkedSupervisorJob(
|
||||
additionalContext: CoroutineContext? = null
|
||||
) = coroutineContext.LinkedSupervisorJob(additionalContext)
|
||||
|
||||
|
||||
fun CoroutineContext.LinkedSupervisorScope(
|
||||
additionalContext: CoroutineContext? = null
|
||||
) = CoroutineScope(
|
||||
this + LinkedSupervisorJob(additionalContext)
|
||||
)
|
||||
fun CoroutineScope.LinkedSupervisorScope(
|
||||
additionalContext: CoroutineContext? = null
|
||||
) = coroutineContext.LinkedSupervisorScope(additionalContext)
|
||||
) = CoroutineScope(
|
||||
coroutineContext + LinkedSupervisorJob(additionalContext)
|
||||
)
|
||||
|
@ -4,71 +4,28 @@ import kotlinx.coroutines.*
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.coroutines.EmptyCoroutineContext
|
||||
|
||||
/**
|
||||
* Created [CoroutineScope] which will [launch] listening of [context] job completing and drop itself. Current weak
|
||||
* scope **will not** be attached to [context] directly. So, this [CoroutineScope] will not prevent parent one from
|
||||
* cancelling if it is launched with [supervisorScope] or [coroutineScope], but still will follow closing status
|
||||
* of parent [Job]
|
||||
*/
|
||||
fun WeakScope(
|
||||
context: CoroutineContext
|
||||
) = CoroutineScope(context.minusKey(Job) + Job()).also { newScope ->
|
||||
newScope.launch {
|
||||
context.job.join()
|
||||
newScope.cancel()
|
||||
}
|
||||
private fun CoroutineScope.createWeakSubScope() = CoroutineScope(coroutineContext.minusKey(Job)).also { newScope ->
|
||||
coroutineContext.job.invokeOnCompletion { newScope.cancel() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Created [CoroutineScope] which will [launch] listening of [scope] [CoroutineContext] job completing and drop itself. Current weak
|
||||
* scope **will not** be attached to [scope] [CoroutineContext] directly. So, this [CoroutineScope] will not prevent parent one from
|
||||
* cancelling if it is launched with [supervisorScope] or [coroutineScope], but still will follow closing status
|
||||
* of parent [Job]
|
||||
*/
|
||||
fun WeakScope(
|
||||
scope: CoroutineScope
|
||||
) = WeakScope(scope.coroutineContext)
|
||||
|
||||
/**
|
||||
* [this] [CoroutineScope] will be used as base for [WeakScope]. Other parameters ([context], [start], [block])
|
||||
* will be used to [launch] [Job]
|
||||
*/
|
||||
fun CoroutineScope.launchWeak(
|
||||
fun CoroutineScope.weakLaunch(
|
||||
context: CoroutineContext = EmptyCoroutineContext,
|
||||
start: CoroutineStart = CoroutineStart.DEFAULT,
|
||||
block: suspend CoroutineScope.() -> Unit
|
||||
): Job {
|
||||
val scope = WeakScope(this)
|
||||
val scope = createWeakSubScope()
|
||||
val job = scope.launch(context, start, block)
|
||||
job.invokeOnCompletion { scope.cancel() }
|
||||
return job
|
||||
}
|
||||
|
||||
/**
|
||||
* [this] [CoroutineScope] will be used as base for [WeakScope]. Other parameters ([context], [start], [block])
|
||||
* will be used to create [async] [Deferred]
|
||||
*/
|
||||
fun <T> CoroutineScope.asyncWeak(
|
||||
context: CoroutineContext = EmptyCoroutineContext,
|
||||
start: CoroutineStart = CoroutineStart.DEFAULT,
|
||||
block: suspend CoroutineScope.() -> T
|
||||
): Deferred<T> {
|
||||
val scope = WeakScope(this)
|
||||
val deferred = scope.async(context, start, block)
|
||||
deferred.invokeOnCompletion { scope.cancel() }
|
||||
return deferred
|
||||
}
|
||||
|
||||
@Deprecated("Renamed", ReplaceWith("launchWeak(context, start, block)", "dev.inmo.micro_utils.coroutines.launchWeak"))
|
||||
fun CoroutineScope.weakLaunch(
|
||||
context: CoroutineContext = EmptyCoroutineContext,
|
||||
start: CoroutineStart = CoroutineStart.DEFAULT,
|
||||
block: suspend CoroutineScope.() -> Unit
|
||||
): Job = launchWeak(context, start, block)
|
||||
|
||||
@Deprecated("Renamed", ReplaceWith("asyncWeak(context, start, block)", "dev.inmo.micro_utils.coroutines.asyncWeak"))
|
||||
fun <T> CoroutineScope.weakAsync(
|
||||
context: CoroutineContext = EmptyCoroutineContext,
|
||||
start: CoroutineStart = CoroutineStart.DEFAULT,
|
||||
block: suspend CoroutineScope.() -> T
|
||||
): Deferred<T> = asyncWeak(context, start, block)
|
||||
): Deferred<T> {
|
||||
val scope = createWeakSubScope()
|
||||
val deferred = scope.async(context, start, block)
|
||||
deferred.invokeOnCompletion { scope.cancel() }
|
||||
return deferred
|
||||
}
|
||||
|
@ -1,33 +0,0 @@
|
||||
import dev.inmo.micro_utils.coroutines.SpecialMutableStateFlow
|
||||
import dev.inmo.micro_utils.coroutines.asDeferred
|
||||
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)
|
||||
}
|
||||
@Test
|
||||
fun specialTest() = runTest {
|
||||
val specialMutableStateFlow = SpecialMutableStateFlow(0)
|
||||
lateinit var subscriberJob: Job
|
||||
subscriberJob = specialMutableStateFlow.subscribe(this) {
|
||||
when (it) {
|
||||
1 -> specialMutableStateFlow.value = 2
|
||||
2 -> subscriberJob.cancel()
|
||||
}
|
||||
}
|
||||
specialMutableStateFlow.value = 1
|
||||
subscriberJob.join()
|
||||
assertEquals(2, specialMutableStateFlow.value)
|
||||
}
|
||||
}
|
@ -1,64 +0,0 @@
|
||||
import dev.inmo.micro_utils.coroutines.asyncWeak
|
||||
import dev.inmo.micro_utils.coroutines.launchWeak
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class WeakJobTests {
|
||||
@Test
|
||||
fun testWeakJob() = runTest {
|
||||
var commonJobDone = false
|
||||
var weakJobStarted = false
|
||||
try {
|
||||
coroutineScope {
|
||||
launch {
|
||||
delay(1000)
|
||||
commonJobDone = true
|
||||
}
|
||||
asyncWeak {
|
||||
weakJobStarted = true
|
||||
delay(100500L)
|
||||
error("This must never happen")
|
||||
}
|
||||
}.await()
|
||||
} catch (error: Throwable) {
|
||||
assertTrue(error is CancellationException)
|
||||
assertTrue(commonJobDone)
|
||||
assertTrue(weakJobStarted)
|
||||
return@runTest
|
||||
}
|
||||
error("Cancellation exception has not been thrown")
|
||||
}
|
||||
@Test
|
||||
fun testThatWeakJobsWorksCorrectly() = runTest {
|
||||
val scope = CoroutineScope(Dispatchers.Default)
|
||||
lateinit var weakLaunchJob: Job
|
||||
lateinit var weakAsyncJob: Job
|
||||
val completeDeferred = Job()
|
||||
coroutineScope {
|
||||
weakLaunchJob = launchWeak {
|
||||
while (isActive) {
|
||||
delay(100L)
|
||||
}
|
||||
}
|
||||
weakAsyncJob = asyncWeak {
|
||||
while (isActive) {
|
||||
delay(100L)
|
||||
}
|
||||
}
|
||||
|
||||
coroutineContext.job.invokeOnCompletion {
|
||||
scope.launch {
|
||||
delay(1000L)
|
||||
completeDeferred.complete()
|
||||
}
|
||||
}
|
||||
launch { delay(1000L); cancel() }
|
||||
}
|
||||
completeDeferred.join()
|
||||
|
||||
assertTrue(!weakLaunchJob.isActive)
|
||||
assertTrue(!weakAsyncJob.isActive)
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package dev.inmo.micro_utils.coroutines
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
import org.junit.Test
|
||||
|
||||
class WeakJob {
|
||||
@Test
|
||||
fun `test that weak jobs works correctly`() {
|
||||
val scope = CoroutineScope(Dispatchers.Default)
|
||||
lateinit var weakLaunchJob: Job
|
||||
lateinit var weakAsyncJob: Job
|
||||
scope.launchSynchronously {
|
||||
val completeDeferred = Job()
|
||||
coroutineScope {
|
||||
weakLaunchJob = weakLaunch {
|
||||
while (isActive) {
|
||||
delay(100L)
|
||||
}
|
||||
}
|
||||
weakAsyncJob = weakAsync {
|
||||
while (isActive) {
|
||||
delay(100L)
|
||||
}
|
||||
}
|
||||
|
||||
coroutineContext.job.invokeOnCompletion {
|
||||
scope.launch {
|
||||
delay(1000L)
|
||||
completeDeferred.complete()
|
||||
}
|
||||
}
|
||||
launch { delay(1000L); cancel() }
|
||||
}
|
||||
completeDeferred.join()
|
||||
}
|
||||
|
||||
assert(!weakLaunchJob.isActive)
|
||||
assert(!weakAsyncJob.isActive)
|
||||
}
|
||||
}
|
@ -4,7 +4,7 @@ plugins {
|
||||
id "com.android.library"
|
||||
}
|
||||
|
||||
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64Project"
|
||||
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64ProjectPresetPath"
|
||||
|
||||
kotlin {
|
||||
sourceSets {
|
||||
|
@ -6,7 +6,6 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion libs.versions.android.props.minSdk.get().toInteger()
|
||||
compileSdkVersion libs.versions.android.props.compileSdk.get().toInteger()
|
||||
targetSdkVersion libs.versions.android.props.compileSdk.get().toInteger()
|
||||
versionCode "${android_code_version}".toInteger()
|
||||
versionName "$version"
|
@ -130,4 +130,4 @@ tasks.dokkaHtml {
|
||||
}
|
||||
}
|
||||
|
||||
apply from: "$defaultAndroidSettings"
|
||||
apply from: "$defaultAndroidSettingsPresetPath"
|
||||
|
@ -1,32 +1,42 @@
|
||||
File templatesFolder = new File("$rootProject.projectDir.absolutePath${File.separatorChar}gradle${File.separatorChar}templates")
|
||||
|
||||
Map properties = new HashMap<String, String>()
|
||||
|
||||
if (templatesFolder.exists() && templatesFolder.isDirectory()) {
|
||||
templatesFolder.listFiles().each {
|
||||
properties[it.name - ".gradle"] = it.absolutePath
|
||||
}
|
||||
}
|
||||
|
||||
properties["projectByName"] = { name ->
|
||||
for (subproject in rootProject.subprojects) {
|
||||
if (subproject.name == name) {
|
||||
return subproject
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
properties["internalProject"] = { name ->
|
||||
projectByName("$name")
|
||||
}
|
||||
|
||||
allprojects {
|
||||
ext {
|
||||
releaseMode = (project.hasProperty('RELEASE_MODE') && project.property('RELEASE_MODE') == "true") || System.getenv('RELEASE_MODE') == "true"
|
||||
|
||||
properties.forEach { k, v ->
|
||||
it[k] = v
|
||||
projectByName = { name ->
|
||||
for (subproject in rootProject.subprojects) {
|
||||
if (subproject.name == name) {
|
||||
return subproject
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
internalProject = { name ->
|
||||
// if (releaseMode) {
|
||||
// "$group:$name:$version"
|
||||
// } else {
|
||||
// projectByName("$name")
|
||||
// }
|
||||
projectByName("$name")
|
||||
}
|
||||
|
||||
releaseMode = (project.hasProperty('RELEASE_MODE') && project.property('RELEASE_MODE') == "true") || System.getenv('RELEASE_MODE') == "true"
|
||||
// String compilerPluginVersionFromProperties = (String) project.properties["compose.kotlinCompilerPluginVersion"]
|
||||
// String compilerPluginVersionFromLibrariesVersions = libs.versions.compose.kotlin.get()
|
||||
// composePluginKotlinVersion = compilerPluginVersionFromProperties
|
||||
// if (compilerPluginVersionFromProperties == null) {
|
||||
// composePluginKotlinVersion = compilerPluginVersionFromLibrariesVersions
|
||||
// }
|
||||
|
||||
mppProjectWithSerializationPresetPath = "${rootProject.projectDir.absolutePath}/mppJvmJsAndroidProject.gradle"
|
||||
mppProjectWithSerializationAndComposePresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerializationAndCompose.gradle"
|
||||
mppJavaProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJavaProject.gradle"
|
||||
mppJvmJsLinuxMingwProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJvmJsLinuxMingwProject.gradle"
|
||||
mppJvmJsLinuxMingwLinuxArm64ProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJvmJsLinuxMingwLinuxArm64Project.gradle"
|
||||
mppJvmJsAndroidLinuxMingwLinuxArm64ProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJvmJsAndroidLinuxMingwLinuxArm64Project.gradle"
|
||||
mppAndroidProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppAndroidProject.gradle"
|
||||
|
||||
defaultAndroidSettingsPresetPath = "${rootProject.projectDir.absolutePath}/defaultAndroidSettings.gradle"
|
||||
|
||||
publishGradlePath = "${rootProject.projectDir.absolutePath}/publish.gradle"
|
||||
publishJvmOnlyPath = "${rootProject.projectDir.absolutePath}/jvm.publish.gradle"
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ plugins {
|
||||
id "com.android.library"
|
||||
}
|
||||
|
||||
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64Project"
|
||||
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64ProjectPresetPath"
|
||||
|
||||
kotlin {
|
||||
sourceSets {
|
||||
|
@ -1,6 +1,7 @@
|
||||
package dev.inmo.micro_utils.fsm.common
|
||||
|
||||
import dev.inmo.micro_utils.common.Optional
|
||||
import dev.inmo.micro_utils.common.onPresented
|
||||
import dev.inmo.micro_utils.coroutines.*
|
||||
import dev.inmo.micro_utils.fsm.common.utils.StateHandlingErrorHandler
|
||||
import dev.inmo.micro_utils.fsm.common.utils.defaultStateHandlingErrorHandler
|
||||
@ -117,28 +118,23 @@ open class DefaultStatesMachine <T: State>(
|
||||
* [launchStateHandling] will returns some [State] then [statesManager] [StatesManager.update] will be used, otherwise
|
||||
* [StatesManager.endChain].
|
||||
*/
|
||||
override fun start(scope: CoroutineScope): Job {
|
||||
val supervisorScope = scope.LinkedSupervisorScope()
|
||||
supervisorScope.launchSafelyWithoutExceptions {
|
||||
(statesManager.getActiveStates().asFlow() + statesManager.onStartChain).subscribeSafelyWithoutExceptions(supervisorScope) {
|
||||
supervisorScope.launch { performStateUpdate(Optional.absent(), it, supervisorScope) }
|
||||
}
|
||||
statesManager.onChainStateUpdated.subscribeSafelyWithoutExceptions(supervisorScope) {
|
||||
supervisorScope.launch { performStateUpdate(Optional.presented(it.first), it.second, supervisorScope) }
|
||||
}
|
||||
statesManager.onEndChain.subscribeSafelyWithoutExceptions(supervisorScope) { removedState ->
|
||||
supervisorScope.launch {
|
||||
statesJobsMutex.withLock {
|
||||
val stateInMap = statesJobs.keys.firstOrNull { stateInMap -> stateInMap == removedState }
|
||||
if (stateInMap === removedState) {
|
||||
statesJobs[stateInMap] ?.cancel()
|
||||
}
|
||||
override fun start(scope: CoroutineScope): Job = scope.launchSafelyWithoutExceptions {
|
||||
(statesManager.getActiveStates().asFlow() + statesManager.onStartChain).subscribeSafelyWithoutExceptions(this) {
|
||||
launch { performStateUpdate(Optional.absent(), it, scope.LinkedSupervisorScope()) }
|
||||
}
|
||||
statesManager.onChainStateUpdated.subscribeSafelyWithoutExceptions(this) {
|
||||
launch { performStateUpdate(Optional.presented(it.first), it.second, scope.LinkedSupervisorScope()) }
|
||||
}
|
||||
statesManager.onEndChain.subscribeSafelyWithoutExceptions(this) { removedState ->
|
||||
launch {
|
||||
statesJobsMutex.withLock {
|
||||
val stateInMap = statesJobs.keys.firstOrNull { stateInMap -> stateInMap == removedState }
|
||||
if (stateInMap === removedState) {
|
||||
statesJobs[stateInMap] ?.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return supervisorScope.coroutineContext.job
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -4,7 +4,7 @@ plugins {
|
||||
id "com.android.library"
|
||||
}
|
||||
|
||||
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64Project"
|
||||
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64ProjectPresetPath"
|
||||
|
||||
kotlin {
|
||||
sourceSets {
|
||||
|
@ -15,5 +15,5 @@ crypto_js_version=4.1.1
|
||||
# Project data
|
||||
|
||||
group=dev.inmo
|
||||
version=0.22.4
|
||||
android_code_version=270
|
||||
version=0.20.29
|
||||
android_code_version=235
|
||||
|
@ -1,47 +1,45 @@
|
||||
[versions]
|
||||
|
||||
kt = "2.0.20"
|
||||
kt-serialization = "1.7.3"
|
||||
kt-coroutines = "1.9.0"
|
||||
kt = "1.9.22"
|
||||
kt-serialization = "1.6.2"
|
||||
kt-coroutines = "1.7.3"
|
||||
|
||||
kslog = "1.3.6"
|
||||
kslog = "1.3.2"
|
||||
|
||||
jb-compose = "1.7.0-beta02"
|
||||
jb-exposed = "0.55.0"
|
||||
jb-dokka = "1.9.20"
|
||||
jb-compose = "1.5.12"
|
||||
jb-exposed = "0.46.0"
|
||||
jb-dokka = "1.9.10"
|
||||
|
||||
sqlite = "3.46.1.2"
|
||||
korlibs = "5.3.1"
|
||||
uuid = "0.8.2"
|
||||
|
||||
korlibs = "5.4.0"
|
||||
uuid = "0.8.4"
|
||||
|
||||
ktor = "2.3.12"
|
||||
ktor = "2.3.7"
|
||||
|
||||
gh-release = "2.5.2"
|
||||
|
||||
koin = "4.0.0"
|
||||
koin = "3.5.3"
|
||||
|
||||
okio = "3.9.1"
|
||||
okio = "3.7.0"
|
||||
|
||||
ksp = "2.0.20-1.0.25"
|
||||
kotlin-poet = "1.18.1"
|
||||
ksp = "1.9.22-1.0.17"
|
||||
kotlin-poet = "1.16.0"
|
||||
|
||||
versions = "0.51.0"
|
||||
|
||||
android-gradle = "8.2.2"
|
||||
dexcount = "4.0.0"
|
||||
|
||||
android-coreKtx = "1.13.1"
|
||||
android-coreKtx = "1.12.0"
|
||||
android-recyclerView = "1.3.2"
|
||||
android-appCompat = "1.7.0"
|
||||
android-fragment = "1.8.3"
|
||||
android-espresso = "3.6.1"
|
||||
android-test = "1.2.1"
|
||||
android-compose-material3 = "1.3.0"
|
||||
android-appCompat = "1.6.1"
|
||||
android-fragment = "1.6.2"
|
||||
android-espresso = "3.5.1"
|
||||
android-test = "1.1.5"
|
||||
android-compose-material3 = "1.1.2"
|
||||
|
||||
android-props-minSdk = "21"
|
||||
android-props-compileSdk = "35"
|
||||
android-props-buildTools = "35.0.0"
|
||||
android-props-compileSdk = "34"
|
||||
android-props-buildTools = "34.0.0"
|
||||
|
||||
[libraries]
|
||||
|
||||
@ -54,7 +52,6 @@ kt-serialization-cbor = { module = "org.jetbrains.kotlinx:kotlinx-serialization-
|
||||
kt-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kt-coroutines" }
|
||||
kt-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kt-coroutines" }
|
||||
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" }
|
||||
|
||||
|
||||
ktor-io = { module = "io.ktor:ktor-io", version.ref = "ktor" }
|
||||
@ -82,14 +79,12 @@ koin = { module = "io.insert-koin:koin-core", version.ref = "koin" }
|
||||
|
||||
|
||||
jb-exposed = { module = "org.jetbrains.exposed:exposed-core", version.ref = "jb-exposed" }
|
||||
jb-exposed-jdbc = { module = "org.jetbrains.exposed:exposed-jdbc", version.ref = "jb-exposed" }
|
||||
sqlite = { module = "org.xerial:sqlite-jdbc", version.ref = "sqlite" }
|
||||
|
||||
jb-compose-material3 = { module = "org.jetbrains.compose.material3:material3", version.ref = "jb-compose" }
|
||||
|
||||
android-coreKtx = { module = "androidx.core:core-ktx", version.ref = "android-coreKtx" }
|
||||
android-recyclerView = { module = "androidx.recyclerview:recyclerview", version.ref = "android-recyclerView" }
|
||||
android-appCompat-resources = { module = "androidx.appcompat:appcompat-resources", version.ref = "android-appCompat" }
|
||||
android-compose-material3 = { module = "androidx.compose.material3:material3", version.ref = "android-compose-material3" }
|
||||
android-fragment = { module = "androidx.fragment:fragment", version.ref = "android-fragment" }
|
||||
android-espresso = { module = "androidx.test.espresso:espresso-core", version.ref = "android-espresso" }
|
||||
android-test-junit = { module = "androidx.test.ext:junit", version.ref = "android-test" }
|
||||
@ -118,6 +113,5 @@ buildscript-android-dexcount = { module = "com.getkeepsafe.dexcount:dexcount-gra
|
||||
[plugins]
|
||||
|
||||
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" }
|
||||
|
@ -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 = "60000"
|
||||
}
|
||||
}
|
||||
}
|
||||
nodejs {
|
||||
testTask {
|
||||
useMocha {
|
||||
timeout = "60000"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
@ -4,7 +4,7 @@ plugins {
|
||||
id "com.android.library"
|
||||
}
|
||||
|
||||
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64Project"
|
||||
apply from: "$mppProjectWithSerializationPresetPath"
|
||||
|
||||
kotlin {
|
||||
sourceSets {
|
||||
|
@ -2,7 +2,7 @@ plugins {
|
||||
id "org.jetbrains.kotlin.jvm"
|
||||
}
|
||||
|
||||
apply from: "$publish_jvm"
|
||||
apply from: "$publishJvmOnlyPath"
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
|
@ -5,7 +5,7 @@ plugins {
|
||||
id "com.google.devtools.ksp"
|
||||
}
|
||||
|
||||
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64Project"
|
||||
apply from: "$mppProjectWithSerializationPresetPath"
|
||||
|
||||
|
||||
kotlin {
|
||||
|
@ -1,7 +0,0 @@
|
||||
plugins {
|
||||
id "org.jetbrains.kotlin.multiplatform"
|
||||
id "org.jetbrains.kotlin.plugin.serialization"
|
||||
id "com.android.library"
|
||||
}
|
||||
|
||||
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64Project"
|
@ -1,21 +0,0 @@
|
||||
plugins {
|
||||
id "org.jetbrains.kotlin.jvm"
|
||||
}
|
||||
|
||||
apply from: "$publish_jvm"
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api project(":micro_utils.ksp.generator")
|
||||
api project(":micro_utils.ksp.classcasts")
|
||||
api libs.kotlin.poet
|
||||
api libs.ksp
|
||||
}
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
@ -1,90 +0,0 @@
|
||||
package dev.inmo.micro_utils.ksp.classcasts.generator
|
||||
|
||||
import com.google.devtools.ksp.symbol.ClassKind
|
||||
import com.google.devtools.ksp.symbol.KSClassDeclaration
|
||||
import com.squareup.kotlinpoet.*
|
||||
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
|
||||
import com.squareup.kotlinpoet.ksp.toClassName
|
||||
import com.squareup.kotlinpoet.ksp.toTypeName
|
||||
|
||||
|
||||
private fun FileSpec.Builder.addTopLevelImport(className: ClassName) {
|
||||
className.topLevelClassName().let {
|
||||
addImport(it.packageName, it.simpleNames)
|
||||
}
|
||||
}
|
||||
|
||||
private fun FileSpec.Builder.createTypeDefinition(ksClassDeclaration: KSClassDeclaration): TypeName {
|
||||
val className = ksClassDeclaration.toClassName()
|
||||
return if (ksClassDeclaration.typeParameters.isNotEmpty()) {
|
||||
className.parameterizedBy(
|
||||
ksClassDeclaration.typeParameters.map {
|
||||
it.bounds.first().resolve().also {
|
||||
val typeClassName = it.toClassName()
|
||||
addTopLevelImport(typeClassName)
|
||||
}.toTypeName()
|
||||
}
|
||||
)
|
||||
} else {
|
||||
className
|
||||
}
|
||||
}
|
||||
|
||||
internal fun FileSpec.Builder.fill(
|
||||
sourceKSClassDeclaration: KSClassDeclaration,
|
||||
subtypes: Set<KSClassDeclaration>
|
||||
) {
|
||||
subtypes.forEach { targetClassDeclaration ->
|
||||
val sourceClassName = sourceKSClassDeclaration.toClassName()
|
||||
val targetClassClassName = targetClassDeclaration.toClassName()
|
||||
val targetClassTypeDefinition = createTypeDefinition(targetClassDeclaration)
|
||||
val simpleName = targetClassDeclaration.simpleName.asString()
|
||||
val withFirstLowerCase = simpleName.replaceFirstChar { it.lowercase() }
|
||||
val castedOrNullName = "${withFirstLowerCase}OrNull"
|
||||
|
||||
addTopLevelImport(targetClassClassName)
|
||||
addFunction(
|
||||
FunSpec.builder(castedOrNullName).apply {
|
||||
receiver(sourceClassName)
|
||||
addCode(
|
||||
"return this as? %L",
|
||||
targetClassTypeDefinition
|
||||
)
|
||||
returns(targetClassTypeDefinition.copy(nullable = true))
|
||||
addModifiers(KModifier.INLINE)
|
||||
}.build()
|
||||
)
|
||||
addFunction(
|
||||
FunSpec.builder("${withFirstLowerCase}OrThrow").apply {
|
||||
receiver(sourceClassName)
|
||||
addCode(
|
||||
"return this as %L",
|
||||
targetClassTypeDefinition
|
||||
)
|
||||
returns(targetClassTypeDefinition)
|
||||
addModifiers(KModifier.INLINE)
|
||||
}.build()
|
||||
)
|
||||
addFunction(
|
||||
FunSpec.builder("if$simpleName").apply {
|
||||
val genericType = TypeVariableName("T", null)
|
||||
addTypeVariable(genericType)
|
||||
receiver(sourceClassName)
|
||||
addParameter(
|
||||
"block",
|
||||
LambdaTypeName.get(
|
||||
null,
|
||||
targetClassTypeDefinition,
|
||||
returnType = genericType
|
||||
)
|
||||
)
|
||||
addCode(
|
||||
"return ${castedOrNullName}() ?.let(block)",
|
||||
targetClassTypeDefinition
|
||||
)
|
||||
returns(genericType.copy(nullable = true))
|
||||
addModifiers(KModifier.INLINE)
|
||||
}.build()
|
||||
)
|
||||
}
|
||||
}
|
@ -1,119 +0,0 @@
|
||||
package dev.inmo.micro_utils.ksp.classcasts.generator
|
||||
|
||||
import com.google.devtools.ksp.KspExperimental
|
||||
import com.google.devtools.ksp.getAllSuperTypes
|
||||
import com.google.devtools.ksp.getAnnotationsByType
|
||||
import com.google.devtools.ksp.isAnnotationPresent
|
||||
import com.google.devtools.ksp.processing.CodeGenerator
|
||||
import com.google.devtools.ksp.processing.Resolver
|
||||
import com.google.devtools.ksp.processing.SymbolProcessor
|
||||
import com.google.devtools.ksp.symbol.*
|
||||
import com.squareup.kotlinpoet.*
|
||||
import com.squareup.kotlinpoet.ksp.toClassName
|
||||
import dev.inmo.micro_ksp.generator.writeFile
|
||||
import dev.inmo.micro_utils.ksp.classcasts.ClassCastsExcluded
|
||||
import dev.inmo.micro_utils.ksp.classcasts.ClassCastsIncluded
|
||||
import java.io.File
|
||||
|
||||
class Processor(
|
||||
private val codeGenerator: CodeGenerator
|
||||
) : SymbolProcessor {
|
||||
@OptIn(KspExperimental::class)
|
||||
private fun FileSpec.Builder.generateClassCasts(
|
||||
ksClassDeclaration: KSClassDeclaration,
|
||||
resolver: Resolver
|
||||
) {
|
||||
val rootAnnotation = ksClassDeclaration.getAnnotationsByType(ClassCastsIncluded::class).first()
|
||||
val (includeRegex: Regex?, excludeRegex: Regex?) = rootAnnotation.let {
|
||||
it.typesRegex.takeIf { it.isNotEmpty() } ?.let(::Regex) to it.excludeRegex.takeIf { it.isNotEmpty() } ?.let(::Regex)
|
||||
}
|
||||
val classesSubtypes = mutableMapOf<KSClassDeclaration, MutableSet<KSClassDeclaration>>()
|
||||
|
||||
fun KSClassDeclaration.checkSupertypeLevel(levelsAllowed: Int?): Boolean {
|
||||
val supertypes by lazy {
|
||||
superTypes.map { it.resolve().declaration }
|
||||
}
|
||||
return when {
|
||||
levelsAllowed == null -> true
|
||||
levelsAllowed <= 0 -> false
|
||||
supertypes.any { it == ksClassDeclaration } -> true
|
||||
else -> supertypes.any {
|
||||
(it as? KSClassDeclaration) ?.checkSupertypeLevel(levelsAllowed - 1) == true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun handleDeclaration(ksDeclarationContainer: KSDeclarationContainer) {
|
||||
ksDeclarationContainer.declarations.forEach { potentialSubtype ->
|
||||
val simpleName = potentialSubtype.simpleName.getShortName()
|
||||
when {
|
||||
potentialSubtype === ksClassDeclaration -> {}
|
||||
potentialSubtype.isAnnotationPresent(ClassCastsExcluded::class) -> return@forEach
|
||||
potentialSubtype !is KSClassDeclaration || !potentialSubtype.checkSupertypeLevel(rootAnnotation.levelsToInclude.takeIf { it >= 0 }) -> return@forEach
|
||||
excludeRegex ?.matches(simpleName) == true -> return@forEach
|
||||
includeRegex ?.matches(simpleName) == false -> {}
|
||||
else -> classesSubtypes.getOrPut(ksClassDeclaration) { mutableSetOf() }.add(potentialSubtype)
|
||||
}
|
||||
handleDeclaration(potentialSubtype as? KSDeclarationContainer ?: return@forEach)
|
||||
}
|
||||
}
|
||||
resolver.getAllFiles().forEach {
|
||||
handleDeclaration(it)
|
||||
}
|
||||
fun fillWithSealeds(current: KSClassDeclaration) {
|
||||
current.getSealedSubclasses().forEach {
|
||||
val simpleName = it.simpleName.getShortName()
|
||||
if (
|
||||
includeRegex ?.matches(simpleName) == false
|
||||
|| excludeRegex ?.matches(simpleName) == true
|
||||
|| it.isAnnotationPresent(ClassCastsExcluded::class)
|
||||
) {
|
||||
return@forEach
|
||||
}
|
||||
classesSubtypes.getOrPut(ksClassDeclaration) { mutableSetOf() }.add(it)
|
||||
fillWithSealeds(it)
|
||||
}
|
||||
}
|
||||
fillWithSealeds(ksClassDeclaration)
|
||||
|
||||
addAnnotation(
|
||||
AnnotationSpec.builder(Suppress::class).apply {
|
||||
addMember("\"unused\"")
|
||||
addMember("\"RemoveRedundantQualifierName\"")
|
||||
addMember("\"RedundantVisibilityModifier\"")
|
||||
addMember("\"NOTHING_TO_INLINE\"")
|
||||
addMember("\"UNCHECKED_CAST\"")
|
||||
addMember("\"OPT_IN_USAGE\"")
|
||||
useSiteTarget(AnnotationSpec.UseSiteTarget.FILE)
|
||||
}.build()
|
||||
)
|
||||
fill(
|
||||
ksClassDeclaration,
|
||||
classesSubtypes.values.flatten().toSet()
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(KspExperimental::class)
|
||||
override fun process(resolver: Resolver): List<KSAnnotated> {
|
||||
(resolver.getSymbolsWithAnnotation(ClassCastsIncluded::class.qualifiedName!!)).filterIsInstance<KSClassDeclaration>().forEach {
|
||||
val prefix = it.getAnnotationsByType(ClassCastsIncluded::class).first().outputFilePrefix
|
||||
it.writeFile(prefix = prefix, suffix = "ClassCasts") {
|
||||
FileSpec.builder(
|
||||
it.packageName.asString(),
|
||||
"${it.simpleName.getShortName()}ClassCasts"
|
||||
).apply {
|
||||
addFileComment(
|
||||
"""
|
||||
THIS CODE HAVE BEEN GENERATED AUTOMATICALLY
|
||||
TO REGENERATE IT JUST DELETE FILE
|
||||
ORIGINAL FILE: ${it.containingFile ?.fileName}
|
||||
""".trimIndent()
|
||||
)
|
||||
generateClassCasts(it, resolver)
|
||||
}.build()
|
||||
}
|
||||
}
|
||||
|
||||
return emptyList()
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
package dev.inmo.micro_utils.ksp.classcasts.generator
|
||||
|
||||
import com.google.devtools.ksp.processing.SymbolProcessor
|
||||
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
|
||||
import com.google.devtools.ksp.processing.SymbolProcessorProvider
|
||||
|
||||
class Provider : SymbolProcessorProvider {
|
||||
override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor = Processor(
|
||||
environment.codeGenerator
|
||||
)
|
||||
}
|
@ -1 +0,0 @@
|
||||
dev.inmo.micro_utils.ksp.classcasts.generator.Provider
|
@ -1,27 +0,0 @@
|
||||
plugins {
|
||||
id "org.jetbrains.kotlin.multiplatform"
|
||||
id "org.jetbrains.kotlin.plugin.serialization"
|
||||
id "com.android.library"
|
||||
id "com.google.devtools.ksp"
|
||||
}
|
||||
|
||||
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64Project"
|
||||
|
||||
|
||||
kotlin {
|
||||
sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
api project(":micro_utils.ksp.classcasts")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
dependencies {
|
||||
add("kspCommonMainMetadata", project(":micro_utils.ksp.classcasts.generator"))
|
||||
}
|
||||
|
||||
ksp {
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
package dev.inmo.micro_utils.ksp.classcasts.generator.test
|
||||
|
||||
import dev.inmo.micro_utils.ksp.classcasts.ClassCastsExcluded
|
||||
import dev.inmo.micro_utils.ksp.classcasts.ClassCastsIncluded
|
||||
|
||||
@ClassCastsIncluded(levelsToInclude = 1)
|
||||
sealed interface Test {
|
||||
object A : Test
|
||||
@ClassCastsExcluded
|
||||
object B : Test // Will not be included in class casts due to annotation ClassCastsExcluded
|
||||
object C : Test
|
||||
interface D : Test {
|
||||
object DD : D // Will not be included in class casts due to levelsToInclude
|
||||
}
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
// THIS CODE HAVE BEEN GENERATED AUTOMATICALLY
|
||||
// TO REGENERATE IT JUST DELETE FILE
|
||||
// ORIGINAL FILE: Test.kt
|
||||
@file:Suppress(
|
||||
"unused",
|
||||
"RemoveRedundantQualifierName",
|
||||
"RedundantVisibilityModifier",
|
||||
"NOTHING_TO_INLINE",
|
||||
"UNCHECKED_CAST",
|
||||
"OPT_IN_USAGE",
|
||||
)
|
||||
|
||||
package dev.inmo.micro_utils.ksp.classcasts.generator.test
|
||||
|
||||
import dev.inmo.micro_utils.ksp.classcasts.generator.test.Test
|
||||
import kotlin.Suppress
|
||||
|
||||
public inline fun Test.aOrNull(): Test.A? = this as?
|
||||
dev.inmo.micro_utils.ksp.classcasts.generator.test.Test.A
|
||||
|
||||
public inline fun Test.aOrThrow(): Test.A = this as
|
||||
dev.inmo.micro_utils.ksp.classcasts.generator.test.Test.A
|
||||
|
||||
public inline fun <T> Test.ifA(block: (Test.A) -> T): T? = aOrNull() ?.let(block)
|
||||
|
||||
public inline fun Test.cOrNull(): Test.C? = this as?
|
||||
dev.inmo.micro_utils.ksp.classcasts.generator.test.Test.C
|
||||
|
||||
public inline fun Test.cOrThrow(): Test.C = this as
|
||||
dev.inmo.micro_utils.ksp.classcasts.generator.test.Test.C
|
||||
|
||||
public inline fun <T> Test.ifC(block: (Test.C) -> T): T? = cOrNull() ?.let(block)
|
||||
|
||||
public inline fun Test.dOrNull(): Test.D? = this as?
|
||||
dev.inmo.micro_utils.ksp.classcasts.generator.test.Test.D
|
||||
|
||||
public inline fun Test.dOrThrow(): Test.D = this as
|
||||
dev.inmo.micro_utils.ksp.classcasts.generator.test.Test.D
|
||||
|
||||
public inline fun <T> Test.ifD(block: (Test.D) -> T): T? = dOrNull() ?.let(block)
|
@ -1,5 +0,0 @@
|
||||
package dev.inmo.micro_utils.ksp.classcasts
|
||||
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
annotation class ClassCastsExcluded
|
@ -1,10 +0,0 @@
|
||||
package dev.inmo.micro_utils.ksp.classcasts
|
||||
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
annotation class ClassCastsIncluded(
|
||||
val typesRegex: String = "",
|
||||
val excludeRegex: String = "",
|
||||
val outputFilePrefix: String = "",
|
||||
val levelsToInclude: Int = -1
|
||||
)
|
@ -1,20 +0,0 @@
|
||||
plugins {
|
||||
id "org.jetbrains.kotlin.jvm"
|
||||
}
|
||||
|
||||
apply from: "$publish_jvm"
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api project(":micro_utils.common")
|
||||
api libs.kotlin.poet
|
||||
api libs.ksp
|
||||
}
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
package dev.inmo.micro_ksp.generator
|
||||
|
||||
import com.google.devtools.ksp.symbol.KSClassDeclaration
|
||||
import com.google.devtools.ksp.symbol.KSFile
|
||||
import com.squareup.kotlinpoet.FileSpec
|
||||
import java.io.File
|
||||
|
||||
fun KSClassDeclaration.writeFile(
|
||||
prefix: String = "",
|
||||
suffix: String = "",
|
||||
relatedPath: String = "",
|
||||
force: Boolean = false,
|
||||
fileSpecBuilder: () -> FileSpec
|
||||
) {
|
||||
val containingFile = containingFile!!
|
||||
File(
|
||||
File(
|
||||
File(containingFile.filePath).parent,
|
||||
relatedPath
|
||||
),
|
||||
"$prefix${simpleName.asString()}$suffix.kt"
|
||||
).takeIf { force || !it.exists() } ?.apply {
|
||||
parentFile.mkdirs()
|
||||
writer().use { writer ->
|
||||
fileSpecBuilder().writeTo(writer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun KSFile.writeFile(
|
||||
prefix: String = "",
|
||||
suffix: String = "",
|
||||
relatedPath: String = "",
|
||||
force: Boolean = false,
|
||||
fileSpecBuilder: () -> FileSpec
|
||||
) {
|
||||
File(
|
||||
File(
|
||||
File(filePath).parent,
|
||||
relatedPath
|
||||
),
|
||||
"$prefix${fileName.dropLastWhile { it != '.' }.removeSuffix(".")}$suffix.kt"
|
||||
).takeIf { force || !it.exists() } ?.apply {
|
||||
parentFile.mkdirs()
|
||||
writer().use { writer ->
|
||||
fileSpecBuilder().writeTo(writer)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
package dev.inmo.micro_ksp.generator
|
||||
|
||||
import com.google.devtools.ksp.getAllSuperTypes
|
||||
import com.google.devtools.ksp.symbol.KSAnnotated
|
||||
import com.google.devtools.ksp.symbol.KSClassDeclaration
|
||||
import com.google.devtools.ksp.symbol.KSDeclarationContainer
|
||||
import com.google.devtools.ksp.symbol.KSFile
|
||||
|
||||
fun KSClassDeclaration.findSubClasses(subSymbol: KSAnnotated): Sequence<KSClassDeclaration> {
|
||||
return when (subSymbol) {
|
||||
is KSClassDeclaration -> if (subSymbol.getAllSuperTypes().map { it.declaration }.contains(this)) {
|
||||
sequenceOf(subSymbol)
|
||||
} else {
|
||||
sequenceOf()
|
||||
}
|
||||
else -> sequenceOf()
|
||||
} + if (subSymbol is KSDeclarationContainer) {
|
||||
subSymbol.declarations.flatMap {
|
||||
findSubClasses(it)
|
||||
}
|
||||
} else {
|
||||
sequenceOf()
|
||||
}
|
||||
}
|
||||
|
||||
fun KSClassDeclaration.findSubClasses(files: Sequence<KSAnnotated>): Sequence<KSClassDeclaration> {
|
||||
return files.flatMap {
|
||||
findSubClasses(it)
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
plugins {
|
||||
id "org.jetbrains.kotlin.multiplatform"
|
||||
id "org.jetbrains.kotlin.plugin.serialization"
|
||||
id "com.android.library"
|
||||
}
|
||||
|
||||
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64Project"
|
@ -1,21 +0,0 @@
|
||||
plugins {
|
||||
id "org.jetbrains.kotlin.jvm"
|
||||
}
|
||||
|
||||
apply from: "$publish_jvm"
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api project(":micro_utils.ksp.generator")
|
||||
api project(":micro_utils.ksp.sealed")
|
||||
api libs.kotlin.poet
|
||||
api libs.ksp
|
||||
}
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
@ -1,130 +0,0 @@
|
||||
package dev.inmo.micro_utils.ksp.sealed.generator
|
||||
|
||||
import com.google.devtools.ksp.KspExperimental
|
||||
import com.google.devtools.ksp.getAnnotationsByType
|
||||
import com.google.devtools.ksp.processing.CodeGenerator
|
||||
import com.google.devtools.ksp.processing.Resolver
|
||||
import com.google.devtools.ksp.processing.SymbolProcessor
|
||||
import com.google.devtools.ksp.symbol.*
|
||||
import com.squareup.kotlinpoet.ClassName
|
||||
import com.squareup.kotlinpoet.CodeBlock
|
||||
import com.squareup.kotlinpoet.FileSpec
|
||||
import com.squareup.kotlinpoet.FunSpec
|
||||
import com.squareup.kotlinpoet.KModifier
|
||||
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
|
||||
import com.squareup.kotlinpoet.PropertySpec
|
||||
import com.squareup.kotlinpoet.asTypeName
|
||||
import com.squareup.kotlinpoet.ksp.toClassName
|
||||
import dev.inmo.micro_ksp.generator.findSubClasses
|
||||
import dev.inmo.micro_ksp.generator.writeFile
|
||||
import dev.inmo.microutils.kps.sealed.GenerateSealedWorkaround
|
||||
import java.io.File
|
||||
|
||||
class Processor(
|
||||
private val codeGenerator: CodeGenerator
|
||||
) : SymbolProcessor {
|
||||
private fun KSClassDeclaration.findSealedConnection(potentialSealedParent: KSClassDeclaration): Boolean {
|
||||
val targetClassname = potentialSealedParent.qualifiedName ?.asString()
|
||||
return superTypes.any {
|
||||
val itAsDeclaration = it.resolve().declaration as? KSClassDeclaration ?: return@any false
|
||||
targetClassname == (itAsDeclaration.qualifiedName ?.asString()) || (itAsDeclaration.getSealedSubclasses().any() && itAsDeclaration.findSealedConnection(potentialSealedParent))
|
||||
}
|
||||
}
|
||||
|
||||
private fun KSClassDeclaration.resolveSubclasses(
|
||||
searchIn: Sequence<KSAnnotated>,
|
||||
allowNonSealed: Boolean
|
||||
): Sequence<KSClassDeclaration> {
|
||||
return findSubClasses(searchIn).let {
|
||||
if (allowNonSealed) {
|
||||
it
|
||||
} else {
|
||||
it.filter {
|
||||
it.findSealedConnection(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(KspExperimental::class)
|
||||
private fun FileSpec.Builder.generateSealedWorkaround(
|
||||
ksClassDeclaration: KSClassDeclaration,
|
||||
resolver: Resolver
|
||||
) {
|
||||
val annotation = ksClassDeclaration.getAnnotationsByType(GenerateSealedWorkaround::class).first()
|
||||
val subClasses = ksClassDeclaration.resolveSubclasses(
|
||||
searchIn = resolver.getAllFiles(),
|
||||
allowNonSealed = annotation.includeNonSealedSubTypes
|
||||
).distinct()
|
||||
val subClassesNames = subClasses.filter {
|
||||
when (it.classKind) {
|
||||
ClassKind.ENUM_ENTRY,
|
||||
ClassKind.OBJECT -> true
|
||||
ClassKind.INTERFACE,
|
||||
ClassKind.CLASS,
|
||||
ClassKind.ENUM_CLASS,
|
||||
ClassKind.ANNOTATION_CLASS -> false
|
||||
}
|
||||
}.filter {
|
||||
it.getAnnotationsByType(GenerateSealedWorkaround.Exclude::class).count() == 0
|
||||
}.sortedBy {
|
||||
(it.getAnnotationsByType(GenerateSealedWorkaround.Order::class).firstOrNull()) ?.order ?: 0
|
||||
}.map {
|
||||
it.toClassName()
|
||||
}.toList()
|
||||
val className = ksClassDeclaration.toClassName()
|
||||
val setType = Set::class.asTypeName().parameterizedBy(
|
||||
ksClassDeclaration.toClassName()
|
||||
)
|
||||
addProperty(
|
||||
PropertySpec.builder(
|
||||
"values",
|
||||
setType
|
||||
).apply {
|
||||
modifiers.add(
|
||||
KModifier.PRIVATE
|
||||
)
|
||||
initializer(
|
||||
CodeBlock.of(
|
||||
"""setOf(${subClassesNames.joinToString(",\n") {it.simpleNames.joinToString(".")}})"""
|
||||
)
|
||||
)
|
||||
}.build()
|
||||
)
|
||||
addFunction(
|
||||
FunSpec.builder("values").apply {
|
||||
receiver(ClassName(className.packageName, *className.simpleNames.toTypedArray(), "Companion"))
|
||||
returns(setType)
|
||||
addCode(
|
||||
CodeBlock.of(
|
||||
"""return values"""
|
||||
)
|
||||
)
|
||||
}.build()
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(KspExperimental::class)
|
||||
override fun process(resolver: Resolver): List<KSAnnotated> {
|
||||
(resolver.getSymbolsWithAnnotation(GenerateSealedWorkaround::class.qualifiedName!!)).filterIsInstance<KSClassDeclaration>().forEach {
|
||||
val prefix = it.getAnnotationsByType(GenerateSealedWorkaround::class).first().prefix
|
||||
it.writeFile(prefix = prefix, suffix = "SealedWorkaround") {
|
||||
FileSpec.builder(
|
||||
it.packageName.asString(),
|
||||
"${it.simpleName.getShortName()}SealedWorkaround"
|
||||
).apply {
|
||||
addFileComment(
|
||||
"""
|
||||
THIS CODE HAVE BEEN GENERATED AUTOMATICALLY
|
||||
TO REGENERATE IT JUST DELETE FILE
|
||||
ORIGINAL FILE: ${it.containingFile ?.fileName}
|
||||
""".trimIndent()
|
||||
)
|
||||
generateSealedWorkaround(it, resolver)
|
||||
}.build()
|
||||
}
|
||||
}
|
||||
|
||||
return emptyList()
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
package dev.inmo.micro_utils.ksp.sealed.generator
|
||||
|
||||
import com.google.devtools.ksp.processing.SymbolProcessor
|
||||
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
|
||||
import com.google.devtools.ksp.processing.SymbolProcessorProvider
|
||||
|
||||
class Provider : SymbolProcessorProvider {
|
||||
override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor = Processor(
|
||||
environment.codeGenerator
|
||||
)
|
||||
}
|
@ -1 +0,0 @@
|
||||
dev.inmo.micro_utils.ksp.sealed.generator.Provider
|
@ -1,27 +0,0 @@
|
||||
plugins {
|
||||
id "org.jetbrains.kotlin.multiplatform"
|
||||
id "org.jetbrains.kotlin.plugin.serialization"
|
||||
id "com.android.library"
|
||||
id "com.google.devtools.ksp"
|
||||
}
|
||||
|
||||
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64Project"
|
||||
|
||||
|
||||
kotlin {
|
||||
sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
api project(":micro_utils.ksp.sealed")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
dependencies {
|
||||
add("kspCommonMainMetadata", project(":micro_utils.ksp.sealed.generator"))
|
||||
}
|
||||
|
||||
ksp {
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
package dev.inmo.micro_utils.ksp.sealed.generator.test
|
||||
|
||||
import dev.inmo.microutils.kps.sealed.GenerateSealedWorkaround
|
||||
|
||||
@GenerateSealedWorkaround
|
||||
sealed interface Test {
|
||||
@GenerateSealedWorkaround.Order(2)
|
||||
object A : Test
|
||||
@GenerateSealedWorkaround.Exclude
|
||||
object B : Test
|
||||
@GenerateSealedWorkaround.Order(0)
|
||||
object C : Test
|
||||
|
||||
// Required for successful sealed workaround generation
|
||||
companion object
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
// THIS CODE HAVE BEEN GENERATED AUTOMATICALLY
|
||||
// TO REGENERATE IT JUST DELETE FILE
|
||||
// ORIGINAL FILE: Test.kt
|
||||
package dev.inmo.micro_utils.ksp.`sealed`.generator.test
|
||||
|
||||
import kotlin.collections.Set
|
||||
|
||||
private val values: Set<Test> = setOf(Test.C,
|
||||
Test.A)
|
||||
|
||||
public fun Test.Companion.values(): Set<Test> = values
|
@ -1,15 +0,0 @@
|
||||
package dev.inmo.microutils.kps.sealed
|
||||
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
annotation class GenerateSealedWorkaround(
|
||||
val prefix: String = "",
|
||||
val includeNonSealedSubTypes: Boolean = false
|
||||
) {
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
annotation class Order(val order: Int)
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
annotation class Exclude
|
||||
}
|
@ -4,7 +4,7 @@ plugins {
|
||||
id "com.android.library"
|
||||
}
|
||||
|
||||
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64Project"
|
||||
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64ProjectPresetPath"
|
||||
|
||||
kotlin {
|
||||
sourceSets {
|
||||
@ -15,6 +15,9 @@ kotlin {
|
||||
api libs.ktor.client
|
||||
}
|
||||
}
|
||||
androidMain {
|
||||
dependsOn jvmMain
|
||||
}
|
||||
|
||||
linuxX64Main {
|
||||
dependencies {
|
||||
|
@ -4,7 +4,7 @@ plugins {
|
||||
id "com.android.library"
|
||||
}
|
||||
|
||||
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64Project"
|
||||
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64ProjectPresetPath"
|
||||
|
||||
kotlin {
|
||||
sourceSets {
|
||||
@ -12,9 +12,13 @@ kotlin {
|
||||
dependencies {
|
||||
api internalProject("micro_utils.common")
|
||||
api libs.kt.serialization.cbor
|
||||
api libs.klock
|
||||
api libs.uuid
|
||||
api libs.ktor.io
|
||||
}
|
||||
}
|
||||
androidMain {
|
||||
dependsOn jvmMain
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ plugins {
|
||||
id "org.jetbrains.kotlin.plugin.serialization"
|
||||
}
|
||||
|
||||
apply from: "$mppJavaProject"
|
||||
apply from: "$mppJavaProjectPresetPath"
|
||||
|
||||
kotlin {
|
||||
sourceSets {
|
||||
|
@ -41,7 +41,7 @@ class TemporalFilesRoutingConfigurator(
|
||||
filesMutex: Mutex,
|
||||
onNewFileFlow: Flow<TemporalFileId>
|
||||
): Job = scope.launchSafelyWithoutExceptions {
|
||||
while (currentCoroutineContext().isActive) {
|
||||
while (isActive) {
|
||||
val filesWithCreationInfo = filesMap.mapNotNull { (fileId, file) ->
|
||||
fileId to ((Files.getAttribute(file.toPath(), "creationTime") as? FileTime) ?.toMillis() ?: return@mapNotNull null)
|
||||
}
|
||||
|
@ -4,4 +4,12 @@ plugins {
|
||||
id "com.android.library"
|
||||
}
|
||||
|
||||
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64Project"
|
||||
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64ProjectPresetPath"
|
||||
|
||||
kotlin {
|
||||
sourceSets {
|
||||
androidMain {
|
||||
dependsOn jvmMain
|
||||
}
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because one or more lines are too long
@ -22,24 +22,17 @@ private const val baseClassSerializerAnnotationName = "@Serializable(${baseClass
|
||||
|
||||
@Serializable
|
||||
private data class LanguageCode(
|
||||
@SerialName("English")
|
||||
val title: String,
|
||||
@SerialName("alpha2")
|
||||
val alpha: String? = null,
|
||||
@SerialName("alpha3-b")
|
||||
val alpha2: String? = null,
|
||||
@SerialName("alpha3-t")
|
||||
val alpha3: String? = null,
|
||||
) {
|
||||
val tag: String
|
||||
get() = alpha ?: alpha2 ?: alpha3!!
|
||||
}
|
||||
val tag: String,
|
||||
@SerialName("English")
|
||||
val title: String
|
||||
)
|
||||
|
||||
fun String.adaptAsTitle() = (if (first().isDigit()) {
|
||||
fun String.adaptAsTitle() = if (first().isDigit()) {
|
||||
"L$this"
|
||||
} else {
|
||||
this
|
||||
}).replace(".", "_").replace("'", "_")
|
||||
}
|
||||
|
||||
fun String.normalized() = Normalizer.normalize(this, Normalizer.Form.NFD).replace(Regex("[^\\p{ASCII}]"), "")
|
||||
|
||||
@ -80,10 +73,7 @@ data class Tag(
|
||||
val title: String,
|
||||
val tag: String,
|
||||
val subtags: List<Tag>
|
||||
) {
|
||||
val adaptedTitle
|
||||
get() = title.adaptAsTitle()
|
||||
}
|
||||
)
|
||||
|
||||
private fun printLanguageCodeAndTags(
|
||||
tag: Tag,
|
||||
@ -91,19 +81,17 @@ private fun printLanguageCodeAndTags(
|
||||
indents: String = " "
|
||||
): String = if (tag.subtags.isEmpty()) {
|
||||
"""${indents}${baseClassSerializerAnnotationName}
|
||||
${indents}object ${tag.adaptedTitle} : ${parent ?.adaptedTitle ?: baseClassName} { override val code: String = "${tag.tag}"${parent ?.let { parent -> "; override val parentLang: ${parent.adaptedTitle} get() = ${parent.adaptedTitle}" } ?: ""}; override fun toString() = code }"""
|
||||
${indents}object ${tag.title} : ${parent ?.title ?: baseClassName}() { override val code: String = "${tag.tag}"${parent ?.let { parent -> "; override val parentLang: ${parent.title} get() = ${parent.title};" } ?: ""} }"""
|
||||
} else {
|
||||
"""
|
||||
${indents}${baseClassSerializerAnnotationName}
|
||||
${indents}sealed interface ${tag.adaptedTitle} : ${parent ?.adaptedTitle ?: baseClassName} {
|
||||
${indents}sealed class ${tag.title} : ${parent ?.title ?: baseClassName}() {
|
||||
${indents} override val code: String = "${tag.tag}"${parent ?.let { parent -> "\n${indents} override val parentLang: ${parent.title} get() = ${parent.title};" } ?: ""}
|
||||
|
||||
${tag.subtags.joinToString("\n") { printLanguageCodeAndTags(it, tag, "${indents} ") }}
|
||||
|
||||
${indents} ${baseClassSerializerAnnotationName}
|
||||
${indents} companion object : ${tag.adaptedTitle} {
|
||||
${indents} override val code: String = "${tag.tag}"${parent ?.let { parent -> "\n${indents} override val parentLang: ${parent.adaptedTitle} get() = ${parent.adaptedTitle};" } ?: ""}
|
||||
${indents} override fun toString() = code
|
||||
${indents} }
|
||||
${indents} companion object : ${tag.title}()
|
||||
${indents}}
|
||||
"""
|
||||
}
|
||||
@ -117,22 +105,23 @@ import kotlinx.serialization.Serializable
|
||||
* https://datahub.io/core/language-codes/ files (base and tags) and create the whole hierarchy using it.
|
||||
*/
|
||||
${baseClassSerializerAnnotationName}
|
||||
sealed interface $baseClassName {
|
||||
val code: String
|
||||
val parentLang: $baseClassName?
|
||||
get() = code.split("-").takeIf { it.size > 1 } ?.first() ?.let(::$unknownBaseClassName)
|
||||
val withoutDialect: String
|
||||
sealed class $baseClassName {
|
||||
abstract val code: String
|
||||
open val parentLang: $baseClassName?
|
||||
get() = null
|
||||
open val withoutDialect: String
|
||||
get() = parentLang ?.code ?: code
|
||||
|
||||
${tags.joinToString("\n") { printLanguageCodeAndTags(it, indents = " ") } }
|
||||
|
||||
$baseClassSerializerAnnotationName
|
||||
data class $unknownBaseClassName (override val code: String) : $baseClassName {
|
||||
data class $unknownBaseClassName (override val code: String) : $baseClassName() {
|
||||
override val parentLang = code.dropLastWhile { it != '-' }.removeSuffix("-").takeIf { it.length > 0 } ?.let(::$unknownBaseClassName)
|
||||
}
|
||||
@Deprecated("Renamed", ReplaceWith("$baseClassName.$unknownBaseClassName", "${if (prePackage.isNotEmpty()) "$prePackage." else ""}$baseClassName.$unknownBaseClassName"))
|
||||
val $oldUnknownBaseClassName
|
||||
get() = $unknownBaseClassName
|
||||
val $oldUnknownBaseClassName = $unknownBaseClassName
|
||||
|
||||
override fun toString() = code
|
||||
}
|
||||
@Deprecated("Renamed", ReplaceWith("$baseClassName", "${if (prePackage.isNotEmpty()) "$prePackage." else ""}$baseClassName"))
|
||||
typealias $oldBaseClassName = $baseClassName
|
||||
@ -144,7 +133,7 @@ fun createStringConverterCode(tags: List<Tag>, prePackage: String): String {
|
||||
pretitle: String = baseClassName,
|
||||
indents: String = " "
|
||||
): String {
|
||||
val currentTitle = "$pretitle.${tag.adaptedTitle}"
|
||||
val currentTitle = "$pretitle.${tag.title}"
|
||||
return """${indents}$currentTitle.code -> $currentTitle${if (tag.subtags.isNotEmpty()) tag.subtags.joinToString("\n", "\n") { createDeserializeVariantForTag(it, currentTitle, indents) } else ""}"""
|
||||
}
|
||||
fun createInheritorVariantForTag(
|
||||
@ -152,7 +141,7 @@ fun createStringConverterCode(tags: List<Tag>, prePackage: String): String {
|
||||
pretitle: String = baseClassName,
|
||||
indents: String = " "
|
||||
): String {
|
||||
val currentTitle = "$pretitle.${tag.adaptedTitle}"
|
||||
val currentTitle = "$pretitle.${tag.title}"
|
||||
val subtags = if (tag.subtags.isNotEmpty()) {
|
||||
tag.subtags.joinToString(",\n", ",\n") { createInheritorVariantForTag(it, currentTitle, "$indents ") }
|
||||
} else {
|
||||
@ -166,7 +155,7 @@ fun createStringConverterCode(tags: List<Tag>, prePackage: String): String {
|
||||
indents: String = " ",
|
||||
codeSuffix: String = ""
|
||||
): String {
|
||||
val currentTitle = "$pretitle.${tag.adaptedTitle}"
|
||||
val currentTitle = "$pretitle.${tag.title}"
|
||||
val subtags = if (tag.subtags.isNotEmpty()) {
|
||||
tag.subtags.joinToString(",\n", ",\n") { createInheritorVariantForMapForTag(it, currentTitle, "$indents ", codeSuffix) }
|
||||
} else {
|
||||
@ -277,7 +266,7 @@ suspend fun main(vararg args: String) {
|
||||
File(outputFolder, "LanguageCodes.kt").apply {
|
||||
delete()
|
||||
createNewFile()
|
||||
writeText("@file:Suppress(\"SERIALIZER_TYPE_INCOMPATIBLE\")\n\n" + targetPackagePrefix + buildKtFileContent(tags, targetPackage ?: ""))
|
||||
writeText(targetPackagePrefix + buildKtFileContent(tags, targetPackage ?: ""))
|
||||
}
|
||||
|
||||
File(outputFolder, "StringToLanguageCodes.kt").apply {
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user