diff --git a/lib/features/about/view/about_page.dart b/lib/features/about/view/about_page.dart index 0516efe1..a156f8fb 100644 --- a/lib/features/about/view/about_page.dart +++ b/lib/features/about/view/about_page.dart @@ -6,10 +6,9 @@ import 'package:hiddify/domain/failures.dart'; import 'package:hiddify/features/common/new_version_dialog.dart'; import 'package:hiddify/features/common/runtime_details.dart'; import 'package:hiddify/gen/assets.gen.dart'; -import 'package:hiddify/utils/alerts.dart'; +import 'package:hiddify/utils/utils.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:recase/recase.dart'; -import 'package:url_launcher/url_launcher.dart'; class AboutPage extends HookConsumerWidget { const AboutPage({super.key}); @@ -76,7 +75,7 @@ class AboutPage extends HookConsumerWidget { "${t.about.version} ${value.version} ${value.buildNumber}", ), ], - ) + ), ], ), ), @@ -91,9 +90,8 @@ class AboutPage extends HookConsumerWidget { title: Text(t.about.sourceCode.sentenceCase), trailing: const Icon(Icons.open_in_new), onTap: () async { - await launchUrl( + await UriUtils.tryLaunch( Uri.parse(Constants.githubUrl), - mode: LaunchMode.externalApplication, ); }, ), @@ -101,9 +99,8 @@ class AboutPage extends HookConsumerWidget { title: Text(t.about.telegramChannel.sentenceCase), trailing: const Icon(Icons.open_in_new), onTap: () async { - await launchUrl( + await UriUtils.tryLaunch( Uri.parse(Constants.telegramChannelUrl), - mode: LaunchMode.externalApplication, ); }, ), @@ -127,7 +124,7 @@ class AboutPage extends HookConsumerWidget { ), ], _ => [], - } + }, ], ), ); diff --git a/lib/features/common/new_version_dialog.dart b/lib/features/common/new_version_dialog.dart index 25f196a1..7bd59e06 100644 --- a/lib/features/common/new_version_dialog.dart +++ b/lib/features/common/new_version_dialog.dart @@ -4,9 +4,9 @@ import 'package:go_router/go_router.dart'; import 'package:hiddify/core/core_providers.dart'; import 'package:hiddify/domain/app/app.dart'; import 'package:hiddify/domain/constants.dart'; +import 'package:hiddify/utils/utils.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:recase/recase.dart'; -import 'package:url_launcher/url_launcher.dart'; // TODO add release notes class NewVersionDialog extends HookConsumerWidget { @@ -86,9 +86,8 @@ class NewVersionDialog extends HookConsumerWidget { ), TextButton( onPressed: () async { - await launchUrl( + await UriUtils.tryLaunch( Uri.parse(Constants.githubLatestReleaseUrl), - mode: LaunchMode.externalApplication, ); }, child: Text(t.appUpdate.updateNowBtnTxt.titleCase), diff --git a/lib/features/logs/view/logs_page.dart b/lib/features/logs/view/logs_page.dart index a003f860..48e4f1d2 100644 --- a/lib/features/logs/view/logs_page.dart +++ b/lib/features/logs/view/logs_page.dart @@ -1,5 +1,3 @@ -import 'dart:io'; - import 'package:dartx/dartx.dart'; import 'package:flutter/material.dart'; import 'package:fpdart/fpdart.dart'; @@ -14,9 +12,7 @@ import 'package:hiddify/services/service_providers.dart'; import 'package:hiddify/utils/utils.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:recase/recase.dart'; -import 'package:share_plus/share_plus.dart'; import 'package:tint/tint.dart'; -import 'package:url_launcher/url_launcher.dart'; class LogsPage extends HookConsumerWidget with PresLogger { const LogsPage({super.key}); @@ -35,18 +31,18 @@ class LogsPage extends HookConsumerWidget with PresLogger { PopupMenuItem( child: Text(t.logs.shareCoreLogs.sentenceCase), onTap: () async { - await shareFileOrOpenDir( - filesEditor.coreLogsPath, - filesEditor.logsDir.uri, + await UriUtils.tryShareOrLaunchFile( + Uri.parse(filesEditor.coreLogsPath), + fileOrDir: filesEditor.logsDir.uri, ); }, ), PopupMenuItem( child: Text(t.logs.shareAppLogs.sentenceCase), onTap: () async { - await shareFileOrOpenDir( - filesEditor.appLogsPath, - filesEditor.logsDir.uri, + await UriUtils.tryShareOrLaunchFile( + Uri.parse(filesEditor.appLogsPath), + fileOrDir: filesEditor.logsDir.uri, ); }, ), @@ -164,17 +160,4 @@ class LogsPage extends HookConsumerWidget with PresLogger { return const Scaffold(); } } - - Future shareFileOrOpenDir(String path, Uri dir) async { - try { - if (Platform.isWindows || Platform.isLinux) { - await launchUrl(dir); - } else { - final file = XFile(path, mimeType: "text/plain"); - await Share.shareXFiles([file]); - } - } catch (err, stackTrace) { - loggy.warning("error sharing log file", err, stackTrace); - } - } } diff --git a/lib/features/settings/widgets/general_setting_tiles.dart b/lib/features/settings/widgets/general_setting_tiles.dart index 402f9b96..85f40824 100644 --- a/lib/features/settings/widgets/general_setting_tiles.dart +++ b/lib/features/settings/widgets/general_setting_tiles.dart @@ -7,10 +7,9 @@ import 'package:hiddify/core/prefs/prefs.dart'; import 'package:hiddify/core/theme/theme.dart'; import 'package:hiddify/features/settings/widgets/theme_mode_switch_button.dart'; import 'package:hiddify/services/service_providers.dart'; -import 'package:hiddify/utils/platform_utils.dart'; +import 'package:hiddify/utils/utils.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:recase/recase.dart'; -import 'package:url_launcher/url_launcher.dart'; class AppearanceSettingTiles extends HookConsumerWidget { const AppearanceSettingTiles({super.key}); @@ -111,7 +110,7 @@ class AppearanceSettingTiles extends HookConsumerWidget { trailing: const Icon(Icons.arrow_outward_outlined), onTap: () async { final path = ref.read(filesEditorServiceProvider).workingDir.uri; - launchUrl(path); + await UriUtils.tryLaunch(path); }, ), ], diff --git a/lib/utils/uri_utils.dart b/lib/utils/uri_utils.dart new file mode 100644 index 00000000..8e0d5aba --- /dev/null +++ b/lib/utils/uri_utils.dart @@ -0,0 +1,44 @@ +import 'dart:io'; + +import 'package:hiddify/utils/custom_loggers.dart'; +import 'package:loggy/loggy.dart'; +import 'package:share_plus/share_plus.dart'; +import 'package:url_launcher/url_launcher.dart'; + +abstract class UriUtils { + static final loggy = Loggy("UriUtils"); + + static Future tryShareOrLaunchFile(Uri uri, {Uri? fileOrDir}) async { + if (Platform.isWindows || Platform.isLinux) { + return tryLaunch(fileOrDir ?? uri); + } + return tryShareFile(uri); + } + + static Future tryLaunch(Uri uri) async { + try { + loggy.debug("launching [$uri]"); + if (!await canLaunchUrl(uri)) { + loggy.warning("can't launch [$uri]"); + return false; + } + return launchUrl(uri, mode: LaunchMode.externalApplication); + } catch (e, stackTrace) { + loggy.warning("error launching [$uri]", e, stackTrace); + return false; + } + } + + static Future tryShareFile(Uri uri, {String? mimeType}) async { + try { + loggy.debug("sharing [$uri]"); + final file = XFile(uri.path, mimeType: mimeType); + final result = await Share.shareXFiles([file]); + loggy.debug("share result: ${result.raw}"); + return result.status == ShareResultStatus.success; + } catch (e, stackTrace) { + loggy.warning("error sharing file [$uri]", e, stackTrace); + return false; + } + } +} diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index 0e7ab7a7..f1d68690 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -11,4 +11,5 @@ export 'number_formatters.dart'; export 'placeholders.dart'; export 'platform_utils.dart'; export 'text_utils.dart'; +export 'uri_utils.dart'; export 'validators.dart';