From 8709e9f38dc2a2387f95d2a03e1c83dd60c796f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bot=20of=20Thomas=20G=C3=BCttler?= Date: Mon, 25 May 2026 22:18:09 +0200 Subject: [PATCH] feat: add Locale, Text Scale, DB Schema Version, Device Model to About page (#258) (#263) --- .../plugins/GeneratedPluginRegistrant.java | 5 ++ lib/core/db_schema_version.dart | 1 + lib/data/db/database.dart | 3 +- lib/ui/screens/about_screen.dart | 63 +++++++++++++++++-- pubspec.lock | 24 +++++++ pubspec.yaml | 1 + scripts/check_coverage.dart | 1 + test/widget/about_screen_test.dart | 6 ++ 8 files changed, 99 insertions(+), 5 deletions(-) create mode 100644 lib/core/db_schema_version.dart diff --git a/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java b/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java index d086c3a..e87397e 100644 --- a/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java +++ b/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java @@ -15,6 +15,11 @@ import io.flutter.embedding.engine.FlutterEngine; public final class GeneratedPluginRegistrant { private static final String TAG = "GeneratedPluginRegistrant"; 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 { flutterEngine.getPlugins().add(new com.mr.flutter.plugin.filepicker.FilePickerPlugin()); } catch (Exception e) { diff --git a/lib/core/db_schema_version.dart b/lib/core/db_schema_version.dart new file mode 100644 index 0000000..a0b61cb --- /dev/null +++ b/lib/core/db_schema_version.dart @@ -0,0 +1 @@ +const int dbSchemaVersion = 32; diff --git a/lib/data/db/database.dart b/lib/data/db/database.dart index 18365a2..37a2283 100644 --- a/lib/data/db/database.dart +++ b/lib/data/db/database.dart @@ -6,6 +6,7 @@ import 'package:drift/native.dart'; import 'package:flutter/services.dart'; import 'package:path/path.dart' as p; import 'package:path_provider/path_provider.dart'; +import 'package:sharedinbox/core/db_schema_version.dart'; part 'database.g.dart'; @@ -329,7 +330,7 @@ class AppDatabase extends _$AppDatabase { AppDatabase([QueryExecutor? executor]) : super(executor ?? _openConnection()); @override - int get schemaVersion => 32; + int get schemaVersion => dbSchemaVersion; Future _createEmailFts() async { await customStatement(''' diff --git a/lib/ui/screens/about_screen.dart b/lib/ui/screens/about_screen.dart index 9252c8f..31dfd36 100644 --- a/lib/ui/screens/about_screen.dart +++ b/lib/ui/screens/about_screen.dart @@ -1,11 +1,13 @@ import 'dart:async'; import 'dart:io'; +import 'package:device_info_plus/device_info_plus.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_markdown_plus/flutter_markdown_plus.dart'; import 'package:flutter_riverpod/flutter_riverpod.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/di.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -19,7 +21,9 @@ class AboutScreen extends ConsumerStatefulWidget { class _AboutScreenState extends ConsumerState { final Future _packageInfoFuture = PackageInfo.fromPlatform(); + late final Future _deviceModelFuture; late final Stream> _accountsStream; + String? _deviceModel; static const _gitHash = String.fromEnvironment('GIT_HASH'); @@ -27,14 +31,35 @@ class _AboutScreenState extends ConsumerState { void initState() { super.initState(); _accountsStream = ref.read(accountRepositoryProvider).observeAccounts(); + _deviceModelFuture = _fetchDeviceModel(); + unawaited( + _deviceModelFuture.then((model) { + if (mounted) setState(() => _deviceModel = model); + }), + ); + } + + static Future _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( BuildContext context, PackageInfo? pkg, int imapCount, - int jmapCount, - ) { + int jmapCount, { + String? deviceModel, + }) { final size = MediaQuery.of(context).size; final pixelRatio = MediaQuery.of(context).devicePixelRatio; final physW = (size.width * pixelRatio).toInt(); @@ -46,10 +71,15 @@ class _AboutScreenState extends ConsumerState { : version; final osName = _capitalize(Platform.operatingSystem); 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 ? '| 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' '| Property | Value |\n' '|----------|-------|\n' @@ -57,12 +87,16 @@ class _AboutScreenState extends ConsumerState { '$gitCommitLine' '| Platform | ${Platform.operatingSystem} |\n' '| $osName Version | ${Platform.operatingSystemVersion} |\n' + '$deviceModelLine' '| Resolution | ${physW}x$physH px' ' (logical: ${size.width.toInt()}x${size.height.toInt()} pt,' ' ratio: ${pixelRatio.toStringAsFixed(1)}x) |\n' '| Dart Version | ${Platform.version.split(' ').first} |\n' '| Processors | ${Platform.numberOfProcessors} |\n' '| Dark Mode | ${isDark ? 'yes' : 'no'} |\n' + '| Locale | $locale |\n' + '| Text Scale | $textScale× |\n' + '| DB Schema Version | $dbSchemaVersion |\n' '| IMAP Accounts | $imapCount |\n' '| JMAP Accounts | $jmapCount |\n'; } @@ -79,10 +113,20 @@ class _AboutScreenState extends ConsumerState { try { pkg = await _packageInfoFuture; } catch (_) {} + String? deviceModel; + try { + deviceModel = await _deviceModelFuture; + } catch (_) {} if (!context.mounted) return; await Clipboard.setData( ClipboardData( - text: _buildMarkdown(context, pkg, imapCount, jmapCount), + text: _buildMarkdown( + context, + pkg, + imapCount, + jmapCount, + deviceModel: deviceModel, + ), ), ); if (context.mounted) { @@ -128,9 +172,19 @@ class _AboutScreenState extends ConsumerState { try { pkg = await _packageInfoFuture; } catch (_) {} + String? deviceModel; + try { + deviceModel = await _deviceModelFuture; + } catch (_) {} if (!context.mounted) return; final body = Uri.encodeComponent( - _buildMarkdown(context, pkg, imapCount, jmapCount), + _buildMarkdown( + context, + pkg, + imapCount, + jmapCount, + deviceModel: deviceModel, + ), ); final url = Uri.parse( 'https://codeberg.org/guettli/sharedinbox/issues/new?body=$body', @@ -186,6 +240,7 @@ class _AboutScreenState extends ConsumerState { snapshot.data, imapCount, jmapCount, + deviceModel: _deviceModel, ), selectable: true, onTapLink: (text, href, title) { diff --git a/pubspec.lock b/pubspec.lock index 2be3b42..30a0a54 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -249,6 +249,22 @@ packages: url: "https://pub.dev" source: hosted 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: dependency: "direct main" description: @@ -1284,6 +1300,14 @@ packages: url: "https://pub.dev" source: hosted 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: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index e1e977b..545eed5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -61,6 +61,7 @@ dependencies: # App version metadata for crash reports package_info_plus: ^10.1.0 share_plus: ^13.1.0 + device_info_plus: ^13.1.0 dev_dependencies: flutter_test: diff --git a/scripts/check_coverage.dart b/scripts/check_coverage.dart index 448b1e4..e6cc7a6 100644 --- a/scripts/check_coverage.dart +++ b/scripts/check_coverage.dart @@ -11,6 +11,7 @@ const _minCoveragePercent = 80; // Pure-abstract interfaces: no executable code, Dart VM never instruments them. const _noCode = { + 'lib/core/db_schema_version.dart', 'lib/core/repositories/account_repository.dart', 'lib/core/repositories/draft_repository.dart', 'lib/core/repositories/email_repository.dart', diff --git a/test/widget/about_screen_test.dart b/test/widget/about_screen_test.dart index 6782fae..5c86718 100644 --- a/test/widget/about_screen_test.dart +++ b/test/widget/about_screen_test.dart @@ -80,6 +80,9 @@ void main() { expect(find.textContaining('Dark Mode'), findsWidgets); expect(find.textContaining('IMAP 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 expect(find.byIcon(Icons.copy), findsOneWidget); expect(find.byIcon(Icons.bug_report), findsOneWidget); @@ -167,6 +170,9 @@ void main() { expect(clipboardText, contains('Dark Mode')); expect(clipboardText, contains('IMAP Accounts')); expect(clipboardText, contains('JMAP Accounts')); + expect(clipboardText, contains('Locale')); + expect(clipboardText, contains('Text Scale')); + expect(clipboardText, contains('DB Schema Version')); expect( clipboardText, contains('[sharedinbox.de](https://sharedinbox.de)'),