Add MainActivity view model

pull/1837/head
Don Turner 3 years ago
parent 4654e67c23
commit eb721f8295

@ -106,6 +106,7 @@ dependencies {
implementation(libs.androidx.core.ktx) implementation(libs.androidx.core.ktx)
implementation(libs.androidx.core.splashscreen) implementation(libs.androidx.core.splashscreen)
implementation(libs.androidx.compose.runtime) implementation(libs.androidx.compose.runtime)
implementation(libs.androidx.lifecycle.runtimeCompose)
implementation(libs.androidx.compose.runtime.tracing) implementation(libs.androidx.compose.runtime.tracing)
implementation(libs.androidx.compose.material3.windowSizeClass) implementation(libs.androidx.compose.material3.windowSizeClass)
implementation(libs.androidx.hilt.navigation.compose) implementation(libs.androidx.hilt.navigation.compose)

@ -19,6 +19,7 @@ package com.google.samples.apps.nowinandroid
import android.os.Bundle import android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi
import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass
@ -34,13 +35,11 @@ import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle import androidx.lifecycle.repeatOnLifecycle
import androidx.metrics.performance.JankStats import androidx.metrics.performance.JankStats
import com.google.accompanist.systemuicontroller.rememberSystemUiController import com.google.accompanist.systemuicontroller.rememberSystemUiController
import com.google.samples.apps.nowinandroid.core.data.repository.UserDataRepository import com.google.samples.apps.nowinandroid.MainActivityUiState.Loading
import com.google.samples.apps.nowinandroid.MainActivityUiState.Success
import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme
import com.google.samples.apps.nowinandroid.core.model.data.DarkThemeConfig import com.google.samples.apps.nowinandroid.core.model.data.DarkThemeConfig
import com.google.samples.apps.nowinandroid.core.model.data.ThemeBrand import com.google.samples.apps.nowinandroid.core.model.data.ThemeBrand
import com.google.samples.apps.nowinandroid.core.model.data.UserData
import com.google.samples.apps.nowinandroid.core.result.Result
import com.google.samples.apps.nowinandroid.core.result.asResult
import com.google.samples.apps.nowinandroid.ui.NiaApp import com.google.samples.apps.nowinandroid.ui.NiaApp
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject import javax.inject.Inject
@ -58,25 +57,20 @@ class MainActivity : ComponentActivity() {
@Inject @Inject
lateinit var lazyStats: dagger.Lazy<JankStats> lateinit var lazyStats: dagger.Lazy<JankStats>
@Inject val viewModel: MainActivityViewModel by viewModels()
lateinit var userDataRepository: UserDataRepository
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
val splashScreen = installSplashScreen() val splashScreen = installSplashScreen()
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
/** var uiState: MainActivityUiState by mutableStateOf(Loading)
* The current user data, updated here to drive the UI theme
*/
var userDataResult: Result<UserData> by mutableStateOf(Result.Loading)
// Update the user data // Update the uiState
lifecycleScope.launch { lifecycleScope.launch {
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
userDataRepository.userDataStream viewModel.uiState
.asResult()
.onEach { .onEach {
userDataResult = it uiState = it
} }
.collect() .collect()
} }
@ -84,9 +78,9 @@ class MainActivity : ComponentActivity() {
// Keep the splash screen on-screen until the user data is loaded // Keep the splash screen on-screen until the user data is loaded
splashScreen.setKeepOnScreenCondition { splashScreen.setKeepOnScreenCondition {
when (userDataResult) { when (uiState) {
Result.Loading -> true Loading -> true
is Result.Success, is Result.Error -> false is Success -> false
} }
} }
@ -96,7 +90,7 @@ class MainActivity : ComponentActivity() {
setContent { setContent {
val systemUiController = rememberSystemUiController() val systemUiController = rememberSystemUiController()
val darkTheme = shouldUseDarkTheme(userDataResult) val darkTheme = shouldUseDarkTheme(uiState)
// Update the dark content of the system bars to match the theme // Update the dark content of the system bars to match the theme
DisposableEffect(systemUiController, darkTheme) { DisposableEffect(systemUiController, darkTheme) {
@ -106,7 +100,7 @@ class MainActivity : ComponentActivity() {
NiaTheme( NiaTheme(
darkTheme = darkTheme, darkTheme = darkTheme,
androidTheme = shouldUseAndroidTheme(userDataResult) androidTheme = shouldUseAndroidTheme(uiState)
) { ) {
NiaApp( NiaApp(
windowSizeClass = calculateWindowSizeClass(this), windowSizeClass = calculateWindowSizeClass(this),
@ -127,29 +121,29 @@ class MainActivity : ComponentActivity() {
} }
/** /**
* Returns `true` if the Android theme should be used, as a function of the [userDataResult]. * Returns `true` if the Android theme should be used, as a function of the [uiState].
*/ */
@Composable @Composable
fun shouldUseAndroidTheme( fun shouldUseAndroidTheme(
userDataResult: Result<UserData>, uiState: MainActivityUiState,
): Boolean = when (userDataResult) { ): Boolean = when (uiState) {
Result.Loading, is Result.Error -> false Loading -> false
is Result.Success -> when (userDataResult.data.themeBrand) { is Success -> when (uiState.userData.themeBrand) {
ThemeBrand.DEFAULT -> false ThemeBrand.DEFAULT -> false
ThemeBrand.ANDROID -> true ThemeBrand.ANDROID -> true
} }
} }
/** /**
* Returns `true` if dark theme should be used, as a function of the [userDataResult] and the * Returns `true` if dark theme should be used, as a function of the [uiState] and the
* current system context. * current system context.
*/ */
@Composable @Composable
fun shouldUseDarkTheme( fun shouldUseDarkTheme(
userDataResult: Result<UserData>, uiState: MainActivityUiState,
): Boolean = when (userDataResult) { ): Boolean = when (uiState) {
Result.Loading, is Result.Error -> isSystemInDarkTheme() Loading -> isSystemInDarkTheme()
is Result.Success -> when (userDataResult.data.darkThemeConfig) { is Success -> when (uiState.userData.darkThemeConfig) {
DarkThemeConfig.FOLLOW_SYSTEM -> isSystemInDarkTheme() DarkThemeConfig.FOLLOW_SYSTEM -> isSystemInDarkTheme()
DarkThemeConfig.LIGHT -> false DarkThemeConfig.LIGHT -> false
DarkThemeConfig.DARK -> true DarkThemeConfig.DARK -> true

@ -0,0 +1,48 @@
/*
* 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
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.google.samples.apps.nowinandroid.MainActivityUiState.Loading
import com.google.samples.apps.nowinandroid.MainActivityUiState.Success
import com.google.samples.apps.nowinandroid.core.data.repository.UserDataRepository
import com.google.samples.apps.nowinandroid.core.model.data.UserData
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
@HiltViewModel
class MainActivityViewModel @Inject constructor(
userDataRepository: UserDataRepository
) : ViewModel() {
val uiState: StateFlow<MainActivityUiState> = userDataRepository.userDataStream.map {
Success(it)
}.stateIn(
scope = viewModelScope,
initialValue = Loading,
started = SharingStarted.WhileSubscribed(5_000)
)
}
sealed interface MainActivityUiState {
object Loading : MainActivityUiState
data class Success(val userData: UserData) : MainActivityUiState
}
Loading…
Cancel
Save