mirror of
				https://github.com/InsanusMokrassar/MicroUtils.git
				synced 2025-10-31 12:10:29 +00:00 
			
		
		
		
	Compare commits
	
		
			36 Commits
		
	
	
		
			v0.25.7
			...
			renovate/k
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 5b7f31de22 | ||
| 9dad353957 | |||
| 89e16b7bdb | |||
| c2965da341 | |||
| ffb072dc5f | |||
| a247dbcb02 | |||
| 1dd71175f4 | |||
| bbe62c0e7b | |||
| 9822ff321b | |||
| b485d485ef | |||
| 0b3d445109 | |||
| d7e48940bc | |||
| 1049eb0fe7 | |||
| c871ef5635 | |||
| 7edfcb20c4 | |||
| 7a1438a2c0 | |||
| 2af8cba8cd | |||
| 27d74c0a62 | |||
| f86d1bfe06 | |||
| 7cc5972ff7 | |||
| 3bbf978b00 | |||
| ed36467600 | |||
| dd0de327fc | |||
| dccd3ce8fd | |||
| fa45e7b696 | |||
| 57f009e8aa | |||
| 04b633a5ea | |||
| 20d42b05bb | |||
| 91ba50f1ff | |||
| f4476c99f9 | |||
| 50f3f586ab | |||
| 36a2d7ec8e | |||
| 4890b5833e | |||
| e20ab89688 | |||
| e557ba8184 | |||
| 8540e21d5a | 
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,4 +1,5 @@ | |||||||
| .idea | .idea | ||||||
|  | .vscode | ||||||
| .kotlin | .kotlin | ||||||
| out/* | out/* | ||||||
| *.iml | *.iml | ||||||
|   | |||||||
							
								
								
									
										66
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										66
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -1,5 +1,71 @@ | |||||||
| # Changelog | # Changelog | ||||||
|  |  | ||||||
|  | ## 0.26.6 | ||||||
|  |  | ||||||
|  | * `Versions`: | ||||||
|  |     * `Ktor`: `3.3.0` -> `3.3.1` | ||||||
|  |     * `Okio`: `3.16.0` -> `3.16.2` | ||||||
|  |  | ||||||
|  | ## 0.26.5 | ||||||
|  |  | ||||||
|  | * `Versions`: | ||||||
|  |     * `Kotlin`: `2.2.10` -> `2.2.20` | ||||||
|  |     * `KSLog`: `1.5.0` -> `1.5.1` | ||||||
|  |     * `Ktor`: `3.2.3` -> `3.3.0` | ||||||
|  |     * `KotlinX Browser`: `0.3` -> `0.5.0` | ||||||
|  |     * `Koin`: `4.1.0` -> `4.1.1` | ||||||
|  |  | ||||||
|  | ## 0.26.4 | ||||||
|  |  | ||||||
|  | * `Common`: | ||||||
|  |   * Add expect/actual `MPPFilePathSeparator` | ||||||
|  |   * Fix `FileName` realization to take care about system file path separator | ||||||
|  |  | ||||||
|  | ## 0.26.3 | ||||||
|  |  | ||||||
|  | * `Versions`: | ||||||
|  |   * `Kotlin`: `2.2.0` -> `2.2.10` | ||||||
|  |   * `KSP`: `2.2.0-2.0.2` -> `2.2.10-2.0.2` | ||||||
|  |   * `Android CoreKTX`: `1.16.0` -> `1.17.0` | ||||||
|  |   * `Android Fragment`: `1.8.8` -> `1.8.9` | ||||||
|  |  | ||||||
|  | ## 0.26.2 | ||||||
|  |  | ||||||
|  | * `Versions`: | ||||||
|  |   * `Ktor`: `3.2.2` -> `3.2.3` | ||||||
|  |   * `Okio`: `3.15.0` -> `3.16.0` | ||||||
|  | * `Coroutines`: | ||||||
|  |   * Rename `SpecialMutableStateFlow` to `MutableRedeliverStateFlow` | ||||||
|  |  | ||||||
|  | ## 0.26.1 | ||||||
|  |  | ||||||
|  | * `Versions`: | ||||||
|  |   * `Compose`: `1.8.1` -> `1.8.2` | ||||||
|  |   * `Ktor`: `3.2.1` -> `3.2.2` | ||||||
|  | * `Coroutines`: | ||||||
|  |   * Add opportunity to pass logger in subscribe async | ||||||
|  |  | ||||||
|  | ## 0.26.0 | ||||||
|  |  | ||||||
|  | **WARNING!!! SINCE THIS VERSION IF YOU WANT TO USE SOME OF KSP MODULES, SET `ksp.useKSP2=false` IN YOUR `gradle.properties`** (see [gh issue 2491](https://github.com/google/ksp/issues/2491)) | ||||||
|  |  | ||||||
|  | * `Versions`: | ||||||
|  |   * `Kotlin`: `2.1.21` -> `2.2.0` | ||||||
|  |   * `Serialization`: `1.8.1` -> `1.9.0` | ||||||
|  |   * `KSLog`: `1.4.2` -> `1.5.0` | ||||||
|  |   * `Ktor`: `3.1.3` -> `3.2.1` | ||||||
|  |   * `Koin`: `4.0.4` -> `4.1.0` | ||||||
|  |   * `Okio`: `3.12.0` -> `3.15.0` | ||||||
|  |   * `KSP`: `2.1.20-1.0.31` -> `2.2.0-2.0.2` | ||||||
|  |   * `kotlin-poet`: `1.18.1` -> `2.2.0` | ||||||
|  |  | ||||||
|  | ## 0.25.8 | ||||||
|  |  | ||||||
|  | * `Pagination`: | ||||||
|  |   * `Compose`: | ||||||
|  |     * New function `rememberInfinityPagedComponentContext` to create `InfinityPagedComponentContext` | ||||||
|  |     * New variants of `InfinityPagedComponent` component | ||||||
|  |  | ||||||
| ## 0.25.7 | ## 0.25.7 | ||||||
|  |  | ||||||
| * `Versions`: | * `Versions`: | ||||||
|   | |||||||
| @@ -28,8 +28,8 @@ if ((project.hasProperty('SONATYPE_USER') || System.getenv('SONATYPE_USER') != n | |||||||
|         centralPortal { |         centralPortal { | ||||||
|             username = project.hasProperty('SONATYPE_USER') ? project.property('SONATYPE_USER') : System.getenv('SONATYPE_USER') |             username = project.hasProperty('SONATYPE_USER') ? project.property('SONATYPE_USER') : System.getenv('SONATYPE_USER') | ||||||
|             password = project.hasProperty('SONATYPE_PASSWORD') ? project.property('SONATYPE_PASSWORD') : System.getenv('SONATYPE_PASSWORD') |             password = project.hasProperty('SONATYPE_PASSWORD') ? project.property('SONATYPE_PASSWORD') : System.getenv('SONATYPE_PASSWORD') | ||||||
|             verificationTimeout = Duration.ofHours(4) |             validationTimeout = Duration.ofHours(4) | ||||||
|             publishingType = "USER_MANAGED" |             publishingType = System.getenv('PUBLISHING_TYPE') != "" ? System.getenv('PUBLISHING_TYPE') : "USER_MANAGED" | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         publishAllProjectsProbablyBreakingProjectIsolation() |         publishAllProjectsProbablyBreakingProjectIsolation() | ||||||
|   | |||||||
| @@ -2,11 +2,9 @@ import androidx.compose.runtime.remember | |||||||
| import androidx.compose.ui.test.ExperimentalTestApi | import androidx.compose.ui.test.ExperimentalTestApi | ||||||
| import androidx.compose.ui.test.runComposeUiTest | import androidx.compose.ui.test.runComposeUiTest | ||||||
| import dev.inmo.micro_utils.common.compose.LoadableComponent | import dev.inmo.micro_utils.common.compose.LoadableComponent | ||||||
| import dev.inmo.micro_utils.coroutines.SpecialMutableStateFlow | import dev.inmo.micro_utils.coroutines.MutableRedeliverStateFlow | ||||||
| import kotlinx.coroutines.flow.MutableSharedFlow |  | ||||||
| import kotlinx.coroutines.flow.filter | import kotlinx.coroutines.flow.filter | ||||||
| import kotlinx.coroutines.flow.first | import kotlinx.coroutines.flow.first | ||||||
| import kotlinx.coroutines.flow.firstOrNull |  | ||||||
| import org.jetbrains.annotations.TestOnly | import org.jetbrains.annotations.TestOnly | ||||||
| import kotlin.test.Test | import kotlin.test.Test | ||||||
| import kotlin.test.assertTrue | import kotlin.test.assertTrue | ||||||
| @@ -16,8 +14,8 @@ class LoadableComponentTests { | |||||||
|     @Test |     @Test | ||||||
|     @TestOnly |     @TestOnly | ||||||
|     fun testSimpleLoad() = runComposeUiTest { |     fun testSimpleLoad() = runComposeUiTest { | ||||||
|         val loadingFlow = SpecialMutableStateFlow<Int>(0) |         val loadingFlow = MutableRedeliverStateFlow<Int>(0) | ||||||
|         val loadedFlow = SpecialMutableStateFlow<Int>(0) |         val loadedFlow = MutableRedeliverStateFlow<Int>(0) | ||||||
|         setContent { |         setContent { | ||||||
|             LoadableComponent<Int>({ |             LoadableComponent<Int>({ | ||||||
|                 loadingFlow.filter { it == 1 }.first() |                 loadingFlow.filter { it == 1 }.first() | ||||||
|   | |||||||
| @@ -7,7 +7,7 @@ import kotlin.jvm.JvmInline | |||||||
| @JvmInline | @JvmInline | ||||||
| value class FileName(val string: String) { | value class FileName(val string: String) { | ||||||
|     val name: String |     val name: String | ||||||
|         get() = withoutSlashAtTheEnd.takeLastWhile { it != '/' } |         get() = withoutSlashAtTheEnd.takeLastWhile { it != MPPFilePathSeparator } | ||||||
|     val extension: String |     val extension: String | ||||||
|         get() = name.takeLastWhile { it != '.' } |         get() = name.takeLastWhile { it != '.' } | ||||||
|     val nameWithoutExtension: String |     val nameWithoutExtension: String | ||||||
| @@ -18,7 +18,7 @@ value class FileName(val string: String) { | |||||||
|             } ?: filename |             } ?: filename | ||||||
|         } |         } | ||||||
|     val withoutSlashAtTheEnd: String |     val withoutSlashAtTheEnd: String | ||||||
|         get() = string.dropLastWhile { it == '/' } |         get() = string.dropLastWhile { it == MPPFilePathSeparator } | ||||||
|     override fun toString(): String = string |     override fun toString(): String = string | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -26,6 +26,7 @@ value class FileName(val string: String) { | |||||||
| expect class MPPFile | expect class MPPFile | ||||||
|  |  | ||||||
| expect val MPPFile.filename: FileName | expect val MPPFile.filename: FileName | ||||||
|  | expect val MPPFilePathSeparator: Char | ||||||
| expect val MPPFile.filesize: Long | expect val MPPFile.filesize: Long | ||||||
| expect val MPPFile.bytesAllocatorSync: ByteArrayAllocator | expect val MPPFile.bytesAllocatorSync: ByteArrayAllocator | ||||||
| expect val MPPFile.bytesAllocator: SuspendByteArrayAllocator | expect val MPPFile.bytesAllocator: SuspendByteArrayAllocator | ||||||
|   | |||||||
| @@ -35,6 +35,10 @@ private suspend fun MPPFile.dirtyReadBytes(): ByteArray = readBytesPromise().awa | |||||||
|  */ |  */ | ||||||
| actual val MPPFile.filename: FileName | actual val MPPFile.filename: FileName | ||||||
|     get() = FileName(name) |     get() = FileName(name) | ||||||
|  |  | ||||||
|  | actual val MPPFilePathSeparator: Char | ||||||
|  |     get() = '/' | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * @suppress |  * @suppress | ||||||
|  */ |  */ | ||||||
|   | |||||||
| @@ -14,6 +14,10 @@ actual typealias MPPFile = File | |||||||
|  */ |  */ | ||||||
| actual val MPPFile.filename: FileName | actual val MPPFile.filename: FileName | ||||||
|     get() = FileName(name) |     get() = FileName(name) | ||||||
|  |  | ||||||
|  | actual val MPPFilePathSeparator: Char | ||||||
|  |     get() = File.separatorChar | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * @suppress |  * @suppress | ||||||
|  */ |  */ | ||||||
|   | |||||||
| @@ -11,6 +11,10 @@ actual typealias MPPFile = Path | |||||||
|  */ |  */ | ||||||
| actual val MPPFile.filename: FileName | actual val MPPFile.filename: FileName | ||||||
|     get() = FileName(toString()) |     get() = FileName(toString()) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | actual val MPPFilePathSeparator: Char = Path.DIRECTORY_SEPARATOR.first() | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * @suppress |  * @suppress | ||||||
|  */ |  */ | ||||||
|   | |||||||
| @@ -37,6 +37,10 @@ private suspend fun MPPFile.dirtyReadBytes(): ByteArray = readBytesPromise().awa | |||||||
|  */ |  */ | ||||||
| actual val MPPFile.filename: FileName | actual val MPPFile.filename: FileName | ||||||
|     get() = FileName(name) |     get() = FileName(name) | ||||||
|  |  | ||||||
|  | actual val MPPFilePathSeparator: Char | ||||||
|  |     get() = '/' | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * @suppress |  * @suppress | ||||||
|  */ |  */ | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ package dev.inmo.micro_utils.coroutines.compose | |||||||
| import androidx.compose.runtime.Composable | import androidx.compose.runtime.Composable | ||||||
| import androidx.compose.runtime.collectAsState | import androidx.compose.runtime.collectAsState | ||||||
| import androidx.compose.runtime.remember | import androidx.compose.runtime.remember | ||||||
| import dev.inmo.micro_utils.coroutines.SpecialMutableStateFlow | import dev.inmo.micro_utils.coroutines.MutableRedeliverStateFlow | ||||||
| import kotlinx.coroutines.flow.StateFlow | import kotlinx.coroutines.flow.StateFlow | ||||||
| import kotlinx.coroutines.flow.asStateFlow | import kotlinx.coroutines.flow.asStateFlow | ||||||
| import kotlinx.coroutines.flow.debounce | import kotlinx.coroutines.flow.debounce | ||||||
| @@ -16,7 +16,7 @@ import org.jetbrains.compose.web.css.StyleSheet | |||||||
|  * to add `Style(stylesheet)` on every compose function call |  * to add `Style(stylesheet)` on every compose function call | ||||||
|  */ |  */ | ||||||
| object StyleSheetsAggregator { | object StyleSheetsAggregator { | ||||||
|     private val _stylesFlow = SpecialMutableStateFlow<Set<CSSRulesHolder>>(emptySet()) |     private val _stylesFlow = MutableRedeliverStateFlow<Set<CSSRulesHolder>>(emptySet()) | ||||||
|     val stylesFlow: StateFlow<Set<CSSRulesHolder>> = _stylesFlow.asStateFlow() |     val stylesFlow: StateFlow<Set<CSSRulesHolder>> = _stylesFlow.asStateFlow() | ||||||
|  |  | ||||||
|     @Composable |     @Composable | ||||||
|   | |||||||
| @@ -2,7 +2,7 @@ import androidx.compose.material.Button | |||||||
| import androidx.compose.material.Text | import androidx.compose.material.Text | ||||||
| import androidx.compose.runtime.collectAsState | import androidx.compose.runtime.collectAsState | ||||||
| import androidx.compose.ui.test.* | import androidx.compose.ui.test.* | ||||||
| import dev.inmo.micro_utils.coroutines.SpecialMutableStateFlow | import dev.inmo.micro_utils.coroutines.MutableRedeliverStateFlow | ||||||
| import org.jetbrains.annotations.TestOnly | import org.jetbrains.annotations.TestOnly | ||||||
| import kotlin.test.Test | import kotlin.test.Test | ||||||
|  |  | ||||||
| @@ -11,7 +11,7 @@ class FlowStateTests { | |||||||
|     @Test |     @Test | ||||||
|     @TestOnly |     @TestOnly | ||||||
|     fun simpleTest() = runComposeUiTest { |     fun simpleTest() = runComposeUiTest { | ||||||
|         val flowState = SpecialMutableStateFlow(0) |         val flowState = MutableRedeliverStateFlow(0) | ||||||
|         setContent { |         setContent { | ||||||
|             Button({ flowState.value++ }) { Text("Click") } |             Button({ flowState.value++ }) { Text("Click") } | ||||||
|             Text(flowState.collectAsState().value.toString()) |             Text(flowState.collectAsState().value.toString()) | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| package dev.inmo.micro_utils.coroutines | package dev.inmo.micro_utils.coroutines | ||||||
|  |  | ||||||
|  | import dev.inmo.kslog.common.KSLog | ||||||
| import kotlinx.coroutines.* | import kotlinx.coroutines.* | ||||||
| import kotlinx.coroutines.channels.Channel | import kotlinx.coroutines.channels.Channel | ||||||
| import kotlinx.coroutines.flow.consumeAsFlow | import kotlinx.coroutines.flow.consumeAsFlow | ||||||
| @@ -7,13 +8,15 @@ import kotlinx.coroutines.flow.consumeAsFlow | |||||||
| fun <T> CoroutineScope.actorAsync( | fun <T> CoroutineScope.actorAsync( | ||||||
|     channelCapacity: Int = Channel.UNLIMITED, |     channelCapacity: Int = Channel.UNLIMITED, | ||||||
|     markerFactory: suspend (T) -> Any? = { null }, |     markerFactory: suspend (T) -> Any? = { null }, | ||||||
|  |     logger: KSLog = KSLog, | ||||||
|     block: suspend (T) -> Unit |     block: suspend (T) -> Unit | ||||||
| ): Channel<T> { | ): Channel<T> { | ||||||
|     val channel = Channel<T>(channelCapacity) |     val channel = Channel<T>(channelCapacity) | ||||||
|     channel.consumeAsFlow().subscribeAsync(this, markerFactory, block) |     channel.consumeAsFlow().subscribeAsync(this, markerFactory, logger, block) | ||||||
|     return channel |     return channel | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @Deprecated("Use standard actosAsync instead", ReplaceWith("actorAsync(channelCapacity, markerFactory, block = block)", "dev.inmo.micro_utils.coroutines.actorAsync")) | ||||||
| inline fun <T> CoroutineScope.safeActorAsync( | inline fun <T> CoroutineScope.safeActorAsync( | ||||||
|     channelCapacity: Int = Channel.UNLIMITED, |     channelCapacity: Int = Channel.UNLIMITED, | ||||||
|     noinline onException: ExceptionHandler<Unit> = defaultSafelyExceptionHandler, |     noinline onException: ExceptionHandler<Unit> = defaultSafelyExceptionHandler, | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| package dev.inmo.micro_utils.coroutines | package dev.inmo.micro_utils.coroutines | ||||||
|  |  | ||||||
|  | import dev.inmo.kslog.common.KSLog | ||||||
| import kotlinx.coroutines.* | import kotlinx.coroutines.* | ||||||
| import kotlinx.coroutines.channels.* | import kotlinx.coroutines.channels.* | ||||||
| import kotlinx.coroutines.flow.* | import kotlinx.coroutines.flow.* | ||||||
| @@ -8,6 +9,7 @@ import kotlinx.coroutines.sync.withLock | |||||||
|  |  | ||||||
| private class SubscribeAsyncReceiver<T>( | private class SubscribeAsyncReceiver<T>( | ||||||
|     val scope: CoroutineScope, |     val scope: CoroutineScope, | ||||||
|  |     val logger: KSLog, | ||||||
|     output: suspend SubscribeAsyncReceiver<T>.(T) -> Unit |     output: suspend SubscribeAsyncReceiver<T>.(T) -> Unit | ||||||
| ) { | ) { | ||||||
|     private val dataChannel: Channel<T> = Channel(Channel.UNLIMITED) |     private val dataChannel: Channel<T> = Channel(Channel.UNLIMITED) | ||||||
| @@ -15,7 +17,7 @@ private class SubscribeAsyncReceiver<T>( | |||||||
|         get() = dataChannel |         get() = dataChannel | ||||||
|  |  | ||||||
|     init { |     init { | ||||||
|         scope.launchLoggingDropExceptions { |         scope.launchLoggingDropExceptions(logger = logger) { | ||||||
|             for (data in dataChannel) { |             for (data in dataChannel) { | ||||||
|                 output(data) |                 output(data) | ||||||
|             } |             } | ||||||
| @@ -33,13 +35,16 @@ private data class AsyncSubscriptionCommandData<T, M>( | |||||||
|     val scope: CoroutineScope, |     val scope: CoroutineScope, | ||||||
|     val markerFactory: suspend (T) -> M, |     val markerFactory: suspend (T) -> M, | ||||||
|     val block: suspend (T) -> Unit, |     val block: suspend (T) -> Unit, | ||||||
|  |     val logger: KSLog, | ||||||
|     val onEmpty: suspend (M) -> Unit |     val onEmpty: suspend (M) -> Unit | ||||||
| ) : AsyncSubscriptionCommand<T, M> { | ) : AsyncSubscriptionCommand<T, M> { | ||||||
|     override suspend fun invoke(markersMap: MutableMap<M, SubscribeAsyncReceiver<T>>) { |     override suspend fun invoke(markersMap: MutableMap<M, SubscribeAsyncReceiver<T>>) { | ||||||
|         val marker = markerFactory(data) |         val marker = markerFactory(data) | ||||||
|         markersMap.getOrPut(marker) { |         markersMap.getOrPut(marker) { | ||||||
|             SubscribeAsyncReceiver(scope.LinkedSupervisorScope()) { |             SubscribeAsyncReceiver(scope.LinkedSupervisorScope(), logger) { | ||||||
|                 safelyWithoutExceptions { block(it) } |                 runCatchingLogging(logger = logger) { | ||||||
|  |                     block(it) | ||||||
|  |                 } | ||||||
|                 if (isEmpty()) { |                 if (isEmpty()) { | ||||||
|                     onEmpty(marker) |                     onEmpty(marker) | ||||||
|                 } |                 } | ||||||
| @@ -63,6 +68,7 @@ private data class AsyncSubscriptionCommandClearReceiver<T, M>( | |||||||
| fun <T, M> Flow<T>.subscribeAsync( | fun <T, M> Flow<T>.subscribeAsync( | ||||||
|     scope: CoroutineScope, |     scope: CoroutineScope, | ||||||
|     markerFactory: suspend (T) -> M, |     markerFactory: suspend (T) -> M, | ||||||
|  |     logger:  KSLog = KSLog, | ||||||
|     block: suspend (T) -> Unit |     block: suspend (T) -> Unit | ||||||
| ): Job { | ): Job { | ||||||
|     val subscope = scope.LinkedSupervisorScope() |     val subscope = scope.LinkedSupervisorScope() | ||||||
| @@ -71,8 +77,14 @@ fun <T, M> Flow<T>.subscribeAsync( | |||||||
|         it.invoke(markersMap) |         it.invoke(markersMap) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     val job = subscribeLoggingDropExceptions(subscope) { data -> |     val job = subscribeLoggingDropExceptions(subscope, logger = logger) { data -> | ||||||
|         val dataCommand = AsyncSubscriptionCommandData(data, subscope, markerFactory, block) { marker -> |         val dataCommand = AsyncSubscriptionCommandData( | ||||||
|  |             data = data, | ||||||
|  |             scope = subscope, | ||||||
|  |             markerFactory = markerFactory, | ||||||
|  |             block = block, | ||||||
|  |             logger = logger | ||||||
|  |         ) { marker -> | ||||||
|             actor.send( |             actor.send( | ||||||
|                 AsyncSubscriptionCommandClearReceiver(marker) |                 AsyncSubscriptionCommandClearReceiver(marker) | ||||||
|             ) |             ) | ||||||
| @@ -85,17 +97,20 @@ fun <T, M> Flow<T>.subscribeAsync( | |||||||
|     return job |     return job | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @Deprecated("Renamed", ReplaceWith("subscribeLoggingDropExceptionsAsync(scope, markerFactory, block = block)", "dev.inmo.micro_utils.coroutines.subscribeLoggingDropExceptionsAsync")) | ||||||
| fun <T, M> Flow<T>.subscribeSafelyAsync( | fun <T, M> Flow<T>.subscribeSafelyAsync( | ||||||
|     scope: CoroutineScope, |     scope: CoroutineScope, | ||||||
|     markerFactory: suspend (T) -> M, |     markerFactory: suspend (T) -> M, | ||||||
|     onException: ExceptionHandler<Unit> = defaultSafelyExceptionHandler, |     onException: ExceptionHandler<Unit> = defaultSafelyExceptionHandler, | ||||||
|  |     logger:  KSLog = KSLog, | ||||||
|     block: suspend (T) -> Unit |     block: suspend (T) -> Unit | ||||||
| ) = subscribeAsync(scope, markerFactory) { | ) = subscribeAsync(scope, markerFactory, logger) { | ||||||
|     safely(onException) { |     safely(onException) { | ||||||
|         block(it) |         block(it) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @Deprecated("Renamed", ReplaceWith("subscribeLoggingDropExceptionsAsync(scope, markerFactory, block = block)", "dev.inmo.micro_utils.coroutines.subscribeLoggingDropExceptionsAsync")) | ||||||
| fun <T, M> Flow<T>.subscribeSafelyWithoutExceptionsAsync( | fun <T, M> Flow<T>.subscribeSafelyWithoutExceptionsAsync( | ||||||
|     scope: CoroutineScope, |     scope: CoroutineScope, | ||||||
|     markerFactory: suspend (T) -> M, |     markerFactory: suspend (T) -> M, | ||||||
| @@ -107,11 +122,22 @@ fun <T, M> Flow<T>.subscribeSafelyWithoutExceptionsAsync( | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | fun <T, M> Flow<T>.subscribeLoggingDropExceptionsAsync( | ||||||
|  |     scope: CoroutineScope, | ||||||
|  |     markerFactory: suspend (T) -> M, | ||||||
|  |     logger:  KSLog = KSLog, | ||||||
|  |     block: suspend (T) -> Unit | ||||||
|  | ) = subscribeAsync(scope, markerFactory, logger) { | ||||||
|  |     block(it) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @Deprecated("Renamed", ReplaceWith("subscribeLoggingDropExceptionsAsync(scope, markerFactory, logger, block = block)", "dev.inmo.micro_utils.coroutines.subscribeLoggingDropExceptionsAsync")) | ||||||
| fun <T, M> Flow<T>.subscribeSafelySkippingExceptionsAsync( | fun <T, M> Flow<T>.subscribeSafelySkippingExceptionsAsync( | ||||||
|     scope: CoroutineScope, |     scope: CoroutineScope, | ||||||
|     markerFactory: suspend (T) -> M, |     markerFactory: suspend (T) -> M, | ||||||
|  |     logger:  KSLog = KSLog, | ||||||
|     block: suspend (T) -> Unit |     block: suspend (T) -> Unit | ||||||
| ) = subscribeAsync(scope, markerFactory) { | ) = subscribeAsync(scope, markerFactory, logger) { | ||||||
|     safelyWithoutExceptions({ /* do nothing */}) { |     safelyWithoutExceptions({ /* do nothing */}) { | ||||||
|         block(it) |         block(it) | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -1,7 +1,5 @@ | |||||||
| package dev.inmo.micro_utils.coroutines | package dev.inmo.micro_utils.coroutines | ||||||
| 
 | 
 | ||||||
| import kotlinx.coroutines.CoroutineScope |  | ||||||
| import kotlinx.coroutines.Dispatchers |  | ||||||
| import kotlinx.coroutines.ExperimentalCoroutinesApi | import kotlinx.coroutines.ExperimentalCoroutinesApi | ||||||
| import kotlinx.coroutines.InternalCoroutinesApi | import kotlinx.coroutines.InternalCoroutinesApi | ||||||
| import kotlinx.coroutines.channels.BufferOverflow | import kotlinx.coroutines.channels.BufferOverflow | ||||||
| @@ -11,13 +9,12 @@ import kotlinx.coroutines.flow.MutableStateFlow | |||||||
| import kotlinx.coroutines.flow.StateFlow | import kotlinx.coroutines.flow.StateFlow | ||||||
| import kotlinx.coroutines.internal.SynchronizedObject | import kotlinx.coroutines.internal.SynchronizedObject | ||||||
| import kotlinx.coroutines.internal.synchronized | import kotlinx.coroutines.internal.synchronized | ||||||
| import kotlin.coroutines.CoroutineContext |  | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Works like [StateFlow], but guarantee that latest value update will always be delivered to |  * Works like [StateFlow], but guarantee that latest value update will always be delivered to | ||||||
|  * each active subscriber |  * each active subscriber | ||||||
|  */ |  */ | ||||||
| open class SpecialMutableStateFlow<T>( | open class MutableRedeliverStateFlow<T>( | ||||||
|     initialValue: T |     initialValue: T | ||||||
| ) : MutableStateFlow<T>, FlowCollector<T>, MutableSharedFlow<T> { | ) : MutableStateFlow<T>, FlowCollector<T>, MutableSharedFlow<T> { | ||||||
|     @OptIn(InternalCoroutinesApi::class) |     @OptIn(InternalCoroutinesApi::class) | ||||||
| @@ -68,3 +65,6 @@ open class SpecialMutableStateFlow<T>( | |||||||
| 
 | 
 | ||||||
|     override suspend fun collect(collector: FlowCollector<T>) = sharingFlow.collect(collector) |     override suspend fun collect(collector: FlowCollector<T>) = sharingFlow.collect(collector) | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | @Deprecated("Renamed to MutableRedeliverStateFlow", ReplaceWith("MutableRedeliverStateFlow<T>")) | ||||||
|  | typealias SpecialMutableStateFlow<T> = MutableRedeliverStateFlow<T> | ||||||
| @@ -1,7 +1,6 @@ | |||||||
| package dev.inmo.micro_utils.coroutines | package dev.inmo.micro_utils.coroutines | ||||||
|  |  | ||||||
| import kotlinx.coroutines.currentCoroutineContext | import kotlinx.coroutines.currentCoroutineContext | ||||||
| import kotlinx.coroutines.flow.MutableStateFlow |  | ||||||
| import kotlinx.coroutines.flow.StateFlow | import kotlinx.coroutines.flow.StateFlow | ||||||
| import kotlinx.coroutines.flow.asStateFlow | import kotlinx.coroutines.flow.asStateFlow | ||||||
| import kotlinx.coroutines.flow.first | import kotlinx.coroutines.flow.first | ||||||
| @@ -44,7 +43,7 @@ sealed interface SmartMutex { | |||||||
|      * @param locked Preset state of [isLocked] and its internal [_lockStateFlow] |      * @param locked Preset state of [isLocked] and its internal [_lockStateFlow] | ||||||
|      */ |      */ | ||||||
|     class Mutable(locked: Boolean = false) : SmartMutex { |     class Mutable(locked: Boolean = false) : SmartMutex { | ||||||
|         private val _lockStateFlow = SpecialMutableStateFlow<Boolean>(locked) |         private val _lockStateFlow = MutableRedeliverStateFlow<Boolean>(locked) | ||||||
|         override val lockStateFlow: StateFlow<Boolean> = _lockStateFlow.asStateFlow() |         override val lockStateFlow: StateFlow<Boolean> = _lockStateFlow.asStateFlow() | ||||||
|  |  | ||||||
|         private val internalChangesMutex = Mutex() |         private val internalChangesMutex = Mutex() | ||||||
|   | |||||||
| @@ -1,7 +1,6 @@ | |||||||
| package dev.inmo.micro_utils.coroutines | package dev.inmo.micro_utils.coroutines | ||||||
|  |  | ||||||
| import kotlinx.coroutines.currentCoroutineContext | import kotlinx.coroutines.currentCoroutineContext | ||||||
| import kotlinx.coroutines.flow.MutableStateFlow |  | ||||||
| import kotlinx.coroutines.flow.StateFlow | import kotlinx.coroutines.flow.StateFlow | ||||||
| import kotlinx.coroutines.flow.asStateFlow | import kotlinx.coroutines.flow.asStateFlow | ||||||
| import kotlinx.coroutines.flow.first | import kotlinx.coroutines.flow.first | ||||||
| @@ -47,7 +46,7 @@ sealed interface SmartSemaphore { | |||||||
|      */ |      */ | ||||||
|     class Mutable(permits: Int, acquiredPermits: Int = 0) : SmartSemaphore { |     class Mutable(permits: Int, acquiredPermits: Int = 0) : SmartSemaphore { | ||||||
|         override val maxPermits: Int = permits |         override val maxPermits: Int = permits | ||||||
|         private val _freePermitsStateFlow = SpecialMutableStateFlow<Int>(permits - acquiredPermits) |         private val _freePermitsStateFlow = MutableRedeliverStateFlow<Int>(permits - acquiredPermits) | ||||||
|         override val permitsStateFlow: StateFlow<Int> = _freePermitsStateFlow.asStateFlow() |         override val permitsStateFlow: StateFlow<Int> = _freePermitsStateFlow.asStateFlow() | ||||||
|  |  | ||||||
|         private val internalChangesMutex = Mutex(false) |         private val internalChangesMutex = Mutex(false) | ||||||
|   | |||||||
| @@ -1,33 +1,31 @@ | |||||||
| import dev.inmo.micro_utils.coroutines.SpecialMutableStateFlow | import dev.inmo.micro_utils.coroutines.MutableRedeliverStateFlow | ||||||
| import dev.inmo.micro_utils.coroutines.asDeferred |  | ||||||
| import dev.inmo.micro_utils.coroutines.subscribe | import dev.inmo.micro_utils.coroutines.subscribe | ||||||
| import kotlinx.coroutines.Job | import kotlinx.coroutines.Job | ||||||
| import kotlinx.coroutines.flow.first | import kotlinx.coroutines.flow.first | ||||||
| import kotlinx.coroutines.test.runTest | import kotlinx.coroutines.test.runTest | ||||||
| import kotlin.test.Test | import kotlin.test.Test | ||||||
| import kotlin.test.assertEquals | import kotlin.test.assertEquals | ||||||
| import kotlin.test.assertTrue |  | ||||||
|  |  | ||||||
| class SpecialMutableStateFlowTests { | class SpecialMutableStateFlowTests { | ||||||
|     @Test |     @Test | ||||||
|     fun simpleTest() = runTest { |     fun simpleTest() = runTest { | ||||||
|         val specialMutableStateFlow = SpecialMutableStateFlow(0) |         val mutableRedeliverStateFlow = MutableRedeliverStateFlow(0) | ||||||
|         specialMutableStateFlow.value = 1 |         mutableRedeliverStateFlow.value = 1 | ||||||
|         specialMutableStateFlow.first { it == 1 } |         mutableRedeliverStateFlow.first { it == 1 } | ||||||
|         assertEquals(1, specialMutableStateFlow.value) |         assertEquals(1, mutableRedeliverStateFlow.value) | ||||||
|     } |     } | ||||||
|     @Test |     @Test | ||||||
|     fun specialTest() = runTest { |     fun specialTest() = runTest { | ||||||
|         val specialMutableStateFlow = SpecialMutableStateFlow(0) |         val mutableRedeliverStateFlow = MutableRedeliverStateFlow(0) | ||||||
|         lateinit var subscriberJob: Job |         lateinit var subscriberJob: Job | ||||||
|         subscriberJob = specialMutableStateFlow.subscribe(this) { |         subscriberJob = mutableRedeliverStateFlow.subscribe(this) { | ||||||
|             when (it) { |             when (it) { | ||||||
|                 1 -> specialMutableStateFlow.value = 2 |                 1 -> mutableRedeliverStateFlow.value = 2 | ||||||
|                 2 -> subscriberJob.cancel() |                 2 -> subscriberJob.cancel() | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         specialMutableStateFlow.value = 1 |         mutableRedeliverStateFlow.value = 1 | ||||||
|         subscriberJob.join() |         subscriberJob.join() | ||||||
|         assertEquals(2, specialMutableStateFlow.value) |         assertEquals(2, mutableRedeliverStateFlow.value) | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -8,6 +8,9 @@ android.useAndroidX=true | |||||||
| android.enableJetifier=true | android.enableJetifier=true | ||||||
| org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=2g | org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=2g | ||||||
|  |  | ||||||
|  | # https://github.com/google/ksp/issues/2491 | ||||||
|  | ksp.useKSP2=false | ||||||
|  |  | ||||||
| # JS NPM | # JS NPM | ||||||
|  |  | ||||||
| crypto_js_version=4.1.1 | crypto_js_version=4.1.1 | ||||||
| @@ -15,5 +18,5 @@ crypto_js_version=4.1.1 | |||||||
| # Project data | # Project data | ||||||
|  |  | ||||||
| group=dev.inmo | group=dev.inmo | ||||||
| version=0.25.7 | version=0.26.6 | ||||||
| android_code_version=297 | android_code_version=305 | ||||||
|   | |||||||
| @@ -1,45 +1,46 @@ | |||||||
| [versions] | [versions] | ||||||
|  |  | ||||||
| kt = "2.1.21" | kt = "2.2.21" | ||||||
| kt-serialization = "1.8.1" | kt-serialization = "1.9.0" | ||||||
| kt-coroutines = "1.10.2" | kt-coroutines = "1.10.2" | ||||||
|  |  | ||||||
| kotlinx-browser = "0.3" | kotlinx-browser = "0.5.0" | ||||||
|  |  | ||||||
| kslog = "1.4.2" | kslog = "1.5.1" | ||||||
|  |  | ||||||
| jb-compose = "1.8.1" | jb-compose = "1.8.2" | ||||||
| jb-exposed = "0.61.0" | jb-exposed = "0.61.0" | ||||||
| jb-dokka = "2.0.0" | jb-dokka = "2.1.0" | ||||||
|  |  | ||||||
|  | # 3.50.3.0 contains bug https://github.com/InsanusMokrassar/MicroUtils/actions/runs/18138301958/job/51629588088 | ||||||
| sqlite = "3.50.1.0" | sqlite = "3.50.1.0" | ||||||
|  |  | ||||||
| korlibs = "5.4.0" | korlibs = "5.4.0" | ||||||
| uuid = "0.8.4" | uuid = "0.8.4" | ||||||
|  |  | ||||||
| ktor = "3.1.3" | ktor = "3.3.1" | ||||||
|  |  | ||||||
| gh-release = "2.5.2" | gh-release = "2.5.2" | ||||||
|  |  | ||||||
| koin = "4.0.4" | koin = "4.1.1" | ||||||
|  |  | ||||||
| okio = "3.12.0" | okio = "3.16.2" | ||||||
|  |  | ||||||
| ksp = "2.1.20-1.0.31" | ksp = "2.2.20-2.0.3" | ||||||
| kotlin-poet = "1.18.1" | kotlin-poet = "2.2.0" | ||||||
|  |  | ||||||
| versions = "0.51.0" | versions = "0.52.0" | ||||||
| nmcp = "0.1.5" | nmcp = "1.1.0" | ||||||
|  |  | ||||||
| android-gradle = "8.9.+" | android-gradle = "8.10.+" | ||||||
| dexcount = "4.0.0" | dexcount = "4.0.0" | ||||||
|  |  | ||||||
| android-coreKtx = "1.16.0" | android-coreKtx = "1.17.0" | ||||||
| android-recyclerView = "1.4.0" | android-recyclerView = "1.4.0" | ||||||
| android-appCompat = "1.7.1" | android-appCompat = "1.7.1" | ||||||
| android-fragment = "1.8.8" | android-fragment = "1.8.9" | ||||||
| android-espresso = "3.6.1" | android-espresso = "3.7.0" | ||||||
| android-test = "1.2.1" | android-test = "1.3.0" | ||||||
|  |  | ||||||
| android-props-minSdk = "21" | android-props-minSdk = "21" | ||||||
| android-props-compileSdk = "36" | android-props-compileSdk = "36" | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							| @@ -1,5 +1,5 @@ | |||||||
| distributionBase=GRADLE_USER_HOME | distributionBase=GRADLE_USER_HOME | ||||||
| distributionPath=wrapper/dists | distributionPath=wrapper/dists | ||||||
| distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip | ||||||
| zipStoreBase=GRADLE_USER_HOME | zipStoreBase=GRADLE_USER_HOME | ||||||
| zipStorePath=wrapper/dists | zipStorePath=wrapper/dists | ||||||
|   | |||||||
| @@ -10,6 +10,7 @@ repositories { | |||||||
|  |  | ||||||
| dependencies { | dependencies { | ||||||
|     api project(":micro_utils.koin") |     api project(":micro_utils.koin") | ||||||
|  |     api project(":micro_utils.ksp.generator") | ||||||
|     api libs.kotlin.poet |     api libs.kotlin.poet | ||||||
|     api libs.ksp |     api libs.ksp | ||||||
| } | } | ||||||
|   | |||||||
| @@ -26,6 +26,7 @@ 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_ksp.generator.safeClassName | ||||||
| import dev.inmo.micro_utils.koin.annotations.GenerateGenericKoinDefinition | 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 | ||||||
| @@ -237,15 +238,7 @@ class Processor( | |||||||
|                     """.trimIndent() |                     """.trimIndent() | ||||||
|                 ) |                 ) | ||||||
|                 ksFile.getAnnotationsByType(GenerateKoinDefinition::class).forEach { |                 ksFile.getAnnotationsByType(GenerateKoinDefinition::class).forEach { | ||||||
|                     val type = runCatching { |                     val type = safeClassName { it.type } | ||||||
|                         it.type.asTypeName() |  | ||||||
|                     }.getOrElse { e -> |  | ||||||
|                         if (e is KSTypeNotPresentException) { |  | ||||||
|                             e.ksType.toClassName() |  | ||||||
|                         } else { |  | ||||||
|                             throw e |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                     val targetType = runCatching { |                     val targetType = runCatching { | ||||||
|                         type.parameterizedBy(*(it.typeArgs.takeIf { it.isNotEmpty() } ?.map { it.asTypeName() } ?.toTypedArray() ?: return@runCatching type)) |                         type.parameterizedBy(*(it.typeArgs.takeIf { it.isNotEmpty() } ?.map { it.asTypeName() } ?.toTypedArray() ?: return@runCatching type)) | ||||||
|                     }.getOrElse { e -> |                     }.getOrElse { e -> | ||||||
|   | |||||||
| @@ -5,7 +5,6 @@ package dev.inmo.micro_utils.koin.generator.test | |||||||
|  |  | ||||||
| import kotlin.Any | 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 | ||||||
| @@ -30,95 +29,59 @@ public val Koin.sampleInfo: Test<String> | |||||||
| /** | /** | ||||||
|  * @return Definition by key "sampleInfo" with [parameters] |  * @return Definition by key "sampleInfo" with [parameters] | ||||||
|  */ |  */ | ||||||
| public inline fun Scope.sampleInfo(noinline parameters: ParametersDefinition): Test<String> = | public inline fun Scope.sampleInfo(noinline parameters: ParametersDefinition): Test<String> = get(named("sampleInfo"), parameters) | ||||||
|     get(named("sampleInfo"), parameters) |  | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * @return Definition by key "sampleInfo" with [parameters] |  * @return Definition by key "sampleInfo" with [parameters] | ||||||
|  */ |  */ | ||||||
| public inline fun Koin.sampleInfo(noinline parameters: ParametersDefinition): Test<String> = | public inline fun Koin.sampleInfo(noinline parameters: ParametersDefinition): Test<String> = get(named("sampleInfo"), parameters) | ||||||
|     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( | public fun Module.singleSampleInfo(createdAtStart: Boolean = false, definition: Definition<Test<String>>): KoinDefinition<Test<String>> = single(named("sampleInfo"), createdAtStart = createdAtStart, definition = definition) | ||||||
|   "This definition is old style and should not be used anymore. Use singleSampleInfo instead", |  | ||||||
|   ReplaceWith("singleSampleInfo"), |  | ||||||
| ) |  | ||||||
| public fun Module.sampleInfoSingle(createdAtStart: Boolean = false, |  | ||||||
|     definition: Definition<Test<String>>): KoinDefinition<Test<String>> = |  | ||||||
|     single(named("sampleInfo"), createdAtStart = createdAtStart, definition = definition) |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Will register [definition] with [org.koin.core.module.Module.single] and key "sampleInfo" |  | ||||||
|  */ |  | ||||||
| public fun Module.singleSampleInfo(createdAtStart: Boolean = false, |  | ||||||
|     definition: Definition<Test<String>>): KoinDefinition<Test<String>> = |  | ||||||
|     single(named("sampleInfo"), createdAtStart = createdAtStart, definition = definition) |  | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * 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( | public fun Module.factorySampleInfo(definition: Definition<Test<String>>): KoinDefinition<Test<String>> = factory(named("sampleInfo"), definition = definition) | ||||||
|   "This definition is old style and should not be used anymore. Use factorySampleInfo instead", |  | ||||||
|   ReplaceWith("factorySampleInfo"), |  | ||||||
| ) |  | ||||||
| public fun Module.sampleInfoFactory(definition: Definition<Test<String>>): |  | ||||||
|     KoinDefinition<Test<String>> = factory(named("sampleInfo"), definition = definition) |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Will register [definition] with [org.koin.core.module.Module.factory] and key "sampleInfo" |  | ||||||
|  */ |  | ||||||
| public fun Module.factorySampleInfo(definition: Definition<Test<String>>): |  | ||||||
|     KoinDefinition<Test<String>> = factory(named("sampleInfo"), definition = definition) |  | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * @return Definition by key "test" with [parameters] |  * @return Definition by key "test" with [parameters] | ||||||
|  */ |  */ | ||||||
| public inline fun <reified T : Any> Scope.test(noinline parameters: ParametersDefinition? = null): T | public inline fun <reified T : Any> Scope.test(noinline parameters: ParametersDefinition? = null): T = get(named("test"), parameters) | ||||||
|     = get(named("test"), parameters) |  | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * @return Definition by key "test" with [parameters] |  * @return Definition by key "test" with [parameters] | ||||||
|  */ |  */ | ||||||
| public inline fun <reified T : Any> Koin.test(noinline parameters: ParametersDefinition? = null): T | public inline fun <reified T : Any> Koin.test(noinline parameters: ParametersDefinition? = null): T = get(named("test"), parameters) | ||||||
|     = get(named("test"), parameters) |  | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Will register [definition] with [org.koin.core.module.Module.single] and key "test" |  * 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 | public inline fun <reified T : Any> Module.singleTest(createdAtStart: Boolean = false, noinline definition: Definition<T>): KoinDefinition<T> = single(named("test"), createdAtStart = createdAtStart, definition = definition) | ||||||
|     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" |  * 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>): | public inline fun <reified T : Any> Module.factoryTest(noinline definition: Definition<T>): KoinDefinition<T> = factory(named("test"), definition = definition) | ||||||
|     KoinDefinition<T> = factory(named("test"), definition = definition) |  | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * @return Definition by key "testNullable" with [parameters] |  * @return Definition by key "testNullable" with [parameters] | ||||||
|  */ |  */ | ||||||
| public inline fun <reified T : Any> Scope.testNullable(noinline parameters: ParametersDefinition? = | public inline fun <reified T : Any> Scope.testNullable(noinline parameters: ParametersDefinition? = null): T? = getOrNull(named("testNullable"), parameters) | ||||||
|     null): T? = getOrNull(named("testNullable"), parameters) |  | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * @return Definition by key "testNullable" with [parameters] |  * @return Definition by key "testNullable" with [parameters] | ||||||
|  */ |  */ | ||||||
| public inline fun <reified T : Any> Koin.testNullable(noinline parameters: ParametersDefinition? = | public inline fun <reified T : Any> Koin.testNullable(noinline parameters: ParametersDefinition? = null): T? = getOrNull(named("testNullable"), parameters) | ||||||
|     null): T? = getOrNull(named("testNullable"), parameters) |  | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Will register [definition] with [org.koin.core.module.Module.single] and key "testNullable" |  * Will register [definition] with [org.koin.core.module.Module.single] and key "testNullable" | ||||||
|  */ |  */ | ||||||
| public inline fun <reified T : Any> Module.singleTestNullable(createdAtStart: Boolean = false, | public inline fun <reified T : Any> Module.singleTestNullable(createdAtStart: Boolean = false, noinline definition: Definition<T>): KoinDefinition<T> = single(named("testNullable"), createdAtStart = createdAtStart, definition = definition) | ||||||
|     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" |  * 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>): | public inline fun <reified T : Any> Module.factoryTestNullable(noinline definition: Definition<T>): KoinDefinition<T> = factory(named("testNullable"), definition = definition) | ||||||
|     KoinDefinition<T> = factory(named("testNullable"), definition = definition) |  | ||||||
|   | |||||||
							
								
								
									
										23
									
								
								ksp/generator/src/main/kotlin/KClassTypeName.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								ksp/generator/src/main/kotlin/KClassTypeName.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | |||||||
|  | package dev.inmo.micro_ksp.generator | ||||||
|  |  | ||||||
|  | import com.google.devtools.ksp.KSTypeNotPresentException | ||||||
|  | import com.google.devtools.ksp.KspExperimental | ||||||
|  | import com.squareup.kotlinpoet.ClassName | ||||||
|  | import com.squareup.kotlinpoet.asTypeName | ||||||
|  | import kotlin.reflect.KClass | ||||||
|  |  | ||||||
|  | @Suppress("NOTHING_TO_INLINE") | ||||||
|  | @OptIn(KspExperimental::class) | ||||||
|  | inline fun safeClassName(classnameGetter: () -> KClass<*>) = runCatching { | ||||||
|  |     classnameGetter().asTypeName() | ||||||
|  | }.getOrElse { e -> | ||||||
|  |     if (e is KSTypeNotPresentException) { | ||||||
|  |         ClassName( | ||||||
|  |             e.ksType.declaration.packageName.asString(), | ||||||
|  |             e.ksType.declaration.qualifiedName ?.asString() ?.replaceFirst(e.ksType.declaration.packageName.asString(), "") | ||||||
|  |                 ?: e.ksType.declaration.simpleName.asString() | ||||||
|  |         ) | ||||||
|  |     } else { | ||||||
|  |         throw e | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,9 +1,8 @@ | |||||||
| package dev.inmo.micro_utils.pagination.compose | package dev.inmo.micro_utils.pagination.compose | ||||||
|  |  | ||||||
| import androidx.compose.runtime.* | import androidx.compose.runtime.* | ||||||
| import dev.inmo.micro_utils.coroutines.SpecialMutableStateFlow | import dev.inmo.micro_utils.coroutines.MutableRedeliverStateFlow | ||||||
| import dev.inmo.micro_utils.coroutines.launchLoggingDropExceptions | import dev.inmo.micro_utils.coroutines.launchLoggingDropExceptions | ||||||
| import dev.inmo.micro_utils.coroutines.runCatchingLogging |  | ||||||
| import dev.inmo.micro_utils.pagination.* | import dev.inmo.micro_utils.pagination.* | ||||||
| import kotlinx.coroutines.CoroutineScope | import kotlinx.coroutines.CoroutineScope | ||||||
| import kotlinx.coroutines.Job | import kotlinx.coroutines.Job | ||||||
| @@ -27,8 +26,8 @@ class InfinityPagedComponentContext<T> internal constructor( | |||||||
|     private val loader: suspend InfinityPagedComponentContext<T>.(Pagination) -> PaginationResult<T> |     private val loader: suspend InfinityPagedComponentContext<T>.(Pagination) -> PaginationResult<T> | ||||||
| ) { | ) { | ||||||
|     internal val startPage = SimplePagination(page, size) |     internal val startPage = SimplePagination(page, size) | ||||||
|     internal val latestLoadedPage = SpecialMutableStateFlow<PaginationResult<T>?>(null) |     internal val latestLoadedPage = MutableRedeliverStateFlow<PaginationResult<T>?>(null) | ||||||
|     internal val dataState = SpecialMutableStateFlow<List<T>?>(null) |     internal val dataState = MutableRedeliverStateFlow<List<T>?>(null) | ||||||
|     internal var loadingJob: Job? = null |     internal var loadingJob: Job? = null | ||||||
|     internal val loadingMutex = Mutex() |     internal val loadingMutex = Mutex() | ||||||
|  |  | ||||||
| @@ -65,6 +64,60 @@ class InfinityPagedComponentContext<T> internal constructor( | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Creates and remembers an [InfinityPagedComponentContext] for managing infinite pagination in a Compose UI. | ||||||
|  |  * This function is used to create a persistent pagination context that survives recompositions. | ||||||
|  |  * | ||||||
|  |  * @param size Number of items to load per page. | ||||||
|  |  * @param page Initial page number to start pagination from (defaults to 0). | ||||||
|  |  * @param scope [CoroutineScope] to launch pagination operations in. If not provided, a new scope will be created | ||||||
|  |  * using [rememberCoroutineScope]. | ||||||
|  |  * @param loader Suspended function that loads paginated data. Receives the current pagination context and | ||||||
|  |  * pagination parameters, and returns a [PaginationResult] containing the loaded data. | ||||||
|  |  * @return An [InfinityPagedComponentContext] instance that manages the pagination state and operations. | ||||||
|  |  */ | ||||||
|  | @Composable | ||||||
|  | fun <T> rememberInfinityPagedComponentContext( | ||||||
|  |     size: Int, | ||||||
|  |     page: Int = 0, | ||||||
|  |     scope: CoroutineScope = rememberCoroutineScope(), | ||||||
|  |     doReloadInInit: Boolean = true, | ||||||
|  |     loader: suspend InfinityPagedComponentContext<T>.(Pagination) -> PaginationResult<T> | ||||||
|  | ): InfinityPagedComponentContext<T> { | ||||||
|  |     val context = remember { | ||||||
|  |         InfinityPagedComponentContext( | ||||||
|  |             page = page, | ||||||
|  |             size = size, | ||||||
|  |             scope = scope, | ||||||
|  |             loader = loader | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     LaunchedEffect(context) { | ||||||
|  |         if (doReloadInInit) { | ||||||
|  |             context.reload() | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return context | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Composable function for managing an infinitely paged component. | ||||||
|  |  * | ||||||
|  |  * @param T The type of the paginated data. | ||||||
|  |  * @param block Composable function that renders the UI with the loaded data. When data is in loading state, block will | ||||||
|  |  * receive null as `it` parameter | ||||||
|  |  */ | ||||||
|  | @Composable | ||||||
|  | fun <T> InfinityPagedComponent( | ||||||
|  |     context: InfinityPagedComponentContext<T>, | ||||||
|  |     block: @Composable InfinityPagedComponentContext<T>.(List<T>?) -> Unit | ||||||
|  | ) { | ||||||
|  |     val dataState = context.dataState.collectAsState() | ||||||
|  |     context.block(dataState.value) | ||||||
|  | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Composable function for managing an infinitely paged component. |  * Composable function for managing an infinitely paged component. | ||||||
|  * |  * | ||||||
| @@ -76,7 +129,7 @@ class InfinityPagedComponentContext<T> internal constructor( | |||||||
|  * receive null as `it` parameter |  * receive null as `it` parameter | ||||||
|  */ |  */ | ||||||
| @Composable | @Composable | ||||||
| internal fun <T> InfinityPagedComponent( | fun <T> InfinityPagedComponent( | ||||||
|     page: Int, |     page: Int, | ||||||
|     size: Int, |     size: Int, | ||||||
|     loader: suspend InfinityPagedComponentContext<T>.(Pagination) -> PaginationResult<T>, |     loader: suspend InfinityPagedComponentContext<T>.(Pagination) -> PaginationResult<T>, | ||||||
| @@ -84,13 +137,8 @@ internal fun <T> InfinityPagedComponent( | |||||||
|     block: @Composable InfinityPagedComponentContext<T>.(List<T>?) -> Unit |     block: @Composable InfinityPagedComponentContext<T>.(List<T>?) -> Unit | ||||||
| ) { | ) { | ||||||
|     val scope = predefinedScope ?: rememberCoroutineScope() |     val scope = predefinedScope ?: rememberCoroutineScope() | ||||||
|     val context = remember { InfinityPagedComponentContext<T>(page, size, scope, loader) } |     val context = rememberInfinityPagedComponentContext(page = page, size = size, scope = scope, loader = loader) | ||||||
|     remember { |     InfinityPagedComponent(context, block) | ||||||
|         context.reload() |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     val dataState = context.dataState.collectAsState() |  | ||||||
|     context.block(dataState.value) |  | ||||||
| } | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| package dev.inmo.micro_utils.pagination.compose | package dev.inmo.micro_utils.pagination.compose | ||||||
|  |  | ||||||
| import androidx.compose.runtime.* | import androidx.compose.runtime.* | ||||||
| import dev.inmo.micro_utils.coroutines.SpecialMutableStateFlow | import dev.inmo.micro_utils.coroutines.MutableRedeliverStateFlow | ||||||
| import dev.inmo.micro_utils.coroutines.launchLoggingDropExceptions | import dev.inmo.micro_utils.coroutines.launchLoggingDropExceptions | ||||||
| import dev.inmo.micro_utils.pagination.* | import dev.inmo.micro_utils.pagination.* | ||||||
| import kotlinx.coroutines.CoroutineScope | import kotlinx.coroutines.CoroutineScope | ||||||
| @@ -28,8 +28,8 @@ class PagedComponentContext<T> internal constructor( | |||||||
|     private val loader: suspend PagedComponentContext<T>.(Pagination) -> PaginationResult<T> |     private val loader: suspend PagedComponentContext<T>.(Pagination) -> PaginationResult<T> | ||||||
| ) { | ) { | ||||||
|     internal val startPage = SimplePagination(initialPage, size) |     internal val startPage = SimplePagination(initialPage, size) | ||||||
|     internal val latestLoadedPage = SpecialMutableStateFlow<PaginationResult<T>?>(null) |     internal val latestLoadedPage = MutableRedeliverStateFlow<PaginationResult<T>?>(null) | ||||||
|     internal val dataState = SpecialMutableStateFlow<PaginationResult<T>?>(null) |     internal val dataState = MutableRedeliverStateFlow<PaginationResult<T>?>(null) | ||||||
|     internal var loadingJob: Job? = null |     internal var loadingJob: Job? = null | ||||||
|     internal val loadingMutex = Mutex() |     internal val loadingMutex = Mutex() | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,10 +1,81 @@ | |||||||
| package dev.inmo.micro_utils.repos.exposed | package dev.inmo.micro_utils.repos.exposed | ||||||
|  |  | ||||||
| import org.jetbrains.exposed.sql.* | import org.jetbrains.exposed.sql.* | ||||||
|  | import org.jetbrains.exposed.sql.SchemaUtils.addMissingColumnsStatements | ||||||
|  | import org.jetbrains.exposed.sql.SchemaUtils.checkMappingConsistence | ||||||
|  | import org.jetbrains.exposed.sql.SchemaUtils.createStatements | ||||||
|  | import org.jetbrains.exposed.sql.transactions.TransactionManager | ||||||
| import org.jetbrains.exposed.sql.transactions.transaction | import org.jetbrains.exposed.sql.transactions.transaction | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Code in this function mostly duplicates Exposed [SchemaUtils.createMissingTablesAndColumns]. It made due to deprecation | ||||||
|  |  * status of the last one and potential lost of it in future updates. | ||||||
|  |  * | ||||||
|  |  * Code doing: | ||||||
|  |  * | ||||||
|  |  * * Creating missed tables | ||||||
|  |  * * Altering missed tables (where possible) | ||||||
|  |  * * Calculate problems with [checkMappingConsistence] and add them in execution too | ||||||
|  |  * | ||||||
|  |  * All changes made in [transaction] with [database] as its `db` argument | ||||||
|  |  */ | ||||||
|  | fun initTablesInTransaction(vararg tables: Table, database: Database, inBatch: Boolean = false, withLogs: Boolean = true) { | ||||||
|  |     transaction(database) { | ||||||
|  |         fun <R> logTimeSpent(message: String, withLogs: Boolean, block: () -> R): R { | ||||||
|  |             return if (withLogs) { | ||||||
|  |                 val start = System.currentTimeMillis() | ||||||
|  |                 val answer = block() | ||||||
|  |                 exposedLogger.info(message + " took " + (System.currentTimeMillis() - start) + "ms") | ||||||
|  |                 answer | ||||||
|  |             } else { | ||||||
|  |                 block() | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         fun Transaction.execStatements(inBatch: Boolean, statements: List<String>) { | ||||||
|  |             if (inBatch) { | ||||||
|  |                 execInBatch(statements) | ||||||
|  |             } else { | ||||||
|  |                 for (statement in statements) { | ||||||
|  |                     exec(statement) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         with(TransactionManager.current()) { | ||||||
|  |             db.dialect.resetCaches() | ||||||
|  |             val createStatements = logTimeSpent("Preparing create tables statements", withLogs) { | ||||||
|  |                 createStatements(*tables) | ||||||
|  |             } | ||||||
|  |             logTimeSpent("Executing create tables statements", withLogs) { | ||||||
|  |                 execStatements(inBatch, createStatements) | ||||||
|  |                 commit() | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             val alterStatements = logTimeSpent("Preparing alter table statements", withLogs) { | ||||||
|  |                 addMissingColumnsStatements(tables = tables, withLogs) | ||||||
|  |             } | ||||||
|  |             logTimeSpent("Executing alter table statements", withLogs) { | ||||||
|  |                 execStatements(inBatch, alterStatements) | ||||||
|  |                 commit() | ||||||
|  |             } | ||||||
|  |             val executedStatements = createStatements + alterStatements | ||||||
|  |             logTimeSpent("Checking mapping consistence", withLogs) { | ||||||
|  |                 val modifyTablesStatements = checkMappingConsistence( | ||||||
|  |                     tables = tables, | ||||||
|  |                     withLogs | ||||||
|  |                 ).filter { it !in executedStatements } | ||||||
|  |                 execStatements(inBatch, modifyTablesStatements) | ||||||
|  |                 commit() | ||||||
|  |             } | ||||||
|  |             db.dialect.resetCaches() | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fun Table.initTable(database: Database, inBatch: Boolean, withLogs: Boolean) { | ||||||
|  |     initTablesInTransaction(this, database = database, inBatch = inBatch, withLogs = withLogs) | ||||||
|  | } | ||||||
| fun Table.initTable(database: Database) { | fun Table.initTable(database: Database) { | ||||||
|     transaction(database) { SchemaUtils.createMissingTablesAndColumns(this@initTable) } |     initTable(database = database, inBatch = false, withLogs = true) | ||||||
| } | } | ||||||
|  |  | ||||||
| fun <T> T.initTable() where T: ExposedRepo, T: Table = initTable(database) | fun <T> T.initTable() where T: ExposedRepo, T: Table = initTable(database) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user