diff --git a/android/app/src/main/java/com/devlab/app/MainActivity.kt b/android/app/src/main/java/com/devlab/app/MainActivity.kt index a2b25ac..79b4b8a 100644 --- a/android/app/src/main/java/com/devlab/app/MainActivity.kt +++ b/android/app/src/main/java/com/devlab/app/MainActivity.kt @@ -299,7 +299,14 @@ fun AppRoot() { composable(Screen.Profile.route) { ProfileScreen( - onBack = { navController.popBackStack() } + onBack = { navController.popBackStack() }, + onLogout = { + loginViewModel.logout() + isLoggedIn = false + navController.navigate(Screen.Login.route) { + popUpTo(0) { inclusive = true } + } + } ) } diff --git a/android/app/src/main/java/com/devlab/app/ui/dashboard/DashboardScreen.kt b/android/app/src/main/java/com/devlab/app/ui/dashboard/DashboardScreen.kt index b71264b..05548d1 100644 --- a/android/app/src/main/java/com/devlab/app/ui/dashboard/DashboardScreen.kt +++ b/android/app/src/main/java/com/devlab/app/ui/dashboard/DashboardScreen.kt @@ -1,7 +1,9 @@ package com.devlab.app.ui.dashboard +import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.* @@ -9,7 +11,8 @@ import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Brush import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle @@ -28,33 +31,63 @@ fun DashboardScreen( viewModel: DashboardViewModel = viewModel() ) { val state by viewModel.uiState.collectAsStateWithLifecycle() - val context = LocalContext.current + val context = androidx.compose.ui.platform.LocalContext.current val appVersion = remember { try { - context.packageManager.getPackageInfo(context.packageName, 0).versionName ?: "1.2.0" - } catch (_: Exception) { "1.2.0" } + context.packageManager.getPackageInfo(context.packageName, 0).versionName ?: "1.3.0" + } catch (_: Exception) { "1.3.0" } } + val initial = state.username.firstOrNull()?.uppercase() ?: "?" - Scaffold( - topBar = { - TopAppBar( - title = { - Column { - Text("Dashboard") - if (state.username.isNotBlank()) { + Box(modifier = Modifier.fillMaxSize()) { + Column(modifier = Modifier.fillMaxSize()) { + Box( + modifier = Modifier + .fillMaxWidth() + .background( + Brush.verticalGradient( + colors = listOf( + MaterialTheme.colorScheme.primary, + MaterialTheme.colorScheme.primary.copy(alpha = 0.85f) + ) + ) + ) + .padding(top = 48.dp, start = 16.dp, end = 16.dp, bottom = 24.dp) + ) { + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + Surface( + modifier = Modifier.size(44.dp), + shape = CircleShape, + color = MaterialTheme.colorScheme.onPrimary.copy(alpha = 0.2f) + ) { + Box(contentAlignment = Alignment.Center) { Text( - text = "Welcome, ${state.username}", - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onPrimary.copy(alpha = 0.8f) + text = initial, + color = MaterialTheme.colorScheme.onPrimary, + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold + ) + } + } + Spacer(modifier = Modifier.width(12.dp)) + Column(modifier = Modifier.weight(1f)) { + Text( + text = "Dashboard", + color = MaterialTheme.colorScheme.onPrimary, + style = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.Bold + ) + if (state.username.isNotBlank()) { + Text( + text = "Welcome, ${state.username}", + color = MaterialTheme.colorScheme.onPrimary.copy(alpha = 0.8f), + style = MaterialTheme.typography.bodyMedium ) } } - }, - colors = TopAppBarDefaults.topAppBarColors( - containerColor = MaterialTheme.colorScheme.primary, - titleContentColor = MaterialTheme.colorScheme.onPrimary - ), - actions = { IconButton(onClick = { onProfileClick() }) { Icon( Icons.Default.AccountCircle, @@ -70,125 +103,50 @@ fun DashboardScreen( ) } } - ) - } - ) { padding -> - when { - state.isLoading -> { - LoadingIndicator(modifier = Modifier.padding(padding)) } - state.error != null -> { - Box( - modifier = Modifier - .fillMaxSize() - .padding(padding), - contentAlignment = Alignment.Center - ) { - Column(horizontalAlignment = Alignment.CenterHorizontally) { - Icon( - Icons.Default.ErrorOutline, - contentDescription = null, - modifier = Modifier.size(48.dp), - tint = MaterialTheme.colorScheme.error - ) - Spacer(modifier = Modifier.height(16.dp)) - Text( - text = state.error ?: "", - color = MaterialTheme.colorScheme.error, - style = MaterialTheme.typography.bodyLarge - ) - Spacer(modifier = Modifier.height(16.dp)) - OutlinedButton(onClick = { viewModel.loadStats() }) { - Text("Retry") + + when { + state.isLoading -> LoadingIndicator() + state.error != null -> { + Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Icon(Icons.Default.ErrorOutline, contentDescription = null, modifier = Modifier.size(48.dp), tint = MaterialTheme.colorScheme.error) + Spacer(modifier = Modifier.height(16.dp)) + Text(text = state.error ?: "", color = MaterialTheme.colorScheme.error, style = MaterialTheme.typography.bodyLarge) + Spacer(modifier = Modifier.height(16.dp)) + OutlinedButton(onClick = { viewModel.loadStats() }) { Text("Retry") } } } } - } - else -> { - val dashboardStats = state.stats - - Column( - modifier = Modifier - .fillMaxSize() - .padding(padding) - .verticalScroll(rememberScrollState()) - .padding(16.dp) - ) { - Text( - text = "ServerManager v$appVersion", - style = MaterialTheme.typography.labelLarge, - color = MaterialTheme.colorScheme.onSurfaceVariant - ) - Spacer(modifier = Modifier.height(16.dp)) - - Text( - text = "Servers", - style = MaterialTheme.typography.titleLarge, - fontWeight = FontWeight.SemiBold - ) - Spacer(modifier = Modifier.height(12.dp)) - - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(12.dp) + else -> { + val dashboardStats = state.stats + Column( + modifier = Modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()) + .padding(16.dp) ) { - StatCard( - title = "Total", - value = "${dashboardStats?.stats?.total_servers ?: 0}", - color = MaterialTheme.colorScheme.primary, - icon = Icons.Default.Dns, - modifier = Modifier.weight(1f) - ) - StatCard( - title = "Online", - value = "${dashboardStats?.stats?.online_servers ?: 0}", - color = Green500, - icon = Icons.Default.CheckCircle, - modifier = Modifier.weight(1f) - ) - StatCard( - title = "Offline", - value = "${dashboardStats?.stats?.offline_servers ?: 0}", - color = Red500, - icon = Icons.Default.Cancel, - modifier = Modifier.weight(1f) - ) - } - - Spacer(modifier = Modifier.height(24.dp)) - - Text( - text = "Average Usage", - style = MaterialTheme.typography.titleLarge, - fontWeight = FontWeight.SemiBold - ) - Spacer(modifier = Modifier.height(12.dp)) - - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(12.dp) - ) { - StatCard( - title = "CPU", - value = "${dashboardStats?.stats?.avg_cpu?.toInt() ?: 0}%", - color = Orange500, - icon = Icons.Default.Memory, - modifier = Modifier.weight(1f) - ) - StatCard( - title = "RAM", - value = "${dashboardStats?.stats?.avg_ram?.toInt() ?: 0}%", - color = Blue700, - icon = Icons.Default.Storage, - modifier = Modifier.weight(1f) - ) - StatCard( - title = "Disk", - value = "${dashboardStats?.stats?.avg_disk?.toInt() ?: 0}%", - color = Green500, - icon = Icons.Default.SdStorage, - modifier = Modifier.weight(1f) + Text( + text = "ServerManager v$appVersion", + style = MaterialTheme.typography.labelLarge, + color = MaterialTheme.colorScheme.onSurfaceVariant ) + Spacer(modifier = Modifier.height(16.dp)) + Text(text = "Servers", style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.SemiBold) + Spacer(modifier = Modifier.height(12.dp)) + Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(12.dp)) { + StatCard(title = "Total", value = "${dashboardStats?.stats?.total_servers ?: 0}", color = MaterialTheme.colorScheme.primary, icon = Icons.Default.Dns, modifier = Modifier.weight(1f)) + StatCard(title = "Online", value = "${dashboardStats?.stats?.online_servers ?: 0}", color = Green500, icon = Icons.Default.CheckCircle, modifier = Modifier.weight(1f)) + StatCard(title = "Offline", value = "${dashboardStats?.stats?.offline_servers ?: 0}", color = Red500, icon = Icons.Default.Cancel, modifier = Modifier.weight(1f)) + } + Spacer(modifier = Modifier.height(24.dp)) + Text(text = "Average Usage", style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.SemiBold) + Spacer(modifier = Modifier.height(12.dp)) + Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(12.dp)) { + StatCard(title = "CPU", value = "${dashboardStats?.stats?.avg_cpu?.toInt() ?: 0}%", color = Orange500, icon = Icons.Default.Memory, modifier = Modifier.weight(1f)) + StatCard(title = "RAM", value = "${dashboardStats?.stats?.avg_ram?.toInt() ?: 0}%", color = Blue700, icon = Icons.Default.Storage, modifier = Modifier.weight(1f)) + StatCard(title = "Disk", value = "${dashboardStats?.stats?.avg_disk?.toInt() ?: 0}%", color = Green500, icon = Icons.Default.SdStorage, modifier = Modifier.weight(1f)) + } } } } diff --git a/android/app/src/main/java/com/devlab/app/ui/notifications/NotificationsScreen.kt b/android/app/src/main/java/com/devlab/app/ui/notifications/NotificationsScreen.kt index 036014c..fe743e1 100644 --- a/android/app/src/main/java/com/devlab/app/ui/notifications/NotificationsScreen.kt +++ b/android/app/src/main/java/com/devlab/app/ui/notifications/NotificationsScreen.kt @@ -4,6 +4,7 @@ import androidx.compose.animation.* import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* +import androidx.compose.ui.graphics.Brush import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState @@ -69,8 +70,16 @@ fun NotificationsScreen( } } }, + modifier = Modifier.background( + androidx.compose.ui.graphics.Brush.horizontalGradient( + colors = listOf( + MaterialTheme.colorScheme.primary, + MaterialTheme.colorScheme.primary.copy(alpha = 0.85f) + ) + ) + ), colors = TopAppBarDefaults.topAppBarColors( - containerColor = MaterialTheme.colorScheme.primary, + containerColor = androidx.compose.ui.graphics.Color.Transparent, titleContentColor = MaterialTheme.colorScheme.onPrimary, navigationIconContentColor = MaterialTheme.colorScheme.onPrimary ) diff --git a/android/app/src/main/java/com/devlab/app/ui/profile/ProfileScreen.kt b/android/app/src/main/java/com/devlab/app/ui/profile/ProfileScreen.kt index 404b320..6689ac8 100644 --- a/android/app/src/main/java/com/devlab/app/ui/profile/ProfileScreen.kt +++ b/android/app/src/main/java/com/devlab/app/ui/profile/ProfileScreen.kt @@ -24,6 +24,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel @Composable fun ProfileScreen( onBack: () -> Unit, + onLogout: () -> Unit = {}, viewModel: ProfileViewModel = viewModel() ) { val state by viewModel.uiState.collectAsStateWithLifecycle() @@ -299,6 +300,26 @@ fun ProfileScreen( Text("Save Changes", fontWeight = FontWeight.SemiBold) } } + + Spacer(modifier = Modifier.height(32.dp)) + + OutlinedButton( + onClick = onLogout, + modifier = Modifier.fillMaxWidth().height(50.dp), + colors = ButtonDefaults.outlinedButtonColors( + contentColor = MaterialTheme.colorScheme.error + ) + ) { + Icon( + Icons.Default.Logout, + contentDescription = null, + modifier = Modifier.size(20.dp) + ) + Spacer(modifier = Modifier.width(8.dp)) + Text("Sign Out", fontWeight = FontWeight.SemiBold) + } + + Spacer(modifier = Modifier.height(24.dp)) } } } diff --git a/android/app/src/main/java/com/devlab/app/ui/servers/ServerDetailScreen.kt b/android/app/src/main/java/com/devlab/app/ui/servers/ServerDetailScreen.kt index fb633f8..f07d7d4 100644 --- a/android/app/src/main/java/com/devlab/app/ui/servers/ServerDetailScreen.kt +++ b/android/app/src/main/java/com/devlab/app/ui/servers/ServerDetailScreen.kt @@ -3,6 +3,7 @@ package com.devlab.app.ui.servers import androidx.compose.animation.* import androidx.compose.foundation.background import androidx.compose.foundation.layout.* +import androidx.compose.ui.graphics.Brush import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll @@ -48,6 +49,14 @@ fun ServerDetailScreen( containerColor = MaterialTheme.colorScheme.primary, titleContentColor = MaterialTheme.colorScheme.onPrimary, navigationIconContentColor = MaterialTheme.colorScheme.onPrimary + ), + modifier = Modifier.background( + Brush.horizontalGradient( + colors = listOf( + MaterialTheme.colorScheme.primary, + MaterialTheme.colorScheme.primary.copy(alpha = 0.85f) + ) + ) ) ) } @@ -72,11 +81,12 @@ fun ServerDetailScreen( } else -> { Column(modifier = Modifier.fillMaxSize().padding(padding)) { - TabRow( + ScrollableTabRow( selectedTabIndex = state.selectedTab, containerColor = MaterialTheme.colorScheme.surface, contentColor = MaterialTheme.colorScheme.primary, - divider = {} + divider = {}, + edgePadding = 8.dp ) { Tab( selected = state.selectedTab == 0, diff --git a/public/sysadmin.apk b/public/sysadmin.apk index fdd1af8..4c31abe 100644 Binary files a/public/sysadmin.apk and b/public/sysadmin.apk differ