From 41f618b155572c7cec55a5f35b4933a90d41b1d2 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Wed, 25 Feb 2026 07:05:45 +0000 Subject: [PATCH] Auto commit: 2026-02-25T07:05:45.288Z --- .../kotlin/com/yumee/panels/MainActivity.kt | 134 ++++++++++++++---- .../com/yumee/panels/ui/components/Visuals.kt | 70 ++++++--- .../kotlin/com/yumee/panels/ui/theme/Theme.kt | 24 ++-- .../main/res/drawable/widget_background.xml | 6 +- .../app/src/main/res/layout/widget_layout.xml | 36 +++-- yumee_panels_android_source.tar.gz | Bin 8202 -> 9227 bytes 6 files changed, 202 insertions(+), 68 deletions(-) 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 4304298ecd2889eaae507e77875b804edb5c33fe..60d350358116209a1e4d8553449711bf000fe674 100644 GIT binary patch literal 9227 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