From 5e5d38bb4cc148b6b164810ce0aa4434af02f49b Mon Sep 17 00:00:00 2001 From: problematicconsumer Date: Sat, 22 Jul 2023 16:02:06 +0330 Subject: [PATCH] Add about page --- assets/translations/strings.i18n.json | 8 ++ assets/translations/strings_fa.i18n.json | 8 ++ lib/core/router/app_router.dart | 3 + lib/core/router/routes/desktop_routes.dart | 12 +++ lib/core/router/routes/mobile_routes.dart | 17 ++++ lib/domain/constants.dart | 2 + lib/features/about/view/about_page.dart | 91 +++++++++++++++++++ lib/features/about/view/view.dart | 1 + lib/features/common/runtime_details.dart | 37 ++++++++ .../wrapper/view/desktop_wrapper.dart | 4 + lib/features/wrapper/view/mobile_wrapper.dart | 6 ++ macos/Flutter/GeneratedPluginRegistrant.swift | 4 + pubspec.lock | 56 ++++++++++++ pubspec.yaml | 2 + 14 files changed, 251 insertions(+) create mode 100644 lib/features/about/view/about_page.dart create mode 100644 lib/features/about/view/view.dart create mode 100644 lib/features/common/runtime_details.dart diff --git a/assets/translations/strings.i18n.json b/assets/translations/strings.i18n.json index 6dccf77f..055be2d2 100644 --- a/assets/translations/strings.i18n.json +++ b/assets/translations/strings.i18n.json @@ -104,6 +104,14 @@ } } }, + "about": { + "pageTitle": "about", + "version": "version", + "whatsNew": "what's new", + "sourceCode": "source code", + "telegramChannel": "telegram channel", + "checkForUpdate": "check for update" + }, "tray": { "dashboard": "dashboard", "quit": "quit", diff --git a/assets/translations/strings_fa.i18n.json b/assets/translations/strings_fa.i18n.json index 9c1ddae9..6a60021a 100644 --- a/assets/translations/strings_fa.i18n.json +++ b/assets/translations/strings_fa.i18n.json @@ -104,6 +104,14 @@ } } }, + "about": { + "pageTitle": "درباره", + "version": "ورژن", + "whatsNew": "تغییرات", + "sourceCode": "سورس کد", + "telegramChannel": "کانال تلگرام", + "checkForUpdate": "بررسی آپدیت جدید" + }, "tray": { "dashboard": "داشبورد", "quit": "خروج", diff --git a/lib/core/router/app_router.dart b/lib/core/router/app_router.dart index 67068e91..04d4d219 100644 --- a/lib/core/router/app_router.dart +++ b/lib/core/router/app_router.dart @@ -39,6 +39,7 @@ int getCurrentIndex(BuildContext context) { if (location.startsWith(ProxiesRoute.path)) return 1; if (location.startsWith(LogsRoute.path)) return 2; if (location.startsWith(SettingsRoute.path)) return 3; + if (location.startsWith(AboutRoute.path)) return 4; return 0; } @@ -52,5 +53,7 @@ void switchTab(int index, BuildContext context) { const LogsRoute().go(context); case 3: const SettingsRoute().go(context); + case 4: + const AboutRoute().go(context); } } diff --git a/lib/core/router/routes/desktop_routes.dart b/lib/core/router/routes/desktop_routes.dart index bdcad07b..302a8403 100644 --- a/lib/core/router/routes/desktop_routes.dart +++ b/lib/core/router/routes/desktop_routes.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:hiddify/core/router/routes/shared_routes.dart'; +import 'package:hiddify/features/about/view/view.dart'; import 'package:hiddify/features/logs/view/view.dart'; import 'package:hiddify/features/settings/view/view.dart'; import 'package:hiddify/features/wrapper/wrapper.dart'; @@ -20,6 +21,7 @@ part 'desktop_routes.g.dart'; TypedGoRoute(path: ProxiesRoute.path), TypedGoRoute(path: LogsRoute.path), TypedGoRoute(path: SettingsRoute.path), + TypedGoRoute(path: AboutRoute.path), ], ) class DesktopWrapperRoute extends ShellRouteData { @@ -50,3 +52,13 @@ class SettingsRoute extends GoRouteData { return const NoTransitionPage(child: SettingsPage()); } } + +class AboutRoute extends GoRouteData { + const AboutRoute(); + static const path = '/about'; + + @override + Page buildPage(BuildContext context, GoRouterState state) { + return const NoTransitionPage(child: AboutPage()); + } +} diff --git a/lib/core/router/routes/mobile_routes.dart b/lib/core/router/routes/mobile_routes.dart index d1a1d5b0..a0bd977b 100644 --- a/lib/core/router/routes/mobile_routes.dart +++ b/lib/core/router/routes/mobile_routes.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:hiddify/core/router/routes/shared_routes.dart'; +import 'package:hiddify/features/about/view/view.dart'; import 'package:hiddify/features/logs/view/view.dart'; import 'package:hiddify/features/settings/view/view.dart'; import 'package:hiddify/features/wrapper/wrapper.dart'; @@ -60,3 +61,19 @@ class SettingsRoute extends GoRouteData { ); } } + +@TypedGoRoute(path: AboutRoute.path) +class AboutRoute extends GoRouteData { + const AboutRoute(); + static const path = '/about'; + + static final GlobalKey $parentNavigatorKey = rootNavigatorKey; + + @override + Page buildPage(BuildContext context, GoRouterState state) { + return const MaterialPage( + fullscreenDialog: true, + child: AboutPage(), + ); + } +} diff --git a/lib/domain/constants.dart b/lib/domain/constants.dart index cb113d6a..e5451003 100644 --- a/lib/domain/constants.dart +++ b/lib/domain/constants.dart @@ -4,4 +4,6 @@ abstract class Constants { static const delayTestUrl = "https://www.google.com"; static const configFileName = "config"; static const countryMMDBFileName = "Country"; + static const githubUrl = "https://github.com/hiddify/hiddify-next"; + static const telegramChannelUrl = "https://t.me/hiddify"; } diff --git a/lib/features/about/view/about_page.dart b/lib/features/about/view/about_page.dart new file mode 100644 index 00000000..888989fc --- /dev/null +++ b/lib/features/about/view/about_page.dart @@ -0,0 +1,91 @@ +import 'package:flutter/material.dart'; +import 'package:gap/gap.dart'; +import 'package:hiddify/core/core_providers.dart'; +import 'package:hiddify/domain/constants.dart'; +import 'package:hiddify/features/common/runtime_details.dart'; +import 'package:hiddify/gen/assets.gen.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}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final t = ref.watch(translationsProvider); + final details = ref.watch(runtimeDetailsNotifierProvider); + + return Scaffold( + body: CustomScrollView( + slivers: [ + SliverAppBar( + title: Text(t.about.pageTitle.titleCase), + ), + ...switch (details) { + AsyncData(:final value) => [ + SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.all(16), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Assets.images.logo.svg(width: 64, height: 64), + const Gap(16), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + t.general.appTitle.titleCase, + style: Theme.of(context).textTheme.titleLarge, + ), + const Gap(4), + Text( + "${t.about.version} ${value.version} ${value.buildNumber}", + ), + ], + ) + ], + ), + ), + ), + SliverList( + delegate: SliverChildListDelegate( + [ + ListTile( + title: Text(t.about.whatsNew.sentenceCase), + ), + ListTile( + title: Text(t.about.sourceCode.sentenceCase), + trailing: const Icon(Icons.open_in_new), + onTap: () async { + await launchUrl( + Uri.parse(Constants.githubUrl), + mode: LaunchMode.externalApplication, + ); + }, + ), + ListTile( + title: Text(t.about.telegramChannel.sentenceCase), + trailing: const Icon(Icons.open_in_new), + onTap: () async { + await launchUrl( + Uri.parse(Constants.telegramChannelUrl), + mode: LaunchMode.externalApplication, + ); + }, + ), + ListTile( + title: Text(t.about.checkForUpdate.sentenceCase), + ), + ], + ), + ), + ], + _ => [], + } + ], + ), + ); + } +} diff --git a/lib/features/about/view/view.dart b/lib/features/about/view/view.dart new file mode 100644 index 00000000..8f120a21 --- /dev/null +++ b/lib/features/about/view/view.dart @@ -0,0 +1 @@ +export 'about_page.dart'; diff --git a/lib/features/common/runtime_details.dart b/lib/features/common/runtime_details.dart new file mode 100644 index 00000000..0251cfbb --- /dev/null +++ b/lib/features/common/runtime_details.dart @@ -0,0 +1,37 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:hiddify/utils/utils.dart'; +import 'package:package_info_plus/package_info_plus.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'runtime_details.freezed.dart'; +part 'runtime_details.g.dart'; + +// TODO implement clash version +@Riverpod(keepAlive: true) +class RuntimeDetailsNotifier extends _$RuntimeDetailsNotifier with AppLogger { + @override + Future build() async { + final packageInfo = await PackageInfo.fromPlatform(); + return RuntimeDetails( + version: packageInfo.version, + buildNumber: packageInfo.buildNumber, + installerStore: packageInfo.installerStore, + clashVersion: "", + ); + } +} + +@freezed +class RuntimeDetails with _$RuntimeDetails { + const RuntimeDetails._(); + + const factory RuntimeDetails({ + required String version, + required String buildNumber, + String? installerStore, + required String clashVersion, + }) = _RuntimeDetails; + + factory RuntimeDetails.fromJson(Map json) => + _$RuntimeDetailsFromJson(json); +} diff --git a/lib/features/wrapper/view/desktop_wrapper.dart b/lib/features/wrapper/view/desktop_wrapper.dart index 04910dac..bf2ccc69 100644 --- a/lib/features/wrapper/view/desktop_wrapper.dart +++ b/lib/features/wrapper/view/desktop_wrapper.dart @@ -33,6 +33,10 @@ class DesktopWrapper extends HookConsumerWidget { icon: const Icon(Icons.settings), label: Text(t.settings.pageTitle.titleCase), ), + NavigationRailDestination( + icon: const Icon(Icons.info), + label: Text(t.about.pageTitle.titleCase), + ), ]; return Scaffold( diff --git a/lib/features/wrapper/view/mobile_wrapper.dart b/lib/features/wrapper/view/mobile_wrapper.dart index ebd15fab..810ad6f4 100644 --- a/lib/features/wrapper/view/mobile_wrapper.dart +++ b/lib/features/wrapper/view/mobile_wrapper.dart @@ -42,6 +42,12 @@ class MobileWrapper extends HookConsumerWidget { selected: location == LogsRoute.path, onSelect: () => const LogsRoute().push(context), ), + DrawerTile( + label: t.about.pageTitle.titleCase, + icon: Icons.info, + selected: location == AboutRoute.path, + onSelect: () => const AboutRoute().push(context), + ), const Spacer(), Align( child: Column( diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 2429992a..7ff62db9 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -7,6 +7,7 @@ import Foundation import flutter_local_notifications import mobile_scanner +import package_info_plus import path_provider_foundation import protocol_handler import proxy_manager @@ -15,11 +16,13 @@ import share_plus import shared_preferences_foundation import sqlite3_flutter_libs import tray_manager +import url_launcher_macos import window_manager func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin")) MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin")) + FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) ProtocolHandlerPlugin.register(with: registry.registrar(forPlugin: "ProtocolHandlerPlugin")) ProxyManagerPlugin.register(with: registry.registrar(forPlugin: "ProxyManagerPlugin")) @@ -28,5 +31,6 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) Sqlite3FlutterLibsPlugin.register(with: registry.registrar(forPlugin: "Sqlite3FlutterLibsPlugin")) TrayManagerPlugin.register(with: registry.registrar(forPlugin: "TrayManagerPlugin")) + UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index 5b1d6f1d..406e2e67 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -645,6 +645,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.15.3" + http: + dependency: transitive + description: + name: http + sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" + url: "https://pub.dev" + source: hosted + version: "1.1.0" http_multi_server: dependency: transitive description: @@ -805,6 +813,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.0" + package_info_plus: + dependency: "direct main" + description: + name: package_info_plus + sha256: ceb027f6bc6a60674a233b4a90a7658af1aebdea833da0b5b53c1e9821a78c7b + url: "https://pub.dev" + source: hosted + version: "4.0.2" + package_info_plus_platform_interface: + dependency: transitive + description: + name: package_info_plus_platform_interface + sha256: "9bc8ba46813a4cc42c66ab781470711781940780fd8beddd0c3da62506d3a6c6" + url: "https://pub.dev" + source: hosted + version: "2.0.1" path: dependency: "direct main" description: @@ -1330,6 +1354,30 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.2" + url_launcher: + dependency: "direct main" + description: + name: url_launcher + sha256: "781bd58a1eb16069412365c98597726cd8810ae27435f04b3b4d3a470bacd61e" + url: "https://pub.dev" + source: hosted + version: "6.1.12" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: "78cb6dea3e93148615109e58e42c35d1ffbf5ef66c44add673d0ab75f12ff3af" + url: "https://pub.dev" + source: hosted + version: "6.0.37" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: "9af7ea73259886b92199f9e42c116072f05ff9bea2dcb339ab935dfc957392c2" + url: "https://pub.dev" + source: hosted + version: "6.1.4" url_launcher_linux: dependency: transitive description: @@ -1338,6 +1386,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.5" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: "1c4fdc0bfea61a70792ce97157e5cc17260f61abbe4f39354513f39ec6fd73b1" + url: "https://pub.dev" + source: hosted + version: "3.0.6" url_launcher_platform_interface: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index c5db5cf3..467fdeee 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -54,6 +54,7 @@ dependencies: share_plus: ^7.0.2 window_manager: ^0.3.5 tray_manager: ^0.2.0 + package_info_plus: ^4.0.2 # utils combine: ^0.5.3 @@ -76,6 +77,7 @@ dependencies: sliver_tools: ^0.2.12 flutter_adaptive_scaffold: ^0.1.6 fl_chart: ^0.63.0 + url_launcher: ^6.1.12 dev_dependencies: flutter_test: