- Schema v33: add error_stack_trace and is_permanent columns to sync_logs - SyncLogEntry gains stackTrace and isPermanent fields; SyncLogRepository.log() gains matching optional parameters; IMAP and JMAP sync loops forward the stack trace string and isPermanent flag when writing error entries - New lib/ui/utils/about_markdown.dart utility shared by AboutScreen and the sync log copy feature; builds the markdown table including Android device info (manufacturer, model, OS version) via device_info_plus - AboutScreen uses the utility and adds Android device info row - SyncLogScreen: subtitle shows "Error (permanent)" for permanent errors; expanded view shows stack trace in red monospace; each tile has a Copy button that copies a markdown summary of the entry plus the About section - Migration test updated for v33; new repo test for stackTrace/isPermanent Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
69 lines
2.6 KiB
Dart
69 lines
2.6 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');
|
|
|
|
/// Builds the About markdown table used in [AboutScreen] and sync log copies.
|
|
///
|
|
/// Pass [androidInfo] when running on Android; omit on other platforms.
|
|
String buildAboutMarkdown({
|
|
required BuildContext context,
|
|
PackageInfo? pkg,
|
|
required int imapCount,
|
|
required int jmapCount,
|
|
AndroidDeviceInfo? androidInfo,
|
|
}) {
|
|
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 androidLines = androidInfo != null
|
|
? '| Android Manufacturer | ${androidInfo.manufacturer} |\n'
|
|
'| Android Model | ${androidInfo.model} |\n'
|
|
'| Android Version | ${androidInfo.version.release} |\n'
|
|
: '';
|
|
|
|
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'
|
|
'$androidLines'
|
|
'| 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';
|
|
}
|
|
|
|
/// Fetches Android device info, or null on non-Android platforms.
|
|
Future<AndroidDeviceInfo?> getAndroidDeviceInfo() async {
|
|
if (!Platform.isAndroid) return null;
|
|
try {
|
|
return await DeviceInfoPlugin().androidInfo;
|
|
} catch (_) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
String _capitalize(String s) =>
|
|
s.isEmpty ? s : '${s[0].toUpperCase()}${s.substring(1)}';
|