From c16e47476d31e560c789c94f44554fefbc80e829 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Wed, 25 Feb 2026 06:50:45 +0000 Subject: [PATCH] save --- .../app/src/main/AndroidManifest.xml | 17 +++++- .../main/kotlin/com/yumee/panels/YumeeApp.kt | 7 +-- .../com/yumee/panels/data/local/Database.kt | 23 ++++++- .../yumee/panels/service/MonitorService.kt | 28 +++++---- .../com/yumee/panels/widget/MonitorWidget.kt | 56 ++++++++++++++++++ .../main/res/drawable/widget_background.xml | 6 ++ .../app/src/main/res/layout/widget_layout.xml | 56 ++++++++++++++++++ .../app/src/main/res/xml/widget_info.xml | 9 +++ yumee_panels_android_source.tar.gz | Bin 6891 -> 8202 bytes 9 files changed, 182 insertions(+), 20 deletions(-) create mode 100644 android-yumee-panels/app/src/main/kotlin/com/yumee/panels/widget/MonitorWidget.kt create mode 100644 android-yumee-panels/app/src/main/res/drawable/widget_background.xml create mode 100644 android-yumee-panels/app/src/main/res/layout/widget_layout.xml create mode 100644 android-yumee-panels/app/src/main/res/xml/widget_info.xml diff --git a/android-yumee-panels/app/src/main/AndroidManifest.xml b/android-yumee-panels/app/src/main/AndroidManifest.xml index 0d0f015..90da082 100644 --- a/android-yumee-panels/app/src/main/AndroidManifest.xml +++ b/android-yumee-panels/app/src/main/AndroidManifest.xml @@ -13,8 +13,6 @@ + + + + + + + + - + \ No newline at end of file diff --git a/android-yumee-panels/app/src/main/kotlin/com/yumee/panels/YumeeApp.kt b/android-yumee-panels/app/src/main/kotlin/com/yumee/panels/YumeeApp.kt index 7bf35d3..7da0830 100644 --- a/android-yumee-panels/app/src/main/kotlin/com/yumee/panels/YumeeApp.kt +++ b/android-yumee-panels/app/src/main/kotlin/com/yumee/panels/YumeeApp.kt @@ -1,7 +1,6 @@ package com.yumee.panels import android.app.Application -import androidx.room.Room import com.yumee.panels.data.local.YumeeDatabase class YumeeApp : Application() { @@ -11,8 +10,6 @@ class YumeeApp : Application() { override fun onCreate() { super.onCreate() - database = Room.databaseBuilder(this, YumeeDatabase::class.java, "yumee_db") - .fallbackToDestructiveMigration() - .build() + database = YumeeDatabase.getDatabase(this) } -} +} \ No newline at end of file diff --git a/android-yumee-panels/app/src/main/kotlin/com/yumee/panels/data/local/Database.kt b/android-yumee-panels/app/src/main/kotlin/com/yumee/panels/data/local/Database.kt index f1a02b0..a8ca4ba 100644 --- a/android-yumee-panels/app/src/main/kotlin/com/yumee/panels/data/local/Database.kt +++ b/android-yumee-panels/app/src/main/kotlin/com/yumee/panels/data/local/Database.kt @@ -1,5 +1,6 @@ package com.yumee.panels.data.local +import android.content.Context import androidx.room.* import kotlinx.coroutines.flow.Flow @@ -8,6 +9,9 @@ interface MonitorDao { @Query("SELECT * FROM monitors") fun getAllMonitors(): Flow> + @Query("SELECT * FROM monitors") + suspend fun getAllMonitorsSync(): List + @Query("SELECT * FROM monitors WHERE isActive = 1") suspend fun getActiveMonitors(): List @@ -36,4 +40,21 @@ interface MonitorDao { @Database(entities = [Monitor::class, MonitorLog::class], version = 1) abstract class YumeeDatabase : RoomDatabase() { abstract fun monitorDao(): MonitorDao -} + + companion object { + @Volatile + private var INSTANCE: YumeeDatabase? = null + + fun getDatabase(context: Context): YumeeDatabase { + return INSTANCE ?: synchronized(this) { + val instance = Room.databaseBuilder( + context.applicationContext, + YumeeDatabase::class.java, + "yumee_database" + ).build() + INSTANCE = instance + instance + } + } + } +} \ No newline at end of file diff --git a/android-yumee-panels/app/src/main/kotlin/com/yumee/panels/service/MonitorService.kt b/android-yumee-panels/app/src/main/kotlin/com/yumee/panels/service/MonitorService.kt index 99d21c5..409302a 100644 --- a/android-yumee-panels/app/src/main/kotlin/com/yumee/panels/service/MonitorService.kt +++ b/android-yumee-panels/app/src/main/kotlin/com/yumee/panels/service/MonitorService.kt @@ -2,10 +2,8 @@ package com.yumee.panels.service import android.app.* import android.content.* -import android.net.ConnectivityManager import android.os.* import androidx.core.app.NotificationCompat -import androidx.room.Room import com.yumee.panels.MainActivity import com.yumee.panels.R import com.yumee.panels.data.local.* @@ -32,7 +30,7 @@ class MonitorService : Service() { override fun onCreate() { super.onCreate() - db = Room.databaseBuilder(applicationContext, YumeeDatabase::class.java, "yumee_db").build() + db = YumeeDatabase.getDatabase(applicationContext) createNotificationChannel() acquireWakeLock() } @@ -49,18 +47,27 @@ class MonitorService : Service() { serviceScope.launch { while (isActive) { val monitors = db.monitorDao().getActiveMonitors() + var updated = false monitors.forEach { monitor -> - // Simplified: In a real app, track individual interval timers - // Here we check if it's time to ping based on lastChecked if (System.currentTimeMillis() - monitor.lastChecked >= monitor.intervalSeconds * 1000) { - launch { pingMonitor(monitor) } + pingMonitor(monitor) + updated = true } } - delay(1000) // check every second + if (updated) { + updateWidget() + } + delay(1000) } } } + private fun updateWidget() { + val intent = Intent("com.yumee.panels.UPDATE_WIDGET") + intent.setPackage(packageName) + sendBroadcast(intent) + } + private suspend fun pingMonitor(monitor: Monitor) { val startTime = System.currentTimeMillis() var isOnline = false @@ -81,7 +88,6 @@ class MonitorService : Service() { responseTime = System.currentTimeMillis() - startTime } - // Update database val updatedMonitor = monitor.copy( lastStatusCode = statusCode, lastResponseTime = responseTime, @@ -107,7 +113,7 @@ class MonitorService : Service() { private fun sendAlert(name: String, message: String) { val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager val alertNotification = NotificationCompat.Builder(this, CHANNEL_ID) - .setSmallIcon(R.drawable.ic_launcher_foreground) // Placeholder + .setSmallIcon(R.mipmap.ic_launcher) .setContentTitle("Alert: $name") .setContentText(message) .setPriority(NotificationCompat.PRIORITY_HIGH) @@ -122,7 +128,7 @@ class MonitorService : Service() { return NotificationCompat.Builder(this, CHANNEL_ID) .setContentTitle("Yumee Panels") .setContentText(text) - .setSmallIcon(R.drawable.ic_launcher_foreground) // Placeholder + .setSmallIcon(R.mipmap.ic_launcher) .setOngoing(true) .setContentIntent(pendingIntent) .build() @@ -152,4 +158,4 @@ class MonitorService : Service() { wakeLock?.let { if (it.isHeld) it.release() } super.onDestroy() } -} +} \ No newline at end of file diff --git a/android-yumee-panels/app/src/main/kotlin/com/yumee/panels/widget/MonitorWidget.kt b/android-yumee-panels/app/src/main/kotlin/com/yumee/panels/widget/MonitorWidget.kt new file mode 100644 index 0000000..626e1ee --- /dev/null +++ b/android-yumee-panels/app/src/main/kotlin/com/yumee/panels/widget/MonitorWidget.kt @@ -0,0 +1,56 @@ +package com.yumee.panels.widget + +import android.app.PendingIntent +import android.appwidget.AppWidgetManager +import android.appwidget.AppWidgetProvider +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.widget.RemoteViews +import com.yumee.panels.MainActivity +import com.yumee.panels.R +import com.yumee.panels.data.local.YumeeDatabase +import kotlinx.coroutines.* + +class MonitorWidget : AppWidgetProvider() { + + override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) { + for (appWidgetId in appWidgetIds) { + updateAppWidget(context, appWidgetManager, appWidgetId) + } + } + + override fun onReceive(context: Context, intent: Intent) { + super.onReceive(context, intent) + if (intent.action == "com.yumee.panels.UPDATE_WIDGET") { + val appWidgetManager = AppWidgetManager.getInstance(context) + val appWidgetIds = appWidgetManager.getAppWidgetIds(ComponentName(context, MonitorWidget::class.java)) + for (appWidgetId in appWidgetIds) { + updateAppWidget(context, appWidgetManager, appWidgetId) + } + } + } + + private fun updateAppWidget(context: Context, appWidgetManager: AppWidgetManager, appWidgetId: Int) { + val views = RemoteViews(context.packageName, R.layout.widget_layout) + + // Launch app on click + val intent = Intent(context, MainActivity::class.java) + val pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE) + views.setOnClickPendingIntent(R.id.widget_title, pendingIntent) + + // Fetch counts from DB + CoroutineScope(Dispatchers.IO).launch { + val db = YumeeDatabase.getDatabase(context) + val allMonitors = db.monitorDao().getAllMonitorsSync() + val onlineCount = allMonitors.count { it.isOnline } + val offlineCount = allMonitors.count { !it.isOnline } + + withContext(Dispatchers.Main) { + views.setTextViewText(R.id.widget_online_count, "Online: $onlineCount") + views.setTextViewText(R.id.widget_offline_count, "Offline: $offlineCount") + appWidgetManager.updateAppWidget(appWidgetId, views) + } + } + } +} 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 new file mode 100644 index 0000000..ca663bf --- /dev/null +++ b/android-yumee-panels/app/src/main/res/drawable/widget_background.xml @@ -0,0 +1,6 @@ + + + + + + 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 new file mode 100644 index 0000000..87d895d --- /dev/null +++ b/android-yumee-panels/app/src/main/res/layout/widget_layout.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + diff --git a/android-yumee-panels/app/src/main/res/xml/widget_info.xml b/android-yumee-panels/app/src/main/res/xml/widget_info.xml new file mode 100644 index 0000000..d22f2a9 --- /dev/null +++ b/android-yumee-panels/app/src/main/res/xml/widget_info.xml @@ -0,0 +1,9 @@ + + + diff --git a/yumee_panels_android_source.tar.gz b/yumee_panels_android_source.tar.gz index 04c517a79bbb0854f558bba0278028fa63ec8ee0..4304298ecd2889eaae507e77875b804edb5c33fe 100644 GIT binary patch literal 8202 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 literal 6891 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