Backup before removing hiddify references

This commit is contained in:
Hiddify User
2026-01-15 12:28:40 +03:00
parent f54603d129
commit 36d9e31236
231 changed files with 6648 additions and 1832 deletions

View File

@@ -2,11 +2,12 @@ import 'package:dartx/dartx.dart';
import 'package:fluentui_system_icons/fluentui_system_icons.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:go_router/go_router.dart';
import 'package:hiddify/core/localization/translations.dart';
import 'package:hiddify/core/model/region.dart';
import 'package:hiddify/core/preferences/general_preferences.dart';
import 'package:hiddify/core/router/routes.dart';
import 'package:hiddify/features/common/nested_app_bar.dart';
import 'package:hiddify/features/config_option/data/config_option_repository.dart';
import 'package:hiddify/features/per_app_proxy/model/installed_package_info.dart';
import 'package:hiddify/features/per_app_proxy/model/per_app_proxy_mode.dart';
import 'package:hiddify/features/per_app_proxy/overview/per_app_proxy_notifier.dart';
@@ -112,7 +113,7 @@ class PerAppProxyPage extends HookConsumerWidget with PresLogger {
return Scaffold(
body: CustomScrollView(
slivers: [
isSearching.value ? searchAppBar : appBar,
if (isSearching.value) searchAppBar else appBar,
SliverFillRemaining(
child: TabBarView(
controller: tabController,
@@ -145,63 +146,105 @@ class PerAppProxyPage extends HookConsumerWidget with PresLogger {
if (currentTab.value == 1)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: Row(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: OutlinedButton(
onPressed: perAppProxyMode == PerAppProxyMode.include
? null
: () async {
await ref.read(Preferences.perAppProxyMode.notifier).update(PerAppProxyMode.include);
},
child: Text(t.settings.network.perAppProxyModes.include),
),
),
const SizedBox(width: 12),
Expanded(
child: OutlinedButton(
onPressed: perAppProxyMode == PerAppProxyMode.exclude
? null
: () async {
await ref.read(Preferences.perAppProxyMode.notifier).update(PerAppProxyMode.exclude);
},
child: Text(t.settings.network.perAppProxyModes.exclude),
),
),
IconButton(
icon: const Icon(Icons.help_outline),
onPressed: () {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text(t.settings.network.perAppProxyPageTitle),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"${t.settings.network.perAppProxyModes.include}:",
style: const TextStyle(fontWeight: FontWeight.bold),
),
Text(t.settings.network.perAppProxyModes.includeMsg),
const SizedBox(height: 12),
Text(
"${t.settings.network.perAppProxyModes.exclude}:",
style: const TextStyle(fontWeight: FontWeight.bold),
),
Text(t.settings.network.perAppProxyModes.excludeMsg),
],
// Подсказка над кнопками
Padding(
padding: const EdgeInsets.only(left: 4.0, bottom: 8.0),
child: Text(
perAppProxyMode == PerAppProxyMode.include ? t.settings.network.perAppProxyModes.includeMsg : t.settings.network.perAppProxyModes.excludeMsg,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontSize: 12,
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7),
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text(MaterialLocalizations.of(context).okButtonLabel),
),
),
Row(
children: [
Expanded(
child: perAppProxyMode == PerAppProxyMode.include
? ElevatedButton(
onPressed: null,
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.primary,
foregroundColor: Theme.of(context).colorScheme.onPrimary,
disabledBackgroundColor: Theme.of(context).colorScheme.primary,
disabledForegroundColor: Theme.of(context).colorScheme.onPrimary,
textStyle: const TextStyle(fontSize: 13),
),
child: Text(t.settings.network.perAppProxyModes.include),
)
: OutlinedButton(
onPressed: () async {
await ref.read(Preferences.perAppProxyMode.notifier).update(PerAppProxyMode.include);
},
style: OutlinedButton.styleFrom(
textStyle: const TextStyle(fontSize: 13),
),
child: Text(t.settings.network.perAppProxyModes.include),
),
),
const SizedBox(width: 12),
Expanded(
child: perAppProxyMode == PerAppProxyMode.exclude
? ElevatedButton(
onPressed: null,
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.primary,
foregroundColor: Theme.of(context).colorScheme.onPrimary,
disabledBackgroundColor: Theme.of(context).colorScheme.primary,
disabledForegroundColor: Theme.of(context).colorScheme.onPrimary,
textStyle: const TextStyle(fontSize: 13),
),
child: Text(t.settings.network.perAppProxyModes.exclude),
)
: OutlinedButton(
onPressed: () async {
await ref.read(Preferences.perAppProxyMode.notifier).update(PerAppProxyMode.exclude);
},
style: OutlinedButton.styleFrom(
textStyle: const TextStyle(fontSize: 13),
),
child: Text(t.settings.network.perAppProxyModes.exclude),
),
),
IconButton(
icon: const Icon(Icons.help_outline),
onPressed: () {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text(t.settings.network.perAppProxyPageTitle),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"${t.settings.network.perAppProxyModes.include}:",
style: const TextStyle(fontWeight: FontWeight.bold),
),
Text(t.settings.network.perAppProxyModes.includeMsg),
const SizedBox(height: 12),
Text(
"${t.settings.network.perAppProxyModes.exclude}:",
style: const TextStyle(fontWeight: FontWeight.bold),
),
Text(t.settings.network.perAppProxyModes.excludeMsg),
],
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text(MaterialLocalizations.of(context).okButtonLabel),
),
],
),
],
),
);
},
tooltip: t.settings.network.perAppProxyPageTitle,
);
},
tooltip: t.settings.network.perAppProxyPageTitle,
),
],
),
],
),
@@ -314,11 +357,11 @@ class PerAppProxyPage extends HookConsumerWidget with PresLogger {
),
switch (filteredPackages) {
AsyncData(value: final packages) => packages.isEmpty
? SliverFillRemaining(
? const SliverFillRemaining(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('No packages found'),
Text('No packages found'),
],
),
)
@@ -354,11 +397,11 @@ class PerAppProxyPage extends HookConsumerWidget with PresLogger {
},
itemCount: packages.length,
),
AsyncError() => SliverFillRemaining(
AsyncError() => const SliverFillRemaining(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('Error loading packages'),
Text('Error loading packages'),
],
),
),
@@ -378,15 +421,59 @@ class PerAppProxyPage extends HookConsumerWidget with PresLogger {
final t = ref.read(translationsProvider);
final excludedDomains = ref.read(excludedDomainsListProvider);
final presetZones = [
'.ru',
'.рф',
'.su',
'.by',
'.kz',
'.ua',
// Получаем регион пользователя
final userRegion = ref.read(ConfigOptions.region);
// Определяем зоны по регионам (СОКРАЩЕННЫЙ список для России)
final Map<Region, List<String>> regionZones = {
// Россия и СНГ (топ-6 самых важных)
Region.ru: ['.ru', '.рф', '.su', '.by', '.kz', '.ua'],
// Иран и окружение
Region.ir: ['.ir', '.ایران', '.af', '.pk'],
// Китай и Азия
Region.cn: ['.cn', '.中国', '.hk', '.tw'],
// Индонезия и Юго-Восточная Азия
Region.id: ['.id', '.my', '.ph', '.vn', '.th'],
// Турция и Тюркский мир
Region.tr: ['.tr', '.az', '.uz'],
// Афганистан и окружение
Region.af: ['.af', '.pk', '.tj'],
// Бразилия и Латинская Америка
Region.br: ['.br', '.pt', '.mx', '.ar'],
// Индия и Южная Азия
Region.in_: ['.in', '.भारत', '.pk', '.bd'],
// Остальные
Region.other: ['.com', '.org', '.net'],
};
// Популярные глобальные зоны (топ-10)
final globalZones = [
'.com',
'.org',
'.net',
'.io',
'.ai',
'.co',
'.app',
'.dev',
'.xyz',
'.me',
];
// Собираем все зоны: региональные + глобальные
final presetZones = <dynamic>{
...?regionZones[userRegion],
...globalZones,
}.toList(); // Убираем дубликаты
// Локальное состояние для выбранных зон
final selectedZones = Set<String>.from(excludedDomains.where((d) => presetZones.contains(d)));
@@ -401,109 +488,131 @@ class PerAppProxyPage extends HookConsumerWidget with PresLogger {
top: 16,
bottom: MediaQuery.of(context).viewInsets.bottom + MediaQuery.of(context).padding.bottom + 16,
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Text(
t.settings.network.excludedDomains.addModalTitle,
style: Theme.of(context).textTheme.titleLarge,
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Text(
t.settings.network.excludedDomains.addModalTitle,
style: Theme.of(context).textTheme.titleLarge,
),
),
),
IconButton(
icon: const Icon(Icons.help_outline),
onPressed: () {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text(t.settings.network.excludedDomains.helpTitle),
content: Text(t.settings.network.excludedDomains.helpDescription),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text(MaterialLocalizations.of(context).okButtonLabel),
),
],
),
);
},
),
],
),
const SizedBox(height: 16),
Text(
t.settings.network.excludedDomains.addOwnDomain,
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 8),
TextField(
controller: controller,
decoration: InputDecoration(
hintText: t.settings.network.excludedDomains.domainInputHint,
border: const OutlineInputBorder(),
IconButton(
icon: const Icon(Icons.help_outline),
onPressed: () {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text(t.settings.network.excludedDomains.helpTitle),
content: Text(t.settings.network.excludedDomains.helpDescription),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text(MaterialLocalizations.of(context).okButtonLabel),
),
],
),
);
},
),
],
),
),
const SizedBox(height: 16),
Text(
t.settings.network.excludedDomains.selectReadyZones,
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 8),
...presetZones.map((zone) {
final isSelected = selectedZones.contains(zone);
return CheckboxListTile(
dense: true,
contentPadding: EdgeInsets.zero,
title: Text(zone),
value: isSelected,
onChanged: (selected) {
setState(() {
if (selected == true) {
selectedZones.add(zone);
} else {
selectedZones.remove(zone);
}
});
},
);
}),
const SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text(t.settings.network.excludedDomains.cancel),
const SizedBox(height: 16),
Text(
t.settings.network.excludedDomains.addOwnDomain,
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 4),
Text(
t.settings.network.excludedDomains.domainInputDescription,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
),
),
const SizedBox(height: 8),
TextField(
controller: controller,
decoration: InputDecoration(
hintText: t.settings.network.excludedDomains.domainInputHint,
border: const OutlineInputBorder(),
),
const SizedBox(width: 8),
ElevatedButton(
onPressed: () {
final newList = List<String>.from(excludedDomains);
// Удаляем все preset зоны из списка
newList.removeWhere((d) => presetZones.contains(d));
// Добавляем выбранные зоны
newList.addAll(selectedZones);
// Добавляем свой домен если введён
final domain = controller.text.trim();
if (domain.isNotEmpty && !newList.contains(domain)) {
newList.add(domain);
}
ref.read(excludedDomainsListProvider.notifier).update(newList);
controller.clear();
Navigator.of(context).pop();
},
child: Text(t.settings.network.excludedDomains.ok),
),
const SizedBox(height: 16),
Text(
t.settings.network.excludedDomains.selectReadyZones,
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 8),
// Ограничиваем высоту списка зон
ConstrainedBox(
constraints: BoxConstraints(
maxHeight: MediaQuery.of(context).size.height * 0.2, // Макс 20% высоты экрана
),
],
),
],
child: ListView(
shrinkWrap: true,
physics: const ClampingScrollPhysics(),
children: presetZones.map((zone) {
final zoneStr = zone as String;
final isSelected = selectedZones.contains(zoneStr);
return CheckboxListTile(
dense: true,
contentPadding: EdgeInsets.zero,
title: Text(zoneStr),
value: isSelected,
activeColor: Theme.of(context).colorScheme.primary,
checkColor: Theme.of(context).colorScheme.onPrimary,
onChanged: (selected) {
setState(() {
if (selected == true) {
selectedZones.add(zoneStr);
} else {
selectedZones.remove(zoneStr);
}
});
},
);
}).toList(),
),
),
const SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text(t.settings.network.excludedDomains.cancel),
),
const SizedBox(width: 8),
ElevatedButton(
onPressed: () {
final newList = List<String>.from(excludedDomains);
// Удаляем все preset зоны из списка
newList.removeWhere((d) => presetZones.contains(d));
// Добавляем выбранные зоны
newList.addAll(selectedZones);
// Добавляем свой домен если введён
final domain = controller.text.trim();
if (domain.isNotEmpty && !newList.contains(domain)) {
newList.add(domain);
}
ref.read(excludedDomainsListProvider.notifier).update(newList);
controller.clear();
Navigator.of(context).pop();
},
child: Text(t.settings.network.excludedDomains.ok),
),
],
),
],
),
),
),
),