diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a5ffc68f5..c8583e165 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -20,6 +20,7 @@ env: UPLOAD_ARTIFACT: "${{ inputs.upload-artifact }}" TAG_NAME: "${{ inputs.tag-name }}" TARGET_NAME_gz: "Hiddify-Linux-x64-AppImage.tar" + TARGET_NAME_AppImage: "Hiddify-Linux-x64-AppImage" TARGET_NAME_deb: "Hiddify-Debian-x64" # TARGET_NAME_rpm: "Hiddify-rpm-x64" TARGET_NAME_apk: "Hiddify-Android" @@ -64,18 +65,23 @@ jobs: fail-fast: false matrix: include: - # - platform: android-apk - # os: ubuntu-latest - # targets: apk + - platform: android-apk + os: ubuntu-latest + targets: apk - platform: android-aab os: ubuntu-latest targets: aab - # - platform: windows - # os: windows-latest - # aarch: amd64 - # targets: exe,msix,zip + - platform: windows + os: windows-latest + aarch: amd64 + targets: exe,msix,zip + + - platform: linux + os: ubuntu-22.04 + aarch: amd64 + targets: deb,gz,AppImage # - platform: linux-amd64 # os: ubuntu-22.04 @@ -97,10 +103,10 @@ jobs: # aarch: arm64 # targets: deb,gz - # - platform: macos - # os: macos-15 - # aarch: universal - # targets: dmg,pkg + - platform: macos + os: macos-15 + aarch: universal + targets: dmg,pkg # - platform: ios # os: macos-15 @@ -133,21 +139,21 @@ jobs: - - name: Setup Flutter for arm64 - if: ${{ startsWith(matrix.platform,'linux-arm64') }} - uses: hurelhuyag/flutter-arm64-action@HEAD - with: - channel: 'stable' - flutter-version: ${{ env.FLUTTER_VERSION }} + # - name: Setup Flutter for arm64 + # if: ${{ startsWith(matrix.platform,'linux-arm64') }} + # uses: hurelhuyag/flutter-arm64-action@HEAD + # with: + # channel: 'stable' + # flutter-version: ${{ env.FLUTTER_VERSION }} - name: Setup Flutter - if: ${{ !startsWith(matrix.platform, 'linux-arm64') }} uses: subosito/flutter-action@v2.21.0 #issue with 2.13 with: flutter-version: ${{ env.FLUTTER_VERSION }} # flutter-version-file: pubspec.yaml channel: 'stable' cache: true + - name: Clean up disk space if: startsWith(matrix.platform,'android') run: | diff --git a/Makefile b/Makefile index 949363f91..8f31a7f1d 100644 --- a/Makefile +++ b/Makefile @@ -92,7 +92,7 @@ ios-prepare: common-prepare ios-libs cd ios; pod repo update; pod install;echo "done ios prepare" macos-prepare: common-prepare macos-libs -linux-prepare: common-prepare linux-libs +linux-prepare: common-prepare linux-amd64-libs linux-amd64-prepare: common-prepare linux-amd64-libs @@ -388,6 +388,8 @@ linux-appimage-release: sed -i '/^\[Desktop Entry\]/a StartupWMClass=app.hiddify.com' "squashfs-root/hiddify.desktop"; \ $(BLUE)Removing old AppImage$(DONE); \ rm *.AppImage; \ + $(BLUE)Deleting bundled libstdc++ to fix Arch Linux compatibility...$(DONE); \ + find squashfs-root/usr/lib -name "libstdc++.so.6" -delete; \ $(BLUE)Rebuilding AppImage$(DONE); \ ARCH=x86_64 appimagetool --no-appstream squashfs-root Hiddify.AppImage > /dev/null; \ $(BLUE)Cleaning up squashfs$(DONE); \ @@ -396,8 +398,8 @@ linux-appimage-release: PKG_DIR_NAME="hiddify-linux-appimage"; \ $(BLUE)Creating dir: $$PKG_DIR_NAME$(DONE); \ mkdir -p "$$PKG_DIR_NAME"; \ - $(BLUE)Moving and Renaming to Hiddify.AppImage$(DONE); \ - mv "Hiddify.AppImage" "$$PKG_DIR_NAME/Hiddify.AppImage"; \ + $(BLUE)Moving Hiddify.AppImage$(DONE); \ + cp -p "Hiddify.AppImage" "$$PKG_DIR_NAME/Hiddify.AppImage"; \ $(BLUE)Creating Portable Home directory$(DONE); \ mkdir -p "$$PKG_DIR_NAME/Hiddify.AppImage.home"; \ $(BLUE)Compressing to .tar.gz$(DONE); \ diff --git a/assets/translations/ar.i18n.json b/assets/translations/ar.i18n.json index 8262298cb..05b751dd7 100644 --- a/assets/translations/ar.i18n.json +++ b/assets/translations/ar.i18n.json @@ -279,13 +279,13 @@ }, "region": "المنطقة", "regions": { - "ir": "إيران (ir) 🇮🇷", - "cn": "الصين (cn) 🇨🇳", - "ru": "روسيا (ru) 🇷🇺", - "af": "أفغانستان (af) 🇦🇫", - "id": "إندونيسيا (id) 🇮🇩", - "tr": "تركيا (tr) 🇹🇷", - "br": "البرازيل (br) 🇧🇷", + "ir": "إيران (ir)", + "cn": "الصين (cn)", + "ru": "روسيا (ru)", + "af": "أفغانستان (af)", + "id": "إندونيسيا (id)", + "tr": "تركيا (tr)", + "br": "البرازيل (br)", "other": "أخرى" }, "balancerStrategy": { diff --git a/assets/translations/en.i18n.json b/assets/translations/en.i18n.json index 206990504..07b3f341d 100644 --- a/assets/translations/en.i18n.json +++ b/assets/translations/en.i18n.json @@ -274,13 +274,13 @@ }, "region": "Region", "regions": { - "ir": "Iran (ir) 🇮🇷", - "cn": "China (cn) 🇨🇳", - "ru": "Russia (ru) 🇷🇺", - "af": "Afghanistan (af) 🇦🇫", - "id": "Indonesia (id) 🇮🇩", - "tr": "Türkiye (tr) 🇹🇷", - "br": "Brazil (br) 🇧🇷", + "ir": "Iran (ir)", + "cn": "China (cn)", + "ru": "Russia (ru)", + "af": "Afghanistan (af)", + "id": "Indonesia (id)", + "tr": "Türkiye (tr)", + "br": "Brazil (br)", "other": "Other" }, "balancerStrategy": { diff --git a/assets/translations/es.i18n.json b/assets/translations/es.i18n.json index 9274d41c5..9baa640b4 100644 --- a/assets/translations/es.i18n.json +++ b/assets/translations/es.i18n.json @@ -273,13 +273,13 @@ }, "region": "Región", "regions": { - "ir": "Irán (ir) 🇮🇷", - "cn": "China (cn) 🇨🇳", - "ru": "Rusia (ru) 🇷🇺", - "af": "Afganistán (af) 🇦🇫", - "id": "Indonesia (id) 🇮🇩", - "tr": "Turquía (tr) 🇹🇷", - "br": "Brasil (br) 🇧🇷", + "ir": "Irán (ir)", + "cn": "China (cn)", + "ru": "Rusia (ru)", + "af": "Afganistán (af)", + "id": "Indonesia (id)", + "tr": "Turquía (tr)", + "br": "Brasil (br)", "other": "Otro" }, "balancerStrategy": { diff --git a/assets/translations/fa.i18n.json b/assets/translations/fa.i18n.json index ec8de8e8f..f3f4729a7 100644 --- a/assets/translations/fa.i18n.json +++ b/assets/translations/fa.i18n.json @@ -273,13 +273,13 @@ }, "region": "منطقه", "regions": { - "ir": "ایران (ir) 🇮🇷", - "cn": "چین (cn) 🇨🇳", - "ru": "روسیه (ru) 🇷🇺", - "af": "افغانستان (af) 🇦🇫", - "id": "اندونزی (id) 🇮🇩", - "tr": "ترکیه (tr) 🇹🇷", - "br": "برزیل (br) 🇧🇷", + "ir": "ایران (ir)", + "cn": "چین (cn)", + "ru": "روسیه (ru)", + "af": "افغانستان (af)", + "id": "اندونزی (id)", + "tr": "ترکیه (tr)", + "br": "برزیل (br)", "other": "سایر" }, "balancerStrategy": { diff --git a/assets/translations/fr.i18n.json b/assets/translations/fr.i18n.json index 16a923726..29743ac19 100644 --- a/assets/translations/fr.i18n.json +++ b/assets/translations/fr.i18n.json @@ -273,13 +273,13 @@ }, "region": "Région", "regions": { - "ir": "Iran (ir) 🇮🇷", - "cn": "Chine (cn) 🇨🇳", - "ru": "Russie (ru) 🇷🇺", - "af": "Afghanistan (af) 🇦🇫", - "id": "Indonésie (id) 🇮🇩", - "tr": "Turquie (tr) 🇹🇷", - "br": "Brésil (br) 🇧🇷", + "ir": "Iran (ir)", + "cn": "Chine (cn)", + "ru": "Russie (ru)", + "af": "Afghanistan (af)", + "id": "Indonésie (id)", + "tr": "Turquie (tr)", + "br": "Brésil (br)", "other": "Autre" }, "balancerStrategy": { diff --git a/assets/translations/id.i18n.json b/assets/translations/id.i18n.json index 76402eff9..41b7ce1be 100644 --- a/assets/translations/id.i18n.json +++ b/assets/translations/id.i18n.json @@ -273,13 +273,13 @@ }, "region": "Wilayah", "regions": { - "ir": "Iran (ir) 🇮🇷", - "cn": "Tiongkok (cn) 🇨🇳", - "ru": "Rusia (ru) 🇷🇺", - "af": "Afghanistan (af) 🇦🇫", - "id": "Indonesia (id) 🇮🇩", - "tr": "Turki (tr) 🇹🇷", - "br": "Brasil (br) 🇧🇷", + "ir": "Iran (ir)", + "cn": "Tiongkok (cn)", + "ru": "Rusia (ru)", + "af": "Afghanistan (af)", + "id": "Indonesia (id)", + "tr": "Turki (tr)", + "br": "Brasil (br)", "other": "Lainnya" }, "balancerStrategy": { diff --git a/assets/translations/pt-BR.i18n.json b/assets/translations/pt-BR.i18n.json index 2558c01d8..c3f536bc6 100644 --- a/assets/translations/pt-BR.i18n.json +++ b/assets/translations/pt-BR.i18n.json @@ -273,13 +273,13 @@ }, "region": "Região", "regions": { - "ir": "Irã (ir) 🇮🇷", - "cn": "China (cn) 🇨🇳", - "ru": "Rússia (ru) 🇷🇺", - "af": "Afeganistão (af) 🇦🇫", - "id": "Indonésia (id) 🇮🇩", - "tr": "Turquia (tr) 🇹🇷", - "br": "Brasil (br) 🇧🇷", + "ir": "Irã (ir)", + "cn": "China (cn)", + "ru": "Rússia (ru)", + "af": "Afeganistão (af)", + "id": "Indonésia (id)", + "tr": "Turquia (tr)", + "br": "Brasil (br)", "other": "Outro" }, "balancerStrategy": { diff --git a/assets/translations/ru.i18n.json b/assets/translations/ru.i18n.json index ed798abef..a6adacbf6 100644 --- a/assets/translations/ru.i18n.json +++ b/assets/translations/ru.i18n.json @@ -277,13 +277,13 @@ }, "region": "Регион", "regions": { - "ir": "Иран (ir) 🇮🇷", - "cn": "Китай (cn) 🇨🇳", - "ru": "Россия (ru) 🇷🇺", - "af": "Афганистан (af) 🇦🇫", - "id": "Индонезия (id) 🇮🇩", - "tr": "Турция (tr) 🇹🇷", - "br": "Бразилия (br) 🇧🇷", + "ir": "Иран (ir)", + "cn": "Китай (cn)", + "ru": "Россия (ru)", + "af": "Афганистан (af)", + "id": "Индонезия (id)", + "tr": "Турция (tr)", + "br": "Бразилия (br)", "other": "Другой" }, "balancerStrategy": { diff --git a/assets/translations/tr.i18n.json b/assets/translations/tr.i18n.json index 9b20e38fc..6cf989195 100644 --- a/assets/translations/tr.i18n.json +++ b/assets/translations/tr.i18n.json @@ -273,13 +273,13 @@ }, "region": "Bölge", "regions": { - "ir": "İran (ir) 🇮🇷", - "cn": "Çin (cn) 🇨🇳", - "ru": "Rusya (ru) 🇷🇺", - "af": "Afganistan (af) 🇦🇫", - "id": "Endonezya (id) 🇮🇩", - "tr": "Türkiye (tr) 🇹🇷", - "br": "Brezilya (br) 🇧🇷", + "ir": "İran (ir)", + "cn": "Çin (cn)", + "ru": "Rusya (ru)", + "af": "Afganistan (af)", + "id": "Endonezya (id)", + "tr": "Türkiye (tr)", + "br": "Brezilya (br)", "other": "Diğer" }, "balancerStrategy": { diff --git a/assets/translations/zh-CN.i18n.json b/assets/translations/zh-CN.i18n.json index c1e1a97c9..35b357475 100644 --- a/assets/translations/zh-CN.i18n.json +++ b/assets/translations/zh-CN.i18n.json @@ -273,13 +273,13 @@ }, "region": "地区", "regions": { - "ir": "伊朗 (ir) 🇮🇷", - "cn": "中国 (cn) 🇨🇳", - "ru": "俄罗斯 (ru) 🇷🇺", - "af": "阿富汗 (af) 🇦🇫", - "id": "印度尼西亚 (id) 🇮🇩", - "tr": "土耳其 (tr) 🇹🇷", - "br": "巴西 (br) 🇧🇷", + "ir": "伊朗 (ir)", + "cn": "中国 (cn)", + "ru": "俄罗斯 (ru)", + "af": "阿富汗 (af)", + "id": "印度尼西亚 (id)", + "tr": "土耳其 (tr)", + "br": "巴西 (br)", "other": "其他" }, "balancerStrategy": { diff --git a/assets/translations/zh-TW.i18n.json b/assets/translations/zh-TW.i18n.json index 0f477738d..6ee0b0337 100644 --- a/assets/translations/zh-TW.i18n.json +++ b/assets/translations/zh-TW.i18n.json @@ -273,13 +273,13 @@ }, "region": "地區", "regions": { - "ir": "伊朗 (ir) 🇮🇷", - "cn": "中國 (cn) 🇨🇳", - "ru": "俄羅斯 (ru) 🇷🇺", - "af": "阿富汗 (af) 🇦🇫", - "id": "印尼 (id) 🇮🇩", - "tr": "土耳其 (tr) 🇹🇷", - "br": "巴西 (br) 🇧🇷", + "ir": "伊朗 (ir)", + "cn": "中國 (cn)", + "ru": "俄羅斯 (ru)", + "af": "阿富汗 (af)", + "id": "印尼 (id)", + "tr": "土耳其 (tr)", + "br": "巴西 (br)", "other": "其他" }, "balancerStrategy": { diff --git a/lib/core/preferences/preferences_provider.dart b/lib/core/preferences/preferences_provider.dart index 20e7053df..bded75abc 100644 --- a/lib/core/preferences/preferences_provider.dart +++ b/lib/core/preferences/preferences_provider.dart @@ -1,5 +1,7 @@ import 'dart:io'; +import 'package:hiddify/core/model/environment.dart'; +import 'package:hiddify/utils/platform_utils.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:loggy/loggy.dart'; import 'package:path/path.dart' as p; @@ -16,6 +18,7 @@ Future sharedPreferences(Ref ref) async { logger.debug("initializing preferences"); try { + if (PlatformUtils.isWindows && Environment.isPortable) SharedPreferences.setPrefix('portable.'); sharedPreferences = await SharedPreferences.getInstance(); } catch (e) { logger.error("error initializing preferences", e); diff --git a/lib/core/router/dialog/dialog_notifier.dart b/lib/core/router/dialog/dialog_notifier.dart index eb19af3d7..c785e3034 100644 --- a/lib/core/router/dialog/dialog_notifier.dart +++ b/lib/core/router/dialog/dialog_notifier.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:hiddify/core/model/region.dart'; import 'package:hiddify/core/preferences/actions_at_closing.dart'; import 'package:hiddify/core/router/dialog/widgets/action_at_closing_dialog.dart'; import 'package:hiddify/core/router/dialog/widgets/confirmation_dialog.dart'; @@ -10,7 +9,6 @@ import 'package:hiddify/core/router/dialog/widgets/new_version_dialog.dart'; import 'package:hiddify/core/router/dialog/widgets/no_active_profile_dialog.dart'; import 'package:hiddify/core/router/dialog/widgets/ok_dialog.dart'; import 'package:hiddify/core/router/dialog/widgets/proxy_info_dialog.dart'; -import 'package:hiddify/core/router/dialog/widgets/region_dialog.dart'; import 'package:hiddify/core/router/dialog/widgets/save_dialog.dart'; import 'package:hiddify/core/router/dialog/widgets/setting_checkbox_dialog.dart'; import 'package:hiddify/core/router/dialog/widgets/setting_input_dialog.dart'; @@ -112,10 +110,6 @@ class DialogNotifier extends _$DialogNotifier { false; } - Future showRegion({required Region selected}) async { - return await _show(RegionDialog(selected: selected)); - } - Future showActionAtClosing({required ActionsAtClosing selected}) async { return await _show(ActionsAtClosingDialog(selected: selected)); } @@ -216,13 +210,21 @@ class DialogNotifier extends _$DialogNotifier { Future showSettingPicker({ required String title, + bool showFlag = false, required T selected, required List options, required String Function(T e) getTitle, VoidCallback? onReset, }) async { return await _show( - SettingPickerDialog(title: title, selected: selected, options: options, getTitle: getTitle, onReset: onReset), + SettingPickerDialog( + title: title, + showFlag: showFlag, + selected: selected, + options: options, + getTitle: getTitle, + onReset: onReset, + ), ); } diff --git a/lib/core/router/dialog/widgets/region_dialog.dart b/lib/core/router/dialog/widgets/region_dialog.dart deleted file mode 100644 index 7b57984dc..000000000 --- a/lib/core/router/dialog/widgets/region_dialog.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; -import 'package:hiddify/core/localization/translations.dart'; -import 'package:hiddify/core/model/region.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; - -class RegionDialog extends HookConsumerWidget { - const RegionDialog({super.key, required this.selected}); - - final Region selected; - @override - Widget build(BuildContext context, WidgetRef ref) { - final t = ref.watch(translationsProvider).requireValue; - return SimpleDialog( - title: Text(t.pages.settings.routing.region), - children: Region.values - .map((e) => RadioListTile(title: Text(e.present(t)), value: e, groupValue: selected, onChanged: context.pop)) - .toList(), - ); - } -} diff --git a/lib/core/router/dialog/widgets/setting_picker_dialog.dart b/lib/core/router/dialog/widgets/setting_picker_dialog.dart index 5e315c968..dd9bd0569 100644 --- a/lib/core/router/dialog/widgets/setting_picker_dialog.dart +++ b/lib/core/router/dialog/widgets/setting_picker_dialog.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:hiddify/core/localization/translations.dart'; +import 'package:hiddify/features/proxy/active/ip_widget.dart'; import 'package:hiddify/utils/custom_loggers.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -8,6 +9,7 @@ class SettingPickerDialog extends HookConsumerWidget with PresLogger { const SettingPickerDialog({ super.key, required this.title, + this.showFlag = false, required this.selected, required this.options, required this.getTitle, @@ -15,6 +17,7 @@ class SettingPickerDialog extends HookConsumerWidget with PresLogger { }); final String title; + final bool showFlag; final T selected; final List options; final String Function(T e) getTitle; @@ -29,16 +32,17 @@ class SettingPickerDialog extends HookConsumerWidget with PresLogger { content: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, - children: options - .map( - (e) => RadioListTile( - title: Text(getTitle(e)), - value: e, - groupValue: selected, - onChanged: (value) => context.pop(e), - ), - ) - .toList(), + children: options.map((e) { + final title = getTitle(e); + final countryCode = title.substring(title.length - 3, title.length - 1); + return RadioListTile( + title: Text(title), + secondary: showFlag ? IPCountryFlag(countryCode: countryCode, size: 32) : null, + value: e, + groupValue: selected, + onChanged: (value) => context.pop(e), + ); + }).toList(), ), ), actions: [ diff --git a/lib/features/common/general_pref_tiles.dart b/lib/features/common/general_pref_tiles.dart index e58fa3696..f130b80cf 100644 --- a/lib/features/common/general_pref_tiles.dart +++ b/lib/features/common/general_pref_tiles.dart @@ -7,7 +7,6 @@ import 'package:hiddify/core/preferences/general_preferences.dart'; import 'package:hiddify/core/router/dialog/dialog_notifier.dart'; import 'package:hiddify/core/theme/app_theme_mode.dart'; import 'package:hiddify/core/theme/theme_preferences.dart'; -import 'package:hiddify/features/settings/data/config_option_repository.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; class LocalePrefTile extends ConsumerWidget { @@ -40,41 +39,6 @@ class LocalePrefTile extends ConsumerWidget { } } -class RegionPrefTile extends ConsumerWidget { - const RegionPrefTile({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final t = ref.watch(translationsProvider).requireValue; - - final region = ref.watch(ConfigOptions.region); - - return ListTile( - title: Text(t.pages.settings.routing.region), - subtitle: Text(region.present(t), style: Theme.of(context).textTheme.bodySmall), - leading: const Icon(Icons.place_rounded), - onTap: () async { - final selectedRegion = await ref.read(dialogNotifierProvider.notifier).showRegion(selected: region); - if (selectedRegion != null) { - // await ref.read(Preferences.region.notifier).update(selectedRegion); - - await ref.watch(ConfigOptions.region.notifier).update(selectedRegion); - - await ref.watch(ConfigOptions.directDnsAddress.notifier).reset(); - - // await ref.read(configOptionNotifierProvider.notifier).build(); - // await ref.watch(ConfigOptions.resolveDestination.notifier).update(!ref.watch(ConfigOptions.resolveDestination.notifier).raw()); - //for reload config - // final tmp = ref.watch(ConfigOptions.resolveDestination.notifier).raw(); - // await ref.watch(ConfigOptions.resolveDestination.notifier).update(!tmp); - // await ref.watch(ConfigOptions.resolveDestination.notifier).update(tmp); - //TODO: fix it - } - }, - ); - } -} - class EnableAnalyticsPrefTile extends ConsumerWidget { const EnableAnalyticsPrefTile({super.key, this.onChanged}); diff --git a/lib/features/intro/widget/intro_page.dart b/lib/features/intro/widget/intro_page.dart index 53dc8254c..24917acc0 100644 --- a/lib/features/intro/widget/intro_page.dart +++ b/lib/features/intro/widget/intro_page.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:io'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; @@ -14,10 +15,10 @@ import 'package:hiddify/core/model/region.dart'; import 'package:hiddify/core/preferences/general_preferences.dart'; import 'package:hiddify/features/common/general_pref_tiles.dart'; import 'package:hiddify/features/settings/data/config_option_repository.dart'; +import 'package:hiddify/features/settings/widget/preference_tile.dart'; import 'package:hiddify/gen/assets.gen.dart'; import 'package:hiddify/utils/utils.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:timezone_to_country/timezone_to_country.dart'; class IntroPage extends HookConsumerWidget with PresLogger { const IntroPage({super.key}); @@ -94,7 +95,18 @@ class IntroPage extends HookConsumerWidget with PresLogger { ), const Gap(24), const LocalePrefTile(), - const RegionPrefTile(), + ChoicePreferenceWidget( + selected: ref.watch(ConfigOptions.region), + preferences: ref.watch(ConfigOptions.region.notifier), + choices: Region.values, + title: t.pages.settings.routing.region, + showFlag: true, + icon: Icons.place_rounded, + presentChoice: (value) => value.present(t), + onChanged: (val) async { + await ref.read(ConfigOptions.directDnsAddress.notifier).reset(); + }, + ), const EnableAnalyticsPrefTile(), const Gap(24), Focus( @@ -182,7 +194,7 @@ class IntroPage extends HookConsumerWidget with PresLogger { Future autoSelectRegion(WidgetRef ref) async { try { - final countryCode = await TimeZoneToCountry.getLocalCountryCode(); + final countryCode = RegionDetector.detect(); final regionLocale = _getRegionLocale(countryCode); loggy.debug('Timezone Region: ${regionLocale.region} Locale: ${regionLocale.locale}'); await ref.read(ConfigOptions.region.notifier).update(regionLocale.region); @@ -227,9 +239,9 @@ class IntroPage extends HookConsumerWidget with PresLogger { case "AF": return RegionLocale(Region.af, AppLocale.fa); case "BR": - return RegionLocale(Region.other, AppLocale.ptBr); + return RegionLocale(Region.br, AppLocale.ptBr); case "TR": - return RegionLocale(Region.other, AppLocale.tr); + return RegionLocale(Region.tr, AppLocale.tr); default: return RegionLocale(Region.other, AppLocale.en); } @@ -242,3 +254,181 @@ class RegionLocale { RegionLocale(this.region, this.locale); } + +class RegionDetector { + /// Returns: 'IR' | 'AF' | 'CN' | 'TR' | 'RU' | 'BR' | 'US' + static String detect() { + final now = DateTime.now(); + final offset = now.timeZoneOffset.inMinutes; + final tz = now.timeZoneName.toLowerCase().trim(); + + if (offset == 210) return 'IR'; + + if (offset == 270) { + final (_, country) = _parseLocale(); + return country == 'IR' ? 'IR' : 'AF'; + } + + final fromName = _fromTzName(tz, offset); + if (fromName != null) return fromName; + + final candidates = _candidatesForOffset(offset); + if (candidates.isEmpty) return 'US'; + + return _resolveByLocale(candidates); + } + + static String? _fromTzName(String tz, int offset) { + if (tz.contains('/')) { + final city = tz.split('/').last.replaceAll(' ', '_'); + final r = _ianaCities[city]; + if (r != null) return r; + } + + if (tz == 'irst' || tz == 'irdt' || tz.contains('iran')) return 'IR'; + + if (tz == 'aft' || tz.contains('afghanistan')) return 'AF'; + + if (tz == 'trt' || tz.contains('turkey') || tz.contains('istanbul')) { + return 'TR'; + } + + if (tz.contains('china') || tz.contains('beijing')) return 'CN'; + if (tz == 'cst' && offset == 480) return 'CN'; + + if (_matchesRussiaTz(tz)) return 'RU'; + + if (_matchesBrazilTz(tz)) return 'BR'; + + return null; + } + + static bool _matchesRussiaTz(String tz) { + if (tz.contains('russia') || tz.contains('moscow')) return true; + + const abbrs = {'msk', 'yekt', 'omst', 'krat', 'irkt', 'yakt', 'vlat', 'magt', 'pett', 'sakt', 'sret'}; + if (abbrs.contains(tz)) return true; + + const winKeys = [ + 'ekaterinburg', + 'kaliningrad', + 'yakutsk', + 'vladivostok', + 'magadan', + 'sakhalin', + 'kamchatka', + 'astrakhan', + 'saratov', + 'volgograd', + 'altai', + 'tomsk', + 'transbaikal', + 'n. central asia', + 'north asia', + ]; + return winKeys.any(tz.contains); + } + + static bool _matchesBrazilTz(String tz) { + if (tz == 'brt' || tz == 'brst') return true; + if (tz.contains('brazil') || tz.contains('brasilia')) return true; + + const winKeys = ['e. south america', 'central brazilian', 'tocantins', 'bahia']; + return winKeys.any(tz.contains); + } + + static Set _candidatesForOffset(int offset) { + final c = {}; + + if (offset == 180) c.add('TR'); + + if (offset == 480) c.add('CN'); + + if (_ruOffsets.contains(offset)) c.add('RU'); + + if (_brOffsets.contains(offset)) c.add('BR'); + + return c; + } + + static const _ruOffsets = {120, 180, 240, 300, 360, 420, 480, 540, 600, 660, 720}; + + static const _brOffsets = {-120, -180, -240, -300}; + + static String _resolveByLocale(Set candidates) { + final (lang, country) = _parseLocale(); + + if (country != null && candidates.contains(country)) { + return country; + } + + final regionFromLang = _langToRegion[lang]; + if (regionFromLang != null && candidates.contains(regionFromLang)) { + return regionFromLang; + } + + return 'US'; + } + + static (String, String?) _parseLocale() { + try { + final parts = Platform.localeName.split(RegExp(r'[_\-.]')); + final lang = parts.first.toLowerCase(); + + String? country; + for (final p in parts.skip(1)) { + if (p.length == 2) { + country = p.toUpperCase(); + break; + } + } + + return (lang, country); + } catch (_) { + return ('en', null); + } + } + + static const _langToRegion = {'fa': 'IR', 'ps': 'AF', 'tr': 'TR', 'zh': 'CN', 'ru': 'RU', 'pt': 'BR'}; + + static const _ianaCities = { + 'tehran': 'IR', + 'kabul': 'AF', + 'istanbul': 'TR', + 'shanghai': 'CN', + 'chongqing': 'CN', + 'urumqi': 'CN', + 'harbin': 'CN', + 'moscow': 'RU', + 'kaliningrad': 'RU', + 'samara': 'RU', + 'yekaterinburg': 'RU', + 'omsk': 'RU', + 'novosibirsk': 'RU', + 'barnaul': 'RU', + 'tomsk': 'RU', + 'krasnoyarsk': 'RU', + 'irkutsk': 'RU', + 'chita': 'RU', + 'yakutsk': 'RU', + 'vladivostok': 'RU', + 'magadan': 'RU', + 'sakhalin': 'RU', + 'kamchatka': 'RU', + 'anadyr': 'RU', + 'volgograd': 'RU', + 'saratov': 'RU', + 'astrakhan': 'RU', + 'sao_paulo': 'BR', + 'fortaleza': 'BR', + 'recife': 'BR', + 'manaus': 'BR', + 'belem': 'BR', + 'cuiaba': 'BR', + 'bahia': 'BR', + 'rio_branco': 'BR', + 'noronha': 'BR', + 'porto_velho': 'BR', + 'campo_grande': 'BR', + }; +} diff --git a/lib/features/settings/overview/sections/route_options_page.dart b/lib/features/settings/overview/sections/route_options_page.dart index bd25208c1..89eb4e90d 100644 --- a/lib/features/settings/overview/sections/route_options_page.dart +++ b/lib/features/settings/overview/sections/route_options_page.dart @@ -46,6 +46,7 @@ class RouteOptionsPage extends HookConsumerWidget { preferences: ref.watch(ConfigOptions.region.notifier), choices: Region.values, title: t.pages.settings.routing.region, + showFlag: true, icon: Icons.place_rounded, presentChoice: (value) => value.present(t), onChanged: (val) async { diff --git a/lib/features/settings/widget/preference_tile.dart b/lib/features/settings/widget/preference_tile.dart index 33fa48aec..71ef8d256 100644 --- a/lib/features/settings/widget/preference_tile.dart +++ b/lib/features/settings/widget/preference_tile.dart @@ -71,6 +71,7 @@ class ChoicePreferenceWidget extends HookConsumerWidget { this.enabled = true, required this.choices, required this.title, + this.showFlag = false, this.icon, required this.presentChoice, this.validateInput, @@ -82,6 +83,7 @@ class ChoicePreferenceWidget extends HookConsumerWidget { final bool enabled; final List choices; final String title; + final bool showFlag; final IconData? icon; final String Function(T value) presentChoice; final bool Function(String value)? validateInput; @@ -98,6 +100,7 @@ class ChoicePreferenceWidget extends HookConsumerWidget { .read(dialogNotifierProvider.notifier) .showSettingPicker( title: title, + showFlag: showFlag, selected: selected, options: choices, getTitle: (e) => presentChoice(e), diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index ae51662bf..3893a3e73 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -7,7 +7,6 @@ #include "generated_plugin_registrant.h" #include -#include #include #include #include @@ -20,9 +19,6 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) dynamic_color_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "DynamicColorPlugin"); dynamic_color_plugin_register_with_registrar(dynamic_color_registrar); - g_autoptr(FlPluginRegistrar) flutter_timezone_registrar = - fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterTimezonePlugin"); - flutter_timezone_plugin_register_with_registrar(flutter_timezone_registrar); g_autoptr(FlPluginRegistrar) gtk_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "GtkPlugin"); gtk_plugin_register_with_registrar(gtk_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 07a9285f1..ebddfcdc7 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -4,7 +4,6 @@ list(APPEND FLUTTER_PLUGIN_LIST dynamic_color - flutter_timezone gtk screen_retriever_linux sentry_flutter diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 25cd81ac2..001b8d666 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -9,7 +9,6 @@ import app_links import device_info_plus import dynamic_color import file_picker -import flutter_timezone import in_app_review import mobile_scanner import network_info_plus @@ -29,7 +28,6 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) DynamicColorPlugin.register(with: registry.registrar(forPlugin: "DynamicColorPlugin")) FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin")) - FlutterTimezonePlugin.register(with: registry.registrar(forPlugin: "FlutterTimezonePlugin")) InAppReviewPlugin.register(with: registry.registrar(forPlugin: "InAppReviewPlugin")) MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin")) NetworkInfoPlusPlugin.register(with: registry.registrar(forPlugin: "NetworkInfoPlusPlugin")) diff --git a/macos/Podfile.lock b/macos/Podfile.lock index d6291b140..e76045733 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -10,12 +10,11 @@ PODS: - FlutterMacOS - file_picker (0.0.1): - FlutterMacOS - - flutter_timezone (0.1.0): - - FlutterMacOS - FlutterMacOS (1.0.0) - in_app_review (2.0.0): - FlutterMacOS - - mobile_scanner (6.0.2): + - mobile_scanner (7.0.0): + - Flutter - FlutterMacOS - network_info_plus (0.0.1): - FlutterMacOS @@ -70,10 +69,9 @@ DEPENDENCIES: - device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`) - dynamic_color (from `Flutter/ephemeral/.symlinks/plugins/dynamic_color/macos`) - file_picker (from `Flutter/ephemeral/.symlinks/plugins/file_picker/macos`) - - flutter_timezone (from `Flutter/ephemeral/.symlinks/plugins/flutter_timezone/macos`) - FlutterMacOS (from `Flutter/ephemeral`) - in_app_review (from `Flutter/ephemeral/.symlinks/plugins/in_app_review/macos`) - - mobile_scanner (from `Flutter/ephemeral/.symlinks/plugins/mobile_scanner/macos`) + - mobile_scanner (from `Flutter/ephemeral/.symlinks/plugins/mobile_scanner/darwin`) - network_info_plus (from `Flutter/ephemeral/.symlinks/plugins/network_info_plus/macos`) - objective_c (from `Flutter/ephemeral/.symlinks/plugins/objective_c/macos`) - package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`) @@ -103,14 +101,12 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/dynamic_color/macos file_picker: :path: Flutter/ephemeral/.symlinks/plugins/file_picker/macos - flutter_timezone: - :path: Flutter/ephemeral/.symlinks/plugins/flutter_timezone/macos FlutterMacOS: :path: Flutter/ephemeral in_app_review: :path: Flutter/ephemeral/.symlinks/plugins/in_app_review/macos mobile_scanner: - :path: Flutter/ephemeral/.symlinks/plugins/mobile_scanner/macos + :path: Flutter/ephemeral/.symlinks/plugins/mobile_scanner/darwin network_info_plus: :path: Flutter/ephemeral/.symlinks/plugins/network_info_plus/macos objective_c: @@ -137,29 +133,28 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/window_manager/macos SPEC CHECKSUMS: - app_links: 86a57d95d4dec830373b8c85c21d1c59a4a5dc21 - cupertino_http: 947a233f40cfea55167a49f2facc18434ea117ba - device_info_plus: 1b14eed9bf95428983aed283a8d51cce3d8c4215 - dynamic_color: 2eaa27267de1ca20d879fbd6e01259773fb1670f - file_picker: e716a70a9fe5fd9e09ebc922d7541464289443af - flutter_timezone: 62400baa441155f2a4144188648f2ff861649747 + app_links: afe860c55c7ef176cea7fb630a2b7d7736de591d + cupertino_http: 94ac07f5ff090b8effa6c5e2c47871d48ab7c86c + device_info_plus: 4fb280989f669696856f8b129e4a5e3cd6c48f76 + dynamic_color: b820c000cc68df65e7ba7ff177cb98404ce56651 + file_picker: 7584aae6fa07a041af2b36a2655122d42f578c1a FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1 - in_app_review: a6a031b9acd03c7d103e341aa334adf2c493fb93 - mobile_scanner: 07710d6b9b2c220ae899de2d7ecf5d77ffa56333 - network_info_plus: 2cb02d8435635eae13b3b79279681985121cf30c - objective_c: e5f8194456e8fc943e034d1af00510a1bc29c067 - package_info_plus: 12f1c5c2cfe8727ca46cbd0b26677728972d9a5b - path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 - screen_retriever_macos: 776e0fa5d42c6163d2bf772d22478df4b302b161 + in_app_review: 0599bccaed5e02f6bed2b0d30d16f86b63ed8638 + mobile_scanner: 9157936403f5a0644ca3779a38ff8404c5434a93 + network_info_plus: 21d1cd6a015ccb2fdff06a1fbfa88d54b4e92f61 + objective_c: ec13431e45ba099cb734eb2829a5c1cd37986cba + package_info_plus: f0052d280d17aa382b932f399edf32507174e870 + path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 + screen_retriever_macos: 452e51764a9e1cdb74b3c541238795849f21557f Sentry: da60d980b197a46db0b35ea12cb8f39af48d8854 - sentry_flutter: 2df8b0aab7e4aba81261c230cbea31c82a62dd1b - share_plus: 1fa619de8392a4398bfaf176d441853922614e89 - shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 + sentry_flutter: 27892878729f42701297c628eb90e7c6529f3684 + share_plus: 510bf0af1a42cd602274b4629920c9649c52f4cc + shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1 - sqlite3_flutter_libs: cc304edcb8e1d8c595d1b08c7aeb46a47691d9db - tray_manager: 9064e219c56d75c476e46b9a21182087930baf90 - url_launcher_macos: c82c93949963e55b228a30115bd219499a6fe404 - window_manager: e25faf20d88283a0d46e7b1a759d07261ca27575 + sqlite3_flutter_libs: f8fc13346870e73fe35ebf6dbb997fbcd156b241 + tray_manager: a104b5c81b578d83f3c3d0f40a997c8b10810166 + url_launcher_macos: 0fba8ddabfc33ce0a9afe7c5fef5aab3d8d2d673 + window_manager: b729e31d38fb04905235df9ea896128991cad99e PODFILE CHECKSUM: a18d1ba050af210055cfb0cee8d759913f9ff3e3 diff --git a/macos/packaging/dmg/background.png b/macos/packaging/dmg/background.png new file mode 100644 index 000000000..283c03859 Binary files /dev/null and b/macos/packaging/dmg/background.png differ diff --git a/macos/packaging/dmg/make_config.yaml b/macos/packaging/dmg/make_config.yaml index 4c545c513..17b4702dc 100644 --- a/macos/packaging/dmg/make_config.yaml +++ b/macos/packaging/dmg/make_config.yaml @@ -1,10 +1,15 @@ title: Hiddify +background: "background.png" +window: + size: + width: 600 + height: 400 contents: - - x: 448 - y: 344 + - x: 440 + y: 230 type: link path: "/Applications" - - x: 192 - y: 344 + - x: 160 + y: 230 type: file path: Hiddify.app diff --git a/pubspec.lock b/pubspec.lock index a21d568d5..5686c3446 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -721,14 +721,6 @@ packages: description: flutter source: sdk version: "0.0.0" - flutter_timezone: - dependency: transitive - description: - name: flutter_timezone - sha256: bc286cecb0366d88e6c4644e3962ebd1ce1d233abc658eb1e0cd803389f84b64 - url: "https://pub.dev" - source: hosted - version: "4.1.0" flutter_typeahead: dependency: "direct main" description: @@ -1892,22 +1884,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.5" - timezone: - dependency: transitive - description: - name: timezone - sha256: ffc9d5f4d1193534ef051f9254063fa53d588609418c84299956c3db9383587d - url: "https://pub.dev" - source: hosted - version: "0.10.0" - timezone_to_country: - dependency: "direct main" - description: - name: timezone_to_country - sha256: c96ffcfbd92f56c1727c18166a73bc6973e47d0516a6989148f219414682948e - url: "https://pub.dev" - source: hosted - version: "3.0.0" timing: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 70a518a6f..1f1d0953e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -85,7 +85,6 @@ dependencies: cupertino_http: ^2.0.1 dart_mappable: ^4.2.1 fluentui_system_icons: ^1.1.229 - timezone_to_country: ^3.0.0 json_path: ^0.7.1 # permission_handler: ^11.3.0 # is not compatible with windows #flutter_easy_permission: ^1.1.2 diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 93cc86637..30d74013f 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -8,7 +8,6 @@ #include #include -#include #include #include #include @@ -23,8 +22,6 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("AppLinksPluginCApi")); DynamicColorPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("DynamicColorPluginCApi")); - FlutterTimezonePluginCApiRegisterWithRegistrar( - registry->GetRegistrarForPlugin("FlutterTimezonePluginCApi")); ScreenRetrieverWindowsPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("ScreenRetrieverWindowsPluginCApi")); SentryFlutterPluginRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index b6f557c31..fa47a18d0 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -5,7 +5,6 @@ list(APPEND FLUTTER_PLUGIN_LIST app_links dynamic_color - flutter_timezone screen_retriever_windows sentry_flutter share_plus