Skip to content

Commit

Permalink
Merge pull request #2089 from acterglobal/ben-better-error-showing
Browse files Browse the repository at this point in the history
Showing better Error messages
  • Loading branch information
gnunicorn authored Aug 28, 2024
2 parents 4a9dd31 + 7eed21e commit 0c65e6a
Show file tree
Hide file tree
Showing 34 changed files with 1,194 additions and 484 deletions.
2 changes: 1 addition & 1 deletion app/.env
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ RAGESHAKE_APP_VERSION=DEV

#enven:const
#enven:type=String
RAGESHAKE_URL=http://localhost:8003/api/submit
RAGESHAKE_URL=



Expand Down
1 change: 1 addition & 0 deletions app/integration_test/tests/bug_reporter.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'package:acter/features/bug_report/actions/open_bug_report.dart';
import 'package:acter/features/bug_report/pages/bug_report_page.dart';
import 'package:acter/features/home/data/keys.dart';
import 'package:acter/config/app_shell.dart';
Expand Down
3 changes: 0 additions & 3 deletions app/lib/common/providers/common_providers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@ import 'package:riverpod/riverpod.dart';

final _log = Logger('a3::common::common_providers');

// Loading Providers
final loadingProvider = StateProvider<bool>((ref) => false);

final genericUpdatesStream =
StreamProvider.family<int, String>((ref, key) async* {
final client = ref.watch(alwaysClientProvider);
Expand Down
6 changes: 6 additions & 0 deletions app/lib/common/providers/space_providers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ final spaceProvider =
throw 'Space not found';
});

final spaceIsBookmarkedProvider =
FutureProvider.family<bool, String>((ref, spaceId) async {
final space = await ref.watch(spaceProvider(spaceId).future);
return space.isBookmarked();
});

/// Attempts to map a spaceId to the space, but could come back empty (null) rather than throw.
/// keeps up to date with underlying client even if the space wasn't found initially,
final maybeSpaceProvider =
Expand Down
217 changes: 217 additions & 0 deletions app/lib/common/toolkit/errors/error_dialog.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
import 'package:acter/common/toolkit/errors/util.dart';
import 'package:acter/features/bug_report/actions/open_bug_report.dart';
import 'package:acter/features/bug_report/providers/bug_report_providers.dart';
import 'package:flutter/material.dart';
import 'package:quickalert/widgets/quickalert_buttons.dart';
import 'package:quickalert/widgets/quickalert_container.dart';
import 'package:quickalert/models/quickalert_options.dart';
import 'package:quickalert/quickalert.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';

class ActerErrorDialog extends StatelessWidget {
static const retryBtn = Key('error-dialog-retry-btn');

final Object error;
final StackTrace stack;
final VoidCallback? onRetryTap;

final String? title;
final String? text;
final String Function(Object error)? textBuilder;
final bool includeBugReportButton;

/// Dialog Border Radius
final double borderRadius;

const ActerErrorDialog({
super.key,
required this.error,
required this.stack,
this.onRetryTap,
this.title,
this.text,
this.textBuilder,
this.includeBugReportButton = true,
this.borderRadius = 15.0,
});

static Future show({
/// BuildContext
required BuildContext context,
required Object error,
required StackTrace stack,

/// Title of the dialog
String? title,

/// Text of the dialog
String? text,
VoidCallback? onRetryTap,
String Function(Object error)? textBuilder,
bool includeBugReportButton = true,

/// Dialog Border Radius
double borderRadius = 15.0,
}) {
return showGeneralDialog(
context: context,
pageBuilder: (context, anim1, __) => ActerErrorDialog(
error: error,
stack: stack,
title: title,
text: text,
textBuilder: textBuilder,
onRetryTap: onRetryTap,
borderRadius: borderRadius,
includeBugReportButton: includeBugReportButton,
),
);
}

@override
Widget build(BuildContext context) {
return AlertDialog(
contentPadding: EdgeInsets.zero,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(borderRadius),
),
content: errorDialog(context),
);
}

Widget errorDialog(BuildContext context) {
final theme = Theme.of(context);
final lang = L10n.of(context);
final err = ErrorCode.guessFromError(error);
QuickAlertOptions options = QuickAlertOptions(
title: title ??
switch (err) {
ErrorCode.notFound => lang.notFound,
_ => lang.fatalError,
},
text: text ?? (textBuilder != null ? textBuilder!(error) : null),
type: switch (err) {
ErrorCode.notFound => QuickAlertType.warning,
_ => QuickAlertType.error,
},
showCancelBtn: true,
showConfirmBtn: false,
cancelBtnText: lang.back,
borderRadius: borderRadius,
);
if (onRetryTap != null) {
options.showConfirmBtn = true;
options.confirmBtnColor = theme.primaryColor;
options.confirmBtnText = lang.retry;
options.onConfirmBtnTap = () {
onRetryTap!();
};
}

return _ActerErrorAlert(
error: error,
stack: stack,
options: options,
includeBugReportButton: includeBugReportButton,
);
}
}

class _ActerErrorAlert extends QuickAlertContainer {
final bool includeBugReportButton;
final Object error;
final StackTrace stack;

const _ActerErrorAlert({
required super.options,
required this.error,
required this.stack,
this.includeBugReportButton = true,
});

@override
Widget buildButtons() {
return _ActerErrorActionButtons(options: options);
}

@override
Widget buildHeader(context) {
final orginalHeader = super.buildHeader(context);
if (!includeBugReportButton || !isBugReportingEnabled) {
return orginalHeader;
}
return Stack(
children: [
orginalHeader,
Positioned(
right: 10,
top: 10,
child: TextButton(
child: Text(L10n.of(context).reportBug),
onPressed: () => openBugReport(
context,
queryParams: {
'error': error.toString(),
'stack': stack.toString(),
},
),
),
),
],
);
}
}

class _ActerErrorActionButtons extends QuickAlertButtons {
const _ActerErrorActionButtons({required super.options});

@override
Widget buildButton({
BuildContext? context,
required bool isOkayBtn,
required String text,
VoidCallback? onTap,
}) {
final btnText = Text(
text,
style: defaultTextStyle(isOkayBtn),
);
if (isOkayBtn) {
return buildOkayBtn(context: context, btnText: btnText, onTap: onTap);
}
return buildCancelBtn(btnText: btnText, onTap: onTap);
}

Widget buildOkayBtn({
BuildContext? context,
required Widget btnText,
VoidCallback? onTap,
}) {
return MaterialButton(
key: ActerErrorDialog.retryBtn,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15.0),
),
color: options.confirmBtnColor ?? Theme.of(context!).primaryColor,
onPressed: onTap,
child: Center(
child: Padding(
padding: const EdgeInsets.all(7.5),
child: btnText,
),
),
);
}

Widget buildCancelBtn({
required Widget btnText,
VoidCallback? onTap,
}) {
return GestureDetector(
onTap: onTap,
child: Center(
child: btnText,
),
);
}
}
55 changes: 55 additions & 0 deletions app/lib/common/toolkit/errors/error_page.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import 'package:acter/common/toolkit/errors/error_dialog.dart';
import 'package:flutter/material.dart';

/// ErrorPage shows a full-screen error to the user (covering other internal errors)
///
class ErrorPage extends StatelessWidget {
static const dialogKey = Key('error-page-dialog');

/// Put this widget in the Background of the screen to give context
final Widget background;
final Object error;
final StackTrace stack;
final VoidCallback? onRetryTap;

final String? title;
final String? text;
final String Function(Object error)? textBuilder;
final bool includeBugReportButton;

/// Dialog Border Radius
final double borderRadius;

const ErrorPage({
super.key,
required this.background,
required this.error,
required this.stack,
this.title,
this.text,
this.textBuilder,
this.onRetryTap,
this.borderRadius = 15.0,
this.includeBugReportButton = true,
});

@override
Widget build(BuildContext context) {
return Stack(
children: [
background,
ActerErrorDialog(
key: dialogKey,
error: error,
stack: stack,
includeBugReportButton: includeBugReportButton,
text: text,
textBuilder: textBuilder,
title: title,
onRetryTap: onRetryTap,
borderRadius: borderRadius,
),
],
);
}
}
13 changes: 13 additions & 0 deletions app/lib/common/toolkit/errors/util.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
enum ErrorCode {
notFound,
other;

static ErrorCode guessFromError(Object error) {
final errorStr = error.toString();
// yay, string-based error guessing!
if (errorStr.contains('not found')) {
return ErrorCode.notFound;
}
return ErrorCode.other;
}
}
2 changes: 1 addition & 1 deletion app/lib/common/widgets/spaces/select_space_form_field.dart
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ class SelectSpaceFormField extends ConsumerWidget {
data: (space) {
if (space == null) return Text(currentId!);
return SpaceChip(
space: space,
spaceId: space.roomId,
onTapOpenSpaceDetail: false,
useCompatView: useCompatView,
onTapSelectSpace: () {
Expand Down
Loading

0 comments on commit 0c65e6a

Please sign in to comment.