1
0
forked from firka/firka

firka_wear: add error screen

Closes #12
This commit is contained in:
2026-03-04 13:43:11 +01:00
parent c2879766eb
commit 23f7f7cd48
5 changed files with 119 additions and 21 deletions

View File

@@ -9,6 +9,10 @@ import 'package:firka_wear/services/wear_sync_store.dart';
late final Logger logger;
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
/// When non-null, the app should show [WearErrorScreen] with this error.
final ValueNotifier<FlutterErrorDetails?> globalErrorNotifier =
ValueNotifier<FlutterErrorDetails?>(null);
late WearAppInitialization initData;
bool initDone = false;

View File

@@ -1,13 +1,13 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:wear_plus/wear_plus.dart';
import 'package:firka_wear/app/app_state.dart';
import 'package:firka_wear/app/initialization.dart';
import 'package:firka_wear/core/bloc/wear_sync_cubit.dart';
import 'package:firka_wear/l10n/app_localizations.dart';
import 'package:firka_wear/ui/theme/style.dart';
import 'package:firka_wear/ui/wear/screens/error/error_screen.dart';
import 'package:firka_wear/ui/wear/screens/home/home_screen.dart';
import 'package:firka_wear/ui/wear/screens/login/login_screen.dart';
@@ -25,25 +25,7 @@ class WearInitializationScreen extends StatelessWidget {
if (snapshot.hasError) {
return MaterialApp(
key: ValueKey('firkaErrorPage'),
home: Scaffold(
body: Center(
child: WatchShape(
builder: (context, shape, child) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'Error initializing app: ${snapshot.error}',
style: TextStyle(color: Colors.red),
),
child!,
],
);
},
child: SizedBox(),
),
),
),
home: WearErrorScreen(exception: snapshot.error!),
);
}

View File

@@ -1,7 +1,9 @@
import 'dart:async';
import 'dart:io';
import 'package:firka_wear/app/app_state.dart';
import 'package:firka_wear/app/initialization_screen.dart';
import 'package:firka_wear/ui/wear/screens/error/error_screen.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:logging/logging.dart';
@@ -26,5 +28,49 @@ void main() async {
await ScreenUtil.ensureScreenSize();
runApp(WearInitializationScreen());
FlutterError.onError = (FlutterErrorDetails details) {
FlutterError.presentError(details);
if (_isFatalError(details.exception)) {
globalErrorNotifier.value = details;
}
};
runZonedGuarded(() => runApp(const _WearAppWrapper()), (
Object error,
StackTrace stackTrace,
) {
if (_isFatalError(error)) {
globalErrorNotifier.value = FlutterErrorDetails(
exception: error,
stack: stackTrace,
library: 'firka_wear',
);
}
});
}
bool _isFatalError(Object error) {
return error is! AssertionError;
}
class _WearAppWrapper extends StatelessWidget {
const _WearAppWrapper();
@override
Widget build(BuildContext context) {
return ValueListenableBuilder<FlutterErrorDetails?>(
valueListenable: globalErrorNotifier,
builder: (context, error, _) {
if (error != null) {
return MaterialApp(
home: WearErrorScreen(
exception: error.exception,
stackTrace: error.stack,
),
);
}
return WearInitializationScreen();
},
);
}
}

View File

@@ -0,0 +1,65 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:qr_flutter/qr_flutter.dart';
import 'package:wear_plus/wear_plus.dart';
import 'package:firka_wear/ui/theme/style.dart';
final int _kMaxQrPayloadChars = 410;
String errorPayload(Object exception, [StackTrace? stackTrace]) {
final buffer = StringBuffer();
buffer.writeln(exception.toString());
if (stackTrace != null) buffer.write(stackTrace.toString());
final s = buffer.toString();
return s.length > _kMaxQrPayloadChars
? s.substring(0, _kMaxQrPayloadChars)
: s;
}
/// Full-screen error UI: encodes [exception] (and [stackTrace]) into a QR code
/// scaled to fit the watch's circular display so it is not clipped.
class WearErrorScreen extends StatelessWidget {
final Object exception;
final StackTrace? stackTrace;
const WearErrorScreen({super.key, required this.exception, this.stackTrace});
@override
Widget build(BuildContext context) {
ScreenUtil.init(context);
final payload = errorPayload(exception, stackTrace);
return Scaffold(
backgroundColor: wearStyle.colors.background,
body: LayoutBuilder(
builder: (context, constraints) {
return Center(
child: WatchShape(
builder: (context, shape, child) {
return SizedBox(
width: 350.w,
height: 350.h,
child: QrImageView(
data: payload,
version: 13,
backgroundColor: wearStyle.colors.background,
eyeStyle: QrEyeStyle(
eyeShape: QrEyeShape.square,
color: wearStyle.colors.textPrimary,
),
dataModuleStyle: QrDataModuleStyle(
dataModuleShape: QrDataModuleShape.square,
color: wearStyle.colors.textPrimary,
),
),
);
},
child: const SizedBox.shrink(),
),
);
},
),
);
}
}

View File

@@ -62,6 +62,7 @@ dependencies:
flutter_svg: ^1.1.6
logging: ^1.3.0
flutter_bloc: ^9.0.0
qr_flutter: ^4.1.0
dev_dependencies:
build_runner: any