main
  1import 'dart:io';
  2
  3import 'package:file_picker/file_picker.dart';
  4import 'package:flutter/material.dart';
  5import 'package:flutter/services.dart';
  6import 'package:package_info_plus/package_info_plus.dart';
  7import 'package:path/path.dart';
  8import 'package:sqflite/sqflite.dart';
  9import 'package:url_launcher/url_launcher.dart';
 10
 11/// A static location to report errors to and disrupt the program flow in case
 12/// there is the risk of data loss when continuing.
 13class ErrorReporting {
 14  ErrorReporting._create();
 15
 16  /// Whether there is already an critical error displayed.
 17  static bool isErrorState = false;
 18
 19  /// Replaces the application with an ErrorScreen.
 20  ///
 21  /// This method can be used to avoid running any further code in your current function, by awaiting
 22  static Future<void> reportCriticalError(String title, String text) async {
 23    if (isErrorState) throw Exception('Tried to report another error:\n title = $title,\n text = $text');
 24    isErrorState = true;
 25    runApp(ErrorScreen(
 26      title: title,
 27      text: text,
 28      debugInfo: await _carefullyCollectDebugInfo(),
 29    ),);
 30    return Future.delayed(const Duration(days: 30,));
 31  }
 32
 33  static Future<PackageInfo> _carefullyCollectDebugInfo() async {
 34    PackageInfo packageInfo;
 35    try {
 36      packageInfo = await PackageInfo.fromPlatform();
 37    } catch (e) {
 38      packageInfo = PackageInfo(appName: 'err', packageName: 'err', version: 'err', buildNumber: 'err');
 39    }
 40
 41    return packageInfo;
 42  }
 43}
 44
 45/// A full [MaterialApp] that is especially safe against throwing errors and
 46/// allows for debugging and data extraction.
 47class ErrorScreen extends StatelessWidget {
 48  
 49  const ErrorScreen({super.key, required this.title, required this.text, required this.debugInfo});
 50
 51  final String title;
 52  final String text;
 53  final PackageInfo debugInfo;
 54
 55  @override
 56  Widget build(BuildContext context) => MaterialApp(
 57      title: 'Critical error',
 58      home: Scaffold(
 59        appBar: AppBar(
 60          title: const Text('Critical error'),
 61          backgroundColor: Colors.red,
 62        ),
 63        body: Builder(
 64          builder: (context) {
 65            final scaffoldMessenger = ScaffoldMessenger.of(context);
 66            return SingleChildScrollView(
 67              padding: const EdgeInsets.symmetric(horizontal: 10),
 68              child: Column(
 69                mainAxisSize: MainAxisSize.min,
 70                children: [
 71                  const SizedBox(height: 20,),
 72                  Text('App version: ${debugInfo.version}'),
 73                  Text('Build number: ${debugInfo.buildNumber}'),
 74                  const Divider(),
 75                  Text(title, style: const TextStyle(fontSize: 20, ), ),
 76                  Text(text),
 77                  const Divider(),
 78                  TextButton(
 79                    onPressed: () {
 80                      Clipboard.setData(ClipboardData(
 81                        text: 'Error:\nBuild number:${debugInfo.buildNumber}\n-----\n$title:\n---\n$text\n',
 82                      ),);
 83                      scaffoldMessenger.showSnackBar(const SnackBar(
 84                          content: Text('Copied to clipboard'),),);
 85                    },
 86                    child: const Text('copy error message'),
 87                  ),
 88                  TextButton(
 89                    onPressed: () async {
 90                      try {
 91                        final url = Uri.parse('https://github.com/derdilla/blood-pressure-monitor-fl/issues');
 92                        if (await canLaunchUrl(url)) {
 93                          await launchUrl(url, mode: LaunchMode.externalApplication);
 94                        } else {
 95                          scaffoldMessenger.showSnackBar(const SnackBar(
 96                            content: Text('ERR: Please open this website: https://github.com/derdilla/blood-pressure-monitor-fl/issues'),),);
 97                        }
 98                      } catch (e) {
 99                        scaffoldMessenger.showSnackBar(SnackBar(
100                            content: Text('ERR: $e'),),);
101                      }
102                    },
103                    child: const Text('open issue reporting website'),
104                  ),
105                  TextButton(
106                    onPressed: () async {
107                      try {
108                        String dbPath = await getDatabasesPath();
109
110                        assert(dbPath != inMemoryDatabasePath);
111                        dbPath = join(dbPath, 'config.db');
112
113                        await FilePicker.platform.saveFile(
114                          fileName: 'config.db',
115                          bytes: File(dbPath).readAsBytesSync(),
116                          type: FileType.any, // application/vnd.sqlite3
117                        );
118                      } catch(e) {
119                        scaffoldMessenger.showSnackBar(SnackBar(
120                            content: Text('ERR: $e'),),);
121                      }
122                    },
123                    child: const Text('rescue config.db'),
124                  ),
125                  TextButton(
126                    onPressed: () async {
127                      try {
128                        String dbPath = await getDatabasesPath();
129
130                        assert(dbPath != inMemoryDatabasePath);
131                        dbPath = join(dbPath, 'bp.db');
132                        await FilePicker.platform.saveFile(
133                          fileName: 'bp.db',
134                          bytes: File(dbPath).readAsBytesSync(),
135                          type: FileType.any, // application/vnd.sqlite3
136                        );
137                      } catch(e) {
138                        scaffoldMessenger.showSnackBar(SnackBar(
139                          content: Text('ERR: $e'),),);
140                      }
141                    },
142                    child: const Text('rescue db'),
143                  ),
144                ],
145              ),
146            );
147          },
148        ),
149      ),
150    );
151  
152}