Compare commits

...
1 Commits
Author SHA1 Message Date
Thomas SharedInboxandClaude Sonnet 4.6 ab2363fd2b feat: show git commit link on crash screen (#150)
Add the git commit hash to the crash screen as a clickable link to the
Codeberg commit page (using GIT_HASH from --dart-define, same as the
about screen). When the report is copied to clipboard, include it as a
Markdown link so it appears in filed issues.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 13:39:13 +02:00
2 changed files with 79 additions and 0 deletions
+32
View File
@@ -15,6 +15,8 @@ class CrashScreen extends StatelessWidget {
final Object exception; final Object exception;
final StackTrace? stackTrace; final StackTrace? stackTrace;
static const _gitHash = String.fromEnvironment('GIT_HASH');
Future<String> _buildReport() async { Future<String> _buildReport() async {
String version = 'unknown'; String version = 'unknown';
try { try {
@@ -23,7 +25,11 @@ class CrashScreen extends StatelessWidget {
} catch (_) {} } catch (_) {}
final platform = final platform =
'${Platform.operatingSystem} ${Platform.operatingSystemVersion}'; '${Platform.operatingSystem} ${Platform.operatingSystemVersion}';
final gitLine = _gitHash.isNotEmpty
? 'Git Commit: [$_gitHash](https://codeberg.org/guettli/sharedinbox/commit/$_gitHash)\n'
: '';
return 'App Version: $version\n' return 'App Version: $version\n'
'$gitLine'
'Platform: $platform\n\n' 'Platform: $platform\n\n'
'Error:\n```\n$exception\n```\n\n' 'Error:\n```\n$exception\n```\n\n'
'Stack Trace:\n```\n$stackTrace\n```'; 'Stack Trace:\n```\n$stackTrace\n```';
@@ -92,6 +98,32 @@ class CrashScreen extends StatelessWidget {
), ),
), ),
], ],
if (_gitHash.isNotEmpty) ...[
const SizedBox(height: 16),
const Text(
'Git Commit:',
style: TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(height: 4),
GestureDetector(
onTap: () async {
final url = Uri.parse(
'https://codeberg.org/guettli/sharedinbox/commit/$_gitHash',
);
await launchUrl(
url,
mode: LaunchMode.externalApplication,
);
},
child: const Text(
_gitHash,
style: TextStyle(
color: Colors.blue,
decoration: TextDecoration.underline,
),
),
),
],
const SizedBox(height: 24), const SizedBox(height: 24),
FilledButton.icon( FilledButton.icon(
onPressed: () async { onPressed: () async {
+47
View File
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart'; import 'package:mockito/mockito.dart';
import 'package:package_info_plus/package_info_plus.dart'; import 'package:package_info_plus/package_info_plus.dart';
@@ -76,6 +77,52 @@ void main() {
expect(mock.launchedUrl, isNot(contains('Stack%20Trace'))); expect(mock.launchedUrl, isNot(contains('Stack%20Trace')));
}); });
testWidgets(
'CrashScreen copy-to-clipboard includes version and platform info',
(tester) async {
tester.view.physicalSize = const Size(800, 1200);
tester.view.devicePixelRatio = 1.0;
addTearDown(() => tester.view.resetPhysicalSize());
String? clipboardText;
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(
SystemChannels.platform,
(MethodCall call) async {
if (call.method == 'Clipboard.setData') {
clipboardText =
(call.arguments as Map<dynamic, dynamic>)['text'] as String?;
}
return null;
},
);
addTearDown(
() => tester.binding.defaultBinaryMessenger
.setMockMethodCallHandler(SystemChannels.platform, null),
);
const exception = 'TestException: clipboard test';
final stackTrace = StackTrace.current;
await tester.pumpWidget(
MaterialApp(
home: CrashScreen(exception: exception, stackTrace: stackTrace),
),
);
await tester.tap(find.text('Copy to Clipboard'));
await tester.pump();
await tester.pump();
await tester.pumpAndSettle();
expect(clipboardText, isNotNull);
expect(clipboardText, contains('App Version: 1.0.0+42'));
expect(clipboardText, contains('Platform:'));
expect(clipboardText, contains('TestException: clipboard test'));
// GIT_HASH is empty in test builds — no Git Commit line expected
expect(clipboardText, isNot(contains('Git Commit:')));
},
);
testWidgets( testWidgets(
'CrashScreen used as root widget — buttons work without ScaffoldMessenger crash', 'CrashScreen used as root widget — buttons work without ScaffoldMessenger crash',
(tester) async { (tester) async {