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}