opt.: display err if home widget fails (#659)

This commit is contained in:
lollipopkit🏳️‍⚧️
2024-12-15 23:39:38 +08:00
committed by GitHub
parent 2f6db2961f
commit aaa1eddeaf
13 changed files with 202 additions and 165 deletions

View File

@@ -86,10 +86,12 @@ android {
debug { debug {
applicationIdSuffix '.debug' applicationIdSuffix '.debug'
resValue "string", "app_name", "SrvBxD"
} }
profile { profile {
applicationIdSuffix '.debug' applicationIdSuffix '.debug'
resValue "string", "app_name", "SrvBxP"
} }
} }
} }

View File

@@ -11,7 +11,7 @@
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<application <application
android:label="ServerBox" android:label="@string/app_name"
android:name="${applicationName}" android:name="${applicationName}"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:allowBackup="true" android:allowBackup="true"

View File

@@ -9,13 +9,15 @@ import androidx.core.content.ContextCompat
import io.flutter.embedding.android.FlutterFragmentActivity import io.flutter.embedding.android.FlutterFragmentActivity
import io.flutter.embedding.engine.FlutterEngine import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel
import android.appwidget.AppWidgetManager
import tech.lolli.toolbox.widget.HomeWidget
class MainActivity: FlutterFragmentActivity() { class MainActivity: FlutterFragmentActivity() {
override fun configureFlutterEngine(flutterEngine: FlutterEngine) { override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine) super.configureFlutterEngine(flutterEngine)
val binaryMessenger = flutterEngine.dartExecutor.binaryMessenger val binaryMessenger = flutterEngine.dartExecutor.binaryMessenger
MethodChannel(binaryMessenger, "tech.lolli.toolbox/app_retain").apply { MethodChannel(binaryMessenger, "tech.lolli.toolbox/main_chan").apply {
setMethodCallHandler { method, result -> setMethodCallHandler { method, result ->
when (method.method) { when (method.method) {
"sendToBackground" -> { "sendToBackground" -> {
@@ -36,6 +38,12 @@ class MainActivity: FlutterFragmentActivity() {
stopService(serviceIntent) stopService(serviceIntent)
result.success(null) result.success(null)
} }
"updateHomeWidget" -> {
val intent = Intent(this@MainActivity, HomeWidget::class.java)
intent.action = AppWidgetManager.ACTION_APPWIDGET_UPDATE
sendBroadcast(intent)
result.success(null)
}
else -> { else -> {
result.notImplemented() result.notImplemented()
} }

View File

@@ -6,15 +6,18 @@ import android.appwidget.AppWidgetProvider
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Build import android.os.Build
import android.util.Log
import android.view.View import android.view.View
import android.widget.RemoteViews import android.widget.RemoteViews
import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.json.JSONObject import org.json.JSONObject
import tech.lolli.toolbox.R import tech.lolli.toolbox.R
import java.net.URL import java.net.URL
import java.net.HttpURLConnection
import java.io.FileNotFoundException
class HomeWidget : AppWidgetProvider() { class HomeWidget : AppWidgetProvider() {
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) { override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
@@ -23,7 +26,6 @@ class HomeWidget : AppWidgetProvider() {
} }
} }
@OptIn(DelicateCoroutinesApi::class)
private fun updateAppWidget(context: Context, appWidgetManager: AppWidgetManager, appWidgetId: Int) { private fun updateAppWidget(context: Context, appWidgetManager: AppWidgetManager, appWidgetId: Int) {
val views = RemoteViews(context.packageName, R.layout.home_widget) val views = RemoteViews(context.packageName, R.layout.home_widget)
val sp = context.getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE) val sp = context.getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE)
@@ -36,6 +38,10 @@ class HomeWidget : AppWidgetProvider() {
url = gUrl url = gUrl
} }
if (url.isNullOrEmpty()) {
Log.e("HomeWidget", "URL not found")
}
val intentUpdate = Intent(context, HomeWidget::class.java) val intentUpdate = Intent(context, HomeWidget::class.java)
intentUpdate.action = AppWidgetManager.ACTION_APPWIDGET_UPDATE intentUpdate.action = AppWidgetManager.ACTION_APPWIDGET_UPDATE
val ids = intArrayOf(appWidgetId) val ids = intArrayOf(appWidgetId)
@@ -54,11 +60,11 @@ class HomeWidget : AppWidgetProvider() {
views.setOnClickPendingIntent(R.id.widget_container, pendingUpdate) views.setOnClickPendingIntent(R.id.widget_container, pendingUpdate)
if (url.isNullOrEmpty()) { if (url.isNullOrEmpty()) {
views.setViewVisibility(R.id.widget_cpu_label, View.INVISIBLE) views.setTextViewText(R.id.widget_name, "No URL")
views.setViewVisibility(R.id.widget_mem_label, View.INVISIBLE) // Update the widget to display a message for missing URL
views.setViewVisibility(R.id.widget_disk_label, View.INVISIBLE) views.setViewVisibility(R.id.error_message, View.VISIBLE)
views.setViewVisibility(R.id.widget_net_label, View.INVISIBLE) views.setTextViewText(R.id.error_message, "Please configure the widget URL.")
views.setTextViewText(R.id.widget_name, "ID: $appWidgetId") views.setViewVisibility(R.id.widget_content, View.GONE)
appWidgetManager.updateAppWidget(appWidgetId, views) appWidgetManager.updateAppWidget(appWidgetId, views)
return return
} else { } else {
@@ -68,9 +74,13 @@ class HomeWidget : AppWidgetProvider() {
views.setViewVisibility(R.id.widget_net_label, View.VISIBLE) views.setViewVisibility(R.id.widget_net_label, View.VISIBLE)
} }
GlobalScope.launch(Dispatchers.IO) { CoroutineScope(Dispatchers.IO).launch {
try { try {
val jsonStr = URL(url).readText() val connection = URL(url).openConnection() as HttpURLConnection
connection.requestMethod = "GET"
val responseCode = connection.responseCode
if (responseCode == HttpURLConnection.HTTP_OK) {
val jsonStr = connection.inputStream.bufferedReader().use { it.readText() }
val jsonObject = JSONObject(jsonStr) val jsonObject = JSONObject(jsonStr)
val data = jsonObject.getJSONObject("data") val data = jsonObject.getJSONObject("data")
val server = data.getString("name") val server = data.getString("name")
@@ -78,34 +88,31 @@ class HomeWidget : AppWidgetProvider() {
val mem = data.getString("mem") val mem = data.getString("mem")
val disk = data.getString("disk") val disk = data.getString("disk")
val net = data.getString("net") val net = data.getString("net")
withContext(Dispatchers.Main) {
GlobalScope.launch(Dispatchers.Main) main@ {
// mem or disk is empty -> get status failed
// (cpu | net) isEmpty -> data is not ready
if (mem.isEmpty() || disk.isEmpty()) { if (mem.isEmpty() || disk.isEmpty()) {
return@main Log.e("HomeWidget", "Failed to retrieve status: Memory or disk information is empty")
return@withContext
} }
views.setTextViewText(R.id.widget_name, server) views.setTextViewText(R.id.widget_name, server)
views.setTextViewText(R.id.widget_cpu, cpu) views.setTextViewText(R.id.widget_cpu, cpu)
views.setTextViewText(R.id.widget_mem, mem) views.setTextViewText(R.id.widget_mem, mem)
views.setTextViewText(R.id.widget_disk, disk) views.setTextViewText(R.id.widget_disk, disk)
views.setTextViewText(R.id.widget_net, net) views.setTextViewText(R.id.widget_net, net)
val timeStr = android.text.format.DateFormat.format("HH:mm", java.util.Date()).toString() val timeStr = android.text.format.DateFormat.format("HH:mm", java.util.Date()).toString()
views.setTextViewText(R.id.widget_time, timeStr) views.setTextViewText(R.id.widget_time, timeStr)
appWidgetManager.updateAppWidget(appWidgetId, views) appWidgetManager.updateAppWidget(appWidgetId, views)
} }
} else {
throw FileNotFoundException("HTTP response code: $responseCode")
}
} catch (e: Exception) { } catch (e: Exception) {
println("ServerBoxHomeWidget: ${e.localizedMessage}") Log.e("HomeWidget", "Error updating widget: ${e.localizedMessage}", e)
GlobalScope.launch(Dispatchers.Main) main@ { withContext(Dispatchers.Main) {
views.setViewVisibility(R.id.widget_cpu_label, View.INVISIBLE) views.setTextViewText(R.id.widget_name, "Error")
views.setViewVisibility(R.id.widget_mem_label, View.INVISIBLE) // Update the widget to display a message for data retrieval failure
views.setViewVisibility(R.id.widget_disk_label, View.INVISIBLE) views.setViewVisibility(R.id.error_message, View.VISIBLE)
views.setViewVisibility(R.id.widget_net_label, View.INVISIBLE) views.setTextViewText(R.id.error_message, "Failed to retrieve data.")
views.setTextViewText(R.id.widget_name, "ID: $appWidgetId") views.setViewVisibility(R.id.widget_content, View.GONE)
views.setTextViewText(R.id.widget_mem, e.localizedMessage)
appWidgetManager.updateAppWidget(appWidgetId, views) appWidgetManager.updateAppWidget(appWidgetId, views)
} }
} }

View File

@@ -18,6 +18,15 @@
android:maxLines="1" android:maxLines="1"
tools:text="Server Name" /> tools:text="Server Name" />
<!-- Wrap the content in a LinearLayout for easy visibility management -->
<LinearLayout
android:id="@+id/widget_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_below="@id/widget_name"
android:paddingTop="13dp">
<RelativeLayout <RelativeLayout
android:id="@+id/widget_container_inner" android:id="@+id/widget_container_inner"
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -135,6 +144,19 @@
</RelativeLayout> </RelativeLayout>
</LinearLayout>
<!-- Add a TextView for error messages -->
<TextView
android:id="@+id/error_message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/widget_name"
android:textColor="@color/widgetSummaryText"
android:textSize="12sp"
android:visibility="gone"
tools:text="Error message" />
<TextView <TextView
android:id="@+id/widget_time" android:id="@+id/widget_time"
android:layout_width="wrap_content" android:layout_width="wrap_content"

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">ServerBox</string>
</resources>

View File

@@ -1,14 +1,15 @@
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:server_box/data/res/misc.dart'; import 'package:server_box/data/res/misc.dart';
abstract final class BgRunMC { abstract final class MethodChans {
static const _channel = MethodChannel('${Miscs.pkgName}/app_retain'); static const _channel = MethodChannel('${Miscs.pkgName}/main_chan');
static void moveToBg() { static void moveToBg() {
_channel.invokeMethod('sendToBackground'); _channel.invokeMethod('sendToBackground');
} }
/// TODO: try fix the fn, then uncomment it and [stopService] /// TODO: try fix the fn, then uncomment it and [stopService]
/// Issues #639
static void startService() { static void startService() {
// _channel.invokeMethod('startService'); // _channel.invokeMethod('startService');
} }
@@ -16,4 +17,9 @@ abstract final class BgRunMC {
static void stopService() { static void stopService() {
// _channel.invokeMethod('stopService'); // _channel.invokeMethod('stopService');
} }
static void updateHomeWidget() async {
//if (!Stores.setting.autoUpdateHomeWidget.fetch()) return;
await _channel.invokeMethod('updateHomeWidget');
}
} }

View File

@@ -1,12 +0,0 @@
import 'package:flutter/services.dart';
import 'package:server_box/data/res/misc.dart';
import 'package:server_box/data/res/store.dart';
abstract final class HomeWidgetMC {
static const _channel = MethodChannel('${Miscs.pkgName}/home_widget');
static void update() {
if (!Stores.setting.autoUpdateHomeWidget.fetch()) return;
_channel.invokeMethod('update');
}
}

View File

@@ -1,6 +1,6 @@
import 'package:fl_lib/fl_lib.dart'; import 'package:fl_lib/fl_lib.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:server_box/core/channel/home_widget.dart'; import 'package:server_box/core/chan.dart';
import 'package:server_box/data/model/app/tab.dart'; import 'package:server_box/data/model/app/tab.dart';
import 'package:server_box/data/provider/app.dart'; import 'package:server_box/data/provider/app.dart';
import 'package:server_box/data/provider/server.dart'; import 'package:server_box/data/provider/server.dart';
@@ -76,7 +76,7 @@ class _HomePageState extends State<HomePage>
if (!ServerProvider.isAutoRefreshOn) { if (!ServerProvider.isAutoRefreshOn) {
ServerProvider.startAutoRefresh(); ServerProvider.startAutoRefresh();
} }
HomeWidgetMC.update(); MethodChans.updateHomeWidget();
break; break;
case AppLifecycleState.paused: case AppLifecycleState.paused:
_shouldAuth = true; _shouldAuth = true;
@@ -172,7 +172,7 @@ class _HomePageState extends State<HomePage>
context: context, context: context,
); );
} }
HomeWidgetMC.update(); MethodChans.updateHomeWidget();
await ServerProvider.refresh(); await ServerProvider.refresh();
} }

View File

@@ -45,20 +45,20 @@ class _ServerOrderPageState extends State<ServerOrderPage> {
} }
Widget _buildBody() { Widget _buildBody() {
final orderNode = ServerProvider.serverOrder; final orders = ServerProvider.serverOrder;
return orderNode.listenVal((order) { return orders.listenVal((order) {
if (order.isEmpty) { if (order.isEmpty) {
return Center(child: Text(libL10n.empty)); return Center(child: Text(libL10n.empty));
} }
return ReorderableListView.builder( return ReorderableListView.builder(
footer: const SizedBox(height: 77), footer: const SizedBox(height: 77),
onReorder: (oldIndex, newIndex) => setState(() { onReorder: (oldIndex, newIndex) => setState(() {
orderNode.value.move( orders.value.move(
oldIndex, oldIndex,
newIndex, newIndex,
property: Stores.setting.serverOrder, property: Stores.setting.serverOrder,
); );
orderNode.notify(); orders.notify();
}), }),
padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 3), padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 3),
buildDefaultDragHandles: false, buildDefaultDragHandles: false,

View File

@@ -6,7 +6,7 @@ import 'package:fl_lib/fl_lib.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:server_box/core/channel/bg_run.dart'; import 'package:server_box/core/chan.dart';
import 'package:server_box/core/extension/context/locale.dart'; import 'package:server_box/core/extension/context/locale.dart';
import 'package:server_box/core/utils/ssh_auth.dart'; import 'package:server_box/core/utils/ssh_auth.dart';
import 'package:server_box/core/utils/server.dart'; import 'package:server_box/core/utils/server.dart';
@@ -84,7 +84,7 @@ class SSHPageState extends State<SSHPage>
if (--_sshConnCount <= 0) { if (--_sshConnCount <= 0) {
WakelockPlus.disable(); WakelockPlus.disable();
if (isAndroid) { if (isAndroid) {
BgRunMC.stopService(); MethodChans.stopService();
} }
} }
} }
@@ -99,7 +99,7 @@ class SSHPageState extends State<SSHPage>
if (++_sshConnCount == 1) { if (++_sshConnCount == 1) {
WakelockPlus.enable(); WakelockPlus.enable();
if (isAndroid) { if (isAndroid) {
BgRunMC.startService(); MethodChans.startService();
} }
} }
} }

View File

@@ -478,8 +478,8 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
path: "." path: "."
ref: "v1.0.224" ref: "v1.0.225"
resolved-ref: ce532383b50fd3bfe74017a2799510c014b0f4f4 resolved-ref: "9ca92213fea15b8c245ed2cc2958e14baea5cdbe"
url: "https://github.com/lppcg/fl_lib" url: "https://github.com/lppcg/fl_lib"
source: git source: git
version: "0.0.1" version: "0.0.1"

View File

@@ -63,7 +63,7 @@ dependencies:
fl_lib: fl_lib:
git: git:
url: https://github.com/lppcg/fl_lib url: https://github.com/lppcg/fl_lib
ref: v1.0.224 ref: v1.0.225
dependency_overrides: dependency_overrides:
# dartssh2: # dartssh2: