From 0edd7ebaab273e0716cf6a4046014181511effad Mon Sep 17 00:00:00 2001 From: bart Date: Mon, 15 Sep 2025 10:01:21 +0200 Subject: [PATCH 01/11] Added separate account management screen to support multiple accounts --- .../main/java/app/gamenative/ui/PluviaMain.kt | 8 +- .../ui/component/dialog/ProfileDialog.kt | 8 + .../app/gamenative/ui/screen/PluviaScreen.kt | 1 + .../accounts/AccountManagementScreen.kt | 247 ++++++++++++++++++ .../ui/screen/accounts/SteamAccountSection.kt | 39 +++ 5 files changed, 302 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/app/gamenative/ui/screen/accounts/AccountManagementScreen.kt create mode 100644 app/src/main/java/app/gamenative/ui/screen/accounts/SteamAccountSection.kt diff --git a/app/src/main/java/app/gamenative/ui/PluviaMain.kt b/app/src/main/java/app/gamenative/ui/PluviaMain.kt index 5d4d64a81..ea02bffe1 100644 --- a/app/src/main/java/app/gamenative/ui/PluviaMain.kt +++ b/app/src/main/java/app/gamenative/ui/PluviaMain.kt @@ -79,6 +79,7 @@ import timber.log.Timber import java.util.Date import java.util.EnumSet import kotlin.reflect.KFunction2 +import app.gamenative.ui.screen.accounts.AccountManagementScreen @Composable fun PluviaMain( @@ -694,7 +695,7 @@ fun PluviaMain( NavHost( navController = navController, - startDestination = PluviaScreen.LoginUser.route, + startDestination = PluviaScreen.Home.route, ) { /** Login **/ /** Login **/ @@ -705,6 +706,11 @@ fun PluviaMain( }, ) } + + /** Account Management **/ + composable(route = PluviaScreen.AccountManagement.route) { + AccountManagementScreen(navController = navController) + } /** Library, Downloads, Friends **/ /** Library, Downloads, Friends **/ composable( diff --git a/app/src/main/java/app/gamenative/ui/component/dialog/ProfileDialog.kt b/app/src/main/java/app/gamenative/ui/component/dialog/ProfileDialog.kt index 26665d65f..8663d8b2d 100644 --- a/app/src/main/java/app/gamenative/ui/component/dialog/ProfileDialog.kt +++ b/app/src/main/java/app/gamenative/ui/component/dialog/ProfileDialog.kt @@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.size import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.Login import androidx.compose.material.icons.automirrored.filled.Logout +import androidx.compose.material.icons.filled.AccountCircle import androidx.compose.material.icons.filled.Help import androidx.compose.material.icons.filled.Settings import androidx.compose.material3.AlertDialog @@ -109,6 +110,13 @@ fun ProfileDialog( /* Action Buttons */ Spacer(modifier = Modifier.height(16.dp)) + + FilledTonalButton(modifier = Modifier.fillMaxWidth(), onClick = { onNavigateRoute(PluviaScreen.AccountManagement.route) }) { + Icon(imageVector = Icons.Default.AccountCircle, contentDescription = null) + Spacer(modifier = Modifier.size(ButtonDefaults.IconSize)) + Text(text = "Manage Accounts") + } + FilledTonalButton(modifier = Modifier.fillMaxWidth(), onClick = { onNavigateRoute(PluviaScreen.Settings.route) }) { Icon(imageVector = Icons.Default.Settings, contentDescription = null) Spacer(modifier = Modifier.size(ButtonDefaults.IconSize)) diff --git a/app/src/main/java/app/gamenative/ui/screen/PluviaScreen.kt b/app/src/main/java/app/gamenative/ui/screen/PluviaScreen.kt index 5fad849b8..9de6afdce 100644 --- a/app/src/main/java/app/gamenative/ui/screen/PluviaScreen.kt +++ b/app/src/main/java/app/gamenative/ui/screen/PluviaScreen.kt @@ -8,6 +8,7 @@ sealed class PluviaScreen(val route: String) { data object Home : PluviaScreen("home") data object XServer : PluviaScreen("xserver") data object Settings : PluviaScreen("settings") + data object AccountManagement : PluviaScreen("accounts") data object Chat : PluviaScreen("chat/{id}") { fun route(id: Long) = "chat/$id" const val ARG_ID = "id" diff --git a/app/src/main/java/app/gamenative/ui/screen/accounts/AccountManagementScreen.kt b/app/src/main/java/app/gamenative/ui/screen/accounts/AccountManagementScreen.kt new file mode 100644 index 000000000..456d2a924 --- /dev/null +++ b/app/src/main/java/app/gamenative/ui/screen/accounts/AccountManagementScreen.kt @@ -0,0 +1,247 @@ +package app.gamenative.ui.screen.accounts + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.* +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.navigation.NavController +import app.gamenative.ui.component.topbar.BackButton +import com.alorma.compose.settings.ui.SettingsGroup +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.ui.graphics.Brush + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun AccountManagementScreen( + navController: NavController, + modifier: Modifier = Modifier +) { + val snackBarHostState = remember { SnackbarHostState() } + val scrollState = rememberScrollState() + + Scaffold( + snackbarHost = { SnackbarHost(hostState = snackBarHostState) }, + topBar = { + CenterAlignedTopAppBar( + title = { Text(text = "Manage Accounts") }, + navigationIcon = { + BackButton(onClick = { navController.popBackStack() }) + }, + ) + }, + ) { paddingValues -> + Column( + modifier = modifier + .padding(paddingValues) + .displayCutoutPadding() + .fillMaxSize() + .verticalScroll(scrollState), + ) { + AccountsGroup(navController = navController) + } + } +} + +@Composable +private fun AccountsGroup( + navController: NavController +) { + SettingsGroup(title = { Text(text = "Accounts") }) { + SteamAccountSection(navController = navController) + // Other account sections (GOG, Epic Games, etc.) + } +} + +@Composable +private fun AccountInfoCard() { + Card( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 8.dp), + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.surfaceVariant + ) + ) { + Column( + modifier = Modifier.padding(16.dp), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + Icon( + imageVector = Icons.Default.Info, + contentDescription = null, + tint = MaterialTheme.colorScheme.primary + ) + Text( + text = "Platform Information", + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + + Text( + text = "• You can use GameNative without logging into any accounts\n" + + "• Steam login enables downloading and playing Steam games", + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + } +} + +// Keep the existing AccountSection for backward compatibility +@Composable +fun AccountSection( + title: String, + description: String, + icon: androidx.compose.ui.graphics.vector.ImageVector, + isLoggedIn: Boolean, + username: String?, + onLogin: () -> Unit, + onLogout: () -> Unit, + modifier: Modifier = Modifier, + isLoading: Boolean = false, + error: String? = null +) { + val primaryColor = MaterialTheme.colorScheme.primary + val tertiaryColor = MaterialTheme.colorScheme.tertiary + + Card( + modifier = modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 4.dp), + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.95f) + ), + border = BorderStroke(1.dp, primaryColor.copy(alpha = 0.2f)), + shape = RoundedCornerShape(16.dp) + ) { + Box( + modifier = Modifier + .fillMaxWidth() + .height(2.dp) + .background( + brush = Brush.horizontalGradient( + colors = listOf(primaryColor, tertiaryColor, primaryColor) + ) + ) + ) + + Column( + modifier = Modifier.padding(16.dp), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + Row( + horizontalArrangement = Arrangement.spacedBy(12.dp) + ) { + Icon( + imageVector = icon, + contentDescription = null, + modifier = Modifier.size(32.dp), + tint = if (isLoggedIn) + MaterialTheme.colorScheme.onPrimaryContainer + else + MaterialTheme.colorScheme.onSurface + ) + + Column(modifier = Modifier.weight(1f)) { + Text( + text = title, + style = MaterialTheme.typography.titleMedium, + color = if (isLoggedIn) + MaterialTheme.colorScheme.onPrimaryContainer + else + MaterialTheme.colorScheme.onSurface + ) + + Text( + text = if (isLoggedIn && username != null) + "Logged in as $username" + else + description, + style = MaterialTheme.typography.bodyMedium, + color = if (isLoggedIn) + MaterialTheme.colorScheme.onPrimaryContainer.copy(alpha = 0.8f) + else + MaterialTheme.colorScheme.onSurfaceVariant + ) + } + + // Status indicator + Icon( + imageVector = if (isLoggedIn) Icons.Default.CheckCircle else Icons.Default.Circle, + contentDescription = if (isLoggedIn) "Connected" else "Not connected", + tint = if (isLoggedIn) + MaterialTheme.colorScheme.primary + else + MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.size(20.dp) + ) + } + + // Error message + if (error != null) { + Card( + modifier = Modifier.fillMaxWidth(), + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.errorContainer + ) + ) { + Text( + text = error, + modifier = Modifier.padding(12.dp), + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onErrorContainer + ) + } + } + + // Action button + if (isLoggedIn) { + OutlinedButton( + onClick = onLogout, + modifier = Modifier.fillMaxWidth(), + enabled = !isLoading + ) { + if (isLoading) { + CircularProgressIndicator( + modifier = Modifier.size(16.dp), + strokeWidth = 2.dp + ) + } else { + Icon(Icons.Default.Logout, contentDescription = null) + } + Spacer(modifier = Modifier.width(8.dp)) + Text("Sign Out") + } + } else { + Button( + onClick = onLogin, + modifier = Modifier.fillMaxWidth(), + enabled = !isLoading + ) { + if (isLoading) { + CircularProgressIndicator( + modifier = Modifier.size(16.dp), + strokeWidth = 2.dp, + color = MaterialTheme.colorScheme.onPrimary + ) + } else { + Icon(Icons.Default.Login, contentDescription = null) + } + Spacer(modifier = Modifier.width(8.dp)) + Text(if (isLoading) "Signing In..." else "Sign In") + } + } + } + } +} diff --git a/app/src/main/java/app/gamenative/ui/screen/accounts/SteamAccountSection.kt b/app/src/main/java/app/gamenative/ui/screen/accounts/SteamAccountSection.kt new file mode 100644 index 000000000..cd1110f90 --- /dev/null +++ b/app/src/main/java/app/gamenative/ui/screen/accounts/SteamAccountSection.kt @@ -0,0 +1,39 @@ +package app.gamenative.ui.screen.accounts + +import androidx.compose.foundation.layout.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Games +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.navigation.NavController +import app.gamenative.service.SteamService +import app.gamenative.ui.screen.PluviaScreen +import kotlinx.coroutines.launch + +@Composable +fun SteamAccountSection( + navController: NavController, + modifier: Modifier = Modifier +) { + val scope = rememberCoroutineScope() + + // State for Steam + val isSteamLoggedIn = SteamService.isLoggedIn + + AccountSection( + title = "Steam", + description = "Access your Steam library and games", + icon = Icons.Default.Games, + isLoggedIn = isSteamLoggedIn, + username = if (isSteamLoggedIn) "Steam User" else null, + onLogin = { + navController.navigate(PluviaScreen.LoginUser.route) + }, + onLogout = { + scope.launch { + SteamService.logOut() + } + }, + modifier = modifier + ) +} From 9f07a9b95da6d96d33a81b02fd6e244db88fe337 Mon Sep 17 00:00:00 2001 From: bart Date: Mon, 15 Sep 2025 10:03:46 +0200 Subject: [PATCH 02/11] Remove the generic logout button. Since it now moved into accounts. --- app/src/main/java/app/gamenative/ui/PluviaMain.kt | 3 --- .../gamenative/ui/component/dialog/ProfileDialog.kt | 12 ++---------- .../gamenative/ui/component/topbar/AccountButton.kt | 6 ------ .../main/java/app/gamenative/ui/screen/HomeScreen.kt | 3 --- .../gamenative/ui/screen/library/LibraryScreen.kt | 5 ----- .../screen/library/components/LibraryDetailPane.kt | 3 +-- .../ui/screen/library/components/LibraryListPane.kt | 3 --- 7 files changed, 3 insertions(+), 32 deletions(-) diff --git a/app/src/main/java/app/gamenative/ui/PluviaMain.kt b/app/src/main/java/app/gamenative/ui/PluviaMain.kt index ea02bffe1..db4ff0dd3 100644 --- a/app/src/main/java/app/gamenative/ui/PluviaMain.kt +++ b/app/src/main/java/app/gamenative/ui/PluviaMain.kt @@ -749,9 +749,6 @@ fun PluviaMain( onNavigateRoute = { navController.navigate(it) }, - onLogout = { - SteamService.logOut() - }, onGoOnline = { navController.navigate(PluviaScreen.LoginUser.route) }, diff --git a/app/src/main/java/app/gamenative/ui/component/dialog/ProfileDialog.kt b/app/src/main/java/app/gamenative/ui/component/dialog/ProfileDialog.kt index 8663d8b2d..21a443588 100644 --- a/app/src/main/java/app/gamenative/ui/component/dialog/ProfileDialog.kt +++ b/app/src/main/java/app/gamenative/ui/component/dialog/ProfileDialog.kt @@ -50,7 +50,6 @@ fun ProfileDialog( state: EPersonaState, onStatusChange: (EPersonaState) -> Unit, onNavigateRoute: (String) -> Unit, - onLogout: () -> Unit, onDismiss: () -> Unit, onGoOnline: () -> Unit, isOffline: Boolean = false, @@ -110,13 +109,13 @@ fun ProfileDialog( /* Action Buttons */ Spacer(modifier = Modifier.height(16.dp)) - + FilledTonalButton(modifier = Modifier.fillMaxWidth(), onClick = { onNavigateRoute(PluviaScreen.AccountManagement.route) }) { Icon(imageVector = Icons.Default.AccountCircle, contentDescription = null) Spacer(modifier = Modifier.size(ButtonDefaults.IconSize)) Text(text = "Manage Accounts") } - + FilledTonalButton(modifier = Modifier.fillMaxWidth(), onClick = { onNavigateRoute(PluviaScreen.Settings.route) }) { Icon(imageVector = Icons.Default.Settings, contentDescription = null) Spacer(modifier = Modifier.size(ButtonDefaults.IconSize)) @@ -135,12 +134,6 @@ fun ProfileDialog( Spacer(modifier = Modifier.size(ButtonDefaults.IconSize)) Text(text = "Go Online") } - } else { - FilledTonalButton(modifier = Modifier.fillMaxWidth(), onClick = onLogout) { - Icon(imageVector = Icons.AutoMirrored.Filled.Logout, contentDescription = null) - Spacer(modifier = Modifier.size(ButtonDefaults.IconSize)) - Text(text = "Log Out") - } } } }, @@ -163,7 +156,6 @@ private fun Preview_ProfileDialog() { state = EPersonaState.Online, onStatusChange = {}, onNavigateRoute = {}, - onLogout = {}, onDismiss = {}, onGoOnline = {}, ) diff --git a/app/src/main/java/app/gamenative/ui/component/topbar/AccountButton.kt b/app/src/main/java/app/gamenative/ui/component/topbar/AccountButton.kt index f97992756..5042b0144 100644 --- a/app/src/main/java/app/gamenative/ui/component/topbar/AccountButton.kt +++ b/app/src/main/java/app/gamenative/ui/component/topbar/AccountButton.kt @@ -29,7 +29,6 @@ import timber.log.Timber @Composable fun AccountButton( onNavigateRoute: (String) -> Unit, - onLogout: () -> Unit, onGoOnline: () -> Unit, isOffline: Boolean = false, ) { @@ -70,10 +69,6 @@ fun AccountButton( onNavigateRoute(it) showDialog = false }, - onLogout = { - onLogout() - showDialog = false - }, onGoOnline = { onGoOnline() showDialog = false @@ -105,7 +100,6 @@ private fun Preview_AccountButton() { actions = { AccountButton( onNavigateRoute = {}, - onLogout = {}, onGoOnline = {}, ) }, diff --git a/app/src/main/java/app/gamenative/ui/screen/HomeScreen.kt b/app/src/main/java/app/gamenative/ui/screen/HomeScreen.kt index c9f5b6751..77ebf7882 100644 --- a/app/src/main/java/app/gamenative/ui/screen/HomeScreen.kt +++ b/app/src/main/java/app/gamenative/ui/screen/HomeScreen.kt @@ -21,7 +21,6 @@ fun HomeScreen( onChat: (Long) -> Unit, onClickExit: () -> Unit, onClickPlay: (Int, Boolean) -> Unit, - onLogout: () -> Unit, onNavigateRoute: (String) -> Unit, onGoOnline: () -> Unit, isOffline: Boolean = false @@ -37,7 +36,6 @@ fun HomeScreen( HomeLibraryScreen( onClickPlay = onClickPlay, onNavigateRoute = onNavigateRoute, - onLogout = onLogout, onGoOnline = onGoOnline, isOffline = isOffline, ) @@ -57,7 +55,6 @@ private fun Preview_HomeScreenContent() { HomeScreen( onChat = {}, onClickPlay = { _, _ -> }, - onLogout = {}, onNavigateRoute = {}, onClickExit = {}, onGoOnline = {}, diff --git a/app/src/main/java/app/gamenative/ui/screen/library/LibraryScreen.kt b/app/src/main/java/app/gamenative/ui/screen/library/LibraryScreen.kt index 060039cb7..e4e178e42 100644 --- a/app/src/main/java/app/gamenative/ui/screen/library/LibraryScreen.kt +++ b/app/src/main/java/app/gamenative/ui/screen/library/LibraryScreen.kt @@ -53,7 +53,6 @@ fun HomeLibraryScreen( viewModel: LibraryViewModel = hiltViewModel(), onClickPlay: (Int, Boolean) -> Unit, onNavigateRoute: (String) -> Unit, - onLogout: () -> Unit, onGoOnline: () -> Unit, isOffline: Boolean = false, ) { @@ -71,7 +70,6 @@ fun HomeLibraryScreen( onSearchQuery = viewModel::onSearchQuery, onClickPlay = onClickPlay, onNavigateRoute = onNavigateRoute, - onLogout = onLogout, onGoOnline = onGoOnline, isOffline = isOffline, ) @@ -90,7 +88,6 @@ private fun LibraryScreenContent( onSearchQuery: (String) -> Unit, onClickPlay: (Int, Boolean) -> Unit, onNavigateRoute: (String) -> Unit, - onLogout: () -> Unit, onGoOnline: () -> Unit, isOffline: Boolean = false, ) { @@ -117,7 +114,6 @@ private fun LibraryScreenContent( onIsSearching = onIsSearching, onSearchQuery = onSearchQuery, onNavigateRoute = onNavigateRoute, - onLogout = onLogout, onNavigate = { appId -> selectedAppId = appId }, onGoOnline = onGoOnline, isOffline = isOffline, @@ -191,7 +187,6 @@ private fun Preview_LibraryScreenContent() { }, onClickPlay = { _, _ -> }, onNavigateRoute = {}, - onLogout = {}, onGoOnline = {}, ) } diff --git a/app/src/main/java/app/gamenative/ui/screen/library/components/LibraryDetailPane.kt b/app/src/main/java/app/gamenative/ui/screen/library/components/LibraryDetailPane.kt index 825d1d79e..a1a28933c 100644 --- a/app/src/main/java/app/gamenative/ui/screen/library/components/LibraryDetailPane.kt +++ b/app/src/main/java/app/gamenative/ui/screen/library/components/LibraryDetailPane.kt @@ -46,10 +46,9 @@ internal fun LibraryDetailPane( onPageChange = {}, onModalBottomSheet = {}, onIsSearching = {}, - onLogout = {}, - onNavigate = {}, onSearchQuery = {}, onNavigateRoute = {}, + onNavigate = {}, onGoOnline = {}, ) } else { diff --git a/app/src/main/java/app/gamenative/ui/screen/library/components/LibraryListPane.kt b/app/src/main/java/app/gamenative/ui/screen/library/components/LibraryListPane.kt index 90ed9f592..98ddd0d03 100644 --- a/app/src/main/java/app/gamenative/ui/screen/library/components/LibraryListPane.kt +++ b/app/src/main/java/app/gamenative/ui/screen/library/components/LibraryListPane.kt @@ -69,7 +69,6 @@ internal fun LibraryListPane( onModalBottomSheet: (Boolean) -> Unit, onPageChange: (Int) -> Unit, onIsSearching: (Boolean) -> Unit, - onLogout: () -> Unit, onNavigate: (String) -> Unit, onSearchQuery: (String) -> Unit, onNavigateRoute: (String) -> Unit, @@ -158,7 +157,6 @@ internal fun LibraryListPane( ) { AccountButton( onNavigateRoute = onNavigateRoute, - onLogout = onLogout, onGoOnline = onGoOnline, isOffline = isOffline, ) @@ -294,7 +292,6 @@ private fun Preview_LibraryListPane() { onIsSearching = { }, onSearchQuery = { }, onNavigateRoute = { }, - onLogout = { }, onNavigate = { }, onGoOnline = { }, ) From c47622bdc83094c23569c62fad0ad6be944c6404 Mon Sep 17 00:00:00 2001 From: bart Date: Thu, 4 Sep 2025 16:01:03 +0200 Subject: [PATCH 03/11] Let user remain in account management screen after sign out --- app/src/main/java/app/gamenative/ui/PluviaMain.kt | 7 +------ .../ui/screen/accounts/SteamAccountSection.kt | 11 ++++++++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/app/gamenative/ui/PluviaMain.kt b/app/src/main/java/app/gamenative/ui/PluviaMain.kt index db4ff0dd3..fde909c38 100644 --- a/app/src/main/java/app/gamenative/ui/PluviaMain.kt +++ b/app/src/main/java/app/gamenative/ui/PluviaMain.kt @@ -193,12 +193,7 @@ fun PluviaMain( } MainViewModel.MainUiEvent.OnLoggedOut -> { - // Pop stack and go back to login. - navController.popBackStack( - route = PluviaScreen.LoginUser.route, - inclusive = false, - saveState = false, - ) + // Do nothing - let users stay on current page after logout } is MainViewModel.MainUiEvent.OnLogonEnded -> { diff --git a/app/src/main/java/app/gamenative/ui/screen/accounts/SteamAccountSection.kt b/app/src/main/java/app/gamenative/ui/screen/accounts/SteamAccountSection.kt index cd1110f90..4ff00a117 100644 --- a/app/src/main/java/app/gamenative/ui/screen/accounts/SteamAccountSection.kt +++ b/app/src/main/java/app/gamenative/ui/screen/accounts/SteamAccountSection.kt @@ -16,10 +16,8 @@ fun SteamAccountSection( modifier: Modifier = Modifier ) { val scope = rememberCoroutineScope() - - // State for Steam val isSteamLoggedIn = SteamService.isLoggedIn - + AccountSection( title = "Steam", description = "Access your Steam library and games", @@ -32,6 +30,13 @@ fun SteamAccountSection( onLogout = { scope.launch { SteamService.logOut() + // Re-navigate to current screen to refresh logged in state + navController.navigate(PluviaScreen.AccountManagement.route) { + popUpTo(PluviaScreen.Home.route) { + inclusive = false + } + launchSingleTop = true + } } }, modifier = modifier From 6fe08b60ae7758d0c6b7e03f515da74972029a6e Mon Sep 17 00:00:00 2001 From: bart Date: Thu, 4 Sep 2025 22:09:44 +0200 Subject: [PATCH 04/11] Use actual steam icon --- .../accounts/AccountManagementScreen.kt | 28 +++++++++++++------ .../ui/screen/accounts/SteamAccountSection.kt | 5 +--- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/app/gamenative/ui/screen/accounts/AccountManagementScreen.kt b/app/src/main/java/app/gamenative/ui/screen/accounts/AccountManagementScreen.kt index 456d2a924..48202bcd4 100644 --- a/app/src/main/java/app/gamenative/ui/screen/accounts/AccountManagementScreen.kt +++ b/app/src/main/java/app/gamenative/ui/screen/accounts/AccountManagementScreen.kt @@ -16,6 +16,8 @@ import app.gamenative.ui.component.topbar.BackButton import com.alorma.compose.settings.ui.SettingsGroup import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.ui.graphics.Brush +import com.skydoves.landscapist.ImageOptions +import com.skydoves.landscapist.coil.CoilImage @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -103,7 +105,7 @@ private fun AccountInfoCard() { fun AccountSection( title: String, description: String, - icon: androidx.compose.ui.graphics.vector.ImageVector, + icon: String, isLoggedIn: Boolean, username: String?, onLogin: () -> Unit, @@ -143,14 +145,24 @@ fun AccountSection( Row( horizontalArrangement = Arrangement.spacedBy(12.dp) ) { - Icon( - imageVector = icon, - contentDescription = null, + CoilImage( + imageModel = { icon }, + imageOptions = ImageOptions( + contentScale = androidx.compose.ui.layout.ContentScale.Fit, + alignment = androidx.compose.ui.Alignment.Center + ), modifier = Modifier.size(32.dp), - tint = if (isLoggedIn) - MaterialTheme.colorScheme.onPrimaryContainer - else - MaterialTheme.colorScheme.onSurface + failure = { + Icon( + imageVector = Icons.Default.AccountCircle, + contentDescription = null, + modifier = Modifier.size(32.dp), + tint = if (isLoggedIn) + MaterialTheme.colorScheme.onPrimaryContainer + else + MaterialTheme.colorScheme.onSurface + ) + } ) Column(modifier = Modifier.weight(1f)) { diff --git a/app/src/main/java/app/gamenative/ui/screen/accounts/SteamAccountSection.kt b/app/src/main/java/app/gamenative/ui/screen/accounts/SteamAccountSection.kt index 4ff00a117..d66cec4e3 100644 --- a/app/src/main/java/app/gamenative/ui/screen/accounts/SteamAccountSection.kt +++ b/app/src/main/java/app/gamenative/ui/screen/accounts/SteamAccountSection.kt @@ -1,8 +1,5 @@ package app.gamenative.ui.screen.accounts -import androidx.compose.foundation.layout.* -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Games import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.navigation.NavController @@ -21,7 +18,7 @@ fun SteamAccountSection( AccountSection( title = "Steam", description = "Access your Steam library and games", - icon = Icons.Default.Games, + icon = "https://store.steampowered.com/favicon.ico", isLoggedIn = isSteamLoggedIn, username = if (isSteamLoggedIn) "Steam User" else null, onLogin = { From 9a53ff5df6b4ef2e66f6aab468052da706d6dd28 Mon Sep 17 00:00:00 2001 From: bart Date: Mon, 15 Sep 2025 10:09:23 +0200 Subject: [PATCH 05/11] Linted code --- .../main/java/app/gamenative/ui/PluviaMain.kt | 1 + .../gamenative/ui/model/LibraryViewModel.kt | 16 +-- .../accounts/AccountManagementScreen.kt | 113 +++++++++--------- .../ui/screen/accounts/SteamAccountSection.kt | 4 +- .../ui/screen/library/LibraryScreen.kt | 22 +--- .../library/components/LibraryDetailPane.kt | 2 +- .../library/components/LibraryListPane.kt | 60 +++++----- 7 files changed, 108 insertions(+), 110 deletions(-) diff --git a/app/src/main/java/app/gamenative/ui/PluviaMain.kt b/app/src/main/java/app/gamenative/ui/PluviaMain.kt index fde909c38..9c9be8e33 100644 --- a/app/src/main/java/app/gamenative/ui/PluviaMain.kt +++ b/app/src/main/java/app/gamenative/ui/PluviaMain.kt @@ -58,6 +58,7 @@ import app.gamenative.ui.enums.Orientation import app.gamenative.ui.model.MainViewModel import app.gamenative.ui.screen.HomeScreen import app.gamenative.ui.screen.PluviaScreen +import app.gamenative.ui.screen.accounts.AccountManagementScreen import app.gamenative.ui.screen.chat.ChatScreen import app.gamenative.ui.screen.login.UserLoginScreen import app.gamenative.ui.screen.settings.SettingsScreen diff --git a/app/src/main/java/app/gamenative/ui/model/LibraryViewModel.kt b/app/src/main/java/app/gamenative/ui/model/LibraryViewModel.kt index 0ce3ec2dd..01db3c0cc 100644 --- a/app/src/main/java/app/gamenative/ui/model/LibraryViewModel.kt +++ b/app/src/main/java/app/gamenative/ui/model/LibraryViewModel.kt @@ -18,6 +18,8 @@ import app.gamenative.ui.enums.AppFilter import dagger.hilt.android.lifecycle.HiltViewModel import java.util.EnumSet import javax.inject.Inject +import kotlin.math.max +import kotlin.math.min import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -25,8 +27,6 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import timber.log.Timber -import kotlin.math.max -import kotlin.math.min @HiltViewModel class LibraryViewModel @Inject constructor( @@ -40,8 +40,8 @@ class LibraryViewModel @Inject constructor( var listState: LazyListState by mutableStateOf(LazyListState(0, 0)) // How many items loaded on one page of results - private var paginationCurrentPage: Int = 0; - private var lastPageInCurrentFilter: Int = 0; + private var paginationCurrentPage: Int = 0 + private var lastPageInCurrentFilter: Int = 0 // Complete and unfiltered app list private var appList: List = emptyList() @@ -156,8 +156,8 @@ class LibraryViewModel @Inject constructor( } .sortedWith( // Comes from DAO in alphabetical order - compareByDescending { downloadDirectoryApps.contains(SteamService.getAppDirName(it)) } - ); + compareByDescending { downloadDirectoryApps.contains(SteamService.getAppDirName(it)) }, + ) // Total count for the current filter val totalFound = filteredList.count() @@ -183,14 +183,14 @@ class LibraryViewModel @Inject constructor( } .toList() - Timber.tag("LibraryViewModel").d("Filtered list size: ${totalFound}") + Timber.tag("LibraryViewModel").d("Filtered list size: $totalFound") _state.update { it.copy( appInfoList = filteredListPage, currentPaginationPage = paginationPage + 1, // visual display is not 0 indexed lastPaginationPage = lastPageInCurrentFilter + 1, totalAppsInFilter = totalFound, - ) + ) } } } diff --git a/app/src/main/java/app/gamenative/ui/screen/accounts/AccountManagementScreen.kt b/app/src/main/java/app/gamenative/ui/screen/accounts/AccountManagementScreen.kt index 48202bcd4..1e7dc1af0 100644 --- a/app/src/main/java/app/gamenative/ui/screen/accounts/AccountManagementScreen.kt +++ b/app/src/main/java/app/gamenative/ui/screen/accounts/AccountManagementScreen.kt @@ -4,18 +4,18 @@ import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.* import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Brush import androidx.compose.ui.unit.dp import androidx.navigation.NavController import app.gamenative.ui.component.topbar.BackButton import com.alorma.compose.settings.ui.SettingsGroup -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.ui.graphics.Brush import com.skydoves.landscapist.ImageOptions import com.skydoves.landscapist.coil.CoilImage @@ -23,7 +23,7 @@ import com.skydoves.landscapist.coil.CoilImage @Composable fun AccountManagementScreen( navController: NavController, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, ) { val snackBarHostState = remember { SnackbarHostState() } val scrollState = rememberScrollState() @@ -53,7 +53,7 @@ fun AccountManagementScreen( @Composable private fun AccountsGroup( - navController: NavController + navController: NavController, ) { SettingsGroup(title = { Text(text = "Accounts") }) { SteamAccountSection(navController = navController) @@ -68,33 +68,33 @@ private fun AccountInfoCard() { .fillMaxWidth() .padding(horizontal = 16.dp, vertical = 8.dp), colors = CardDefaults.cardColors( - containerColor = MaterialTheme.colorScheme.surfaceVariant - ) + containerColor = MaterialTheme.colorScheme.surfaceVariant, + ), ) { Column( modifier = Modifier.padding(16.dp), - verticalArrangement = Arrangement.spacedBy(8.dp) + verticalArrangement = Arrangement.spacedBy(8.dp), ) { Row( - horizontalArrangement = Arrangement.spacedBy(8.dp) + horizontalArrangement = Arrangement.spacedBy(8.dp), ) { Icon( imageVector = Icons.Default.Info, contentDescription = null, - tint = MaterialTheme.colorScheme.primary + tint = MaterialTheme.colorScheme.primary, ) Text( text = "Platform Information", style = MaterialTheme.typography.titleMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant + color = MaterialTheme.colorScheme.onSurfaceVariant, ) } - + Text( text = "• You can use GameNative without logging into any accounts\n" + - "• Steam login enables downloading and playing Steam games", + "• Steam login enables downloading and playing Steam games", style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant + color = MaterialTheme.colorScheme.onSurfaceVariant, ) } } @@ -112,20 +112,20 @@ fun AccountSection( onLogout: () -> Unit, modifier: Modifier = Modifier, isLoading: Boolean = false, - error: String? = null + error: String? = null, ) { val primaryColor = MaterialTheme.colorScheme.primary val tertiaryColor = MaterialTheme.colorScheme.tertiary - + Card( modifier = modifier .fillMaxWidth() .padding(horizontal = 16.dp, vertical = 4.dp), colors = CardDefaults.cardColors( - containerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.95f) + containerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.95f), ), border = BorderStroke(1.dp, primaryColor.copy(alpha = 0.2f)), - shape = RoundedCornerShape(16.dp) + shape = RoundedCornerShape(16.dp), ) { Box( modifier = Modifier @@ -133,23 +133,23 @@ fun AccountSection( .height(2.dp) .background( brush = Brush.horizontalGradient( - colors = listOf(primaryColor, tertiaryColor, primaryColor) - ) - ) + colors = listOf(primaryColor, tertiaryColor, primaryColor), + ), + ), ) - + Column( modifier = Modifier.padding(16.dp), - verticalArrangement = Arrangement.spacedBy(12.dp) + verticalArrangement = Arrangement.spacedBy(12.dp), ) { Row( - horizontalArrangement = Arrangement.spacedBy(12.dp) + horizontalArrangement = Arrangement.spacedBy(12.dp), ) { CoilImage( imageModel = { icon }, imageOptions = ImageOptions( contentScale = androidx.compose.ui.layout.ContentScale.Fit, - alignment = androidx.compose.ui.Alignment.Center + alignment = androidx.compose.ui.Alignment.Center, ), modifier = Modifier.size(32.dp), failure = { @@ -157,77 +157,82 @@ fun AccountSection( imageVector = Icons.Default.AccountCircle, contentDescription = null, modifier = Modifier.size(32.dp), - tint = if (isLoggedIn) - MaterialTheme.colorScheme.onPrimaryContainer - else + tint = if (isLoggedIn) { + MaterialTheme.colorScheme.onPrimaryContainer + } else { MaterialTheme.colorScheme.onSurface + }, ) - } + }, ) - + Column(modifier = Modifier.weight(1f)) { Text( text = title, style = MaterialTheme.typography.titleMedium, - color = if (isLoggedIn) - MaterialTheme.colorScheme.onPrimaryContainer - else + color = if (isLoggedIn) { + MaterialTheme.colorScheme.onPrimaryContainer + } else { MaterialTheme.colorScheme.onSurface + }, ) - + Text( - text = if (isLoggedIn && username != null) - "Logged in as $username" - else - description, + text = if (isLoggedIn && username != null) { + "Logged in as $username" + } else { + description + }, style = MaterialTheme.typography.bodyMedium, - color = if (isLoggedIn) + color = if (isLoggedIn) { MaterialTheme.colorScheme.onPrimaryContainer.copy(alpha = 0.8f) - else + } else { MaterialTheme.colorScheme.onSurfaceVariant + }, ) } - + // Status indicator Icon( imageVector = if (isLoggedIn) Icons.Default.CheckCircle else Icons.Default.Circle, contentDescription = if (isLoggedIn) "Connected" else "Not connected", - tint = if (isLoggedIn) - MaterialTheme.colorScheme.primary - else - MaterialTheme.colorScheme.onSurfaceVariant, - modifier = Modifier.size(20.dp) + tint = if (isLoggedIn) { + MaterialTheme.colorScheme.primary + } else { + MaterialTheme.colorScheme.onSurfaceVariant + }, + modifier = Modifier.size(20.dp), ) } - + // Error message if (error != null) { Card( modifier = Modifier.fillMaxWidth(), colors = CardDefaults.cardColors( - containerColor = MaterialTheme.colorScheme.errorContainer - ) + containerColor = MaterialTheme.colorScheme.errorContainer, + ), ) { Text( text = error, modifier = Modifier.padding(12.dp), style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onErrorContainer + color = MaterialTheme.colorScheme.onErrorContainer, ) } } - + // Action button if (isLoggedIn) { OutlinedButton( onClick = onLogout, modifier = Modifier.fillMaxWidth(), - enabled = !isLoading + enabled = !isLoading, ) { if (isLoading) { CircularProgressIndicator( modifier = Modifier.size(16.dp), - strokeWidth = 2.dp + strokeWidth = 2.dp, ) } else { Icon(Icons.Default.Logout, contentDescription = null) @@ -239,13 +244,13 @@ fun AccountSection( Button( onClick = onLogin, modifier = Modifier.fillMaxWidth(), - enabled = !isLoading + enabled = !isLoading, ) { if (isLoading) { CircularProgressIndicator( modifier = Modifier.size(16.dp), strokeWidth = 2.dp, - color = MaterialTheme.colorScheme.onPrimary + color = MaterialTheme.colorScheme.onPrimary, ) } else { Icon(Icons.Default.Login, contentDescription = null) diff --git a/app/src/main/java/app/gamenative/ui/screen/accounts/SteamAccountSection.kt b/app/src/main/java/app/gamenative/ui/screen/accounts/SteamAccountSection.kt index d66cec4e3..c237bcd3a 100644 --- a/app/src/main/java/app/gamenative/ui/screen/accounts/SteamAccountSection.kt +++ b/app/src/main/java/app/gamenative/ui/screen/accounts/SteamAccountSection.kt @@ -10,7 +10,7 @@ import kotlinx.coroutines.launch @Composable fun SteamAccountSection( navController: NavController, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, ) { val scope = rememberCoroutineScope() val isSteamLoggedIn = SteamService.isLoggedIn @@ -36,6 +36,6 @@ fun SteamAccountSection( } } }, - modifier = modifier + modifier = modifier, ) } diff --git a/app/src/main/java/app/gamenative/ui/screen/library/LibraryScreen.kt b/app/src/main/java/app/gamenative/ui/screen/library/LibraryScreen.kt index e4e178e42..484a33d5d 100644 --- a/app/src/main/java/app/gamenative/ui/screen/library/LibraryScreen.kt +++ b/app/src/main/java/app/gamenative/ui/screen/library/LibraryScreen.kt @@ -5,29 +5,21 @@ import androidx.activity.compose.BackHandler import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.displayCutoutPadding -import androidx.compose.foundation.layout.statusBarsPadding -import androidx.compose.foundation.layout.systemBarsPadding import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.material3.SheetState import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi -import androidx.compose.material3.adaptive.layout.AnimatedPane -import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffold -import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffoldRole -import androidx.compose.material3.adaptive.navigation.BackNavigationBehavior -import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaffoldNavigator import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.tooling.preview.Preview import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle @@ -37,15 +29,11 @@ import app.gamenative.data.GameSource import app.gamenative.service.SteamService import app.gamenative.ui.data.LibraryState import app.gamenative.ui.enums.AppFilter -import app.gamenative.ui.enums.Orientation -import app.gamenative.events.AndroidEvent -import app.gamenative.PluviaApp import app.gamenative.ui.internal.fakeAppInfo import app.gamenative.ui.model.LibraryViewModel import app.gamenative.ui.screen.library.components.LibraryDetailPane import app.gamenative.ui.screen.library.components.LibraryListPane import app.gamenative.ui.theme.PluviaTheme -import java.util.EnumSet @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -95,14 +83,16 @@ private fun LibraryScreenContent( BackHandler(selectedAppId != null) { selectedAppId = null } val safePaddingModifier = - if (LocalConfiguration.current.orientation == Configuration.ORIENTATION_PORTRAIT) + if (LocalConfiguration.current.orientation == Configuration.ORIENTATION_PORTRAIT) { Modifier.displayCutoutPadding() - else + } else { Modifier + } Box( Modifier.background(MaterialTheme.colorScheme.background) - .then(safePaddingModifier)) { + .then(safePaddingModifier), + ) { if (selectedAppId == null) { LibraryListPane( state = state, diff --git a/app/src/main/java/app/gamenative/ui/screen/library/components/LibraryDetailPane.kt b/app/src/main/java/app/gamenative/ui/screen/library/components/LibraryDetailPane.kt index a1a28933c..599cd4211 100644 --- a/app/src/main/java/app/gamenative/ui/screen/library/components/LibraryDetailPane.kt +++ b/app/src/main/java/app/gamenative/ui/screen/library/components/LibraryDetailPane.kt @@ -34,7 +34,7 @@ internal fun LibraryDetailPane( LibraryState( appInfoList = emptyList(), // Use the same default filter as in PrefManager (GAME) - appInfoSortType = EnumSet.of(AppFilter.GAME) + appInfoSortType = EnumSet.of(AppFilter.GAME), ) } diff --git a/app/src/main/java/app/gamenative/ui/screen/library/components/LibraryListPane.kt b/app/src/main/java/app/gamenative/ui/screen/library/components/LibraryListPane.kt index 98ddd0d03..5918b18d6 100644 --- a/app/src/main/java/app/gamenative/ui/screen/library/components/LibraryListPane.kt +++ b/app/src/main/java/app/gamenative/ui/screen/library/components/LibraryListPane.kt @@ -9,12 +9,16 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.FilterList +import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExtendedFloatingActionButton +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ModalBottomSheet @@ -24,13 +28,16 @@ import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Surface import androidx.compose.material3.Text +import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -39,22 +46,16 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import app.gamenative.PrefManager import app.gamenative.data.LibraryItem +import app.gamenative.service.DownloadService +import app.gamenative.ui.component.topbar.AccountButton import app.gamenative.ui.data.LibraryState import app.gamenative.ui.enums.AppFilter import app.gamenative.ui.internal.fakeAppInfo -import app.gamenative.service.DownloadService import app.gamenative.ui.theme.PluviaTheme -import app.gamenative.ui.component.topbar.AccountButton -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items -import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.HorizontalDivider -import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.snapshotFlow -import app.gamenative.PrefManager import app.gamenative.utils.DeviceUtils +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.distinctUntilChanged import app.gamenative.data.GameSource @@ -88,31 +89,32 @@ internal fun LibraryListPane( .filterNotNull() .distinctUntilChanged() .collect { lastVisibleIndex -> - if (lastVisibleIndex >= state.appInfoList.lastIndex - && state.appInfoList.size < state.totalAppsInFilter) { + if (lastVisibleIndex >= state.appInfoList.lastIndex && + state.appInfoList.size < state.totalAppsInFilter + ) { onPageChange(1) } } } Scaffold( - snackbarHost = { SnackbarHost(snackBarHost) } + snackbarHost = { SnackbarHost(snackBarHost) }, ) { paddingValues -> Column( modifier = Modifier .fillMaxSize() - .padding(top = paddingValues.calculateTopPadding()) + .padding(top = paddingValues.calculateTopPadding()), ) { // Modern Header with gradient Box( modifier = Modifier .fillMaxWidth() - .padding(16.dp) + .padding(16.dp), ) { Row( modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = androidx.compose.foundation.layout.Arrangement.SpaceBetween + horizontalArrangement = androidx.compose.foundation.layout.Arrangement.SpaceBetween, ) { Column { Text( @@ -122,15 +124,15 @@ internal fun LibraryListPane( brush = Brush.horizontalGradient( colors = listOf( MaterialTheme.colorScheme.primary, - MaterialTheme.colorScheme.tertiary - ) - ) - ) + MaterialTheme.colorScheme.tertiary, + ), + ), + ), ) Text( text = "${state.totalAppsInFilter} games • $installedCount installed", style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant + color = MaterialTheme.colorScheme.onSurfaceVariant, ) } @@ -138,7 +140,7 @@ internal fun LibraryListPane( Box( modifier = Modifier .weight(1f) - .padding(horizontal = 30.dp) + .padding(horizontal = 30.dp), ) { LibrarySearchBar( state = state, @@ -153,7 +155,7 @@ internal fun LibraryListPane( modifier = Modifier .clip(RoundedCornerShape(12.dp)) .background(MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.3f)) - .padding(8.dp) + .padding(8.dp), ) { AccountButton( onNavigateRoute = onNavigateRoute, @@ -164,12 +166,12 @@ internal fun LibraryListPane( } } - if (! isViewWide) { + if (!isViewWide) { // Search bar Box( modifier = Modifier .fillMaxWidth() - .padding(horizontal = 20.dp, vertical = 12.dp) + .padding(horizontal = 20.dp, vertical = 12.dp), ) { LibrarySearchBar( state = state, @@ -189,14 +191,14 @@ internal fun LibraryListPane( contentPadding = PaddingValues( start = 20.dp, end = 20.dp, - bottom = 72.dp + bottom = 72.dp, ), ) { items(items = state.appInfoList, key = { it.index }) { item -> AppItem( modifier = Modifier.animateItem(), appInfo = item, - onClick = { onNavigate(item.appId) } + onClick = { onNavigate(item.appId) }, ) if (item.index < state.appInfoList.lastIndex) { HorizontalDivider() @@ -208,7 +210,7 @@ internal fun LibraryListPane( modifier = Modifier .fillMaxWidth() .padding(16.dp), - contentAlignment = Alignment.Center + contentAlignment = Alignment.Center, ) { CircularProgressIndicator() } @@ -227,7 +229,7 @@ internal fun LibraryListPane( contentColor = MaterialTheme.colorScheme.onPrimary, modifier = Modifier .align(Alignment.BottomEnd) - .padding(24.dp) + .padding(24.dp), ) } From 0b7e6fc1bc46530c8ef2018947f7fe30e4c4be0c Mon Sep 17 00:00:00 2001 From: bart Date: Fri, 5 Sep 2025 08:37:32 +0200 Subject: [PATCH 06/11] Added preview of account management screen preview (+ removed unused func) --- .../accounts/AccountManagementScreen.kt | 48 ++++--------------- 1 file changed, 9 insertions(+), 39 deletions(-) diff --git a/app/src/main/java/app/gamenative/ui/screen/accounts/AccountManagementScreen.kt b/app/src/main/java/app/gamenative/ui/screen/accounts/AccountManagementScreen.kt index 1e7dc1af0..b5519ddf8 100644 --- a/app/src/main/java/app/gamenative/ui/screen/accounts/AccountManagementScreen.kt +++ b/app/src/main/java/app/gamenative/ui/screen/accounts/AccountManagementScreen.kt @@ -12,8 +12,10 @@ import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.navigation.NavController +import androidx.navigation.compose.rememberNavController import app.gamenative.ui.component.topbar.BackButton import com.alorma.compose.settings.ui.SettingsGroup import com.skydoves.landscapist.ImageOptions @@ -61,45 +63,6 @@ private fun AccountsGroup( } } -@Composable -private fun AccountInfoCard() { - Card( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp, vertical = 8.dp), - colors = CardDefaults.cardColors( - containerColor = MaterialTheme.colorScheme.surfaceVariant, - ), - ) { - Column( - modifier = Modifier.padding(16.dp), - verticalArrangement = Arrangement.spacedBy(8.dp), - ) { - Row( - horizontalArrangement = Arrangement.spacedBy(8.dp), - ) { - Icon( - imageVector = Icons.Default.Info, - contentDescription = null, - tint = MaterialTheme.colorScheme.primary, - ) - Text( - text = "Platform Information", - style = MaterialTheme.typography.titleMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) - } - - Text( - text = "• You can use GameNative without logging into any accounts\n" + - "• Steam login enables downloading and playing Steam games", - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) - } - } -} - // Keep the existing AccountSection for backward compatibility @Composable fun AccountSection( @@ -262,3 +225,10 @@ fun AccountSection( } } } + +@Preview(showBackground = true) +@Composable +private fun AccountManagementScreenPreview() { + val navController = rememberNavController() + AccountManagementScreen(navController = navController) +} From fe10b4d97926a677c128e91ced33e69b4178235f Mon Sep 17 00:00:00 2001 From: RadicalDog Date: Fri, 5 Sep 2025 09:07:14 +0100 Subject: [PATCH 07/11] Preview matches theme --- .../ui/screen/accounts/AccountManagementScreen.kt | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/app/gamenative/ui/screen/accounts/AccountManagementScreen.kt b/app/src/main/java/app/gamenative/ui/screen/accounts/AccountManagementScreen.kt index b5519ddf8..6d596c366 100644 --- a/app/src/main/java/app/gamenative/ui/screen/accounts/AccountManagementScreen.kt +++ b/app/src/main/java/app/gamenative/ui/screen/accounts/AccountManagementScreen.kt @@ -1,5 +1,6 @@ package app.gamenative.ui.screen.accounts +import android.content.res.Configuration import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.background import androidx.compose.foundation.layout.* @@ -17,6 +18,7 @@ import androidx.compose.ui.unit.dp import androidx.navigation.NavController import androidx.navigation.compose.rememberNavController import app.gamenative.ui.component.topbar.BackButton +import app.gamenative.ui.theme.PluviaTheme import com.alorma.compose.settings.ui.SettingsGroup import com.skydoves.landscapist.ImageOptions import com.skydoves.landscapist.coil.CoilImage @@ -226,9 +228,12 @@ fun AccountSection( } } -@Preview(showBackground = true) +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES or Configuration.UI_MODE_TYPE_NORMAL) +@Preview(device = "spec:width=1920px,height=1080px,dpi=440") // Odin2 Mini @Composable private fun AccountManagementScreenPreview() { - val navController = rememberNavController() - AccountManagementScreen(navController = navController) + PluviaTheme { + val navController = rememberNavController() + AccountManagementScreen(navController = navController) + } } From 088440c600c60e09eedc22ecdf0385f872e31bf9 Mon Sep 17 00:00:00 2001 From: RadicalDog Date: Fri, 5 Sep 2025 09:27:17 +0100 Subject: [PATCH 08/11] Update deprecated icons --- .../ui/screen/accounts/AccountManagementScreen.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/app/gamenative/ui/screen/accounts/AccountManagementScreen.kt b/app/src/main/java/app/gamenative/ui/screen/accounts/AccountManagementScreen.kt index 6d596c366..956c40583 100644 --- a/app/src/main/java/app/gamenative/ui/screen/accounts/AccountManagementScreen.kt +++ b/app/src/main/java/app/gamenative/ui/screen/accounts/AccountManagementScreen.kt @@ -8,6 +8,8 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.Login +import androidx.compose.material.icons.automirrored.filled.Logout import androidx.compose.material.icons.filled.* import androidx.compose.material3.* import androidx.compose.runtime.* @@ -200,7 +202,7 @@ fun AccountSection( strokeWidth = 2.dp, ) } else { - Icon(Icons.Default.Logout, contentDescription = null) + Icon(Icons.AutoMirrored.Filled.Logout, contentDescription = null) } Spacer(modifier = Modifier.width(8.dp)) Text("Sign Out") @@ -218,7 +220,7 @@ fun AccountSection( color = MaterialTheme.colorScheme.onPrimary, ) } else { - Icon(Icons.Default.Login, contentDescription = null) + Icon(Icons.AutoMirrored.Filled.Login, contentDescription = null) } Spacer(modifier = Modifier.width(8.dp)) Text(if (isLoading) "Signing In..." else "Sign In") From 6957a83f1ff0e80b7280ca31ef2a8785594b13bf Mon Sep 17 00:00:00 2001 From: RadicalDog Date: Fri, 5 Sep 2025 09:41:38 +0100 Subject: [PATCH 09/11] Match navigation pattern with the rest of the app --- .../main/java/app/gamenative/ui/PluviaMain.kt | 9 ++++++++- .../accounts/AccountManagementScreen.kt | 19 ++++++++++--------- .../ui/screen/accounts/SteamAccountSection.kt | 14 +++----------- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/app/src/main/java/app/gamenative/ui/PluviaMain.kt b/app/src/main/java/app/gamenative/ui/PluviaMain.kt index 9c9be8e33..3464b923f 100644 --- a/app/src/main/java/app/gamenative/ui/PluviaMain.kt +++ b/app/src/main/java/app/gamenative/ui/PluviaMain.kt @@ -705,7 +705,14 @@ fun PluviaMain( /** Account Management **/ composable(route = PluviaScreen.AccountManagement.route) { - AccountManagementScreen(navController = navController) + AccountManagementScreen( + onNavigateRoute = { + navController.navigate(it) + }, + onBack = { + navController.navigateUp() + }, + ) } /** Library, Downloads, Friends **/ /** Library, Downloads, Friends **/ diff --git a/app/src/main/java/app/gamenative/ui/screen/accounts/AccountManagementScreen.kt b/app/src/main/java/app/gamenative/ui/screen/accounts/AccountManagementScreen.kt index 956c40583..69796f511 100644 --- a/app/src/main/java/app/gamenative/ui/screen/accounts/AccountManagementScreen.kt +++ b/app/src/main/java/app/gamenative/ui/screen/accounts/AccountManagementScreen.kt @@ -17,8 +17,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Brush import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import androidx.navigation.NavController -import androidx.navigation.compose.rememberNavController import app.gamenative.ui.component.topbar.BackButton import app.gamenative.ui.theme.PluviaTheme import com.alorma.compose.settings.ui.SettingsGroup @@ -28,7 +26,8 @@ import com.skydoves.landscapist.coil.CoilImage @OptIn(ExperimentalMaterial3Api::class) @Composable fun AccountManagementScreen( - navController: NavController, + onNavigateRoute: (String) -> Unit, + onBack: () -> Unit, modifier: Modifier = Modifier, ) { val snackBarHostState = remember { SnackbarHostState() } @@ -40,7 +39,7 @@ fun AccountManagementScreen( CenterAlignedTopAppBar( title = { Text(text = "Manage Accounts") }, navigationIcon = { - BackButton(onClick = { navController.popBackStack() }) + BackButton(onClick = { onBack() }) }, ) }, @@ -52,17 +51,17 @@ fun AccountManagementScreen( .fillMaxSize() .verticalScroll(scrollState), ) { - AccountsGroup(navController = navController) + AccountsGroup(onNavigateRoute = onNavigateRoute) } } } @Composable private fun AccountsGroup( - navController: NavController, + onNavigateRoute: (String) -> Unit, ) { SettingsGroup(title = { Text(text = "Accounts") }) { - SteamAccountSection(navController = navController) + SteamAccountSection(onNavigateRoute = onNavigateRoute) // Other account sections (GOG, Epic Games, etc.) } } @@ -235,7 +234,9 @@ fun AccountSection( @Composable private fun AccountManagementScreenPreview() { PluviaTheme { - val navController = rememberNavController() - AccountManagementScreen(navController = navController) + AccountManagementScreen( + onNavigateRoute = {}, + onBack = {}, + ) } } diff --git a/app/src/main/java/app/gamenative/ui/screen/accounts/SteamAccountSection.kt b/app/src/main/java/app/gamenative/ui/screen/accounts/SteamAccountSection.kt index c237bcd3a..36b009416 100644 --- a/app/src/main/java/app/gamenative/ui/screen/accounts/SteamAccountSection.kt +++ b/app/src/main/java/app/gamenative/ui/screen/accounts/SteamAccountSection.kt @@ -2,14 +2,13 @@ package app.gamenative.ui.screen.accounts import androidx.compose.runtime.* import androidx.compose.ui.Modifier -import androidx.navigation.NavController import app.gamenative.service.SteamService import app.gamenative.ui.screen.PluviaScreen import kotlinx.coroutines.launch @Composable fun SteamAccountSection( - navController: NavController, + onNavigateRoute: (String) -> Unit, modifier: Modifier = Modifier, ) { val scope = rememberCoroutineScope() @@ -21,19 +20,12 @@ fun SteamAccountSection( icon = "https://store.steampowered.com/favicon.ico", isLoggedIn = isSteamLoggedIn, username = if (isSteamLoggedIn) "Steam User" else null, - onLogin = { - navController.navigate(PluviaScreen.LoginUser.route) - }, + onLogin = { onNavigateRoute(PluviaScreen.LoginUser.route) }, onLogout = { scope.launch { SteamService.logOut() // Re-navigate to current screen to refresh logged in state - navController.navigate(PluviaScreen.AccountManagement.route) { - popUpTo(PluviaScreen.Home.route) { - inclusive = false - } - launchSingleTop = true - } + onNavigateRoute(PluviaScreen.AccountManagement.route) } }, modifier = modifier, From 4ff7e176c37534da3fd2d3094a56645196e964d7 Mon Sep 17 00:00:00 2001 From: RadicalDog Date: Fri, 5 Sep 2025 17:55:51 +0100 Subject: [PATCH 10/11] Redraw UI on logOut --- .../ui/screen/accounts/SteamAccountSection.kt | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/app/gamenative/ui/screen/accounts/SteamAccountSection.kt b/app/src/main/java/app/gamenative/ui/screen/accounts/SteamAccountSection.kt index 36b009416..2f9c84239 100644 --- a/app/src/main/java/app/gamenative/ui/screen/accounts/SteamAccountSection.kt +++ b/app/src/main/java/app/gamenative/ui/screen/accounts/SteamAccountSection.kt @@ -11,22 +11,18 @@ fun SteamAccountSection( onNavigateRoute: (String) -> Unit, modifier: Modifier = Modifier, ) { - val scope = rememberCoroutineScope() - val isSteamLoggedIn = SteamService.isLoggedIn + val isSteamLoggedIn = remember { mutableStateOf(SteamService.isLoggedIn)} AccountSection( title = "Steam", description = "Access your Steam library and games", icon = "https://store.steampowered.com/favicon.ico", - isLoggedIn = isSteamLoggedIn, - username = if (isSteamLoggedIn) "Steam User" else null, + isLoggedIn = isSteamLoggedIn.value, + username = if (isSteamLoggedIn.value) "Steam User" else null, onLogin = { onNavigateRoute(PluviaScreen.LoginUser.route) }, onLogout = { - scope.launch { - SteamService.logOut() - // Re-navigate to current screen to refresh logged in state - onNavigateRoute(PluviaScreen.AccountManagement.route) - } + SteamService.logOut() + isSteamLoggedIn.value = false // Trigger a redraw }, modifier = modifier, ) From 613c00c208e7cd2dc12eb27b2696fccd96534bc5 Mon Sep 17 00:00:00 2001 From: bart Date: Tue, 16 Sep 2025 21:25:59 +0200 Subject: [PATCH 11/11] Welcome Screen POC commit --- .../main/java/app/gamenative/ui/PluviaMain.kt | 25 ++- .../java/app/gamenative/ui/data/MainState.kt | 1 + .../app/gamenative/ui/model/MainViewModel.kt | 19 +-- .../app/gamenative/ui/screen/PluviaScreen.kt | 1 + .../ui/screen/welcome/WelcomeScreen.kt | 154 ++++++++++++++++++ 5 files changed, 185 insertions(+), 15 deletions(-) create mode 100644 app/src/main/java/app/gamenative/ui/screen/welcome/WelcomeScreen.kt diff --git a/app/src/main/java/app/gamenative/ui/PluviaMain.kt b/app/src/main/java/app/gamenative/ui/PluviaMain.kt index 3464b923f..6d0ced206 100644 --- a/app/src/main/java/app/gamenative/ui/PluviaMain.kt +++ b/app/src/main/java/app/gamenative/ui/PluviaMain.kt @@ -81,6 +81,7 @@ import java.util.Date import java.util.EnumSet import kotlin.reflect.KFunction2 import app.gamenative.ui.screen.accounts.AccountManagementScreen +import app.gamenative.ui.screen.welcome.WelcomeScreen @Composable fun PluviaMain( @@ -201,8 +202,9 @@ fun PluviaMain( when (event.result) { LoginResult.Success -> { if (PluviaApp.xEnvironment == null) { - Timber.i("Navigating to library") - navController.navigate(PluviaScreen.Home.route) + navController.navigate(PluviaScreen.AccountManagement.route) { + popUpTo(PluviaScreen.LoginUser.route) { inclusive = true } + } // If a crash happen, lets not ask for a tip yet. // Instead, ask the user to contribute their issues to be addressed. @@ -309,7 +311,9 @@ fun PluviaMain( isConnecting = true context.startForegroundService(Intent(context, SteamService::class.java)) } - if (SteamService.isLoggedIn && state.currentScreen == PluviaScreen.LoginUser) { + // Only auto-navigate to home if user is logged in, on login screen, AND it's not first launch + if (SteamService.isLoggedIn && state.currentScreen == PluviaScreen.LoginUser && !state.isFirstLaunch) { + Timber.i("DEBUG: Auto-navigation triggered - SteamService.isLoggedIn=${SteamService.isLoggedIn}, currentScreen=${state.currentScreen}, isFirstLaunch=${state.isFirstLaunch}") navController.navigate(PluviaScreen.Home.route) } } @@ -691,9 +695,20 @@ fun PluviaMain( NavHost( navController = navController, - startDestination = PluviaScreen.Home.route, + startDestination = PluviaScreen.Welcome.route, ) { - /** Login **/ + /** Welcome **/ + composable(route = PluviaScreen.Welcome.route) { + WelcomeScreen( + onSetupAccounts = { + navController.navigate(PluviaScreen.AccountManagement.route) + }, + onSkipSignIn = { + navController.navigate(PluviaScreen.Home.route + "?offline=true") + }, + ) + } + /** Login **/ composable(route = PluviaScreen.LoginUser.route) { UserLoginScreen( diff --git a/app/src/main/java/app/gamenative/ui/data/MainState.kt b/app/src/main/java/app/gamenative/ui/data/MainState.kt index fc9ccf133..3864dd859 100644 --- a/app/src/main/java/app/gamenative/ui/data/MainState.kt +++ b/app/src/main/java/app/gamenative/ui/data/MainState.kt @@ -18,4 +18,5 @@ data class MainState( val launchedAppId: String = "", val bootToContainer: Boolean = false, val showBootingSplash: Boolean = false, + val isFirstLaunch: Boolean = true, ) diff --git a/app/src/main/java/app/gamenative/ui/model/MainViewModel.kt b/app/src/main/java/app/gamenative/ui/model/MainViewModel.kt index b34048bb0..642eaa40c 100644 --- a/app/src/main/java/app/gamenative/ui/model/MainViewModel.kt +++ b/app/src/main/java/app/gamenative/ui/model/MainViewModel.kt @@ -131,6 +131,15 @@ class MainViewModel @Inject constructor( _state.update { it.copy(paletteStyle = value) } } } + + _state.update { + it.copy( + isFirstLaunch = true, // Temporarily always true for debugging + isSteamConnected = SteamService.isConnected, + hasCrashedLastStart = PrefManager.recentlyCrashed, + launchedAppId = "", + ) + } } override fun onCleared() { @@ -142,16 +151,6 @@ class MainViewModel @Inject constructor( PluviaApp.events.off(onLoggedOut) } - init { - _state.update { - it.copy( - isSteamConnected = SteamService.isConnected, - hasCrashedLastStart = PrefManager.recentlyCrashed, - launchedAppId = "", - ) - } - } - fun setTheme(value: AppTheme) { appTheme.currentTheme = value } diff --git a/app/src/main/java/app/gamenative/ui/screen/PluviaScreen.kt b/app/src/main/java/app/gamenative/ui/screen/PluviaScreen.kt index 9de6afdce..943571da4 100644 --- a/app/src/main/java/app/gamenative/ui/screen/PluviaScreen.kt +++ b/app/src/main/java/app/gamenative/ui/screen/PluviaScreen.kt @@ -4,6 +4,7 @@ package app.gamenative.ui.screen * Destinations for top level screens, excluding home screen destinations. */ sealed class PluviaScreen(val route: String) { + data object Welcome : PluviaScreen("welcome") data object LoginUser : PluviaScreen("login") data object Home : PluviaScreen("home") data object XServer : PluviaScreen("xserver") diff --git a/app/src/main/java/app/gamenative/ui/screen/welcome/WelcomeScreen.kt b/app/src/main/java/app/gamenative/ui/screen/welcome/WelcomeScreen.kt new file mode 100644 index 000000000..65410d86e --- /dev/null +++ b/app/src/main/java/app/gamenative/ui/screen/welcome/WelcomeScreen.kt @@ -0,0 +1,154 @@ +package app.gamenative.ui.screen.welcome + +import android.content.res.Configuration +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import app.gamenative.R +import app.gamenative.ui.theme.PluviaTheme + +@Composable +fun WelcomeScreen( + onSetupAccounts: () -> Unit, + onSkipSignIn: () -> Unit, + modifier: Modifier = Modifier, +) { + val scrollState = rememberScrollState() + + Box( + modifier = modifier + .fillMaxSize() + .background(MaterialTheme.colorScheme.background) + .verticalScroll(scrollState) + .padding(24.dp), + contentAlignment = Alignment.Center, + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(32.dp), + modifier = Modifier.fillMaxWidth(), + ) { + // Logo and branding - using the exact styling you provided + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(16.dp), + ) { + // Brand name with the exact styling you specified + Text( + text = "GameNative", + style = MaterialTheme.typography.headlineLarge.copy( + fontWeight = FontWeight.Bold, + brush = Brush.horizontalGradient( + colors = listOf( + MaterialTheme.colorScheme.primary, + MaterialTheme.colorScheme.tertiary, + ), + ), + ), + ) + } + + Spacer(modifier = Modifier.height(16.dp)) + + // Welcome content + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(16.dp), + ) { + Text( + text = "Get Started", + style = MaterialTheme.typography.headlineMedium.copy( + fontWeight = FontWeight.SemiBold, + ), + color = MaterialTheme.colorScheme.onBackground, + ) + + Text( + text = "Welcome to the ultimate PC gaming experience on Android. Let's set up your accounts to get you gaming.", + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.8f), + textAlign = TextAlign.Center, + lineHeight = 24.sp, + ) + } + + Spacer(modifier = Modifier.height(32.dp)) + + // Action buttons + Column( + verticalArrangement = Arrangement.spacedBy(12.dp), + modifier = Modifier.fillMaxWidth(), + ) { + // Set up accounts button (primary) + Button( + onClick = onSetupAccounts, + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(12.dp), + colors = ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.primary, + contentColor = MaterialTheme.colorScheme.onPrimary, + ), + contentPadding = PaddingValues(vertical = 16.dp), + ) { + Text( + text = "Set Up Accounts", + style = MaterialTheme.typography.titleMedium.copy( + fontWeight = FontWeight.SemiBold, + ), + ) + } + + // Skip sign in button (secondary) + OutlinedButton( + onClick = onSkipSignIn, + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(12.dp), + colors = ButtonDefaults.outlinedButtonColors( + contentColor = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.8f), + ), + border = ButtonDefaults.outlinedButtonBorder.copy( + brush = Brush.linearGradient( + colors = listOf( + MaterialTheme.colorScheme.outline.copy(alpha = 0.5f), + MaterialTheme.colorScheme.outline.copy(alpha = 0.3f), + ), + ), + ), + contentPadding = PaddingValues(vertical = 16.dp), + ) { + Text( + text = "Skip Sign In", + style = MaterialTheme.typography.titleMedium, + ) + } + } + } + } +} + +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES or Configuration.UI_MODE_TYPE_NORMAL) +@Preview(device = "spec:width=1920px,height=1080px,dpi=440") // Odin2 Mini +@Composable +private fun WelcomeScreenPreview() { + PluviaTheme { + WelcomeScreen( + onSetupAccounts = {}, + onSkipSignIn = {}, + ) + } +}