diff --git a/samples/emoji-search/android-views/src/main/kotlin/com/example/redwood/emojisearch/android/views/EmojiSearchActivity.kt b/samples/emoji-search/android-views/src/main/kotlin/com/example/redwood/emojisearch/android/views/EmojiSearchActivity.kt index 2005665e95..7567a5d479 100644 --- a/samples/emoji-search/android-views/src/main/kotlin/com/example/redwood/emojisearch/android/views/EmojiSearchActivity.kt +++ b/samples/emoji-search/android-views/src/main/kotlin/com/example/redwood/emojisearch/android/views/EmojiSearchActivity.kt @@ -18,11 +18,14 @@ package com.example.redwood.emojisearch.android.views import android.annotation.SuppressLint import android.os.Bundle import android.util.Log +import android.view.ViewGroup.LayoutParams.MATCH_PARENT +import android.widget.LinearLayout import androidx.activity.ComponentActivity import androidx.core.view.WindowCompat import app.cash.redwood.compose.AndroidUiDispatcher.Companion.Main import app.cash.redwood.layout.view.ViewRedwoodLayoutWidgetFactory import app.cash.redwood.lazylayout.view.ViewRedwoodLazyLayoutWidgetFactory +import app.cash.redwood.treehouse.CodeListener import app.cash.redwood.treehouse.EventListener import app.cash.redwood.treehouse.TreehouseApp import app.cash.redwood.treehouse.TreehouseAppFactory @@ -78,11 +81,22 @@ class EmojiSearchActivity : ComponentActivity() { } treehouseLayout = TreehouseLayout(this, widgetSystem, onBackPressedDispatcher).apply { - treehouseContentSource.bindWhenReady(this, treehouseApp) + treehouseContentSource.bindWhenReady(this, treehouseApp, codeListener) } setContentView(treehouseLayout) } + private val codeListener: CodeListener = object : CodeListener() { + override fun onUncaughtException(view: TreehouseView<*>, e: Throwable) { + val treehouseLayout = view as TreehouseLayout + treehouseLayout.reset() + treehouseLayout.addView( + ExceptionView(treehouseLayout, e), + LinearLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT), + ) + } + } + private val appEventListener: EventListener = object : EventListener() { private var success = true private var snackbar: Snackbar? = null diff --git a/samples/emoji-search/android-views/src/main/kotlin/com/example/redwood/emojisearch/android/views/ExceptionView.kt b/samples/emoji-search/android-views/src/main/kotlin/com/example/redwood/emojisearch/android/views/ExceptionView.kt new file mode 100644 index 0000000000..bc24fc11f8 --- /dev/null +++ b/samples/emoji-search/android-views/src/main/kotlin/com/example/redwood/emojisearch/android/views/ExceptionView.kt @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2023 Square, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.redwood.emojisearch.android.views + +import android.annotation.SuppressLint +import android.graphics.Color +import android.text.TextUtils +import android.view.Gravity.CENTER +import android.view.ViewGroup.LayoutParams.MATCH_PARENT +import android.view.ViewGroup.LayoutParams.WRAP_CONTENT +import android.widget.LinearLayout +import androidx.appcompat.widget.AppCompatTextView +import app.cash.redwood.treehouse.TreehouseLayout +import app.cash.redwood.ui.Density +import app.cash.redwood.ui.dp + +/** + * Renders an emoji, plus the first line of the exception message, centered and wrapped. The view + * has a light-yellow background. + * + * ``` + * 🦨 + * app.cash.zipline.ZiplineException + * RuntimeException + * boom! + * ``` + */ +@SuppressLint("ViewConstructor") +internal class ExceptionView( + parent: TreehouseLayout, + private val exception: Throwable, +) : LinearLayout(parent.context) { + + init { + orientation = VERTICAL + gravity = CENTER + setBackgroundColor(Color.argb(255, 255, 250, 225)) + + addView( + AppCompatTextView(context).apply { + textAlignment = TEXT_ALIGNMENT_CENTER + setTextColor(Color.BLACK) + textSize = 40f + text = "🦨" + }, + ) + + addView( + AppCompatTextView(context).apply { + textAlignment = TEXT_ALIGNMENT_CENTER + setTextColor(Color.BLACK) + textSize = 16f + text = exception.toString().substringBefore("\n").replace(": ", "\n") + ellipsize = TextUtils.TruncateAt.END + }, + ) + } + + override fun generateDefaultLayoutParams(): LayoutParams { + return LayoutParams(MATCH_PARENT, WRAP_CONTENT).apply { + with(Density(resources)) { + setMargins( + 10f.dp.toPxInt(), + 5f.dp.toPxInt(), + 10f.dp.toPxInt(), + 5f.dp.toPxInt(), + ) + } + } + } +} diff --git a/samples/emoji-search/presenter/src/commonMain/kotlin/com/example/redwood/emojisearch/presenter/EmojiSearch.kt b/samples/emoji-search/presenter/src/commonMain/kotlin/com/example/redwood/emojisearch/presenter/EmojiSearch.kt index 7223f52937..5f1a5da19b 100644 --- a/samples/emoji-search/presenter/src/commonMain/kotlin/com/example/redwood/emojisearch/presenter/EmojiSearch.kt +++ b/samples/emoji-search/presenter/src/commonMain/kotlin/com/example/redwood/emojisearch/presenter/EmojiSearch.kt @@ -137,10 +137,12 @@ private fun LazyColumn( TextInput( state = TextFieldState(searchTerm.text), hint = "Search", - onChange = { it -> - searchTerm = it - - if (it.text == "ex") throw RuntimeException("boom") + onChange = { textFieldState -> + // Make it easy to trigger a crash to manually test exception handling! + if (textFieldState.text == "crash") { + throw RuntimeException("boom!") + } + searchTerm = textFieldState }, ) LazyColumn(