Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial vice-portal implementation #2

Merged
merged 1 commit into from
Jan 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ android-sdk-compile = "34"
android-sdk-target = "34"
android-sdk-min = "24"

androidx-activity = "1.8.2"

composeJetbrains = "1.6.0-alpha01"

conventions = "0.0.65"
Expand Down Expand Up @@ -40,6 +42,7 @@ buildscript-kotlin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", ver
buildscript-publish = { module = "com.vanniktech:gradle-maven-publish-plugin", version.ref = "publish" }
buildscript-ksp = { module = "com.google.devtools.ksp:symbol-processing-gradle-plugin", version.ref = "ksp" }

compose-android-activity = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity" }
compose-compilerJetbrains = "org.jetbrains.compose.compiler:compiler:1.5.7.1"

detektEygraber-formatting = { module = "com.eygraber.detekt.rules:formatting", version.ref = "detektEygraber" }
Expand All @@ -54,3 +57,5 @@ kotlinInject-runtime = { module = "me.tatarka.inject:kotlin-inject-runtime", ver

# not actually used; just here so renovate picks it up
ktlint = { module = "com.pinterest.ktlint:ktlint-bom", version.ref = "ktlint" }

portalCompose = "com.eygraber:portal-compose:0.9.29-SNAPSHOT"
554 changes: 554 additions & 0 deletions kotlin-js-store/yarn.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ plugins {
rootProject.name = "vice"

include("vice-core")
include("vice-portal")

enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")

Expand Down
2 changes: 2 additions & 0 deletions vice-core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ kotlin {
commonMain {
dependencies {
implementation(compose.runtime)

implementation(libs.kotlinx.coroutines.core)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.eygraber.vice

import androidx.compose.runtime.Composable
import kotlinx.coroutines.flow.Flow

public abstract class ViceCompositor<Intent, State> {
@Composable
internal abstract fun composite(intents: Flow<Intent>): State

internal open suspend fun onIntent(intent: Intent) {}

@Composable
internal open fun isBackHandlerEnabled(): Boolean = false

internal open fun onBackPressed(emitIntent: (Intent) -> Unit) {}
}
24 changes: 24 additions & 0 deletions vice-core/src/commonMain/kotlin/com/eygraber/vice/ViceEffects.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.eygraber.vice

import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch

public abstract class ViceEffects {
internal fun initialize(scope: CoroutineScope) {
scope.onInitialized()
}

protected abstract fun CoroutineScope.onInitialized()
}

@Composable
internal fun ViceEffects.Launch() {
LaunchedEffect(Unit) {
launch(Dispatchers.Default) {
initialize(this)
}
}
}
5 changes: 5 additions & 0 deletions vice-core/src/commonMain/kotlin/com/eygraber/vice/ViceView.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.eygraber.vice

import androidx.compose.runtime.Composable

public typealias ViceView<Intent, State> = @Composable (State, (Intent) -> Unit) -> Unit
42 changes: 42 additions & 0 deletions vice-portal/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
plugins {
id("com.eygraber.conventions-kotlin-multiplatform")
id("com.eygraber.conventions-android-library")
id("com.eygraber.conventions-compose-jetbrains")
id("com.eygraber.conventions-detekt")
id("com.eygraber.conventions-publish-maven-central")
}

android {
namespace = "com.eygraber.vice.portal"
}

kotlin {
defaultKmpTargets(
project = project,
)

sourceSets {
androidMain {
dependencies {
implementation(libs.compose.android.activity)
}
}
commonMain {
dependencies {
implementation(projects.viceCore)

implementation(compose.runtime)

implementation(libs.kotlinx.coroutines.core)
implementation(libs.portalCompose)
}
}

commonTest {
dependencies {
implementation(kotlin("test-common"))
implementation(kotlin("test-annotations-common"))
}
}
}
}
3 changes: 3 additions & 0 deletions vice-portal/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
POM_ARTIFACT_ID=vice-portal
POM_NAME=VICE Portal
POM_DESCRIPTION=A VICE integration with ComposePortal
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.eygraber.vice.portal

import androidx.compose.runtime.Composable

@Composable
public actual fun BackHandler(enabled: Boolean, onBack: () -> Unit) {
androidx.activity.compose.BackHandler(enabled, onBack)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.eygraber.vice.portal

import androidx.compose.runtime.Composable

@Composable
public actual fun BackHandler(enabled: Boolean, onBack: () -> Unit) {
// NO-OP for now
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.eygraber.vice.portal

import androidx.compose.runtime.Composable

@Composable
public expect fun BackHandler(enabled: Boolean, onBack: () -> Unit)
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// needed to access internal members of vice-core
@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")

package com.eygraber.vice.portal

import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import com.eygraber.portal.compose.ComposePortal
import com.eygraber.vice.Launch
import com.eygraber.vice.ViceCompositor
import com.eygraber.vice.ViceEffects
import com.eygraber.vice.ViceView
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.launch

public abstract class VicePortal<K, V, I, C, E, S> : ComposePortal<K>
where V : ViceView<I, S>, C : ViceCompositor<I, S>, E : ViceEffects {
protected abstract val view: V
private val intents = MutableSharedFlow<I>(extraBufferCapacity = 64)
protected abstract val compositor: C
protected abstract val effects: E

@Composable
public final override fun Render() {
val scope = rememberCoroutineScope {
Dispatchers.Main.immediate
}

BackHandler(enabled = compositor.isBackHandlerEnabled()) {
compositor.onBackPressed { intent ->
// this is synchronous because the dispatcher is Main.immediate
scope.launch {
compositor.onIntent(intent)
}

intents.tryEmit(intent)
}
}

Render(
view as ViceView<I, S>,
intents,
compositor,
effects,
scope,
)
}
}

@Composable
private inline fun <I, S> Render(
view: ViceView<I, S>,
intents: SharedFlow<I>,
compositor: ViceCompositor<I, S>,
effects: ViceEffects,
scope: CoroutineScope,
) {
effects.Launch()

val state = compositor.composite(intents)
val intentHandler = remember(scope, compositor, intents) {
{ intent: I ->
// this is synchronous because the dispatcher is Main.immediate
scope.launch {
compositor.onIntent(intent)
}

(intents as MutableSharedFlow<I>).tryEmit(intent)
}
}

view(state, intentHandler)
}