Auto commit: 2026-02-25T07:05:45.288Z

This commit is contained in:
Flatlogic Bot 2026-02-25 07:05:45 +00:00
parent c16e47476d
commit 41f618b155
6 changed files with 202 additions and 68 deletions

View File

@ -4,25 +4,31 @@ import android.content.*
import android.os.* import android.os.*
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent 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.layout.*
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.* 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.compose.ui.unit.dp
import androidx.room.Room import androidx.room.Room
import com.yumee.panels.data.local.* import com.yumee.panels.data.local.*
import com.yumee.panels.service.MonitorService import com.yumee.panels.service.MonitorService
import com.yumee.panels.ui.theme.* import com.yumee.panels.ui.theme.*
import com.yumee.panels.ui.components.* import com.yumee.panels.ui.components.*
import kotlinx.coroutines.flow.collect
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
private lateinit var db: YumeeDatabase private lateinit var db: YumeeDatabase
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
db = Room.databaseBuilder(applicationContext, YumeeDatabase::class.java, "yumee_db").build() db = YumeeDatabase.getDatabase(applicationContext)
setContent { setContent {
YumeePanelsTheme { YumeePanelsTheme {
@ -38,24 +44,55 @@ class MainActivity : ComponentActivity() {
@Composable @Composable
fun DashboardScreen(db: YumeeDatabase, onStartService: () -> Unit) { fun DashboardScreen(db: YumeeDatabase, onStartService: () -> Unit) {
val monitors by db.monitorDao().getAllMonitors().collectAsState(initial = emptyList()) 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) { Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
Column(modifier = Modifier.padding(16.dp)) { Column(modifier = Modifier.padding(24.dp)) {
Text("YUMEE PANELS", style = MaterialTheme.typography.headlineLarge, color = NeonWhite) Text(
Spacer(modifier = Modifier.height(16.dp)) 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( Button(
onClick = onStartService, 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 -> 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 @Composable
fun MonitorItem(monitor: Monitor) { fun MonitorItem(monitor: Monitor, db: YumeeDatabase) {
val logs by db.monitorDao().getLogsForMonitor(monitor.id).collectAsState(initial = emptyList())
Card( Card(
modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp), modifier = Modifier
colors = CardDefaults.cardColors(containerColor = DarkerBlue) .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) { Column(modifier = Modifier.padding(20.dp)) {
BatteryHealthBar( Row(verticalAlignment = Alignment.CenterVertically) {
isOnline = monitor.isOnline, BatteryHealthBar(
health = if (monitor.isOnline) 100 else 0, isOnline = monitor.isOnline,
modifier = Modifier.size(width = 40.dp, height = 80.dp) health = monitor.uptimePercent.toInt().coerceIn(0, 100),
) modifier = Modifier.size(width = 44.dp, height = 84.dp)
Spacer(modifier = Modifier.width(16.dp)) )
Column { Spacer(modifier = Modifier.width(24.dp))
Text(monitor.name, style = MaterialTheme.typography.titleLarge, color = NeonWhite) Column(modifier = Modifier.weight(1f)) {
Text(monitor.url, style = MaterialTheme.typography.bodySmall, color = NeonWhite.copy(alpha = 0.7f)) Text(
Text("Status: ${if (monitor.isOnline) "ONLINE" else "OFFLINE"}", color = if (monitor.isOnline) NeonGreen else NeonRed) text = monitor.name,
Text("Response: ${monitor.lastResponseTime}ms | Code: ${monitor.lastStatusCode}", color = NeonWhite) 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)
)
} }
} }
} }

View File

@ -1,15 +1,24 @@
package com.yumee.panels.ui.components 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.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Text import androidx.compose.runtime.*
import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment
import androidx.compose.ui.* import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip 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 androidx.compose.ui.unit.dp
import com.yumee.panels.ui.theme.* import com.yumee.panels.ui.theme.*
import androidx.compose.foundation.Canvas
@Composable @Composable
fun BatteryHealthBar( fun BatteryHealthBar(
@ -17,22 +26,37 @@ fun BatteryHealthBar(
health: Int, // 0 to 100 health: Int, // 0 to 100
modifier: Modifier = Modifier 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( Box(
modifier = Modifier modifier = Modifier
.width(40.dp) .width(40.dp)
.height(80.dp) .height(80.dp)
.border(2.dp, color, RoundedCornerShape(4.dp)) .background(DarkPastelBlue, RoundedCornerShape(6.dp))
.padding(4.dp) .border(2.dp, color, RoundedCornerShape(6.dp))
.padding(3.dp)
) { ) {
// Battery Level with smooth animation
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.fillMaxHeight(health / 100f) .fillMaxHeight(animatedHealth)
.align(Alignment.BottomCenter) .align(Alignment.BottomCenter)
.background(color) .background(color, RoundedCornerShape(3.dp))
) )
} }
} }
@ -51,15 +75,29 @@ fun CandlestickChart(
logs.forEachIndexed { index, log -> logs.forEachIndexed { index, log ->
val color = if (log.isOnline) NeonWhite else NeonRed val color = if (log.isOnline) NeonWhite else NeonRed
val glowColor = if (log.isOnline) NeonWhiteGlow else NeonRedGlow
val barHeight = (log.responseTime.toFloat() / maxResponseTime) * height val barHeight = (log.responseTime.toFloat() / maxResponseTime) * height
val x = index * barWidth 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, color = color,
topLeft = Offset(x + 2, height - barHeight), topLeft = Offset(x + 2f, height - barHeight),
size = androidx.compose.ui.geometry.Size(barWidth - 4, barHeight), size = Size(barWidth - 4f, barHeight),
alpha = 0.8f cornerRadius = CornerRadius(2f, 2f),
alpha = 1.0f,
style = Fill
) )
} }
} }
} }

View File

@ -1,26 +1,26 @@
package com.yumee.panels.ui.theme package com.yumee.panels.ui.theme
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme import androidx.compose.material3.darkColorScheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
val DarkPastelBlue = Color(0xFF1B263B) val DarkPastelBlue = Color(0xFF131B2F) // Background biru pastel gelap
val DarkerBlue = Color(0xFF0D1B2A) val PanelBackground = Color(0xFF1B2640) // Background panel sedikit lebih terang
val NeonWhite = Color(0xFFE0E1DD) val NeonWhite = Color(0xFFFFFFFF) // Putih neon
val NeonRed = Color(0xFFFF4D4D) val NeonWhiteGlow = Color(0x66FFFFFF) // Glow putih neon
val NeonGreen = Color(0xFF00FFCC) val NeonRed = Color(0xFFFF4D4D) // Merah neon
val NeonRedGlow = Color(0x66FF4D4D)
private val DarkColorScheme = darkColorScheme( private val DarkColorScheme = darkColorScheme(
primary = NeonWhite, primary = NeonWhite,
secondary = NeonWhite, secondary = NeonWhite,
tertiary = NeonWhite, tertiary = NeonWhite,
background = DarkPastelBlue, background = DarkPastelBlue,
surface = DarkerBlue, surface = PanelBackground,
onPrimary = DarkerBlue, onPrimary = DarkPastelBlue,
onSecondary = DarkerBlue, onSecondary = DarkPastelBlue,
onTertiary = DarkerBlue, onTertiary = DarkPastelBlue,
onBackground = NeonWhite, onBackground = NeonWhite,
onSurface = NeonWhite, onSurface = NeonWhite,
) )
@ -31,7 +31,7 @@ fun YumeePanelsTheme(
) { ) {
MaterialTheme( MaterialTheme(
colorScheme = DarkColorScheme, colorScheme = DarkColorScheme,
typography = Typography, // Using default typography, can be customized later
content = content content = content
) )
} }

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"> <shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#1A2130" /> <solid android:color="#131B2F" />
<corners android:radius="12dp" /> <corners android:radius="16dp" />
<stroke android:width="2dp" android:color="#FFFFFF" /> <stroke android:width="2dp" android:color="#FFFFFF" />
</shape> </shape>

View File

@ -4,22 +4,27 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@drawable/widget_background" android:background="@drawable/widget_background"
android:orientation="vertical" android:orientation="vertical"
android:padding="8dp"> android:padding="12dp">
<TextView <TextView
android:id="@+id/widget_title" android:id="@+id/widget_title"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Yumee Panels" android:text="YUMEE PANELS"
android:textColor="#FFFFFF" android:textColor="#FFFFFF"
android:textStyle="bold" android:textStyle="bold"
android:textSize="12sp" /> android:textSize="12sp"
android:shadowColor="#66FFFFFF"
android:shadowDx="0"
android:shadowDy="0"
android:shadowRadius="10" />
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="horizontal" android:orientation="horizontal"
android:gravity="center_vertical"> android:gravity="center_vertical"
android:layout_marginTop="8dp">
<LinearLayout <LinearLayout
android:layout_width="0dp" android:layout_width="0dp"
@ -32,25 +37,32 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Online: 0" android:text="Online: 0"
android:textColor="#FFFFFF" android:textColor="#FFFFFF"
android:textStyle="bold"
android:textSize="14sp" android:textSize="14sp"
android:shadowColor="#FFFFFF" android:shadowColor="#66FFFFFF"
android:shadowDx="0" android:shadowDx="0"
android:shadowDy="0" android:shadowDy="0"
android:shadowRadius="5" /> android:shadowRadius="10" />
<TextView <TextView
android:id="@+id/widget_offline_count" android:id="@+id/widget_offline_count"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Offline: 0" android:text="Offline: 0"
android:textColor="#FF4444" android:textColor="#FF4D4D"
android:textSize="14sp" /> android:textStyle="bold"
android:textSize="14sp"
android:shadowColor="#66FF4D4D"
android:shadowDx="0"
android:shadowDy="0"
android:shadowRadius="10" />
</LinearLayout> </LinearLayout>
<ImageView <ImageView
android:id="@+id/widget_refresh" android:id="@+id/widget_refresh"
android:layout_width="24dp" android:layout_width="28dp"
android:layout_height="24dp" android:layout_height="28dp"
android:src="@android:drawable/stat_notify_sync" android:src="@android:drawable/stat_notify_sync"
android:contentDescription="Refresh" /> android:contentDescription="Refresh"
android:tint="#FFFFFF" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>

Binary file not shown.