diff --git a/app/src/main/kotlin/androidx/compose/material3/Snackbar2.kt b/app/src/main/kotlin/androidx/compose/material3/Snackbar2.kt
new file mode 100644
index 000000000..7fd2d4f3d
--- /dev/null
+++ b/app/src/main/kotlin/androidx/compose/material3/Snackbar2.kt
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * 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 androidx.compose.material3
+
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Close
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.unit.dp
+
+/**
+ * Material Design snackbar.
+ *
+ * Snackbars provide brief messages about app processes at the bottom of the screen.
+ *
+ * 
+ *
+ * Snackbars inform users of a process that an app has performed or will perform. They appear
+ * temporarily, towards the bottom of the screen. They shouldn’t interrupt the user experience,
+ * and they don’t require user input to disappear.
+ *
+ * A Snackbar can contain a single action. "Dismiss" or "cancel" actions are optional.
+ *
+ * Snackbars with an action should not timeout or self-dismiss until the user performs another
+ * action. Here, moving the keyboard focus indicator to navigate through interactive elements in a
+ * page is not considered an action.
+ *
+ * This version of snackbar is designed to work with [SnackbarData] provided by the
+ * [SnackbarHost], which is usually used inside of the [Scaffold].
+ *
+ * This components provides only the visuals of the Snackbar. If you need to show a Snackbar
+ * with defaults on the screen, use [SnackbarHostState.showSnackbar]:
+ *
+ * @sample androidx.compose.material3.samples.ScaffoldWithSimpleSnackbar
+ *
+ * If you want to customize appearance of the Snackbar, you can pass your own version as a child
+ * of the [SnackbarHost] to the [Scaffold]:
+ *
+ * @sample androidx.compose.material3.samples.ScaffoldWithCustomSnackbar
+ *
+ * When a [SnackbarData.visuals] sets the Snackbar's duration as [SnackbarDuration.Indefinite], it's
+ * recommended to display an additional close affordance action.
+ * See [SnackbarVisuals.withDismissAction]:
+ *
+ * @sample androidx.compose.material3.samples.ScaffoldWithIndefiniteSnackbar
+ *
+ * @param snackbarData data about the current snackbar showing via [SnackbarHostState]
+ * @param modifier the [Modifier] to be applied to this snackbar
+ * @param actionOnNewLine whether or not action should be put on a separate line. Recommended for
+ * action with long action text.
+ * @param shape defines the shape of this snackbar's container
+ * @param containerColor the color used for the background of this snackbar. Use [Color.Transparent]
+ * to have no color.
+ * @param contentColor the preferred color for content inside this snackbar
+ * @param actionColor the color of the snackbar's action
+ * @param actionContentColor the preferred content color for the optional action inside this
+ * snackbar. See [SnackbarVisuals.actionLabel].
+ * @param dismissActionContentColor the preferred content color for the optional dismiss action
+ * inside this snackbar. See [SnackbarVisuals.withDismissAction].
+ */
+@Composable
+fun Snackbar2(
+ snackbarData: SnackbarData,
+ modifier: Modifier = Modifier,
+ actionOnNewLine: Boolean = false,
+ shape: Shape = SnackbarDefaults.shape,
+ containerColor: Color = SnackbarDefaults.color,
+ contentColor: Color = SnackbarDefaults.contentColor,
+ actionColor: Color = SnackbarDefaults.actionColor,
+ actionContentColor: Color = SnackbarDefaults.actionContentColor,
+ dismissActionContentColor: Color = SnackbarDefaults.dismissActionContentColor,
+) {
+ val actionLabel = snackbarData.visuals.actionLabel
+ val actionComposable: (@Composable () -> Unit)? = if (actionLabel != null) {
+ @Composable {
+ TextButton(
+ colors = ButtonDefaults.textButtonColors(contentColor = actionColor),
+ onClick = { snackbarData.performAction() },
+ content = { Text(actionLabel) },
+ )
+ }
+ } else {
+ null
+ }
+ val dismissActionComposable: (@Composable () -> Unit)? =
+ if (snackbarData.visuals.withDismissAction) {
+ @Composable {
+ IconButton(
+ onClick = { snackbarData.dismiss() },
+ content = {
+ Icon(
+ Icons.Filled.Close,
+ contentDescription = null,
+ // Workaround for regression of https://android-review.googlesource.com/c/platform/frameworks/support/+/2881006
+ tint = SnackbarDefaults.dismissActionContentColor,
+ )
+ },
+ )
+ }
+ } else {
+ null
+ }
+ Snackbar(
+ modifier = modifier.padding(12.dp),
+ action = actionComposable,
+ dismissAction = dismissActionComposable,
+ actionOnNewLine = actionOnNewLine,
+ shape = shape,
+ containerColor = containerColor,
+ contentColor = contentColor,
+ actionContentColor = actionContentColor,
+ dismissActionContentColor = dismissActionContentColor,
+ content = { Text(snackbarData.visuals.message) },
+ )
+}
diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/MainActivity.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/MainActivity.kt
index e5d5b291d..f1e9811d1 100644
--- a/app/src/main/kotlin/com/hippo/ehviewer/ui/MainActivity.kt
+++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/MainActivity.kt
@@ -59,6 +59,7 @@ import androidx.compose.material3.NavigationDrawerItem
import androidx.compose.material3.Scaffold
import androidx.compose.material3.ShapeDefaults
import androidx.compose.material3.SideDrawer
+import androidx.compose.material3.Snackbar2
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.SnackbarResult
@@ -307,7 +308,7 @@ class MainActivity : EhActivity() {
val snackMessage = stringResource(R.string.clipboard_gallery_url_snack_message)
val snackAction = stringResource(R.string.clipboard_gallery_url_snack_action)
LifecycleResumeEffect {
- scope.launch {
+ val job = scope.launch {
delay(300)
val text = clipboardManager.getUrlFromClipboard(applicationContext)
val hashCode = text?.hashCode() ?: 0
@@ -328,7 +329,7 @@ class MainActivity : EhActivity() {
}
Settings.clipboardTextHashCode = hashCode
}
- onPauseOrDispose { }
+ onPauseOrDispose { job.cancel() }
}
val currentDestination by navController.currentDestinationAsState()
val lockDrawerHandle = remember { mutableStateListOf() }
@@ -340,7 +341,7 @@ class MainActivity : EhActivity() {
LocalDrawerLockHandle provides lockDrawerHandle,
LocalSnackbarHostState provides snackbarState,
) {
- Scaffold(snackbarHost = { SnackbarHost(snackbarState) }) {
+ Scaffold(snackbarHost = { SnackbarHost(snackbarState) { Snackbar2(it) } }) {
LocalTouchSlopProvider(Settings.touchSlopFactor.toFloat()) {
ModalNavigationDrawer(
drawerContent = {