Auto commit: 2026-02-25T07:05:45.288Z
This commit is contained in:
parent
c16e47476d
commit
41f618b155
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="#1A2130" />
|
||||
<corners android:radius="12dp" />
|
||||
<solid android:color="#131B2F" />
|
||||
<corners android:radius="16dp" />
|
||||
<stroke android:width="2dp" android:color="#FFFFFF" />
|
||||
</shape>
|
||||
</shape>
|
||||
@ -4,22 +4,27 @@
|
||||
android:layout_height="match_parent"
|
||||
android:background="@drawable/widget_background"
|
||||
android:orientation="vertical"
|
||||
android:padding="8dp">
|
||||
android:padding="12dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/widget_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Yumee Panels"
|
||||
android:text="YUMEE PANELS"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textStyle="bold"
|
||||
android:textSize="12sp" />
|
||||
android:textSize="12sp"
|
||||
android:shadowColor="#66FFFFFF"
|
||||
android:shadowDx="0"
|
||||
android:shadowDy="0"
|
||||
android:shadowRadius="10" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical">
|
||||
android:gravity="center_vertical"
|
||||
android:layout_marginTop="8dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
@ -32,25 +37,32 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Online: 0"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textStyle="bold"
|
||||
android:textSize="14sp"
|
||||
android:shadowColor="#FFFFFF"
|
||||
android:shadowColor="#66FFFFFF"
|
||||
android:shadowDx="0"
|
||||
android:shadowDy="0"
|
||||
android:shadowRadius="5" />
|
||||
android:shadowRadius="10" />
|
||||
<TextView
|
||||
android:id="@+id/widget_offline_count"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Offline: 0"
|
||||
android:textColor="#FF4444"
|
||||
android:textSize="14sp" />
|
||||
android:textColor="#FF4D4D"
|
||||
android:textStyle="bold"
|
||||
android:textSize="14sp"
|
||||
android:shadowColor="#66FF4D4D"
|
||||
android:shadowDx="0"
|
||||
android:shadowDy="0"
|
||||
android:shadowRadius="10" />
|
||||
</LinearLayout>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/widget_refresh"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_width="28dp"
|
||||
android:layout_height="28dp"
|
||||
android:src="@android:drawable/stat_notify_sync"
|
||||
android:contentDescription="Refresh" />
|
||||
android:contentDescription="Refresh"
|
||||
android:tint="#FFFFFF" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user