diff --git a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/NiaApp.kt b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/NiaApp.kt
index bfaa27fa6..9599ebab0 100644
--- a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/NiaApp.kt
+++ b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/NiaApp.kt
@@ -65,6 +65,7 @@ import androidx.navigation3.runtime.NavKey
import androidx.navigation3.runtime.entryProvider
import androidx.navigation3.ui.NavDisplay
import com.google.samples.apps.nowinandroid.R
+import com.google.samples.apps.nowinandroid.core.designsystem.component.LocalSnackbarHostState
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaBackground
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaGradientBackground
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaNavigationSuiteScaffold
@@ -74,7 +75,6 @@ import com.google.samples.apps.nowinandroid.core.designsystem.theme.GradientColo
import com.google.samples.apps.nowinandroid.core.designsystem.theme.LocalGradientColors
import com.google.samples.apps.nowinandroid.core.navigation.Navigator
import com.google.samples.apps.nowinandroid.core.navigation.toEntries
-import com.google.samples.apps.nowinandroid.feature.bookmarks.impl.navigation.LocalSnackbarHostState
import com.google.samples.apps.nowinandroid.feature.bookmarks.impl.navigation.bookmarksEntry
import com.google.samples.apps.nowinandroid.feature.foryou.api.navigation.ForYouNavKey
import com.google.samples.apps.nowinandroid.feature.foryou.impl.navigation.forYouEntry
diff --git a/app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/SnackbarInsetsScreenshotTests.kt b/app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/SnackbarInsetsScreenshotTests.kt
index 3c7610193..e9522d042 100644
--- a/app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/SnackbarInsetsScreenshotTests.kt
+++ b/app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/SnackbarInsetsScreenshotTests.kt
@@ -65,9 +65,9 @@ import com.google.samples.apps.nowinandroid.core.data.repository.UserNewsResourc
import com.google.samples.apps.nowinandroid.core.data.test.repository.FakeUserDataRepository
import com.google.samples.apps.nowinandroid.core.data.util.NetworkMonitor
import com.google.samples.apps.nowinandroid.core.data.util.TimeZoneMonitor
+import com.google.samples.apps.nowinandroid.core.designsystem.component.LocalSnackbarHostState
import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme
import com.google.samples.apps.nowinandroid.core.testing.util.DefaultRoborazziOptions
-import com.google.samples.apps.nowinandroid.feature.bookmarks.impl.navigation.LocalSnackbarHostState
import com.google.samples.apps.nowinandroid.uitesthiltmanifest.HiltComponentActivity
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
diff --git a/app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/SnackbarScreenshotTests.kt b/app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/SnackbarScreenshotTests.kt
index 75dc6baa7..7a24df05b 100644
--- a/app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/SnackbarScreenshotTests.kt
+++ b/app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/SnackbarScreenshotTests.kt
@@ -38,9 +38,9 @@ import com.google.samples.apps.nowinandroid.core.data.repository.UserNewsResourc
import com.google.samples.apps.nowinandroid.core.data.test.repository.FakeUserDataRepository
import com.google.samples.apps.nowinandroid.core.data.util.NetworkMonitor
import com.google.samples.apps.nowinandroid.core.data.util.TimeZoneMonitor
+import com.google.samples.apps.nowinandroid.core.designsystem.component.LocalSnackbarHostState
import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme
import com.google.samples.apps.nowinandroid.core.testing.util.DefaultRoborazziOptions
-import com.google.samples.apps.nowinandroid.feature.bookmarks.impl.navigation.LocalSnackbarHostState
import com.google.samples.apps.nowinandroid.uitesthiltmanifest.HiltComponentActivity
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
diff --git a/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/Snackbar.kt b/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/Snackbar.kt
new file mode 100644
index 000000000..b0e914037
--- /dev/null
+++ b/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/Snackbar.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2024 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
+ *
+ * https://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.google.samples.apps.nowinandroid.core.designsystem.component
+
+import androidx.compose.material3.SnackbarHostState
+import androidx.compose.runtime.compositionLocalOf
+
+val LocalSnackbarHostState = compositionLocalOf { SnackbarHostState() }
diff --git a/core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/NewsFeed.kt b/core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/NewsFeed.kt
index 0b7b9f570..e7bf1728d 100644
--- a/core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/NewsFeed.kt
+++ b/core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/NewsFeed.kt
@@ -16,8 +16,10 @@
package com.google.samples.apps.nowinandroid.core.ui
+import android.content.ActivityNotFoundException
import android.content.Context
import android.net.Uri
+import android.util.Log
import androidx.annotation.ColorInt
import androidx.browser.customtabs.CustomTabColorSchemeParams
import androidx.browser.customtabs.CustomTabsIntent
@@ -29,16 +31,20 @@ import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells
import androidx.compose.foundation.lazy.staggeredgrid.items
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Devices
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import com.google.samples.apps.nowinandroid.core.analytics.LocalAnalyticsHelper
+import com.google.samples.apps.nowinandroid.core.designsystem.component.LocalSnackbarHostState
import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme
import com.google.samples.apps.nowinandroid.core.model.data.UserNewsResource
+import kotlinx.coroutines.launch
/**
* An extension on [LazyListScope] defining a feed with news resources.
@@ -62,6 +68,9 @@ fun LazyStaggeredGridScope.newsFeed(
val context = LocalContext.current
val analyticsHelper = LocalAnalyticsHelper.current
val backgroundColor = MaterialTheme.colorScheme.background.toArgb()
+ val snackbarHostState = LocalSnackbarHostState.current
+ val coroutineScope = rememberCoroutineScope()
+ val noBrowserMessage = stringResource(R.string.core_ui_no_browser_available)
NewsResourceCardExpanded(
userNewsResource = userNewsResource,
@@ -71,7 +80,11 @@ fun LazyStaggeredGridScope.newsFeed(
analyticsHelper.logNewsResourceOpened(
newsResourceId = userNewsResource.id,
)
- launchCustomChromeTab(context, Uri.parse(userNewsResource.url), backgroundColor)
+ if (!launchCustomChromeTab(context, Uri.parse(userNewsResource.url), backgroundColor)) {
+ coroutineScope.launch {
+ snackbarHostState.showSnackbar(noBrowserMessage)
+ }
+ }
onNewsResourceViewed(userNewsResource.id)
},
@@ -92,14 +105,25 @@ fun LazyStaggeredGridScope.newsFeed(
}
}
-fun launchCustomChromeTab(context: Context, uri: Uri, @ColorInt toolbarColor: Int) {
+/**
+ * Launches a Chrome Custom Tab for the given [uri].
+ *
+ * @return `true` if the URL was launched successfully, `false` if no browser activity was found.
+ */
+fun launchCustomChromeTab(context: Context, uri: Uri, @ColorInt toolbarColor: Int): Boolean {
val customTabBarColor = CustomTabColorSchemeParams.Builder()
.setToolbarColor(toolbarColor).build()
val customTabsIntent = CustomTabsIntent.Builder()
.setDefaultColorSchemeParams(customTabBarColor)
.build()
- customTabsIntent.launchUrl(context, uri)
+ return try {
+ customTabsIntent.launchUrl(context, uri)
+ true
+ } catch (e: ActivityNotFoundException) {
+ Log.e("NewsFeed", "No browser activity found to handle URI: $uri", e)
+ false
+ }
}
/**
diff --git a/core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/NewsResourceCardList.kt b/core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/NewsResourceCardList.kt
index ea1c09d01..32b9512b7 100644
--- a/core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/NewsResourceCardList.kt
+++ b/core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/NewsResourceCardList.kt
@@ -20,11 +20,15 @@ import android.net.Uri
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
import com.google.samples.apps.nowinandroid.core.analytics.LocalAnalyticsHelper
+import com.google.samples.apps.nowinandroid.core.designsystem.component.LocalSnackbarHostState
import com.google.samples.apps.nowinandroid.core.model.data.UserNewsResource
+import kotlinx.coroutines.launch
/**
* Extension function for displaying a [List] of [NewsResourceCardExpanded] backed by a list of
@@ -47,6 +51,9 @@ fun LazyListScope.userNewsResourceCardItems(
val backgroundColor = MaterialTheme.colorScheme.background.toArgb()
val context = LocalContext.current
val analyticsHelper = LocalAnalyticsHelper.current
+ val snackbarHostState = LocalSnackbarHostState.current
+ val coroutineScope = rememberCoroutineScope()
+ val noBrowserMessage = stringResource(R.string.core_ui_no_browser_available)
NewsResourceCardExpanded(
userNewsResource = userNewsResource,
@@ -57,7 +64,11 @@ fun LazyListScope.userNewsResourceCardItems(
analyticsHelper.logNewsResourceOpened(
newsResourceId = userNewsResource.id,
)
- launchCustomChromeTab(context, resourceUrl, backgroundColor)
+ if (!launchCustomChromeTab(context, resourceUrl, backgroundColor)) {
+ coroutineScope.launch {
+ snackbarHostState.showSnackbar(noBrowserMessage)
+ }
+ }
onNewsResourceViewed(userNewsResource.id)
},
onTopicClick = onTopicClick,
diff --git a/core/ui/src/main/res/values/strings.xml b/core/ui/src/main/res/values/strings.xml
index a97746a9c..564d9c176 100644
--- a/core/ui/src/main/res/values/strings.xml
+++ b/core/ui/src/main/res/values/strings.xml
@@ -31,4 +31,5 @@
Unfollow interest
Feed sharing
%1$s: %2$s
+ No browser available to open link
diff --git a/feature/bookmarks/impl/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/bookmarks/impl/navigation/BookmarksEntryProvider.kt b/feature/bookmarks/impl/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/bookmarks/impl/navigation/BookmarksEntryProvider.kt
index 467965651..42398edfe 100644
--- a/feature/bookmarks/impl/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/bookmarks/impl/navigation/BookmarksEntryProvider.kt
+++ b/feature/bookmarks/impl/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/bookmarks/impl/navigation/BookmarksEntryProvider.kt
@@ -17,11 +17,10 @@
package com.google.samples.apps.nowinandroid.feature.bookmarks.impl.navigation
import androidx.compose.material3.SnackbarDuration.Short
-import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.SnackbarResult.ActionPerformed
-import androidx.compose.runtime.compositionLocalOf
import androidx.navigation3.runtime.EntryProviderScope
import androidx.navigation3.runtime.NavKey
+import com.google.samples.apps.nowinandroid.core.designsystem.component.LocalSnackbarHostState
import com.google.samples.apps.nowinandroid.core.navigation.Navigator
import com.google.samples.apps.nowinandroid.feature.bookmarks.api.navigation.BookmarksNavKey
import com.google.samples.apps.nowinandroid.feature.bookmarks.impl.BookmarksScreen
@@ -42,8 +41,3 @@ fun EntryProviderScope.bookmarksEntry(navigator: Navigator) {
)
}
}
-
-// TODO: Why is this here?
-val LocalSnackbarHostState = compositionLocalOf {
- error("SnackbarHostState state should be initialized at runtime")
-}
diff --git a/feature/foryou/impl/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/foryou/impl/ForYouScreen.kt b/feature/foryou/impl/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/foryou/impl/ForYouScreen.kt
index 0ae916db3..937d56a41 100644
--- a/feature/foryou/impl/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/foryou/impl/ForYouScreen.kt
+++ b/feature/foryou/impl/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/foryou/impl/ForYouScreen.kt
@@ -86,6 +86,7 @@ import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.PermissionStatus.Denied
import com.google.accompanist.permissions.rememberPermissionState
import com.google.samples.apps.nowinandroid.core.designsystem.component.DynamicAsyncImage
+import com.google.samples.apps.nowinandroid.core.designsystem.component.LocalSnackbarHostState
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaButton
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaIconToggleButton
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaOverlayLoadingWheel
@@ -104,6 +105,7 @@ import com.google.samples.apps.nowinandroid.core.ui.UserNewsResourcePreviewParam
import com.google.samples.apps.nowinandroid.core.ui.launchCustomChromeTab
import com.google.samples.apps.nowinandroid.core.ui.newsFeed
import com.google.samples.apps.nowinandroid.feature.foryou.api.R
+import com.google.samples.apps.nowinandroid.core.ui.R as UiR
@Composable
fun ForYouScreen(
@@ -469,16 +471,21 @@ private fun DeepLinkEffect(
) {
val context = LocalContext.current
val backgroundColor = MaterialTheme.colorScheme.background.toArgb()
+ val snackbarHostState = LocalSnackbarHostState.current
+ val noBrowserMessage = stringResource(UiR.string.core_ui_no_browser_available)
LaunchedEffect(userNewsResource) {
if (userNewsResource == null) return@LaunchedEffect
if (!userNewsResource.hasBeenViewed) onDeepLinkOpened(userNewsResource.id)
- launchCustomChromeTab(
+ val launched = launchCustomChromeTab(
context = context,
uri = Uri.parse(userNewsResource.url),
toolbarColor = backgroundColor,
)
+ if (!launched) {
+ snackbarHostState.showSnackbar(noBrowserMessage)
+ }
}
}