main
1import 'dart:io';
2
3import 'package:blood_pressure_app/data_util/consistent_future_builder.dart';
4import 'package:blood_pressure_app/logging.dart';
5import 'package:blood_pressure_app/screens/error_reporting_screen.dart';
6import 'package:file_picker/file_picker.dart';
7import 'package:flutter/material.dart';
8import 'package:flutter/services.dart';
9import 'package:blood_pressure_app/l10n/app_localizations.dart';
10import 'package:package_info_plus/package_info_plus.dart';
11import 'package:path/path.dart';
12import 'package:sqflite/sqflite.dart';
13
14/// Screen that shows app version and debug options.
15class VersionScreen extends StatefulWidget {
16 /// Screen that shows app version and debug options.
17 const VersionScreen({super.key});
18
19 @override
20 State<VersionScreen> createState() => _VersionScreenState();
21}
22
23class _VersionScreenState extends State<VersionScreen> with TypeLogger {
24 @override
25 Widget build(BuildContext context) {
26 final localizations = AppLocalizations.of(context)!;
27 return Scaffold(
28 appBar: AppBar(
29 title: Text(localizations.version),
30 actions: [
31 IconButton(
32 onPressed: () async {
33 final packageInfo = await PackageInfo.fromPlatform();
34 await Clipboard.setData(ClipboardData(
35 text: 'Blood pressure monitor\n'
36 '${packageInfo.packageName}\n'
37 '${packageInfo.version} - ${packageInfo.buildNumber}',
38 ),);
39 },
40 tooltip: localizations.export,
41 icon: const Icon(Icons.copy),
42 ),
43 ],
44 backgroundColor: Theme.of(context).primaryColor,
45 ),
46 body: Padding(
47 padding: const EdgeInsets.all(10.0),
48 child: ListView(
49 children: [
50 // Debug info
51 ConsistentFutureBuilder<PackageInfo>(
52 future: PackageInfo.fromPlatform(),
53 onData: (context, packageInfo) => Column(
54 crossAxisAlignment: CrossAxisAlignment.start,
55 children: [
56 Text(localizations.packageNameOf(packageInfo.packageName)),
57 Text(localizations.versionOf(packageInfo.version)),
58 Text(localizations.buildNumberOf(packageInfo.buildNumber)),
59 ],
60 ),
61 ),
62 // Logs
63 SwitchListTile(
64 // Would not be used by regular users so no need to translate
65 title: Text('Enable ultra-verbose logging until app restart'),
66 subtitle: Text('This can help to track down hard to reproduce bugs'),
67 value: Log.isVerbose,
68 onChanged: (v) => setState(() => Log.setVerbose(v)),
69 ),
70 FutureBuilder(future: Future(() async {
71 final dbPath = await getDatabasesPath();
72 return join(dbPath, 'blood_pressure.db');
73 }), builder: (context, snapshot) {
74 if (snapshot.data == null || !File(snapshot.data!).existsSync()) {
75 return SizedBox.shrink();
76 }
77 return ListTile(
78 onTap: () async {
79 try {
80 await FilePicker.platform.saveFile(
81 fileName: 'blood_pressure.db',
82 bytes: File(snapshot.data!).readAsBytesSync(),
83 type: FileType.any, // application/vnd.sqlite3
84 );
85 } catch(e) {
86 if (!context.mounted) return;
87 ScaffoldMessenger.of(context).showSnackBar(SnackBar(
88 content: Text('ERR: $e'),),);
89 }
90 },
91 title: const Text('rescue legacy db'),
92 );
93 }),
94 ListTile(
95 title: Text('Logs:'),
96 trailing: Icon(Icons.copy),
97 onTap: () async {
98 await Clipboard.setData(ClipboardData(
99 text: Log.logs
100 .map((e) => '${e.level.name} - ${e.time.toIso8601String()}||'
101 '${e.loggerName}||"${e.message}"||{${e.stackTrace}}\n')
102 .fold('', (res, e) => res + e),
103 ));
104 if(context.mounted){
105 ScaffoldMessenger.of(context).showSnackBar(SnackBar(
106 content: Text('Logs copied to clipboard'),
107 ));
108 }
109 },
110 ),
111 SizedBox(
112 height: 600,
113 child: ListView.builder(
114 shrinkWrap: true,
115 itemCount: Log.logs.length,
116 itemBuilder: (context, idx) {
117 final record = Log.logs[Log.logs.length - idx - 1];
118 return ExpansionTile(
119 title: Wrap(
120 spacing: 4.0,
121 crossAxisAlignment: WrapCrossAlignment.center,
122 //mainAxisAlignment: MainAxisAlignment.spaceBetween,
123 children: [
124 Chip(
125 // FIXME: doesn't work in light mode
126 backgroundColor: switch(record.level.value) {
127 <= 500 => Colors.transparent,
128 <= 800 => Colors.grey.shade800,
129 <= 900 => Colors.deepOrange,
130 <= 1000 => Colors.red,
131 int() => Colors.red.shade900,
132 },
133 label: Text(record.level.name),
134 ),
135 Text(record.loggerName),
136 ],
137 ),
138 subtitle: Text('Timestamp: ${record.time.hour}:${record.time.minute}.${record.time.second}'),
139 children: [
140 Text(record.message),
141 if (record.stackTrace != null)
142 Text(record.stackTrace.toString()),
143 ],
144 );
145 },
146 ),
147 ),
148 ListTile(
149 title: Text('Test log messages'),
150 trailing: Icon(Icons.chevron_right),
151 onTap: () {
152 logger.finest('test finest');
153 logger.finer('test finer');
154 logger.fine('test fine');
155 logger.info('test info');
156 logger.warning('test warning');
157 logger.severe('test severe');
158 logger.shout('test shout');
159 },
160 )
161 ],
162 ),
163 ),
164 );
165 }
166}