feat: add Locale, Text Scale, DB Schema Version, Device Model to About page (#258) (#263)

This commit was merged in pull request #263.
This commit is contained in:
Bot of Thomas Güttler
2026-05-25 22:18:09 +02:00
parent 7997ff0980
commit 8709e9f38d
8 changed files with 99 additions and 5 deletions
@@ -15,6 +15,11 @@ import io.flutter.embedding.engine.FlutterEngine;
public final class GeneratedPluginRegistrant { public final class GeneratedPluginRegistrant {
private static final String TAG = "GeneratedPluginRegistrant"; private static final String TAG = "GeneratedPluginRegistrant";
public static void registerWith(@NonNull FlutterEngine flutterEngine) { public static void registerWith(@NonNull FlutterEngine flutterEngine) {
try {
flutterEngine.getPlugins().add(new dev.fluttercommunity.plus.device_info.DeviceInfoPlusPlugin());
} catch (Exception e) {
Log.e(TAG, "Error registering plugin device_info_plus, dev.fluttercommunity.plus.device_info.DeviceInfoPlusPlugin", e);
}
try { try {
flutterEngine.getPlugins().add(new com.mr.flutter.plugin.filepicker.FilePickerPlugin()); flutterEngine.getPlugins().add(new com.mr.flutter.plugin.filepicker.FilePickerPlugin());
} catch (Exception e) { } catch (Exception e) {
+1
View File
@@ -0,0 +1 @@
const int dbSchemaVersion = 32;
+2 -1
View File
@@ -6,6 +6,7 @@ import 'package:drift/native.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:path/path.dart' as p; import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:sharedinbox/core/db_schema_version.dart';
part 'database.g.dart'; part 'database.g.dart';
@@ -329,7 +330,7 @@ class AppDatabase extends _$AppDatabase {
AppDatabase([QueryExecutor? executor]) : super(executor ?? _openConnection()); AppDatabase([QueryExecutor? executor]) : super(executor ?? _openConnection());
@override @override
int get schemaVersion => 32; int get schemaVersion => dbSchemaVersion;
Future<void> _createEmailFts() async { Future<void> _createEmailFts() async {
await customStatement(''' await customStatement('''
+59 -4
View File
@@ -1,11 +1,13 @@
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_markdown_plus/flutter_markdown_plus.dart'; import 'package:flutter_markdown_plus/flutter_markdown_plus.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:package_info_plus/package_info_plus.dart'; import 'package:package_info_plus/package_info_plus.dart';
import 'package:sharedinbox/core/db_schema_version.dart';
import 'package:sharedinbox/core/models/account.dart'; import 'package:sharedinbox/core/models/account.dart';
import 'package:sharedinbox/di.dart'; import 'package:sharedinbox/di.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
@@ -19,7 +21,9 @@ class AboutScreen extends ConsumerStatefulWidget {
class _AboutScreenState extends ConsumerState<AboutScreen> { class _AboutScreenState extends ConsumerState<AboutScreen> {
final Future<PackageInfo> _packageInfoFuture = PackageInfo.fromPlatform(); final Future<PackageInfo> _packageInfoFuture = PackageInfo.fromPlatform();
late final Future<String?> _deviceModelFuture;
late final Stream<List<Account>> _accountsStream; late final Stream<List<Account>> _accountsStream;
String? _deviceModel;
static const _gitHash = String.fromEnvironment('GIT_HASH'); static const _gitHash = String.fromEnvironment('GIT_HASH');
@@ -27,14 +31,35 @@ class _AboutScreenState extends ConsumerState<AboutScreen> {
void initState() { void initState() {
super.initState(); super.initState();
_accountsStream = ref.read(accountRepositoryProvider).observeAccounts(); _accountsStream = ref.read(accountRepositoryProvider).observeAccounts();
_deviceModelFuture = _fetchDeviceModel();
unawaited(
_deviceModelFuture.then((model) {
if (mounted) setState(() => _deviceModel = model);
}),
);
}
static Future<String?> _fetchDeviceModel() async {
try {
final info = DeviceInfoPlugin();
if (Platform.isAndroid) {
final android = await info.androidInfo;
return '${android.manufacturer} / ${android.model}';
} else if (Platform.isIOS) {
final ios = await info.iosInfo;
return ios.utsname.machine;
}
} catch (_) {}
return null;
} }
String _buildMarkdown( String _buildMarkdown(
BuildContext context, BuildContext context,
PackageInfo? pkg, PackageInfo? pkg,
int imapCount, int imapCount,
int jmapCount, int jmapCount, {
) { String? deviceModel,
}) {
final size = MediaQuery.of(context).size; final size = MediaQuery.of(context).size;
final pixelRatio = MediaQuery.of(context).devicePixelRatio; final pixelRatio = MediaQuery.of(context).devicePixelRatio;
final physW = (size.width * pixelRatio).toInt(); final physW = (size.width * pixelRatio).toInt();
@@ -46,10 +71,15 @@ class _AboutScreenState extends ConsumerState<AboutScreen> {
: version; : version;
final osName = _capitalize(Platform.operatingSystem); final osName = _capitalize(Platform.operatingSystem);
final isDark = MediaQuery.of(context).platformBrightness == Brightness.dark; final isDark = MediaQuery.of(context).platformBrightness == Brightness.dark;
final locale = Localizations.localeOf(context).toString();
final textScale =
MediaQuery.of(context).textScaler.scale(1.0).toStringAsFixed(1);
final gitCommitLine = _gitHash.isNotEmpty final gitCommitLine = _gitHash.isNotEmpty
? '| Git Commit | [$_gitHash](https://codeberg.org/guettli/sharedinbox/commit/$_gitHash) |\n' ? '| Git Commit | [$_gitHash](https://codeberg.org/guettli/sharedinbox/commit/$_gitHash) |\n'
: ''; : '';
final deviceModelLine =
deviceModel != null ? '| Device Model | $deviceModel |\n' : '';
return '## [sharedinbox.de](https://sharedinbox.de)\n\n' return '## [sharedinbox.de](https://sharedinbox.de)\n\n'
'| Property | Value |\n' '| Property | Value |\n'
'|----------|-------|\n' '|----------|-------|\n'
@@ -57,12 +87,16 @@ class _AboutScreenState extends ConsumerState<AboutScreen> {
'$gitCommitLine' '$gitCommitLine'
'| Platform | ${Platform.operatingSystem} |\n' '| Platform | ${Platform.operatingSystem} |\n'
'| $osName Version | ${Platform.operatingSystemVersion} |\n' '| $osName Version | ${Platform.operatingSystemVersion} |\n'
'$deviceModelLine'
'| Resolution | ${physW}x$physH px' '| Resolution | ${physW}x$physH px'
' (logical: ${size.width.toInt()}x${size.height.toInt()} pt,' ' (logical: ${size.width.toInt()}x${size.height.toInt()} pt,'
' ratio: ${pixelRatio.toStringAsFixed(1)}x) |\n' ' ratio: ${pixelRatio.toStringAsFixed(1)}x) |\n'
'| Dart Version | ${Platform.version.split(' ').first} |\n' '| Dart Version | ${Platform.version.split(' ').first} |\n'
'| Processors | ${Platform.numberOfProcessors} |\n' '| Processors | ${Platform.numberOfProcessors} |\n'
'| Dark Mode | ${isDark ? 'yes' : 'no'} |\n' '| Dark Mode | ${isDark ? 'yes' : 'no'} |\n'
'| Locale | $locale |\n'
'| Text Scale | $textScale× |\n'
'| DB Schema Version | $dbSchemaVersion |\n'
'| IMAP Accounts | $imapCount |\n' '| IMAP Accounts | $imapCount |\n'
'| JMAP Accounts | $jmapCount |\n'; '| JMAP Accounts | $jmapCount |\n';
} }
@@ -79,10 +113,20 @@ class _AboutScreenState extends ConsumerState<AboutScreen> {
try { try {
pkg = await _packageInfoFuture; pkg = await _packageInfoFuture;
} catch (_) {} } catch (_) {}
String? deviceModel;
try {
deviceModel = await _deviceModelFuture;
} catch (_) {}
if (!context.mounted) return; if (!context.mounted) return;
await Clipboard.setData( await Clipboard.setData(
ClipboardData( ClipboardData(
text: _buildMarkdown(context, pkg, imapCount, jmapCount), text: _buildMarkdown(
context,
pkg,
imapCount,
jmapCount,
deviceModel: deviceModel,
),
), ),
); );
if (context.mounted) { if (context.mounted) {
@@ -128,9 +172,19 @@ class _AboutScreenState extends ConsumerState<AboutScreen> {
try { try {
pkg = await _packageInfoFuture; pkg = await _packageInfoFuture;
} catch (_) {} } catch (_) {}
String? deviceModel;
try {
deviceModel = await _deviceModelFuture;
} catch (_) {}
if (!context.mounted) return; if (!context.mounted) return;
final body = Uri.encodeComponent( final body = Uri.encodeComponent(
_buildMarkdown(context, pkg, imapCount, jmapCount), _buildMarkdown(
context,
pkg,
imapCount,
jmapCount,
deviceModel: deviceModel,
),
); );
final url = Uri.parse( final url = Uri.parse(
'https://codeberg.org/guettli/sharedinbox/issues/new?body=$body', 'https://codeberg.org/guettli/sharedinbox/issues/new?body=$body',
@@ -186,6 +240,7 @@ class _AboutScreenState extends ConsumerState<AboutScreen> {
snapshot.data, snapshot.data,
imapCount, imapCount,
jmapCount, jmapCount,
deviceModel: _deviceModel,
), ),
selectable: true, selectable: true,
onTapLink: (text, href, title) { onTapLink: (text, href, title) {
+24
View File
@@ -249,6 +249,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.7.12" version: "0.7.12"
device_info_plus:
dependency: "direct main"
description:
name: device_info_plus
sha256: "6a642e1daa10190af89ba6cb6386c0df7d071a3592080bfe1e44faa63ae1df65"
url: "https://pub.dev"
source: hosted
version: "13.1.0"
device_info_plus_platform_interface:
dependency: transitive
description:
name: device_info_plus_platform_interface
sha256: "04b173a92e2d9161dfead145667037c8d834db725ce2e7b942bfe18fd2f45a46"
url: "https://pub.dev"
source: hosted
version: "8.1.0"
drift: drift:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -1284,6 +1300,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.3.0" version: "6.3.0"
win32_registry:
dependency: transitive
description:
name: win32_registry
sha256: "73b1d78920a9d6e03f8b4e43e612b87bf3152a0e5c5e5150267762b7c4116904"
url: "https://pub.dev"
source: hosted
version: "3.0.3"
workmanager: workmanager:
dependency: "direct main" dependency: "direct main"
description: description:
+1
View File
@@ -61,6 +61,7 @@ dependencies:
# App version metadata for crash reports # App version metadata for crash reports
package_info_plus: ^10.1.0 package_info_plus: ^10.1.0
share_plus: ^13.1.0 share_plus: ^13.1.0
device_info_plus: ^13.1.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
+1
View File
@@ -11,6 +11,7 @@ const _minCoveragePercent = 80;
// Pure-abstract interfaces: no executable code, Dart VM never instruments them. // Pure-abstract interfaces: no executable code, Dart VM never instruments them.
const _noCode = { const _noCode = {
'lib/core/db_schema_version.dart',
'lib/core/repositories/account_repository.dart', 'lib/core/repositories/account_repository.dart',
'lib/core/repositories/draft_repository.dart', 'lib/core/repositories/draft_repository.dart',
'lib/core/repositories/email_repository.dart', 'lib/core/repositories/email_repository.dart',
+6
View File
@@ -80,6 +80,9 @@ void main() {
expect(find.textContaining('Dark Mode'), findsWidgets); expect(find.textContaining('Dark Mode'), findsWidgets);
expect(find.textContaining('IMAP Accounts'), findsWidgets); expect(find.textContaining('IMAP Accounts'), findsWidgets);
expect(find.textContaining('JMAP Accounts'), findsWidgets); expect(find.textContaining('JMAP Accounts'), findsWidgets);
expect(find.textContaining('Locale'), findsWidgets);
expect(find.textContaining('Text Scale'), findsWidgets);
expect(find.textContaining('DB Schema Version'), findsWidgets);
// Buttons are in the body, not in the AppBar actions // Buttons are in the body, not in the AppBar actions
expect(find.byIcon(Icons.copy), findsOneWidget); expect(find.byIcon(Icons.copy), findsOneWidget);
expect(find.byIcon(Icons.bug_report), findsOneWidget); expect(find.byIcon(Icons.bug_report), findsOneWidget);
@@ -167,6 +170,9 @@ void main() {
expect(clipboardText, contains('Dark Mode')); expect(clipboardText, contains('Dark Mode'));
expect(clipboardText, contains('IMAP Accounts')); expect(clipboardText, contains('IMAP Accounts'));
expect(clipboardText, contains('JMAP Accounts')); expect(clipboardText, contains('JMAP Accounts'));
expect(clipboardText, contains('Locale'));
expect(clipboardText, contains('Text Scale'));
expect(clipboardText, contains('DB Schema Version'));
expect( expect(
clipboardText, clipboardText,
contains('[sharedinbox.de](https://sharedinbox.de)'), contains('[sharedinbox.de](https://sharedinbox.de)'),