- Add copy button to each sync log tile; copies a markdown summary of the
entry plus the full About section (app version, platform, device info).
- Store stack trace and isPermanent flag on error entries (schema v33) so
bug reports contain enough context to diagnose device-specific failures
like MissingPluginException on Android.
- Add Android device info (manufacturer, model, OS version) to the About
screen via device_info_plus; shared with the sync log copy via a new
lib/ui/utils/about_markdown.dart utility.
- Show isPermanent in the subtitle ("Error (permanent)") and in the
copied markdown.
- Display stack trace in red monospace in the expanded tile view.
- Update migration tests to assert schema v33 columns exist.
- Update fake SyncLogRepository implementations in tests.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
74 lines
2.7 KiB
Dart
74 lines
2.7 KiB
Dart
import 'dart:io';
|
|
|
|
import 'package:device_info_plus/device_info_plus.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:package_info_plus/package_info_plus.dart';
|
|
|
|
const _gitHash = String.fromEnvironment('GIT_HASH');
|
|
|
|
/// Returns Android device info (manufacturer, model, OS release).
|
|
/// Returns an empty map on non-Android platforms or if the plugin fails.
|
|
Future<Map<String, String>> fetchAndroidDeviceInfo() async {
|
|
if (!Platform.isAndroid) return {};
|
|
try {
|
|
final info = await DeviceInfoPlugin().androidInfo;
|
|
return {
|
|
'Manufacturer': info.manufacturer,
|
|
'Model': info.model,
|
|
'Android Version': info.version.release,
|
|
};
|
|
} catch (_) {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
String _capitalize(String s) =>
|
|
s.isEmpty ? s : '${s[0].toUpperCase()}${s.substring(1)}';
|
|
|
|
/// Builds the standard "about" markdown table for sharing / bug reports.
|
|
///
|
|
/// Pass [deviceInfo] from [fetchAndroidDeviceInfo].
|
|
/// Pass [imapCount] and [jmapCount] when available; both default to 0.
|
|
String buildAboutMarkdown(
|
|
BuildContext context,
|
|
PackageInfo? pkg,
|
|
int imapCount,
|
|
int jmapCount,
|
|
Map<String, String> deviceInfo,
|
|
) {
|
|
final size = MediaQuery.of(context).size;
|
|
final pixelRatio = MediaQuery.of(context).devicePixelRatio;
|
|
final physW = (size.width * pixelRatio).toInt();
|
|
final physH = (size.height * pixelRatio).toInt();
|
|
final version = pkg != null ? '${pkg.version}+${pkg.buildNumber}' : 'unknown';
|
|
final versionDisplay = _gitHash.isNotEmpty
|
|
? '[$version](https://codeberg.org/guettli/sharedinbox/commit/$_gitHash)'
|
|
: version;
|
|
final osName = _capitalize(Platform.operatingSystem);
|
|
final isDark = MediaQuery.of(context).platformBrightness == Brightness.dark;
|
|
|
|
final gitCommitLine = _gitHash.isNotEmpty
|
|
? '| Git Commit | [$_gitHash](https://codeberg.org/guettli/sharedinbox/commit/$_gitHash) |\n'
|
|
: '';
|
|
|
|
final deviceLines =
|
|
deviceInfo.entries.map((e) => '| ${e.key} | ${e.value} |\n').join();
|
|
|
|
return '## [sharedinbox.de](https://sharedinbox.de)\n\n'
|
|
'| Property | Value |\n'
|
|
'|----------|-------|\n'
|
|
'| App Version | $versionDisplay |\n'
|
|
'$gitCommitLine'
|
|
'| Platform | ${Platform.operatingSystem} |\n'
|
|
'| $osName Version | ${Platform.operatingSystemVersion} |\n'
|
|
'$deviceLines'
|
|
'| 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'
|
|
'| IMAP Accounts | $imapCount |\n'
|
|
'| JMAP Accounts | $jmapCount |\n';
|
|
}
|