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