Compare commits

..

89 Commits

Author SHA1 Message Date
7090566041 fill changelog 2023-10-09 13:58:17 +06:00
f0d5035cd0 update dependencies 2023-10-09 13:53:44 +06:00
0fb9b8dc30 update compose version 2023-09-27 17:59:09 +06:00
eef6e81134 Update libs.versions.toml 2023-09-26 01:28:36 +06:00
1593159a3f Update libs.versions.toml 2023-09-26 01:27:20 +06:00
3da9eb9dbe temporal update 2023-09-22 14:14:23 +06:00
f17613f3fb update dependencies 2023-09-12 14:11:12 +06:00
14337ccb46 start 0.20.7 2023-09-10 17:09:08 +06:00
1a3913b09c Merge pull request #311 from InsanusMokrassar/0.20.6
0.20.6
2023-09-08 04:20:58 +06:00
039aed2747 improvements in ExposedReadKeyValuesRepo 2023-09-08 02:59:53 +06:00
173991e3cb start 0.20.6 2023-09-08 02:55:25 +06:00
8b3f8cab01 Merge pull request #310 from InsanusMokrassar/0.20.5
0.20.5
2023-09-07 20:09:58 +06:00
2a20d24589 fixes in SmartRWLocker 2023-09-07 16:57:59 +06:00
53c2d552ec start 0.20.5 2023-09-07 16:57:08 +06:00
af11c1a83d Merge pull request #307 from InsanusMokrassar/0.20.4
0.20.4
2023-09-06 19:17:17 +06:00
a65cf1481c fixes in build 2023-09-06 19:15:09 +06:00
0318716236 update kslog version 2023-09-06 19:08:32 +06:00
88eb4b3342 update dependencies 2023-09-06 18:58:10 +06:00
4810d1ef6a start 0.20.4 2023-09-06 18:55:48 +06:00
f2a9514d89 Merge pull request #306 from InsanusMokrassar/renovate/jb.dokka
Update dependency org.jetbrains.dokka:dokka-gradle-plugin to v1.9.0
2023-09-02 04:15:10 +06:00
925ba6ac24 Update build.gradle 2023-09-02 04:04:04 +06:00
renovate[bot]
ef407268a2 Update dependency org.jetbrains.dokka:dokka-gradle-plugin to v1.9.0 2023-09-01 21:04:25 +00:00
cd6c4bbe38 Merge pull request #304 from InsanusMokrassar/0.20.3
0.20.3
2023-09-02 03:03:57 +06:00
c058e18408 update smart rw locker tests 2023-09-01 20:11:04 +06:00
6d3ca565ca update ktor 2023-09-01 19:41:49 +06:00
236a7b4fd2 Update libs.versions.toml 2023-09-01 04:33:36 +06:00
e1f387dbf7 update dependencies and make a lot of fixes in repos and smart lockers/semaphors 2023-08-31 01:37:35 +06:00
3d113dd31e fixes in locks of caches repos 2023-08-29 15:06:55 +06:00
e0e57f0336 start 0.20.3 2023-08-29 14:59:57 +06:00
e775b58d41 Merge pull request #298 from InsanusMokrassar/0.20.2
0.20.2
2023-08-24 20:10:06 +06:00
5b070a8478 add small kdoc for map repos 2023-08-24 17:15:29 +06:00
8a61193500 update korlibs 2023-08-24 14:29:15 +06:00
fad522b8fe Update CHANGELOG.md 2023-08-24 03:00:08 +06:00
0acac205af fix build 2023-08-24 02:17:27 +06:00
069d4c61b7 update android target 2023-08-23 14:52:09 +06:00
7c113f5700 Update libs.versions.toml 2023-08-23 13:27:23 +06:00
dfdaf4225b start 0.20.2 and all main repos uses 'SmartRWLocker' 2023-08-23 03:26:54 +06:00
bd39ab2467 Merge pull request #295 from InsanusMokrassar/0.20.1
0.20.1
2023-08-12 22:47:03 +06:00
6ce1eb3f2d add tests for smartrwlocker 2023-08-12 22:37:35 +06:00
ce7d4fe9a2 fix of #293 2023-08-12 22:08:12 +06:00
a65bb2f419 add number picker, set picker and small text field 2023-08-11 01:51:40 +06:00
cbc868448b start 0.20.1 2023-08-11 00:58:09 +06:00
9c336a0b56 Update CHANGELOG.md 2023-08-09 02:43:53 +06:00
0f0d09399e Update CHANGELOG.md 2023-08-09 02:42:28 +06:00
e13a1162a9 Merge pull request #294 from InsanusMokrassar/0.20.0
0.20.0
2023-08-09 00:32:03 +06:00
57ebed903f done migration 2023-08-08 18:45:36 +06:00
4478193d8a update dependencies 2023-08-05 13:49:45 +06:00
ee948395e3 start 0.20.0 2023-08-02 13:03:40 +06:00
0616b051ae Merge pull request #291 from InsanusMokrassar/0.19.9
0.19.9
2023-07-29 17:37:38 +06:00
4d155d0505 update koin 2023-07-29 16:37:25 +06:00
a169e733d9 fill changelog 2023-07-29 16:31:50 +06:00
f081e237c8 improvements in startup plugin 2023-07-29 16:30:17 +06:00
f412d387fa start 0.19.9 2023-07-29 16:05:24 +06:00
67354b43e2 Merge pull request #287 from InsanusMokrassar/0.19.8
0.19.8
2023-07-27 01:33:49 +06:00
39135a4000 skipStartInvalidate 2023-07-27 00:35:01 +06:00
eaa014cebd update dependencies 2023-07-27 00:30:26 +06:00
856e657f81 fixes in KeyValueRepo.clear 2023-07-23 13:47:20 +06:00
3a609e5b66 start 0.19.8 2023-07-23 13:26:39 +06:00
d0022dd599 Merge pull request #281 from InsanusMokrassar/0.19.7
0.19.7
2023-06-30 14:37:36 +06:00
7ba6eed453 Update CHANGELOG.md 2023-06-30 12:04:42 +06:00
beeb6ecc0a Update CHANGELOG.md 2023-06-30 12:03:17 +06:00
7cdc17a714 Update libs.versions.toml 2023-06-30 12:02:20 +06:00
4765a950a9 start 0.19.7 2023-06-30 12:01:21 +06:00
65e8137e08 Merge pull request #279 from InsanusMokrassar/0.19.6
0.19.6
2023-06-29 20:27:19 +06:00
ae546dd9ad update dependencies 2023-06-29 18:38:58 +06:00
8110c42be0 update compose 2023-06-27 16:53:10 +06:00
bd2b5ae5fc add versions plugin 2023-06-27 16:46:45 +06:00
2ddfffa6a9 start 0.19.6 2023-06-27 16:46:45 +06:00
a4b54e861d Merge pull request #277 from InsanusMokrassar/0.19.5
0.19.5
2023-06-20 20:22:33 +06:00
c6785f1a4f fixes in types generation 2023-06-20 20:20:47 +06:00
83fe621c56 start 0.19.5 2023-06-20 20:16:24 +06:00
b3a93e17eb Merge pull request #275 from InsanusMokrassar/0.19.4
0.19.4
2023-06-19 16:25:43 +06:00
546a391af3 dependencies update 2023-06-19 16:19:14 +06:00
786cf9bd8b fix in koin generator 2023-06-19 13:52:41 +06:00
dfd6fe062d start 0.19.4 2023-06-19 13:45:29 +06:00
b6ef818613 Merge pull request #274 from InsanusMokrassar/0.19.3
0.19.3
2023-06-19 08:45:45 +06:00
b0f9e9c30a generic-oriented koin definitions 2023-06-19 00:32:45 +06:00
7e5c88ddc3 koin generator improvements 2023-06-18 23:38:36 +06:00
9824c3e00f start 0.19.3 2023-06-18 23:11:50 +06:00
9171d5ed11 update dokka version and return js kdocs 2023-06-09 12:01:11 +06:00
a1830ebb82 Merge pull request #269 from InsanusMokrassar/0.19.2
0.19.2
2023-06-02 13:02:38 +06:00
22d2a3d9bf Update CHANGELOG.md 2023-06-02 12:47:45 +06:00
c9b97fc965 Update libs.versions.toml 2023-06-02 12:46:56 +06:00
51f85becd5 Merge pull request #267 from InsanusMokrassar/0.19.2
0.19.2
2023-06-02 12:39:27 +06:00
a8a281cfb4 downgrade kotlin-poet version 2023-06-02 12:19:02 +06:00
ddd1304949 update dependencies 2023-06-02 12:17:21 +06:00
86d70b6c02 start 0.19.2 2023-06-02 12:11:49 +06:00
a22bdb39e7 Merge pull request #261 from InsanusMokrassar/0.19.1
update kotlin poet (0.19.1)
2023-05-29 17:35:29 +06:00
a2038cbefa Merge pull request #259 from InsanusMokrassar/0.19.1
0.19.1
2023-05-29 17:28:19 +06:00
176 changed files with 2494 additions and 572 deletions

View File

@@ -1,5 +1,143 @@
# Changelog # Changelog
## 0.20.7
**THIS VERSION CONTAINS UPDATES OF DEPENDENCIES UP TO BETA VERSIONS. USE WITH CAUTION**
* `Versions`:
* `Kotlin`: `1.9.10` -> `1.9.20-Beta2`
* `Compose`: `1.5.1` -> `1.5.10-beta02`
* `Exposed`: `0.43.0` -> `0.44.0`
* `Ktor`: `2.3.4` -> `2.3.5`
* `Koin`: `3.4.3` -> `3.5.0`
* `Okio`: `3.5.0` -> `3.6.0`
* `Android Core`: `1.10.1` -> `1.12.0`
* `Android Compose Material`: `1.1.1` -> `1.1.2`
## 0.20.6
* `Repos`:
* `Exposed`
* Fixes in exposed key-values repos
## 0.20.5
* `Coroutines`:
* Fixes in `SmartRWLocker`
## 0.20.4
* `Versions`:
* `Kotlin`: `1.9.0` -> `1.9.10`
* `KSLog`: `1.2.0` -> `1.2.1`
* `Compose`: `1.5.0` -> `1.5.1`
* `UUID`: `0.8.0` -> `0.8.1`
## 0.20.3
* `Versions`:
* `Compose`: `1.4.3` -> `1.5.0`
* `Exposed`: `0.42.1` -> `0.43.0`
* `Ktor`: `2.3.3` -> `2.3.4`
* `Repos`:
* `Cache`:
* Fixes in locks of caches
## 0.20.2
* All main repos uses `SmartRWLocker`
* `Versions`:
* `Serialization`: `1.5.1` -> `1.6.0`
* `Exposed`: `0.42.0` -> `0.42.1`
* `Korlibs`: `4.0.9` -> `4.0.10`
* `Androis SDK`: `33` -> `34`
## 0.20.1
* `SmallTextField`:
* Module is initialized
* `Pickers`:
* Module is initialized
* `Coroutines`:
* Add `SmartSemaphore`
* Add `SmartRWLocker`
## 0.20.0
* `Versions`:
* `Kotlin`: `1.8.22` -> `1.9.0`
* `KSLog`: `1.1.1` -> `1.2.0`
* `Exposed`: `0.41.1` -> `0.42.0`
* `UUID`: `0.7.1` -> `0.8.0`
* `Korlibs`: `4.0.3` -> `4.0.9`
* `Ktor`: `2.3.2` -> `2.3.3`
* `Okio`: `3.4.0` -> `3.5.0`
## 0.19.9
* `Versions`:
* `Koin`: `3.4.2` -> `3.4.3`
* `Startup`:
* Now it is possible to start application in synchronous way
## 0.19.8
* `Versions`:
* `Coroutines`: `1.7.2` -> `1.7.3`
* `Kotlin`: `1.8.20` -> `1.8.22`
* `Compose`: `1.4.1` -> `1.4.3`
* `Okio`: `3.3.0` -> `3.4.0`
* `RecyclerView`: `1.3.0` -> `1.3.1`
* `Fragment`: `1.6.0` -> `1.6.1`
* `Repos`:
* Fixes In `KeyValueRepo.clear()` of almost all inheritors of `KeyValueRepo`
* `Cache`:
* All full caches got `skipStartInvalidate` property. By default, this property is `false` and fully caching repos
will be automatically invalidated on start of their work
## 0.19.7
* `Versions`:
* `Coroutines`: `1.7.1` -> `1.7.2`
## 0.19.6
* `Versions`:
* `Coroutines`: `1.6.4` -> `1.7.1`
* `Ktor`: `2.3.1` -> `2.3.2`
* `Compose`: `1.4.0` -> `1.4.1`
## 0.19.5
* `Repos`:
* `Generator`:
* Fixes in new type generation
## 0.19.4
* `Versions`:
* `Koin`: `3.4.1` -> `3.4.2`
* `Android Fragments`: `1.5.7` -> `1.6.0`
* `Koin`
* `Generator`
* Fixes in new generic generator part
## 0.19.3
* `Koin`
* `Generator`
* New getter methods now available with opportunity to use parameters
* Old notation `*Single` and `*Factory` is deprecated since this release. With old
will be generated new `single*` and `factory*` notations for new generations
* Add opportunity to use generic-oriented koin definitions
## 0.19.2
* `Versions`:
* `Ktor`: `2.3.0` -> `2.3.1`
* `Koin`: `3.4.0` -> `3.4.1`
* `Uuid`: `0.7.0` -> `0.7.1`
## 0.19.1 ## 0.19.1
* `Versions`: * `Versions`:

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,18 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
alias(libs.plugins.jb.compose)
}
apply from: "$mppProjectWithSerializationAndComposePresetPath"
kotlin {
sourceSets {
androidMain {
dependencies {
api project(":micro_utils.android.smalltextfield")
}
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,18 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
alias(libs.plugins.jb.compose)
}
apply from: "$mppProjectWithSerializationAndComposePresetPath"
kotlin {
sourceSets {
androidMain {
dependencies {
api libs.android.compose.material3
}
}
}
}

View File

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

View File

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

View File

@@ -17,6 +17,10 @@ buildscript {
} }
} }
plugins {
alias(libs.plugins.versions)
}
allprojects { allprojects {
repositories { repositories {
mavenLocal() mavenLocal()
@@ -38,3 +42,4 @@ allprojects {
apply from: "./extensions.gradle" apply from: "./extensions.gradle"
apply from: "./github_release.gradle" apply from: "./github_release.gradle"
apply from: "./versions_plugin_setup.gradle"

View File

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

View File

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

View File

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

View File

@@ -1,12 +1,10 @@
package dev.inmo.micro_utils.common package dev.inmo.micro_utils.common
import kotlinx.cinterop.ByteVar import kotlinx.cinterop.*
import kotlinx.cinterop.allocArray
import kotlinx.cinterop.memScoped
import kotlinx.cinterop.toKString
import platform.posix.snprintf import platform.posix.snprintf
import platform.posix.sprintf import platform.posix.sprintf
@OptIn(ExperimentalForeignApi::class)
actual fun Float.fixed(signs: Int): Float { actual fun Float.fixed(signs: Int): Float {
return memScoped { return memScoped {
val buff = allocArray<ByteVar>(Float.SIZE_BYTES * 2) val buff = allocArray<ByteVar>(Float.SIZE_BYTES * 2)
@@ -16,6 +14,7 @@ actual fun Float.fixed(signs: Int): Float {
} }
} }
@OptIn(ExperimentalForeignApi::class)
actual fun Double.fixed(signs: Int): Double { actual fun Double.fixed(signs: Int): Double {
return memScoped { return memScoped {
val buff = allocArray<ByteVar>(Double.SIZE_BYTES * 2) val buff = allocArray<ByteVar>(Double.SIZE_BYTES * 2)

View File

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

View File

@@ -1,12 +1,10 @@
package dev.inmo.micro_utils.common package dev.inmo.micro_utils.common
import kotlinx.cinterop.ByteVar import kotlinx.cinterop.*
import kotlinx.cinterop.allocArray
import kotlinx.cinterop.memScoped
import kotlinx.cinterop.toKString
import platform.posix.snprintf import platform.posix.snprintf
import platform.posix.sprintf import platform.posix.sprintf
@OptIn(ExperimentalForeignApi::class)
actual fun Float.fixed(signs: Int): Float { actual fun Float.fixed(signs: Int): Float {
return memScoped { return memScoped {
val buff = allocArray<ByteVar>(Float.SIZE_BYTES * 2) val buff = allocArray<ByteVar>(Float.SIZE_BYTES * 2)
@@ -16,6 +14,7 @@ actual fun Float.fixed(signs: Int): Float {
} }
} }
@OptIn(ExperimentalForeignApi::class)
actual fun Double.fixed(signs: Int): Double { actual fun Double.fixed(signs: Int): Double {
return memScoped { return memScoped {
val buff = allocArray<ByteVar>(Double.SIZE_BYTES * 2) val buff = allocArray<ByteVar>(Double.SIZE_BYTES * 2)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -19,6 +19,12 @@ allprojects {
} }
releaseMode = (project.hasProperty('RELEASE_MODE') && project.property('RELEASE_MODE') == "true") || System.getenv('RELEASE_MODE') == "true" releaseMode = (project.hasProperty('RELEASE_MODE') && project.property('RELEASE_MODE') == "true") || System.getenv('RELEASE_MODE') == "true"
// String compilerPluginVersionFromProperties = (String) project.properties["compose.kotlinCompilerPluginVersion"]
// String compilerPluginVersionFromLibrariesVersions = libs.versions.compose.kotlin.get()
// composePluginKotlinVersion = compilerPluginVersionFromProperties
// if (compilerPluginVersionFromProperties == null) {
// composePluginKotlinVersion = compilerPluginVersionFromLibrariesVersions
// }
mppProjectWithSerializationPresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerialization.gradle" mppProjectWithSerializationPresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerialization.gradle"
mppProjectWithSerializationAndComposePresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerializationAndCompose.gradle" mppProjectWithSerializationAndComposePresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerializationAndCompose.gradle"

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,9 +3,10 @@ org.gradle.parallel=true
kotlin.js.generate.externals=true kotlin.js.generate.externals=true
kotlin.incremental=true kotlin.incremental=true
kotlin.incremental.js=true kotlin.incremental.js=true
#kotlin.experimental.tryK2=true
android.useAndroidX=true android.useAndroidX=true
android.enableJetifier=true android.enableJetifier=true
org.gradle.jvmargs=-Xmx2g org.gradle.jvmargs=-Xmx2500m
# JS NPM # JS NPM
@@ -14,5 +15,5 @@ crypto_js_version=4.1.1
# Project data # Project data
group=dev.inmo group=dev.inmo
version=0.19.1 version=0.20.7
android_code_version=197 android_code_version=213

View File

@@ -1,42 +1,46 @@
[versions] [versions]
kt = "1.8.20" kt = "1.9.20-Beta2"
kt-serialization = "1.5.1" #compose-kotlin = "1.5.10-beta02"
kt-coroutines = "1.6.4" kt-serialization = "1.6.0"
kt-coroutines = "1.7.3"
kslog = "1.1.1" kslog = "1.2.1"
jb-compose = "1.4.0" jb-compose = "1.5.10-beta02"
jb-exposed = "0.41.1" jb-exposed = "0.44.0"
jb-dokka = "1.8.10" jb-dokka = "1.9.0"
korlibs = "4.0.3" korlibs = "4.0.10"
uuid = "0.7.0" uuid = "0.8.1"
ktor = "2.3.0" ktor = "2.3.5"
gh-release = "2.4.1" gh-release = "2.4.1"
koin = "3.4.0" koin = "3.5.0"
okio = "3.3.0" okio = "3.6.0"
ksp = "1.8.20-1.0.11" ksp = "1.9.20-Beta2-1.0.13"
kotlin-poet = "1.14.0" kotlin-poet = "1.14.2"
versions = "0.49.0"
android-gradle = "7.4.2" android-gradle = "7.4.2"
dexcount = "4.0.0" dexcount = "4.0.0"
android-coreKtx = "1.10.1" android-coreKtx = "1.12.0"
android-recyclerView = "1.3.0" android-recyclerView = "1.3.1"
android-appCompat = "1.6.1" android-appCompat = "1.6.1"
android-fragment = "1.5.7" android-fragment = "1.6.1"
android-espresso = "3.5.1" android-espresso = "3.5.1"
android-test = "1.1.5" android-test = "1.1.5"
android-compose-material3 = "1.1.2"
android-props-minSdk = "21" android-props-minSdk = "21"
android-props-compileSdk = "33" android-props-compileSdk = "34"
android-props-buildTools = "33.0.2" android-props-buildTools = "34.0.0"
[libraries] [libraries]
@@ -81,6 +85,7 @@ jb-exposed = { module = "org.jetbrains.exposed:exposed-core", version.ref = "jb-
android-coreKtx = { module = "androidx.core:core-ktx", version.ref = "android-coreKtx" } android-coreKtx = { module = "androidx.core:core-ktx", version.ref = "android-coreKtx" }
android-recyclerView = { module = "androidx.recyclerview:recyclerview", version.ref = "android-recyclerView" } android-recyclerView = { module = "androidx.recyclerview:recyclerview", version.ref = "android-recyclerView" }
android-appCompat-resources = { module = "androidx.appcompat:appcompat-resources", version.ref = "android-appCompat" } android-appCompat-resources = { module = "androidx.appcompat:appcompat-resources", version.ref = "android-appCompat" }
android-compose-material3 = { module = "androidx.compose.material3:material3", version.ref = "android-compose-material3" }
android-fragment = { module = "androidx.fragment:fragment", version.ref = "android-fragment" } android-fragment = { module = "androidx.fragment:fragment", version.ref = "android-fragment" }
android-espresso = { module = "androidx.test.espresso:espresso-core", version.ref = "android-espresso" } android-espresso = { module = "androidx.test.espresso:espresso-core", version.ref = "android-espresso" }
android-test-junit = { module = "androidx.test.ext:junit", version.ref = "android-test" } android-test-junit = { module = "androidx.test.ext:junit", version.ref = "android-test" }
@@ -109,3 +114,5 @@ buildscript-android-dexcount = { module = "com.getkeepsafe.dexcount:dexcount-gra
[plugins] [plugins]
jb-compose = { id = "org.jetbrains.compose", version.ref = "jb-compose" } jb-compose = { id = "org.jetbrains.compose", version.ref = "jb-compose" }
versions = { id = "com.github.ben-manes.versions", version.ref = "versions" }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,26 @@
package dev.inmo.micro_utils.koin.annotations
import kotlin.reflect.KClass
/**
* Use this annotation to mark files near to which generator should place generated extensions for koin [org.koin.core.scope.Scope]
* and [org.koin.core.Koin]
*
* @param name Name for definitions. This name will be available as extension for [org.koin.core.scope.Scope] and [org.koin.core.Koin]
* @param type Type of extensions. It is base star-typed class
* @param typeArgs Generic types for [type]. For example, if [type] == `Something::class` and [typeArgs] == `G1::class,
* G2::class`, the result type will be `Something<G1, G2>`
* @param nullable In case when true, extension will not throw error when definition has not been registered in koin
* @param generateSingle Generate definition factory with [org.koin.core.module.Module.single]. You will be able to use
* the extension [org.koin.core.module.Module].[name]Single(createdAtStart/* default false */) { /* your definition */ }
* @param generateFactory Generate definition factory with [org.koin.core.module.Module.factory]. You will be able to use
* the extension [org.koin.core.module.Module].[name]Factory { /* your definition */ }
*/
@Target(AnnotationTarget.FILE)
@Repeatable
annotation class GenerateGenericKoinDefinition(
val name: String,
val nullable: Boolean = true,
val generateSingle: Boolean = true,
val generateFactory: Boolean = true
)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

12
local.migrate.folder.sh Executable file
View File

@@ -0,0 +1,12 @@
#!/bin/bash
function renameFolders() {
for folder in $(find . -depth -type d -name "$1");
do
sedString="s/$1/$2/g"
newFolder="$(echo $folder | sed $sedString)"
echo $folder "$newFolder"
done
}
renameFolders "androidTest" "androidUnitTest"

View File

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

View File

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

View File

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

View File

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

View File

@@ -4,7 +4,7 @@ project.group = "$group"
apply from: "$publishGradlePath" apply from: "$publishGradlePath"
kotlin { kotlin {
android { androidTarget {
publishAllLibraryVariants() publishAllLibraryVariants()
} }
@@ -18,6 +18,7 @@ kotlin {
dependencies { dependencies {
implementation kotlin('test-common') implementation kotlin('test-common')
implementation kotlin('test-annotations-common') implementation kotlin('test-annotations-common')
implementation libs.kt.coroutines.test
} }
} }
} }

View File

@@ -22,6 +22,7 @@ kotlin {
dependencies { dependencies {
implementation kotlin('test-common') implementation kotlin('test-common')
implementation kotlin('test-annotations-common') implementation kotlin('test-annotations-common')
implementation libs.kt.coroutines.test
} }
} }

View File

@@ -28,6 +28,7 @@ kotlin {
dependencies { dependencies {
implementation kotlin('test-common') implementation kotlin('test-common')
implementation kotlin('test-annotations-common') implementation kotlin('test-annotations-common')
implementation libs.kt.coroutines.test
} }
} }

View File

@@ -15,7 +15,7 @@ kotlin {
browser() browser()
nodejs() nodejs()
} }
android { androidTarget {
publishAllLibraryVariants() publishAllLibraryVariants()
} }
linuxX64() linuxX64()
@@ -32,6 +32,7 @@ kotlin {
dependencies { dependencies {
implementation kotlin('test-common') implementation kotlin('test-common')
implementation kotlin('test-annotations-common') implementation kotlin('test-annotations-common')
implementation libs.kt.coroutines.test
} }
} }
jvmTest { jvmTest {
@@ -45,7 +46,7 @@ kotlin {
implementation kotlin('test-junit') implementation kotlin('test-junit')
} }
} }
androidTest { androidUnitTest {
dependencies { dependencies {
implementation kotlin('test-junit') implementation kotlin('test-junit')
implementation libs.android.test.junit implementation libs.android.test.junit

View File

@@ -15,7 +15,7 @@ kotlin {
browser() browser()
nodejs() nodejs()
} }
android { androidTarget {
publishAllLibraryVariants() publishAllLibraryVariants()
} }
@@ -31,6 +31,7 @@ kotlin {
dependencies { dependencies {
implementation kotlin('test-common') implementation kotlin('test-common')
implementation kotlin('test-annotations-common') implementation kotlin('test-annotations-common')
implementation libs.kt.coroutines.test
} }
} }
jvmMain { jvmMain {
@@ -54,7 +55,7 @@ kotlin {
implementation kotlin('test-junit') implementation kotlin('test-junit')
} }
} }
androidTest { androidUnitTest {
dependencies { dependencies {
implementation kotlin('test-junit') implementation kotlin('test-junit')
implementation libs.android.test.junit implementation libs.android.test.junit
@@ -72,3 +73,10 @@ java {
sourceCompatibility = JavaVersion.VERSION_1_8 sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8
} }
//compose {
// if (composePluginKotlinVersion != null && !composePluginKotlinVersion.isEmpty()) {
// kotlinCompilerPlugin.set(composePluginKotlinVersion)
// }
//}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,8 @@
package dev.inmo.micro_utils.repos.cache package dev.inmo.micro_utils.repos.cache
import dev.inmo.micro_utils.coroutines.SmartRWLocker
import dev.inmo.micro_utils.coroutines.withReadAcquire
import dev.inmo.micro_utils.coroutines.withWriteLock
import dev.inmo.micro_utils.repos.* import dev.inmo.micro_utils.repos.*
import dev.inmo.micro_utils.repos.cache.cache.KVCache import dev.inmo.micro_utils.repos.cache.cache.KVCache
import dev.inmo.micro_utils.repos.cache.util.actualizeAll import dev.inmo.micro_utils.repos.cache.util.actualizeAll
@@ -10,32 +13,47 @@ import kotlinx.coroutines.flow.*
open class ReadCRUDCacheRepo<ObjectType, IdType>( open class ReadCRUDCacheRepo<ObjectType, IdType>(
protected open val parentRepo: ReadCRUDRepo<ObjectType, IdType>, protected open val parentRepo: ReadCRUDRepo<ObjectType, IdType>,
protected open val kvCache: KVCache<IdType, ObjectType>, protected open val kvCache: KVCache<IdType, ObjectType>,
protected val locker: SmartRWLocker = SmartRWLocker(),
protected open val idGetter: (ObjectType) -> IdType protected open val idGetter: (ObjectType) -> IdType
) : ReadCRUDRepo<ObjectType, IdType> by parentRepo, CommonCacheRepo { ) : ReadCRUDRepo<ObjectType, IdType> by parentRepo, CommonCacheRepo {
override suspend fun getById(id: IdType): ObjectType? = kvCache.get(id) ?: (parentRepo.getById(id) ?.also { override suspend fun getById(id: IdType): ObjectType? = locker.withReadAcquire {
kvCache.set(id, it) kvCache.get(id)
} ?: (parentRepo.getById(id) ?.also {
locker.withWriteLock {
kvCache.set(id, it)
}
}) })
override suspend fun getAll(): Map<IdType, ObjectType> { override suspend fun getAll(): Map<IdType, ObjectType> {
return kvCache.getAll().takeIf { it.size.toLong() == count() } ?: parentRepo.getAll().also { return locker.withReadAcquire {
kvCache.actualizeAll(true) { it } kvCache.getAll()
}.takeIf { it.size.toLong() == count() } ?: parentRepo.getAll().also {
locker.withWriteLock {
kvCache.actualizeAll(true) { it }
}
} }
} }
override suspend fun contains(id: IdType): Boolean = kvCache.contains(id) || parentRepo.contains(id) override suspend fun contains(id: IdType): Boolean = locker.withReadAcquire {
kvCache.contains(id)
} || parentRepo.contains(id)
override suspend fun invalidate() = kvCache.clear() override suspend fun invalidate() = locker.withWriteLock {
kvCache.clear()
}
} }
fun <ObjectType, IdType> ReadCRUDRepo<ObjectType, IdType>.cached( fun <ObjectType, IdType> ReadCRUDRepo<ObjectType, IdType>.cached(
kvCache: KVCache<IdType, ObjectType>, kvCache: KVCache<IdType, ObjectType>,
locker: SmartRWLocker = SmartRWLocker(),
idGetter: (ObjectType) -> IdType idGetter: (ObjectType) -> IdType
) = ReadCRUDCacheRepo(this, kvCache, idGetter) ) = ReadCRUDCacheRepo(this, kvCache, locker, idGetter)
open class WriteCRUDCacheRepo<ObjectType, IdType, InputValueType>( open class WriteCRUDCacheRepo<ObjectType, IdType, InputValueType>(
protected open val parentRepo: WriteCRUDRepo<ObjectType, IdType, InputValueType>, protected open val parentRepo: WriteCRUDRepo<ObjectType, IdType, InputValueType>,
protected open val kvCache: KVCache<IdType, ObjectType>, protected open val kvCache: KeyValueRepo<IdType, ObjectType>,
protected open val scope: CoroutineScope = CoroutineScope(Dispatchers.Default), protected open val scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
protected val locker: SmartRWLocker = SmartRWLocker(),
protected open val idGetter: (ObjectType) -> IdType protected open val idGetter: (ObjectType) -> IdType
) : WriteCRUDRepo<ObjectType, IdType, InputValueType>, CommonCacheRepo { ) : WriteCRUDRepo<ObjectType, IdType, InputValueType>, CommonCacheRepo {
override val newObjectsFlow: Flow<ObjectType> by parentRepo::newObjectsFlow override val newObjectsFlow: Flow<ObjectType> by parentRepo::newObjectsFlow
@@ -43,69 +61,91 @@ open class WriteCRUDCacheRepo<ObjectType, IdType, InputValueType>(
override val deletedObjectsIdsFlow: Flow<IdType> by parentRepo::deletedObjectsIdsFlow override val deletedObjectsIdsFlow: Flow<IdType> by parentRepo::deletedObjectsIdsFlow
val createdObjectsFlowJob = parentRepo.newObjectsFlow.onEach { val createdObjectsFlowJob = parentRepo.newObjectsFlow.onEach {
kvCache.set(idGetter(it), it) locker.withWriteLock {
kvCache.set(idGetter(it), it)
}
}.launchIn(scope) }.launchIn(scope)
val updatedObjectsFlowJob = parentRepo.updatedObjectsFlow.onEach { val updatedObjectsFlowJob = parentRepo.updatedObjectsFlow.onEach {
kvCache.set(idGetter(it), it) locker.withWriteLock {
kvCache.set(idGetter(it), it)
}
}.launchIn(scope) }.launchIn(scope)
val deletedObjectsFlowJob = parentRepo.deletedObjectsIdsFlow.onEach { val deletedObjectsFlowJob = parentRepo.deletedObjectsIdsFlow.onEach {
kvCache.unset(it) locker.withWriteLock {
kvCache.unset(it)
}
}.launchIn(scope) }.launchIn(scope)
override suspend fun deleteById(ids: List<IdType>) = parentRepo.deleteById(ids) override suspend fun deleteById(ids: List<IdType>) = parentRepo.deleteById(ids).also {
locker.withWriteLock {
kvCache.unset(ids)
}
}
override suspend fun update(values: List<UpdatedValuePair<IdType, InputValueType>>): List<ObjectType> { override suspend fun update(values: List<UpdatedValuePair<IdType, InputValueType>>): List<ObjectType> {
val updated = parentRepo.update(values) val updated = parentRepo.update(values)
kvCache.unset(values.map { it.id }) locker.withWriteLock {
kvCache.set(updated.associateBy { idGetter(it) }) kvCache.unset(values.map { it.id })
kvCache.set(updated.associateBy { idGetter(it) })
}
return updated return updated
} }
override suspend fun update(id: IdType, value: InputValueType): ObjectType? { override suspend fun update(id: IdType, value: InputValueType): ObjectType? {
return parentRepo.update(id, value) ?.also { return parentRepo.update(id, value) ?.also {
kvCache.unset(id) locker.withWriteLock {
kvCache.set(idGetter(it), it) kvCache.unset(id)
kvCache.set(idGetter(it), it)
}
} }
} }
override suspend fun create(values: List<InputValueType>): List<ObjectType> { override suspend fun create(values: List<InputValueType>): List<ObjectType> {
val created = parentRepo.create(values) val created = parentRepo.create(values)
kvCache.set( locker.withWriteLock {
created.associateBy { idGetter(it) } kvCache.set(
) created.associateBy { idGetter(it) }
)
}
return created return created
} }
override suspend fun invalidate() = kvCache.clear() override suspend fun invalidate() = locker.withWriteLock {
kvCache.clear()
}
} }
fun <ObjectType, IdType, InputType> WriteCRUDRepo<ObjectType, IdType, InputType>.caching( fun <ObjectType, IdType, InputType> WriteCRUDRepo<ObjectType, IdType, InputType>.caching(
kvCache: KVCache<IdType, ObjectType>, kvCache: KVCache<IdType, ObjectType>,
scope: CoroutineScope, scope: CoroutineScope,
locker: SmartRWLocker = SmartRWLocker(),
idGetter: (ObjectType) -> IdType idGetter: (ObjectType) -> IdType
) = WriteCRUDCacheRepo(this, kvCache, scope, idGetter) ) = WriteCRUDCacheRepo(this, kvCache, scope, locker, idGetter)
open class CRUDCacheRepo<ObjectType, IdType, InputValueType>( open class CRUDCacheRepo<ObjectType, IdType, InputValueType>(
override val parentRepo: CRUDRepo<ObjectType, IdType, InputValueType>, override val parentRepo: CRUDRepo<ObjectType, IdType, InputValueType>,
kvCache: KVCache<IdType, ObjectType>, kvCache: KVCache<IdType, ObjectType>,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default), scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
locker: SmartRWLocker = SmartRWLocker(),
idGetter: (ObjectType) -> IdType idGetter: (ObjectType) -> IdType
) : ReadCRUDCacheRepo<ObjectType, IdType>( ) : ReadCRUDCacheRepo<ObjectType, IdType>(
parentRepo, parentRepo,
kvCache, kvCache,
locker,
idGetter idGetter
), ),
WriteCRUDRepo<ObjectType, IdType, InputValueType> by WriteCRUDCacheRepo( WriteCRUDRepo<ObjectType, IdType, InputValueType> by WriteCRUDCacheRepo(
parentRepo, parentRepo,
kvCache, kvCache,
scope, scope,
locker,
idGetter idGetter
), ),
CRUDRepo<ObjectType, IdType, InputValueType> CRUDRepo<ObjectType, IdType, InputValueType>
@@ -113,5 +153,6 @@ open class CRUDCacheRepo<ObjectType, IdType, InputValueType>(
fun <ObjectType, IdType, InputType> CRUDRepo<ObjectType, IdType, InputType>.cached( fun <ObjectType, IdType, InputType> CRUDRepo<ObjectType, IdType, InputType>.cached(
kvCache: KVCache<IdType, ObjectType>, kvCache: KVCache<IdType, ObjectType>,
scope: CoroutineScope, scope: CoroutineScope,
locker: SmartRWLocker = SmartRWLocker(),
idGetter: (ObjectType) -> IdType idGetter: (ObjectType) -> IdType
) = CRUDCacheRepo(this, kvCache, scope, idGetter) ) = CRUDCacheRepo(this, kvCache, scope, locker, idGetter)

View File

@@ -1,5 +1,8 @@
package dev.inmo.micro_utils.repos.cache package dev.inmo.micro_utils.repos.cache
import dev.inmo.micro_utils.coroutines.SmartRWLocker
import dev.inmo.micro_utils.coroutines.withReadAcquire
import dev.inmo.micro_utils.coroutines.withWriteLock
import dev.inmo.micro_utils.pagination.* import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.repos.* import dev.inmo.micro_utils.repos.*
import dev.inmo.micro_utils.repos.cache.cache.KVCache import dev.inmo.micro_utils.repos.cache.cache.KVCache
@@ -10,45 +13,82 @@ import kotlinx.coroutines.flow.*
open class ReadKeyValueCacheRepo<Key,Value>( open class ReadKeyValueCacheRepo<Key,Value>(
protected open val parentRepo: ReadKeyValueRepo<Key, Value>, protected open val parentRepo: ReadKeyValueRepo<Key, Value>,
protected open val kvCache: KVCache<Key, Value>, protected open val kvCache: KVCache<Key, Value>,
protected val locker: SmartRWLocker = SmartRWLocker(),
) : ReadKeyValueRepo<Key,Value> by parentRepo, CommonCacheRepo { ) : ReadKeyValueRepo<Key,Value> by parentRepo, CommonCacheRepo {
override suspend fun get(k: Key): Value? = kvCache.get(k) ?: parentRepo.get(k) ?.also { kvCache.set(k, it) } override suspend fun get(k: Key): Value? = locker.withReadAcquire {
override suspend fun contains(key: Key): Boolean = kvCache.contains(key) || parentRepo.contains(key) kvCache.get(k)
} ?: parentRepo.get(k) ?.also {
locker.withWriteLock {
kvCache.set(k, it)
}
}
override suspend fun contains(key: Key): Boolean = locker.withReadAcquire {
kvCache.contains(key)
} || parentRepo.contains(key)
override suspend fun values(pagination: Pagination, reversed: Boolean): PaginationResult<Value> { override suspend fun values(pagination: Pagination, reversed: Boolean): PaginationResult<Value> {
return keys(pagination, reversed).let { return locker.withReadAcquire {
it.changeResultsUnchecked( keys(pagination, reversed).let {
it.results.mapNotNull { it.changeResultsUnchecked(
get(it) it.results.mapNotNull {
} get(it)
) }
)
}
} }
} }
override suspend fun getAll(): Map<Key, Value> = kvCache.getAll().takeIf { override suspend fun getAll(): Map<Key, Value> = locker.withReadAcquire {
kvCache.getAll()
}.takeIf {
it.size.toLong() == count() it.size.toLong() == count()
} ?: parentRepo.getAll().also { } ?: parentRepo.getAll().also {
kvCache.set(it) locker.withWriteLock {
kvCache.set(it)
}
} }
override suspend fun invalidate() = kvCache.clear() override suspend fun invalidate() = locker.withWriteLock {
kvCache.clear()
}
} }
fun <Key, Value> ReadKeyValueRepo<Key, Value>.cached( fun <Key, Value> ReadKeyValueRepo<Key, Value>.cached(
kvCache: KVCache<Key, Value> kvCache: KVCache<Key, Value>,
) = ReadKeyValueCacheRepo(this, kvCache) locker: SmartRWLocker = SmartRWLocker(),
) = ReadKeyValueCacheRepo(this, kvCache, locker)
open class KeyValueCacheRepo<Key,Value>( open class KeyValueCacheRepo<Key,Value>(
parentRepo: KeyValueRepo<Key, Value>, override val parentRepo: KeyValueRepo<Key, Value>,
kvCache: KVCache<Key, Value>, kvCache: KVCache<Key, Value>,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default) scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
) : ReadKeyValueCacheRepo<Key,Value>(parentRepo, kvCache), KeyValueRepo<Key,Value>, WriteKeyValueRepo<Key, Value> by parentRepo, CommonCacheRepo { locker: SmartRWLocker = SmartRWLocker(),
protected val onNewJob = parentRepo.onNewValue.onEach { kvCache.set(it.first, it.second) }.launchIn(scope) ) : ReadKeyValueCacheRepo<Key,Value>(parentRepo, kvCache, locker), KeyValueRepo<Key,Value>, WriteKeyValueRepo<Key, Value> by parentRepo, CommonCacheRepo {
protected val onRemoveJob = parentRepo.onValueRemoved.onEach { kvCache.unset(it) }.launchIn(scope) protected val onNewJob = parentRepo.onNewValue.onEach {
locker.withWriteLock {
kvCache.set(it.first, it.second)
}
}.launchIn(scope)
protected val onRemoveJob = parentRepo.onValueRemoved.onEach {
locker.withWriteLock {
kvCache.unset(it)
}
}.launchIn(scope)
override suspend fun invalidate() = kvCache.clear() override suspend fun invalidate() = locker.withWriteLock {
kvCache.clear()
}
override suspend fun clear() {
parentRepo.clear()
locker.withWriteLock {
kvCache.clear()
}
}
} }
fun <Key, Value> KeyValueRepo<Key, Value>.cached( fun <Key, Value> KeyValueRepo<Key, Value>.cached(
kvCache: KVCache<Key, Value>, kvCache: KVCache<Key, Value>,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default) scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
) = KeyValueCacheRepo(this, kvCache, scope) locker: SmartRWLocker = SmartRWLocker(),
) = KeyValueCacheRepo(this, kvCache, scope, locker)

View File

@@ -1,5 +1,8 @@
package dev.inmo.micro_utils.repos.cache package dev.inmo.micro_utils.repos.cache
import dev.inmo.micro_utils.coroutines.SmartRWLocker
import dev.inmo.micro_utils.coroutines.withReadAcquire
import dev.inmo.micro_utils.coroutines.withWriteLock
import dev.inmo.micro_utils.pagination.* import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.pagination.utils.* import dev.inmo.micro_utils.pagination.utils.*
import dev.inmo.micro_utils.repos.* import dev.inmo.micro_utils.repos.*
@@ -10,59 +13,85 @@ import kotlinx.coroutines.flow.*
open class ReadKeyValuesCacheRepo<Key,Value>( open class ReadKeyValuesCacheRepo<Key,Value>(
protected open val parentRepo: ReadKeyValuesRepo<Key, Value>, protected open val parentRepo: ReadKeyValuesRepo<Key, Value>,
protected open val kvCache: KVCache<Key, List<Value>> protected open val kvCache: KVCache<Key, List<Value>>,
protected val locker: SmartRWLocker = SmartRWLocker(),
) : ReadKeyValuesRepo<Key,Value> by parentRepo, CommonCacheRepo { ) : ReadKeyValuesRepo<Key,Value> by parentRepo, CommonCacheRepo {
override suspend fun get(k: Key, pagination: Pagination, reversed: Boolean): PaginationResult<Value> { override suspend fun get(k: Key, pagination: Pagination, reversed: Boolean): PaginationResult<Value> {
return getAll(k, reversed).paginate( return locker.withReadAcquire {
getAll(k, reversed)
}.paginate(
pagination pagination
) )
} }
override suspend fun getAll(k: Key, reversed: Boolean): List<Value> { override suspend fun getAll(k: Key, reversed: Boolean): List<Value> {
return kvCache.get(k) ?.let { return locker.withReadAcquire {
kvCache.get(k)
} ?.let {
if (reversed) it.reversed() else it if (reversed) it.reversed() else it
} ?: parentRepo.getAll(k, reversed).also { } ?: parentRepo.getAll(k, reversed).also {
kvCache.set(k, it) locker.withWriteLock {
kvCache.set(k, it)
}
} }
} }
override suspend fun contains(k: Key, v: Value): Boolean = kvCache.get(k) ?.contains(v) ?: (parentRepo.contains(k, v).also { override suspend fun contains(k: Key, v: Value): Boolean = locker.withReadAcquire {
kvCache.get(k)
} ?.contains(v) ?: (parentRepo.contains(k, v).also {
if (it) { if (it) {
kvCache.unset(k) // clear as invalid locker.withWriteLock {
kvCache.unset(k) // clear as invalid
}
} }
}) })
override suspend fun contains(k: Key): Boolean = kvCache.contains(k) || parentRepo.contains(k) override suspend fun contains(k: Key): Boolean = locker.withReadAcquire {
kvCache.contains(k)
} || parentRepo.contains(k)
override suspend fun invalidate() = kvCache.clear() override suspend fun invalidate() = locker.withWriteLock {
kvCache.clear()
}
} }
fun <Key, Value> ReadKeyValuesRepo<Key, Value>.cached( fun <Key, Value> ReadKeyValuesRepo<Key, Value>.cached(
kvCache: KVCache<Key, List<Value>> kvCache: KVCache<Key, List<Value>>,
) = ReadKeyValuesCacheRepo(this, kvCache) locker: SmartRWLocker = SmartRWLocker(),
) = ReadKeyValuesCacheRepo(this, kvCache, locker)
open class KeyValuesCacheRepo<Key,Value>( open class KeyValuesCacheRepo<Key,Value>(
parentRepo: KeyValuesRepo<Key, Value>, parentRepo: KeyValuesRepo<Key, Value>,
kvCache: KVCache<Key, List<Value>>, kvCache: KVCache<Key, List<Value>>,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default) scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
) : ReadKeyValuesCacheRepo<Key,Value>(parentRepo, kvCache), KeyValuesRepo<Key,Value>, WriteKeyValuesRepo<Key,Value> by parentRepo, CommonCacheRepo { locker: SmartRWLocker = SmartRWLocker(),
) : ReadKeyValuesCacheRepo<Key,Value>(parentRepo, kvCache, locker), KeyValuesRepo<Key,Value>, WriteKeyValuesRepo<Key,Value> by parentRepo, CommonCacheRepo {
protected val onNewJob = parentRepo.onNewValue.onEach { (k, v) -> protected val onNewJob = parentRepo.onNewValue.onEach { (k, v) ->
kvCache.set( locker.withWriteLock {
k, kvCache.set(
kvCache.get(k) ?.plus(v) ?: return@onEach k,
) kvCache.get(k) ?.plus(v) ?: return@onEach
)
}
}.launchIn(scope) }.launchIn(scope)
protected val onRemoveJob = parentRepo.onValueRemoved.onEach { (k, v) -> protected val onRemoveJob = parentRepo.onValueRemoved.onEach { (k, v) ->
kvCache.set( locker.withWriteLock {
k, kvCache.set(
kvCache.get(k) ?.minus(v) ?: return@onEach k,
) kvCache.get(k)?.minus(v) ?: return@onEach
)
}
}.launchIn(scope) }.launchIn(scope)
protected val onDataClearedJob = parentRepo.onDataCleared.onEach { protected val onDataClearedJob = parentRepo.onDataCleared.onEach {
kvCache.unset(it) locker.withWriteLock {
kvCache.unset(it)
}
}.launchIn(scope) }.launchIn(scope)
override suspend fun invalidate() = kvCache.clear() override suspend fun invalidate() = locker.withWriteLock {
kvCache.clear()
}
} }
fun <Key, Value> KeyValuesRepo<Key, Value>.cached( fun <Key, Value> KeyValuesRepo<Key, Value>.cached(
kvCache: KVCache<Key, List<Value>>, kvCache: KVCache<Key, List<Value>>,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default) scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
) = KeyValuesCacheRepo(this, kvCache, scope) locker: SmartRWLocker = SmartRWLocker(),
) = KeyValuesCacheRepo(this, kvCache, scope, locker)

View File

@@ -3,6 +3,7 @@ package dev.inmo.micro_utils.repos.cache.cache
/** /**
* This interface declares that current type of [KVCache] will contains all the data all the time of its life * This interface declares that current type of [KVCache] will contains all the data all the time of its life
*/ */
@Deprecated("This type of KV repos is obsolete and will be removed soon", ReplaceWith("KeyValueRepo<K, V>", "dev.inmo.micro_utils.repos.KeyValueRepo"))
interface FullKVCache<K, V> : KVCache<K, V> { interface FullKVCache<K, V> : KVCache<K, V> {
companion object companion object
} }

View File

@@ -5,7 +5,9 @@ import dev.inmo.micro_utils.repos.MapKeyValueRepo
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
open class SimpleFullKVCache<K, V>( @Deprecated("This type of KV repos is obsolete and will be removed soon", ReplaceWith("MapKeyValueRepo<K, V>()", "dev.inmo.micro_utils.repos.MapKeyValueRepo"))
class SimpleFullKVCache<K, V>(
private val kvParent: KeyValueRepo<K, V> = MapKeyValueRepo<K, V>() private val kvParent: KeyValueRepo<K, V> = MapKeyValueRepo<K, V>()
) : FullKVCache<K, V>, KeyValueRepo<K, V> by kvParent { ) : FullKVCache<K, V>, KeyValueRepo<K, V> by kvParent {
protected val syncMutex = Mutex() protected val syncMutex = Mutex()
@@ -21,8 +23,15 @@ open class SimpleFullKVCache<K, V>(
kvParent.unset(toUnset) kvParent.unset(toUnset)
} }
} }
override suspend fun clear() {
syncMutex.withLock {
kvParent.clear()
}
}
} }
@Deprecated("This type of KV repos is obsolete and will be removed soon", ReplaceWith("kvParent", "dev.inmo.micro_utils.repos.MapKeyValueRepo"))
inline fun <K, V> FullKVCache( inline fun <K, V> FullKVCache(
kvParent: KeyValueRepo<K, V> = MapKeyValueRepo<K, V>() kvParent: KeyValueRepo<K, V> = MapKeyValueRepo<K, V>()
) = SimpleFullKVCache<K, V>(kvParent) ) = SimpleFullKVCache<K, V>(kvParent)

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