Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ 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.core.ui.LocalIsOffline
import com.google.samples.apps.nowinandroid.core.ui.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
Expand Down Expand Up @@ -117,7 +118,10 @@ fun NiaApp(
)
}
}
CompositionLocalProvider(LocalSnackbarHostState provides snackbarHostState) {
CompositionLocalProvider(
LocalSnackbarHostState provides snackbarHostState,
LocalIsOffline provides isOffline,
) {
NiaApp(
appState = appState,

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ 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.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.core.ui.LocalSnackbarHostState
import com.google.samples.apps.nowinandroid.uitesthiltmanifest.HiltComponentActivity
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ 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.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.core.ui.LocalSnackbarHostState
import com.google.samples.apps.nowinandroid.uitesthiltmanifest.HiltComponentActivity
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright 2026 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.ui

import androidx.compose.runtime.compositionLocalOf

val LocalIsOffline = compositionLocalOf { false }
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright 2022 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.ui

import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.compositionLocalOf

val LocalSnackbarHostState = compositionLocalOf<SnackbarHostState> {
error("SnackbarHostState should be initialized at runtime")
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package com.google.samples.apps.nowinandroid.core.ui

import android.content.Context
import android.net.Uri
import android.widget.Toast
import androidx.annotation.ColorInt
import androidx.browser.customtabs.CustomTabColorSchemeParams
import androidx.browser.customtabs.CustomTabsIntent
Expand Down Expand Up @@ -62,18 +63,26 @@ fun LazyStaggeredGridScope.newsFeed(
val context = LocalContext.current
val analyticsHelper = LocalAnalyticsHelper.current
val backgroundColor = MaterialTheme.colorScheme.background.toArgb()
val isOffline = LocalIsOffline.current

NewsResourceCardExpanded(
userNewsResource = userNewsResource,
isBookmarked = userNewsResource.isSaved,
onClick = {
onExpandedCardClick()
analyticsHelper.logNewsResourceOpened(
newsResourceId = userNewsResource.id,
)
launchCustomChromeTab(context, Uri.parse(userNewsResource.url), backgroundColor)

onNewsResourceViewed(userNewsResource.id)
if (isOffline) {
Toast.makeText(
context,
context.getString(R.string.core_ui_not_connected),
Toast.LENGTH_SHORT,
).show()
} else {
Comment on lines +72 to +78
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The logic for checking the offline state and displaying a Toast is duplicated here and in NewsResourceCardList.kt. Consider abstracting this into a reusable helper function (e.g., an extension on Context) within the core:ui module. This would improve maintainability and ensure that any future changes to the offline notification logic only need to be made in one place. Additionally, using a Toast is inconsistent with the Snackbar-based notification pattern used elsewhere in the app; since LocalSnackbarHostState is now available in this module, it should be preferred for a more consistent user experience.

onExpandedCardClick()
analyticsHelper.logNewsResourceOpened(
newsResourceId = userNewsResource.id,
)
launchCustomChromeTab(context, Uri.parse(userNewsResource.url), backgroundColor)
onNewsResourceViewed(userNewsResource.id)
}
},
hasBeenViewed = userNewsResource.hasBeenViewed,
onToggleBookmark = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package com.google.samples.apps.nowinandroid.core.ui

import android.net.Uri
import android.widget.Toast
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.MaterialTheme
Expand Down Expand Up @@ -47,18 +48,27 @@ fun LazyListScope.userNewsResourceCardItems(
val backgroundColor = MaterialTheme.colorScheme.background.toArgb()
val context = LocalContext.current
val analyticsHelper = LocalAnalyticsHelper.current
val isOffline = LocalIsOffline.current

NewsResourceCardExpanded(
userNewsResource = userNewsResource,
isBookmarked = userNewsResource.isSaved,
hasBeenViewed = userNewsResource.hasBeenViewed,
onToggleBookmark = { onToggleBookmark(userNewsResource) },
onClick = {
analyticsHelper.logNewsResourceOpened(
newsResourceId = userNewsResource.id,
)
launchCustomChromeTab(context, resourceUrl, backgroundColor)
onNewsResourceViewed(userNewsResource.id)
if (isOffline) {
Toast.makeText(
context,
context.getString(R.string.core_ui_not_connected),
Toast.LENGTH_SHORT,
).show()
} else {
Comment on lines +59 to +65
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This offline check and Toast notification logic is identical to the implementation in NewsFeed.kt. To adhere to DRY (Don't Repeat Yourself) principles and simplify future updates to the user feedback mechanism, it is recommended to consolidate this logic into a shared utility function within this module.

analyticsHelper.logNewsResourceOpened(
newsResourceId = userNewsResource.id,
)
launchCustomChromeTab(context, resourceUrl, backgroundColor)
onNewsResourceViewed(userNewsResource.id)
}
},
onTopicClick = onTopicClick,
modifier = itemModifier,
Expand Down
1 change: 1 addition & 0 deletions core/ui/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,5 @@
<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_data">%1$s: %2$s</string>
<string name="core_ui_not_connected">Can\'t open, no internet connection</string>
</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,11 @@
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.navigation.Navigator
import com.google.samples.apps.nowinandroid.core.ui.LocalSnackbarHostState
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.topic.api.navigation.navigateToTopic
Expand All @@ -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")
}