diff --git a/frontend/appflowy_flutter/lib/core/helpers/url_launcher.dart b/frontend/appflowy_flutter/lib/core/helpers/url_launcher.dart new file mode 100644 index 0000000000..28820b9968 --- /dev/null +++ b/frontend/appflowy_flutter/lib/core/helpers/url_launcher.dart @@ -0,0 +1,50 @@ +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; + +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/workspace/presentation/home/toast.dart'; +import 'package:appflowy_backend/log.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:url_launcher/url_launcher.dart' as launcher; + +typedef OnFailureCallback = void Function(Uri uri); + +Future afLaunchUrl( + Uri uri, { + BuildContext? context, + OnFailureCallback? onFailure, + launcher.LaunchMode mode = launcher.LaunchMode.platformDefault, + String? webOnlyWindowName, +}) async { + try { + return await launcher.launchUrl( + uri, + mode: mode, + webOnlyWindowName: webOnlyWindowName, + ); + } on PlatformException catch (e) { + Log.error("Failed to open uri: $e"); + if (onFailure != null) { + onFailure(uri); + } else { + showMessageToast( + LocaleKeys.failedToOpenUrl.tr(args: [e.message ?? "PlatformException"]), + context: context, + ); + } + } + + return false; +} + +Future afLaunchUrlString(String url) async { + try { + final uri = Uri.parse(url); + + await launcher.launchUrl(uri); + } on PlatformException catch (e) { + Log.error("Failed to open uri: $e"); + } on FormatException catch (e) { + Log.error("Failed to parse url: $e"); + } +} diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/setting/about/about_setting_group.dart b/frontend/appflowy_flutter/lib/mobile/presentation/setting/about/about_setting_group.dart index dd4f2c2660..66d25c58c6 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/setting/about/about_setting_group.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/setting/about/about_setting_group.dart @@ -1,9 +1,10 @@ +import 'package:flutter/material.dart'; + +import 'package:appflowy/core/helpers/url_launcher.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/startup/tasks/device_info_task.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:flutter/material.dart'; import '../widgets/widgets.dart'; @@ -22,14 +23,14 @@ class AboutSettingGroup extends StatelessWidget { trailing: const Icon( Icons.chevron_right, ), - onTap: () => safeLaunchUrl('https://appflowy.io/privacy/app'), + onTap: () => afLaunchUrlString('https://appflowy.io/privacy/app'), ), MobileSettingItem( name: LocaleKeys.settings_mobile_termsAndConditions.tr(), trailing: const Icon( Icons.chevron_right, ), - onTap: () => safeLaunchUrl('https://appflowy.io/terms/app'), + onTap: () => afLaunchUrlString('https://appflowy.io/terms/app'), ), MobileSettingItem( name: LocaleKeys.settings_mobile_version.tr(), diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/setting/support_setting_group.dart b/frontend/appflowy_flutter/lib/mobile/presentation/setting/support_setting_group.dart index 03dc888c4c..89e306b18b 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/setting/support_setting_group.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/setting/support_setting_group.dart @@ -1,12 +1,13 @@ import 'dart:io'; +import 'package:flutter/material.dart'; + +import 'package:appflowy/core/helpers/url_launcher.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/mobile/presentation/bottom_sheet/show_mobile_bottom_sheet.dart'; import 'package:appflowy/mobile/presentation/widgets/widgets.dart'; import 'package:appflowy/util/share_log_files.dart'; -import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:easy_localization/easy_localization.dart'; -import 'package:flutter/material.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'widgets/widgets.dart'; @@ -28,7 +29,7 @@ class SupportSettingGroup extends StatelessWidget { trailing: const Icon( Icons.chevron_right, ), - onTap: () => safeLaunchUrl('https://discord.gg/JucBXeU2FE'), + onTap: () => afLaunchUrlString('https://discord.gg/JucBXeU2FE'), ), MobileSettingItem( name: LocaleKeys.workspace_errorActions_reportIssue.tr(), @@ -73,7 +74,7 @@ class _ReportIssuesWidget extends StatelessWidget { text: LocaleKeys.workspace_errorActions_reportIssueOnGithub.tr(), onTap: () { final String os = Platform.operatingSystem; - safeLaunchUrl( + afLaunchUrlString( 'https://github.com/AppFlowy-IO/AppFlowy/issues/new?assignees=&labels=&projects=&template=bug_report.yaml&title=[Bug]%20Mobile:%20&version=$version&os=$os', ); }, diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/widgets/flowy_mobile_state_container.dart b/frontend/appflowy_flutter/lib/mobile/presentation/widgets/flowy_mobile_state_container.dart index 9497d779dd..8aea36fbb9 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/widgets/flowy_mobile_state_container.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/widgets/flowy_mobile_state_container.dart @@ -1,9 +1,10 @@ import 'dart:io'; -import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy_editor/appflowy_editor.dart'; -import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; + +import 'package:appflowy/core/helpers/url_launcher.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:package_info_plus/package_info_plus.dart'; enum _FlowyMobileStateContainerType { @@ -80,7 +81,7 @@ class FlowyMobileStateContainer extends StatelessWidget { onPressed: () { final String? version = snapshot.data?.version; final String os = Platform.operatingSystem; - safeLaunchUrl( + afLaunchUrlString( 'https://github.com/AppFlowy-IO/AppFlowy/issues/new?assignees=&labels=&projects=&template=bug_report.yaml&title=[Bug]%20Mobile:%20&version=$version&os=$os&context=Error%20log:%20$errorMsg', ); }, @@ -90,7 +91,7 @@ class FlowyMobileStateContainer extends StatelessWidget { ), OutlinedButton( onPressed: () => - safeLaunchUrl('https://discord.gg/JucBXeU2FE'), + afLaunchUrlString('https://discord.gg/JucBXeU2FE'), child: Text( LocaleKeys.workspace_errorActions_reachOut.tr(), ), diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/cell/cell_cache.dart b/frontend/appflowy_flutter/lib/plugins/database/application/cell/cell_cache.dart index 0c4c5c0697..171f86f11d 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/application/cell/cell_cache.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/application/cell/cell_cache.dart @@ -4,7 +4,7 @@ import 'cell_controller.dart'; /// CellMemCache is used to cache cell data of each block. /// We use CellContext to index the cell in the cache. -/// Read https://appflowy.gitbook.io/docs/essential-documentation/contribute-to-appflowy/architecture/frontend/grid +/// Read https://docs.appflowy.io/docs/documentation/software-contributions/architecture/frontend/frontend/grid /// for more information class CellMemCache { CellMemCache(); diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/row/row_cache.dart b/frontend/appflowy_flutter/lib/plugins/database/application/row/row_cache.dart index 9482c4add0..331dd159da 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/application/row/row_cache.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/application/row/row_cache.dart @@ -1,13 +1,15 @@ import 'dart:collection'; +import 'package:flutter/foundation.dart'; + import 'package:appflowy/plugins/database/application/field/field_info.dart'; import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; -import 'package:flutter/foundation.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import '../cell/cell_cache.dart'; import '../cell/cell_controller.dart'; + import 'row_list.dart'; import 'row_service.dart'; @@ -25,7 +27,7 @@ abstract mixin class RowLifeCycle { void onRowDisposed(); } -/// Read https://appflowy.gitbook.io/docs/essential-documentation/contribute-to-appflowy/architecture/frontend/grid for more information. +/// Read https://docs.appflowy.io/docs/documentation/software-contributions/architecture/frontend/frontend/grid for more information. class RowCache { RowCache({ diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/view/view_cache.dart b/frontend/appflowy_flutter/lib/plugins/database/application/view/view_cache.dart index 2c1a92783a..77670fb0bb 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/application/view/view_cache.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/application/view/view_cache.dart @@ -1,10 +1,13 @@ import 'dart:async'; import 'dart:collection'; + import 'package:appflowy/plugins/database/application/row/row_service.dart'; import 'package:appflowy_backend/log.dart'; + import '../defines.dart'; import '../field/field_controller.dart'; import '../row/row_cache.dart'; + import 'view_listener.dart'; class DatabaseViewCallbacks { @@ -30,7 +33,7 @@ class DatabaseViewCallbacks { final OnRowsDeleted? onRowsDeleted; } -/// Read https://appflowy.gitbook.io/docs/essential-documentation/contribute-to-appflowy/architecture/frontend/grid for more information +/// Read https://docs.appflowy.io/docs/documentation/software-contributions/architecture/frontend/frontend/grid for more information class DatabaseViewCache { DatabaseViewCache({ required this.viewId, diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_url_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_url_cell.dart index 2ecd035999..4f3e69bad1 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_url_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_url_cell.dart @@ -1,16 +1,17 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +import 'package:appflowy/core/helpers/url_launcher.dart'; import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/database/application/cell/bloc/url_cell_bloc.dart'; import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart'; import 'package:appflowy/plugins/database/widgets/row/accessory/cell_accessory.dart'; import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; -import 'package:appflowy/plugins/database/application/cell/bloc/url_cell_bloc.dart'; import 'package:appflowy/workspace/presentation/home/toast.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/widget/flowy_tooltip.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:url_launcher/url_launcher_string.dart'; import '../editable_cell_skeleton/url.dart'; @@ -189,7 +190,7 @@ class _VisitURLAccessoryState extends State<_VisitURLAccessory> final shouldAddScheme = !['http', 'https'].any((pattern) => content.startsWith(pattern)); final url = shouldAddScheme ? 'http://$content' : content; - canLaunchUrlString(url).then((value) => launchUrlString(url)); + afLaunchUrlString(url); } } diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_grid/mobile_grid_url_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_grid/mobile_grid_url_cell.dart index 267ef65a37..6705a7d776 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_grid/mobile_grid_url_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_grid/mobile_grid_url_cell.dart @@ -1,13 +1,14 @@ +import 'package:flutter/material.dart'; + +import 'package:appflowy/core/helpers/url_launcher.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/mobile/presentation/bottom_sheet/show_mobile_bottom_sheet.dart'; +import 'package:appflowy/plugins/database/application/cell/bloc/url_cell_bloc.dart'; import 'package:appflowy/plugins/database/widgets/row/accessory/cell_accessory.dart'; import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; -import 'package:appflowy/plugins/database/application/cell/bloc/url_cell_bloc.dart'; import 'package:easy_localization/easy_localization.dart'; -import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; -import 'package:url_launcher/url_launcher_string.dart'; import '../editable_cell_skeleton/url.dart'; @@ -51,7 +52,7 @@ class MobileGridURLCellSkin extends IEditableURLCellSkin { final shouldAddScheme = !['http', 'https'] .any((pattern) => content.startsWith(pattern)); final url = shouldAddScheme ? 'http://$content' : content; - canLaunchUrlString(url).then((value) => launchUrlString(url)); + afLaunchUrlString(url); }, onLongPress: () => showMobileBottomSheet( context, diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_url_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_url_cell.dart index e48a82c054..ec4dc11826 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_url_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_url_cell.dart @@ -1,13 +1,14 @@ +import 'package:flutter/material.dart'; + +import 'package:appflowy/core/helpers/url_launcher.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/mobile/presentation/bottom_sheet/show_mobile_bottom_sheet.dart'; +import 'package:appflowy/plugins/database/application/cell/bloc/url_cell_bloc.dart'; import 'package:appflowy/plugins/database/widgets/row/accessory/cell_accessory.dart'; import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; -import 'package:appflowy/plugins/database/application/cell/bloc/url_cell_bloc.dart'; import 'package:easy_localization/easy_localization.dart'; -import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; -import 'package:url_launcher/url_launcher_string.dart'; import '../editable_cell_skeleton/url.dart'; @@ -34,7 +35,7 @@ class MobileRowDetailURLCellSkin extends IEditableURLCellSkin { final shouldAddScheme = !['http', 'https'] .any((pattern) => content.startsWith(pattern)); final url = shouldAddScheme ? 'http://$content' : content; - canLaunchUrlString(url).then((value) => launchUrlString(url)); + afLaunchUrlString(url); }, onLongPress: () => _showURLEditor(context, bloc, content), child: Container( diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/link_preview/custom_link_preview.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/link_preview/custom_link_preview.dart index cd7de6ff32..152e7ed20a 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/link_preview/custom_link_preview.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/link_preview/custom_link_preview.dart @@ -1,3 +1,6 @@ +import 'package:flutter/material.dart'; + +import 'package:appflowy/core/helpers/url_launcher.dart'; import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/mobile/presentation/widgets/widgets.dart'; @@ -7,10 +10,8 @@ import 'package:appflowy/shared/appflowy_network_image.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:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:provider/provider.dart'; -import 'package:url_launcher/url_launcher_string.dart'; class CustomLinkPreviewWidget extends StatelessWidget { const CustomLinkPreviewWidget({ @@ -113,7 +114,7 @@ class CustomLinkPreviewWidget extends StatelessWidget { if (PlatformExtension.isDesktopOrWeb) { return InkWell( - onTap: () => launchUrlString(url), + onTap: () => afLaunchUrlString(url), child: child, ); } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/util/learn_more_action.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/util/learn_more_action.dart index 49b047c758..17e89b1bca 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/util/learn_more_action.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/util/learn_more_action.dart @@ -1,10 +1,8 @@ -import 'package:url_launcher/url_launcher.dart'; +import 'package:appflowy/core/helpers/url_launcher.dart'; + +const String learnMoreUrl = + 'https://docs.appflowy.io/docs/appflowy/product/appflowy-x-openai'; Future openLearnMorePage() async { - final uri = Uri.parse( - 'https://appflowy.gitbook.io/docs/essential-documentation/appflowy-x-openai', - ); - if (await canLaunchUrl(uri)) { - await launchUrl(uri); - } + await afLaunchUrlString(learnMoreUrl); } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_style.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_style.dart index 1bf65ecd84..9095f1841e 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_style.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_style.dart @@ -3,6 +3,7 @@ import 'dart:math'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; +import 'package:appflowy/core/helpers/url_launcher.dart'; import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_block.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_toolbar_item/utils.dart'; @@ -293,7 +294,7 @@ class EditorStyleCustomizer { ..onTap = () { final editorState = context.read(); if (editorState.selection == null) { - safeLaunchUrl(href); + afLaunchUrlString(href); return; } diff --git a/frontend/appflowy_flutter/lib/user/application/auth/af_cloud_auth_service.dart b/frontend/appflowy_flutter/lib/user/application/auth/af_cloud_auth_service.dart index 0140316ed2..aabbd6e286 100644 --- a/frontend/appflowy_flutter/lib/user/application/auth/af_cloud_auth_service.dart +++ b/frontend/appflowy_flutter/lib/user/application/auth/af_cloud_auth_service.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:appflowy/core/helpers/url_launcher.dart'; import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/startup/tasks/appflowy_cloud_task.dart'; import 'package:appflowy/user/application/auth/auth_service.dart'; @@ -55,7 +56,7 @@ class AppFlowyCloudAuthService implements AuthService { (data) async { // Open the webview with oauth url final uri = Uri.parse(data.oauthUrl); - final isSuccess = await launchUrl( + final isSuccess = await afLaunchUrl( uri, mode: LaunchMode.externalApplication, webOnlyWindowName: '_self', diff --git a/frontend/appflowy_flutter/lib/user/presentation/screens/skip_log_in_screen.dart b/frontend/appflowy_flutter/lib/user/presentation/screens/skip_log_in_screen.dart index 60f49937ec..b125abf93c 100644 --- a/frontend/appflowy_flutter/lib/user/presentation/screens/skip_log_in_screen.dart +++ b/frontend/appflowy_flutter/lib/user/presentation/screens/skip_log_in_screen.dart @@ -1,4 +1,7 @@ +import 'package:flutter/material.dart'; + import 'package:appflowy/core/frameless_window.dart'; +import 'package:appflowy/core/helpers/url_launcher.dart'; import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/startup/startup.dart'; @@ -15,9 +18,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/language.dart'; import 'package:flowy_infra/size.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:url_launcher/url_launcher.dart'; class SkipLogInScreen extends StatefulWidget { const SkipLogInScreen({super.key}); @@ -158,9 +159,8 @@ class SubscribeButtons extends StatelessWidget { fontColor: Theme.of(context).colorScheme.primary, hoverColor: Colors.transparent, fillColor: Colors.transparent, - onPressed: () => _launchURL( - 'https://github.com/AppFlowy-IO/appflowy', - ), + onPressed: () => + afLaunchUrlString('https://github.com/AppFlowy-IO/appflowy'), ), ], ), @@ -179,22 +179,14 @@ class SubscribeButtons extends StatelessWidget { fontColor: Theme.of(context).colorScheme.primary, hoverColor: Colors.transparent, fillColor: Colors.transparent, - onPressed: () => _launchURL('https://www.appflowy.io/blog'), + onPressed: () => + afLaunchUrlString('https://www.appflowy.io/blog'), ), ], ), ], ); } - - Future _launchURL(String url) async { - final uri = Uri.parse(url); - if (await canLaunchUrl(uri)) { - await launchUrl(uri); - } else { - throw 'Could not launch $url'; - } - } } class LanguageSelectorOnWelcomePage extends StatelessWidget { diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/errors/workspace_failed_screen.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/errors/workspace_failed_screen.dart index 60f3330408..68cac12dc5 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/errors/workspace_failed_screen.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/errors/workspace_failed_screen.dart @@ -1,11 +1,12 @@ import 'dart:io'; +import 'package:flutter/material.dart'; + +import 'package:appflowy/core/helpers/url_launcher.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/widget/rounded_button.dart'; import 'package:flowy_infra_ui/widget/spacing.dart'; -import 'package:flutter/material.dart'; import 'package:package_info_plus/package_info_plus.dart'; class WorkspaceFailedScreen extends StatefulWidget { @@ -51,7 +52,7 @@ class _WorkspaceFailedScreenState extends State { title: LocaleKeys.workspace_errorActions_reportIssue.tr(), height: 40, - onPressed: () => safeLaunchUrl( + onPressed: () => afLaunchUrlString( 'https://github.com/AppFlowy-IO/AppFlowy/issues/new?assignees=&labels=&projects=&template=bug_report.yaml&title=[Bug]%20Workspace%20failed%20to%20load&version=$version&os=$os', ), ), @@ -62,7 +63,7 @@ class _WorkspaceFailedScreenState extends State { title: LocaleKeys.workspace_errorActions_reachOut.tr(), height: 40, onPressed: () => - safeLaunchUrl('https://discord.gg/JucBXeU2FE'), + afLaunchUrlString('https://discord.gg/JucBXeU2FE'), ), ), ], diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/toast.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/toast.dart index 57c3590a25..c890aea90c 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/toast.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/toast.dart @@ -1,9 +1,10 @@ +import 'package:flutter/material.dart'; + import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/startup/startup.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/size.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; -import 'package:flutter/material.dart'; import 'package:fluttertoast/fluttertoast.dart'; class FlowyMessageToast extends StatelessWidget { diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/setting_appflowy_cloud.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/setting_appflowy_cloud.dart index 30c7da23f2..6805633f74 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/setting_appflowy_cloud.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/setting_appflowy_cloud.dart @@ -1,3 +1,7 @@ +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; + +import 'package:appflowy/core/helpers/url_launcher.dart'; import 'package:appflowy/env/cloud_env.dart'; import 'package:appflowy/env/env.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; @@ -6,7 +10,6 @@ import 'package:appflowy/workspace/application/settings/appflowy_cloud_urls_bloc import 'package:appflowy/workspace/presentation/settings/widgets/_restart_app_button.dart'; import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; import 'package:appflowy_backend/dispatch/dispatch.dart'; -import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-user/user_setting.pb.dart'; import 'package:appflowy_result/appflowy_result.dart'; @@ -14,10 +17,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/size.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/widget/error_page.dart'; -import 'package:flutter/gestures.dart'; -import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:url_launcher/url_launcher.dart'; class AppFlowyCloudViewSetting extends StatelessWidget { const AppFlowyCloudViewSetting({ @@ -227,7 +227,8 @@ class AppFlowySelfhostTip extends StatelessWidget { color: Theme.of(context).colorScheme.primary, decoration: TextDecoration.underline, ), - recognizer: TapGestureRecognizer()..onTap = () => _launchURL(), + recognizer: TapGestureRecognizer() + ..onTap = () => afLaunchUrlString(url), ), TextSpan( text: LocaleKeys.settings_menu_selfHostEnd.tr(), @@ -238,15 +239,6 @@ class AppFlowySelfhostTip extends StatelessWidget { ), ); } - - Future _launchURL() async { - final uri = Uri.parse(url); - if (await canLaunchUrl(uri)) { - await launchUrl(uri); - } else { - Log.error("Could not launch $url"); - } - } } @visibleForTesting diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/setting_file_import_appflowy_data_view.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/setting_file_import_appflowy_data_view.dart index e3faa32a93..20498752b9 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/setting_file_import_appflowy_data_view.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/setting_file_import_appflowy_data_view.dart @@ -1,16 +1,16 @@ +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; + +import 'package:appflowy/core/helpers/url_launcher.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/workspace/application/settings/setting_file_importer_bloc.dart'; import 'package:appflowy/workspace/presentation/home/toast.dart'; -import 'package:appflowy_backend/log.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/file_picker/file_picker_service.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flutter/gestures.dart'; -import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:fluttertoast/fluttertoast.dart'; -import 'package:url_launcher/url_launcher.dart'; class ImportAppFlowyData extends StatefulWidget { const ImportAppFlowyData({super.key}); @@ -92,22 +92,14 @@ class AppFlowyDataImportTip extends StatelessWidget { color: Theme.of(context).colorScheme.primary, decoration: TextDecoration.underline, ), - recognizer: TapGestureRecognizer()..onTap = () => _launchURL(), + recognizer: TapGestureRecognizer() + ..onTap = () => afLaunchUrlString(url), ), ], ), ), ); } - - Future _launchURL() async { - final uri = Uri.parse(url); - if (await canLaunchUrl(uri)) { - await launchUrl(uri); - } else { - Log.error("Could not launch $url"); - } - } } class ImportAppFlowyDataButton extends StatefulWidget { diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/setting_supabase_cloud.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/setting_supabase_cloud.dart index dbfaa3f7f6..c014cdf516 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/setting_supabase_cloud.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/setting_supabase_cloud.dart @@ -1,3 +1,8 @@ +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +import 'package:appflowy/core/helpers/url_launcher.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/workspace/application/settings/supabase_cloud_setting_bloc.dart'; import 'package:appflowy/workspace/application/settings/supabase_cloud_urls_bloc.dart'; @@ -5,7 +10,6 @@ import 'package:appflowy/workspace/presentation/home/toast.dart'; import 'package:appflowy/workspace/presentation/settings/widgets/_restart_app_button.dart'; import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; import 'package:appflowy_backend/dispatch/dispatch.dart'; -import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-user/user_setting.pb.dart'; import 'package:appflowy_result/appflowy_result.dart'; @@ -14,11 +18,7 @@ import 'package:flowy_infra/size.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/flowy_tooltip.dart'; -import 'package:flutter/gestures.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:url_launcher/url_launcher.dart'; class SettingSupabaseCloudView extends StatelessWidget { const SettingSupabaseCloudView({required this.restartAppFlowy, super.key}); @@ -330,7 +330,8 @@ class SupabaseSelfhostTip extends StatelessWidget { color: Theme.of(context).colorScheme.primary, decoration: TextDecoration.underline, ), - recognizer: TapGestureRecognizer()..onTap = () => _launchURL(), + recognizer: TapGestureRecognizer() + ..onTap = () => afLaunchUrlString(url), ), TextSpan( text: LocaleKeys.settings_menu_selfHostEnd.tr(), @@ -341,13 +342,4 @@ class SupabaseSelfhostTip extends StatelessWidget { ), ); } - - Future _launchURL() async { - final uri = Uri.parse(url); - if (await canLaunchUrl(uri)) { - await launchUrl(uri); - } else { - Log.error("Could not launch $url"); - } - } } diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_file_customize_location_view.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_file_customize_location_view.dart index 0116690dbb..6f2a3c556f 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_file_customize_location_view.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_file_customize_location_view.dart @@ -1,5 +1,9 @@ import 'dart:io'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +import 'package:appflowy/core/helpers/url_launcher.dart'; import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/workspace/application/settings/settings_location_cubit.dart'; import 'package:easy_localization/easy_localization.dart'; @@ -8,11 +12,8 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/style_widget/hover.dart'; import 'package:flowy_infra_ui/widget/buttons/secondary_button.dart'; import 'package:flowy_infra_ui/widget/flowy_tooltip.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:styled_widget/styled_widget.dart'; -import 'package:url_launcher/url_launcher.dart'; import '../../../../generated/locale_keys.g.dart'; import '../../../../startup/startup.dart'; @@ -234,9 +235,7 @@ class _OpenStorageButton extends StatelessWidget { ), onPressed: () async { final uri = Directory(usingPath).uri; - if (await canLaunchUrl(uri)) { - await launchUrl(uri); - } + await afLaunchUrl(uri, context: context); }, ); } diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/theme_upload/theme_upload_learn_more_button.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/theme_upload/theme_upload_learn_more_button.dart index e386aa3ebc..628232bd71 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/theme_upload/theme_upload_learn_more_button.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/theme_upload/theme_upload_learn_more_button.dart @@ -1,3 +1,6 @@ +import 'package:flutter/material.dart'; + +import 'package:appflowy/core/helpers/url_launcher.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/workspace/presentation/settings/widgets/theme_upload/theme_upload_view.dart'; import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; @@ -5,14 +8,12 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/widget/buttons/secondary_button.dart'; import 'package:flowy_infra_ui/widget/error_page.dart'; -import 'package:flutter/material.dart'; -import 'package:url_launcher/url_launcher.dart'; class ThemeUploadLearnMoreButton extends StatelessWidget { const ThemeUploadLearnMoreButton({super.key}); static const learnMoreURL = - 'https://appflowy.gitbook.io/docs/essential-documentation/themes'; + 'https://docs.appflowy.io/docs/appflowy/product/themes'; @override Widget build(BuildContext context) { @@ -30,27 +31,29 @@ class ThemeUploadLearnMoreButton extends StatelessWidget { ), onPressed: () async { final uri = Uri.parse(learnMoreURL); - if (await canLaunchUrl(uri)) { - await launchUrl(uri); - } else { - if (context.mounted) { - await Dialogs.show( - context, - child: FlowyDialog( - child: FlowyErrorPage.message( - LocaleKeys - .settings_appearance_themeUpload_urlUploadFailure - .tr() - .replaceAll( - '{}', - uri.toString(), - ), - howToFix: LocaleKeys.errorDialog_howToFixFallback.tr(), + await afLaunchUrl( + uri, + context: context, + onFailure: (_) async { + if (context.mounted) { + await Dialogs.show( + context, + child: FlowyDialog( + child: FlowyErrorPage.message( + LocaleKeys + .settings_appearance_themeUpload_urlUploadFailure + .tr() + .replaceAll( + '{}', + uri.toString(), + ), + howToFix: LocaleKeys.errorDialog_howToFixFallback.tr(), + ), ), - ), - ); - } - } + ); + } + }, + ); }, ), ), diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/float_bubble/question_bubble.dart b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/float_bubble/question_bubble.dart index 0190e74a47..e72dfa098c 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/float_bubble/question_bubble.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/float_bubble/question_bubble.dart @@ -1,3 +1,7 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +import 'package:appflowy/core/helpers/url_launcher.dart'; import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/startup/tasks/rust_sdk.dart'; @@ -10,11 +14,8 @@ import 'package:flowy_infra/size.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/spacing.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:styled_widget/styled_widget.dart'; -import 'package:url_launcher/url_launcher.dart'; class QuestionBubble extends StatelessWidget { const QuestionBubble({super.key}); @@ -86,26 +87,26 @@ class _BubbleActionListState extends State { if (action is BubbleActionWrapper) { switch (action.inner) { case BubbleAction.whatsNews: - _launchURL("https://www.appflowy.io/what-is-new"); + afLaunchUrlString("https://www.appflowy.io/what-is-new"); break; case BubbleAction.help: - _launchURL("https://discord.gg/9Q2xaN37tV"); + afLaunchUrlString("https://discord.gg/9Q2xaN37tV"); break; case BubbleAction.debug: _DebugToast().show(); break; case BubbleAction.shortcuts: - _launchURL( - "https://appflowy.gitbook.io/docs/essential-documentation/shortcuts", + afLaunchUrlString( + "https://docs.appflowy.io/docs/appflowy/product/shortcuts", ); break; case BubbleAction.markdown: - _launchURL( - "https://appflowy.gitbook.io/docs/essential-documentation/markdown", + afLaunchUrlString( + "https://docs.appflowy.io/docs/appflowy/product/markdown", ); break; case BubbleAction.github: - _launchURL( + afLaunchUrlString( 'https://github.com/AppFlowy-IO/AppFlowy/issues/new/choose', ); break; @@ -116,15 +117,6 @@ class _BubbleActionListState extends State { }, ); } - - void _launchURL(String url) async { - final uri = Uri.parse(url); - if (await canLaunchUrl(uri)) { - await launchUrl(uri); - } else { - throw 'Could not launch $url'; - } - } } class _DebugToast { diff --git a/frontend/resources/translations/en.json b/frontend/resources/translations/en.json index d105849aa6..f07c4972a9 100644 --- a/frontend/resources/translations/en.json +++ b/frontend/resources/translations/en.json @@ -9,6 +9,7 @@ "title": "Title", "youCanAlso": "You can also", "and": "and", + "failedToOpenUrl": "Failed to open url: {}", "blockActions": { "addBelowTooltip": "Click to add below", "addAboveCmd": "Alt+click",