mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
[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
This commit is contained in:
@ -476,5 +476,10 @@
|
|||||||
"name": "Calendar layout"
|
"name": "Calendar layout"
|
||||||
},
|
},
|
||||||
"referencedCalendarPrefix": "View of"
|
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -95,7 +95,7 @@ class BoardPage extends StatelessWidget {
|
|||||||
(_) => BoardContent(
|
(_) => BoardContent(
|
||||||
onEditStateChanged: onEditStateChanged,
|
onEditStateChanged: onEditStateChanged,
|
||||||
),
|
),
|
||||||
(err) => FlowyErrorPage(err.toString()),
|
(err) => FlowyErrorPage.message(err.toString(), howToFix: LocaleKeys.errorDialog_howToFixFallback.tr(),),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -125,7 +125,10 @@ class _GridPageState extends State<GridPage> {
|
|||||||
(_) => GridShortcuts(
|
(_) => GridShortcuts(
|
||||||
child: GridPageContent(view: widget.view),
|
child: GridPageContent(view: widget.view),
|
||||||
),
|
),
|
||||||
(err) => FlowyErrorPage(err.toString()),
|
(err) => FlowyErrorPage.message(
|
||||||
|
err.toString(),
|
||||||
|
howToFix: LocaleKeys.errorDialog_howToFixFallback.tr(),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -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/database_view/grid/application/row/row_document_bloc.dart';
|
||||||
import 'package:appflowy/plugins/document/application/doc_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_page.dart';
|
||||||
import 'package:appflowy/plugins/document/presentation/editor_style.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/plugins/document/presentation/more/cubit/document_appearance_cubit.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.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:flowy_infra_ui/widget/error_page.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
@ -35,8 +37,9 @@ class RowDocument extends StatelessWidget {
|
|||||||
loading: () => const Center(
|
loading: () => const Center(
|
||||||
child: CircularProgressIndicator.adaptive(),
|
child: CircularProgressIndicator.adaptive(),
|
||||||
),
|
),
|
||||||
error: (error) => FlowyErrorPage(
|
error: (error) => FlowyErrorPage.message(
|
||||||
error.toString(),
|
error.toString(),
|
||||||
|
howToFix: LocaleKeys.errorDialog_howToFixFallback.tr(),
|
||||||
),
|
),
|
||||||
finish: () => RowEditor(
|
finish: () => RowEditor(
|
||||||
viewPB: state.viewPB!,
|
viewPB: state.viewPB!,
|
||||||
@ -94,8 +97,9 @@ class _RowEditorState extends State<RowEditor> {
|
|||||||
),
|
),
|
||||||
finish: (result) {
|
finish: (result) {
|
||||||
return result.fold(
|
return result.fold(
|
||||||
(error) => FlowyErrorPage(
|
(error) => FlowyErrorPage.message(
|
||||||
error.toString(),
|
error.toString(),
|
||||||
|
howToFix: LocaleKeys.errorDialog_howToFixFallback.tr(),
|
||||||
),
|
),
|
||||||
(_) {
|
(_) {
|
||||||
final editorState = documentBloc.editorState;
|
final editorState = documentBloc.editorState;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
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/application/doc_bloc.dart';
|
||||||
import 'package:appflowy/plugins/document/presentation/banner.dart';
|
import 'package:appflowy/plugins/document/presentation/banner.dart';
|
||||||
import 'package:appflowy/plugins/document/presentation/editor_page.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;
|
hide DocumentEvent;
|
||||||
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||||
import 'package:appflowy_editor/appflowy_editor.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/flowy_infra_ui.dart';
|
||||||
import 'package:flowy_infra_ui/widget/error_page.dart';
|
import 'package:flowy_infra_ui/widget/error_page.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:intl/intl.dart';
|
|
||||||
import 'package:path/path.dart' as p;
|
import 'package:path/path.dart' as p;
|
||||||
|
|
||||||
class DocumentPage extends StatefulWidget {
|
class DocumentPage extends StatefulWidget {
|
||||||
@ -65,7 +66,10 @@ class _DocumentPageState extends State<DocumentPage> {
|
|||||||
return state.loadingState.when(
|
return state.loadingState.when(
|
||||||
loading: () => const SizedBox.shrink(),
|
loading: () => const SizedBox.shrink(),
|
||||||
finish: (result) => result.fold(
|
finish: (result) => result.fold(
|
||||||
(error) => FlowyErrorPage(error.toString()),
|
(error) => FlowyErrorPage.message(
|
||||||
|
error.toString(),
|
||||||
|
howToFix: LocaleKeys.errorDialog_howToFixFallback.tr(),
|
||||||
|
),
|
||||||
(data) {
|
(data) {
|
||||||
if (state.forceClose) {
|
if (state.forceClose) {
|
||||||
widget.onDeleted();
|
widget.onDeleted();
|
||||||
|
@ -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/database_view/application/database_view_service.dart';
|
||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||||
import 'package:appflowy/workspace/application/view/view_service.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_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
@ -31,14 +32,22 @@ extension InsertDatabase on EditorState {
|
|||||||
await apply(transaction);
|
await apply(transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> insertReferencePage(ViewPB childView) async {
|
Future<void> insertReferencePage(
|
||||||
|
ViewPB childView,
|
||||||
|
) async {
|
||||||
final selection = this.selection;
|
final selection = this.selection;
|
||||||
if (selection == null || !selection.isCollapsed) {
|
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);
|
final node = getNodeAtPath(selection.end.path);
|
||||||
if (node == null) {
|
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
|
// get the database id that the view is associated with
|
||||||
@ -60,10 +69,11 @@ extension InsertDatabase on EditorState {
|
|||||||
databaseId: databaseId,
|
databaseId: databaseId,
|
||||||
).then((value) => value.swap().toOption().toNullable());
|
).then((value) => value.swap().toOption().toNullable());
|
||||||
|
|
||||||
// TODO(a-wallen): Show error dialog here.
|
|
||||||
// Maybe extend the FlowyErrorPage.
|
|
||||||
if (ref == null) {
|
if (ref == null) {
|
||||||
return;
|
throw FlowyError(
|
||||||
|
msg:
|
||||||
|
"The `ViewBackendService` failed to create a database reference view",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final transaction = this.transaction;
|
final transaction = this.transaction;
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/base/insert_page_command.dart';
|
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_ext.dart';
|
||||||
import 'package:appflowy/workspace/application/view/view_service.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_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
import 'package:flowy_infra/image.dart';
|
import 'package:flowy_infra/image.dart';
|
||||||
import 'package:flowy_infra_ui/style_widget/button.dart';
|
import 'package:flowy_infra_ui/style_widget/button.dart';
|
||||||
import 'package:flowy_infra_ui/style_widget/text.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/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
@ -37,9 +40,19 @@ void showLinkToPageMenu(
|
|||||||
editorState: editorState,
|
editorState: editorState,
|
||||||
layoutType: pageType,
|
layoutType: pageType,
|
||||||
hintText: pageType.toHintText(),
|
hintText: pageType.toHintText(),
|
||||||
onSelected: (appPB, viewPB) {
|
onSelected: (appPB, viewPB) async {
|
||||||
editorState.insertReferencePage(viewPB);
|
try {
|
||||||
linkToPageMenuEntry.remove();
|
await editorState.insertReferencePage(viewPB);
|
||||||
|
linkToPageMenuEntry.remove();
|
||||||
|
} on FlowyError catch (e) {
|
||||||
|
Dialogs.show(
|
||||||
|
FlowyErrorPage.message(
|
||||||
|
e.msg,
|
||||||
|
howToFix: LocaleKeys.errorDialog_howToFixFallback.tr(),
|
||||||
|
),
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -44,7 +44,7 @@ class WelcomeScreen extends StatelessWidget {
|
|||||||
Widget _renderBody(WelcomeState state) {
|
Widget _renderBody(WelcomeState state) {
|
||||||
final body = state.successOrFailure.fold(
|
final body = state.successOrFailure.fold(
|
||||||
(_) => _renderList(state.workspaces),
|
(_) => _renderList(state.workspaces),
|
||||||
(error) => FlowyErrorPage(error.toString()),
|
(error) => FlowyErrorPage.message(error.toString(), howToFix: LocaleKeys.errorDialog_howToFixFallback.tr(),),
|
||||||
);
|
);
|
||||||
return body;
|
return body;
|
||||||
}
|
}
|
||||||
|
@ -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/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
class FlowyErrorPage extends StatelessWidget {
|
class FlowyErrorPage extends StatelessWidget {
|
||||||
final String error;
|
factory FlowyErrorPage.error(
|
||||||
const FlowyErrorPage(this.error, {Key? key}) : super(key: key);
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Text(error);
|
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 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);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ dependencies:
|
|||||||
animations: ^2.0.7
|
animations: ^2.0.7
|
||||||
loading_indicator: ^3.1.0
|
loading_indicator: ^3.1.0
|
||||||
async:
|
async:
|
||||||
|
url_launcher: ^6.1.11
|
||||||
|
|
||||||
# Federated Platform Interface
|
# Federated Platform Interface
|
||||||
flowy_infra_ui_platform_interface:
|
flowy_infra_ui_platform_interface:
|
||||||
|
Reference in New Issue
Block a user