Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions CUSTOM_ROUTING_RULES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Custom Routing Rules Feature

این فیچر امکان تعریف قوانین مسیریابی سفارشی را فراهم می‌کند که دقیقاً مثل v2rayN عمل می‌کند.

## ویژگی‌ها

- **تعریف قوانین بر اساس دامنه**: می‌توانید دامنه‌های خاصی را برای اتصال مستقیم یا مسدود کردن تعریف کنید
- **تعریف قوانین بر اساس IP**: می‌توانید IP های خاصی را برای اتصال مستقیم یا مسدود کردن تعریف کنید
- **تعریف قوانین بر اساس پورت**: می‌توانید پورت‌های خاصی را برای اتصال مستقیم یا مسدود کردن تعریف کنید
- **تعریف قوانین بر اساس پروتکل**: می‌توانید پروتکل‌های خاصی را برای اتصال مستقیم یا مسدود کردن تعریف کنید
- **انتخاب نوع شبکه**: TCP، UDP یا هر دو
- **انتخاب نوع خروجی**: Proxy، Direct یا Block

## نحوه استفاده

1. به بخش **Config Options** بروید
2. روی **Custom Routing Rules** کلیک کنید
3. روی دکمه **+** کلیک کنید تا قانون جدید اضافه کنید
4. نوع قانون را انتخاب کنید (دامنه، IP، پورت یا پروتکل)
5. مقدار را وارد کنید
6. نوع خروجی را انتخاب کنید (Proxy، Direct یا Block)
7. نوع شبکه را انتخاب کنید (TCP، UDP یا هر دو)
8. روی **Save** کلیک کنید

## نمونه‌های کاربردی

### اتصال مستقیم به سایت‌های ایرانی
```
Domains: *.ir, geosite:ir
Outbound: Direct
Network: TCP/UDP
```

### مسدود کردن تبلیغات
```
Domains: *.doubleclick.net, *.googleadservices.com
Outbound: Block
Network: TCP/UDP
```

### اتصال مستقیم به پورت‌های خاص
```
Port: 80, 443
Outbound: Direct
Network: TCP
```

### استفاده از پروکسی برای پروتکل خاص
```
Protocol: http
Outbound: Proxy
Network: TCP
```

## نکات مهم

- قوانین به ترتیب اولویت اعمال می‌شوند
- قانون اول که با درخواست مطابقت داشته باشد اعمال می‌شود
- می‌توانید از wildcard (*) برای دامنه‌ها استفاده کنید
- می‌توانید از geoip و geosite استفاده کنید
- قوانین به صورت خودکار در SingBox config اعمال می‌شوند

24 changes: 22 additions & 2 deletions assets/translations/strings_en.i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
"showMore": "Show More",
"showLess": "Show Less",
"openAppSettings": "Open App Settings",
"grantPermission": "Grant Permission"
"grantPermission": "Grant Permission",
"rules": "rules"
},
"intro": {
"termsAndPolicyCaution(rich)": "By Continuing You Agree With ${tap(@:about.termsAndConditions)}",
Expand Down Expand Up @@ -426,7 +427,26 @@
"warpNoise": "Noise Count",
"warpNoiseSize": "Noise Size",
"warpNoiseMode": "Noise Mode",
"warpNoiseDelay": "Noise Delay"
"warpNoiseDelay": "Noise Delay",
"customRoutingRules": "Custom Routing Rules",
"addCustomRule": "Add Custom Rule",
"editCustomRule": "Edit Custom Rule",
"noCustomRules": "No custom rules defined",
"addCustomRuleHint": "Click the + button to add a new rule",
"domains": "Domains",
"domainsHint": "Example: example.com, *.google.com, geosite:ir",
"ip": "IP",
"ipHint": "Example: 192.168.1.1, geoip:ir",
"port": "Port",
"portHint": "Example: 80, 443, 8080",
"protocol": "Protocol",
"protocolHint": "Example: http, https, tcp, udp",
"outbound": "Outbound",
"network": "Network",
"proxy": "Proxy",
"direct": "Direct",
"block": "Block",
"customRule": "Custom Rule"
},
"window": {
"hide": "Hide",
Expand Down
24 changes: 22 additions & 2 deletions assets/translations/strings_fa.i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
"showMore": "نمایش بیشتر",
"showLess": "نمایش کمتر",
"openAppSettings": "باز کردن تنظیمات برنامه",
"grantPermission": "اجازه دادن"
"grantPermission": "اجازه دادن",
"rules": "قانون"
},
"intro": {
"termsAndPolicyCaution(rich)": "در صورت ادامه با ${tap(@:about.termsAndConditions)} موافقت می‌کنید",
Expand Down Expand Up @@ -426,7 +427,26 @@
"warpNoise": "تعداد نویز",
"warpNoiseSize": "اندازه نویز",
"warpNoiseMode": "حالت نویز",
"warpNoiseDelay": "تأخیر نویز"
"warpNoiseDelay": "تأخیر نویز",
"customRoutingRules": "قوانین مسیریابی سفارشی",
"addCustomRule": "افزودن قانون سفارشی",
"editCustomRule": "ویرایش قانون سفارشی",
"noCustomRules": "هیچ قانون سفارشی تعریف نشده است",
"addCustomRuleHint": "برای افزودن قانون جدید، روی دکمه + کلیک کنید",
"domains": "دامنه‌ها",
"domainsHint": "مثال: example.com, *.google.com, geosite:ir",
"ip": "آی‌پی",
"ipHint": "مثال: 192.168.1.1, geoip:ir",
"port": "پورت",
"portHint": "مثال: 80, 443, 8080",
"protocol": "پروتکل",
"protocolHint": "مثال: http, https, tcp, udp",
"outbound": "خروجی",
"network": "شبکه",
"proxy": "پروکسی",
"direct": "مستقیم",
"block": "مسدود",
"customRule": "قانون سفارشی"
},
"window": {
"hide": "مخفی کردن",
Expand Down
23 changes: 23 additions & 0 deletions lib/core/router/routes.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'package:go_router/go_router.dart';
import 'package:hiddify/core/router/app_router.dart';
import 'package:hiddify/features/common/adaptive_root_scaffold.dart';
import 'package:hiddify/features/config_option/overview/config_options_page.dart';
import 'package:hiddify/features/config_option/routing_rules/routing_rules_page.dart';
import 'package:hiddify/features/config_option/widget/quick_settings_modal.dart';

import 'package:hiddify/features/home/widget/home_page.dart';
Expand Down Expand Up @@ -122,6 +123,10 @@ class MobileWrapperRoute extends ShellRouteData {
path: "/config-options",
name: ConfigOptionsRoute.name,
),
TypedGoRoute<RoutingRulesRoute>(
path: "/routing-rules",
name: RoutingRulesRoute.name,
),
TypedGoRoute<SettingsRoute>(
path: "/settings",
name: SettingsRoute.name,
Expand Down Expand Up @@ -291,6 +296,24 @@ class QuickSettingsRoute extends GoRouteData {
}
}

class RoutingRulesRoute extends GoRouteData {
const RoutingRulesRoute();
static const name = "Routing Rules";

static final GlobalKey<NavigatorState>? $parentNavigatorKey = _dynamicRootKey;

@override
Page<void> buildPage(BuildContext context, GoRouterState state) {
if (useMobileRouter) {
return const MaterialPage(
name: name,
child: RoutingRulesPage(),
);
}
return const NoTransitionPage(name: name, child: RoutingRulesPage());
}
}

class SettingsRoute extends GoRouteData {
const SettingsRoute();
static const name = "Settings";
Expand Down
28 changes: 27 additions & 1 deletion lib/features/config_option/data/config_option_repository.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'dart:convert';

import 'package:dartx/dartx.dart';
import 'package:fpdart/fpdart.dart';
import 'package:hiddify/core/model/optional_range.dart';
Expand Down Expand Up @@ -335,6 +337,26 @@ abstract class ConfigOptions {
"",
);

static final customRoutingRules = PreferencesNotifier.create<List<SingboxRule>, String>(
"custom-routing-rules",
<SingboxRule>[],
mapFrom: (value) {
try {
final List<dynamic> jsonList = jsonDecode(value);
return jsonList.map((json) => SingboxRule.fromJson(json as Map<String, dynamic>)).toList();
} catch (e) {
return <SingboxRule>[];
}
},
mapTo: (value) {
try {
return jsonEncode(value.map((rule) => rule.toJson()).toList());
} catch (e) {
return jsonEncode(<Map<String, dynamic>>[]);
}
},
);

static final hasExperimentalFeatures = Provider.autoDispose<bool>(
(ref) {
final mode = ref.watch(serviceMode);
Expand Down Expand Up @@ -417,12 +439,16 @@ abstract class ConfigOptions {
"warp2.account-id": warp2AccountId,
"warp2.access-token": warp2AccessToken,
"warp2.wireguard-config": warp2WireguardConfig,

// custom routing rules
"custom-routing-rules": customRoutingRules,
};

static final singboxConfigOptions = FutureProvider<SingboxConfigOption>(
(ref) async {
// final region = ref.watch(Preferences.region);
final rules = <SingboxRule>[];
final customRules = ref.watch(customRoutingRules);
final rules = <SingboxRule>[...customRules];
// final rules = switch (region) {
// Region.ir => [
// const SingboxRule(
Expand Down
10 changes: 10 additions & 0 deletions lib/features/config_option/overview/config_options_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ import 'package:dartx/dartx.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:gap/gap.dart';
import 'package:go_router/go_router.dart';
import 'package:hiddify/core/localization/translations.dart';
import 'package:hiddify/core/model/optional_range.dart';
import 'package:hiddify/core/model/region.dart';
import 'package:hiddify/core/notification/in_app_notification_controller.dart';
import 'package:hiddify/core/router/routes.dart';
import 'package:hiddify/core/widget/adaptive_icon.dart';
import 'package:hiddify/core/widget/tip_card.dart';
import 'package:hiddify/features/common/confirmation_dialogs.dart';
Expand Down Expand Up @@ -164,6 +166,14 @@ class ConfigOptionsPage extends HookConsumerWidget {
value: ref.watch(ConfigOptions.resolveDestination),
onChanged: ref.watch(ConfigOptions.resolveDestination.notifier).update,
),
ListTile(
title: Text(t.config.customRoutingRules),
subtitle: Text('${ref.watch(ConfigOptions.customRoutingRules).length} ${t.general.rules}'),
trailing: const Icon(Icons.chevron_right),
onTap: () {
const RoutingRulesRoute().go(context);
},
),
ChoicePreferenceWidget(
selected: ref.watch(ConfigOptions.ipv6Mode),
preferences: ref.watch(ConfigOptions.ipv6Mode.notifier),
Expand Down
Loading