pull/2118/merge
Saad Khan 1 week ago committed by GitHub
commit e4f384a3d8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -65,6 +65,7 @@ import androidx.navigation3.runtime.NavKey
import androidx.navigation3.runtime.entryProvider import androidx.navigation3.runtime.entryProvider
import androidx.navigation3.ui.NavDisplay import androidx.navigation3.ui.NavDisplay
import com.google.samples.apps.nowinandroid.R 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.NiaBackground
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaGradientBackground import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaGradientBackground
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaNavigationSuiteScaffold 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.designsystem.theme.LocalGradientColors
import com.google.samples.apps.nowinandroid.core.navigation.Navigator import com.google.samples.apps.nowinandroid.core.navigation.Navigator
import com.google.samples.apps.nowinandroid.core.navigation.toEntries 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.bookmarks.impl.navigation.bookmarksEntry
import com.google.samples.apps.nowinandroid.feature.foryou.api.navigation.ForYouNavKey import com.google.samples.apps.nowinandroid.feature.foryou.api.navigation.ForYouNavKey
import com.google.samples.apps.nowinandroid.feature.foryou.impl.navigation.forYouEntry import com.google.samples.apps.nowinandroid.feature.foryou.impl.navigation.forYouEntry

@ -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.test.repository.FakeUserDataRepository
import com.google.samples.apps.nowinandroid.core.data.util.NetworkMonitor 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.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.designsystem.theme.NiaTheme
import com.google.samples.apps.nowinandroid.core.testing.util.DefaultRoborazziOptions 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 com.google.samples.apps.nowinandroid.uitesthiltmanifest.HiltComponentActivity
import dagger.hilt.android.testing.HiltAndroidRule import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest import dagger.hilt.android.testing.HiltAndroidTest

@ -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.test.repository.FakeUserDataRepository
import com.google.samples.apps.nowinandroid.core.data.util.NetworkMonitor 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.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.designsystem.theme.NiaTheme
import com.google.samples.apps.nowinandroid.core.testing.util.DefaultRoborazziOptions 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 com.google.samples.apps.nowinandroid.uitesthiltmanifest.HiltComponentActivity
import dagger.hilt.android.testing.HiltAndroidRule import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest import dagger.hilt.android.testing.HiltAndroidTest

@ -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() }

@ -16,8 +16,10 @@
package com.google.samples.apps.nowinandroid.core.ui package com.google.samples.apps.nowinandroid.core.ui
import android.content.ActivityNotFoundException
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
import android.util.Log
import androidx.annotation.ColorInt import androidx.annotation.ColorInt
import androidx.browser.customtabs.CustomTabColorSchemeParams import androidx.browser.customtabs.CustomTabColorSchemeParams
import androidx.browser.customtabs.CustomTabsIntent 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.foundation.lazy.staggeredgrid.items
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext 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.Devices
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.google.samples.apps.nowinandroid.core.analytics.LocalAnalyticsHelper 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.designsystem.theme.NiaTheme
import com.google.samples.apps.nowinandroid.core.model.data.UserNewsResource import com.google.samples.apps.nowinandroid.core.model.data.UserNewsResource
import kotlinx.coroutines.launch
/** /**
* An extension on [LazyListScope] defining a feed with news resources. * An extension on [LazyListScope] defining a feed with news resources.
@ -62,6 +68,9 @@ fun LazyStaggeredGridScope.newsFeed(
val context = LocalContext.current val context = LocalContext.current
val analyticsHelper = LocalAnalyticsHelper.current val analyticsHelper = LocalAnalyticsHelper.current
val backgroundColor = MaterialTheme.colorScheme.background.toArgb() val backgroundColor = MaterialTheme.colorScheme.background.toArgb()
val snackbarHostState = LocalSnackbarHostState.current
val coroutineScope = rememberCoroutineScope()
val noBrowserMessage = stringResource(R.string.core_ui_no_browser_available)
NewsResourceCardExpanded( NewsResourceCardExpanded(
userNewsResource = userNewsResource, userNewsResource = userNewsResource,
@ -71,7 +80,11 @@ fun LazyStaggeredGridScope.newsFeed(
analyticsHelper.logNewsResourceOpened( analyticsHelper.logNewsResourceOpened(
newsResourceId = userNewsResource.id, 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) 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() val customTabBarColor = CustomTabColorSchemeParams.Builder()
.setToolbarColor(toolbarColor).build() .setToolbarColor(toolbarColor).build()
val customTabsIntent = CustomTabsIntent.Builder() val customTabsIntent = CustomTabsIntent.Builder()
.setDefaultColorSchemeParams(customTabBarColor) .setDefaultColorSchemeParams(customTabBarColor)
.build() .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
}
} }
/** /**

@ -20,11 +20,15 @@ import android.net.Uri
import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext 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.analytics.LocalAnalyticsHelper
import com.google.samples.apps.nowinandroid.core.designsystem.component.LocalSnackbarHostState
import com.google.samples.apps.nowinandroid.core.model.data.UserNewsResource 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 * 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 backgroundColor = MaterialTheme.colorScheme.background.toArgb()
val context = LocalContext.current val context = LocalContext.current
val analyticsHelper = LocalAnalyticsHelper.current val analyticsHelper = LocalAnalyticsHelper.current
val snackbarHostState = LocalSnackbarHostState.current
val coroutineScope = rememberCoroutineScope()
val noBrowserMessage = stringResource(R.string.core_ui_no_browser_available)
NewsResourceCardExpanded( NewsResourceCardExpanded(
userNewsResource = userNewsResource, userNewsResource = userNewsResource,
@ -57,7 +64,11 @@ fun LazyListScope.userNewsResourceCardItems(
analyticsHelper.logNewsResourceOpened( analyticsHelper.logNewsResourceOpened(
newsResourceId = userNewsResource.id, newsResourceId = userNewsResource.id,
) )
launchCustomChromeTab(context, resourceUrl, backgroundColor) if (!launchCustomChromeTab(context, resourceUrl, backgroundColor)) {
coroutineScope.launch {
snackbarHostState.showSnackbar(noBrowserMessage)
}
}
onNewsResourceViewed(userNewsResource.id) onNewsResourceViewed(userNewsResource.id)
}, },
onTopicClick = onTopicClick, onTopicClick = onTopicClick,

@ -31,4 +31,5 @@
<string name="core_ui_interests_card_unfollow_button_content_desc">Unfollow interest</string> <string name="core_ui_interests_card_unfollow_button_content_desc">Unfollow interest</string>
<string name="core_ui_feed_sharing">Feed sharing</string> <string name="core_ui_feed_sharing">Feed sharing</string>
<string name="core_ui_feed_sharing_data">%1$s: %2$s</string> <string name="core_ui_feed_sharing_data">%1$s: %2$s</string>
<string name="core_ui_no_browser_available">No browser available to open link</string>
</resources> </resources>

@ -17,11 +17,10 @@
package com.google.samples.apps.nowinandroid.feature.bookmarks.impl.navigation package com.google.samples.apps.nowinandroid.feature.bookmarks.impl.navigation
import androidx.compose.material3.SnackbarDuration.Short import androidx.compose.material3.SnackbarDuration.Short
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.SnackbarResult.ActionPerformed import androidx.compose.material3.SnackbarResult.ActionPerformed
import androidx.compose.runtime.compositionLocalOf
import androidx.navigation3.runtime.EntryProviderScope import androidx.navigation3.runtime.EntryProviderScope
import androidx.navigation3.runtime.NavKey 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.core.navigation.Navigator
import com.google.samples.apps.nowinandroid.feature.bookmarks.api.navigation.BookmarksNavKey import com.google.samples.apps.nowinandroid.feature.bookmarks.api.navigation.BookmarksNavKey
import com.google.samples.apps.nowinandroid.feature.bookmarks.impl.BookmarksScreen import com.google.samples.apps.nowinandroid.feature.bookmarks.impl.BookmarksScreen
@ -42,8 +41,3 @@ fun EntryProviderScope<NavKey>.bookmarksEntry(navigator: Navigator) {
) )
} }
} }
// TODO: Why is this here?
val LocalSnackbarHostState = compositionLocalOf<SnackbarHostState> {
error("SnackbarHostState state should be initialized at runtime")
}

@ -86,6 +86,7 @@ import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.PermissionStatus.Denied import com.google.accompanist.permissions.PermissionStatus.Denied
import com.google.accompanist.permissions.rememberPermissionState 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.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.NiaButton
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaIconToggleButton import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaIconToggleButton
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaOverlayLoadingWheel 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.launchCustomChromeTab
import com.google.samples.apps.nowinandroid.core.ui.newsFeed 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.feature.foryou.api.R
import com.google.samples.apps.nowinandroid.core.ui.R as UiR
@Composable @Composable
fun ForYouScreen( fun ForYouScreen(
@ -469,16 +471,21 @@ private fun DeepLinkEffect(
) { ) {
val context = LocalContext.current val context = LocalContext.current
val backgroundColor = MaterialTheme.colorScheme.background.toArgb() val backgroundColor = MaterialTheme.colorScheme.background.toArgb()
val snackbarHostState = LocalSnackbarHostState.current
val noBrowserMessage = stringResource(UiR.string.core_ui_no_browser_available)
LaunchedEffect(userNewsResource) { LaunchedEffect(userNewsResource) {
if (userNewsResource == null) return@LaunchedEffect if (userNewsResource == null) return@LaunchedEffect
if (!userNewsResource.hasBeenViewed) onDeepLinkOpened(userNewsResource.id) if (!userNewsResource.hasBeenViewed) onDeepLinkOpened(userNewsResource.id)
launchCustomChromeTab( val launched = launchCustomChromeTab(
context = context, context = context,
uri = Uri.parse(userNewsResource.url), uri = Uri.parse(userNewsResource.url),
toolbarColor = backgroundColor, toolbarColor = backgroundColor,
) )
if (!launched) {
snackbarHostState.showSnackbar(noBrowserMessage)
}
} }
} }

Loading…
Cancel
Save