Merge pull request 'Android redesign: v1.4.0' (#28) from feat/android-app-redesign into master
Reviewed-on: #28 Reviewed-by: rafaga21 <rafaelminaya20@hotmail.com>
This commit was merged in pull request #28.
This commit is contained in:
@@ -12,8 +12,8 @@ android {
|
|||||||
applicationId = "com.devlab.app"
|
applicationId = "com.devlab.app"
|
||||||
minSdk = 26
|
minSdk = 26
|
||||||
targetSdk = 37
|
targetSdk = 37
|
||||||
versionCode = 4
|
versionCode = 5
|
||||||
versionName = "1.3.0"
|
versionName = "1.4.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
|
|||||||
@@ -299,7 +299,14 @@ fun AppRoot() {
|
|||||||
|
|
||||||
composable(Screen.Profile.route) {
|
composable(Screen.Profile.route) {
|
||||||
ProfileScreen(
|
ProfileScreen(
|
||||||
onBack = { navController.popBackStack() }
|
onBack = { navController.popBackStack() },
|
||||||
|
onLogout = {
|
||||||
|
loginViewModel.logout()
|
||||||
|
isLoggedIn = false
|
||||||
|
navController.navigate(Screen.Login.route) {
|
||||||
|
popUpTo(0) { inclusive = true }
|
||||||
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package com.devlab.app.ui.dashboard
|
package com.devlab.app.ui.dashboard
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.*
|
import androidx.compose.material.icons.filled.*
|
||||||
@@ -9,7 +11,8 @@ import androidx.compose.material3.*
|
|||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
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.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
@@ -28,33 +31,63 @@ fun DashboardScreen(
|
|||||||
viewModel: DashboardViewModel = viewModel()
|
viewModel: DashboardViewModel = viewModel()
|
||||||
) {
|
) {
|
||||||
val state by viewModel.uiState.collectAsStateWithLifecycle()
|
val state by viewModel.uiState.collectAsStateWithLifecycle()
|
||||||
val context = LocalContext.current
|
val context = androidx.compose.ui.platform.LocalContext.current
|
||||||
val appVersion = remember {
|
val appVersion = remember {
|
||||||
try {
|
try {
|
||||||
context.packageManager.getPackageInfo(context.packageName, 0).versionName ?: "1.2.0"
|
context.packageManager.getPackageInfo(context.packageName, 0).versionName ?: "1.3.0"
|
||||||
} catch (_: Exception) { "1.2.0" }
|
} catch (_: Exception) { "1.3.0" }
|
||||||
}
|
}
|
||||||
|
val initial = state.username.firstOrNull()?.uppercase() ?: "?"
|
||||||
|
|
||||||
Scaffold(
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
topBar = {
|
Column(modifier = Modifier.fillMaxSize()) {
|
||||||
TopAppBar(
|
Box(
|
||||||
title = {
|
modifier = Modifier
|
||||||
Column {
|
.fillMaxWidth()
|
||||||
Text("Dashboard")
|
.background(
|
||||||
if (state.username.isNotBlank()) {
|
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(
|
||||||
text = "Welcome, ${state.username}",
|
text = initial,
|
||||||
style = MaterialTheme.typography.bodySmall,
|
color = MaterialTheme.colorScheme.onPrimary,
|
||||||
color = MaterialTheme.colorScheme.onPrimary.copy(alpha = 0.8f)
|
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() }) {
|
IconButton(onClick = { onProfileClick() }) {
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Default.AccountCircle,
|
Icons.Default.AccountCircle,
|
||||||
@@ -70,47 +103,26 @@ fun DashboardScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
|
||||||
}
|
}
|
||||||
) { padding ->
|
|
||||||
when {
|
when {
|
||||||
state.isLoading -> {
|
state.isLoading -> LoadingIndicator()
|
||||||
LoadingIndicator(modifier = Modifier.padding(padding))
|
|
||||||
}
|
|
||||||
state.error != null -> {
|
state.error != null -> {
|
||||||
Box(
|
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.padding(padding),
|
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
|
||||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
Icon(
|
Icon(Icons.Default.ErrorOutline, contentDescription = null, modifier = Modifier.size(48.dp), tint = MaterialTheme.colorScheme.error)
|
||||||
Icons.Default.ErrorOutline,
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.size(48.dp),
|
|
||||||
tint = MaterialTheme.colorScheme.error
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
Text(
|
Text(text = state.error ?: "", color = MaterialTheme.colorScheme.error, style = MaterialTheme.typography.bodyLarge)
|
||||||
text = state.error ?: "",
|
|
||||||
color = MaterialTheme.colorScheme.error,
|
|
||||||
style = MaterialTheme.typography.bodyLarge
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
OutlinedButton(onClick = { viewModel.loadStats() }) {
|
OutlinedButton(onClick = { viewModel.loadStats() }) { Text("Retry") }
|
||||||
Text("Retry")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
val dashboardStats = state.stats
|
val dashboardStats = state.stats
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(padding)
|
|
||||||
.verticalScroll(rememberScrollState())
|
.verticalScroll(rememberScrollState())
|
||||||
.padding(16.dp)
|
.padding(16.dp)
|
||||||
) {
|
) {
|
||||||
@@ -120,75 +132,21 @@ fun DashboardScreen(
|
|||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
Text(text = "Servers", style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.SemiBold)
|
||||||
Text(
|
|
||||||
text = "Servers",
|
|
||||||
style = MaterialTheme.typography.titleLarge,
|
|
||||||
fontWeight = FontWeight.SemiBold
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||||
Row(
|
StatCard(title = "Total", value = "${dashboardStats?.stats?.total_servers ?: 0}", color = MaterialTheme.colorScheme.primary, icon = Icons.Default.Dns, modifier = Modifier.weight(1f))
|
||||||
modifier = Modifier.fillMaxWidth(),
|
StatCard(title = "Online", value = "${dashboardStats?.stats?.online_servers ?: 0}", color = Green500, icon = Icons.Default.CheckCircle, modifier = Modifier.weight(1f))
|
||||||
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
StatCard(title = "Offline", value = "${dashboardStats?.stats?.offline_servers ?: 0}", color = Red500, icon = Icons.Default.Cancel, modifier = Modifier.weight(1f))
|
||||||
) {
|
|
||||||
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))
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
Text(text = "Average Usage", style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.SemiBold)
|
||||||
Text(
|
|
||||||
text = "Average Usage",
|
|
||||||
style = MaterialTheme.typography.titleLarge,
|
|
||||||
fontWeight = FontWeight.SemiBold
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||||
Row(
|
StatCard(title = "CPU", value = "${dashboardStats?.stats?.avg_cpu?.toInt() ?: 0}%", color = Orange500, icon = Icons.Default.Memory, modifier = Modifier.weight(1f))
|
||||||
modifier = Modifier.fillMaxWidth(),
|
StatCard(title = "RAM", value = "${dashboardStats?.stats?.avg_ram?.toInt() ?: 0}%", color = Blue700, icon = Icons.Default.Storage, modifier = Modifier.weight(1f))
|
||||||
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
StatCard(title = "Disk", value = "${dashboardStats?.stats?.avg_disk?.toInt() ?: 0}%", color = Green500, icon = Icons.Default.SdStorage, modifier = Modifier.weight(1f))
|
||||||
) {
|
}
|
||||||
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)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import androidx.compose.animation.*
|
|||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.ui.graphics.Brush
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
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(
|
colors = TopAppBarDefaults.topAppBarColors(
|
||||||
containerColor = MaterialTheme.colorScheme.primary,
|
containerColor = androidx.compose.ui.graphics.Color.Transparent,
|
||||||
titleContentColor = MaterialTheme.colorScheme.onPrimary,
|
titleContentColor = MaterialTheme.colorScheme.onPrimary,
|
||||||
navigationIconContentColor = MaterialTheme.colorScheme.onPrimary
|
navigationIconContentColor = MaterialTheme.colorScheme.onPrimary
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel
|
|||||||
@Composable
|
@Composable
|
||||||
fun ProfileScreen(
|
fun ProfileScreen(
|
||||||
onBack: () -> Unit,
|
onBack: () -> Unit,
|
||||||
|
onLogout: () -> Unit = {},
|
||||||
viewModel: ProfileViewModel = viewModel()
|
viewModel: ProfileViewModel = viewModel()
|
||||||
) {
|
) {
|
||||||
val state by viewModel.uiState.collectAsStateWithLifecycle()
|
val state by viewModel.uiState.collectAsStateWithLifecycle()
|
||||||
@@ -299,6 +300,26 @@ fun ProfileScreen(
|
|||||||
Text("Save Changes", fontWeight = FontWeight.SemiBold)
|
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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.devlab.app.ui.servers
|
|||||||
import androidx.compose.animation.*
|
import androidx.compose.animation.*
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.ui.graphics.Brush
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
@@ -48,6 +49,14 @@ fun ServerDetailScreen(
|
|||||||
containerColor = MaterialTheme.colorScheme.primary,
|
containerColor = MaterialTheme.colorScheme.primary,
|
||||||
titleContentColor = MaterialTheme.colorScheme.onPrimary,
|
titleContentColor = MaterialTheme.colorScheme.onPrimary,
|
||||||
navigationIconContentColor = 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 -> {
|
else -> {
|
||||||
Column(modifier = Modifier.fillMaxSize().padding(padding)) {
|
Column(modifier = Modifier.fillMaxSize().padding(padding)) {
|
||||||
TabRow(
|
ScrollableTabRow(
|
||||||
selectedTabIndex = state.selectedTab,
|
selectedTabIndex = state.selectedTab,
|
||||||
containerColor = MaterialTheme.colorScheme.surface,
|
containerColor = MaterialTheme.colorScheme.surface,
|
||||||
contentColor = MaterialTheme.colorScheme.primary,
|
contentColor = MaterialTheme.colorScheme.primary,
|
||||||
divider = {}
|
divider = {},
|
||||||
|
edgePadding = 8.dp
|
||||||
) {
|
) {
|
||||||
Tab(
|
Tab(
|
||||||
selected = state.selectedTab == 0,
|
selected = state.selectedTab == 0,
|
||||||
|
|||||||
Binary file not shown.
Reference in New Issue
Block a user