diff --git a/README_ru.md b/README_ru.md
index 5f7fb2b5..a08b63ff 100644
--- a/README_ru.md
+++ b/README_ru.md
@@ -46,9 +46,14 @@
⭐ Подходящая конфигурация для Ирана, Китая, России и других стран.
-📱 Доступно в [Google Play](https://play.google.com/store/apps/details?id=app.hiddify.com)
+📱Доступно в официальных магазинах.
-## 🔗 Скачать
+## 🛍️ Приобретите в магазинах
+
@@ -59,27 +64,42 @@
- | Android |
- 
- 
- 
- 
-
- |
+ iOS |
+
+
+ |
+
+
+ | Android |
+
+ 
+ 
+ 
+
+ |
| Windows |
- 
-
- |
+
+ 
+ 
+
+ |
| MacOS |
-  |
+
+ 
+
+ |
| Linux |
- |
+
+
+
+
+ |
diff --git a/android/app/src/main/kotlin/com/hiddify/hiddify/bg/BoxService.kt b/android/app/src/main/kotlin/com/hiddify/hiddify/bg/BoxService.kt
index b89353bf..0935bc1a 100644
--- a/android/app/src/main/kotlin/com/hiddify/hiddify/bg/BoxService.kt
+++ b/android/app/src/main/kotlin/com/hiddify/hiddify/bg/BoxService.kt
@@ -292,6 +292,9 @@ class BoxService(
}
}
}
+ override fun postServiceClose(){
+ //TODO:
+ }
private suspend fun stopAndAlert(type: Alert, message: String? = null) {
Settings.startedByUser = false
diff --git a/assets/images/connect_norouz.PNG b/assets/images/connect_norouz.PNG
new file mode 100644
index 00000000..280edc56
Binary files /dev/null and b/assets/images/connect_norouz.PNG differ
diff --git a/assets/images/disconnect_norouz.PNG b/assets/images/disconnect_norouz.PNG
new file mode 100644
index 00000000..05bcb560
Binary files /dev/null and b/assets/images/disconnect_norouz.PNG differ
diff --git a/assets/translations/strings_en.i18n.json b/assets/translations/strings_en.i18n.json
index b09df18c..94f76aa8 100644
--- a/assets/translations/strings_en.i18n.json
+++ b/assets/translations/strings_en.i18n.json
@@ -74,7 +74,8 @@
"permissionDeniedError": "Permission Denied",
"unexpectedError": "Something Went Wrong",
"torchSemanticLabel": "Flash Light",
- "facingSemanticLabel": "Camera Facing"
+ "facingSemanticLabel": "Camera Facing",
+ "permissionRequest": "Permission to camera to scan QR Code"
},
"manually": "Manual Entry",
"addingProfileMsg": "Adding Profile",
@@ -372,7 +373,7 @@
"directDnsDomainStrategy": "Direct DNS Domain Strategy",
"mixedPort": "Mixed Port",
"localDnsPort": "Local DNS Port",
- "allowConnectionFromLan": "Allow Connection from LAN",
+ "allowConnectionFromLan": "Share VPN in Local Network",
"tunImplementation": "TUN Implementation",
"mtu": "MTU",
"connectionTestUrl": "Connection Test URL",
diff --git a/assets/translations/strings_fa.i18n.json b/assets/translations/strings_fa.i18n.json
index fe47c651..32081246 100644
--- a/assets/translations/strings_fa.i18n.json
+++ b/assets/translations/strings_fa.i18n.json
@@ -218,17 +218,17 @@
"clearSelection": "پاک کردن انتخابها"
},
"geoAssets": {
- "pageTitle": "داراییهای مسیریابی",
+ "pageTitle": "فایلهای مسیریابی",
"geoip": "ژئو آیپی",
"geosite": "ژئو سایت",
"version": "نسخه ${version}",
"fileMissing": "فایل موجود نیست",
"update": "بهروزرسانی",
"download": "دانلود",
- "failureMsg": "بهروزرسانی دارایی انجام نشد",
- "successMsg": "دارایی با موفقیت بهروزرسانی شد",
- "addRecommended": "افزودن داراییهای پیشنهادی",
- "missingGeoAssetsMsg": "فایلهای داراییهای مسیریابی انتخابی وجود ندارد. یا آنها را دانلود کنید و یا موارد موجود را انتخاب کنید"
+ "failureMsg": "بهروزرسانی فایل انجام نشد",
+ "successMsg": "فایل با موفقیت بهروزرسانی شد",
+ "addRecommended": "افزودن فایلهای توصیهشده",
+ "missingGeoAssetsMsg": "فایلهای مسیریابی انتخابی وجود ندارد. یا آنها را دانلود کنید و یا موارد موجود را انتخاب کنید"
}
},
"about": {
@@ -271,11 +271,12 @@
"unexpected": "خطای غیرمنتظره در سرویس",
"serviceNotRunning": "سرویس در حال اجرا نیست",
"missingPrivilege": "نیازمند دسترسی",
- "missingPrivilegeMsg": "حالت VPN به دسترسی سرپرست نیاز دارد. یا برنامه را دوباره بهعنوان سرپرست راهاندازی کنید و یا حالت سرویس را تغییر دهید.",
- "missingGeoAssets": "داراییهای جغرافیایی وجود ندارند",
- "missingGeoAssetsMsg": "داراییهای جغرافیایی گم شدهاند. تغییر دارایی فعال را در نظر بگیرید و یا یکی را در تنظیمات دانلود کنید.",
- "invalidConfigOptions": "تنظیمات پیکربندی نامعتبر است",
- "invalidConfig": "پیکربندی نامعتبر است",
+
+ "missingPrivilegeMsg": "حالت VPN به دسترسی سرپرست نیاز دارد. یا برنامه را دوباره بهعنوان سرپرست راهاندازی کنید یا حالت سرویس را تغییر دهید.",
+ "missingGeoAssets": "فایلهای جغرافیایی وجود ندارد",
+ "missingGeoAssetsMsg": "فایلهای جغرافیایی گم شدهاند. تغییر فایل فعال را در نظر بگیرید و یا یکی را در تنظیمات دانلود کنید.",
+ "invalidConfigOptions": "تنظیمات پیکربندی نامعتبر",
+ "invalidConfig": "پیکربندی نامعتبر",
"create": "خطای ایجاد سرویس",
"start": "خطای راهاندازی سرویس"
},
@@ -301,7 +302,7 @@
"geoAssets": {
"unexpected": "خطای غیرمنتظره",
"notUpdate": "هیچ بهروزرسانی موجود نیست",
- "activeNotFound": "دارایی فعال ژئو یافت نشد"
+ "activeNotFound": "فایل فعال ژئو یافت نشد"
}
},
"play": {
@@ -372,7 +373,7 @@
"directDnsDomainStrategy": "استراتژی دامنه DNS مستقیم",
"mixedPort": "درگاه چندمنظوره",
"localDnsPort": "درگاه DNS داخلی",
- "allowConnectionFromLan": "اجازهی اتصال از LAN",
+ "allowConnectionFromLan": "اشتراک فیلترشکن در شبکه",
"tunImplementation": "پیادهسازی TUN",
"mtu": "سایز بستهها",
"connectionTestUrl": "لینک بررسی ارتباط",
@@ -395,15 +396,15 @@
"muxProtocol": "پروتکل Mux",
"muxMaxStreams": "حداکثر جریانهای همزمان",
"enableWarp": "فعالسازی WARP",
- "warpDetourMode": "حالت انحرافی",
+ "warpDetourMode": "حالت وارپ",
"warpDetourModes": {
- "proxyOverWarp": "انحراف پراکسیها از طریق WARP",
- "warpOverProxy": "انحراف WARP از طریق پراکسیها",
- "inbound": "انحراف WARP از طریق پراکسیها",
- "outbound": "انحراف پراکسیها از طریق WARP"
+ "proxyOverWarp": "عبور پراکسیها از طریق WARP",
+ "warpOverProxy": "عبور WARP از طریق پراکسیها",
+ "inbound": "عبور WARP از طریق پراکسیها",
+ "outbound": "عبور پراکسیها از طریق WARP"
},
"warpLicenseKey": "کلید مجوز",
- "warpCleanIp": "پاکسازی آیپی",
+ "warpCleanIp": "آیپی تمیز",
"warpPort": "درگاه",
"warpNoise": "تعداد نویز",
"warpNoiseDelay": "تأخیر نویز"
diff --git a/dependencies.properties b/dependencies.properties
index 5ae439c2..6b49c2fc 100644
--- a/dependencies.properties
+++ b/dependencies.properties
@@ -1 +1 @@
-core.version=0.17.8
\ No newline at end of file
+core.version=1.0.0
\ No newline at end of file
diff --git a/ios/Podfile b/ios/Podfile
index c461cbd0..8e07cf3d 100644
--- a/ios/Podfile
+++ b/ios/Podfile
@@ -32,6 +32,8 @@ target 'Runner' do
use_modular_headers!
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
+ pod 'EasyPermissionX/Camera'
+
target 'RunnerTests' do
inherit! :search_paths
end
diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj
index f5a6151e..3522728a 100644
--- a/ios/Runner.xcodeproj/project.pbxproj
+++ b/ios/Runner.xcodeproj/project.pbxproj
@@ -751,7 +751,7 @@
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = SingBoxPacketTunnel/SingBoxPacketTunnel.entitlements;
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 1712;
+ CURRENT_PROJECT_VERSION = 10000;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
EXCLUDED_ARCHS = armv7;
GCC_C_LANGUAGE_STANDARD = gnu17;
@@ -770,7 +770,7 @@
"@executable_path/libcore/",
);
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
- MARKETING_VERSION = 0.17.12;
+ MARKETING_VERSION = 1.0.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
OTHER_LDFLAGS = "-lresolv";
@@ -802,7 +802,7 @@
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = SingBoxPacketTunnel/SingBoxPacketTunnel.entitlements;
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 1712;
+ CURRENT_PROJECT_VERSION = 10000;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
EXCLUDED_ARCHS = armv7;
GCC_C_LANGUAGE_STANDARD = gnu17;
@@ -821,7 +821,7 @@
"@executable_path/libcore/",
);
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
- MARKETING_VERSION = 0.17.12;
+ MARKETING_VERSION = 1.0.0;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = NO;
OTHER_LDFLAGS = "-lresolv";
@@ -851,7 +851,7 @@
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = SingBoxPacketTunnel/SingBoxPacketTunnel.entitlements;
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 1712;
+ CURRENT_PROJECT_VERSION = 10000;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
EXCLUDED_ARCHS = armv7;
GCC_C_LANGUAGE_STANDARD = gnu17;
@@ -870,7 +870,7 @@
"@executable_path/libcore/",
);
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
- MARKETING_VERSION = 0.17.12;
+ MARKETING_VERSION = 1.0.0;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = NO;
OTHER_LDFLAGS = "-lresolv";
diff --git a/ios/SingBoxPacketTunnel/SingBox/ExtensionPlatformInterface.swift b/ios/SingBoxPacketTunnel/SingBox/ExtensionPlatformInterface.swift
index 8d8bb9fb..ff8f68cd 100644
--- a/ios/SingBoxPacketTunnel/SingBox/ExtensionPlatformInterface.swift
+++ b/ios/SingBoxPacketTunnel/SingBox/ExtensionPlatformInterface.swift
@@ -222,7 +222,12 @@ public class ExtensionPlatformInterface: NSObject, LibboxPlatformInterfaceProtoc
}
}
+ public func postServiceClose() {
+ // TODO
+ }
+
func reset() {
networkSettings = nil
}
+
}
diff --git a/ios/SingBoxPacketTunnel/SingBox/ExtensionProvider.swift b/ios/SingBoxPacketTunnel/SingBox/ExtensionProvider.swift
index 3be722fb..85ab3102 100644
--- a/ios/SingBoxPacketTunnel/SingBox/ExtensionProvider.swift
+++ b/ios/SingBoxPacketTunnel/SingBox/ExtensionProvider.swift
@@ -137,6 +137,7 @@ open class ExtensionProvider: NEPacketTunnelProvider {
stopService()
await startService()
}
+
override open func stopTunnel(with reason: NEProviderStopReason) async {
writeMessage("(packet-tunnel) stopping, reason: \(reason)")
diff --git a/lib/core/preferences/general_preferences.dart b/lib/core/preferences/general_preferences.dart
index aefae330..bc36ab66 100644
--- a/lib/core/preferences/general_preferences.dart
+++ b/lib/core/preferences/general_preferences.dart
@@ -64,6 +64,11 @@ abstract class Preferences {
"started_by_user",
false,
);
+
+ static final storeReviewedByUser = PreferencesNotifier.create
(
+ "store_reviewed_by_user",
+ false,
+ );
}
@Riverpod(keepAlive: true)
diff --git a/lib/features/common/qr_code_scanner_screen.dart b/lib/features/common/qr_code_scanner_screen.dart
index d534279f..cfb3f679 100644
--- a/lib/features/common/qr_code_scanner_screen.dart
+++ b/lib/features/common/qr_code_scanner_screen.dart
@@ -1,14 +1,15 @@
import 'package:dartx/dartx.dart';
import 'package:fluentui_system_icons/fluentui_system_icons.dart';
import 'package:flutter/material.dart';
+import 'package:flutter_easy_permission/easy_permissions.dart';
import 'package:hiddify/core/localization/translations.dart';
import 'package:hiddify/utils/utils.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
-import 'package:permission_handler/permission_handler.dart';
+// import 'package:permission_handler/permission_handler.dart';
-final _cameraPermissionProvider =
- FutureProvider.autoDispose((ref) => Permission.camera.request());
+const permissions = [Permissions.CAMERA];
+const permissionGroup = [PermissionGroup.Camera];
class QRCodeScannerScreen extends StatefulHookConsumerWidget {
const QRCodeScannerScreen({super.key});
@@ -32,185 +33,157 @@ class _QRCodeScannerScreenState extends ConsumerState
final controller =
MobileScannerController(detectionTimeoutMs: 500, autoStart: false);
bool started = false;
- bool settingsOpened = false;
+ late FlutterEasyPermission _easyPermission;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
+
+ _easyPermission = FlutterEasyPermission()
+ ..addPermissionCallback(onGranted: (requestCode, androidPerms, iosPerm) {
+ debugPrint("android:$androidPerms");
+ debugPrint("iOS:$iosPerm");
+ startQrScannerIfPermissionGranted();
+ }, onDenied: (requestCode, androidPerms, iosPerm, isPermanent) {
+ if (isPermanent) {
+ FlutterEasyPermission.showAppSettingsDialog(title: "Camera");
+ } else {
+ debugPrint("android:$androidPerms");
+ debugPrint("iOS:$iosPerm");
+ }
+ }, onSettingsReturned: () {
+ startQrScannerIfPermissionGranted();
+ });
}
@override
void dispose() {
controller.stop();
+ _easyPermission.dispose();
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
- @override
- void didChangeAppLifecycleState(AppLifecycleState state) {
- if (state == AppLifecycleState.resumed && settingsOpened) {
- loggy.debug("resumed");
- ref.invalidate(_cameraPermissionProvider);
- settingsOpened = false;
- }
+ void startQrScannerIfPermissionGranted() {
+ FlutterEasyPermission.has(perms: permissions, permsGroup: permissionGroup)
+ .then((value) {
+ if (value) {
+ controller.start().then((result) {
+ if (result != null) {
+ setState(() {
+ started = true;
+ });
+ }
+ }).catchError((error) {
+ loggy.warning("Error starting scanner: $error");
+ });
+ } else {}
+ });
}
@override
Widget build(BuildContext context) {
final t = ref.watch(translationsProvider);
- ref.listen(
- _cameraPermissionProvider,
- (previous, next) async {
- if (next case AsyncData(:final value)
- when value == PermissionStatus.granted) {
- try {
- final result = await controller.start();
- if (result != null) {
- setState(() {
- started = true;
- });
- }
- } catch (error) {
- loggy.warning("error starting scanner: $error", error);
- }
- }
- },
- );
+ startQrScannerIfPermissionGranted();
- switch (ref.watch(_cameraPermissionProvider)) {
- case AsyncData(value: final status)
- when status == PermissionStatus.granted:
- final size = MediaQuery.sizeOf(context);
- final overlaySize = (size.shortestSide - 12).coerceAtMost(248);
+ final size = MediaQuery.sizeOf(context);
+ final overlaySize = (size.shortestSide - 12).coerceAtMost(248);
- return Scaffold(
- extendBodyBehindAppBar: true,
- appBar: AppBar(
- backgroundColor: Colors.transparent,
- iconTheme: Theme.of(context).iconTheme.copyWith(
- color: Colors.white,
- size: 32,
- ),
- actions: [
- IconButton(
- icon: ValueListenableBuilder(
- valueListenable: controller.torchState,
- builder: (context, state, child) {
- switch (state) {
- case TorchState.off:
- return const Icon(
- FluentIcons.flash_off_24_regular,
- color: Colors.grey,
- );
- case TorchState.on:
- return const Icon(
- FluentIcons.flash_24_regular,
- color: Colors.yellow,
- );
- }
- },
- ),
- tooltip: t.profile.add.qrScanner.torchSemanticLabel,
- onPressed: () => controller.toggleTorch(),
- ),
- IconButton(
- icon: const Icon(FluentIcons.camera_switch_24_regular),
- tooltip: t.profile.add.qrScanner.facingSemanticLabel,
- onPressed: () => controller.switchCamera(),
- ),
- ],
- ),
- body: Stack(
- children: [
- MobileScanner(
- controller: controller,
- onDetect: (capture) {
- final rawData = capture.barcodes.first.rawValue;
- loggy.debug('captured raw: [$rawData]');
- if (rawData != null) {
- final uri = Uri.tryParse(rawData);
- if (context.mounted && uri != null) {
- loggy.debug('captured url: [$uri]');
- Navigator.of(context, rootNavigator: true)
- .pop(uri.toString());
- }
- } else {
- loggy.warning("unable to capture");
- }
- },
- errorBuilder: (_, error, __) {
- final message = switch (error.errorCode) {
- MobileScannerErrorCode.permissionDenied =>
- t.profile.add.qrScanner.permissionDeniedError,
- _ => t.profile.add.qrScanner.unexpectedError,
- };
-
- return Center(
- child: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- const Padding(
- padding: EdgeInsets.only(bottom: 8),
- child: Icon(
- FluentIcons.error_circle_24_regular,
- color: Colors.white,
- ),
- ),
- Text(message),
- Text(error.errorDetails?.message ?? ''),
- ],
- ),
- );
- },
- ),
- if (started)
- CustomPaint(
- painter: ScannerOverlay(
- Rect.fromCenter(
- center: size.center(Offset.zero),
- width: overlaySize,
- height: overlaySize,
- ),
- ),
- ),
- ],
- ),
- );
-
- case AsyncData(value: final status):
- return Scaffold(
- appBar: AppBar(),
- body: Center(
- child: Column(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- Text(t.profile.add.qrScanner.permissionDeniedError),
- if (status == PermissionStatus.permanentlyDenied)
- TextButton(
- onPressed: () async {
- settingsOpened = await openAppSettings();
- },
- child: Text(t.general.openAppSettings),
- )
- else
- TextButton(
- onPressed: () {
- ref.invalidate(_cameraPermissionProvider);
- },
- child: Text(t.general.grantPermission),
- ),
- ],
+ return Scaffold(
+ extendBodyBehindAppBar: true,
+ appBar: AppBar(
+ backgroundColor: Colors.transparent,
+ iconTheme: Theme.of(context).iconTheme.copyWith(
+ color: Colors.white,
+ size: 32,
),
+ actions: [
+ IconButton(
+ icon: ValueListenableBuilder(
+ valueListenable: controller.torchState,
+ builder: (context, state, child) {
+ switch (state) {
+ case TorchState.off:
+ return const Icon(
+ FluentIcons.flash_off_24_regular,
+ color: Colors.grey,
+ );
+ case TorchState.on:
+ return const Icon(
+ FluentIcons.flash_24_regular,
+ color: Colors.yellow,
+ );
+ }
+ },
+ ),
+ tooltip: t.profile.add.qrScanner.torchSemanticLabel,
+ onPressed: () => controller.toggleTorch(),
),
- );
+ IconButton(
+ icon: const Icon(FluentIcons.camera_switch_24_regular),
+ tooltip: t.profile.add.qrScanner.facingSemanticLabel,
+ onPressed: () => controller.switchCamera(),
+ ),
+ ],
+ ),
+ body: Stack(
+ children: [
+ MobileScanner(
+ controller: controller,
+ onDetect: (capture) {
+ final rawData = capture.barcodes.first.rawValue;
+ loggy.debug('captured raw: [$rawData]');
+ if (rawData != null) {
+ final uri = Uri.tryParse(rawData);
+ if (context.mounted && uri != null) {
+ loggy.debug('captured url: [$uri]');
+ Navigator.of(context, rootNavigator: true)
+ .pop(uri.toString());
+ }
+ } else {
+ loggy.warning("unable to capture");
+ }
+ },
+ errorBuilder: (_, error, __) {
+ final message = switch (error.errorCode) {
+ MobileScannerErrorCode.permissionDenied =>
+ t.profile.add.qrScanner.permissionDeniedError,
+ _ => t.profile.add.qrScanner.unexpectedError,
+ };
- default:
- return Scaffold(
- appBar: AppBar(),
- );
- }
+ return Center(
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ const Padding(
+ padding: EdgeInsets.only(bottom: 8),
+ child: Icon(
+ FluentIcons.error_circle_24_regular,
+ color: Colors.white,
+ ),
+ ),
+ Text(message),
+ Text(error.errorDetails?.message ?? ''),
+ ],
+ ),
+ );
+ },
+ ),
+ if (started)
+ CustomPaint(
+ painter: ScannerOverlay(
+ Rect.fromCenter(
+ center: size.center(Offset.zero),
+ width: overlaySize,
+ height: overlaySize,
+ ),
+ ),
+ ),
+ ],
+ ),
+ );
}
}
diff --git a/lib/features/config_option/notifier/warp_option_notifier.dart b/lib/features/config_option/notifier/warp_option_notifier.dart
index 31cb58a9..5dcdc2ed 100644
--- a/lib/features/config_option/notifier/warp_option_notifier.dart
+++ b/lib/features/config_option/notifier/warp_option_notifier.dart
@@ -65,7 +65,9 @@ class WarpOptionNotifier extends _$WarpOptionNotifier with AppLogger {
await ref
.read(ConfigOptions.warpAccessToken.notifier)
.update(warp.accessToken);
-
+ await ref
+ .read(ConfigOptions.warpWireguardConfig.notifier)
+ .update(warp.wireguardConfig);
return warp.log;
});
diff --git a/lib/features/config_option/overview/config_options_page.dart b/lib/features/config_option/overview/config_options_page.dart
index ff2e4104..c03ea937 100644
--- a/lib/features/config_option/overview/config_options_page.dart
+++ b/lib/features/config_option/overview/config_options_page.dart
@@ -212,29 +212,29 @@ class ConfigOptionsPage extends HookConsumerWidget {
.watch(ConfigOptions.enableDnsRouting.notifier)
.update,
),
- const SettingsDivider(),
- SettingsSection(experimental(t.config.section.mux)),
- SwitchListTile(
- title: Text(t.config.enableMux),
- value: ref.watch(ConfigOptions.enableMux),
- onChanged:
- ref.watch(ConfigOptions.enableMux.notifier).update,
- ),
- ChoicePreferenceWidget(
- selected: ref.watch(ConfigOptions.muxProtocol),
- preferences: ref.watch(ConfigOptions.muxProtocol.notifier),
- choices: MuxProtocol.values,
- title: t.config.muxProtocol,
- presentChoice: (value) => value.name,
- ),
- ValuePreferenceWidget(
- value: ref.watch(ConfigOptions.muxMaxStreams),
- preferences:
- ref.watch(ConfigOptions.muxMaxStreams.notifier),
- title: t.config.muxMaxStreams,
- inputToValue: int.tryParse,
- digitsOnly: true,
- ),
+ // const SettingsDivider(),
+ // SettingsSection(experimental(t.config.section.mux)),
+ // SwitchListTile(
+ // title: Text(t.config.enableMux),
+ // value: ref.watch(ConfigOptions.enableMux),
+ // onChanged:
+ // ref.watch(ConfigOptions.enableMux.notifier).update,
+ // ),
+ // ChoicePreferenceWidget(
+ // selected: ref.watch(ConfigOptions.muxProtocol),
+ // preferences: ref.watch(ConfigOptions.muxProtocol.notifier),
+ // choices: MuxProtocol.values,
+ // title: t.config.muxProtocol,
+ // presentChoice: (value) => value.name,
+ // ),
+ // ValuePreferenceWidget(
+ // value: ref.watch(ConfigOptions.muxMaxStreams),
+ // preferences:
+ // ref.watch(ConfigOptions.muxMaxStreams.notifier),
+ // title: t.config.muxMaxStreams,
+ // inputToValue: int.tryParse,
+ // digitsOnly: true,
+ // ),
const SettingsDivider(),
SettingsSection(t.config.section.inbound),
ChoicePreferenceWidget(
diff --git a/lib/features/config_option/widget/quick_settings_modal.dart b/lib/features/config_option/widget/quick_settings_modal.dart
index 2ed83d1c..e5acea73 100644
--- a/lib/features/config_option/widget/quick_settings_modal.dart
+++ b/lib/features/config_option/widget/quick_settings_modal.dart
@@ -65,11 +65,11 @@ class QuickSettingsModal extends HookConsumerWidget {
ref.watch(ConfigOptions.enableTlsFragment.notifier).update,
title: Text(t.config.enableTlsFragment),
),
- SwitchListTile(
- value: ref.watch(ConfigOptions.enableMux),
- onChanged: ref.watch(ConfigOptions.enableMux.notifier).update,
- title: Text(t.config.enableMux),
- ),
+ // SwitchListTile(
+ // value: ref.watch(ConfigOptions.enableMux),
+ // onChanged: ref.watch(ConfigOptions.enableMux.notifier).update,
+ // title: Text(t.config.enableMux),
+ // ),
ListTile(
title: Text(t.config.allOptions),
trailing: const Icon(FluentIcons.chevron_right_24_regular),
diff --git a/lib/features/connection/notifier/connection_notifier.dart b/lib/features/connection/notifier/connection_notifier.dart
index 8e3b07f7..8ce1ddfa 100644
--- a/lib/features/connection/notifier/connection_notifier.dart
+++ b/lib/features/connection/notifier/connection_notifier.dart
@@ -11,6 +11,7 @@ import 'package:hiddify/utils/utils.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:rxdart/rxdart.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
+import 'package:in_app_review/in_app_review.dart';
part 'connection_notifier.g.dart';
@@ -30,6 +31,14 @@ class ConnectionNotifier extends _$ConnectionNotifier with AppLogger {
if (previous case AsyncData(:final value) when !value.isConnected) {
if (next case AsyncData(value: final Connected _)) {
await ref.read(hapticServiceProvider.notifier).heavyImpact();
+
+ if (Platform.isAndroid &&
+ !ref.read(Preferences.storeReviewedByUser)) {
+ if (await InAppReview.instance.isAvailable()) {
+ InAppReview.instance.requestReview();
+ ref.read(Preferences.storeReviewedByUser.notifier).update(true);
+ }
+ }
}
}
},
diff --git a/lib/features/home/widget/connection_button.dart b/lib/features/home/widget/connection_button.dart
index e8aa4b29..e0cda02b 100644
--- a/lib/features/home/widget/connection_button.dart
+++ b/lib/features/home/widget/connection_button.dart
@@ -25,6 +25,7 @@ class ConnectionButton extends HookConsumerWidget {
final connectionStatus = ref.watch(connectionNotifierProvider);
final requiresReconnect =
ref.watch(configOptionNotifierProvider).valueOrNull;
+ final today = DateTime.now();
ref.listen(
connectionNotifierProvider,
@@ -93,6 +94,19 @@ class ConnectionButton extends HookConsumerWidget {
AsyncData(value: _) => buttonTheme.idleColor!,
_ => Colors.red,
},
+ image: switch (connectionStatus) {
+ AsyncData(value: Connected()) when requiresReconnect == true =>
+ Assets.images.disconnectNorouz,
+ AsyncData(value: Connected()) => Assets.images.connectNorouz,
+ AsyncData(value: _) => Assets.images.disconnectNorouz,
+ _ => Assets.images.disconnectNorouz,
+ AsyncData(value: Disconnected()) ||
+ AsyncError() =>
+ Assets.images.disconnectNorouz,
+ AsyncData(value: Connected()) => Assets.images.connectNorouz,
+ _ => Assets.images.disconnectNorouz,
+ },
+ useImage: today.day >= 19 && today.day <= 23 && today.month == 3,
);
}
}
@@ -103,12 +117,16 @@ class _ConnectionButton extends StatelessWidget {
required this.enabled,
required this.label,
required this.buttonColor,
+ required this.image,
+ required this.useImage,
});
final VoidCallback onTap;
final bool enabled;
final String label;
final Color buttonColor;
+ final AssetGenImage image;
+ final bool useImage;
@override
Widget build(BuildContext context) {
@@ -144,12 +162,16 @@ class _ConnectionButton extends StatelessWidget {
tween: ColorTween(end: buttonColor),
duration: const Duration(milliseconds: 250),
builder: (context, value, child) {
- return Assets.images.logo.svg(
- colorFilter: ColorFilter.mode(
- value!,
- BlendMode.srcIn,
- ),
- );
+ if (useImage) {
+ return image.image(filterQuality: FilterQuality.medium);
+ } else {
+ return Assets.images.logo.svg(
+ colorFilter: ColorFilter.mode(
+ value!,
+ BlendMode.srcIn,
+ ),
+ );
+ }
},
),
),
diff --git a/lib/singbox/service/ffi_singbox_service.dart b/lib/singbox/service/ffi_singbox_service.dart
index 61b72675..83583a0f 100644
--- a/lib/singbox/service/ffi_singbox_service.dart
+++ b/lib/singbox/service/ffi_singbox_service.dart
@@ -287,7 +287,7 @@ class FFISingboxService with InfraLogger implements SingboxService {
logger.debug("stopping");
receiver.close();
_outboundsStream = null;
- final err = _box.stopCommandClient(4).cast().toDartString();
+ final err = _box.stopCommandClient(5).cast().toDartString();
if (err.isNotEmpty) {
_logger.error("error stopping group client");
}
@@ -311,7 +311,7 @@ class FFISingboxService with InfraLogger implements SingboxService {
try {
final err = _box
- .startCommandClient(4, receiver.sendPort.nativePort)
+ .startCommandClient(5, receiver.sendPort.nativePort)
.cast()
.toDartString();
if (err.isNotEmpty) {
@@ -334,7 +334,7 @@ class FFISingboxService with InfraLogger implements SingboxService {
onCancel: (_) {
logger.debug("stopping");
receiver.close();
- final err = _box.stopCommandClient(12).cast().toDartString();
+ final err = _box.stopCommandClient(13).cast().toDartString();
if (err.isNotEmpty) {
logger.error("failed stopping: $err");
}
@@ -358,7 +358,7 @@ class FFISingboxService with InfraLogger implements SingboxService {
try {
final err = _box
- .startCommandClient(12, receiver.sendPort.nativePort)
+ .startCommandClient(13, receiver.sendPort.nativePort)
.cast()
.toDartString();
if (err.isNotEmpty) {
diff --git a/lib/singbox/service/platform_singbox_service.dart b/lib/singbox/service/platform_singbox_service.dart
index e7707efa..e8376584 100644
--- a/lib/singbox/service/platform_singbox_service.dart
+++ b/lib/singbox/service/platform_singbox_service.dart
@@ -9,14 +9,11 @@ import 'package:hiddify/singbox/model/singbox_outbound.dart';
import 'package:hiddify/singbox/model/singbox_stats.dart';
import 'package:hiddify/singbox/model/singbox_status.dart';
import 'package:hiddify/singbox/model/warp_account.dart';
-import 'package:hiddify/singbox/service/core_singbox_service.dart';
import 'package:hiddify/singbox/service/singbox_service.dart';
import 'package:hiddify/utils/custom_loggers.dart';
import 'package:rxdart/rxdart.dart';
-class PlatformSingboxService extends CoreSingboxService
- with InfraLogger
- implements SingboxService {
+class PlatformSingboxService with InfraLogger implements SingboxService {
static const channelPrefix = "com.hiddify.app";
static const methodChannel = MethodChannel("$channelPrefix/method");
@@ -49,13 +46,34 @@ class PlatformSingboxService extends CoreSingboxService
TaskEither setup(Directories directories, bool debug) {
return TaskEither(
() async {
- await methodChannel.invokeMethod("setup");
+ if (!Platform.isIOS) {
+ return right(unit);
+ }
+ await methodChannel.invokeMethod("setup");
return right(unit);
},
);
}
+ @override
+ TaskEither validateConfigByPath(
+ String path,
+ String tempPath,
+ bool debug,
+ ) {
+ return TaskEither(
+ () async {
+ final message = await methodChannel.invokeMethod(
+ "parse_config",
+ {"path": path, "tempPath": tempPath, "debug": debug},
+ );
+ if (message == null || message.isEmpty) return right(unit);
+ return left(message);
+ },
+ );
+ }
+
@override
TaskEither changeOptions(SingboxConfigOption options) {
return TaskEither(
@@ -70,6 +88,23 @@ class PlatformSingboxService extends CoreSingboxService
);
}
+ @override
+ TaskEither generateFullConfigByPath(String path) {
+ return TaskEither(
+ () async {
+ loggy.debug("generating full config by path");
+ final configJson = await methodChannel.invokeMethod(
+ "generate_config",
+ {"path": path},
+ );
+ if (configJson == null || configJson.isEmpty) {
+ return left("null response");
+ }
+ return right(configJson);
+ },
+ );
+ }
+
@override
TaskEither start(
String path,
diff --git a/libcore b/libcore
index 85cc81a4..aab998fa 160000
--- a/libcore
+++ b/libcore
@@ -1 +1 @@
-Subproject commit 85cc81a46ad041a5c6f4ebb22df3af2ce9c3206a
+Subproject commit aab998fae98d47abf8531e5ed8a736c62bd0f3dc
diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift
index caa49dda..b54cb7b6 100644
--- a/macos/Flutter/GeneratedPluginRegistrant.swift
+++ b/macos/Flutter/GeneratedPluginRegistrant.swift
@@ -7,6 +7,7 @@ import Foundation
import device_info_plus
import flutter_timezone
+import in_app_review
import mobile_scanner
import package_info_plus
import path_provider_foundation
@@ -23,6 +24,7 @@ import window_manager
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
FlutterTimezonePlugin.register(with: registry.registrar(forPlugin: "FlutterTimezonePlugin"))
+ InAppReviewPlugin.register(with: registry.registrar(forPlugin: "InAppReviewPlugin"))
MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin"))
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
diff --git a/pubspec.lock b/pubspec.lock
index fe3121a1..d633ab54 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -503,6 +503,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.6.0"
+ flutter_easy_permission:
+ dependency: "direct main"
+ description:
+ name: flutter_easy_permission
+ sha256: "05eb1b561c894adef28b3ae38d8087fc2635f1047c5e18cf2698fb42b6ccc132"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.2"
flutter_gen_core:
dependency: transitive
description:
@@ -822,6 +830,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.1.3"
+ in_app_review:
+ dependency: "direct main"
+ description:
+ name: in_app_review
+ sha256: "99869244d09adc76af16bf8fd731dd13cef58ecafd5917847589c49f378cbb30"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.9"
+ in_app_review_platform_interface:
+ dependency: transitive
+ description:
+ name: in_app_review_platform_interface
+ sha256: fed2c755f2125caa9ae10495a3c163aa7fab5af3585a9c62ef4a6920c5b45f10
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.5"
injector:
dependency: transitive
description:
@@ -1134,54 +1158,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.2.3"
- permission_handler:
- dependency: "direct main"
- description:
- name: permission_handler
- sha256: "74e962b7fad7ff75959161bb2c0ad8fe7f2568ee82621c9c2660b751146bfe44"
- url: "https://pub.dev"
- source: hosted
- version: "11.3.0"
- permission_handler_android:
- dependency: transitive
- description:
- name: permission_handler_android
- sha256: "1acac6bae58144b442f11e66621c062aead9c99841093c38f5bcdcc24c1c3474"
- url: "https://pub.dev"
- source: hosted
- version: "12.0.5"
- permission_handler_apple:
- dependency: transitive
- description:
- name: permission_handler_apple
- sha256: bdafc6db74253abb63907f4e357302e6bb786ab41465e8635f362ee71fd8707b
- url: "https://pub.dev"
- source: hosted
- version: "9.4.0"
- permission_handler_html:
- dependency: transitive
- description:
- name: permission_handler_html
- sha256: "54bf176b90f6eddd4ece307e2c06cf977fb3973719c35a93b85cc7093eb6070d"
- url: "https://pub.dev"
- source: hosted
- version: "0.1.1"
- permission_handler_platform_interface:
- dependency: transitive
- description:
- name: permission_handler_platform_interface
- sha256: "23dfba8447c076ab5be3dee9ceb66aad345c4a648f0cac292c77b1eb0e800b78"
- url: "https://pub.dev"
- source: hosted
- version: "4.2.0"
- permission_handler_windows:
- dependency: transitive
- description:
- name: permission_handler_windows
- sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e"
- url: "https://pub.dev"
- source: hosted
- version: "0.2.1"
petitparser:
dependency: transitive
description:
diff --git a/pubspec.yaml b/pubspec.yaml
index bba7cd48..a085ce28 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,7 +1,7 @@
name: hiddify
description: Cross Platform Multi Protocol Proxy Frontend.
publish_to: "none"
-version: 0.17.12+1712
+version: 1.0.0+10000
environment:
sdk: ">=3.3.0 <4.0.0"
@@ -76,7 +76,9 @@ dependencies:
http: ^1.2.0
timezone_to_country: ^2.1.0
json_path: ^0.7.1
- permission_handler: ^11.3.0
+ # permission_handler: ^11.3.0 # is not compatible with windows
+ flutter_easy_permission: ^1.1.2
+ in_app_review: ^2.0.9
# circle_flags: ^4.0.2
circle_flags:
git: https://github.com/hiddify-com/flutter_circle_flags.git
@@ -113,6 +115,8 @@ flutter:
- assets/images/tray_icon.png
- assets/images/tray_icon_connected.ico
- assets/images/tray_icon_disconnected.ico
+ - assets/images/connect_norouz.PNG
+ - assets/images/disconnect_norouz.PNG
fonts:
- family: Shabnam
diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc
index 47f071f8..c9dae969 100644
--- a/windows/flutter/generated_plugin_registrant.cc
+++ b/windows/flutter/generated_plugin_registrant.cc
@@ -6,7 +6,6 @@
#include "generated_plugin_registrant.h"
-#include
#include
#include
#include
@@ -18,8 +17,6 @@
#include
void RegisterPlugins(flutter::PluginRegistry* registry) {
- PermissionHandlerWindowsPluginRegisterWithRegistrar(
- registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
ProtocolHandlerWindowsPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("ProtocolHandlerWindowsPluginCApi"));
ScreenRetrieverPluginRegisterWithRegistrar(
diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake
index 58127d66..9642d1c9 100644
--- a/windows/flutter/generated_plugins.cmake
+++ b/windows/flutter/generated_plugins.cmake
@@ -3,7 +3,6 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
- permission_handler_windows
protocol_handler_windows
screen_retriever
sentry_flutter
diff --git a/windows/packaging/msix/make_config.yaml b/windows/packaging/msix/make_config.yaml
index b4cb74e5..e4c3b768 100644
--- a/windows/packaging/msix/make_config.yaml
+++ b/windows/packaging/msix/make_config.yaml
@@ -1,7 +1,7 @@
display_name: Hiddify
publisher_display_name: Hiddify
identity_name: Hiddify.HiddifyNext
-msix_version: 0.17.12.0
+msix_version: 1.0.0.0
logo_path: windows\runner\resources\app_icon.ico
capabilities: internetClient, internetClientServer, privateNetworkClientServer
languages: en-us, zh-cn, zh-tw, tr-tr,fa-ir,ru-ru,pt-br,es-es