Skip to content

Commit

Permalink
3.1.0-beta01 (#96)
Browse files Browse the repository at this point in the history
  • Loading branch information
Nek-12 authored Sep 11, 2024
2 parents a55a540 + 53f9fb7 commit 22f5766
Show file tree
Hide file tree
Showing 91 changed files with 2,522 additions and 685 deletions.
17 changes: 17 additions & 0 deletions .idea/runConfigurations/Debugger_Run_and_Detach.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 17 additions & 0 deletions .idea/runConfigurations/Sample_Desktop_Run_and_Detach.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 3 additions & 4 deletions buildSrc/src/main/kotlin/Config.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ object Config {

const val versionCode = 8
const val majorRelease = 3
const val minorRelease = 0
const val patch = 1
const val postfix = "-beta02" // include dash (-)
const val minorRelease = 1
const val patch = 0
const val postfix = "-beta01" // include dash (-)
const val majorVersionName = "$majorRelease.$minorRelease.$patch"
const val versionName = "$majorVersionName$postfix"
const val url = "https://github.com/respawn-app/FlowMVI"
Expand Down Expand Up @@ -80,7 +80,6 @@ object Config {
const val targetSdk = compileSdk
const val minSdk = 21
const val appMinSdk = 26
const val publishingVariant = "release"

// android
const val namespace = artifactId
Expand Down
3 changes: 1 addition & 2 deletions buildSrc/src/main/kotlin/ConfigureMultiplatform.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
@file:Suppress("MissingPackageDeclaration", "unused", "UndocumentedPublicFunction", "LongMethod", "UnusedImports")

import org.gradle.api.Project
import org.gradle.kotlin.dsl.assign
import org.gradle.kotlin.dsl.getValue
import org.gradle.kotlin.dsl.getting
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
Expand Down Expand Up @@ -117,7 +116,7 @@ fun Project.configureMultiplatform(
compilerOptions {
freeCompilerArgs.addAll(Config.compilerArgs)
optIn.addAll(Config.optIns)
progressiveMode = true
progressiveMode.set(true)
}
}
}
Expand Down
14 changes: 8 additions & 6 deletions buildSrc/src/main/kotlin/Util.kt
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,15 @@ fun List<String>.toJavaArrayString() = buildString {

fun String.toBase64() = Base64.getEncoder().encodeToString(toByteArray())

fun Project.localProperties() = Properties().apply {
val file = File(rootProject.rootDir.absolutePath, "local.properties")
if (!file.exists()) {
println("w: Local.properties file does not exist. You may be missing some publishing keys")
return@apply
fun Project.localProperties() = lazy {
Properties().apply {
val file = File(rootProject.rootDir.absolutePath, "local.properties")
if (!file.exists()) {
println("w: Local.properties file does not exist. You may be missing some publishing keys")
return@apply
}
load(FileInputStream(file))
}
load(FileInputStream(file))
}

fun stabilityLevel(version: String): Int {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,9 @@
package pro.respawn.flowmvi.compose.dsl

import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.produceState
import androidx.compose.runtime.rememberUpdatedState
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
Expand Down Expand Up @@ -48,20 +46,18 @@ public fun <S : MVIState, I : MVIIntent, A : MVIAction> ImmutableStore<S, I, A>.
mode: SubscriptionMode = SubscriptionMode.Started,
consume: suspend CoroutineScope.(action: A) -> Unit,
): State<S> {
val state = remember(this) { mutableStateOf(state) }
val block by rememberUpdatedState(consume)
LaunchedEffect(this@subscribe, mode, lifecycle) {
return produceState(state, this, mode, lifecycle) {
withContext(Dispatchers.Main.immediateOrDefault) {
lifecycle.repeatOnLifecycle(mode) {
subscribe(
store = this@subscribe,
consume = { block(it) },
render = { state.value = it }
render = { value = it }
).join()
}
}
}
return state
}

/**
Expand All @@ -82,17 +78,13 @@ public fun <S : MVIState, I : MVIIntent, A : MVIAction> ImmutableStore<S, I, A>.
public fun <S : MVIState, I : MVIIntent, A : MVIAction> ImmutableStore<S, I, A>.subscribe(
lifecycle: SubscriberLifecycle = DefaultLifecycle,
mode: SubscriptionMode = SubscriptionMode.Started,
): State<S> {
val state = remember(this) { mutableStateOf(state) }
LaunchedEffect(this@subscribe, mode, lifecycle) {
withContext(Dispatchers.Main.immediateOrDefault) {
lifecycle.repeatOnLifecycle(mode) {
subscribe(
store = this@subscribe,
render = { state.value = it }
).join()
}
): State<S> = produceState(state, mode, lifecycle, this@subscribe) {
withContext(Dispatchers.Main.immediateOrDefault) {
lifecycle.repeatOnLifecycle(mode) {
subscribe(
store = this@subscribe,
render = { value = it },
).join()
}
}
return state
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ package pro.respawn.flowmvi.api
* The store can be mutated only through [MVIIntent].
* Store is an [IntentReceiver] and can be [close]d to stop it.
*/
@OptIn(ExperimentalStdlibApi::class)
public interface Store<out S : MVIState, in I : MVIIntent, out A : MVIAction> :
ImmutableStore<S, I, A>,
IntentReceiver<I>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,35 @@ internal inline fun template(
tag: String?,
message: () -> String
) = "${level.asSymbol} ${if (tag.isNullOrBlank()) "" else "$tag: "}${message()}"

/**
* Shorthand for [log] with [StoreLogLevel.Trace] as the log level.
*/
public fun StoreLogger.trace(
tag: String? = null,
message: () -> String
): Unit = log(StoreLogLevel.Trace, tag, message)

/**
* Shorthand for [log] with [StoreLogLevel.Debug] as the log level.
*/
public fun StoreLogger.debug(tag: String? = null, message: () -> String): Unit =
log(StoreLogLevel.Debug, tag, message)

/**
* Shorthand for [log] with [StoreLogLevel.Info] as the log level.
*/
public fun StoreLogger.info(tag: String? = null, message: () -> String): Unit =
log(StoreLogLevel.Info, tag, message)

/**
* Shorthand for [log] with [StoreLogLevel.Warn] as the log level.
*/
public fun StoreLogger.warn(tag: String? = null, message: () -> String): Unit =
log(StoreLogLevel.Warn, tag, message)

/**
* Shorthand for [log] with [StoreLogLevel.Error] as the log level.
*/
public fun StoreLogger.error(tag: String? = null, message: () -> String): Unit =
log(StoreLogLevel.Error, tag, message)
Original file line number Diff line number Diff line change
Expand Up @@ -29,42 +29,68 @@ public class SubscriberManager {
internal const val Name = "AwaitSubscribersPlugin"
}

private var subscriber by atomic<Job?>(null)
private val subscriber = atomic<Job?>(null)

/**
* Start waiting for subscribers, suspending until the given number of subscribers arrive.
* After the subscribers arrived, this call will return immediately
*/
public suspend fun await() {
subscriber?.join()
subscriber.value?.join()
}

/**
* Complete the wait period, freeing the store and coroutines that called [await] to continue.
*/
public fun complete() {
subscriber?.cancel()
subscriber.getAndSet(null)?.cancel()
}

/**
* Same as [complete], but suspends until completion
*/
public suspend fun completeAndWait(): Unit? = subscriber?.cancelAndJoin()
public suspend fun completeAndWait(): Unit? = subscriber.getAndSet(null)?.cancelAndJoin()

/**
* Starts waiting for the subscribers until either [this] [CoroutineScope] is cancelled or [complete] is called.
* Usually not called manually but rather launched by the [awaitSubscribersPlugin].
*/
public fun CoroutineScope.launch(timeout: Duration) {
val previous = subscriber
subscriber = launch {
previous?.cancelAndJoin()
withTimeoutOrNull<Nothing>(timeout) { awaitCancellation() }
}.apply {
invokeOnCompletion {
subscriber = null
private fun CoroutineScope.launch(timeout: Duration) {
val previous = subscriber.value
subscriber.getAndSet(
launch {
previous?.cancelAndJoin()
withTimeoutOrNull<Nothing>(timeout) { awaitCancellation() }
}.apply {
invokeOnCompletion { complete() }
}
)
}

internal fun <S : MVIState, I : MVIIntent, A : MVIAction> asPlugin(
name: String,
timeout: Duration,
suspendStore: Boolean,
minSubs: Int,
) = plugin<S, I, A> {
this.name = name

onStart { launch(timeout) }

onState { _, new ->
if (suspendStore) await()
new
}
onAction {
if (suspendStore) await()
it
}
onIntent {
if (suspendStore) await()
it
}
onStop { complete() }
onSubscribe { current -> if (current >= minSubs) completeAndWait() }
}
}

Expand Down Expand Up @@ -96,27 +122,4 @@ public fun <S : MVIState, I : MVIIntent, A : MVIAction> awaitSubscribersPlugin(
suspendStore: Boolean = true,
timeout: Duration = Duration.INFINITE,
name: String = SubscriberManager.Name,
): StorePlugin<S, I, A> = plugin {
this.name = name
onStart {
with(manager) { launch(timeout) }
}
onState { _, new ->
if (suspendStore) manager.await()
new
}
onAction {
if (suspendStore) manager.await()
it
}
onIntent {
if (suspendStore) manager.await()
it
}
onStop {
manager.complete()
}
onSubscribe { current ->
if (current >= minSubs) manager.completeAndWait()
}
}
): StorePlugin<S, I, A> = manager.asPlugin(name, timeout, suspendStore, minSubs)
Original file line number Diff line number Diff line change
Expand Up @@ -24,27 +24,27 @@ public fun <S : MVIState, I : MVIIntent, A : MVIAction> compositePlugin(
name: String? = null,
): StorePlugin<S, I, A> = StorePlugin(
name = name,
onState = { old: S, new: S -> plugins.fold(new) { next -> onState(old, next) } },
onIntent = { intent: I -> plugins.fold(intent) { onIntent(it) } },
onAction = { action: A -> plugins.fold(action) { onAction(it) } },
onException = { e: Exception -> plugins.foldException(e) { onException(it) } },
onSubscribe = { subs: Int -> plugins.fold { onSubscribe(subs) } },
onUnsubscribe = { subs: Int -> plugins.fold { onUnsubscribe(subs) } },
onStart = { plugins.fold { onStart() } },
onStop = { plugins.fold { onStop(it) } }
onState = { old: S, new: S -> plugins.chain(new) { next -> onState(old, next) } },
onIntent = { intent: I -> plugins.chain(intent) { onIntent(it) } },
onAction = { action: A -> plugins.chain(action) { onAction(it) } },
onException = { e: Exception -> plugins.chainException(e) { onException(it) } },
onSubscribe = { subs: Int -> plugins.chain { onSubscribe(subs) } },
onUnsubscribe = { subs: Int -> plugins.chain { onUnsubscribe(subs) } },
onStart = { plugins.chain { onStart() } },
onStop = { plugins.chain { onStop(it) } }
)

private inline fun <S : MVIState, I : MVIIntent, A : MVIAction> List<StorePlugin<S, I, A>>.fold(
private inline fun <S : MVIState, I : MVIIntent, A : MVIAction> List<StorePlugin<S, I, A>>.chain(
block: StorePlugin<S, I, A>.() -> Unit,
) = fastForEach(block)

private inline fun <R, S : MVIState, I : MVIIntent, A : MVIAction> List<StorePlugin<S, I, A>>.fold(
private inline fun <R, S : MVIState, I : MVIIntent, A : MVIAction> List<StorePlugin<S, I, A>>.chain(
initial: R,
block: StorePlugin<S, I, A>.(R) -> R?
) = fastFold<_, R?>(initial) inner@{ acc, it -> block(it, acc ?: return@fold acc) }
) = fastFold<_, R?>(initial) inner@{ acc, it -> block(it, acc ?: return@chain acc) }

// TODO: https://youtrack.jetbrains.com/issue/KT-68509
private inline fun <S : MVIState, I : MVIIntent, A : MVIAction> List<StorePlugin<S, I, A>>.foldException(
private inline fun <S : MVIState, I : MVIIntent, A : MVIAction> List<StorePlugin<S, I, A>>.chainException(
initial: Exception,
block: StorePlugin<S, I, A>.(Exception) -> Exception?
) = fastFold<_, Exception?>(initial) inner@{ acc, it -> block(it, acc ?: return@foldException acc) }
) = fastFold<_, Exception?>(initial) inner@{ acc, it -> block(it, acc ?: return@chainException acc) }
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package pro.respawn.flowmvi.plugins

import pro.respawn.flowmvi.api.MVIAction
import pro.respawn.flowmvi.api.MVIIntent
import pro.respawn.flowmvi.api.MVIState
import pro.respawn.flowmvi.api.StorePlugin

private data object NoOpPlugin : StorePlugin<Nothing, Nothing, Nothing> {

override val name: String? = null
}

/**
* A plugin that does nothing. Useful for testing or mocking
*/
@Suppress("UNCHECKED_CAST")
public fun <S : MVIState, I : MVIIntent, A : MVIAction> NoOpPlugin(): StorePlugin<S, I, A> =
NoOpPlugin as StorePlugin<S, I, A>
2 changes: 2 additions & 0 deletions debugger/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ kotlin {
implementation(compose.components.resources)

implementation(applibs.apiresult)
implementation(applibs.decompose)
implementation(applibs.decompose.compose)
implementation(applibs.bundles.kmputils)
implementation(applibs.bundles.koin)

Expand Down
Loading

0 comments on commit 22f5766

Please sign in to comment.