reworked developer tools with diagnostics, storage viewers and release-capable debug overlays

This commit is contained in:
2026-09-26 14:34:47 +02:00
parent 0f6c388940
commit e6f74ca6fe
18 changed files with 1330 additions and 239 deletions
+90 -36
View File
@@ -1,62 +1,116 @@
import 'dart:convert';
import 'package:filesize/filesize.dart';
import 'package:flutter/material.dart';
import 'package:jiffy/jiffy.dart';
import '../../../widget/placeholder_view.dart';
import '../../api/cache_store.dart';
import '../../extensions/date_time.dart';
import '../../routing/app_routes.dart';
import '../app_progress_indicator.dart';
import 'json_viewer.dart';
import '../confirm_dialog.dart';
import '../placeholder_view.dart';
/// Lists the [CacheStore] entries (one file per request key), newest first.
class CacheView extends StatefulWidget {
const CacheView({super.key});
@override
State<CacheView> createState() => _CacheViewState();
}
class _CacheViewState extends State<CacheView> {
late final Future<Map<String, CacheEntry>> files = CacheStore.instance
.readAll();
late Future<List<MapEntry<String, CacheEntry>>> _entries = _read();
Future<List<MapEntry<String, CacheEntry>>> _read() async =>
(await CacheStore.instance.readAll()).entries.toList()
..sort((a, b) => b.value.lastUpdate.compareTo(a.value.lastUpdate));
void _reload() => setState(() {
_entries = _read();
});
void _confirmDelete(String key) {
ConfirmDialog(
title: 'Eintrag löschen',
content: '„$key“ wird gelöscht und bei Bedarf neu geladen.',
confirmButton: 'Löschen',
errorBuilder: (e) => '$e',
onConfirmAsync: () async {
await CacheStore.instance.delete(key);
if (mounted) _reload();
},
).asDialog(context);
}
void _confirmClear() {
ConfirmDialog(
title: 'Request-Cache leeren',
content:
'Alle Einträge werden gelöscht. Der Cache baut sich bei Nutzung der '
'App automatisch wieder auf.',
confirmButton: 'Alles löschen',
errorBuilder: (e) => '$e',
onConfirmAsync: () async {
await CacheStore.instance.clear();
if (mounted) _reload();
},
).asDialog(context);
}
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(title: const Text('Cache storage')),
appBar: AppBar(
title: const Text('Request-Cache'),
actions: [
IconButton(
tooltip: 'Neu laden',
icon: const Icon(Icons.refresh),
onPressed: _reload,
),
IconButton(
tooltip: 'Alles löschen',
icon: const Icon(Icons.delete_sweep_outlined),
onPressed: _confirmClear,
),
],
),
body: FutureBuilder(
future: files,
future: _entries,
builder: (context, snapshot) {
if (snapshot.hasData && snapshot.data!.isNotEmpty) {
return ListView.builder(
itemCount: snapshot.data!.length,
itemBuilder: (context, index) {
final key = snapshot.data!.keys.elementAt(index);
final element = snapshot.data![key]!;
return ListTile(
leading: const Icon(Icons.text_snippet_outlined),
title: Text(key),
subtitle: Text(
'${filesize(utf8.encode(element.json).length)}, ${Jiffy.parseFromMillisecondsSinceEpoch(element.lastUpdate).fromNow()}',
),
trailing: const Icon(Icons.arrow_right),
onTap: () => JsonViewer.asDialog(
context,
jsonDecode(element.json) as Map<String, dynamic>,
),
);
},
);
} else if (snapshot.connectionState != ConnectionState.done) {
if (snapshot.connectionState != ConnectionState.done) {
return const Center(child: AppProgressIndicator.large());
} else {
return const Center(
child: PlaceholderView(
icon: Icons.hourglass_empty,
text: 'Keine Daten',
),
}
final entries = snapshot.data ?? const [];
if (entries.isEmpty) {
return const PlaceholderView(
icon: Icons.hourglass_empty,
text: 'Keine Daten',
);
}
return ListView.builder(
itemCount: entries.length,
itemBuilder: (context, index) {
final MapEntry(:key, :value) = entries[index];
return ListTile(
leading: const Icon(Icons.text_snippet_outlined),
title: Text(key),
subtitle: Text(
'${filesize(utf8.encode(value.json).length)} · '
'${DateTime.fromMillisecondsSinceEpoch(value.lastUpdate).formatRelative()}',
),
onTap: () => AppRoutes.openJsonViewer(
context,
jsonDecode(value.json),
title: key,
),
trailing: IconButton(
tooltip: 'Löschen',
icon: const Icon(Icons.delete_outline),
onPressed: () => _confirmDelete(key),
),
);
},
);
},
),
);