[workstation] Handle exceptions thrown while filing an issue report

This CL displays an error page when an exception is caught while filing
a user feedback report.

Change-Id: I3259255011f7a418def269bbba87725a33d787fb
Reviewed-on: https://fuchsia-review.googlesource.com/c/experiences/+/679945
Reviewed-by: Sanjay Chouksey <sanjayc@google.com>
Commit-Queue: Yeonhee Lee <yhlee@google.com>
diff --git a/session_shells/ermine/internationalization/lib/strings.dart b/session_shells/ermine/internationalization/lib/strings.dart
index 65fff0a..7b1faef 100644
--- a/session_shells/ermine/internationalization/lib/strings.dart
+++ b/session_shells/ermine/internationalization/lib/strings.dart
@@ -1347,6 +1347,20 @@
         args: [id],
       );
 
+  static String get failedToFileTitle => Intl.message(
+        'Failed to file your report',
+        name: 'failed to file your report',
+        desc: 'The title of the user feedback filing error page',
+      );
+
+  static String failedToFileDesc(String url) => Intl.message(
+        'Please try again later. Alternatively, you can create a report at $url.',
+        name: 'try to file the report later',
+        desc: 'The description of the user feedback filing error page',
+        examples: const {'url': 'go/workstation-feedback'},
+        args: [url],
+      );
+
   static String dataSharingLegalStatement(String legalHelpUrl,
           String privacyPolicyUrl, String termsOfServiceUrl,
           [String prefix = '[', String midfix = '](', String suffix = ')']) =>
diff --git a/session_shells/ermine/shell/lib/src/services/user_feedback_service.dart b/session_shells/ermine/shell/lib/src/services/user_feedback_service.dart
index 68c5c34..2e14f7c 100644
--- a/session_shells/ermine/shell/lib/src/services/user_feedback_service.dart
+++ b/session_shells/ermine/shell/lib/src/services/user_feedback_service.dart
@@ -3,17 +3,16 @@
 // found in the LICENSE file.
 
 import 'package:fidl_fuchsia_feedback/fidl_async.dart';
-import 'package:flutter/material.dart';
 import 'package:fuchsia_logger/logger.dart';
 import 'package:fuchsia_services/services.dart';
 import 'package:uuid/uuid.dart';
 import 'package:zircon/zircon.dart' as zircon;
 
-typedef FeedbackSubmitCallback = void Function(String url);
+typedef UserFeedbackCallback = void Function(String url);
 
 class UserFeedbackService {
-  late final FeedbackSubmitCallback onSubmit;
-  late final VoidCallback onError;
+  late final UserFeedbackCallback onSubmit;
+  late final UserFeedbackCallback onError;
 
   Future<void> submit(String title, String desc, String username) async {
     final uptime = zircon.System.clockGetMonotonic();
@@ -33,8 +32,6 @@
       ],
     );
 
-    // TODO(fxb/88445): Do one-off data sharing opt-in if it is currently opt-out.
-
     final reporter = CrashReporterProxy();
     final connection = Incoming.fromSvcPath()..connectToService(reporter);
 
@@ -44,8 +41,7 @@
       log.info('Filed a user feedback report with eventID: $eventId');
       onSubmit(eventId);
     } on Exception catch (e) {
-      // TODO(fxb/88445): Add the error handling UX
-      // onError();
+      onError(e.toString());
       log.warning('Failed to file user feedback: $e ${StackTrace.current}');
     }
     reporter.ctrl.close();
diff --git a/session_shells/ermine/shell/lib/src/states/app_state.dart b/session_shells/ermine/shell/lib/src/states/app_state.dart
index fca6a27..5f77b6f 100644
--- a/session_shells/ermine/shell/lib/src/states/app_state.dart
+++ b/session_shells/ermine/shell/lib/src/states/app_state.dart
@@ -56,6 +56,7 @@
   List<Map<String, String>> get appLaunchEntries;
   FeedbackPage get feedbackPage;
   String get feedbackUuid;
+  String get feedbackErrorMsg;
   String get simpleLocale;
   double get scale;
 
diff --git a/session_shells/ermine/shell/lib/src/states/app_state_impl.dart b/session_shells/ermine/shell/lib/src/states/app_state_impl.dart
index c62a663..43000b8 100644
--- a/session_shells/ermine/shell/lib/src/states/app_state_impl.dart
+++ b/session_shells/ermine/shell/lib/src/states/app_state_impl.dart
@@ -238,6 +238,11 @@
   final _feedbackUuid = ''.asObservable();
 
   @override
+  String get feedbackErrorMsg => _feedbackErrorMsg.value;
+  set feedbackErrorMsg(String value) => _feedbackErrorMsg.value = value;
+  final _feedbackErrorMsg = ''.asObservable();
+
+  @override
   bool get viewsVisible => _viewsVisible.value;
   late final _viewsVisible = () {
     return views.isNotEmpty && !isIdle;
@@ -943,8 +948,9 @@
     });
   }
 
-  void _onFeedbackError() {
+  void _onFeedbackError(String error) {
     runInAction(() {
+      _feedbackErrorMsg.value = error;
       _feedbackPage.value = FeedbackPage.failed;
     });
   }
diff --git a/session_shells/ermine/shell/lib/src/widgets/user_feedback.dart b/session_shells/ermine/shell/lib/src/widgets/user_feedback.dart
index 6d646a9..306f4bd 100644
--- a/session_shells/ermine/shell/lib/src/widgets/user_feedback.dart
+++ b/session_shells/ermine/shell/lib/src/widgets/user_feedback.dart
@@ -36,6 +36,8 @@
             );
           case FeedbackPage.submitted:
             return UserFeedbackSubmitted(app);
+          case FeedbackPage.failed:
+            return UserFeedbackError(app);
           default:
             return Offstage();
         }
@@ -270,3 +272,50 @@
         ),
       );
 }
+
+/// A page displayed when filing report has been failed.
+class UserFeedbackError extends StatelessWidget {
+  final AppState app;
+
+  const UserFeedbackError(this.app);
+
+  @override
+  Widget build(BuildContext context) => Container(
+        padding: EdgeInsets.symmetric(vertical: _kPageVerticalPaddings),
+        color: Theme.of(context).bottomAppBarColor,
+        child: FocusScope(
+          child: Center(
+            child: SizedBox(
+              width: _kContentWidth,
+              child: Column(
+                crossAxisAlignment: CrossAxisAlignment.start,
+                children: [
+                  Expanded(
+                    child: Column(
+                      mainAxisSize: MainAxisSize.min,
+                      crossAxisAlignment: CrossAxisAlignment.start,
+                      children: [
+                        Text(Strings.failedToFileTitle,
+                            style: Theme.of(context).textTheme.headline5),
+                        SizedBox(height: 32),
+                        Text(app.feedbackErrorMsg,
+                            style: Theme.of(context).textTheme.bodyText1),
+                        SizedBox(height: 24),
+                        Text(
+                            Strings.failedToFileDesc('go/workstation-feedback'),
+                            style: Theme.of(context).textTheme.bodyText1),
+                      ],
+                    ),
+                  ),
+                  OutlinedButton(
+                    child: Text(Strings.close.toUpperCase()),
+                    style: ErmineButtonStyle.outlinedButton(Theme.of(context)),
+                    onPressed: app.closeUserFeedback,
+                  ),
+                ],
+              ),
+            ),
+          ),
+        ),
+      );
+}