diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..2a660029 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,33 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Hiddify Dev", + "request": "launch", + "type": "dart", + "flutterMode": "debug", + "program": "lib/main_dev.dart", + }, + { + "name": "Hiddify Dev Release", + "request": "launch", + "type": "dart", + "flutterMode": "release", + "program": "lib/main_dev.dart", + }, + { + "name": "Hiddify Dev Profile", + "request": "launch", + "type": "dart", + "flutterMode": "profile", + "program": "lib/main_dev.dart", + }, + { + "name": "Hiddify Prod", + "request": "launch", + "type": "dart", + "flutterMode": "release", + "program": "lib/main_prod.dart", + } + ] +} \ No newline at end of file diff --git a/Makefile b/Makefile index 7b66a2ac..56e2c0df 100644 --- a/Makefile +++ b/Makefile @@ -15,6 +15,13 @@ else CORE_URL=https://github.com/hiddify/hiddify-next-core/releases/download/draft endif +ifeq ($(BRANCH),RELEASE) +FLAVOR=prod +else +FLAVOR=dev +endif +TARGET=lib/main_$(FLAVOR).dart + get: flutter pub get @@ -25,23 +32,24 @@ translate: dart run slang android-release: android-aab-release android-apk-release + android-apk-release: - flutter build apk --target-platform android-arm,android-arm64,android-x64 --split-per-abi + flutter build apk --target-platform android-arm,android-arm64,android-x64 --split-per-abi --target $(TARGET) android-aab-release: - flutter build appbundle + flutter build appbundle --target $(TARGET) windows-release: - flutter_distributor package --platform windows --targets exe --skip-clean + flutter_distributor package --platform windows --targets exe --skip-clean --build-target $(TARGET) linux-release: - flutter_distributor package --platform linux --targets appimage --skip-clean + flutter_distributor package --platform linux --targets appimage --skip-clean --build-target $(TARGET) macos-release: - flutter_distributor package --platform macos --targets dmg --skip-clean + flutter_distributor package --platform macos --targets dmg --skip-clean --build-target $(TARGET) ios-release: #not tested - flutter_distributor package --platform ios --targets ipa --build-export-options-plist ios/exportOptions.plist + flutter_distributor package --platform ios --targets ipa --build-export-options-plist ios/exportOptions.plist --build-target $(TARGET) android-libs: mkdir -p $(ANDROID_OUT) diff --git a/lib/bootstrap.dart b/lib/bootstrap.dart index 5d73e6b8..bd4659b6 100644 --- a/lib/bootstrap.dart +++ b/lib/bootstrap.dart @@ -5,10 +5,12 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_native_splash/flutter_native_splash.dart'; import 'package:hiddify/core/app/app.dart'; +import 'package:hiddify/core/core_providers.dart'; import 'package:hiddify/core/prefs/prefs.dart'; import 'package:hiddify/data/data_providers.dart'; +import 'package:hiddify/data/repository/app_repository_impl.dart'; +import 'package:hiddify/domain/environment.dart'; import 'package:hiddify/features/common/active_profile/active_profile_notifier.dart'; -import 'package:hiddify/features/common/common.dart'; import 'package:hiddify/features/common/window/window_controller.dart'; import 'package:hiddify/features/system_tray/system_tray.dart'; import 'package:hiddify/services/auto_start_service.dart'; @@ -23,15 +25,22 @@ import 'package:window_manager/window_manager.dart'; final _loggy = Loggy('bootstrap'); final _stopWatch = Stopwatch(); -Future lazyBootstrap(WidgetsBinding widgetsBinding) async { +Future lazyBootstrap( + WidgetsBinding widgetsBinding, + Environment env, +) async { _stopWatch.start(); FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding); if (PlatformUtils.isDesktop) await windowManager.ensureInitialized(); + final appInfo = await AppRepositoryImpl.getAppInfo(env); final sharedPreferences = await SharedPreferences.getInstance(); final container = ProviderContainer( - overrides: [sharedPreferencesProvider.overrideWithValue(sharedPreferences)], + overrides: [ + appInfoProvider.overrideWithValue(appInfo), + sharedPreferencesProvider.overrideWithValue(sharedPreferences), + ], ); final debug = container.read(debugModeNotifierProvider) || kDebugMode; @@ -40,7 +49,9 @@ Future lazyBootstrap(WidgetsBinding widgetsBinding) async { await filesEditor.init(); initLoggers(container.read, debug); - await container.read(runtimeDetailsServiceProvider).init(); + _loggy.info( + "os: [${Platform.operatingSystem}](${Platform.operatingSystemVersion}), processor count [${Platform.numberOfProcessors}]", + ); _loggy.info("basic setup took [${_stopWatch.elapsedMilliseconds}]ms"); final silentStart = container.read(silentStartNotifierProvider); @@ -104,7 +115,6 @@ Future initControllers( [ read(activeProfileProvider.future), read(deepLinkServiceProvider.future), - read(runtimeDetailsNotifierProvider.future), if (PlatformUtils.isDesktop) read(systemTrayControllerProvider.future), ], ); diff --git a/lib/core/core_providers.dart b/lib/core/core_providers.dart index 3d943dbb..9cd598ff 100644 --- a/lib/core/core_providers.dart +++ b/lib/core/core_providers.dart @@ -1,8 +1,17 @@ import 'package:hiddify/core/prefs/prefs.dart'; +import 'package:hiddify/domain/app/app.dart'; +import 'package:hiddify/domain/environment.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'core_providers.g.dart'; +@Riverpod(keepAlive: true) +AppInfo appInfo(AppInfoRef ref) => + throw UnimplementedError('AppInfo must be overridden'); + +@Riverpod(keepAlive: true) +Environment env(EnvRef ref) => ref.watch(appInfoProvider).environment; + @Riverpod(keepAlive: true) TranslationsEn translations(TranslationsRef ref) => ref.watch(localeNotifierProvider).build(); diff --git a/lib/core/prefs/general_prefs.dart b/lib/core/prefs/general_prefs.dart index 47b54783..32f93b5c 100644 --- a/lib/core/prefs/general_prefs.dart +++ b/lib/core/prefs/general_prefs.dart @@ -1,4 +1,6 @@ +import 'package:hiddify/core/core_providers.dart'; import 'package:hiddify/data/data_providers.dart'; +import 'package:hiddify/domain/environment.dart'; import 'package:hiddify/utils/pref_notifier.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; @@ -20,8 +22,11 @@ class SilentStartNotifier extends _$SilentStartNotifier { @Riverpod(keepAlive: true) class DebugModeNotifier extends _$DebugModeNotifier { - late final _pref = - Pref(ref.watch(sharedPreferencesProvider), "debug_mode", false); + late final _pref = Pref( + ref.watch(sharedPreferencesProvider), + "debug_mode", + ref.read(envProvider) == Environment.dev, + ); @override bool build() => _pref.getValue(); diff --git a/lib/data/data_providers.dart b/lib/data/data_providers.dart index d62220d1..4e669dba 100644 --- a/lib/data/data_providers.dart +++ b/lib/data/data_providers.dart @@ -1,10 +1,11 @@ import 'package:dio/dio.dart'; +import 'package:hiddify/core/core_providers.dart'; import 'package:hiddify/data/api/clash_api.dart'; import 'package:hiddify/data/local/dao/dao.dart'; import 'package:hiddify/data/local/database.dart'; +import 'package:hiddify/data/repository/app_repository_impl.dart'; import 'package:hiddify/data/repository/config_options_store.dart'; import 'package:hiddify/data/repository/repository.dart'; -import 'package:hiddify/data/repository/update_repository_impl.dart'; import 'package:hiddify/domain/app/app.dart'; import 'package:hiddify/domain/constants.dart'; import 'package:hiddify/domain/core_facade.dart'; @@ -27,7 +28,7 @@ SharedPreferences sharedPreferences(SharedPreferencesRef ref) => Dio dio(DioRef ref) => Dio( BaseOptions( headers: { - "User-Agent": ref.watch(runtimeDetailsServiceProvider).userAgent, + "User-Agent": ref.watch(appInfoProvider).userAgent, }, ), ); @@ -47,8 +48,8 @@ ProfilesRepository profilesRepository(ProfilesRepositoryRef ref) => ); @Riverpod(keepAlive: true) -UpdateRepository updateRepository(UpdateRepositoryRef ref) => - UpdateRepositoryImpl(ref.watch(dioProvider)); +AppRepository appRepository(AppRepositoryRef ref) => + AppRepositoryImpl(ref.watch(dioProvider)); @Riverpod(keepAlive: true) ClashApi clashApi(ClashApiRef ref) => ClashApi(Defaults.clashApiPort); diff --git a/lib/data/repository/update_repository_impl.dart b/lib/data/repository/app_repository_impl.dart similarity index 55% rename from lib/data/repository/update_repository_impl.dart rename to lib/data/repository/app_repository_impl.dart index 490c9d7c..ad937964 100644 --- a/lib/data/repository/update_repository_impl.dart +++ b/lib/data/repository/app_repository_impl.dart @@ -1,41 +1,35 @@ +import 'dart:io'; + import 'package:dio/dio.dart'; import 'package:fpdart/fpdart.dart'; import 'package:hiddify/data/repository/exception_handlers.dart'; import 'package:hiddify/domain/app/app.dart'; import 'package:hiddify/domain/constants.dart'; +import 'package:hiddify/domain/environment.dart'; import 'package:hiddify/utils/custom_loggers.dart'; import 'package:package_info_plus/package_info_plus.dart'; -class UpdateRepositoryImpl +class AppRepositoryImpl with ExceptionHandler, InfraLogger - implements UpdateRepository { - UpdateRepositoryImpl(this.dio); + implements AppRepository { + AppRepositoryImpl(this.dio); final Dio dio; - @override - TaskEither getCurrentVersion() { - return exceptionHandler( - () async { - loggy.debug("getting current app version"); - final packageInfo = await PackageInfo.fromPlatform(); - return right( - InstalledVersionInfo( - version: packageInfo.version, - buildNumber: packageInfo.buildNumber, - installerMedia: packageInfo.installerStore, - ), - ); - }, - (error, stackTrace) { - loggy.warning("error getting current app version", error, stackTrace); - return UpdateFailure.unexpected(error, stackTrace); - }, + static Future getAppInfo(Environment environment) async { + final packageInfo = await PackageInfo.fromPlatform(); + return AppInfo( + name: packageInfo.appName, + version: packageInfo.version, + buildNumber: packageInfo.buildNumber, + installerMedia: packageInfo.installerStore, + operatingSystem: Platform.operatingSystem, + environment: environment, ); } @override - TaskEither getLatestVersion({ + TaskEither getLatestVersion({ bool includePreReleases = false, }) { return exceptionHandler( @@ -44,7 +38,7 @@ class UpdateRepositoryImpl if (response.statusCode != 200 || response.data == null) { loggy.warning("failed to fetch latest version info"); - return left(const UpdateFailure.unexpected()); + return left(const AppFailure.unexpected()); } final releases = response.data! @@ -57,7 +51,7 @@ class UpdateRepositoryImpl } return right(latest); }, - UpdateFailure.unexpected, + AppFailure.unexpected, ); } } diff --git a/lib/domain/app/app.dart b/lib/domain/app/app.dart index 32895acb..1853b271 100644 --- a/lib/domain/app/app.dart +++ b/lib/domain/app/app.dart @@ -1,3 +1,3 @@ -export 'update_failure.dart'; -export 'update_repository.dart'; -export 'version_info.dart'; +export 'app_failure.dart'; +export 'app_info.dart'; +export 'app_repository.dart'; diff --git a/lib/domain/app/update_failure.dart b/lib/domain/app/app_failure.dart similarity index 72% rename from lib/domain/app/update_failure.dart rename to lib/domain/app/app_failure.dart index 97212b36..4f37126e 100644 --- a/lib/domain/app/update_failure.dart +++ b/lib/domain/app/app_failure.dart @@ -2,13 +2,13 @@ import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:hiddify/core/prefs/prefs.dart'; import 'package:hiddify/domain/failures.dart'; -part 'update_failure.freezed.dart'; +part 'app_failure.freezed.dart'; @freezed -sealed class UpdateFailure with _$UpdateFailure, Failure { - const UpdateFailure._(); +sealed class AppFailure with _$AppFailure, Failure { + const AppFailure._(); - const factory UpdateFailure.unexpected([ + const factory AppFailure.unexpected([ Object? error, StackTrace? stackTrace, ]) = UpdateUnexpectedFailure; diff --git a/lib/domain/app/version_info.dart b/lib/domain/app/app_info.dart similarity index 73% rename from lib/domain/app/version_info.dart rename to lib/domain/app/app_info.dart index 94edce67..db7e3233 100644 --- a/lib/domain/app/version_info.dart +++ b/lib/domain/app/app_info.dart @@ -1,24 +1,27 @@ import 'package:dartx/dartx.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:hiddify/domain/environment.dart'; -part 'version_info.freezed.dart'; -part 'version_info.g.dart'; +part 'app_info.freezed.dart'; +part 'app_info.g.dart'; @freezed -class InstalledVersionInfo with _$InstalledVersionInfo { - const InstalledVersionInfo._(); +class AppInfo with _$AppInfo { + const AppInfo._(); - const factory InstalledVersionInfo({ + const factory AppInfo({ + required String name, required String version, required String buildNumber, String? installerMedia, - }) = _InstalledVersionInfo; + required String operatingSystem, + required Environment environment, + }) = _AppInfo; - String get fullVersion => - buildNumber.isBlank ? version : "$version+$buildNumber"; + String get userAgent => "HiddifyNext/$version ($operatingSystem)"; - factory InstalledVersionInfo.fromJson(Map json) => - _$InstalledVersionInfoFromJson(json); + factory AppInfo.fromJson(Map json) => + _$AppInfoFromJson(json); } // TODO ignore drafts diff --git a/lib/domain/app/app_repository.dart b/lib/domain/app/app_repository.dart new file mode 100644 index 00000000..e51bcb35 --- /dev/null +++ b/lib/domain/app/app_repository.dart @@ -0,0 +1,9 @@ +import 'package:fpdart/fpdart.dart'; +import 'package:hiddify/domain/app/app_failure.dart'; +import 'package:hiddify/domain/app/app_info.dart'; + +abstract interface class AppRepository { + TaskEither getLatestVersion({ + bool includePreReleases = false, + }); +} diff --git a/lib/domain/app/update_repository.dart b/lib/domain/app/update_repository.dart deleted file mode 100644 index 66a76fc7..00000000 --- a/lib/domain/app/update_repository.dart +++ /dev/null @@ -1,11 +0,0 @@ -import 'package:fpdart/fpdart.dart'; -import 'package:hiddify/domain/app/update_failure.dart'; -import 'package:hiddify/domain/app/version_info.dart'; - -abstract interface class UpdateRepository { - TaskEither getCurrentVersion(); - - TaskEither getLatestVersion({ - bool includePreReleases = false, - }); -} diff --git a/lib/domain/environment.dart b/lib/domain/environment.dart new file mode 100644 index 00000000..c223ad53 --- /dev/null +++ b/lib/domain/environment.dart @@ -0,0 +1,4 @@ +enum Environment { + prod, + dev; +} diff --git a/lib/features/about/view/about_page.dart b/lib/features/about/view/about_page.dart index a9467d20..0c327088 100644 --- a/lib/features/about/view/about_page.dart +++ b/lib/features/about/view/about_page.dart @@ -3,8 +3,8 @@ import 'package:gap/gap.dart'; import 'package:hiddify/core/core_providers.dart'; import 'package:hiddify/domain/constants.dart'; import 'package:hiddify/domain/failures.dart'; +import 'package:hiddify/features/common/common.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/services/service_providers.dart'; import 'package:hiddify/utils/utils.dart'; @@ -16,33 +16,22 @@ class AboutPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final t = ref.watch(translationsProvider); - final appVersion = ref.watch(appVersionProvider); - - final isCheckingForUpdate = ref.watch( - runtimeDetailsNotifierProvider.select( - (value) => value.maybeWhen( - data: (data) => data.latestVersion.isLoading, - orElse: () => false, - ), - ), - ); + final appInfo = ref.watch(appInfoProvider); + final appUpdate = ref.watch(appUpdateNotifierProvider); ref.listen( - runtimeDetailsNotifierProvider, + appUpdateNotifierProvider, (_, next) async { - if (next case AsyncData(:final value)) { - switch (value.latestVersion) { - case AsyncError(:final error): - CustomToast.error(t.printError(error)).show(context); - default: - if (value.newVersionAvailable) { - await NewVersionDialog( - value.appVersion, - value.latestVersion.value!, - canIgnore: false, - ).show(context); - } - } + switch (next) { + case AsyncData(value: final remoteVersion?): + await NewVersionDialog( + appInfo.version, + remoteVersion, + canIgnore: false, + ).show(context); + case AsyncError(:final error): + if (!context.mounted) return; + CustomToast.error(t.printError(error)).show(context); } }, ); @@ -53,86 +42,77 @@ class AboutPage extends HookConsumerWidget { SliverAppBar( title: Text(t.about.pageTitle), ), - ...switch (appVersion) { - 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, - 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.sourceCode), - trailing: const Icon(Icons.open_in_new), - onTap: () async { - await UriUtils.tryLaunch( - Uri.parse(Constants.githubUrl), - ); - }, + 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, + style: Theme.of(context).textTheme.titleLarge, ), - ListTile( - title: Text(t.about.telegramChannel), - trailing: const Icon(Icons.open_in_new), - onTap: () async { - await UriUtils.tryLaunch( - Uri.parse(Constants.telegramChannelUrl), - ); - }, - ), - ListTile( - title: Text(t.about.checkForUpdate), - trailing: isCheckingForUpdate - ? const SizedBox( - width: 24, - height: 24, - child: CircularProgressIndicator(), - ) - : const Icon(Icons.update), - onTap: () async { - await ref - .read(runtimeDetailsNotifierProvider.notifier) - .checkForUpdates(); - }, - ), - ListTile( - title: Text(t.settings.general.openWorkingDir), - trailing: const Icon(Icons.arrow_outward_outlined), - onTap: () async { - final path = ref - .read(filesEditorServiceProvider) - .workingDir - .uri; - await UriUtils.tryLaunch(path); - }, + const Gap(4), + Text( + "${t.about.version} ${appInfo.version}", ), ], ), + ], + ), + ), + ), + SliverList( + delegate: SliverChildListDelegate( + [ + ListTile( + title: Text(t.about.sourceCode), + trailing: const Icon(Icons.open_in_new), + onTap: () async { + await UriUtils.tryLaunch( + Uri.parse(Constants.githubUrl), + ); + }, + ), + ListTile( + title: Text(t.about.telegramChannel), + trailing: const Icon(Icons.open_in_new), + onTap: () async { + await UriUtils.tryLaunch( + Uri.parse(Constants.telegramChannelUrl), + ); + }, + ), + ListTile( + title: Text(t.about.checkForUpdate), + trailing: appUpdate.isLoading + ? const SizedBox( + width: 24, + height: 24, + child: CircularProgressIndicator(), + ) + : const Icon(Icons.update), + onTap: () { + ref.invalidate(appUpdateNotifierProvider); + }, + ), + ListTile( + title: Text(t.settings.general.openWorkingDir), + trailing: const Icon(Icons.arrow_outward_outlined), + onTap: () async { + final path = + ref.read(filesEditorServiceProvider).workingDir.uri; + await UriUtils.tryLaunch(path); + }, ), ], - _ => [], - }, + ), + ), ], ), ); diff --git a/lib/features/common/app_update_notifier.dart b/lib/features/common/app_update_notifier.dart new file mode 100644 index 00000000..4fa2cd46 --- /dev/null +++ b/lib/features/common/app_update_notifier.dart @@ -0,0 +1,33 @@ +import 'package:hiddify/core/core_providers.dart'; +import 'package:hiddify/data/data_providers.dart'; +import 'package:hiddify/domain/app/app.dart'; +import 'package:hiddify/utils/utils.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'app_update_notifier.g.dart'; + +@Riverpod(keepAlive: true) +class AppUpdateNotifier extends _$AppUpdateNotifier with AppLogger { + @override + Future build() async { + loggy.debug("checking for update"); + final currentVersion = ref.watch(appInfoProvider).version; + return ref + .watch(appRepositoryProvider) + .getLatestVersion(includePreReleases: true) + .match( + (l) { + loggy.warning("failed to get latest version, $l"); + throw l; + }, + (remote) { + if (remote.version.compareTo(currentVersion) > 0) { + loggy.info("new version available: $remote"); + return remote; + } + loggy.info("already using latest version[$currentVersion], remote: $remote"); + return null; + }, + ).run(); + } +} diff --git a/lib/features/common/common.dart b/lib/features/common/common.dart index 4ac4edcf..b4eaf530 100644 --- a/lib/features/common/common.dart +++ b/lib/features/common/common.dart @@ -1,5 +1,5 @@ +export 'app_update_notifier.dart'; export 'confirmation_dialogs.dart'; export 'custom_app_bar.dart'; export 'profile_tile.dart'; export 'qr_code_scanner_screen.dart'; -export 'runtime_details.dart'; diff --git a/lib/features/common/new_version_dialog.dart b/lib/features/common/new_version_dialog.dart index f18133a2..6e04a066 100644 --- a/lib/features/common/new_version_dialog.dart +++ b/lib/features/common/new_version_dialog.dart @@ -16,7 +16,7 @@ class NewVersionDialog extends HookConsumerWidget { this.canIgnore = true, }); - final InstalledVersionInfo currentVersion; + final String currentVersion; final RemoteVersionInfo newVersion; final bool canIgnore; @@ -48,7 +48,7 @@ class NewVersionDialog extends HookConsumerWidget { style: theme.textTheme.bodySmall, ), TextSpan( - text: currentVersion.fullVersion, + text: currentVersion, style: theme.textTheme.labelMedium, ), ], diff --git a/lib/features/common/runtime_details.dart b/lib/features/common/runtime_details.dart deleted file mode 100644 index 84a9e642..00000000 --- a/lib/features/common/runtime_details.dart +++ /dev/null @@ -1,79 +0,0 @@ -import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:hiddify/data/data_providers.dart'; -import 'package:hiddify/domain/app/app.dart'; -import 'package:hiddify/utils/utils.dart'; -import 'package:riverpod_annotation/riverpod_annotation.dart'; - -part 'runtime_details.freezed.dart'; -part 'runtime_details.g.dart'; - -// TODO add clash version -@Riverpod(keepAlive: true) -class RuntimeDetailsNotifier extends _$RuntimeDetailsNotifier with AppLogger { - @override - Future build() async { - loggy.debug("initializing"); - final appVersion = await ref - .watch(updateRepositoryProvider) - .getCurrentVersion() - .getOrElse((l) => throw l) - .run(); - return RuntimeDetails(appVersion: appVersion); - } - - Future checkForUpdates() async { - if (state case AsyncData(:final value)) { - switch (value.latestVersion) { - case AsyncLoading(): - return; - default: - loggy.debug("checking for updates"); - state = - AsyncData(value.copyWith(latestVersion: const AsyncLoading())); - // TODO use prefs - const includePreReleases = true; - await ref - .read(updateRepositoryProvider) - .getLatestVersion(includePreReleases: includePreReleases) - .match( - (l) { - loggy.warning("failed to get latest version, $l"); - state = AsyncData( - value.copyWith( - latestVersion: AsyncError(l, StackTrace.current), - ), - ); - }, - (r) { - state = AsyncData( - value.copyWith(latestVersion: AsyncData(r)), - ); - }, - ).run(); - } - } - } -} - -@Riverpod(keepAlive: true) -AsyncValue appVersion(AppVersionRef ref) => ref.watch( - runtimeDetailsNotifierProvider - .select((value) => value.whenData((value) => value.appVersion)), - ); - -@freezed -class RuntimeDetails with _$RuntimeDetails { - const RuntimeDetails._(); - - const factory RuntimeDetails({ - required InstalledVersionInfo appVersion, - @Default(AsyncData(null)) AsyncValue latestVersion, - }) = _RuntimeDetails; - - bool get newVersionAvailable => latestVersion.maybeWhen( - data: (data) => - data != null && - data.fullVersion.compareTo(this.appVersion.fullVersion) > 0, - orElse: () => false, - ); -} diff --git a/lib/features/home/view/home_page.dart b/lib/features/home/view/home_page.dart index e6a14383..771abd23 100644 --- a/lib/features/home/view/home_page.dart +++ b/lib/features/home/view/home_page.dart @@ -1,7 +1,9 @@ +import 'package:dartx/dartx.dart'; import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:hiddify/core/core_providers.dart'; import 'package:hiddify/core/router/router.dart'; +import 'package:hiddify/domain/environment.dart'; import 'package:hiddify/domain/failures.dart'; import 'package:hiddify/features/common/active_profile/active_profile_notifier.dart'; import 'package:hiddify/features/common/active_profile/has_any_profile_notifier.dart'; @@ -86,16 +88,12 @@ class AppVersionLabel extends HookConsumerWidget { final t = ref.watch(translationsProvider); final theme = Theme.of(context); - final version = ref.watch( - appVersionProvider.select( - (value) => switch (value) { - AsyncData(:final value) => value.version, - _ => "", - }, - ), - ); - - if (version.isEmpty) return const SizedBox(); + final appInfo = ref.watch(appInfoProvider); + final version = appInfo.version + + (appInfo.environment == Environment.prod + ? "" + : " ${appInfo.environment.name}"); + if (version.isBlank) return const SizedBox(); return Semantics( label: t.about.version, @@ -111,6 +109,7 @@ class AppVersionLabel extends HookConsumerWidget { ), child: Text( version, + textDirection: TextDirection.ltr, style: theme.textTheme.bodySmall?.copyWith( color: theme.colorScheme.onSecondaryContainer, ), diff --git a/lib/main.dart b/lib/main_dev.dart similarity index 61% rename from lib/main.dart rename to lib/main_dev.dart index 52630a4d..8dcdbd01 100644 --- a/lib/main.dart +++ b/lib/main_dev.dart @@ -1,7 +1,8 @@ import 'package:flutter/widgets.dart'; import 'package:hiddify/bootstrap.dart'; +import 'package:hiddify/domain/environment.dart'; void main() async { final widgetsBinding = WidgetsFlutterBinding.ensureInitialized(); - return lazyBootstrap(widgetsBinding); + return lazyBootstrap(widgetsBinding, Environment.dev); } diff --git a/lib/main_prod.dart b/lib/main_prod.dart new file mode 100644 index 00000000..81b713ca --- /dev/null +++ b/lib/main_prod.dart @@ -0,0 +1,8 @@ +import 'package:flutter/widgets.dart'; +import 'package:hiddify/bootstrap.dart'; +import 'package:hiddify/domain/environment.dart'; + +void main() async { + final widgetsBinding = WidgetsFlutterBinding.ensureInitialized(); + return lazyBootstrap(widgetsBinding, Environment.prod); +} diff --git a/lib/services/auto_start_service.dart b/lib/services/auto_start_service.dart index a03aeb2c..bb0a31bf 100644 --- a/lib/services/auto_start_service.dart +++ b/lib/services/auto_start_service.dart @@ -1,6 +1,6 @@ import 'dart:io'; -import 'package:hiddify/services/service_providers.dart'; +import 'package:hiddify/core/core_providers.dart'; import 'package:hiddify/utils/utils.dart'; import 'package:launch_at_startup/launch_at_startup.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; @@ -13,9 +13,9 @@ class AutoStartService extends _$AutoStartService with InfraLogger { Future build() async { loggy.debug("initializing"); if (!PlatformUtils.isDesktop) return false; - final packageInfo = ref.watch(runtimeDetailsServiceProvider).packageInfo; + final appInfo = ref.watch(appInfoProvider); launchAtStartup.setup( - appName: packageInfo.appName, + appName: appInfo.name, appPath: Platform.resolvedExecutable, ); final isEnabled = await launchAtStartup.isEnabled(); diff --git a/lib/services/runtime_details_service.dart b/lib/services/runtime_details_service.dart deleted file mode 100644 index ff51bd08..00000000 --- a/lib/services/runtime_details_service.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'dart:io'; - -import 'package:hiddify/utils/utils.dart'; -import 'package:package_info_plus/package_info_plus.dart'; - -class RuntimeDetailsService with InfraLogger { - late final PackageInfo packageInfo; - - String get appVersion => packageInfo.version; - String get buildNumber => packageInfo.buildNumber; - - late final String operatingSystem = Platform.operatingSystem; - late final String userAgent; - - Future init() async { - loggy.debug("initializing"); - packageInfo = await PackageInfo.fromPlatform(); - userAgent = "HiddifyNext/$appVersion ($operatingSystem)"; - - loggy.info( - "os: [$operatingSystem](${Platform.operatingSystemVersion}), processor count [${Platform.numberOfProcessors}]", - ); - } -} diff --git a/lib/services/service_providers.dart b/lib/services/service_providers.dart index 87df56f8..187e04bb 100644 --- a/lib/services/service_providers.dart +++ b/lib/services/service_providers.dart @@ -1,6 +1,5 @@ import 'package:hiddify/services/files_editor_service.dart'; import 'package:hiddify/services/platform_settings.dart'; -import 'package:hiddify/services/runtime_details_service.dart'; import 'package:hiddify/services/singbox/singbox_service.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; @@ -10,10 +9,6 @@ part 'service_providers.g.dart'; FilesEditorService filesEditorService(FilesEditorServiceRef ref) => FilesEditorService(); -@Riverpod(keepAlive: true) -RuntimeDetailsService runtimeDetailsService(RuntimeDetailsServiceRef ref) => - RuntimeDetailsService(); - @Riverpod(keepAlive: true) SingboxService singboxService(SingboxServiceRef ref) => SingboxService();