From 9957193bd9d82e4e506bc170cd83dc5e379175ec Mon Sep 17 00:00:00 2001 From: Rikin Marfatia Date: Tue, 23 Jul 2024 13:06:46 -0700 Subject: [PATCH 1/2] Login Cleanup This change request adds a bunch of cleanup after the initial infrastructure was in. Including: - Logout - A better looking login card in settings - Removing default styling on text fields - Better IME selection for text entry --- .../com/emergetools/hackernews/AppActivity.kt | 105 ++++---- .../hackernews/data/UserStorage.kt | 6 + .../hackernews/features/login/LoginRouting.kt | 10 +- .../hackernews/features/login/LoginScreen.kt | 55 +++- .../features/settings/SettingsDomain.kt | 17 +- .../features/settings/SettingsScreen.kt | 252 ++++++++++++++---- android/gradle/libs.versions.toml | 5 +- 7 files changed, 330 insertions(+), 120 deletions(-) diff --git a/android/app/src/main/java/com/emergetools/hackernews/AppActivity.kt b/android/app/src/main/java/com/emergetools/hackernews/AppActivity.kt index d713ecca..40ef4ca4 100644 --- a/android/app/src/main/java/com/emergetools/hackernews/AppActivity.kt +++ b/android/app/src/main/java/com/emergetools/hackernews/AppActivity.kt @@ -10,6 +10,9 @@ import androidx.compose.animation.slideIn import androidx.compose.animation.slideOut import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.navigation.ModalBottomSheetLayout +import androidx.compose.material.navigation.rememberBottomSheetNavigator import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.NavigationBar @@ -24,9 +27,11 @@ import androidx.compose.runtime.staticCompositionLocalOf import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavDestination import androidx.navigation.NavHostController +import androidx.navigation.Navigator import androidx.navigation.compose.NavHost import androidx.navigation.compose.rememberNavController import com.emergetools.hackernews.data.ChromeTabsProvider @@ -60,9 +65,10 @@ class MainActivity : ComponentActivity() { @Composable fun rememberNavController( + vararg navigators: Navigator, onDestinationChanged: (NavDestination) -> Unit ): NavHostController { - return rememberNavController().apply { + return rememberNavController(*navigators).apply { addOnDestinationChangedListener { _, destination, _ -> onDestinationChanged(destination) } @@ -73,57 +79,64 @@ fun rememberNavController( fun App() { val model = viewModel() val state by model.state.collectAsState() - val navController = rememberNavController() { destination -> + val bottomSheetNavigator = rememberBottomSheetNavigator() + val navController = rememberNavController(bottomSheetNavigator) { destination -> model.actions(AppAction.DestinationChanged(destination)) } - Scaffold( - bottomBar = { - NavigationBar { - state.navItems.forEach { navItem -> - NavigationBarItem( - selected = navItem.selected, - colors = NavigationBarItemDefaults.colors( - selectedIconColor = MaterialTheme.colorScheme.primary, - indicatorColor = MaterialTheme.colorScheme.primaryContainer - ), - onClick = { - model.actions(AppAction.NavItemSelected(navItem)) - navController.navigate(navItem.route) { - popUpTo { - saveState = true + ModalBottomSheetLayout( + bottomSheetNavigator = bottomSheetNavigator, + sheetShape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp) + ) { + Scaffold( + bottomBar = { + NavigationBar { + state.navItems.forEach { navItem -> + NavigationBarItem( + selected = navItem.selected, + colors = NavigationBarItemDefaults.colors( + selectedIconColor = MaterialTheme.colorScheme.primary, + indicatorColor = MaterialTheme.colorScheme.primaryContainer + ), + onClick = { + model.actions(AppAction.NavItemSelected(navItem)) + + navController.navigate(navItem.route) { + popUpTo { + saveState = true + } + launchSingleTop = true + restoreState = true } - launchSingleTop = true - restoreState = true - } - }, - icon = { - Icon( - painter = painterResource(navItem.icon), - contentDescription = navItem.label - ) - }, - ) + }, + icon = { + Icon( + painter = painterResource(navItem.icon), + contentDescription = navItem.label + ) + }, + ) + } } } - } - ) { innerPadding -> - NavHost( - modifier = Modifier - .fillMaxSize() - .padding(innerPadding), - navController = navController, - enterTransition = { slideIn { IntOffset(x = it.width, y = 0) } }, - exitTransition = { slideOut { IntOffset(x = -it.width / 3, y = 0) } + fadeOut() }, - popEnterTransition = { slideIn { IntOffset(x = -it.width, y = 0) } }, - popExitTransition = { slideOut { IntOffset(x = it.width, y = 0) } }, - startDestination = Stories - ) { - storiesGraph(navController) - commentsRoutes() - bookmarksRoutes(navController) - settingsRoutes(navController) - loginRoutes(navController) + ) { innerPadding -> + NavHost( + modifier = Modifier + .fillMaxSize() + .padding(innerPadding), + navController = navController, + enterTransition = { slideIn { IntOffset(x = it.width, y = 0) } }, + exitTransition = { slideOut { IntOffset(x = -it.width / 3, y = 0) } + fadeOut() }, + popEnterTransition = { slideIn { IntOffset(x = -it.width, y = 0) } }, + popExitTransition = { slideOut { IntOffset(x = it.width, y = 0) } }, + startDestination = Stories + ) { + storiesGraph(navController) + commentsRoutes() + bookmarksRoutes(navController) + settingsRoutes(navController) + loginRoutes(navController) + } } } } diff --git a/android/app/src/main/java/com/emergetools/hackernews/data/UserStorage.kt b/android/app/src/main/java/com/emergetools/hackernews/data/UserStorage.kt index 0d2e03b2..6bfd87dc 100644 --- a/android/app/src/main/java/com/emergetools/hackernews/data/UserStorage.kt +++ b/android/app/src/main/java/com/emergetools/hackernews/data/UserStorage.kt @@ -16,6 +16,12 @@ class UserStorage(private val appContext: Context) { } } + suspend fun clearCookie() { + appContext.dataStore.edit { store -> + store.remove(cookieKey) + } + } + fun getCookie(): Flow { return appContext.dataStore.data.map { it[cookieKey] } } diff --git a/android/app/src/main/java/com/emergetools/hackernews/features/login/LoginRouting.kt b/android/app/src/main/java/com/emergetools/hackernews/features/login/LoginRouting.kt index 822246d2..4e0ba328 100644 --- a/android/app/src/main/java/com/emergetools/hackernews/features/login/LoginRouting.kt +++ b/android/app/src/main/java/com/emergetools/hackernews/features/login/LoginRouting.kt @@ -3,10 +3,11 @@ package com.emergetools.hackernews.features.login import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.window.DialogProperties import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder -import androidx.navigation.compose.composable +import androidx.navigation.compose.dialog import com.emergetools.hackernews.webClient import kotlinx.serialization.Serializable @@ -16,7 +17,12 @@ sealed interface LoginDestinations { } fun NavGraphBuilder.loginRoutes(navController: NavController) { - composable { + dialog( + dialogProperties = DialogProperties( + usePlatformDefaultWidth = false, + decorFitsSystemWindows = false + ) + ) { val context = LocalContext.current val model = viewModel( factory = LoginViewModel.Factory( diff --git a/android/app/src/main/java/com/emergetools/hackernews/features/login/LoginScreen.kt b/android/app/src/main/java/com/emergetools/hackernews/features/login/LoginScreen.kt index d0e3bccd..01734de5 100644 --- a/android/app/src/main/java/com/emergetools/hackernews/features/login/LoginScreen.kt +++ b/android/app/src/main/java/com/emergetools/hackernews/features/login/LoginScreen.kt @@ -4,21 +4,26 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Warning import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.TextField +import androidx.compose.material3.TextFieldDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.input.OffsetMapping -import androidx.compose.ui.text.input.TransformedText +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.unit.dp import com.emergetools.hackernews.ui.theme.HackerNewsTheme @@ -30,7 +35,6 @@ fun LoginScreen( actions: (LoginAction) -> Unit, navigation: (LoginNavigation) -> Unit ) { - LaunchedEffect(state.status) { if (state.status == LoginStatus.Success) { navigation(LoginNavigation.Dismiss) @@ -41,7 +45,10 @@ fun LoginScreen( modifier = Modifier .fillMaxSize() .background(color = MaterialTheme.colorScheme.background), - verticalArrangement = Arrangement.spacedBy(16.dp, alignment = Alignment.CenterVertically), + verticalArrangement = Arrangement.spacedBy( + 16.dp, + alignment = Alignment.CenterVertically + ), horizontalAlignment = Alignment.CenterHorizontally ) { Text( @@ -51,6 +58,15 @@ fun LoginScreen( ) TextField( value = state.username, + shape = RoundedCornerShape(8.dp), + colors = TextFieldDefaults.colors( + focusedIndicatorColor = Color.Transparent, + unfocusedIndicatorColor = Color.Transparent, + ), + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Text, + imeAction = ImeAction.Next + ), placeholder = { Text("Username") }, trailingIcon = { if (state.status == LoginStatus.Failed) { @@ -65,13 +81,17 @@ fun LoginScreen( ) TextField( value = state.password, + shape = RoundedCornerShape(8.dp), + colors = TextFieldDefaults.colors( + focusedIndicatorColor = Color.Transparent, + unfocusedIndicatorColor = Color.Transparent, + ), placeholder = { Text("Password") }, - visualTransformation = { text -> - TransformedText( - text = AnnotatedString("*".repeat(text.text.length)), - offsetMapping = OffsetMapping.Identity - ) - }, + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Password, + imeAction = ImeAction.Done + ), + visualTransformation = PasswordVisualTransformation(), trailingIcon = { if (state.status == LoginStatus.Failed) { Icon( @@ -83,8 +103,17 @@ fun LoginScreen( }, onValueChange = { actions(LoginAction.PasswordUpdated(it)) } ) - Button(onClick = { actions(LoginAction.LoginSubmit) }) { - Text(text = "Submit", style = MaterialTheme.typography.labelMedium, fontWeight = FontWeight.Bold) + Button( + colors = ButtonDefaults.buttonColors( + contentColor = MaterialTheme.colorScheme.onBackground + ), + onClick = { actions(LoginAction.LoginSubmit) } + ) { + Text( + text = "Submit", + style = MaterialTheme.typography.labelMedium, + fontWeight = FontWeight.Bold + ) } } } diff --git a/android/app/src/main/java/com/emergetools/hackernews/features/settings/SettingsDomain.kt b/android/app/src/main/java/com/emergetools/hackernews/features/settings/SettingsDomain.kt index 592eb440..5e121b11 100644 --- a/android/app/src/main/java/com/emergetools/hackernews/features/settings/SettingsDomain.kt +++ b/android/app/src/main/java/com/emergetools/hackernews/features/settings/SettingsDomain.kt @@ -11,6 +11,7 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch data class SettingsState( val loggedIn: Boolean @@ -18,6 +19,7 @@ data class SettingsState( sealed interface SettingsAction { data object LoginPressed : SettingsAction + data object LogoutPressed: SettingsAction } sealed interface SettingsNavigation { @@ -26,18 +28,14 @@ sealed interface SettingsNavigation { } } -class SettingsViewModel(userStorage: UserStorage) : ViewModel() { +class SettingsViewModel(private val userStorage: UserStorage) : ViewModel() { private val internalState = MutableStateFlow(SettingsState(false)) val state = combine( userStorage.getCookie(), internalState.asStateFlow() ) { cookie, state -> - if (!cookie.isNullOrEmpty()) { - state.copy(loggedIn = true) - } else { - state - } + state.copy(loggedIn = !cookie.isNullOrEmpty()) }.stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(), @@ -47,7 +45,12 @@ class SettingsViewModel(userStorage: UserStorage) : ViewModel() { fun actions(action: SettingsAction) { when (action) { SettingsAction.LoginPressed -> { - // TODO + } + + SettingsAction.LogoutPressed -> { + viewModelScope.launch { + userStorage.clearCookie() + } } } } diff --git a/android/app/src/main/java/com/emergetools/hackernews/features/settings/SettingsScreen.kt b/android/app/src/main/java/com/emergetools/hackernews/features/settings/SettingsScreen.kt index 46c4733c..f491c612 100644 --- a/android/app/src/main/java/com/emergetools/hackernews/features/settings/SettingsScreen.kt +++ b/android/app/src/main/java/com/emergetools/hackernews/features/settings/SettingsScreen.kt @@ -1,28 +1,54 @@ package com.emergetools.hackernews.features.settings +import androidx.compose.animation.animateColorAsState +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.spring +import androidx.compose.animation.core.tween import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Button +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.Person +import androidx.compose.material.icons.rounded.ThumbUp +import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.RadialGradient +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import com.emergetools.hackernews.R +import com.emergetools.hackernews.ui.theme.HackerBlue import com.emergetools.hackernews.ui.theme.HackerGreen import com.emergetools.hackernews.ui.theme.HackerNewsTheme +import com.emergetools.hackernews.ui.theme.HackerRed @Composable fun SettingsScreen( @@ -40,63 +66,39 @@ fun SettingsScreen( Text( text = "Settings", modifier = Modifier.fillMaxWidth(), - style = MaterialTheme.typography.titleMedium + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.onBackground ) - Column( - modifier = Modifier - .fillMaxWidth() - .clip(RoundedCornerShape(8.dp)) - .background( - color = MaterialTheme.colorScheme.surfaceContainer - ) - .padding(16.dp), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(16.dp) + verticalArrangement = Arrangement.spacedBy(4.dp) ) { - Box( - modifier = Modifier - .size(80.dp) - .clip(CircleShape) - .background( - color = if (state.loggedIn) { - HackerGreen - } else { - MaterialTheme.colorScheme.surfaceDim - } - ), - contentAlignment = Alignment.Center - ) { - Text( - text = if (!state.loggedIn) { - "🤔" - } else { - "😎" - }, - fontSize = 24.sp - ) - } - - Button( - onClick = { - navigation(SettingsNavigation.GoToLogin) - } + Row( + horizontalArrangement = Arrangement.spacedBy(4.dp), + verticalAlignment = Alignment.CenterVertically ) { Text( - text = if (!state.loggedIn) { - "Login" - } else { - "Logout" - }, + text = "Profile", style = MaterialTheme.typography.labelSmall, - fontWeight = FontWeight.Medium + fontWeight = FontWeight.Medium, + color = MaterialTheme.colorScheme.onBackground, + fontSize = 12.sp ) } + LoginCard( + state = state, + onClicked = { + if (state.loggedIn) { + actions(SettingsAction.LogoutPressed) + } else { + navigation(SettingsNavigation.GoToLogin) + } + } + ) } } } -@Preview +@PreviewLightDark @Composable private fun SettingsScreenPreview() { HackerNewsTheme { @@ -106,4 +108,156 @@ private fun SettingsScreenPreview() { navigation = {} ) } -} \ No newline at end of file +} + +@Composable +fun LoginCard( + state: SettingsState, + onClicked: () -> Unit +) { + val iconScale by animateFloatAsState( + targetValue = if (state.loggedIn) 1.5f else 1f, + label = "Icon Scale" + ) + val personColor by animateColorAsState( + targetValue = if (state.loggedIn) HackerRed else MaterialTheme.colorScheme.onSurface.copy(alpha = 0.2f), + label = "Person Icon Color" + ) + val likeColor by animateColorAsState( + targetValue = if (state.loggedIn) HackerGreen else MaterialTheme.colorScheme.onSurface.copy( + alpha = 0.2f + ), + label = "Like Icon Color" + ) + val likeRotation by animateFloatAsState( + targetValue = if (state.loggedIn) 5f else 0f, + animationSpec = spring( + stiffness = Spring.StiffnessVeryLow, + dampingRatio = Spring.DampingRatioHighBouncy + ), + label = "Like Icon Rotation" + ) + val commentColor by animateColorAsState( + targetValue = if (state.loggedIn) HackerBlue else MaterialTheme.colorScheme.onSurface.copy(alpha = 0.2f), + label = "Comment Icon Color" + ) + val commentRotation by animateFloatAsState( + targetValue = if (state.loggedIn) -5f else 0f, + animationSpec = spring( + stiffness = Spring.StiffnessVeryLow, + dampingRatio = Spring.DampingRatioHighBouncy + ), + label = "Comment Icon Rotation" + ) + val glowColor by animateColorAsState( + targetValue = if (state.loggedIn) HackerGreen else Color.Transparent, + animationSpec = tween(durationMillis = 600, easing = LinearEasing), + label = "Glow Color" + ) + val blinkColor by animateColorAsState( + targetValue = if (state.loggedIn) { + HackerGreen + } else { + MaterialTheme.colorScheme.onSurface.copy(alpha = 0.2f) + }, + label = "Glow Color" + ) + Column( + modifier = Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(8.dp)) + .background(color = MaterialTheme.colorScheme.surface) + .padding(16.dp) + .clickable { onClicked() } + ) { + Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(4.dp) + ) { + Box( + modifier = Modifier + .size(16.dp) + .drawBehind { + val blinkRadius = size.width * 0.15f + val glowRadius = size.width + drawCircle( + radius = glowRadius, + brush = Brush.radialGradient( + 0.0f to glowColor, + 1.0f to Color.Transparent + ) + ) + drawCircle( + radius = blinkRadius, + color = blinkColor + ) + }, + ) + Text( + text = if (state.loggedIn) "Logout" else "Login", + style = MaterialTheme.typography.labelMedium, + color = MaterialTheme.colorScheme.onSurface, + fontSize = 16.sp, + fontWeight = FontWeight.Bold + ) + } + Spacer(modifier = Modifier.weight(1f)) + Row( + horizontalArrangement = Arrangement.spacedBy( + space = 8.dp, + alignment = Alignment.CenterHorizontally + ), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + modifier = Modifier + .graphicsLayer { + rotationZ = likeRotation + scaleX = iconScale + scaleY = iconScale + } + .size(12.dp), + imageVector = Icons.Rounded.ThumbUp, + tint = likeColor, + contentDescription = "Likes" + ) + Icon( + modifier = Modifier + .graphicsLayer { + rotationZ = commentRotation + scaleX = iconScale + scaleY = iconScale + } + .size(12.dp), + painter = painterResource(R.drawable.ic_chat), + tint = commentColor, + contentDescription = "Comments" + ) + } + } + } +} + +@PreviewLightDark +@Composable +private fun LoginCardPreview() { + var loggedIn by remember { mutableStateOf(false) } + HackerNewsTheme { + LoginCard( + state = SettingsState(loggedIn = loggedIn), + onClicked = { loggedIn = !loggedIn} + ) + } +} + +@PreviewLightDark +@Composable +private fun LoginCardLoggedInPreview() { + HackerNewsTheme { + LoginCard( + state = SettingsState(loggedIn = true), + onClicked = {} + ) + } +} diff --git a/android/gradle/libs.versions.toml b/android/gradle/libs.versions.toml index c5feafa2..75d0eac1 100644 --- a/android/gradle/libs.versions.toml +++ b/android/gradle/libs.versions.toml @@ -12,13 +12,12 @@ retrofit = "2.11.0" okhttp = "4.12.0" kotlinx-serialization-json = "1.6.3" viewmodel = "2.8.2" -navigation = "2.8.0-beta04" -accompanist = "0.34.0" +navigation = "2.8.0-beta06" browser = "1.5.0" emergePlugin = "3.1.1" emergeSnapshots = "1.1.2" composeCompilerExtension = "1.5.3" -material3 = "1.3.0-beta04" +material3 = "1.3.0-beta05" datastore = "1.1.1" room = "2.6.1" jsoup = "1.17.2" From 82c7a9c86a7c3df4ec0da5415f24152f401b23bb Mon Sep 17 00:00:00 2001 From: Rikin Marfatia Date: Wed, 24 Jul 2024 14:56:57 -0700 Subject: [PATCH 2/2] Fix Build --- .../com/emergetools/hackernews/AppActivity.kt | 108 ++++++++---------- 1 file changed, 45 insertions(+), 63 deletions(-) diff --git a/android/app/src/main/java/com/emergetools/hackernews/AppActivity.kt b/android/app/src/main/java/com/emergetools/hackernews/AppActivity.kt index 40ef4ca4..298abb98 100644 --- a/android/app/src/main/java/com/emergetools/hackernews/AppActivity.kt +++ b/android/app/src/main/java/com/emergetools/hackernews/AppActivity.kt @@ -4,15 +4,11 @@ import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge -import androidx.browser.customtabs.CustomTabsIntent import androidx.compose.animation.fadeOut import androidx.compose.animation.slideIn import androidx.compose.animation.slideOut import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.navigation.ModalBottomSheetLayout -import androidx.compose.material.navigation.rememberBottomSheetNavigator import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.NavigationBar @@ -20,14 +16,11 @@ import androidx.compose.material3.NavigationBarItem import androidx.compose.material3.NavigationBarItemDefaults import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue -import androidx.compose.runtime.staticCompositionLocalOf import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.IntOffset -import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavDestination import androidx.navigation.NavHostController @@ -35,8 +28,6 @@ import androidx.navigation.Navigator import androidx.navigation.compose.NavHost import androidx.navigation.compose.rememberNavController import com.emergetools.hackernews.data.ChromeTabsProvider -import com.emergetools.hackernews.data.LocalCustomTabsIntent -import com.emergetools.hackernews.features.bookmarks.BookmarksNavigation import com.emergetools.hackernews.features.bookmarks.bookmarksRoutes import com.emergetools.hackernews.features.comments.commentsRoutes import com.emergetools.hackernews.features.login.loginRoutes @@ -45,9 +36,6 @@ import com.emergetools.hackernews.features.stories.Stories import com.emergetools.hackernews.features.stories.StoriesDestinations.Feed import com.emergetools.hackernews.features.stories.storiesGraph import com.emergetools.hackernews.ui.theme.HackerNewsTheme -import com.emergetools.hackernews.ui.theme.HackerOrangeLight -import com.emergetools.hackernews.ui.theme.HackerRed -import com.emergetools.hackernews.ui.theme.HackerRedLight class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { @@ -79,64 +67,58 @@ fun rememberNavController( fun App() { val model = viewModel() val state by model.state.collectAsState() - val bottomSheetNavigator = rememberBottomSheetNavigator() - val navController = rememberNavController(bottomSheetNavigator) { destination -> + val navController = rememberNavController { destination -> model.actions(AppAction.DestinationChanged(destination)) } - ModalBottomSheetLayout( - bottomSheetNavigator = bottomSheetNavigator, - sheetShape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp) - ) { - Scaffold( - bottomBar = { - NavigationBar { - state.navItems.forEach { navItem -> - NavigationBarItem( - selected = navItem.selected, - colors = NavigationBarItemDefaults.colors( - selectedIconColor = MaterialTheme.colorScheme.primary, - indicatorColor = MaterialTheme.colorScheme.primaryContainer - ), - onClick = { - model.actions(AppAction.NavItemSelected(navItem)) + Scaffold( + bottomBar = { + NavigationBar { + state.navItems.forEach { navItem -> + NavigationBarItem( + selected = navItem.selected, + colors = NavigationBarItemDefaults.colors( + selectedIconColor = MaterialTheme.colorScheme.primary, + indicatorColor = MaterialTheme.colorScheme.primaryContainer + ), + onClick = { + model.actions(AppAction.NavItemSelected(navItem)) - navController.navigate(navItem.route) { - popUpTo { - saveState = true - } - launchSingleTop = true - restoreState = true + navController.navigate(navItem.route) { + popUpTo { + saveState = true } - }, - icon = { - Icon( - painter = painterResource(navItem.icon), - contentDescription = navItem.label - ) - }, - ) - } + launchSingleTop = true + restoreState = true + } + }, + icon = { + Icon( + painter = painterResource(navItem.icon), + contentDescription = navItem.label + ) + }, + ) } } - ) { innerPadding -> - NavHost( - modifier = Modifier - .fillMaxSize() - .padding(innerPadding), - navController = navController, - enterTransition = { slideIn { IntOffset(x = it.width, y = 0) } }, - exitTransition = { slideOut { IntOffset(x = -it.width / 3, y = 0) } + fadeOut() }, - popEnterTransition = { slideIn { IntOffset(x = -it.width, y = 0) } }, - popExitTransition = { slideOut { IntOffset(x = it.width, y = 0) } }, - startDestination = Stories - ) { - storiesGraph(navController) - commentsRoutes() - bookmarksRoutes(navController) - settingsRoutes(navController) - loginRoutes(navController) - } + } + ) { innerPadding -> + NavHost( + modifier = Modifier + .fillMaxSize() + .padding(innerPadding), + navController = navController, + enterTransition = { slideIn { IntOffset(x = it.width, y = 0) } }, + exitTransition = { slideOut { IntOffset(x = -it.width / 3, y = 0) } + fadeOut() }, + popEnterTransition = { slideIn { IntOffset(x = -it.width, y = 0) } }, + popExitTransition = { slideOut { IntOffset(x = it.width, y = 0) } }, + startDestination = Stories + ) { + storiesGraph(navController) + commentsRoutes() + bookmarksRoutes(navController) + settingsRoutes(navController) + loginRoutes(navController) } } }