diff --git a/core/designsystem/src/main/java/com/google/samples/apps/nowinandroid/core/designsystem/component/LoadingWheel.kt b/core/designsystem/src/main/java/com/google/samples/apps/nowinandroid/core/designsystem/component/LoadingWheel.kt
index ba3f7c2b0..c32637b69 100644
--- a/core/designsystem/src/main/java/com/google/samples/apps/nowinandroid/core/designsystem/component/LoadingWheel.kt
+++ b/core/designsystem/src/main/java/com/google/samples/apps/nowinandroid/core/designsystem/component/LoadingWheel.kt
@@ -42,6 +42,7 @@ import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.drawscope.rotate
import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.platform.LocalInspectionMode
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
@@ -56,7 +57,8 @@ fun NiaLoadingWheel(
val infiniteTransition = rememberInfiniteTransition()
// Specifies the float animation for slowly drawing out the lines on entering
- val floatAnimValues = (0 until NUM_OF_LINES).map { remember { Animatable(1F) } }
+ val startValue = if (LocalInspectionMode.current) 0F else 1F
+ val floatAnimValues = (0 until NUM_OF_LINES).map { remember { Animatable(startValue) } }
LaunchedEffect(floatAnimValues) {
(0 until NUM_OF_LINES).map { index ->
launch {
diff --git a/feature/bookmarks/src/main/java/com/google/samples/apps/nowinandroid/feature/bookmarks/BookmarksScreen.kt b/feature/bookmarks/src/main/java/com/google/samples/apps/nowinandroid/feature/bookmarks/BookmarksScreen.kt
index 560378370..06a87bc9e 100644
--- a/feature/bookmarks/src/main/java/com/google/samples/apps/nowinandroid/feature/bookmarks/BookmarksScreen.kt
+++ b/feature/bookmarks/src/main/java/com/google/samples/apps/nowinandroid/feature/bookmarks/BookmarksScreen.kt
@@ -16,14 +16,17 @@
package com.google.samples.apps.nowinandroid.feature.bookmarks
+import androidx.annotation.VisibleForTesting
+import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
-import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.layout.windowInsetsBottomHeight
import androidx.compose.foundation.layout.wrapContentSize
@@ -31,19 +34,29 @@ import androidx.compose.foundation.lazy.grid.GridCells.Adaptive
import androidx.compose.foundation.lazy.grid.GridItemSpan
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
+import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.ExperimentalLifecycleComposeApi
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaLoadingWheel
+import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme
+import com.google.samples.apps.nowinandroid.core.domain.model.SaveableNewsResource
+import com.google.samples.apps.nowinandroid.core.model.data.previewNewsResources
import com.google.samples.apps.nowinandroid.core.ui.NewsFeedUiState
+import com.google.samples.apps.nowinandroid.core.ui.NewsFeedUiState.Loading
+import com.google.samples.apps.nowinandroid.core.ui.NewsFeedUiState.Success
import com.google.samples.apps.nowinandroid.core.ui.TrackScrollJank
import com.google.samples.apps.nowinandroid.core.ui.newsFeed
@@ -61,8 +74,39 @@ internal fun BookmarksRoute(
)
}
+/**
+ * Displays the user's bookmarked articles. Includes support for loading and empty states.
+ */
+@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+@Composable
+internal fun BookmarksScreen(
+ feedState: NewsFeedUiState,
+ removeFromBookmarks: (String) -> Unit,
+ modifier: Modifier = Modifier
+) {
+ when (feedState) {
+ Loading -> LoadingState(modifier)
+ is Success -> if (feedState.feed.isNotEmpty()) {
+ BookmarksGrid(feedState, removeFromBookmarks, modifier)
+ } else {
+ EmptyState(modifier)
+ }
+ }
+}
+
@Composable
-fun BookmarksScreen(
+private fun LoadingState(modifier: Modifier = Modifier) {
+ NiaLoadingWheel(
+ modifier = modifier
+ .fillMaxWidth()
+ .wrapContentSize()
+ .testTag("forYou:loading"),
+ contentDesc = stringResource(id = R.string.saved_loading),
+ )
+}
+
+@Composable
+private fun BookmarksGrid(
feedState: NewsFeedUiState,
removeFromBookmarks: (String) -> Unit,
modifier: Modifier = Modifier
@@ -79,41 +123,80 @@ fun BookmarksScreen(
.fillMaxSize()
.testTag("bookmarks:feed")
) {
+ newsFeed(
+ feedState = feedState,
+ onNewsResourcesCheckedChanged = { id, _ -> removeFromBookmarks(id) },
+ )
+ item(span = { GridItemSpan(maxLineSpan) }) {
+ Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.safeDrawing))
+ }
+ }
+}
- when (feedState) {
- is NewsFeedUiState.Loading -> {
- item(span = { GridItemSpan(maxLineSpan) }) {
- NiaLoadingWheel(
- modifier = Modifier
- .fillMaxWidth()
- .wrapContentSize()
- .testTag("forYou:loading"),
- contentDesc = stringResource(id = R.string.saved_loading),
- )
- }
- }
+@Composable
+private fun EmptyState(modifier: Modifier = Modifier) {
+ Column(
+ modifier = modifier
+ .padding(16.dp)
+ .fillMaxSize()
+ .testTag("bookmarks:empty"),
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Image(
+ modifier = Modifier.fillMaxWidth(),
+ painter = painterResource(id = R.drawable.img_empty_bookmarks),
+ contentDescription = null
+ )
+
+ Spacer(modifier = Modifier.height(16.dp))
+
+ Text(
+ text = stringResource(id = R.string.bookmarks_empty_error),
+ modifier = Modifier.fillMaxWidth(),
+ textAlign = TextAlign.Center,
+ style = MaterialTheme.typography.titleMedium,
+ fontWeight = FontWeight.Bold
+ )
- is NewsFeedUiState.Success -> {
- if (feedState.feed.isNotEmpty()) {
- newsFeed(
- feedState = feedState,
- onNewsResourcesCheckedChanged = { id, _ -> removeFromBookmarks(id) },
- )
- item(span = { GridItemSpan(maxLineSpan) }) {
- Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.safeDrawing))
- }
- } else item(span = { GridItemSpan(maxLineSpan) }) {
- Box(
- modifier = Modifier
- .fillMaxHeight()
- .wrapContentSize()
- .testTag("bookmarks:empty"),
- contentAlignment = Alignment.Center
- ) {
- Text(text = stringResource(id = R.string.bookmarks_empty))
- }
+ Spacer(modifier = Modifier.height(8.dp))
+
+ Text(
+ text = stringResource(id = R.string.bookmarks_empty_description),
+ modifier = Modifier.fillMaxWidth(),
+ textAlign = TextAlign.Center,
+ style = MaterialTheme.typography.bodyMedium
+ )
+ }
+}
+
+@Preview
+@Composable
+private fun LoadingStatePreview() {
+ NiaTheme {
+ LoadingState()
+ }
+}
+
+@Preview
+@Composable
+private fun BookmarksGridPreview() {
+ NiaTheme {
+ BookmarksGrid(
+ feedState = Success(
+ previewNewsResources.map {
+ SaveableNewsResource(it, false)
}
- }
- }
+ ),
+ removeFromBookmarks = {}
+ )
+ }
+}
+
+@Preview
+@Composable
+fun EmptyStatePreview() {
+ NiaTheme {
+ EmptyState()
}
}
diff --git a/feature/bookmarks/src/main/res/drawable/img_empty_bookmarks.xml b/feature/bookmarks/src/main/res/drawable/img_empty_bookmarks.xml
new file mode 100644
index 000000000..b9e2f2963
--- /dev/null
+++ b/feature/bookmarks/src/main/res/drawable/img_empty_bookmarks.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
diff --git a/feature/bookmarks/src/main/res/values/strings.xml b/feature/bookmarks/src/main/res/values/strings.xml
index 965b78333..61781ad6e 100644
--- a/feature/bookmarks/src/main/res/values/strings.xml
+++ b/feature/bookmarks/src/main/res/values/strings.xml
@@ -20,5 +20,6 @@
Saved
Search
Menu
- No bookmarks yet!
+ No saved updates
+ Updates you save will be stored here\nto read later
\ No newline at end of file