diff --git a/core-network/src/main/AndroidManifest.xml b/core-network/src/main/AndroidManifest.xml index dfab2494f..0d9ff8a0f 100644 --- a/core-network/src/main/AndroidManifest.xml +++ b/core-network/src/main/AndroidManifest.xml @@ -16,5 +16,6 @@ --> + \ No newline at end of file diff --git a/core-ui/src/main/java/com/google/samples/apps/nowinandroid/core/ui/NewsResourceCardList.kt b/core-ui/src/main/java/com/google/samples/apps/nowinandroid/core/ui/NewsResourceCardList.kt new file mode 100644 index 000000000..25a7bd2dc --- /dev/null +++ b/core-ui/src/main/java/com/google/samples/apps/nowinandroid/core/ui/NewsResourceCardList.kt @@ -0,0 +1,67 @@ +/* + * 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 android.content.Intent +import android.net.Uri +import androidx.compose.foundation.lazy.LazyListScope +import androidx.compose.foundation.lazy.items +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.core.content.ContextCompat +import com.google.samples.apps.nowinandroid.core.model.data.NewsResource + +/** + * Extension function for displaying a [List] of [NewsResourceCardExpanded] backed by a generic + * [List] [T]. + * + * [newsResourceMapper] maps type [T] to a [NewsResource] + * [isBookmarkedMapper] maps type [T] to whether the [NewsResource] is bookmarked + * [onToggleBookmark] defines the action invoked when a user wishes to bookmark an item + * [onItemClick] optional parameter for action to be performed when the card is clicked. The + * default action launches an intent matching the card. + */ +fun LazyListScope.newsResourceCardItems( + items: List, + newsResourceMapper: (item: T) -> NewsResource, + isBookmarkedMapper: (item: T) -> Boolean, + onToggleBookmark: (item: T) -> Unit, + onItemClick: ((item: T) -> Unit)? = null, + itemModifier: Modifier = Modifier, +) = items( + items = items, + key = { newsResourceMapper(it).id }, + itemContent = { item -> + val newsResource = newsResourceMapper(item) + val launchResourceIntent = + Intent(Intent.ACTION_VIEW, Uri.parse(newsResource.url)) + val context = LocalContext.current + + NewsResourceCardExpanded( + newsResource = newsResource, + isBookmarked = isBookmarkedMapper(item), + onToggleBookmark = { onToggleBookmark(item) }, + onClick = { + when (onItemClick) { + null -> ContextCompat.startActivity(context, launchResourceIntent, null) + else -> onItemClick(item) + } + }, + modifier = itemModifier + ) + }, +) diff --git a/feature-foryou/src/main/java/com/google/samples/apps/nowinandroid/feature/foryou/ForYouScreen.kt b/feature-foryou/src/main/java/com/google/samples/apps/nowinandroid/feature/foryou/ForYouScreen.kt index cc2650112..a7a3e2cc5 100644 --- a/feature-foryou/src/main/java/com/google/samples/apps/nowinandroid/feature/foryou/ForYouScreen.kt +++ b/feature-foryou/src/main/java/com/google/samples/apps/nowinandroid/feature/foryou/ForYouScreen.kt @@ -16,8 +16,6 @@ package com.google.samples.apps.nowinandroid.feature.foryou -import android.content.Intent -import android.net.Uri import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row @@ -38,7 +36,6 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.grid.GridCells.Fixed import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid import androidx.compose.foundation.lazy.grid.items -import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.CornerSize import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons @@ -55,7 +52,6 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -64,7 +60,6 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.max import androidx.compose.ui.unit.sp -import androidx.core.content.ContextCompat.startActivity import androidx.hilt.navigation.compose.hiltViewModel import coil.compose.AsyncImage import com.google.samples.apps.nowinandroid.core.model.data.Author @@ -75,10 +70,10 @@ import com.google.samples.apps.nowinandroid.core.model.data.NewsResourceType.Vid import com.google.samples.apps.nowinandroid.core.model.data.SaveableNewsResource import com.google.samples.apps.nowinandroid.core.model.data.Topic import com.google.samples.apps.nowinandroid.core.ui.LoadingWheel -import com.google.samples.apps.nowinandroid.core.ui.NewsResourceCardExpanded import com.google.samples.apps.nowinandroid.core.ui.component.NiaToggleButton import com.google.samples.apps.nowinandroid.core.ui.component.NiaTopAppBar import com.google.samples.apps.nowinandroid.core.ui.icon.NiaIcons +import com.google.samples.apps.nowinandroid.core.ui.newsResourceCardItems import com.google.samples.apps.nowinandroid.core.ui.theme.NiaTypography import kotlinx.datetime.Instant @@ -220,21 +215,18 @@ fun ForYouScreen( } } is ForYouFeedState.Success -> { - items(feedState.feed) { (newsResource: NewsResource, isBookmarked: Boolean) -> - val launchResourceIntent = - Intent(Intent.ACTION_VIEW, Uri.parse(newsResource.url)) - val context = LocalContext.current - - NewsResourceCardExpanded( - newsResource = newsResource, - isBookmarked = isBookmarked, - onClick = { startActivity(context, launchResourceIntent, null) }, - onToggleBookmark = { - onNewsResourcesCheckedChanged(newsResource.id, !isBookmarked) - }, - modifier = Modifier.padding(24.dp) - ) - } + newsResourceCardItems( + items = feedState.feed, + newsResourceMapper = SaveableNewsResource::newsResource, + isBookmarkedMapper = SaveableNewsResource::isSaved, + onToggleBookmark = { saveableNewsResource -> + onNewsResourcesCheckedChanged( + saveableNewsResource.newsResource.id, + !saveableNewsResource.isSaved + ) + }, + itemModifier = Modifier.padding(24.dp) + ) } } diff --git a/feature-topic/src/main/java/com/google/samples/apps/nowinandroid/feature/topic/TopicScreen.kt b/feature-topic/src/main/java/com/google/samples/apps/nowinandroid/feature/topic/TopicScreen.kt index bc940ba18..3f7316db1 100644 --- a/feature-topic/src/main/java/com/google/samples/apps/nowinandroid/feature/topic/TopicScreen.kt +++ b/feature-topic/src/main/java/com/google/samples/apps/nowinandroid/feature/topic/TopicScreen.kt @@ -17,6 +17,7 @@ package com.google.samples.apps.nowinandroid.feature.topic import androidx.annotation.VisibleForTesting +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -32,6 +33,7 @@ import androidx.compose.foundation.layout.safeDrawing import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.material.icons.Icons.Filled import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material3.Icon @@ -52,6 +54,7 @@ import androidx.hilt.navigation.compose.hiltViewModel import com.google.samples.apps.nowinandroid.core.model.data.FollowableTopic import com.google.samples.apps.nowinandroid.core.ui.LoadingWheel import com.google.samples.apps.nowinandroid.core.ui.component.NiaFilterChip +import com.google.samples.apps.nowinandroid.core.ui.newsResourceCardItems import com.google.samples.apps.nowinandroid.feature.topic.R.string import com.google.samples.apps.nowinandroid.feature.topic.TopicUiState.Loading @@ -72,6 +75,7 @@ fun TopicRoute( ) } +@OptIn(ExperimentalFoundationApi::class) @VisibleForTesting @Composable internal fun TopicScreen( @@ -81,31 +85,35 @@ internal fun TopicScreen( onFollowClick: (Boolean) -> Unit, modifier: Modifier = Modifier, ) { - Column( + LazyColumn( modifier = modifier, horizontalAlignment = Alignment.CenterHorizontally ) { - Spacer( - // TODO: Replace with windowInsetsTopHeight after - // https://issuetracker.google.com/issues/230383055 - Modifier.windowInsetsPadding( - WindowInsets.safeDrawing.only(WindowInsetsSides.Top) + item { + Spacer( + // TODO: Replace with windowInsetsTopHeight after + // https://issuetracker.google.com/issues/230383055 + Modifier.windowInsetsPadding( + WindowInsets.safeDrawing.only(WindowInsetsSides.Top) + ) ) - ) - + } when (topicState) { - Loading -> + Loading -> item { LoadingWheel( modifier = modifier, contentDesc = stringResource(id = string.topic_loading), ) + } TopicUiState.Error -> TODO() is TopicUiState.Success -> { - TopicToolbar( - onBackClick = onBackClick, - onFollowClick = onFollowClick, - uiState = topicState.followableTopic - ) + item { + TopicToolbar( + onBackClick = onBackClick, + onFollowClick = onFollowClick, + uiState = topicState.followableTopic, + ) + } TopicBody( name = topicState.followableTopic.topic.name, description = topicState.followableTopic.topic.longDescription, @@ -113,13 +121,36 @@ internal fun TopicScreen( ) } } + item { + Spacer( + // TODO: Replace with windowInsetsBottomHeight after + // https://issuetracker.google.com/issues/230383055 + Modifier.windowInsetsPadding( + WindowInsets.safeDrawing.only(WindowInsetsSides.Bottom) + ) + ) + } } } +private fun LazyListScope.TopicBody( + name: String, + description: String, + news: NewsUiState +) { + // TODO: Show icon if available + item { + TopicHeader(name, description) + } + + TopicCards(news) +} + @Composable -private fun TopicBody(name: String, description: String, news: NewsUiState) { - Column(modifier = Modifier.padding(horizontal = 24.dp)) { - // TODO: Show icon if available +private fun TopicHeader(name: String, description: String) { + Column( + modifier = Modifier.padding(horizontal = 24.dp) + ) { Box( modifier = Modifier .size(216.dp) @@ -139,32 +170,24 @@ private fun TopicBody(name: String, description: String, news: NewsUiState) { style = MaterialTheme.typography.bodyLarge ) } - TopicList(news, Modifier.padding(top = 24.dp)) - - Spacer( - // TODO: Replace with windowInsetsBottomHeight after - // https://issuetracker.google.com/issues/230383055 - Modifier.windowInsetsPadding( - WindowInsets.safeDrawing.only(WindowInsetsSides.Bottom) - ) - ) } } -@Composable -private fun TopicList(news: NewsUiState, modifier: Modifier = Modifier) { +private fun LazyListScope.TopicCards(news: NewsUiState) { when (news) { is NewsUiState.Success -> { - LazyColumn(modifier = modifier) { - items(news.news.size) { index -> - Text(news.news[index].title) - } - } + newsResourceCardItems( + items = news.news, + newsResourceMapper = { it }, + isBookmarkedMapper = { /* TODO */ false }, + onToggleBookmark = { /* TODO */ }, + itemModifier = Modifier.padding(24.dp) + ) } - is NewsUiState.Loading -> { + is NewsUiState.Loading -> item { LoadingWheel(contentDesc = "Loading news") // TODO } - else -> { + else -> item { Text("Error") // TODO } } @@ -174,7 +197,9 @@ private fun TopicList(news: NewsUiState, modifier: Modifier = Modifier) { @Composable private fun TopicBodyPreview() { MaterialTheme { - TopicBody("Jetpack Compose", "Lorem ipsum maximum", NewsUiState.Success(emptyList())) + LazyColumn { + TopicBody("Jetpack Compose", "Lorem ipsum maximum", NewsUiState.Success(emptyList())) + } } }