Migrate for you to the multiplatform module

pull/2064/head
lihenggui 2 years ago
parent 84f9f19307
commit 471713e50a

@ -15,8 +15,9 @@
*/
plugins {
alias(libs.plugins.nowinandroid.android.feature)
alias(libs.plugins.nowinandroid.android.library.compose)
alias(libs.plugins.nowinandroid.cmp.feature)
alias(libs.plugins.jetbrains.compose)
alias(libs.plugins.compose)
alias(libs.plugins.nowinandroid.android.library.jacoco)
alias(libs.plugins.roborazzi)
}
@ -25,16 +26,32 @@ android {
namespace = "com.google.samples.apps.nowinandroid.feature.foryou"
}
dependencies {
implementation(libs.accompanist.permissions)
implementation(projects.core.data)
implementation(projects.core.domain)
testImplementation(libs.hilt.android.testing)
testImplementation(libs.robolectric)
testImplementation(projects.core.testing)
testImplementation(projects.core.screenshotTesting)
testDemoImplementation(libs.roborazzi)
androidTestImplementation(projects.core.testing)
kotlin {
sourceSets {
commonMain.dependencies {
implementation(projects.core.data)
implementation(projects.core.domain)
implementation(compose.material3)
implementation(compose.foundation)
implementation(compose.ui)
implementation(compose.components.resources)
implementation(compose.components.uiToolingPreview)
implementation(libs.coil)
implementation(libs.coil.compose)
}
androidMain.dependencies {
implementation(libs.accompanist.permissions)
}
commonMain.dependencies {
implementation(projects.core.testing)
}
androidUnitTest.dependencies {
implementation(libs.robolectric)
implementation(libs.roborazzi)
implementation(projects.core.screenshotTesting)
}
androidInstrumentedTest.dependencies {
implementation(projects.core.testing)
}
}
}

@ -16,10 +16,6 @@
package com.google.samples.apps.nowinandroid.feature.foryou
import android.net.Uri
import android.os.Build.VERSION
import android.os.Build.VERSION_CODES
import androidx.activity.compose.ReportDrawnWhen
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
@ -63,29 +59,22 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalContext
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.layout.layout
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalInspectionMode
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.max
import androidx.compose.ui.unit.sp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.tracing.trace
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.PermissionStatus.Denied
import com.google.accompanist.permissions.rememberPermissionState
import coil3.ImageLoader
import coil3.compose.LocalPlatformContext
import com.google.samples.apps.nowinandroid.core.designsystem.component.DynamicAsyncImage
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaButton
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaIconToggleButton
@ -100,16 +89,24 @@ import com.google.samples.apps.nowinandroid.core.model.data.UserNewsResource
import com.google.samples.apps.nowinandroid.core.ui.DevicePreviews
import com.google.samples.apps.nowinandroid.core.ui.NewsFeedUiState
import com.google.samples.apps.nowinandroid.core.ui.TrackScreenViewEvent
import com.google.samples.apps.nowinandroid.core.ui.TrackScrollJank
import com.google.samples.apps.nowinandroid.core.ui.UserNewsResourcePreviewParameterProvider
import com.google.samples.apps.nowinandroid.core.ui.collectAsStateWithLifecycle
import com.google.samples.apps.nowinandroid.core.ui.launchCustomChromeTab
import com.google.samples.apps.nowinandroid.core.ui.newsFeed
import nowinandroid.feature.foryou.generated.resources.Res
import nowinandroid.feature.foryou.generated.resources.feature_foryou_done
import nowinandroid.feature.foryou.generated.resources.feature_foryou_loading
import nowinandroid.feature.foryou.generated.resources.feature_foryou_onboarding_guidance_subtitle
import nowinandroid.feature.foryou.generated.resources.feature_foryou_onboarding_guidance_title
import org.jetbrains.compose.resources.painterResource
import org.jetbrains.compose.resources.stringResource
import org.jetbrains.compose.ui.tooling.preview.PreviewParameter
@Composable
internal fun ForYouRoute(
onTopicClick: (String) -> Unit,
modifier: Modifier = Modifier,
viewModel: ForYouViewModel = hiltViewModel(),
viewModel: ForYouViewModel,
) {
val onboardingUiState by viewModel.onboardingUiState.collectAsStateWithLifecycle()
val feedState by viewModel.feedState.collectAsStateWithLifecycle()
@ -148,8 +145,8 @@ internal fun ForYouScreen(
val isOnboardingLoading = onboardingUiState is OnboardingUiState.Loading
val isFeedLoading = feedState is NewsFeedUiState.Loading
// This code should be called when the UI is ready for use and relates to Time To Full Display.
ReportDrawnWhen { !isSyncing && !isOnboardingLoading && !isFeedLoading }
// // This code should be called when the UI is ready for use and relates to Time To Full Display.
// ReportDrawnWhen { !isSyncing && !isOnboardingLoading && !isFeedLoading }
val itemsAvailable = feedItemsSize(feedState, onboardingUiState)
@ -157,7 +154,7 @@ internal fun ForYouScreen(
val scrollbarState = state.scrollbarState(
itemsAvailable = itemsAvailable,
)
TrackScrollJank(scrollableState = state, stateName = "forYou:feed")
// TrackScrollJank(scrollableState = state, stateName = "forYou:feed")
Box(
modifier = modifier
@ -216,7 +213,7 @@ internal fun ForYouScreen(
targetOffsetY = { fullHeight -> -fullHeight },
) + fadeOut(),
) {
val loadingContentDescription = stringResource(id = R.string.feature_foryou_loading)
val loadingContentDescription = stringResource(Res.string.feature_foryou_loading)
Box(
modifier = Modifier
.fillMaxWidth()
@ -243,7 +240,7 @@ internal fun ForYouScreen(
)
}
TrackScreenViewEvent(screenName = "ForYou")
NotificationPermissionEffect()
// NotificationPermissionEffect()
DeepLinkEffect(
deepLinkedUserNewsResource,
onDeepLinkOpened,
@ -271,7 +268,7 @@ private fun LazyStaggeredGridScope.onboarding(
item(span = StaggeredGridItemSpan.FullLine, contentType = "onboarding") {
Column(modifier = interestsItemModifier) {
Text(
text = stringResource(R.string.feature_foryou_onboarding_guidance_title),
text = stringResource(Res.string.feature_foryou_onboarding_guidance_title),
textAlign = TextAlign.Center,
modifier = Modifier
.fillMaxWidth()
@ -279,7 +276,7 @@ private fun LazyStaggeredGridScope.onboarding(
style = MaterialTheme.typography.titleMedium,
)
Text(
text = stringResource(R.string.feature_foryou_onboarding_guidance_subtitle),
text = stringResource(Res.string.feature_foryou_onboarding_guidance_subtitle),
modifier = Modifier
.fillMaxWidth()
.padding(top = 8.dp, start = 24.dp, end = 24.dp),
@ -305,7 +302,7 @@ private fun LazyStaggeredGridScope.onboarding(
.fillMaxWidth(),
) {
Text(
text = stringResource(R.string.feature_foryou_done),
text = stringResource(Res.string.feature_foryou_done),
)
}
}
@ -320,11 +317,11 @@ private fun TopicSelection(
onboardingUiState: OnboardingUiState.Shown,
onTopicCheckedChanged: (String, Boolean) -> Unit,
modifier: Modifier = Modifier,
) = trace("TopicSelection") {
) {
val lazyGridState = rememberLazyGridState()
val topicSelectionTestTag = "forYou:topicSelection"
TrackScrollJank(scrollableState = lazyGridState, stateName = topicSelectionTestTag)
// TrackScrollJank(scrollableState = lazyGridState, stateName = topicSelectionTestTag)
Box(
modifier = modifier
@ -381,7 +378,7 @@ private fun SingleTopicButton(
imageUrl: String,
isSelected: Boolean,
onClick: (String, Boolean) -> Unit,
) = trace("SingleTopicButton") {
) {
Surface(
modifier = Modifier
.width(312.dp)
@ -434,52 +431,53 @@ fun TopicIcon(
modifier: Modifier = Modifier,
) {
DynamicAsyncImage(
placeholder = painterResource(R.drawable.feature_foryou_ic_icon_placeholder),
// placeholder = painterResource(R.drawable.feature_foryou_ic_icon_placeholder),
imageUrl = imageUrl,
// decorative
contentDescription = null,
modifier = modifier
.padding(10.dp)
.size(32.dp),
imageLoader = ImageLoader(LocalPlatformContext.current),
)
}
@Composable
@OptIn(ExperimentalPermissionsApi::class)
private fun NotificationPermissionEffect() {
// Permission requests should only be made from an Activity Context, which is not present
// in previews
if (LocalInspectionMode.current) return
if (VERSION.SDK_INT < VERSION_CODES.TIRAMISU) return
val notificationsPermissionState = rememberPermissionState(
android.Manifest.permission.POST_NOTIFICATIONS,
)
LaunchedEffect(notificationsPermissionState) {
val status = notificationsPermissionState.status
if (status is Denied && !status.shouldShowRationale) {
notificationsPermissionState.launchPermissionRequest()
}
}
}
//@Composable
//@OptIn(ExperimentalPermissionsApi::class)
//private fun NotificationPermissionEffect() {
// // Permission requests should only be made from an Activity Context, which is not present
// // in previews
// if (LocalInspectionMode.current) return
// if (VERSION.SDK_INT < VERSION_CODES.TIRAMISU) return
// val notificationsPermissionState = rememberPermissionState(
// android.Manifest.permission.POST_NOTIFICATIONS,
// )
// LaunchedEffect(notificationsPermissionState) {
// val status = notificationsPermissionState.status
// if (status is Denied && !status.shouldShowRationale) {
// notificationsPermissionState.launchPermissionRequest()
// }
// }
//}
@Composable
private fun DeepLinkEffect(
userNewsResource: UserNewsResource?,
onDeepLinkOpened: (String) -> Unit,
) {
val context = LocalContext.current
val backgroundColor = MaterialTheme.colorScheme.background.toArgb()
LaunchedEffect(userNewsResource) {
if (userNewsResource == null) return@LaunchedEffect
if (!userNewsResource.hasBeenViewed) onDeepLinkOpened(userNewsResource.id)
launchCustomChromeTab(
context = context,
uri = Uri.parse(userNewsResource.url),
toolbarColor = backgroundColor,
)
}
// val context = LocalContext.current
// val backgroundColor = MaterialTheme.colorScheme.background.toArgb()
//
// LaunchedEffect(userNewsResource) {
// if (userNewsResource == null) return@LaunchedEffect
// if (!userNewsResource.hasBeenViewed) onDeepLinkOpened(userNewsResource.id)
//
// launchCustomChromeTab(
// context = context,
// uri = Uri.parse(userNewsResource.url),
// toolbarColor = backgroundColor,
// )
// }
}
private fun feedItemsSize(

@ -29,7 +29,6 @@ import com.google.samples.apps.nowinandroid.core.data.util.SyncManager
import com.google.samples.apps.nowinandroid.core.domain.GetFollowableTopicsUseCase
import com.google.samples.apps.nowinandroid.core.ui.NewsFeedUiState
import com.google.samples.apps.nowinandroid.feature.foryou.navigation.LINKED_NEWS_RESOURCE_ID
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
@ -39,9 +38,8 @@ import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import javax.inject.Inject
import me.tatarka.inject.annotations.Inject
@HiltViewModel
class ForYouViewModel @Inject constructor(
private val savedStateHandle: SavedStateHandle,
syncManager: SyncManager,

@ -22,7 +22,6 @@ import androidx.navigation.NavOptions
import androidx.navigation.NavType
import androidx.navigation.compose.composable
import androidx.navigation.navArgument
import androidx.navigation.navDeepLink
import com.google.samples.apps.nowinandroid.feature.foryou.ForYouRoute
const val LINKED_NEWS_RESOURCE_ID = "linkedNewsResourceId"
@ -36,12 +35,12 @@ fun NavGraphBuilder.forYouScreen(onTopicClick: (String) -> Unit) {
composable(
route = FOR_YOU_ROUTE,
deepLinks = listOf(
navDeepLink { uriPattern = DEEP_LINK_URI_PATTERN },
// navDeepLink { uriPattern = DEEP_LINK_URI_PATTERN },
),
arguments = listOf(
navArgument(LINKED_NEWS_RESOURCE_ID) { type = NavType.StringType },
),
) {
ForYouRoute(onTopicClick)
// ForYouRoute(onTopicClick)
}
}

@ -1,17 +0,0 @@
<?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.
-->
<manifest />
Loading…
Cancel
Save