fix: improve crash reporting on Codeberg

- Pre-fill Codeberg issue with crash details (title and body).
- Remove unreliable canLaunchUrl check.
- Add SnackBar error handling if launch fails.
- Add https intent to Android queries manifest for better link visibility.
- Add widget test for CrashScreen.
This commit is contained in:
Thomas SharedInbox
2026-05-10 22:21:09 +02:00
parent 0895a79a33
commit 9815e105d3
4 changed files with 95 additions and 4 deletions
+4
View File
@@ -38,6 +38,10 @@
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="https" />
</intent>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
+26 -4
View File
@@ -90,11 +90,33 @@ class CrashScreen extends StatelessWidget {
const SizedBox(height: 16),
OutlinedButton.icon(
onPressed: () async {
final url = Uri.parse(
'https://codeberg.org/guettli/sharedinbox/issues/new',
final title = Uri.encodeComponent(
'Crash: ${exception.toString().split('\n').first}',
);
if (await canLaunchUrl(url)) {
await launchUrl(url, mode: LaunchMode.externalApplication);
final body = Uri.encodeComponent(
'Error: $exception\n\nStack Trace:\n$stackTrace',
);
final url = Uri.parse(
'https://codeberg.org/guettli/sharedinbox/issues/new?title=$title&body=$body',
);
try {
final launched = await launchUrl(
url,
mode: LaunchMode.externalApplication,
);
if (!launched && context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Could not open browser.'),
),
);
}
} catch (e) {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error: $e')),
);
}
}
},
icon: const Icon(Icons.bug_report),
+2
View File
@@ -57,6 +57,8 @@ dev_dependencies:
mockito: ^5.4.4
fake_async: ^1.3.1
sqlite3: any # used directly in test/unit/db_test_helper.dart
url_launcher_platform_interface: ^2.3.2
plugin_platform_interface: ^2.1.8
flutter:
uses-material-design: true
+63
View File
@@ -0,0 +1,63 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
import 'package:sharedinbox/ui/screens/crash_screen.dart';
import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart';
class MockUrlLauncher extends Mock
with MockPlatformInterfaceMixin
implements UrlLauncherPlatform {
String? launchedUrl;
LaunchOptions? launchOptions;
@override
Future<bool> canLaunch(String? url) async => true;
@override
Future<bool> launchUrl(String? url, LaunchOptions? options) async {
launchedUrl = url;
launchOptions = options;
return true;
}
}
void main() {
testWidgets('CrashScreen shows error details and has a report button',
(tester) async {
tester.view.physicalSize = const Size(800, 1200);
tester.view.devicePixelRatio = 1.0;
addTearDown(() => tester.view.resetPhysicalSize());
final mock = MockUrlLauncher();
UrlLauncherPlatform.instance = mock;
const exception = 'TestException: something broke';
final stackTrace = StackTrace.current;
await tester.pumpWidget(
MaterialApp(
home: CrashScreen(exception: exception, stackTrace: stackTrace),
),
);
expect(find.textContaining('TestException'), findsOneWidget);
expect(find.text('Report Issue on Codeberg'), findsOneWidget);
await tester.tap(find.text('Report Issue on Codeberg'));
await tester.pumpAndSettle();
expect(
mock.launchedUrl,
contains('https://codeberg.org/guettli/sharedinbox/issues/new'),
);
expect(
mock.launchedUrl,
contains('title=Crash%3A%20TestException%3A%20something%20broke'),
);
expect(
mock.launchedUrl,
contains('body=Error%3A%20TestException%3A%20something%20broke'),
);
});
}