From 7ff04886242e7dcfa33698beb94f317cd70cef99 Mon Sep 17 00:00:00 2001 From: Jake Wharton Date: Fri, 29 Sep 2023 16:20:00 -0400 Subject: [PATCH] Add a code load status snackbar for Android Compose UI frontend (#1528) iOS to follow. --- .../treehouse/composeui/TreehouseContent.kt | 3 +- .../android/composeui/EmojiSearchActivity.kt | 62 ++++++++++++++----- 2 files changed, 49 insertions(+), 16 deletions(-) diff --git a/redwood-treehouse-host-composeui/src/commonMain/kotlin/app/cash/redwood/treehouse/composeui/TreehouseContent.kt b/redwood-treehouse-host-composeui/src/commonMain/kotlin/app/cash/redwood/treehouse/composeui/TreehouseContent.kt index 15f0ec98f3..5873d46267 100644 --- a/redwood-treehouse-host-composeui/src/commonMain/kotlin/app/cash/redwood/treehouse/composeui/TreehouseContent.kt +++ b/redwood-treehouse-host-composeui/src/commonMain/kotlin/app/cash/redwood/treehouse/composeui/TreehouseContent.kt @@ -51,6 +51,7 @@ public fun TreehouseContent( widgetSystem: WidgetSystem<@Composable () -> Unit>, codeListener: CodeListener = CodeListener(), contentSource: TreehouseContentSource, + modifier: Modifier = Modifier, ) { val onBackPressedDispatcher = platformOnBackPressedDispatcher() @@ -87,7 +88,7 @@ public fun TreehouseContent( } Box( - modifier = Modifier.onSizeChanged { size -> + modifier = modifier.onSizeChanged { size -> viewportSize = with(Density(density.density.toDouble())) { Size(size.width.toDp().value.redwoodDp, size.height.toDp().value.redwoodDp) } diff --git a/samples/emoji-search/android-composeui/src/main/kotlin/com/example/redwood/emojisearch/android/composeui/EmojiSearchActivity.kt b/samples/emoji-search/android-composeui/src/main/kotlin/com/example/redwood/emojisearch/android/composeui/EmojiSearchActivity.kt index 58c57556e6..68f32d033b 100644 --- a/samples/emoji-search/android-composeui/src/main/kotlin/com/example/redwood/emojisearch/android/composeui/EmojiSearchActivity.kt +++ b/samples/emoji-search/android-composeui/src/main/kotlin/com/example/redwood/emojisearch/android/composeui/EmojiSearchActivity.kt @@ -18,9 +18,15 @@ package com.example.redwood.emojisearch.android.composeui import android.os.Bundle import android.util.Log import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Scaffold +import androidx.compose.material.SnackbarDuration.Indefinite +import androidx.compose.material.SnackbarHost +import androidx.compose.material.SnackbarHostState import androidx.compose.runtime.Composable import androidx.compose.runtime.NoLiveLiterals -import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.Modifier import androidx.core.view.WindowCompat import app.cash.redwood.compose.AndroidUiDispatcher.Companion.Main import app.cash.redwood.layout.composeui.ComposeUiRedwoodLayoutWidgetFactory @@ -41,13 +47,16 @@ import com.example.redwood.emojisearch.treehouse.EmojiSearchPresenter import com.example.redwood.emojisearch.widget.EmojiSearchProtocolNodeFactory import com.example.redwood.emojisearch.widget.EmojiSearchWidgetFactories import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.launch import okhttp3.OkHttpClient @NoLiveLiterals class EmojiSearchActivity : ComponentActivity() { private val scope: CoroutineScope = CoroutineScope(Main) + private val snackbarHostState = SnackbarHostState() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -68,13 +77,46 @@ class EmojiSearchActivity : ComponentActivity() { ) } - val view = ComposeView(this) - view.setContent { + setContent { EmojiSearchTheme { - TreehouseContent(treehouseApp, widgetSystem, contentSource = treehouseContentSource) + Scaffold( + snackbarHost = { SnackbarHost(snackbarHostState) }, + ) { contentPadding -> + TreehouseContent( + treehouseApp = treehouseApp, + widgetSystem = widgetSystem, + contentSource = treehouseContentSource, + modifier = Modifier.padding(contentPadding), + ) + } } } - setContentView(view) + } + + val appEventListener: EventListener = object : EventListener() { + private var success = true + private var snackbarJob: Job? = null + + override fun codeLoadFailed(app: TreehouseApp<*>, manifestUrl: String?, exception: Exception, startValue: Any?) { + Log.w("Treehouse", "codeLoadFailed", exception) + if (success) { + // Only show the Snackbar on the first transition from success. + success = false + snackbarJob = scope.launch { + snackbarHostState.showSnackbar( + message = "Unable to load guest code from server", + actionLabel = "Dismiss", + duration = Indefinite, + ) + } + } + } + + override fun codeLoadSuccess(app: TreehouseApp<*>, manifestUrl: String?, manifest: ZiplineManifest, zipline: Zipline, startValue: Any?) { + Log.i("Treehouse", "codeLoadSuccess") + success = true + snackbarJob?.cancel() + } } private fun createTreehouseApp(): TreehouseApp { @@ -109,13 +151,3 @@ class EmojiSearchActivity : ComponentActivity() { super.onDestroy() } } - -val appEventListener: EventListener = object : EventListener() { - override fun codeLoadFailed(app: TreehouseApp<*>, manifestUrl: String?, exception: Exception, startValue: Any?) { - Log.w("Treehouse", "codeLoadFailed", exception) - } - - override fun codeLoadSuccess(app: TreehouseApp<*>, manifestUrl: String?, manifest: ZiplineManifest, zipline: Zipline, startValue: Any?) { - Log.i("Treehouse", "codeLoadSuccess") - } -}