diff --git a/android-yumee-panels/app/src/main/kotlin/com/yumee/panels/MainActivity.kt b/android-yumee-panels/app/src/main/kotlin/com/yumee/panels/MainActivity.kt index b8229d4..4977143 100644 --- a/android-yumee-panels/app/src/main/kotlin/com/yumee/panels/MainActivity.kt +++ b/android-yumee-panels/app/src/main/kotlin/com/yumee/panels/MainActivity.kt @@ -4,25 +4,31 @@ import android.content.* import android.os.* import androidx.activity.ComponentActivity import androidx.activity.compose.setContent +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.slideInVertically import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.* +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.room.Room import com.yumee.panels.data.local.* import com.yumee.panels.service.MonitorService import com.yumee.panels.ui.theme.* import com.yumee.panels.ui.components.* -import kotlinx.coroutines.flow.collect class MainActivity : ComponentActivity() { private lateinit var db: YumeeDatabase override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - db = Room.databaseBuilder(applicationContext, YumeeDatabase::class.java, "yumee_db").build() + db = YumeeDatabase.getDatabase(applicationContext) setContent { YumeePanelsTheme { @@ -38,24 +44,55 @@ class MainActivity : ComponentActivity() { @Composable fun DashboardScreen(db: YumeeDatabase, onStartService: () -> Unit) { val monitors by db.monitorDao().getAllMonitors().collectAsState(initial = emptyList()) + var isVisible by remember { mutableStateOf(false) } + + LaunchedEffect(Unit) { + isVisible = true + } Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) { - Column(modifier = Modifier.padding(16.dp)) { - Text("YUMEE PANELS", style = MaterialTheme.typography.headlineLarge, color = NeonWhite) - Spacer(modifier = Modifier.height(16.dp)) + Column(modifier = Modifier.padding(24.dp)) { + Text( + text = "YUMEE PANELS", + style = MaterialTheme.typography.headlineLarge.copy(fontWeight = FontWeight.Bold), + color = NeonWhite, + modifier = Modifier.padding(bottom = 8.dp) + ) + + Text( + text = "REALTIME DASHBOARD", + style = MaterialTheme.typography.labelMedium.copy(letterSpacing = 2.dp), + color = NeonWhite.copy(alpha = 0.6f) + ) + + Spacer(modifier = Modifier.height(32.dp)) Button( onClick = onStartService, - colors = ButtonDefaults.buttonColors(containerColor = NeonWhite, contentColor = DarkerBlue) + colors = ButtonDefaults.buttonColors(containerColor = NeonWhite, contentColor = DarkPastelBlue), + shape = RoundedCornerShape(12.dp), + modifier = Modifier + .fillMaxWidth() + .height(56.dp) + .shadow(8.dp, RoundedCornerShape(12.dp), spotColor = NeonWhiteGlow, ambientColor = NeonWhiteGlow) ) { - Text("START MONITORING SERVICE") + Text("START MONITORING", style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.Bold)) } - Spacer(modifier = Modifier.height(24.dp)) + Spacer(modifier = Modifier.height(32.dp)) - LazyColumn { + LazyColumn( + contentPadding = PaddingValues(bottom = 16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { items(monitors.size) { index -> - MonitorItem(monitors[index]) + AnimatedVisibility( + visible = isVisible, + enter = fadeIn(animationSpec = tween(500, delayMillis = index * 100)) + + slideInVertically(initialOffsetY = { 50 }, animationSpec = tween(500, delayMillis = index * 100)) + ) { + MonitorItem(monitors[index], db) + } } } } @@ -63,23 +100,70 @@ fun DashboardScreen(db: YumeeDatabase, onStartService: () -> Unit) { } @Composable -fun MonitorItem(monitor: Monitor) { +fun MonitorItem(monitor: Monitor, db: YumeeDatabase) { + val logs by db.monitorDao().getLogsForMonitor(monitor.id).collectAsState(initial = emptyList()) + Card( - modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp), - colors = CardDefaults.cardColors(containerColor = DarkerBlue) + modifier = Modifier + .fillMaxWidth() + .shadow(6.dp, RoundedCornerShape(16.dp), spotColor = NeonWhiteGlow, ambientColor = NeonWhiteGlow), + shape = RoundedCornerShape(16.dp), + colors = CardDefaults.cardColors(containerColor = PanelBackground) ) { - Row(modifier = Modifier.padding(16.dp), verticalAlignment = Alignment.CenterVertically) { - BatteryHealthBar( - isOnline = monitor.isOnline, - health = if (monitor.isOnline) 100 else 0, - modifier = Modifier.size(width = 40.dp, height = 80.dp) - ) - Spacer(modifier = Modifier.width(16.dp)) - Column { - Text(monitor.name, style = MaterialTheme.typography.titleLarge, color = NeonWhite) - Text(monitor.url, style = MaterialTheme.typography.bodySmall, color = NeonWhite.copy(alpha = 0.7f)) - Text("Status: ${if (monitor.isOnline) "ONLINE" else "OFFLINE"}", color = if (monitor.isOnline) NeonGreen else NeonRed) - Text("Response: ${monitor.lastResponseTime}ms | Code: ${monitor.lastStatusCode}", color = NeonWhite) + Column(modifier = Modifier.padding(20.dp)) { + Row(verticalAlignment = Alignment.CenterVertically) { + BatteryHealthBar( + isOnline = monitor.isOnline, + health = monitor.uptimePercent.toInt().coerceIn(0, 100), + modifier = Modifier.size(width = 44.dp, height = 84.dp) + ) + Spacer(modifier = Modifier.width(24.dp)) + Column(modifier = Modifier.weight(1f)) { + Text( + text = monitor.name, + style = MaterialTheme.typography.titleLarge.copy(fontWeight = FontWeight.SemiBold), + color = NeonWhite + ) + Spacer(modifier = Modifier.height(4.dp)) + Text( + text = monitor.url, + style = MaterialTheme.typography.bodySmall, + color = NeonWhite.copy(alpha = 0.5f) + ) + Spacer(modifier = Modifier.height(12.dp)) + Row( + horizontalArrangement = Arrangement.SpaceBetween, + modifier = Modifier.fillMaxWidth() + ) { + Text( + text = if (monitor.isOnline) "ONLINE" else "OFFLINE", + style = MaterialTheme.typography.labelLarge.copy(fontWeight = FontWeight.Bold), + color = if (monitor.isOnline) NeonWhite else NeonRed + ) + Text( + text = "${monitor.lastResponseTime} ms", + style = MaterialTheme.typography.labelLarge, + color = NeonWhite.copy(alpha = 0.8f) + ) + } + } + } + + // Add CandlestickChart if we have logs + if (logs.isNotEmpty()) { + Spacer(modifier = Modifier.height(20.dp)) + Text( + text = "RESPONSE HISTORY", + style = MaterialTheme.typography.labelSmall.copy(letterSpacing = 1.dp), + color = NeonWhite.copy(alpha = 0.4f), + modifier = Modifier.padding(bottom = 8.dp) + ) + CandlestickChart( + logs = logs.reversed(), // Render chronological since DB query is DESC + modifier = Modifier + .fillMaxWidth() + .height(80.dp) + ) } } } diff --git a/android-yumee-panels/app/src/main/kotlin/com/yumee/panels/ui/components/Visuals.kt b/android-yumee-panels/app/src/main/kotlin/com/yumee/panels/ui/components/Visuals.kt index 210889b..0033ebb 100644 --- a/android-yumee-panels/app/src/main/kotlin/com/yumee/panels/ui/components/Visuals.kt +++ b/android-yumee-panels/app/src/main/kotlin/com/yumee/panels/ui/components/Visuals.kt @@ -1,15 +1,24 @@ package com.yumee.panels.ui.components -import androidx.compose.foundation.* +import androidx.compose.animation.animateColorAsState +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.tween +import androidx.compose.foundation.background +import androidx.compose.foundation.border import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.* +import androidx.compose.ui.geometry.CornerRadius +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.drawscope.Fill import androidx.compose.ui.unit.dp import com.yumee.panels.ui.theme.* +import androidx.compose.foundation.Canvas @Composable fun BatteryHealthBar( @@ -17,22 +26,37 @@ fun BatteryHealthBar( health: Int, // 0 to 100 modifier: Modifier = Modifier ) { - val color = if (isOnline) NeonWhite else NeonRed + val targetColor = if (isOnline) NeonWhite else NeonRed + val targetGlowColor = if (isOnline) NeonWhiteGlow else NeonRedGlow - Column(modifier = modifier) { + val color by animateColorAsState(targetValue = targetColor, animationSpec = tween(500)) + val glowColor by animateColorAsState(targetValue = targetGlowColor, animationSpec = tween(500)) + val animatedHealth by animateFloatAsState(targetValue = health / 100f, animationSpec = tween(800)) + + Box(modifier = modifier, contentAlignment = Alignment.Center) { + // Outer Glow + Box( + modifier = Modifier + .width(44.dp) + .height(84.dp) + .background(glowColor, RoundedCornerShape(8.dp)) + ) + // Battery Container Box( modifier = Modifier .width(40.dp) .height(80.dp) - .border(2.dp, color, RoundedCornerShape(4.dp)) - .padding(4.dp) + .background(DarkPastelBlue, RoundedCornerShape(6.dp)) + .border(2.dp, color, RoundedCornerShape(6.dp)) + .padding(3.dp) ) { + // Battery Level with smooth animation Box( modifier = Modifier .fillMaxWidth() - .fillMaxHeight(health / 100f) + .fillMaxHeight(animatedHealth) .align(Alignment.BottomCenter) - .background(color) + .background(color, RoundedCornerShape(3.dp)) ) } } @@ -51,15 +75,29 @@ fun CandlestickChart( logs.forEachIndexed { index, log -> val color = if (log.isOnline) NeonWhite else NeonRed + val glowColor = if (log.isOnline) NeonWhiteGlow else NeonRedGlow val barHeight = (log.responseTime.toFloat() / maxResponseTime) * height val x = index * barWidth - drawRect( + // Draw glow + drawRoundRect( + color = glowColor, + topLeft = Offset(x + 1f, height - barHeight - 4f), + size = Size(barWidth - 2f, barHeight + 8f), + cornerRadius = CornerRadius(4f, 4f), + alpha = 0.5f, + style = Fill + ) + + // Draw core + drawRoundRect( color = color, - topLeft = Offset(x + 2, height - barHeight), - size = androidx.compose.ui.geometry.Size(barWidth - 4, barHeight), - alpha = 0.8f + topLeft = Offset(x + 2f, height - barHeight), + size = Size(barWidth - 4f, barHeight), + cornerRadius = CornerRadius(2f, 2f), + alpha = 1.0f, + style = Fill ) } } -} +} \ No newline at end of file diff --git a/android-yumee-panels/app/src/main/kotlin/com/yumee/panels/ui/theme/Theme.kt b/android-yumee-panels/app/src/main/kotlin/com/yumee/panels/ui/theme/Theme.kt index b74c655..3a42016 100644 --- a/android-yumee-panels/app/src/main/kotlin/com/yumee/panels/ui/theme/Theme.kt +++ b/android-yumee-panels/app/src/main/kotlin/com/yumee/panels/ui/theme/Theme.kt @@ -1,26 +1,26 @@ package com.yumee.panels.ui.theme -import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.material3.MaterialTheme import androidx.compose.material3.darkColorScheme import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.Color -val DarkPastelBlue = Color(0xFF1B263B) -val DarkerBlue = Color(0xFF0D1B2A) -val NeonWhite = Color(0xFFE0E1DD) -val NeonRed = Color(0xFFFF4D4D) -val NeonGreen = Color(0xFF00FFCC) +val DarkPastelBlue = Color(0xFF131B2F) // Background biru pastel gelap +val PanelBackground = Color(0xFF1B2640) // Background panel sedikit lebih terang +val NeonWhite = Color(0xFFFFFFFF) // Putih neon +val NeonWhiteGlow = Color(0x66FFFFFF) // Glow putih neon +val NeonRed = Color(0xFFFF4D4D) // Merah neon +val NeonRedGlow = Color(0x66FF4D4D) private val DarkColorScheme = darkColorScheme( primary = NeonWhite, secondary = NeonWhite, tertiary = NeonWhite, background = DarkPastelBlue, - surface = DarkerBlue, - onPrimary = DarkerBlue, - onSecondary = DarkerBlue, - onTertiary = DarkerBlue, + surface = PanelBackground, + onPrimary = DarkPastelBlue, + onSecondary = DarkPastelBlue, + onTertiary = DarkPastelBlue, onBackground = NeonWhite, onSurface = NeonWhite, ) @@ -31,7 +31,7 @@ fun YumeePanelsTheme( ) { MaterialTheme( colorScheme = DarkColorScheme, - typography = Typography, + // Using default typography, can be customized later content = content ) -} +} \ No newline at end of file diff --git a/android-yumee-panels/app/src/main/res/drawable/widget_background.xml b/android-yumee-panels/app/src/main/res/drawable/widget_background.xml index ca663bf..7e68da1 100644 --- a/android-yumee-panels/app/src/main/res/drawable/widget_background.xml +++ b/android-yumee-panels/app/src/main/res/drawable/widget_background.xml @@ -1,6 +1,6 @@ - - + + - + \ No newline at end of file diff --git a/android-yumee-panels/app/src/main/res/layout/widget_layout.xml b/android-yumee-panels/app/src/main/res/layout/widget_layout.xml index 87d895d..c3b48ee 100644 --- a/android-yumee-panels/app/src/main/res/layout/widget_layout.xml +++ b/android-yumee-panels/app/src/main/res/layout/widget_layout.xml @@ -4,22 +4,27 @@ android:layout_height="match_parent" android:background="@drawable/widget_background" android:orientation="vertical" - android:padding="8dp"> + android:padding="12dp"> + android:textSize="12sp" + android:shadowColor="#66FFFFFF" + android:shadowDx="0" + android:shadowDy="0" + android:shadowRadius="10" /> + android:gravity="center_vertical" + android:layout_marginTop="8dp"> + android:shadowRadius="10" /> + android:textColor="#FF4D4D" + android:textStyle="bold" + android:textSize="14sp" + android:shadowColor="#66FF4D4D" + android:shadowDx="0" + android:shadowDy="0" + android:shadowRadius="10" /> + android:contentDescription="Refresh" + android:tint="#FFFFFF" /> - + \ No newline at end of file diff --git a/yumee_panels_android_source.tar.gz b/yumee_panels_android_source.tar.gz index 4304298..60d3503 100644 Binary files a/yumee_panels_android_source.tar.gz and b/yumee_panels_android_source.tar.gz differ