mirror of
https://github.com/zapstore/zapstore.git
synced 2026-10-05 12:38:24 +00:00
Improve error collection and reporting. Fixes #37
This commit is contained in:
+20
-19
@@ -1,7 +1,7 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_data/flutter_data.dart';
|
||||
import 'package:flutter_phoenix/flutter_phoenix.dart';
|
||||
@@ -16,8 +16,8 @@ const kDbVersion = 1;
|
||||
|
||||
/// Application entry point.
|
||||
/// - Initializes Riverpod (and Flutter Data local storage)
|
||||
/// - Handles errors globally
|
||||
/// - Calls router
|
||||
/// - Handles application errors
|
||||
/// - Triggers routing
|
||||
void main() {
|
||||
runZonedGuarded(() {
|
||||
runApp(
|
||||
@@ -62,23 +62,24 @@ Directory? _dir;
|
||||
|
||||
void errorHandler(Object exception, StackTrace? stack) {
|
||||
debounce((exception: exception, stack: stack), (records) async {
|
||||
// TODO Implement reporting
|
||||
final map = records.groupSetsBy((err) => '${err.exception}\n${err.stack}');
|
||||
print(map.length);
|
||||
|
||||
// final text =
|
||||
// '${DateTime.now().toIso8601String()}\n${err.exception}\n${err.stack}';
|
||||
// // print('${err.exception}\n${err.stack}');
|
||||
|
||||
// final hash = sha256.convert(utf8.encode('${err.exception}\n${err.stack}'));
|
||||
// print(hash);
|
||||
// print(text);
|
||||
// print('-----');
|
||||
|
||||
if (records.isEmpty) return;
|
||||
_dir ??= await getApplicationDocumentsDirectory();
|
||||
// // final file = File('${directory.path}/data.txt');
|
||||
// // await file.writeAsString(newContents);
|
||||
final file = File('${_dir!.path}/errors.json');
|
||||
|
||||
// collect system information
|
||||
final errorMap = await file.exists()
|
||||
? Map<String, String>.from(jsonDecode(await file.readAsString()))
|
||||
: <String, String>{};
|
||||
|
||||
for (final record in records) {
|
||||
final full =
|
||||
'${record.exception}${record.stack?.toString() ?? ''}${DateTime.now().toIso8601String()}';
|
||||
final key = full.split('\n').take(2).join();
|
||||
// Only keep longest stack of similar errors, prevents duplicates
|
||||
if (full.length > (errorMap[key]?.length ?? 0)) {
|
||||
errorMap[key] = full;
|
||||
}
|
||||
}
|
||||
print(errorMap.length);
|
||||
await file.writeAsString(jsonEncode(errorMap));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -17,7 +17,8 @@ class MobileScaffold extends HookConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
// TODO Inefficient, should be able to query for this (we need to store app info in database)
|
||||
// TODO Inefficient, should be able to query for this
|
||||
// (need to store app status in database, local only)
|
||||
final appsToUpdate =
|
||||
ref.apps.watchAll().model.where((a) => a.canUpdate).length;
|
||||
return Scaffold(
|
||||
|
||||
@@ -139,7 +139,7 @@ final dataLibrariesInitializer = FutureProvider<void>((ref) async {
|
||||
ref
|
||||
.read(relayMessageNotifierProvider.notifier)
|
||||
.initialize(['wss://relay.zap.store', 'wss://relay.nostr.band']);
|
||||
// TODO Is this best place for this listener?
|
||||
|
||||
_lifecycleListener = AppLifecycleListener(
|
||||
onStateChange: (state) async {
|
||||
if (state == AppLifecycleState.resumed) {
|
||||
|
||||
@@ -60,7 +60,7 @@ class SettingsScreen extends HookConsumerWidget {
|
||||
if (controller.text.trim().isNotEmpty) {
|
||||
final text =
|
||||
'${controller.text.trim()} [from ${user.npub} on ${DateFormat('MMMM d, y').format(DateTime.now())}]';
|
||||
final event = BaseEvent(content: text).sign(kI);
|
||||
final event = BaseEvent(kind: 1011, content: text).sign(kI);
|
||||
await ref.apps.nostrAdapter.notifier
|
||||
.publish(event, relayUrls: ['wss://relay.zap.store']);
|
||||
controller.clear();
|
||||
|
||||
@@ -4,13 +4,13 @@ const _debounceTime = 1200;
|
||||
Timer? timer;
|
||||
final acc = [];
|
||||
|
||||
void debounce<T>(T input, Function(List<T>) cb) {
|
||||
void debounce<T>(T input, Future Function(List<T>) cb) {
|
||||
if (timer?.isActive ?? false) {
|
||||
timer!.cancel();
|
||||
}
|
||||
acc.add(input);
|
||||
timer = Timer(Duration(milliseconds: _debounceTime), () {
|
||||
cb(acc.cast<T>());
|
||||
timer = Timer(Duration(milliseconds: _debounceTime), () async {
|
||||
await cb(acc.cast<T>());
|
||||
acc.clear();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_data/flutter_data.dart';
|
||||
import 'package:flutter_phoenix/flutter_phoenix.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:purplebase/purplebase.dart';
|
||||
import 'package:zapstore/screens/settings_screen.dart';
|
||||
import 'package:zapstore/utils/system_info.dart';
|
||||
import 'package:zapstore/utils/theme.dart';
|
||||
|
||||
class ErrorContainer extends HookConsumerWidget {
|
||||
@@ -51,43 +57,12 @@ class ErrorContainer extends HookConsumerWidget {
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
Gap(24),
|
||||
Text(
|
||||
'First try restart. If it does not fix it, try clear.\nLastly, you can send us an error report.',
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
color: Colors.grey,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
Gap(10),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
Phoenix.rebirth(context);
|
||||
context.go('/');
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Color.fromARGB(255, 255, 99, 99),
|
||||
foregroundColor: Colors.white,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
child: const Text(
|
||||
'Restart',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
Gap(10),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
ref.read(localStorageProvider).destroy().then((_) {
|
||||
print('destory done');
|
||||
Phoenix.rebirth(context);
|
||||
context.go('/');
|
||||
});
|
||||
@@ -100,7 +75,7 @@ class ErrorContainer extends HookConsumerWidget {
|
||||
),
|
||||
),
|
||||
child: const Text(
|
||||
'Clear',
|
||||
'Clear and reload app',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
@@ -109,7 +84,7 @@ class ErrorContainer extends HookConsumerWidget {
|
||||
),
|
||||
Gap(10),
|
||||
ElevatedButton(
|
||||
onPressed: () {},
|
||||
onPressed: () => _sendErrorReport(ref, exception, stack),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Color.fromARGB(255, 255, 22, 22),
|
||||
foregroundColor: Colors.white,
|
||||
@@ -118,7 +93,7 @@ class ErrorContainer extends HookConsumerWidget {
|
||||
),
|
||||
),
|
||||
child: const Text(
|
||||
'Report',
|
||||
'Report error',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
@@ -150,3 +125,21 @@ Widget materialErrorBuilder(BuildContext context, Widget? widget) {
|
||||
if (widget != null) return widget;
|
||||
throw StateError('widget is null');
|
||||
}
|
||||
|
||||
Future<void> _sendErrorReport(
|
||||
WidgetRef ref, Object exception, StackTrace? stack) async {
|
||||
final systemInfo = await ref.read(systemInfoProvider.future);
|
||||
|
||||
var map = {
|
||||
'e': exception.toString(),
|
||||
if (stack != null) 'stack': stack.toString(),
|
||||
'info': systemInfo.androidInfo.toString()
|
||||
};
|
||||
|
||||
final client = http.Client();
|
||||
final event = BaseEvent(kind: 1011, content: jsonEncode(map)).sign(kI);
|
||||
await client.post(Uri.parse('https://relay.zap.store/'),
|
||||
body: jsonEncode(event.toMap()),
|
||||
headers: {'Content-Type': 'application/json'});
|
||||
client.close();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user