Ver Fonte

[feat] make error widget sexy (#2825)

* feat: add translations

* feat: style error page

* chore: update api usage

* feat: add GitHub redirect

* chore: rebase error?

* chore: remove todo comment

* chore: remove impossible code

* fix: update url launcher invocation

* chore: use new api

* fix: analyzer errors

* fix: revert changes in Cargo.toml
Alex Wallen há 1 ano atrás
pai
commit
5cced92646

+ 6 - 1
frontend/appflowy_flutter/assets/translations/en.json

@@ -476,5 +476,10 @@
       "name": "Calendar layout"
     },
     "referencedCalendarPrefix": "View of"
+  },
+  "errorDialog": {
+    "title": "AppFlowy Error",
+    "howToFixFallback": "We're sorry for the inconvenience! Submit an issue on our GitHub page that describes your error.",
+    "github": "View on GitHub"
   }
-}
+}

+ 1 - 1
frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/board_page.dart

@@ -95,7 +95,7 @@ class BoardPage extends StatelessWidget {
                 (_) => BoardContent(
                   onEditStateChanged: onEditStateChanged,
                 ),
-                (err) => FlowyErrorPage(err.toString()),
+                (err) => FlowyErrorPage.message(err.toString(), howToFix: LocaleKeys.errorDialog_howToFixFallback.tr(),),
               );
             },
           );

+ 4 - 1
frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/grid_page.dart

@@ -125,7 +125,10 @@ class _GridPageState extends State<GridPage> {
               (_) => GridShortcuts(
                 child: GridPageContent(view: widget.view),
               ),
-              (err) => FlowyErrorPage(err.toString()),
+              (err) => FlowyErrorPage.message(
+                err.toString(),
+                howToFix: LocaleKeys.errorDialog_howToFixFallback.tr(),
+              ),
             ),
           );
         },

+ 6 - 2
frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/row_document.dart

@@ -1,9 +1,11 @@
+import 'package:appflowy/generated/locale_keys.g.dart';
 import 'package:appflowy/plugins/database_view/grid/application/row/row_document_bloc.dart';
 import 'package:appflowy/plugins/document/application/doc_bloc.dart';
 import 'package:appflowy/plugins/document/presentation/editor_page.dart';
 import 'package:appflowy/plugins/document/presentation/editor_style.dart';
 import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart';
 import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
+import 'package:easy_localization/easy_localization.dart';
 import 'package:flowy_infra_ui/widget/error_page.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
@@ -35,8 +37,9 @@ class RowDocument extends StatelessWidget {
             loading: () => const Center(
               child: CircularProgressIndicator.adaptive(),
             ),
-            error: (error) => FlowyErrorPage(
+            error: (error) => FlowyErrorPage.message(
               error.toString(),
+              howToFix: LocaleKeys.errorDialog_howToFixFallback.tr(),
             ),
             finish: () => RowEditor(
               viewPB: state.viewPB!,
@@ -94,8 +97,9 @@ class _RowEditorState extends State<RowEditor> {
             ),
             finish: (result) {
               return result.fold(
-                (error) => FlowyErrorPage(
+                (error) => FlowyErrorPage.message(
                   error.toString(),
+                  howToFix: LocaleKeys.errorDialog_howToFixFallback.tr(),
                 ),
                 (_) {
                   final editorState = documentBloc.editorState;

+ 6 - 2
frontend/appflowy_flutter/lib/plugins/document/document_page.dart

@@ -1,6 +1,7 @@
 import 'dart:convert';
 import 'dart:io';
 
+import 'package:appflowy/generated/locale_keys.g.dart';
 import 'package:appflowy/plugins/document/application/doc_bloc.dart';
 import 'package:appflowy/plugins/document/presentation/banner.dart';
 import 'package:appflowy/plugins/document/presentation/editor_page.dart';
@@ -14,11 +15,11 @@ import 'package:appflowy_backend/protobuf/flowy-document2/protobuf.dart'
     hide DocumentEvent;
 import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
 import 'package:appflowy_editor/appflowy_editor.dart';
+import 'package:easy_localization/easy_localization.dart';
 import 'package:flowy_infra_ui/flowy_infra_ui.dart';
 import 'package:flowy_infra_ui/widget/error_page.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
-import 'package:intl/intl.dart';
 import 'package:path/path.dart' as p;
 
 class DocumentPage extends StatefulWidget {
@@ -65,7 +66,10 @@ class _DocumentPageState extends State<DocumentPage> {
           return state.loadingState.when(
             loading: () => const SizedBox.shrink(),
             finish: (result) => result.fold(
-              (error) => FlowyErrorPage(error.toString()),
+              (error) => FlowyErrorPage.message(
+                error.toString(),
+                howToFix: LocaleKeys.errorDialog_howToFixFallback.tr(),
+              ),
               (data) {
                 if (state.forceClose) {
                   widget.onDeleted();

+ 16 - 6
frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/insert_page_command.dart

@@ -2,6 +2,7 @@ import 'package:appflowy/generated/locale_keys.g.dart';
 import 'package:appflowy/plugins/database_view/application/database_view_service.dart';
 import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
 import 'package:appflowy/workspace/application/view/view_service.dart';
+import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
 import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
 import 'package:appflowy_editor/appflowy_editor.dart';
 import 'package:easy_localization/easy_localization.dart';
@@ -31,14 +32,22 @@ extension InsertDatabase on EditorState {
     await apply(transaction);
   }
 
-  Future<void> insertReferencePage(ViewPB childView) async {
+  Future<void> insertReferencePage(
+    ViewPB childView,
+  ) async {
     final selection = this.selection;
     if (selection == null || !selection.isCollapsed) {
-      return;
+      throw FlowyError(
+        msg:
+            "Could not insert the reference page because the current selection was null or collapsed.",
+      );
     }
     final node = getNodeAtPath(selection.end.path);
     if (node == null) {
-      return;
+      throw FlowyError(
+        msg:
+            "Could not insert the reference page because the current node at the selection does not exist.",
+      );
     }
 
     // get the database id that the view is associated with
@@ -60,10 +69,11 @@ extension InsertDatabase on EditorState {
       databaseId: databaseId,
     ).then((value) => value.swap().toOption().toNullable());
 
-    // TODO(a-wallen): Show error dialog here.
-    // Maybe extend the FlowyErrorPage.
     if (ref == null) {
-      return;
+      throw FlowyError(
+        msg:
+            "The `ViewBackendService` failed to create a database reference view",
+      );
     }
 
     final transaction = this.transaction;

+ 16 - 3
frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/link_to_page_widget.dart

@@ -1,11 +1,14 @@
 import 'package:appflowy/plugins/document/presentation/editor_plugins/base/insert_page_command.dart';
 import 'package:appflowy/workspace/application/view/view_ext.dart';
 import 'package:appflowy/workspace/application/view/view_service.dart';
+import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
+import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
 import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
 import 'package:appflowy_editor/appflowy_editor.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra_ui/style_widget/button.dart';
 import 'package:flowy_infra_ui/style_widget/text.dart';
+import 'package:flowy_infra_ui/widget/error_page.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/services.dart';
 import 'package:appflowy/generated/locale_keys.g.dart';
@@ -37,9 +40,19 @@ void showLinkToPageMenu(
         editorState: editorState,
         layoutType: pageType,
         hintText: pageType.toHintText(),
-        onSelected: (appPB, viewPB) {
-          editorState.insertReferencePage(viewPB);
-          linkToPageMenuEntry.remove();
+        onSelected: (appPB, viewPB) async {
+          try {
+            await editorState.insertReferencePage(viewPB);
+            linkToPageMenuEntry.remove();
+          } on FlowyError catch (e) {
+            Dialogs.show(
+              FlowyErrorPage.message(
+                e.msg,
+                howToFix: LocaleKeys.errorDialog_howToFixFallback.tr(),
+              ),
+              context,
+            );
+          }
         },
       ),
     ),

+ 1 - 1
frontend/appflowy_flutter/lib/user/presentation/welcome_screen.dart

@@ -44,7 +44,7 @@ class WelcomeScreen extends StatelessWidget {
   Widget _renderBody(WelcomeState state) {
     final body = state.successOrFailure.fold(
       (_) => _renderList(state.workspaces),
-      (error) => FlowyErrorPage(error.toString()),
+      (error) => FlowyErrorPage.message(error.toString(), howToFix: LocaleKeys.errorDialog_howToFixFallback.tr(),),
     );
     return body;
   }

+ 183 - 3
frontend/appflowy_flutter/packages/flowy_infra_ui/lib/widget/error_page.dart

@@ -1,11 +1,191 @@
+import 'package:flowy_infra/image.dart';
+import 'package:flowy_infra_ui/flowy_infra_ui.dart';
 import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:url_launcher/url_launcher.dart';
 
 class FlowyErrorPage extends StatelessWidget {
-  final String error;
-  const FlowyErrorPage(this.error, {Key? key}) : super(key: key);
+  factory FlowyErrorPage.error(
+    Error e, {
+    required String howToFix,
+    Key? key,
+  }) =>
+      FlowyErrorPage._(
+        e.toString(),
+        stackTrace: e.stackTrace?.toString(),
+        howToFix: howToFix,
+        key: key,
+      );
+
+  factory FlowyErrorPage.message(
+    String message, {
+    required String howToFix,
+    String? stackTrace,
+    Key? key,
+  }) =>
+      FlowyErrorPage._(
+        message,
+        key: key,
+        stackTrace: stackTrace,
+        howToFix: howToFix,
+      );
+
+  factory FlowyErrorPage.exception(
+    Exception e, {
+    required String howToFix,
+    String? stackTrace,
+    Key? key,
+  }) =>
+      FlowyErrorPage._(
+        e.toString(),
+        stackTrace: stackTrace,
+        key: key,
+        howToFix: howToFix,
+      );
+
+  const FlowyErrorPage._(
+    this.message, {
+    required this.howToFix,
+    this.stackTrace,
+    super.key,
+  });
+
+  static const _titleFontSize = 24.0;
+  static const _titleToMessagePadding = 8.0;
+
+  final String message;
+  final String? stackTrace;
+  final String howToFix;
+
+  @override
+  Widget build(BuildContext context) {
+    return Padding(
+      padding: const EdgeInsets.all(8.0),
+      child: Column(
+        children: [
+          const FlowyText.medium(
+            "AppFlowy Error",
+            fontSize: _titleFontSize,
+          ),
+          const SizedBox(
+            height: _titleToMessagePadding,
+          ),
+          FlowyText.semibold(
+            message,
+          ),
+          const SizedBox(
+            height: _titleToMessagePadding,
+          ),
+          FlowyText.regular(
+            howToFix,
+          ),
+          const SizedBox(
+            height: _titleToMessagePadding,
+          ),
+          const GitHubRedirectButton(),
+          const SizedBox(
+            height: _titleToMessagePadding,
+          ),
+          if (stackTrace != null) StackTracePreview(stackTrace!),
+        ],
+      ),
+    );
+  }
+}
+
+class StackTracePreview extends StatelessWidget {
+  const StackTracePreview(
+    this.stackTrace, {
+    super.key,
+  });
+
+  final String stackTrace;
+
+  @override
+  Widget build(BuildContext context) {
+    return ConstrainedBox(
+      constraints: const BoxConstraints(
+        minWidth: 350,
+        maxWidth: 450,
+      ),
+      child: Card(
+        elevation: 0,
+        shape: RoundedRectangleBorder(
+          borderRadius: BorderRadius.circular(8),
+        ),
+        clipBehavior: Clip.antiAlias,
+        margin: EdgeInsets.zero,
+        child: Padding(
+          padding: const EdgeInsets.all(8.0),
+          child: Column(
+            children: [
+              const Align(
+                alignment: Alignment.centerLeft,
+                child: FlowyText.semibold(
+                  "Stack Trace",
+                ),
+              ),
+              Container(
+                height: 120,
+                padding: const EdgeInsets.symmetric(vertical: 8),
+                child: SingleChildScrollView(
+                  child: Text(
+                    stackTrace,
+                    style: Theme.of(context).textTheme.bodySmall,
+                  ),
+                ),
+              ),
+              Align(
+                alignment: Alignment.centerRight,
+                child: FlowyButton(
+                  hoverColor: Theme.of(context).colorScheme.onBackground,
+                  text: const FlowyText(
+                    "Copy",
+                  ),
+                  useIntrinsicWidth: true,
+                  onTap: () => Clipboard.setData(
+                    ClipboardData(text: stackTrace),
+                  ),
+                ),
+              ),
+            ],
+          ),
+        ),
+      ),
+    );
+  }
+}
+
+class GitHubRedirectButton extends StatelessWidget {
+  const GitHubRedirectButton({super.key});
+
+  static const _height = 32.0;
+
+  Uri get _gitHubNewBugUri => Uri(
+        scheme: 'https',
+        host: 'github.com',
+        path: '/AppFlowy-IO/AppFlowy/issues/new',
+        query:
+            'assignees=&labels=&projects=&template=bug_report.yaml&title=%5BBug%5D+',
+      );
 
   @override
   Widget build(BuildContext context) {
-    return Text(error);
+    return FlowyButton(
+      leftIconSize: const Size.square(_height),
+      text: const FlowyText(
+        "AppFlowy",
+      ),
+      useIntrinsicWidth: true,
+      leftIcon: Padding(
+        padding: const EdgeInsets.all(4.0),
+        child: svgWidget('login/github-mark'),
+      ),
+      onTap: () async {
+        if (await canLaunchUrl(_gitHubNewBugUri)) {
+          await launchUrl(_gitHubNewBugUri);
+        }
+      },
+    );
   }
 }

+ 1 - 0
frontend/appflowy_flutter/packages/flowy_infra_ui/pubspec.yaml

@@ -20,6 +20,7 @@ dependencies:
   animations: ^2.0.7
   loading_indicator: ^3.1.0
   async:
+  url_launcher: ^6.1.11
 
   # Federated Platform Interface
   flowy_infra_ui_platform_interface: