New empty bookmarks handling (#443)

* Added the png file for error state

This should be removed later - couldn't convert the design to vector from svg - there is a problem with the `<mask` tag.

* Added two new strings based on the Figma file

* Redesign and reimplementation for the empty BookmarksScreen along with two clarifying comments.

* Removed the png file and replaced it with vector

* Code refactor after code reviewing

Made contentDescription null, removed extra spaces and renaming for the Composable function as suggested.

* Moved the modifier after Text

* Added a space after the curly bracket to pass the Spotless check

* Spotless apply

* Simplify logic in Bookmarks screen

* Change order of composables in BookmarksScreen and add previews

* Refactor after code review

Removed the space before `if`, and changed the padding from 5 to 8dp.

Co-authored-by: Jolanda Verhoef <JolandaVerhoef@users.noreply.github.com>
feature/add_wear_os_app
Mohsen Rzna 2 years ago committed by GitHub
parent aa4d930c20
commit c19b8b9319
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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 {

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

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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
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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="192dp"
android:height="192dp"
android:viewportWidth="64"
android:viewportHeight="64">
<path
android:fillColor="#8B418F"
android:pathData="M16,1C15.448,1 15,1.448 15,2C15,2.552 15.448,3 16,3V1ZM58,52C58,52.552 58.448,53 59,53C59.552,53 60,52.552 60,52H58ZM16,3H55V1H16V3ZM58,6V52H60V6H58ZM55,3C56.657,3 58,4.343 58,6H60C60,3.239 57.761,1 55,1V3Z" />
<path
android:fillColor="#00000000"
android:pathData="M45,10H9C6.791,10 5,11.791 5,14V55.854C5,59.177 8.817,61.051 11.446,59.019L24.554,48.89C25.995,47.777 28.005,47.777 29.446,48.89L42.554,59.019C45.183,61.051 49,59.177 49,55.854V14C49,11.791 47.209,10 45,10Z"
android:strokeColor="#8B418F"
android:strokeLineCap="round"
android:strokeWidth="2" />
</vector>

@ -20,5 +20,6 @@
<string name="top_app_bar_title">Saved</string>
<string name="top_app_bar_action_search">Search</string>
<string name="top_app_bar_action_menu">Menu</string>
<string name="bookmarks_empty">No bookmarks yet!</string>
<string name="bookmarks_empty_error">No saved updates</string>
<string name="bookmarks_empty_description">Updates you save will be stored here\nto read later</string>
</resources>
Loading…
Cancel
Save