Skip to content

Commit e10b145

Browse files
committed
feat: generalize error page
1 parent ec4159d commit e10b145

File tree

2 files changed

+145
-32
lines changed

2 files changed

+145
-32
lines changed

apps/ubuntu_bootstrap/lib/app/installer_wizard.dart

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
33
import 'package:subiquity_client/subiquity_client.dart';
44
import 'package:ubuntu_bootstrap/app/installation_step.dart';
55
import 'package:ubuntu_bootstrap/app/installer_model.dart';
6+
import 'package:ubuntu_bootstrap/l10n.dart';
67
import 'package:ubuntu_bootstrap/pages.dart';
8+
import 'package:ubuntu_bootstrap/pages/autoinstall/autoinstall_model.dart';
79
import 'package:ubuntu_bootstrap/services.dart';
810
import 'package:ubuntu_provision/ubuntu_provision.dart';
911
import 'package:ubuntu_wizard/ubuntu_wizard.dart';
@@ -83,7 +85,12 @@ class _InstallWizard extends ConsumerWidget {
8385
...preInstallRoutes,
8486
InstallationStep.done.route: WizardRoute(
8587
builder: (_) => const DonePage(),
86-
onLoad: (_) => const DonePage().load(context, ref),
88+
onLoad: (_) {
89+
if (!ref.context.mounted) {
90+
return false;
91+
}
92+
return const DonePage().load(context, ref);
93+
},
8794
),
8895
InstallationStep.error.route: WizardRoute(
8996
builder: (_) => const ErrorPage(allowRestart: false),
@@ -146,24 +153,66 @@ class _AutoinstallWizard extends ConsumerWidget {
146153
onLoad: (_) => const DonePage().load(context, ref),
147154
),
148155
InstallationStep.error.route: WizardRoute(
149-
builder: (_) => const ErrorPage(allowRestart: false),
156+
builder: (_) => ErrorPage(
157+
allowRestart: false,
158+
errorParser: (e) => errorParser(
159+
e,
160+
UbuntuBootstrapLocalizations.of(context),
161+
),
162+
),
150163
),
151164
},
152165
);
153166
}
154167
}
155168

156-
class _ErrorWizard extends StatelessWidget {
169+
class _ErrorWizard extends ConsumerWidget {
157170
const _ErrorWizard();
158171

159172
@override
160-
Widget build(BuildContext context) {
173+
Widget build(BuildContext context, WidgetRef ref) {
174+
final status = ref.read(installerModelProvider.select((m) => m.status));
161175
return Wizard(
162176
routes: <String, WizardRoute>{
163177
InstallationStep.error.route: WizardRoute(
164-
builder: (_) => const ErrorPage(allowRestart: false),
178+
builder: (_) => ErrorPage(
179+
allowRestart: false,
180+
error: status,
181+
errorParser: (e) => errorParser(
182+
e,
183+
UbuntuBootstrapLocalizations.of(context),
184+
),
185+
),
165186
),
166187
},
167188
);
168189
}
169190
}
191+
192+
ErrorDetails? errorParser(Object? e, UbuntuBootstrapLocalizations l10n) =>
193+
switch (e) {
194+
ApplicationStatus(
195+
state: ApplicationState.ERROR,
196+
nonreportableError: NonReportableError(
197+
cause: 'AutoinstallUserSuppliedCmdError',
198+
message: final message,
199+
details: final details,
200+
)
201+
) =>
202+
ErrorDetails(
203+
title: l10n.installationFailed,
204+
message: [
205+
l10n.autoinstallErrorMessage,
206+
l10n.autoinstallErrorInstructions,
207+
],
208+
details: [
209+
message,
210+
details,
211+
].nonNulls.join('\n'),
212+
action: (ref) async {
213+
await ref.read(autoinstallModelProvider.notifier).restart();
214+
},
215+
actionLabel: l10n.restartInstaller,
216+
),
217+
_ => null,
218+
};

packages/ubuntu_provision/lib/src/error/error_page.dart

Lines changed: 91 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,41 @@ import 'package:flutter/material.dart';
33
import 'package:flutter_riverpod/flutter_riverpod.dart';
44
import 'package:ubuntu_provision/src/error/error_model.dart';
55
import 'package:ubuntu_provision/ubuntu_provision.dart';
6+
import 'package:ubuntu_utils/ubuntu_utils.dart';
67
import 'package:ubuntu_widgets/ubuntu_widgets.dart';
78
import 'package:ubuntu_wizard/ubuntu_wizard.dart';
89
import 'package:yaru/foundation.dart';
910
import 'package:yaru/icons.dart';
1011
import 'package:yaru/widgets.dart';
1112

13+
class ErrorDetails {
14+
const ErrorDetails({
15+
this.title,
16+
this.message,
17+
this.details,
18+
this.action,
19+
this.actionLabel,
20+
}) : assert((action == null) == (actionLabel == null));
21+
final String? title;
22+
final List<String>? message;
23+
final String? details;
24+
final void Function(WidgetRef)? action;
25+
final String? actionLabel;
26+
}
27+
1228
/// This is an error page that is shown when an unexpected error occurs.
1329
/// It shows a message and a the log.
1430
class ErrorPage extends ConsumerWidget with ProvisioningPage {
1531
const ErrorPage({
1632
required this.allowRestart,
33+
this.error,
34+
this.errorParser,
1735
super.key,
1836
});
1937

2038
final bool allowRestart;
39+
final Object? error;
40+
final ErrorDetails? Function(Object?)? errorParser;
2141

2242
@override
2343
Widget build(BuildContext context, WidgetRef ref) {
@@ -33,11 +53,37 @@ class ErrorPage extends ConsumerWidget with ProvisioningPage {
3353
final endText = match?.end != null ? bodyText.substring(match!.end) : '';
3454
final model = ref.watch(errorModelProvider);
3555

56+
final errorDetails =
57+
errorParser?.call(error ?? ModalRoute.of(context)?.settings.arguments);
58+
59+
final content = errorDetails?.message != null
60+
? errorDetails!.message!.map(Text.new).withSpacing(kWizardSpacing / 2)
61+
: [
62+
Text.rich(
63+
TextSpan(
64+
text: normalText,
65+
children: [
66+
TextSpan(
67+
text: linkText,
68+
style: const TextStyle(
69+
color: Colors.blue,
70+
decoration: TextDecoration.underline,
71+
),
72+
recognizer: TapGestureRecognizer()
73+
..onTap = model.launchApport,
74+
// Handle link tap
75+
),
76+
TextSpan(text: endText),
77+
],
78+
),
79+
),
80+
];
81+
3682
return WizardPage(
3783
headerPadding: EdgeInsets.zero,
3884
contentPadding: EdgeInsets.zero,
3985
title: YaruWindowTitleBar(
40-
title: Text(lang.errorPageTitle),
86+
title: Text(errorDetails?.title ?? lang.errorPageTitle),
4187
closeSemanticLabel: lang.closeIconSemanticLabel,
4288
maximizeSemanticLabel: lang.maximizeIconSemanticLabel,
4389
minimizeSemanticLabel: lang.minimizeIconSemanticLabel,
@@ -47,31 +93,45 @@ class ErrorPage extends ConsumerWidget with ProvisioningPage {
4793
Row(
4894
mainAxisAlignment: MainAxisAlignment.center,
4995
children: [
50-
SizedBox(
51-
width: 400,
52-
child: Column(
53-
mainAxisAlignment: MainAxisAlignment.center,
54-
children: [
55-
if (image != null) image,
56-
Text.rich(
57-
TextSpan(
58-
text: normalText,
59-
children: [
60-
TextSpan(
61-
text: linkText,
62-
style: const TextStyle(
63-
color: Colors.blue,
64-
decoration: TextDecoration.underline,
65-
),
66-
recognizer: TapGestureRecognizer()
67-
..onTap = model.launchApport,
68-
// Handle link tap
69-
),
70-
TextSpan(text: endText),
71-
],
96+
if (image != null)
97+
Expanded(
98+
flex: 6,
99+
child: image,
100+
),
101+
const SizedBox(width: kWizardSpacing),
102+
Expanded(
103+
flex: 8,
104+
child: Padding(
105+
padding: const EdgeInsets.only(right: 3 * kWizardSpacing),
106+
child: Column(
107+
mainAxisAlignment: MainAxisAlignment.center,
108+
crossAxisAlignment: CrossAxisAlignment.start,
109+
children: [
110+
Semantics(
111+
focused: true,
112+
header: true,
113+
child: Text(
114+
errorDetails?.title ?? lang.errorPageTitle,
115+
style: Theme.of(context).textTheme.titleLarge,
116+
),
72117
),
73-
),
74-
],
118+
SizedBox(height: kWizardSpacing),
119+
...content,
120+
if (errorDetails?.details != null) ...[
121+
const SizedBox(height: kWizardSpacing),
122+
YaruExpandable(
123+
expandButtonPosition:
124+
YaruExpandableButtonPosition.start,
125+
header: Text('Technical details'),
126+
child: TextFormField(
127+
initialValue: errorDetails!.details,
128+
readOnly: true,
129+
maxLines: null,
130+
),
131+
),
132+
],
133+
],
134+
),
75135
),
76136
),
77137
],
@@ -115,7 +175,11 @@ class ErrorPage extends ConsumerWidget with ProvisioningPage {
115175
model.copyWith(isLogVisible: !model.isLogVisible);
116176
},
117177
),
118-
const SizedBox(width: kWizardBarSpacing),
178+
if (errorDetails?.action != null)
179+
PushButton.filled(
180+
onPressed: () => errorDetails.action!.call(ref),
181+
child: Text(errorDetails!.actionLabel!),
182+
),
119183
WizardButton(
120184
label: allowRestart ? lang.restart : lang.close,
121185
highlighted: true,
@@ -128,7 +192,7 @@ class ErrorPage extends ConsumerWidget with ProvisioningPage {
128192
}
129193
},
130194
),
131-
],
195+
].withSpacing(kWizardBarSpacing),
132196
),
133197
);
134198
}

0 commit comments

Comments
 (0)