pull/205/head
commit
1cf4ff585d
@ -0,0 +1,203 @@
|
|||||||
|
/*
|
||||||
|
* 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.annotation.IntRange
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.wrapContentSize
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.LazyListScope
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
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.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
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.model.data.SaveableNewsResource
|
||||||
|
import com.google.samples.apps.nowinandroid.core.model.data.previewNewsResources
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An extension on [LazyListScope] defining a feed with news resources.
|
||||||
|
* Depending on the [feedState], this might emit no items.
|
||||||
|
*
|
||||||
|
* @param showLoadingUIIfLoading if true, show a visual indication of loading if the
|
||||||
|
* [feedState] is loading. This allows a caller to suppress a loading visual if one is already
|
||||||
|
* present in the UI elsewhere.
|
||||||
|
*/
|
||||||
|
fun LazyListScope.NewsFeed(
|
||||||
|
feedState: NewsFeedUiState,
|
||||||
|
showLoadingUIIfLoading: Boolean,
|
||||||
|
@StringRes loadingContentDescription: Int,
|
||||||
|
@IntRange(from = 1) numberOfColumns: Int,
|
||||||
|
onNewsResourcesCheckedChanged: (String, Boolean) -> Unit
|
||||||
|
) {
|
||||||
|
when (feedState) {
|
||||||
|
NewsFeedUiState.Loading -> {
|
||||||
|
if (showLoadingUIIfLoading) {
|
||||||
|
item {
|
||||||
|
NiaLoadingWheel(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.wrapContentSize(),
|
||||||
|
contentDesc = stringResource(loadingContentDescription),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is NewsFeedUiState.Success -> {
|
||||||
|
items(
|
||||||
|
feedState.feed.chunked(numberOfColumns)
|
||||||
|
) { saveableNewsResources ->
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.padding(
|
||||||
|
top = 32.dp,
|
||||||
|
start = 16.dp,
|
||||||
|
end = 16.dp
|
||||||
|
),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(32.dp)
|
||||||
|
) {
|
||||||
|
// The last row may not be complete, but for a consistent grid
|
||||||
|
// structure we still want an element taking up the empty space.
|
||||||
|
// Therefore, the last row may have empty boxes.
|
||||||
|
repeat(numberOfColumns) { index ->
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
) {
|
||||||
|
val saveableNewsResource =
|
||||||
|
saveableNewsResources.getOrNull(index)
|
||||||
|
|
||||||
|
if (saveableNewsResource != null) {
|
||||||
|
val launchResourceIntent =
|
||||||
|
Intent(
|
||||||
|
Intent.ACTION_VIEW,
|
||||||
|
Uri.parse(saveableNewsResource.newsResource.url)
|
||||||
|
)
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
NewsResourceCardExpanded(
|
||||||
|
newsResource = saveableNewsResource.newsResource,
|
||||||
|
isBookmarked = saveableNewsResource.isSaved,
|
||||||
|
onClick = {
|
||||||
|
ContextCompat.startActivity(
|
||||||
|
context,
|
||||||
|
launchResourceIntent,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onToggleBookmark = {
|
||||||
|
onNewsResourcesCheckedChanged(
|
||||||
|
saveableNewsResource.newsResource.id,
|
||||||
|
!saveableNewsResource.isSaved
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A sealed hierarchy describing the state of the feed of news resources.
|
||||||
|
*/
|
||||||
|
sealed interface NewsFeedUiState {
|
||||||
|
/**
|
||||||
|
* The feed is still loading.
|
||||||
|
*/
|
||||||
|
object Loading : NewsFeedUiState
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The feed is loaded with the given list of news resources.
|
||||||
|
*/
|
||||||
|
data class Success(
|
||||||
|
/**
|
||||||
|
* The list of news resources contained in this feed.
|
||||||
|
*/
|
||||||
|
val feed: List<SaveableNewsResource>
|
||||||
|
) : NewsFeedUiState
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun NewsFeedLoadingPreview() {
|
||||||
|
NiaTheme {
|
||||||
|
LazyColumn {
|
||||||
|
NewsFeed(
|
||||||
|
feedState = NewsFeedUiState.Loading,
|
||||||
|
showLoadingUIIfLoading = true,
|
||||||
|
loadingContentDescription = 0,
|
||||||
|
numberOfColumns = 1,
|
||||||
|
onNewsResourcesCheckedChanged = { _, _ -> }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun NewsFeedSingleColumnPreview() {
|
||||||
|
NiaTheme {
|
||||||
|
LazyColumn {
|
||||||
|
NewsFeed(
|
||||||
|
feedState = NewsFeedUiState.Success(
|
||||||
|
previewNewsResources.map {
|
||||||
|
SaveableNewsResource(it, false)
|
||||||
|
}
|
||||||
|
),
|
||||||
|
showLoadingUIIfLoading = true,
|
||||||
|
loadingContentDescription = 0,
|
||||||
|
numberOfColumns = 1,
|
||||||
|
onNewsResourcesCheckedChanged = { _, _ -> }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(device = Devices.TABLET)
|
||||||
|
@Composable
|
||||||
|
fun NewsFeedTwoColumnPreview() {
|
||||||
|
NiaTheme {
|
||||||
|
LazyColumn {
|
||||||
|
NewsFeed(
|
||||||
|
feedState = NewsFeedUiState.Success(
|
||||||
|
(previewNewsResources + previewNewsResources).map {
|
||||||
|
SaveableNewsResource(it, false)
|
||||||
|
}
|
||||||
|
),
|
||||||
|
showLoadingUIIfLoading = true,
|
||||||
|
loadingContentDescription = 0,
|
||||||
|
numberOfColumns = 2,
|
||||||
|
onNewsResourcesCheckedChanged = { _, _ -> }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,39 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.feature.foryou
|
|
||||||
|
|
||||||
import com.google.samples.apps.nowinandroid.core.model.data.SaveableNewsResource
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A sealed hierarchy describing the state of the feed on the for you screen.
|
|
||||||
*/
|
|
||||||
sealed interface ForYouFeedUiState {
|
|
||||||
/**
|
|
||||||
* The feed is still loading.
|
|
||||||
*/
|
|
||||||
object Loading : ForYouFeedUiState
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The feed is loaded with the given list of news resources.
|
|
||||||
*/
|
|
||||||
data class Success(
|
|
||||||
/**
|
|
||||||
* The list of news resources contained in this [PopulatedFeed].
|
|
||||||
*/
|
|
||||||
val feed: List<SaveableNewsResource>
|
|
||||||
) : ForYouFeedUiState
|
|
||||||
}
|
|
Loading…
Reference in new issue