feat: add clear logs with confirmation dialog and fix DB dispatchers

- Add clearLogs method to BridgeModule with proper coroutine scope
- Fix fetchLogs and clearLogs using Thread+runBlocking which caused app close
- Change serviceScope from Dispatchers.Main to Dispatchers.IO
- Add error logging in fetchLogs for better diagnostics
- Add clear logs button with confirmation dialog in StatusScreen
This commit is contained in:
fiatcode 2026-05-04 08:38:25 +07:00
parent 1b3440e2fe
commit 60d051ee7b
No known key found for this signature in database
2 changed files with 61 additions and 27 deletions

View file

@ -11,8 +11,9 @@ import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.EventChannel import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class BridgeModule : FlutterPlugin, MethodChannel.MethodCallHandler { class BridgeModule : FlutterPlugin, MethodChannel.MethodCallHandler {
@ -174,30 +175,28 @@ class BridgeModule : FlutterPlugin, MethodChannel.MethodCallHandler {
} }
private fun fetchLogs(limit: Int, result: MethodChannel.Result) { private fun fetchLogs(limit: Int, result: MethodChannel.Result) {
Thread { CoroutineScope(Dispatchers.IO).launch {
try { try {
val logs = runBlocking(Dispatchers.IO) { val db = context?.let { AppDatabase.getInstance(it) }
val db = context?.let { AppDatabase.getInstance(it) } val logs = if (db != null) {
if (db != null) { val events = db.eventLogDao().getRecentEventsList(limit)
val events = db.eventLogDao().getRecentEventsList(limit) events.map { event ->
events.map { event -> mapOf(
mapOf( "id" to event.id,
"id" to event.id, "timestamp" to event.timestamp,
"timestamp" to event.timestamp, "eventType" to event.eventType,
"eventType" to event.eventType, "message" to event.message
"message" to event.message )
)
}
} else {
emptyList()
} }
} else {
emptyList()
} }
mainHandler.post { result.success(logs) } mainHandler.post { result.success(logs) }
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "fetchLogs failed", e) Log.e(TAG, "fetchLogs failed", e)
mainHandler.post { result.success(emptyList<Map<String, Any>>()) } mainHandler.post { result.success(emptyList<Map<String, Any>>()) }
} }
}.start() }
} }
private fun openBatteryOptimizationSettings(result: MethodChannel.Result) { private fun openBatteryOptimizationSettings(result: MethodChannel.Result) {
@ -234,21 +233,17 @@ class BridgeModule : FlutterPlugin, MethodChannel.MethodCallHandler {
} }
private fun clearLogs(result: MethodChannel.Result) { private fun clearLogs(result: MethodChannel.Result) {
Thread { CoroutineScope(Dispatchers.IO).launch {
try { try {
val deleted = runBlocking(Dispatchers.IO) { val db = context?.let { AppDatabase.getInstance(it) }
val db = context?.let { AppDatabase.getInstance(it) } if (db != null) {
if (db != null) { db.eventLogDao().deleteAll()
db.eventLogDao().deleteAll()
} else {
0
}
} }
mainHandler.post { result.success(deleted) } mainHandler.post { result.success(0) }
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "clearLogs failed", e) Log.e(TAG, "clearLogs failed", e)
mainHandler.post { result.success(-1) } mainHandler.post { result.success(-1) }
} }
}.start() }
} }
} }

View file

@ -48,6 +48,45 @@ class _StatusScreenState extends State<StatusScreen> {
} }
Future<void> _clearLogs() async { Future<void> _clearLogs() async {
final confirmed = await showDialog<bool>(
context: context,
builder: (ctx) => AlertDialog(
backgroundColor: const Color(0xFF1e1e1e),
title: const Text(
'Clear Event Logs',
style: TextStyle(fontFamily: 'monospace', color: Color(0xFFe0e0e0)),
),
content: const Text(
'This will permanently delete all logged events. Continue?',
style: TextStyle(fontFamily: 'monospace', color: Color(0xFF9e9e9e)),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(ctx, false),
child: const Text(
'CANCEL',
style: TextStyle(
fontFamily: 'monospace',
color: Color(0xFF616161),
),
),
),
TextButton(
onPressed: () => Navigator.pop(ctx, true),
child: const Text(
'CLEAR',
style: TextStyle(
fontFamily: 'monospace',
color: Color(0xFFff5252),
),
),
),
],
),
);
if (confirmed != true) return;
final result = await LocationBridge.clearLogs(); final result = await LocationBridge.clearLogs();
if (mounted) { if (mounted) {
if (result >= 0) { if (result >= 0) {