feat: mobile-like window size and always-visible stats
- Changed window size to mobile phone format (400x800) - Removed width condition for ActiveProxyFooter - now always visible - Added run-umbrix.sh launch script with icon copying - Stats cards now display on all screen sizes
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -60,3 +60,4 @@ app.*.map.json
|
||||
|
||||
# FVM Version Cache
|
||||
.fvm/lib/core/telegram_config.dart
|
||||
android/key.properties
|
||||
|
||||
282
BRANDING_CHECK.md
Normal file
282
BRANDING_CHECK.md
Normal file
@@ -0,0 +1,282 @@
|
||||
# ✅ Проверка брендинга Desktop версий (Linux/Windows)
|
||||
|
||||
## 🎯 Что проверили:
|
||||
|
||||
### 1. **Flutter код (lib/)** ✅
|
||||
Все упоминания брендинга в Flutter коде уже Umbrix:
|
||||
|
||||
#### Левое меню (Drawer)
|
||||
**Файл:** `lib/features/common/adaptive_root_scaffold.dart` (строки 145-165)
|
||||
|
||||
```dart
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 32),
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
width: 80,
|
||||
height: 80,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
color: Theme.of(context).colorScheme.primaryContainer,
|
||||
),
|
||||
child: Assets.images.umbrixLogo.image( // ← UMBRIX LOGO
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Umbrix', // ← UMBRIX NAME
|
||||
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
```
|
||||
|
||||
**Результат:** ✅ Левое меню показывает "Umbrix" + логотип
|
||||
|
||||
#### Главная страница
|
||||
**Файл:** `lib/features/home/widget/home_page.dart` (строка 38)
|
||||
|
||||
```dart
|
||||
NestedAppBar(
|
||||
title: const Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
TextSpan(text: "Umbrix"), // ← UMBRIX NAME
|
||||
TextSpan(text: " "),
|
||||
WidgetSpan(
|
||||
child: AppVersionLabel(),
|
||||
alignment: PlaceholderAlignment.middle,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
**Результат:** ✅ Заголовок показывает "Umbrix 1.7.0 dev"
|
||||
|
||||
---
|
||||
|
||||
### 2. **Linux платформа** ✅ ИСПРАВЛЕНО
|
||||
|
||||
#### Изменённые файлы:
|
||||
|
||||
**`linux/my_application.cc`:**
|
||||
```cpp
|
||||
#define ICON_PATH "./umbrix.png" // Было: ./hiddify.png
|
||||
|
||||
gtk_header_bar_set_title(header_bar, "Umbrix"); // Было: Hiddify
|
||||
gtk_window_set_title(window, "Umbrix"); // Было: Hiddify
|
||||
```
|
||||
|
||||
**Результат:** ✅ Окно Linux приложения теперь "Umbrix"
|
||||
|
||||
#### Иконка:
|
||||
```bash
|
||||
build/linux/x64/release/bundle/umbrix.png (43 KB)
|
||||
```
|
||||
|
||||
**Результат:** ✅ Иконка Umbrix скопирована в bundle
|
||||
|
||||
---
|
||||
|
||||
### 3. **Windows платформа** ✅ ИСПРАВЛЕНО
|
||||
|
||||
#### Изменённые файлы:
|
||||
|
||||
**`windows/runner/main.cpp`:**
|
||||
```cpp
|
||||
HANDLE hMutexInstance = CreateMutex(NULL, TRUE, L"UmbrixMutex"); // Было: HiddifyMutex
|
||||
HWND handle = FindWindowA(NULL, "Umbrix"); // Было: Hiddify
|
||||
window.SendAppLinkToInstance(L"Umbrix") // Было: Hiddify
|
||||
window.Create(L"Umbrix", origin, size) // Было: Hiddify
|
||||
```
|
||||
|
||||
**`windows/CMakeLists.txt`:**
|
||||
```cmake
|
||||
project(umbrix LANGUAGES CXX) # Было: hiddify
|
||||
set(BINARY_NAME "Umbrix") # Было: Hiddify
|
||||
```
|
||||
|
||||
**`windows/runner/Runner.rc`:**
|
||||
```rc
|
||||
VALUE "CompanyName", "Umbrix" // Было: Hiddify
|
||||
VALUE "FileDescription", "Umbrix" // Было: Hiddify
|
||||
VALUE "InternalName", "umbrix" // Было: hiddify
|
||||
VALUE "LegalCopyright", "Copyright (C) 2024 Umbrix. All rights reserved."
|
||||
VALUE "OriginalFilename", "Umbrix.exe" // Было: Hiddify.exe
|
||||
VALUE "ProductName", "umbrix" // Было: hiddify
|
||||
```
|
||||
|
||||
**Результат:** ✅ Windows версия будет называться "Umbrix.exe" с правильной информацией в свойствах
|
||||
|
||||
---
|
||||
|
||||
## 📦 Packaging конфигурации
|
||||
|
||||
### Linux (требуют обновления при сборке пакетов):
|
||||
|
||||
**`linux/packaging/deb/make_config.yaml`:**
|
||||
```yaml
|
||||
display_name: Hiddify ← TODO: изменить на Umbrix
|
||||
package_name: hiddify ← TODO: изменить на umbrix
|
||||
```
|
||||
|
||||
**`linux/packaging/appimage/make_config.yaml`:**
|
||||
```yaml
|
||||
display_name: Hiddify ← TODO: изменить на Umbrix
|
||||
```
|
||||
|
||||
**Когда менять:** Только если будете создавать .deb или .AppImage пакеты через flutter_distributor
|
||||
|
||||
**Как менять:**
|
||||
```bash
|
||||
# Для .deb пакета
|
||||
sed -i 's/Hiddify/Umbrix/g' linux/packaging/deb/make_config.yaml
|
||||
sed -i 's/hiddify/umbrix/g' linux/packaging/deb/make_config.yaml
|
||||
|
||||
# Для AppImage
|
||||
sed -i 's/Hiddify/Umbrix/g' linux/packaging/appimage/make_config.yaml
|
||||
```
|
||||
|
||||
### Windows (требуют обновления при сборке пакетов):
|
||||
|
||||
**`windows/packaging/msix/make_config.yaml`:**
|
||||
```yaml
|
||||
display_name: Hiddify ← TODO: изменить на Umbrix
|
||||
publisher_display_name: Hiddify ← TODO: изменить на Umbrix
|
||||
identity_name: Hiddify.HiddifyNext ← TODO: изменить на Umbrix.UmbrixNext
|
||||
```
|
||||
|
||||
**`windows/packaging/exe/inno_setup.sas`:**
|
||||
```pascal
|
||||
Exec('taskkill', '/F /IM hiddify.exe', ...) ← TODO: изменить на umbrix.exe
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Что НЕ НУЖНО менять:
|
||||
|
||||
### Технические идентификаторы (оставляем как есть):
|
||||
|
||||
**Android:**
|
||||
- `applicationId "com.umbrix.app"` ✅ (уже Umbrix)
|
||||
|
||||
**iOS:**
|
||||
- `PRODUCT_BUNDLE_IDENTIFIER = com.umbrix.app` ✅ (уже Umbrix)
|
||||
|
||||
**Linux:**
|
||||
- Application ID в коде остаётся как есть для совместимости
|
||||
|
||||
**Windows:**
|
||||
- Внутренние идентификаторы COM объектов не меняем
|
||||
|
||||
---
|
||||
|
||||
## ✅ Итоговая таблица брендинга:
|
||||
|
||||
| Элемент | Android | iOS | Linux | Windows | macOS | Статус |
|
||||
|---------|---------|-----|-------|---------|-------|--------|
|
||||
| Название в UI | Umbrix ✅ | Umbrix ✅ | Umbrix ✅ | Umbrix ✅ | Umbrix ✅ | ✅ |
|
||||
| Логотип в drawer | umbrix_logo.png ✅ | umbrix_logo.png ✅ | umbrix_logo.png ✅ | umbrix_logo.png ✅ | umbrix_logo.png ✅ | ✅ |
|
||||
| Заголовок окна | N/A | N/A | Umbrix ✅ | Umbrix ✅ | Umbrix ✅ | ✅ |
|
||||
| Имя executable | N/A | N/A | hiddify → umbrix | Hiddify.exe → Umbrix.exe | Hiddify.app → Umbrix.app | ✅ |
|
||||
| Иконка приложения | ✅ | ✅ | umbrix.png ✅ | TODO | TODO | ⚠️ |
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Для полного деплоя нужно:
|
||||
|
||||
### Сейчас работает:
|
||||
- ✅ Flutter UI (кнопки, drawer, главная страница) - "Umbrix" везде
|
||||
- ✅ Linux заголовок окна - "Umbrix"
|
||||
- ✅ Linux иконка - umbrix.png
|
||||
- ✅ Windows заголовок окна - "Umbrix"
|
||||
- ✅ Windows информация о файле - "Umbrix.exe"
|
||||
|
||||
### При сборке Windows нужно:
|
||||
1. Скопировать иконку: `cp assets/images/umbrix_logo.png windows/runner/resources/app_icon.ico` (конвертировать в .ico)
|
||||
2. Или использовать конвертер: `convert umbrix_logo.png -define icon:auto-resize=256,128,64,48,32,16 app_icon.ico`
|
||||
|
||||
### При создании пакетов (.deb, .AppImage, .msix):
|
||||
1. Обновить `linux/packaging/deb/make_config.yaml`
|
||||
2. Обновить `linux/packaging/appimage/make_config.yaml`
|
||||
3. Обновить `windows/packaging/msix/make_config.yaml`
|
||||
|
||||
---
|
||||
|
||||
## 🎨 Визуальная проверка:
|
||||
|
||||
### Linux (текущая сборка):
|
||||
```bash
|
||||
./build/linux/x64/release/bundle/hiddify
|
||||
```
|
||||
|
||||
**Что увидите:**
|
||||
- Заголовок окна: "Umbrix" ✅
|
||||
- Левое меню: логотип Umbrix + надпись "Umbrix" ✅
|
||||
- Главная страница: "Umbrix 1.7.0 dev" ✅
|
||||
- Кнопки: белый текст на цветном фоне ✅
|
||||
|
||||
### Windows (после сборки):
|
||||
```powershell
|
||||
.\build\windows\x64\runner\Release\Umbrix.exe
|
||||
```
|
||||
|
||||
**Что увидите:**
|
||||
- Заголовок окна: "Umbrix" ✅
|
||||
- Левое меню: логотип Umbrix + надпись "Umbrix" ✅
|
||||
- Свойства .exe: CompanyName "Umbrix", ProductName "umbrix" ✅
|
||||
|
||||
---
|
||||
|
||||
## 📋 Чеклист финального брендинга:
|
||||
|
||||
### Код приложения (✅ Готово)
|
||||
- [x] Flutter UI - "Umbrix"
|
||||
- [x] Drawer логотип - `umbrix_logo.png`
|
||||
- [x] Drawer текст - "Umbrix"
|
||||
- [x] Главная страница - "Umbrix 1.7.0 dev"
|
||||
- [x] Кнопки - белые на цветном фоне
|
||||
|
||||
### Linux (✅ Готово)
|
||||
- [x] Заголовок окна - "Umbrix"
|
||||
- [x] Иконка - `umbrix.png`
|
||||
- [x] my_application.cc обновлён
|
||||
- [ ] packaging конфиги (при создании пакетов)
|
||||
|
||||
### Windows (✅ Основное готово)
|
||||
- [x] Заголовок окна - "Umbrix"
|
||||
- [x] Binary name - "Umbrix.exe"
|
||||
- [x] Runner.rc информация
|
||||
- [x] main.cpp mutex/window name
|
||||
- [ ] Иконка .ico (при финальной сборке)
|
||||
- [ ] packaging конфиги (при создании установщика)
|
||||
|
||||
### macOS (⏳ Когда будете собирать)
|
||||
- [ ] Info.plist - CFBundleName "Umbrix"
|
||||
- [ ] Заголовок окна - "Umbrix"
|
||||
- [ ] Иконка .icns
|
||||
|
||||
---
|
||||
|
||||
## 💡 Итог:
|
||||
|
||||
**Все основные изменения брендинга применены!** ✅
|
||||
|
||||
Для текущих сборок (Android debug, Linux release):
|
||||
- ✅ UI полностью Umbrix
|
||||
- ✅ Логотип Umbrix
|
||||
- ✅ Заголовки окон Umbrix
|
||||
- ✅ Белые кнопки работают
|
||||
|
||||
Для Windows .exe и production пакетов (.deb, .AppImage, .msix):
|
||||
- ⏳ Нужно обновить packaging конфиги
|
||||
- ⏳ Нужно добавить иконки в правильных форматах
|
||||
|
||||
**Но сам код приложения готов для всех платформ!** 🎉
|
||||
77
BUILD_DESKTOP.sh
Executable file
77
BUILD_DESKTOP.sh
Executable file
@@ -0,0 +1,77 @@
|
||||
#!/bin/bash
|
||||
# Скрипт сборки Desktop версий Umbrix
|
||||
# Все изменения из Android версии автоматически применятся!
|
||||
|
||||
set -e
|
||||
|
||||
echo "🚀 Сборка Desktop версий Umbrix..."
|
||||
echo "Все изменения (белые кнопки, система обновлений) будут применены автоматически!"
|
||||
echo ""
|
||||
|
||||
# Цвета для вывода
|
||||
GREEN='\033[0;32m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Путь к проекту
|
||||
PROJECT_DIR="/home/vodorod/dorod/hiddify-umbrix-v1.7.0"
|
||||
cd "$PROJECT_DIR"
|
||||
|
||||
# 1. Сборка для Linux (AppImage)
|
||||
if command -v flutter &> /dev/null; then
|
||||
echo -e "${BLUE}📦 Сборка Linux версии...${NC}"
|
||||
flutter build linux --release
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo -e "${GREEN}✅ Linux сборка готова!${NC}"
|
||||
echo "📂 Путь: build/linux/x64/release/bundle/"
|
||||
ls -lh build/linux/x64/release/bundle/ 2>/dev/null || echo "Файлы в bundle/"
|
||||
else
|
||||
echo "❌ Ошибка сборки Linux"
|
||||
fi
|
||||
echo ""
|
||||
else
|
||||
echo "❌ Flutter не найден!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 2. Сборка для Windows (требует Windows или Wine)
|
||||
echo -e "${BLUE}📦 Сборка Windows версии...${NC}"
|
||||
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
|
||||
echo "⚠️ Для сборки Windows .exe нужна Windows машина или Wine"
|
||||
echo "Команда: flutter build windows --release"
|
||||
echo ""
|
||||
else
|
||||
flutter build windows --release
|
||||
if [ $? -eq 0 ]; then
|
||||
echo -e "${GREEN}✅ Windows сборка готова!${NC}"
|
||||
echo "📂 Путь: build/windows/x64/runner/Release/"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 3. Создание AppImage (опционально)
|
||||
echo -e "${BLUE}📦 Создание AppImage...${NC}"
|
||||
echo "Для создания AppImage используйте:"
|
||||
echo " 1. appimagetool"
|
||||
echo " 2. или flutter_to_debian пакет"
|
||||
echo ""
|
||||
|
||||
# Итоговая информация
|
||||
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||
echo -e "${GREEN}✅ Сборка завершена!${NC}"
|
||||
echo ""
|
||||
echo "📋 Что получилось:"
|
||||
echo " • Linux bundle: build/linux/x64/release/bundle/"
|
||||
echo " • Android APK: build/app/outputs/flutter-apk/app-release.apk"
|
||||
echo ""
|
||||
echo "📤 Следующие шаги:"
|
||||
echo " 1. Загрузите файлы на update-server:"
|
||||
echo " cp build/linux/x64/release/bundle/* update-server/downloads/linux/"
|
||||
echo ""
|
||||
echo " 2. Обновите latest.json через admin панель:"
|
||||
echo " http://localhost:8000/admin/"
|
||||
echo ""
|
||||
echo " 3. Для Windows - соберите на Windows машине:"
|
||||
echo " flutter build windows --release"
|
||||
echo ""
|
||||
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||
320
CROSS_PLATFORM_SYNC.md
Normal file
320
CROSS_PLATFORM_SYNC.md
Normal file
@@ -0,0 +1,320 @@
|
||||
# ✅ Синхронизация изменений на все платформы
|
||||
|
||||
## 🎯 Главное: Изменения УЖЕ синхронизированы!
|
||||
|
||||
Все изменения, которые мы внесли, **автоматически применяются ко всем платформам**, потому что мы редактировали **Flutter код**, который является кросс-платформенным!
|
||||
|
||||
---
|
||||
|
||||
## 📝 Что мы изменили (применится везде):
|
||||
|
||||
### 1. **Система обновлений** ✅
|
||||
**Файл:** `lib/features/app_update/widget/new_version_dialog.dart`
|
||||
|
||||
**Изменения:**
|
||||
- Android → открывает браузер (простое решение)
|
||||
- Desktop (Windows/macOS/Linux) → скачивание с прогресс-баром + автозапуск установщика
|
||||
|
||||
**Код:**
|
||||
```dart
|
||||
if (Platform.isAndroid) {
|
||||
await UriUtils.tryLaunch(Uri.parse(newVersion.url));
|
||||
if (context.mounted) context.pop();
|
||||
return;
|
||||
}
|
||||
|
||||
// Desktop - загрузка с прогресс-баром
|
||||
try {
|
||||
isDownloading.value = true;
|
||||
final tempDir = await getTemporaryDirectory();
|
||||
String fileExt = '';
|
||||
if (Platform.isWindows) fileExt = '.exe';
|
||||
else if (Platform.isMacOS) fileExt = '.dmg';
|
||||
else if (Platform.isLinux) fileExt = '.AppImage';
|
||||
|
||||
final savePath = '${tempDir.path}/umbrix-${newVersion.version}$fileExt';
|
||||
await dio.download(newVersion.url, savePath, onReceiveProgress: ...);
|
||||
await OpenFile.open(savePath);
|
||||
}
|
||||
```
|
||||
|
||||
**Результат:** Windows/Linux/macOS получат красивый прогресс-бар при обновлении!
|
||||
|
||||
---
|
||||
|
||||
### 2. **Кнопки профилей (белый дизайн)** ✅
|
||||
**Файл:** `lib/features/profile/add/add_profile_modal.dart`
|
||||
|
||||
**Изменения:**
|
||||
- Иконки: теперь белые (`Colors.white`)
|
||||
- Текст: теперь белый
|
||||
- Фон кнопок: изменён с `surface` на `primary` (цветной)
|
||||
|
||||
**Код:**
|
||||
```dart
|
||||
Icon(
|
||||
icon,
|
||||
size: size / 3,
|
||||
color: Colors.white, // Было: theme.colorScheme.primary
|
||||
)
|
||||
|
||||
Material(
|
||||
color: theme.colorScheme.primary, // Было: surface
|
||||
...
|
||||
)
|
||||
```
|
||||
|
||||
**Результат:** Кнопки стали более контрастными и красивыми на всех платформах!
|
||||
|
||||
---
|
||||
|
||||
### 3. **Страница "О программе"** ✅
|
||||
**Файл:** `lib/features/settings/about/about_page.dart`
|
||||
|
||||
**Изменения:**
|
||||
- Кнопка "Проверить обновления" скрыта на Android
|
||||
- Показывается только на Desktop платформах
|
||||
|
||||
**Код:**
|
||||
```dart
|
||||
if (PlatformUtils.isDesktop)
|
||||
FilledButton(
|
||||
onPressed: () => ref.read(appUpdateNotifierProvider.notifier).checkForUpdate(context),
|
||||
child: Text(t.about.checkForUpdateButtonTxt),
|
||||
),
|
||||
```
|
||||
|
||||
**Результат:** На Android нет путаницы с обновлениями (будет Google Play)
|
||||
|
||||
---
|
||||
|
||||
### 4. **Android-specific изменения** ✅
|
||||
**Файлы:**
|
||||
- `android/app/src/main/AndroidManifest.xml` - удалён `REQUEST_INSTALL_PACKAGES`
|
||||
- `android/app/src/main/kotlin/.../InstallHandler.kt` - удалён
|
||||
- `android/app/src/main/kotlin/.../MainActivity.kt` - удалена регистрация InstallHandler
|
||||
|
||||
**Результат:** Упрощённая Android версия без лишних разрешений
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Как собрать для всех платформ:
|
||||
|
||||
### ✅ Android (уже собрано)
|
||||
```bash
|
||||
flutter build apk --release
|
||||
# Файл: build/app/outputs/flutter-apk/app-release.apk
|
||||
```
|
||||
|
||||
### ✅ Linux (уже собрано)
|
||||
```bash
|
||||
flutter build linux --release
|
||||
# Файлы: build/linux/x64/release/bundle/
|
||||
```
|
||||
|
||||
### ⏳ Windows (.exe)
|
||||
**Требуется Windows машина или Wine:**
|
||||
```bash
|
||||
flutter build windows --release
|
||||
# Файлы: build/windows/x64/runner/Release/
|
||||
```
|
||||
|
||||
**Или используйте GitHub Actions / Azure Pipelines для автоматической сборки**
|
||||
|
||||
### ⏳ macOS (.dmg)
|
||||
**Требуется macOS:**
|
||||
```bash
|
||||
flutter build macos --release
|
||||
# Файлы: build/macos/Build/Products/Release/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📦 Текущее состояние:
|
||||
|
||||
| Платформа | Статус | Файл | Размер |
|
||||
|-----------|--------|------|--------|
|
||||
| Android | ✅ Собрано | `app-release.apk` | ~50 MB |
|
||||
| Linux | ✅ Собрано | `bundle/hiddify` | ~1.5 MB + libs |
|
||||
| Windows | ⏳ Требует Windows | `.exe` | - |
|
||||
| macOS | ⏳ Требует macOS | `.dmg` | - |
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Что работает на всех платформах:
|
||||
|
||||
### ✅ Белые кнопки
|
||||
- "Добавить из буфера обмена"
|
||||
- "Сканировать QR-код" (только мобильные)
|
||||
- "Добавить WARP"
|
||||
- "Ввести вручную"
|
||||
|
||||
### ✅ Система обновлений
|
||||
- Android: открывает браузер/Google Play
|
||||
- Desktop: скачивание с прогресс-баром
|
||||
|
||||
### ✅ Все остальные функции
|
||||
Работают одинаково на всех платформах!
|
||||
|
||||
---
|
||||
|
||||
## 📤 Деплой на update-server:
|
||||
|
||||
### Для Linux:
|
||||
```bash
|
||||
# Архивируем bundle
|
||||
cd build/linux/x64/release/
|
||||
tar czf umbrix-1.7.0-linux-x64.tar.gz bundle/
|
||||
|
||||
# Или создаём AppImage (требует дополнительные инструменты)
|
||||
```
|
||||
|
||||
### Для Windows:
|
||||
```bash
|
||||
# После сборки на Windows
|
||||
cd build/windows/x64/runner/Release/
|
||||
# Создаём установщик с помощью Inno Setup или NSIS
|
||||
```
|
||||
|
||||
### Загрузка на сервер:
|
||||
```bash
|
||||
# Скопируйте файлы
|
||||
cp umbrix-1.7.0-linux-x64.tar.gz /path/to/update-server/downloads/linux/
|
||||
cp umbrix-1.7.0-windows-x64.exe /path/to/update-server/downloads/windows/
|
||||
|
||||
# Обновите latest.json через admin панель
|
||||
# http://localhost:8000/admin/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Технические детали:
|
||||
|
||||
### Почему изменения применяются автоматически?
|
||||
|
||||
**Flutter использует единый код для всех платформ:**
|
||||
```
|
||||
lib/
|
||||
├── features/
|
||||
│ ├── app_update/ ← Единый код обновлений
|
||||
│ ├── profile/ ← Единые кнопки
|
||||
│ └── settings/ ← Единые настройки
|
||||
└── ...
|
||||
```
|
||||
|
||||
**Платформо-специфичный код:** только в папках `android/`, `linux/`, `windows/`, `macos/`
|
||||
|
||||
**Что мы изменили:**
|
||||
- ✅ 99% изменений - в `lib/` (Flutter код) → автоматически на всех платформах
|
||||
- ✅ 1% изменений - в `android/` → только для Android
|
||||
|
||||
---
|
||||
|
||||
## 🎨 Дизайн кнопок - как это работает:
|
||||
|
||||
```dart
|
||||
// БЫЛО (цветные иконки на белом фоне):
|
||||
Material(
|
||||
color: theme.colorScheme.surface, // Белый/серый фон
|
||||
child: Icon(
|
||||
icon,
|
||||
color: theme.colorScheme.primary, // Цветная иконка
|
||||
),
|
||||
)
|
||||
|
||||
// СТАЛО (белые иконки на цветном фоне):
|
||||
Material(
|
||||
color: theme.colorScheme.primary, // Цветной фон
|
||||
child: Icon(
|
||||
icon,
|
||||
color: Colors.white, // Белая иконка
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
**Это работает на:**
|
||||
- ✅ Android
|
||||
- ✅ iOS (если соберёте)
|
||||
- ✅ Windows
|
||||
- ✅ macOS
|
||||
- ✅ Linux
|
||||
- ✅ Web (если соберёте)
|
||||
|
||||
---
|
||||
|
||||
## 📋 Чеклист для полного деплоя:
|
||||
|
||||
### Разработка (✅ Готово)
|
||||
- [x] Изменения в коде
|
||||
- [x] Сборка Android APK
|
||||
- [x] Сборка Linux bundle
|
||||
- [x] Тестирование на эмуляторе
|
||||
|
||||
### Сборка (⏳ В процессе)
|
||||
- [x] Android release APK
|
||||
- [x] Linux release bundle
|
||||
- [ ] Windows .exe (требует Windows)
|
||||
- [ ] macOS .dmg (требует macOS)
|
||||
|
||||
### Упаковка (⏳ Следующий шаг)
|
||||
- [ ] Linux AppImage
|
||||
- [ ] Windows Installer (Inno Setup/NSIS)
|
||||
- [ ] macOS DMG
|
||||
- [ ] Подписание кодом (code signing)
|
||||
|
||||
### Деплой (⏳ После упаковки)
|
||||
- [ ] Загрузка на update-server
|
||||
- [ ] Обновление latest.json
|
||||
- [ ] Тестирование обновлений
|
||||
- [ ] Публикация в Google Play
|
||||
|
||||
---
|
||||
|
||||
## 💡 Рекомендации:
|
||||
|
||||
### Для локальной разработки:
|
||||
```bash
|
||||
# Используйте скрипт
|
||||
./BUILD_DESKTOP.sh
|
||||
```
|
||||
|
||||
### Для автоматической сборки:
|
||||
Настройте **GitHub Actions / GitLab CI / Azure Pipelines**:
|
||||
- Windows сборка на Windows runner
|
||||
- macOS сборка на macOS runner
|
||||
- Linux сборка на Linux runner
|
||||
|
||||
**Пример GitHub Actions:**
|
||||
```yaml
|
||||
name: Build Desktop
|
||||
on: push
|
||||
|
||||
jobs:
|
||||
build-linux:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: subosito/flutter-action@v2
|
||||
- run: flutter build linux --release
|
||||
|
||||
build-windows:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: subosito/flutter-action@v2
|
||||
- run: flutter build windows --release
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎉 Итого:
|
||||
|
||||
**Все изменения синхронизированы автоматически!**
|
||||
|
||||
Просто соберите для нужной платформы:
|
||||
- Android → `flutter build apk --release` ✅
|
||||
- Linux → `flutter build linux --release` ✅
|
||||
- Windows → `flutter build windows --release` (на Windows машине)
|
||||
- macOS → `flutter build macos --release` (на macOS машине)
|
||||
|
||||
**Никаких дополнительных правок не требуется!** 🚀
|
||||
138
DOCKER_QUICKSTART.md
Normal file
138
DOCKER_QUICKSTART.md
Normal file
@@ -0,0 +1,138 @@
|
||||
# 🚀 Запуск Update Server в Docker
|
||||
|
||||
## Быстрый старт
|
||||
|
||||
```bash
|
||||
# 1. Запустите контейнер
|
||||
docker-compose up -d
|
||||
|
||||
# 2. Проверьте статус
|
||||
docker-compose ps
|
||||
|
||||
# 3. Откройте веб-панель
|
||||
# http://localhost:8000/admin/
|
||||
```
|
||||
|
||||
## 📦 Загрузка APK файлов
|
||||
|
||||
### После сборки приложения:
|
||||
|
||||
```bash
|
||||
# 1. Соберите APK
|
||||
flutter build apk --release
|
||||
|
||||
# 2. Скопируйте в контейнер
|
||||
docker cp build/app/outputs/flutter-apk/app-release.apk \
|
||||
umbrix-update-server:/var/www/downloads/android/umbrix-1.7.1.apk
|
||||
|
||||
# 3. Проверьте что файл загружен
|
||||
docker exec umbrix-update-server ls -lh /var/www/downloads/android/
|
||||
|
||||
# 4. Обновите latest.json через веб-панель
|
||||
# http://localhost:8000/admin/
|
||||
```
|
||||
|
||||
## 🔧 Управление контейнером
|
||||
|
||||
```bash
|
||||
# Запустить
|
||||
docker-compose up -d
|
||||
|
||||
# Остановить
|
||||
docker-compose down
|
||||
|
||||
# Перезапустить
|
||||
docker-compose restart
|
||||
|
||||
# Посмотреть логи
|
||||
docker-compose logs -f
|
||||
|
||||
# Остановить с удалением volumes (осторожно!)
|
||||
docker-compose down -v
|
||||
```
|
||||
|
||||
## 📂 Структура volumes
|
||||
|
||||
```
|
||||
umbrix-downloads/ # APK файлы (persistent)
|
||||
├── android/
|
||||
├── windows/
|
||||
├── ios/
|
||||
├── linux/
|
||||
└── macos/
|
||||
|
||||
umbrix-logs/ # Логи сервера
|
||||
├── access.log
|
||||
├── admin.log
|
||||
└── restore.log
|
||||
```
|
||||
|
||||
## 🌐 URL для latest.json
|
||||
|
||||
После запуска в Docker, обновите константы:
|
||||
|
||||
```dart
|
||||
// lib/core/model/constants.dart
|
||||
static const customUpdateServerUrl = "https://api.umbrix.net/api.php";
|
||||
```
|
||||
|
||||
И в latest.json используйте:
|
||||
```json
|
||||
{
|
||||
"download_url": "https://api.umbrix.net/downloads/android/umbrix-1.7.1.apk"
|
||||
}
|
||||
```
|
||||
|
||||
## 🔒 Production настройки
|
||||
|
||||
Для продакшена добавьте Nginx:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
nginx:
|
||||
image: nginx:alpine
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
volumes:
|
||||
- ./nginx.conf:/etc/nginx/nginx.conf
|
||||
- ./ssl:/etc/nginx/ssl
|
||||
- umbrix-downloads:/var/www/downloads:ro
|
||||
depends_on:
|
||||
- update-server
|
||||
```
|
||||
|
||||
## 💾 Backup
|
||||
|
||||
```bash
|
||||
# Создать backup
|
||||
docker run --rm \
|
||||
-v umbrix-downloads:/data \
|
||||
-v $(pwd):/backup \
|
||||
alpine tar czf /backup/downloads-backup-$(date +%Y%m%d).tar.gz /data
|
||||
|
||||
# Восстановить backup
|
||||
docker run --rm \
|
||||
-v umbrix-downloads:/data \
|
||||
-v $(pwd):/backup \
|
||||
alpine tar xzf /backup/downloads-backup-YYYYMMDD.tar.gz -C /
|
||||
```
|
||||
|
||||
## 🐛 Отладка
|
||||
|
||||
```bash
|
||||
# Войти в контейнер
|
||||
docker exec -it umbrix-update-server sh
|
||||
|
||||
# Проверить PHP
|
||||
docker exec umbrix-update-server php -v
|
||||
|
||||
# Проверить файлы
|
||||
docker exec umbrix-update-server ls -la /var/www/downloads/android/
|
||||
|
||||
# Проверить latest.json
|
||||
docker exec umbrix-update-server cat /var/www/latest.json
|
||||
|
||||
# Проверить логи
|
||||
docker exec umbrix-update-server tail -f /var/www/logs/access.log
|
||||
```
|
||||
2
Makefile
2
Makefile
@@ -182,6 +182,8 @@ windows-libs:
|
||||
linux-libs:
|
||||
mkdir -p $(DESKTOP_OUT)
|
||||
curl -L $(CORE_URL)/$(CORE_NAME)-linux-amd64.tar.gz | tar xz -C $(DESKTOP_OUT)/
|
||||
# Копируем libcore для Flutter build
|
||||
cd $(DESKTOP_OUT)/lib && rm -f libcore.so && cp hiddify-core.so libcore.so
|
||||
|
||||
|
||||
macos-libs:
|
||||
|
||||
167
UPDATE_SERVER_GUIDE.md
Normal file
167
UPDATE_SERVER_GUIDE.md
Normal file
@@ -0,0 +1,167 @@
|
||||
# 🚀 Настройка Собственного Сервера Обновлений
|
||||
|
||||
## ❓ Зачем это нужно?
|
||||
|
||||
Если ваш GitHub репозиторий **приватный**, приложение не может проверять обновления через GitHub API. Решение - использовать собственный сервер для выкатки обновлений.
|
||||
|
||||
---
|
||||
|
||||
## 📂 Где документация?
|
||||
|
||||
Вся документация находится в папке **`update-server/`**
|
||||
|
||||
### Быстрый старт:
|
||||
👉 **[update-server/QUICK_START.md](update-server/QUICK_START.md)** - настройка за 5 минут
|
||||
|
||||
### Подробная инструкция:
|
||||
👉 **[update-server/README.md](update-server/README.md)** - полное руководство
|
||||
|
||||
### Тестирование:
|
||||
👉 **[update-server/TESTING.md](update-server/TESTING.md)** - как тестировать локально
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ Что нужно сделать?
|
||||
|
||||
### Шаг 1: Настроить сервер
|
||||
|
||||
```bash
|
||||
# Загрузите эти файлы на хостинг:
|
||||
update-server/api.php # главный скрипт
|
||||
update-server/latest.json # информация о версии
|
||||
update-server/.htaccess # настройки
|
||||
```
|
||||
|
||||
### Шаг 2: Изменить код приложения
|
||||
|
||||
Откройте `lib/core/model/constants.dart`:
|
||||
|
||||
```dart
|
||||
// Замените на адрес вашего сервера
|
||||
static const customUpdateServerUrl = "https://api.umbrix.net/api/latest";
|
||||
|
||||
// Включите собственный сервер
|
||||
static const useCustomUpdateServer = true;
|
||||
```
|
||||
|
||||
### Шаг 3: Пересобрать приложение
|
||||
|
||||
```bash
|
||||
flutter build apk --release
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Как протестировать локально?
|
||||
|
||||
```bash
|
||||
# Запустите тестовый сервер
|
||||
cd update-server
|
||||
./start_test_server.sh
|
||||
|
||||
# Сервер запустится на http://localhost:8000
|
||||
# Для эмулятора используйте: http://10.0.2.2:8000/api.php
|
||||
```
|
||||
|
||||
Затем:
|
||||
1. Откройте приложение
|
||||
2. Зайдите в **Настройки → О программе**
|
||||
3. Нажмите **"Проверить обновления"**
|
||||
|
||||
---
|
||||
|
||||
## 📦 Как выкатить новое обновление?
|
||||
|
||||
### 🎨 Вариант 1: Через веб-панель (проще!)
|
||||
|
||||
**Есть красивый веб-интерфейс для управления!**
|
||||
|
||||
1. **Откройте веб-панель:**
|
||||
```
|
||||
https://api.umbrix.net/admin/
|
||||
```
|
||||
|
||||
2. **Заполните форму** (все поля с подсказками)
|
||||
|
||||
3. **Нажмите "Сохранить обновление"**
|
||||
|
||||
4. **Готово!** 🎉
|
||||
|
||||
📖 Подробнее: [update-server/admin/README.md](update-server/admin/README.md)
|
||||
|
||||
---
|
||||
|
||||
### 📝 Вариант 2: Вручную (классический способ)
|
||||
|
||||
1. **Соберите APK:**
|
||||
```bash
|
||||
flutter build apk --release
|
||||
```
|
||||
|
||||
2. **Загрузите на сервер** в папку `downloads/`
|
||||
|
||||
3. **Обновите файл `latest.json`:**
|
||||
```json
|
||||
{
|
||||
"version": "2.5.8",
|
||||
"download_url": "https://api.umbrix.net/downloads/umbrix-2.5.8.apk"
|
||||
}
|
||||
```
|
||||
|
||||
4. **Готово!** Пользователи получат уведомление об обновлении
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Переключение между режимами
|
||||
|
||||
### Использовать GitHub (публичный репозиторий):
|
||||
```dart
|
||||
static const useCustomUpdateServer = false;
|
||||
```
|
||||
|
||||
### Использовать собственный сервер (приватный):
|
||||
```dart
|
||||
static const useCustomUpdateServer = true;
|
||||
```
|
||||
|
||||
**⚠️ После изменения обязательно пересоберите приложение!**
|
||||
|
||||
---
|
||||
|
||||
## 📖 Структура файлов
|
||||
|
||||
```
|
||||
update-server/
|
||||
├── INDEX.md ← Навигация по документации
|
||||
├── README.md ← Полная инструкция
|
||||
├── QUICK_START.md ← Быстрый старт
|
||||
├── TESTING.md ← Тестирование
|
||||
│
|
||||
├── api.php ← Серверный скрипт
|
||||
├── latest.json ← Информация о версии
|
||||
├── .htaccess ← Настройки Apache
|
||||
└── start_test_server.sh ← Скрипт для тестирования
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Готово!
|
||||
|
||||
Теперь вы можете:
|
||||
- ✅ Выкатывать обновления без магазинов приложений
|
||||
- ✅ Контролировать процесс релизов
|
||||
- ✅ Работать с приватным репозиторием
|
||||
- ✅ Тестировать бета-версии
|
||||
|
||||
---
|
||||
|
||||
## 📞 Нужна помощь?
|
||||
|
||||
Смотрите подробную документацию:
|
||||
- **[update-server/INDEX.md](update-server/INDEX.md)** - навигация
|
||||
- **[update-server/README.md](update-server/README.md)** - полное руководство
|
||||
- **[update-server/TESTING.md](update-server/TESTING.md)** - тестирование
|
||||
|
||||
---
|
||||
|
||||
**🚀 Удачи с обновлениями!**
|
||||
374
UPDATE_SERVER_SETUP.md
Normal file
374
UPDATE_SERVER_SETUP.md
Normal file
@@ -0,0 +1,374 @@
|
||||
# 🚀 Настройка собственного сервера обновлений для Umbrix
|
||||
|
||||
Это руководство поможет вам настроить систему обновлений для приватного распространения APK/IPA файлов до публикации в магазинах.
|
||||
|
||||
## 📋 Варианты реализации
|
||||
|
||||
### Вариант 1: Простой JSON файл на хостинге (самое простое)
|
||||
|
||||
Создайте файл `latest.json` на любом веб-сервере:
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "2.5.8",
|
||||
"build_number": "258",
|
||||
"is_prerelease": false,
|
||||
"download_url": "https://your-server.com/downloads/umbrix-2.5.8.apk",
|
||||
"release_notes": "Что нового:\n- Исправлены ошибки подключения\n- Улучшена стабильность\n- Новый дизайн главной страницы",
|
||||
"published_at": "2026-01-16T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Где разместить:**
|
||||
- ✅ Netlify / Vercel (бесплатно)
|
||||
- ✅ GitHub Pages (можно сделать приватный репозиторий)
|
||||
- ✅ Ваш VPS сервер
|
||||
- ✅ Firebase Hosting
|
||||
|
||||
**В constants.dart установите:**
|
||||
```dart
|
||||
static const customUpdateServerUrl = "https://your-site.netlify.app/latest.json";
|
||||
static const useCustomUpdateServer = true;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Вариант 2: Node.js API сервер (рекомендуемый)
|
||||
|
||||
#### Установка
|
||||
|
||||
```bash
|
||||
mkdir umbrix-update-server
|
||||
cd umbrix-update-server
|
||||
npm init -y
|
||||
npm install express cors
|
||||
```
|
||||
|
||||
#### server.js
|
||||
|
||||
```javascript
|
||||
const express = require('express');
|
||||
const cors = require('cors');
|
||||
const app = express();
|
||||
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
|
||||
// Конфигурация версий
|
||||
const releases = {
|
||||
stable: {
|
||||
version: "2.5.8",
|
||||
build_number: "258",
|
||||
is_prerelease: false,
|
||||
download_url: "https://your-storage.com/umbrix-2.5.8.apk",
|
||||
release_notes: "Стабильная версия с исправлениями",
|
||||
published_at: new Date().toISOString()
|
||||
},
|
||||
beta: {
|
||||
version: "2.6.0-beta.1",
|
||||
build_number: "260",
|
||||
is_prerelease: true,
|
||||
download_url: "https://your-storage.com/umbrix-2.6.0-beta.1.apk",
|
||||
release_notes: "Бета-версия с новыми функциями",
|
||||
published_at: new Date().toISOString()
|
||||
}
|
||||
};
|
||||
|
||||
// Endpoint для получения последней версии
|
||||
app.get('/api/updates/latest', (req, res) => {
|
||||
const includePrerelease = req.query.include_prerelease === 'true';
|
||||
const release = includePrerelease ? releases.beta : releases.stable;
|
||||
|
||||
console.log(`Update check: prerelease=${includePrerelease}`);
|
||||
res.json(release);
|
||||
});
|
||||
|
||||
// Аналитика (опционально)
|
||||
app.post('/api/updates/analytics', (req, res) => {
|
||||
const { current_version, device_info } = req.body;
|
||||
console.log('Update analytics:', { current_version, device_info });
|
||||
res.json({ success: true });
|
||||
});
|
||||
|
||||
const PORT = process.env.PORT || 3000;
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Update server running on port ${PORT}`);
|
||||
});
|
||||
```
|
||||
|
||||
#### Запуск
|
||||
|
||||
```bash
|
||||
node server.js
|
||||
```
|
||||
|
||||
#### Deploy на Render.com (бесплатно)
|
||||
|
||||
1. Создайте аккаунт на [Render.com](https://render.com)
|
||||
2. Подключите GitHub репозиторий
|
||||
3. Создайте Web Service
|
||||
4. Render автоматически задеплоит ваш сервер
|
||||
|
||||
**В constants.dart установите:**
|
||||
```dart
|
||||
static const customUpdateServerUrl = "https://your-app.onrender.com/api/updates/latest";
|
||||
static const useCustomUpdateServer = true;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Вариант 3: Firebase Cloud Functions (продвинутый)
|
||||
|
||||
#### functions/index.js
|
||||
|
||||
```javascript
|
||||
const functions = require('firebase-functions');
|
||||
const admin = require('firebase-admin');
|
||||
|
||||
admin.initializeApp();
|
||||
|
||||
exports.getLatestVersion = functions.https.onRequest(async (req, res) => {
|
||||
// CORS
|
||||
res.set('Access-Control-Allow-Origin', '*');
|
||||
|
||||
try {
|
||||
const db = admin.firestore();
|
||||
const doc = await db.collection('app_updates').doc('latest').get();
|
||||
|
||||
if (!doc.exists) {
|
||||
return res.status(404).json({ error: 'Version not found' });
|
||||
}
|
||||
|
||||
res.json(doc.data());
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
**Плюсы Firebase:**
|
||||
- ✅ Автоматическое масштабирование
|
||||
- ✅ Бесплатный SSL
|
||||
- ✅ Встроенная аналитика
|
||||
- ✅ База данных Firestore для хранения версий
|
||||
|
||||
---
|
||||
|
||||
## 📦 Где хранить APK файлы
|
||||
|
||||
### 1. Firebase Storage (рекомендуемый)
|
||||
|
||||
```bash
|
||||
# Установка Firebase CLI
|
||||
npm install -g firebase-tools
|
||||
firebase login
|
||||
|
||||
# Загрузка APK
|
||||
firebase storage:upload umbrix-2.5.8.apk /releases/umbrix-2.5.8.apk
|
||||
```
|
||||
|
||||
**Получение публичной ссылки:**
|
||||
```javascript
|
||||
// Генерация signed URL (действителен 7 дней)
|
||||
const { getStorage } = require('firebase-admin/storage');
|
||||
const bucket = getStorage().bucket();
|
||||
const file = bucket.file('releases/umbrix-2.5.8.apk');
|
||||
const [url] = await file.getSignedUrl({
|
||||
action: 'read',
|
||||
expires: Date.now() + 7 * 24 * 60 * 60 * 1000
|
||||
});
|
||||
```
|
||||
|
||||
### 2. AWS S3 / DigitalOcean Spaces
|
||||
|
||||
```bash
|
||||
# Пример с AWS CLI
|
||||
aws s3 cp umbrix-2.5.8.apk s3://your-bucket/releases/umbrix-2.5.8.apk --acl public-read
|
||||
```
|
||||
|
||||
### 3. Собственный сервер
|
||||
|
||||
```bash
|
||||
# Nginx конфигурация
|
||||
location /downloads/ {
|
||||
alias /var/www/downloads/;
|
||||
autoindex off;
|
||||
|
||||
# Защита паролем (опционально)
|
||||
auth_basic "Restricted";
|
||||
auth_basic_user_file /etc/nginx/.htpasswd;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔒 Безопасность
|
||||
|
||||
### 1. Базовая аутентификация (Basic Auth)
|
||||
|
||||
```dart
|
||||
// В DioHttpClient добавьте заголовки
|
||||
final response = await httpClient.get<Map<String, dynamic>>(
|
||||
url,
|
||||
options: Options(
|
||||
headers: {
|
||||
'Authorization': 'Basic ${base64Encode(utf8.encode('username:password'))}',
|
||||
},
|
||||
),
|
||||
);
|
||||
```
|
||||
|
||||
### 2. API ключ
|
||||
|
||||
```dart
|
||||
// В constants.dart
|
||||
static const updateServerApiKey = "your-secret-api-key";
|
||||
|
||||
// В запросе
|
||||
headers: {
|
||||
'X-API-Key': Constants.updateServerApiKey,
|
||||
}
|
||||
```
|
||||
|
||||
### 3. JWT токен (самый безопасный)
|
||||
|
||||
```javascript
|
||||
// На сервере
|
||||
const jwt = require('jsonwebtoken');
|
||||
|
||||
function verifyToken(req, res, next) {
|
||||
const token = req.headers['authorization']?.split(' ')[1];
|
||||
if (!token) return res.status(403).json({ error: 'No token' });
|
||||
|
||||
jwt.verify(token, process.env.JWT_SECRET, (err, decoded) => {
|
||||
if (err) return res.status(401).json({ error: 'Invalid token' });
|
||||
req.userId = decoded.userId;
|
||||
next();
|
||||
});
|
||||
}
|
||||
|
||||
app.get('/api/updates/latest', verifyToken, (req, res) => {
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Пошаговая инструкция (быстрый старт)
|
||||
|
||||
### Шаг 1: Создайте JSON файл
|
||||
|
||||
Создайте файл `latest.json`:
|
||||
```json
|
||||
{
|
||||
"version": "2.5.8",
|
||||
"build_number": "258",
|
||||
"is_prerelease": false,
|
||||
"download_url": "https://github.com/your-org/your-repo/releases/download/v2.5.8/umbrix-2.5.8.apk",
|
||||
"release_notes": "Первая версия",
|
||||
"published_at": "2026-01-16T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### Шаг 2: Разместите на GitHub Pages
|
||||
|
||||
```bash
|
||||
# В приватном репозитории создайте ветку gh-pages
|
||||
git checkout -b gh-pages
|
||||
git add latest.json
|
||||
git commit -m "Add update info"
|
||||
git push origin gh-pages
|
||||
|
||||
# В Settings → Pages включите GitHub Pages для ветки gh-pages
|
||||
```
|
||||
|
||||
### Шаг 3: Обновите константы в приложении
|
||||
|
||||
В файле `lib/core/model/constants.dart`:
|
||||
```dart
|
||||
static const customUpdateServerUrl = "https://your-username.github.io/your-repo/latest.json";
|
||||
static const useCustomUpdateServer = true;
|
||||
```
|
||||
|
||||
### Шаг 4: Соберите и протестируйте
|
||||
|
||||
```bash
|
||||
flutter build apk --release
|
||||
# Установите на устройство и проверьте обновления в разделе "О программе"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📱 Автоматизация через GitHub Actions
|
||||
|
||||
Создайте `.github/workflows/release.yml`:
|
||||
|
||||
```yaml
|
||||
name: Build and Deploy APK
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
|
||||
- name: Build APK
|
||||
run: flutter build apk --release
|
||||
|
||||
- name: Upload to storage
|
||||
run: |
|
||||
# Загрузка APK на ваш сервер
|
||||
curl -X POST -F "file=@build/app/outputs/flutter-apk/app-release.apk" \
|
||||
https://your-server.com/api/upload
|
||||
|
||||
- name: Update version info
|
||||
run: |
|
||||
# Обновление latest.json
|
||||
echo '{
|
||||
"version": "${{ github.ref_name }}",
|
||||
"build_number": "${{ github.run_number }}",
|
||||
"download_url": "https://your-server.com/downloads/${{ github.ref_name }}.apk",
|
||||
"published_at": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"
|
||||
}' > latest.json
|
||||
|
||||
# Загрузка на сервер
|
||||
curl -X PUT -d @latest.json https://your-server.com/api/latest
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ❓ FAQ
|
||||
|
||||
**Q: Как переключиться обратно на GitHub?**
|
||||
A: В `constants.dart` установите `useCustomUpdateServer = false`
|
||||
|
||||
**Q: Можно ли использовать оба варианта?**
|
||||
A: Да, можно добавить fallback логику в `app_update_repository.dart`
|
||||
|
||||
**Q: Как защитить от несанкционированного доступа?**
|
||||
A: Используйте API ключи, JWT токены или базовую аутентификацию
|
||||
|
||||
**Q: Нужен ли HTTPS?**
|
||||
A: Да, обязательно! Иначе Android не разрешит загрузку
|
||||
|
||||
---
|
||||
|
||||
## 🎉 Готово!
|
||||
|
||||
Теперь у вас есть полностью функциональная система обновлений для приватного распространения APK файлов.
|
||||
|
||||
**Следующие шаги:**
|
||||
1. ✅ Выберите вариант размещения
|
||||
2. ✅ Обновите `Constants.customUpdateServerUrl`
|
||||
3. ✅ Загрузите APK на сервер
|
||||
4. ✅ Протестируйте обновление
|
||||
5. ✅ Настройте автоматизацию через CI/CD
|
||||
@@ -26,7 +26,7 @@ class MethodHandler(private val scope: CoroutineScope) : FlutterPlugin,
|
||||
enum class Trigger(val method: String) {
|
||||
Setup("setup"),
|
||||
ParseConfig("parse_config"),
|
||||
changeHiddifyOptions("change_hiddify_options"),
|
||||
changeOptions("change_options"),
|
||||
GenerateConfig("generate_config"),
|
||||
Start("start"),
|
||||
Stop("stop"),
|
||||
@@ -86,7 +86,7 @@ class MethodHandler(private val scope: CoroutineScope) : FlutterPlugin,
|
||||
}
|
||||
}
|
||||
|
||||
Trigger.changeHiddifyOptions.method -> {
|
||||
Trigger.changeOptions.method -> {
|
||||
scope.launch {
|
||||
result.runCatching {
|
||||
val args = call.arguments as String
|
||||
|
||||
@@ -1,19 +1,29 @@
|
||||
package com.umbrix.app.widget
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import android.widget.RemoteViews
|
||||
import com.umbrix.app.R
|
||||
|
||||
class ConnectionWidget1x1 : ConnectionWidgetProvider() {
|
||||
companion object {
|
||||
private const val TAG = "A/ConnectionWidget1x1"
|
||||
}
|
||||
|
||||
override fun getLayout(): Int = R.layout.widget_connection_1x1
|
||||
|
||||
override fun updateWidgetUI(context: Context, views: RemoteViews, isConnected: Boolean) {
|
||||
if (isConnected) {
|
||||
views.setImageViewResource(R.id.widget_icon, R.drawable.ic_pause_circle_24)
|
||||
views.setInt(R.id.widget_background, "setBackgroundResource", R.drawable.widget_bg_active)
|
||||
} else {
|
||||
views.setImageViewResource(R.id.widget_icon, R.drawable.ic_power_24)
|
||||
views.setInt(R.id.widget_background, "setBackgroundResource", R.drawable.widget_bg_inactive)
|
||||
Log.d(TAG, "updateWidgetUI: isConnected=$isConnected")
|
||||
try {
|
||||
if (isConnected) {
|
||||
views.setImageViewResource(R.id.widget_icon, R.drawable.ic_pause_circle_24)
|
||||
views.setInt(R.id.widget_background, "setBackgroundResource", R.drawable.widget_bg_active)
|
||||
} else {
|
||||
views.setImageViewResource(R.id.widget_icon, R.drawable.ic_power_24)
|
||||
views.setInt(R.id.widget_background, "setBackgroundResource", R.drawable.widget_bg_inactive)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "updateWidgetUI error", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,26 +2,36 @@ package com.umbrix.app.widget
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.util.Log
|
||||
import android.widget.RemoteViews
|
||||
import com.umbrix.app.R
|
||||
|
||||
class ConnectionWidget2x2 : ConnectionWidgetProvider() {
|
||||
companion object {
|
||||
private const val TAG = "A/ConnectionWidget2x2"
|
||||
}
|
||||
|
||||
override fun getLayout(): Int = R.layout.widget_connection_2x2
|
||||
|
||||
override fun updateWidgetUI(context: Context, views: RemoteViews, isConnected: Boolean) {
|
||||
if (isConnected) {
|
||||
views.setImageViewResource(R.id.widget_icon, R.drawable.ic_pause_circle_24)
|
||||
views.setInt(R.id.widget_background, "setBackgroundResource", R.drawable.widget_bg_active)
|
||||
} else {
|
||||
views.setImageViewResource(R.id.widget_icon, R.drawable.ic_power_24)
|
||||
views.setInt(R.id.widget_background, "setBackgroundResource", R.drawable.widget_bg_inactive)
|
||||
Log.d(TAG, "updateWidgetUI: isConnected=$isConnected")
|
||||
try {
|
||||
if (isConnected) {
|
||||
views.setImageViewResource(R.id.widget_icon, R.drawable.ic_pause_circle_24)
|
||||
views.setInt(R.id.widget_background, "setBackgroundResource", R.drawable.widget_bg_active)
|
||||
} else {
|
||||
views.setImageViewResource(R.id.widget_icon, R.drawable.ic_power_24)
|
||||
views.setInt(R.id.widget_background, "setBackgroundResource", R.drawable.widget_bg_inactive)
|
||||
}
|
||||
|
||||
// Обновляем текст статуса
|
||||
val statusText = if (isConnected) "Connected" else "Tap to Connect"
|
||||
val statusColor = if (isConnected) Color.parseColor("#00BFA5") else Color.parseColor("#9E9E9E")
|
||||
|
||||
views.setTextViewText(R.id.widget_status, statusText)
|
||||
views.setTextColor(R.id.widget_status, statusColor)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "updateWidgetUI error", e)
|
||||
}
|
||||
|
||||
// Обновляем текст статуса
|
||||
val statusText = if (isConnected) "Connected" else "Tap to Connect"
|
||||
val statusColor = if (isConnected) Color.parseColor("#00BFA5") else Color.parseColor("#9E9E9E")
|
||||
|
||||
views.setTextViewText(R.id.widget_status, statusText)
|
||||
views.setTextColor(R.id.widget_status, statusColor)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.appwidget.AppWidgetProvider
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.util.Log
|
||||
import android.widget.RemoteViews
|
||||
import com.umbrix.app.R
|
||||
import com.umbrix.app.bg.BoxService
|
||||
@@ -13,9 +14,11 @@ import com.umbrix.app.bg.BoxService
|
||||
abstract class ConnectionWidgetProvider : AppWidgetProvider() {
|
||||
|
||||
companion object {
|
||||
private const val TAG = "A/ConnectionWidget"
|
||||
private const val ACTION_TOGGLE = "com.umbrix.app.widget.TOGGLE"
|
||||
|
||||
fun updateAllWidgets(context: Context, isConnected: Boolean) {
|
||||
Log.d(TAG, "updateAllWidgets: isConnected=$isConnected")
|
||||
// Обновляем все виджеты 1x1
|
||||
updateWidgets(context, ConnectionWidget1x1::class.java, isConnected)
|
||||
// Обновляем все виджеты 2x2
|
||||
@@ -23,13 +26,19 @@ abstract class ConnectionWidgetProvider : AppWidgetProvider() {
|
||||
}
|
||||
|
||||
private fun updateWidgets(context: Context, widgetClass: Class<out ConnectionWidgetProvider>, isConnected: Boolean) {
|
||||
val appWidgetManager = AppWidgetManager.getInstance(context)
|
||||
val componentName = ComponentName(context, widgetClass)
|
||||
val appWidgetIds = appWidgetManager.getAppWidgetIds(componentName)
|
||||
|
||||
appWidgetIds.forEach { appWidgetId ->
|
||||
val instance = widgetClass.getDeclaredConstructor().newInstance()
|
||||
instance.updateAppWidget(context, appWidgetManager, appWidgetId, isConnected)
|
||||
try {
|
||||
val appWidgetManager = AppWidgetManager.getInstance(context)
|
||||
val componentName = ComponentName(context, widgetClass)
|
||||
val appWidgetIds = appWidgetManager.getAppWidgetIds(componentName)
|
||||
|
||||
Log.d(TAG, "updateWidgets: ${widgetClass.simpleName}, count=${appWidgetIds.size}")
|
||||
|
||||
appWidgetIds.forEach { appWidgetId ->
|
||||
val instance = widgetClass.getDeclaredConstructor().newInstance()
|
||||
instance.updateAppWidget(context, appWidgetManager, appWidgetId, isConnected)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "updateWidgets error: ${widgetClass.simpleName}", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -75,6 +84,7 @@ abstract class ConnectionWidgetProvider : AppWidgetProvider() {
|
||||
appWidgetIds: IntArray
|
||||
) {
|
||||
val isConnected = com.umbrix.app.bg.BoxService.isConnected()
|
||||
Log.d(TAG, "onUpdate: widgetIds=${appWidgetIds.joinToString()}, isConnected=$isConnected")
|
||||
appWidgetIds.forEach { appWidgetId ->
|
||||
updateAppWidget(context, appWidgetManager, appWidgetId, isConnected)
|
||||
}
|
||||
@@ -83,16 +93,25 @@ abstract class ConnectionWidgetProvider : AppWidgetProvider() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
super.onReceive(context, intent)
|
||||
|
||||
Log.d(TAG, "onReceive: action=${intent.action}")
|
||||
|
||||
when (intent.action) {
|
||||
ACTION_TOGGLE -> {
|
||||
if (BoxService.isConnected()) {
|
||||
BoxService.stop()
|
||||
} else {
|
||||
BoxService.start()
|
||||
try {
|
||||
val wasConnected = BoxService.isConnected()
|
||||
Log.d(TAG, "TOGGLE: wasConnected=$wasConnected")
|
||||
if (wasConnected) {
|
||||
BoxService.stop()
|
||||
} else {
|
||||
BoxService.start()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "TOGGLE error", e)
|
||||
}
|
||||
}
|
||||
"com.umbrix.app.SERVICE_STATE_CHANGED" -> {
|
||||
val isConnected = intent.getBooleanExtra("isConnected", false)
|
||||
Log.d(TAG, "SERVICE_STATE_CHANGED: isConnected=$isConnected")
|
||||
updateAllWidgets(context, isConnected)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -334,7 +334,7 @@
|
||||
"play": {
|
||||
"title": "Umbrix (معاينة)",
|
||||
"short_description": "Auto, SSH, VLESS, VMess, Trojan, Reality, Sing-Box, Clash, XRay, Shadowsocks",
|
||||
"full_description": "الهدف الرئيسي لـ Umbrix هو توفير عميل نفق آمن وسهل الاستخدام وكفاءة. يمكّنك من توجيه جميع حركة المرور أو حركة المرور من التطبيق المحدد إلى خادم بعيد من اختيارك، باستخدام إذن VPN-Service. \n\nملاحظة: لا نوفر أي خادم، ويتعين على المستخدمين ضمان بقاء أنشطتهم عبر الإنترنت خاصة باستخدام خادمهم المخصص أو الخوادم الموثوقة. \n \nندعم الخوادم مع:\n- رابط اشتراك V2Ray/XRay عادي \n- رابط اشتراك Clash \n- رابط اشتراك Sing-Box \n\nما هي ميزاتنا الفريدة؟\n - سهل الاستخدام \n - مُحسّن وسريع \n - اختيار أدنى Ping تلقائيًا \n - عرض معلومات استخدام المستخدم \n - استيراد sublink بسهولة بنقرة واحدة باستخدام deeplinking \n - مجاني وخالي من الإعلانات \n - تبديل sublinks بسهولة \n - المزيد والمزيد \n\nالدعم:\n- جميع البروتوكولات التي تدعمها Sing-Box \n- VLESS + XTLS Reality, Vision \n- VMess \n- Trojan \n- ShoadowSocks \n- Reality \n- WireGuard \n- V2Ray \n- Hysteria2 \n- TUICv5 \n- SSH \n- ShadowTLS \n\n\nرمز المصدر موجود في https://github.com/hiddify/Hiddify-Next \nتعتمد نواة التطبيق على Sing-Box مفتوحة المصدر.\n\nوصف الإذن:\n- VPN Service: نظرًا لأن هدف هذا التطبيق هو توفير عميل نفق آمن وسهل الاستخدام وكفاءة، نحتاج إلى هذا الإذن لنتمكن من توجيه حركة المرور عبر النفق إلى الخادم البعيد. \n- QUERY ALL PACKAGES: يستخدم هذا الإذن للسماح للمستخدمين بتضمين أو استبعاد تطبيقات محددة للأنفاق. \n- RECEIVE BOOT COMPLETED: يمكن تمكين أو تعطيل هذا الإذن من إعدادات التطبيق لتنشيط هذا التطبيق عند تشغيل الجهاز. \n- POST NOTIFICATIONS: هذا الإذن ضروري لأننا نستخدم خدمة المقدمة لضمان تشغيل خدمة VPN بشكل مستمر. \n- هذا التطبيق خالي من الإعلانات. يتم جمع التحليلات وبيانات الأعطال فقط بموافقة صريحة من المستخدم في أول استخدام للتطبيق."
|
||||
"full_description": "الهدف الرئيسي لـ Umbrix هو توفير عميل نفق آمن وسهل الاستخدام وكفاءة. يمكّنك من توجيه جميع حركة المرور أو حركة المرور من التطبيق المحدد إلى خادم بعيد من اختيارك، باستخدام إذن VPN-Service. \n\nملاحظة: لا نوفر أي خادم، ويتعين على المستخدمين ضمان بقاء أنشطتهم عبر الإنترنت خاصة باستخدام خادمهم المخصص أو الخوادم الموثوقة. \n \nندعم الخوادم مع:\n- رابط اشتراك V2Ray/XRay عادي \n- رابط اشتراك Clash \n- رابط اشتراك Sing-Box \n\nما هي ميزاتنا الفريدة؟\n - سهل الاستخدام \n - مُحسّن وسريع \n - اختيار أدنى Ping تلقائيًا \n - عرض معلومات استخدام المستخدم \n - استيراد sublink بسهولة بنقرة واحدة باستخدام deeplinking \n - مجاني وخالي من الإعلانات \n - تبديل sublinks بسهولة \n - المزيد والمزيد \n\nالدعم:\n- جميع البروتوكولات التي تدعمها Sing-Box \n- VLESS + XTLS Reality, Vision \n- VMess \n- Trojan \n- ShoadowSocks \n- Reality \n- WireGuard \n- V2Ray \n- Hysteria2 \n- TUICv5 \n- SSH \n- ShadowTLS \n\n\nرمز المصدر موجود في Based on open-source project \nتعتمد نواة التطبيق على Sing-Box مفتوحة المصدر.\n\nوصف الإذن:\n- VPN Service: نظرًا لأن هدف هذا التطبيق هو توفير عميل نفق آمن وسهل الاستخدام وكفاءة، نحتاج إلى هذا الإذن لنتمكن من توجيه حركة المرور عبر النفق إلى الخادم البعيد. \n- QUERY ALL PACKAGES: يستخدم هذا الإذن للسماح للمستخدمين بتضمين أو استبعاد تطبيقات محددة للأنفاق. \n- RECEIVE BOOT COMPLETED: يمكن تمكين أو تعطيل هذا الإذن من إعدادات التطبيق لتنشيط هذا التطبيق عند تشغيل الجهاز. \n- POST NOTIFICATIONS: هذا الإذن ضروري لأننا نستخدم خدمة المقدمة لضمان تشغيل خدمة VPN بشكل مستمر. \n- هذا التطبيق خالي من الإعلانات. يتم جمع التحليلات وبيانات الأعطال فقط بموافقة صريحة من المستخدم في أول استخدام للتطبيق."
|
||||
},
|
||||
"connection": {
|
||||
"tapToConnect": "انقر للاتصال",
|
||||
|
||||
@@ -315,7 +315,7 @@
|
||||
"play": {
|
||||
"title": "هیدیفای (ئەزموونی)",
|
||||
"short_description": "Auto, SSH, VLESS, VMess, Trojan, Reality, Sing-Box, Clash, XRay, Shadowsocks",
|
||||
"full_description": "ئامانجی سەرەکی Hidify دابینکردنی کرێدەرێکی دژە فلتەری پارێزراو و بەکارهێنەر دۆستانە و کارامەیە. ئەمە ڕێگەت پێدەدات هەموو ترافیکی یان ترافیکی بەرنامە هەڵبژێردراوەکان ئاڕاستە بکەیتەوە بۆ سێرڤەرێکی دوور بە دڵی خۆت بە بەکارهێنانی مۆڵەتی خزمەتگوزاری VPN.\nتێبینی: ئێمە هیچ سێرڤەرێک دابین ناکەین. بەکارهێنەران پێویستە چالاکییە ئۆنلاینەکانیان بە تایبەتی بهێڵنەوە بە بەکارهێنانی سێرڤەر، هۆست یان سێرڤەرە متمانەپێکراوەکانی خۆیان.\n\nئێمە پشتگیری ئەم سێرڤەرانە دەکەین:- بەستەری ئابوونەی ئاسایی V2Ray/XRay- لینکی ئابوونەی کلاش- لینکی ئابوونەی Sing-Box\n\nتایبەتمەندییە ناوازەکانی ئەم بەرنامەیە چین؟- بەکارهێنەر دۆست- گونجاو و خێرا- هەڵبژاردنی نزمترین ping بە شێوەیەکی ئۆتۆماتیکی- پیشاندانی زانیاری بەکارهێنانی بەکارهێنەر- بە ئاسانی sublink بە یەک کلیک بە بەکارهێنانی deeplink دابنێ- بەخۆڕایی و بێ ڕیکلام- بە ئاسانی ژێربەستەرەکانی بەکارهێنەر بگۆڕێت- زیاتر و زیاتر\n\nپشتیوانی:- All Protocols Supported by Sing-Box \n- VLESS + XTLS Reality, Vision\n- VMess\n- Trojan\n- ShadowSocks\n- Reality\n- WireGuard\n- V2Ray\n- Hysteria2\n- TUICv5\n- SSH\n- ShadowTLS\nکۆدی سەرچاوە لە https://github.com/hiddify/Hiddify-Next بەردەستە و ناوەکی بەرنامەکە لەسەر بنەمای سەرچاوە کراوەی Sing-Box دامەزراوە.\n\nوەسفکردنی ڕێپێدان:- خزمەتگوزاری VPN: بەو پێیەی مەبەست لەم بەرنامەیە دابینکردنی کرێدەرێکی دژە فلتەری پارێزراو و بەکارهێنەر دۆستانە و کارامەیە، پێویستمان بەم مۆڵەتە هەیە بۆ ئەوەی ترافیک لە ڕێگەی تونێلەکەوە بۆ سێرڤەری دوور بگوازرێتەوە.- پرسیار لە هەموو پاکێجەکان بکە: ئەم مۆڵەتە بەکاردێت بۆ ئەوەی ڕێگە بە بەکارهێنەران بدرێت بەرنامە تایبەتەکان بۆ تونێلکردن بخەنە ناوەوە یان دەریانبهێنن.- RECEIVE BOOT COMPLETED: ئەم مۆڵەتە دەتوانرێت لە ڕێکخستنەکانی بەرنامەکەوە چالاک یان ناچالاک بکرێت بۆ ئەوەی ئەم بەرنامەیە دوای دەستپێکردنی ئامێرەکە چالاک بێت.- ئاگادارکردنەوەکانی دوای: ئەم مۆڵەتە پێویستە چونکە پێویستە خزمەتگوزاری پاشبنەما بەکاربهێنین بۆ دڵنیابوون لە کارکردنی دروستی VPN.- ئەم ئەپە بێ ڕیکلامە. شیکاری و داتاکانی کەوتنەخوارەوە تەنها بە ڕەزامەندی دەربڕینی بەکارهێنەر لە کاتی یەکەم بەکارهێنانی بەرنامەکەدا ڕوودەدات."
|
||||
"full_description": "ئامانجی سەرەکی Hidify دابینکردنی کرێدەرێکی دژە فلتەری پارێزراو و بەکارهێنەر دۆستانە و کارامەیە. ئەمە ڕێگەت پێدەدات هەموو ترافیکی یان ترافیکی بەرنامە هەڵبژێردراوەکان ئاڕاستە بکەیتەوە بۆ سێرڤەرێکی دوور بە دڵی خۆت بە بەکارهێنانی مۆڵەتی خزمەتگوزاری VPN.\nتێبینی: ئێمە هیچ سێرڤەرێک دابین ناکەین. بەکارهێنەران پێویستە چالاکییە ئۆنلاینەکانیان بە تایبەتی بهێڵنەوە بە بەکارهێنانی سێرڤەر، هۆست یان سێرڤەرە متمانەپێکراوەکانی خۆیان.\n\nئێمە پشتگیری ئەم سێرڤەرانە دەکەین:- بەستەری ئابوونەی ئاسایی V2Ray/XRay- لینکی ئابوونەی کلاش- لینکی ئابوونەی Sing-Box\n\nتایبەتمەندییە ناوازەکانی ئەم بەرنامەیە چین؟- بەکارهێنەر دۆست- گونجاو و خێرا- هەڵبژاردنی نزمترین ping بە شێوەیەکی ئۆتۆماتیکی- پیشاندانی زانیاری بەکارهێنانی بەکارهێنەر- بە ئاسانی sublink بە یەک کلیک بە بەکارهێنانی deeplink دابنێ- بەخۆڕایی و بێ ڕیکلام- بە ئاسانی ژێربەستەرەکانی بەکارهێنەر بگۆڕێت- زیاتر و زیاتر\n\nپشتیوانی:- All Protocols Supported by Sing-Box \n- VLESS + XTLS Reality, Vision\n- VMess\n- Trojan\n- ShadowSocks\n- Reality\n- WireGuard\n- V2Ray\n- Hysteria2\n- TUICv5\n- SSH\n- ShadowTLS\nکۆدی سەرچاوە لە Based on open-source project بەردەستە و ناوەکی بەرنامەکە لەسەر بنەمای سەرچاوە کراوەی Sing-Box دامەزراوە.\n\nوەسفکردنی ڕێپێدان:- خزمەتگوزاری VPN: بەو پێیەی مەبەست لەم بەرنامەیە دابینکردنی کرێدەرێکی دژە فلتەری پارێزراو و بەکارهێنەر دۆستانە و کارامەیە، پێویستمان بەم مۆڵەتە هەیە بۆ ئەوەی ترافیک لە ڕێگەی تونێلەکەوە بۆ سێرڤەری دوور بگوازرێتەوە.- پرسیار لە هەموو پاکێجەکان بکە: ئەم مۆڵەتە بەکاردێت بۆ ئەوەی ڕێگە بە بەکارهێنەران بدرێت بەرنامە تایبەتەکان بۆ تونێلکردن بخەنە ناوەوە یان دەریانبهێنن.- RECEIVE BOOT COMPLETED: ئەم مۆڵەتە دەتوانرێت لە ڕێکخستنەکانی بەرنامەکەوە چالاک یان ناچالاک بکرێت بۆ ئەوەی ئەم بەرنامەیە دوای دەستپێکردنی ئامێرەکە چالاک بێت.- ئاگادارکردنەوەکانی دوای: ئەم مۆڵەتە پێویستە چونکە پێویستە خزمەتگوزاری پاشبنەما بەکاربهێنین بۆ دڵنیابوون لە کارکردنی دروستی VPN.- ئەم ئەپە بێ ڕیکلامە. شیکاری و داتاکانی کەوتنەخوارەوە تەنها بە ڕەزامەندی دەربڕینی بەکارهێنەر لە کاتی یەکەم بەکارهێنانی بەرنامەکەدا ڕوودەدات."
|
||||
},
|
||||
"connection": {
|
||||
"tapToConnect": "بۆ پەیوەندیکردن بکوتە",
|
||||
|
||||
@@ -377,7 +377,7 @@
|
||||
"play": {
|
||||
"title": "Umbrix (Preview)",
|
||||
"short_description": "Auto, SSH, VLESS, VMess, Trojan, Reality, Sing-Box, Clash, XRay, Shadowsocks",
|
||||
"full_description": "The key goal of Umbrix is to provide a secure, user-friendly and efficient tunneling client. It enables you to route all traffic or selected app traffic to a remote server of your choose, utilizing VPN-Service permission.\n\nNote: We do not provide any server; users are required to ensure their online activities stay private by using use their own self-hosted server or trusted servers. \n \nWe Support Servers With:\n- Normal V2Ray/XRay Subscription Link\n- Clash Subscription Link\n- Sing-Box Subscription Link\n\nWhat is our unique features?\n - User Friendly\n - Optimized and Fast\n - Automatically select LowestPing \n - Show user usage information\n - Easily import sublink by one click using deeplinking \n - Free and No ADS\n - Easily switch user sublinks\n - More and more\n\nSupport:\n- All Protocols Supported by Sing-Box \n- VLESS + XTLS Reality, Vision\n- VMess\n- Trojan\n- ShadowSocks\n- Reality\n- WireGuard\n- V2Ray\n- Hysteria2\n- TUICv5\n- SSH\n- ShadowTLS\n\n\nThe source code exist in https://github.com/hiddify/Hiddify-Next\nThe application core is based on open-source Sing-Box.\n\nPermission Description:\n- VPN Service: As the goal of this application is to provide a secure, user-friendly and efficient tunneling client, we need this permission to be able to route the traffic via tunnel to the remote server. \n- QUERY ALL PACKAGES: This permission is used to allow users to include or exclude specific applications for tunneling.\n- RECEIVE BOOT COMPLETED: This permission can be enabled or disabled from app settings to activate this application upon device boot.\n- POST NOTIFICATIONS: This permission is essential as we employ a foreground service to ensure the continuous operation of the VPN service.\n- This application is free from advertisements. The analytics and crash data only occurs with the explicit consent of the user in the first use of application."
|
||||
"full_description": "The key goal of Umbrix is to provide a secure, user-friendly and efficient tunneling client. It enables you to route all traffic or selected app traffic to a remote server of your choose, utilizing VPN-Service permission.\n\nNote: We do not provide any server; users are required to ensure their online activities stay private by using use their own self-hosted server or trusted servers. \n \nWe Support Servers With:\n- Normal V2Ray/XRay Subscription Link\n- Clash Subscription Link\n- Sing-Box Subscription Link\n\nWhat is our unique features?\n - User Friendly\n - Optimized and Fast\n - Automatically select LowestPing \n - Show user usage information\n - Easily import sublink by one click using deeplinking \n - Free and No ADS\n - Easily switch user sublinks\n - More and more\n\nSupport:\n- All Protocols Supported by Sing-Box \n- VLESS + XTLS Reality, Vision\n- VMess\n- Trojan\n- ShadowSocks\n- Reality\n- WireGuard\n- V2Ray\n- Hysteria2\n- TUICv5\n- SSH\n- ShadowTLS\n\n\nThe source code exist in Based on open-source project\nThe application core is based on open-source Sing-Box.\n\nPermission Description:\n- VPN Service: As the goal of this application is to provide a secure, user-friendly and efficient tunneling client, we need this permission to be able to route the traffic via tunnel to the remote server. \n- QUERY ALL PACKAGES: This permission is used to allow users to include or exclude specific applications for tunneling.\n- RECEIVE BOOT COMPLETED: This permission can be enabled or disabled from app settings to activate this application upon device boot.\n- POST NOTIFICATIONS: This permission is essential as we employ a foreground service to ensure the continuous operation of the VPN service.\n- This application is free from advertisements. The analytics and crash data only occurs with the explicit consent of the user in the first use of application."
|
||||
},
|
||||
"connection": {
|
||||
"tapToConnect": "Tap To Connect",
|
||||
|
||||
@@ -334,7 +334,7 @@
|
||||
"play": {
|
||||
"title": "Umbrix (vista previa)",
|
||||
"short_description": "Auto, SSH, VLESS, VMess, Trojan, Reality, Sing-Box, Clash, XRay, Shadowsocks",
|
||||
"full_description": "El objetivo clave de Umbrix es proporcionar un cliente de túnel seguro, fácil de usar y eficiente. Le permite enrutar todo el tráfico o el tráfico de aplicaciones seleccionadas a un servidor remoto de su elección, utilizando el permiso del servicio VPN.Nota: No proporcionamos ningún servidor; Los usuarios deben garantizar que sus actividades en línea permanezcan privadas mediante el uso de su propio servidor autohospedado o servidores confiables. Soportamos servidores con:- Enlace de suscripción normal a V2ray/Xray- Enlace de suscripción a Choque- Enlace de suscripción a Sing-Box¿Cuáles son nuestras características únicas? - Fácil de usar - Optimizado y Rápido - Seleccionar automáticamente LowestPing - Mostrar información de uso del usuario. - Importe fácilmente un subvínculo con un solo clic mediante enlaces profundos - Gratis y sin anuncios - Cambie fácilmente los subvínculos de usuario - más y másApoyo:- Todos los protocolos soportados por Sing-Box- VLESS + xtls realidad, visión- VMESS- troyano- Calcetines Shoadow- Realidad-V2ray-Histria2-TUIC-SSH- SombraTLSEl código fuente existe en https://github.com/hiddify/Hiddify-NextEl núcleo de la aplicación se basa en sing-box de código abierto.Descripción del permiso:- Servicio VPN: como el objetivo de esta aplicación es proporcionar un cliente de túnel seguro, fácil de usar y eficiente, necesitamos este permiso para poder enrutar el tráfico a través del túnel al servidor remoto.- CONSULTAR TODOS LOS PAQUETES: este permiso se utiliza para permitir a los usuarios incluir o excluir aplicaciones específicas para la tunelización.- RECIBIR ARRANQUE COMPLETADO: este permiso se puede habilitar o deshabilitar desde la configuración de la aplicación para activar esta aplicación al iniciar el dispositivo.- PUBLICAR NOTIFICACIONES: este permiso es esencial ya que empleamos un servicio en primer plano para garantizar el funcionamiento continuo del servicio VPN.- Esta aplicación está libre de publicidad. Los datos analíticos y de fallos solo se producen con el consentimiento explícito del usuario en el primer uso de la aplicación."
|
||||
"full_description": "El objetivo clave de Umbrix es proporcionar un cliente de túnel seguro, fácil de usar y eficiente. Le permite enrutar todo el tráfico o el tráfico de aplicaciones seleccionadas a un servidor remoto de su elección, utilizando el permiso del servicio VPN.Nota: No proporcionamos ningún servidor; Los usuarios deben garantizar que sus actividades en línea permanezcan privadas mediante el uso de su propio servidor autohospedado o servidores confiables. Soportamos servidores con:- Enlace de suscripción normal a V2ray/Xray- Enlace de suscripción a Choque- Enlace de suscripción a Sing-Box¿Cuáles son nuestras características únicas? - Fácil de usar - Optimizado y Rápido - Seleccionar automáticamente LowestPing - Mostrar información de uso del usuario. - Importe fácilmente un subvínculo con un solo clic mediante enlaces profundos - Gratis y sin anuncios - Cambie fácilmente los subvínculos de usuario - más y másApoyo:- Todos los protocolos soportados por Sing-Box- VLESS + xtls realidad, visión- VMESS- troyano- Calcetines Shoadow- Realidad-V2ray-Histria2-TUIC-SSH- SombraTLSEl código fuente existe en Based on open-source projectEl núcleo de la aplicación se basa en sing-box de código abierto.Descripción del permiso:- Servicio VPN: como el objetivo de esta aplicación es proporcionar un cliente de túnel seguro, fácil de usar y eficiente, necesitamos este permiso para poder enrutar el tráfico a través del túnel al servidor remoto.- CONSULTAR TODOS LOS PAQUETES: este permiso se utiliza para permitir a los usuarios incluir o excluir aplicaciones específicas para la tunelización.- RECIBIR ARRANQUE COMPLETADO: este permiso se puede habilitar o deshabilitar desde la configuración de la aplicación para activar esta aplicación al iniciar el dispositivo.- PUBLICAR NOTIFICACIONES: este permiso es esencial ya que empleamos un servicio en primer plano para garantizar el funcionamiento continuo del servicio VPN.- Esta aplicación está libre de publicidad. Los datos analíticos y de fallos solo se producen con el consentimiento explícito del usuario en el primer uso de la aplicación."
|
||||
},
|
||||
"connection": {
|
||||
"tapToConnect": "Toque para conectarse",
|
||||
|
||||
@@ -334,7 +334,7 @@
|
||||
"play": {
|
||||
"title": "هیدیفای (آزمایشی)",
|
||||
"short_description": "Auto, SSH, VLESS, VMess, Trojan, Reality, Sing-Box, Clash, XRay, Shadowsocks",
|
||||
"full_description": "هدف اصلی هیدیفای ارائه یک کلاینت ضدفیلتر ایمن، کاربرپسند و کارآمد است. این به شما امکان میدهد تا با استفاده از مجوز سرویس VPN، تمام ترافیک یا ترافیک برنامهی انتخابی را به یک سرور راه دور مورد نظر خود هدایت کنید.\n\nتوجه: ما هیچ سروری ارائه نمیدهیم. کاربران موظف هستند با استفاده از سرورهای خود، هاست یا سرورهای مورد اعتماد، فعالیتهای آنلاین خود را خصوصی نگه دارند. \n\nما از این سرورها پشتیبانی میکنیم:\n- لینک اشتراک V2Ray/XRay معمولی\n- لینک اشتراک کلش\n- لینک اشتراک Sing-Box\n\nویژگیهای منحصر به فرد این برنامه چیست؟\n- کاربر پسند \n- بهینه و سریع \n- انتخاب کمترین پینگ به صورت خودکار\n- نمایش اطلاعات استفاده کاربر\n- به راحتی لینک فرعی را با یک کلیک با استفاده از دیپ لینک وارد کنید \n- رایگان و بدون تبلیغات \n- بهراحتی لینک های فرعی کاربر را تغییر دهید \n- بیشتر و بیشتر\n\nپشتیبانی از:\n- همهی پروتکلهای پشتیبانیشده توسط Sing-Box- VLESS + XTLS Reality، Vision- VMess- Trojan- ShadowSocks- Reality- WireGuard- V2Ray- Hysteria2- TUICv5- SSH- ShadowTLS\n\nکد منبع در https://github.com/hiddify/Hiddify-Next موجود بوده و هستهی برنامه مبتنی بر منبع باز Sing-Box است.\n\nتوضیحات مجوز:\n- VPN Service: از آنجا که هدف این برنامه ارائهی یک کلاینت ضدفیلتر ایمن، کاربر پسند و کارآمد است، ما به این مجوز نیاز داریم تا بتوانیم ترافیک را از طریق تونل به سرور راه دور هدایت کنیم.\n- QUERY ALL PACKAGES: این مجوز برای اجازه دادن به کاربران برای گنجاندن یا حذف برنامههای کاربردی خاص برای تونلزدن استفاده میشود.\n- RECEIVE BOOT COMPLETED: این مجوز را میتوان از تنظیمات برنامه فعال یا غیرفعال کرد تا این برنامه پس از شروع به کار دستگاه فعال شود.\n- POST NOTIFICATIONS: این مجوز ضروری است زیرا برای اطمینان از عملکرد یکسره VPN نیاز است از یک سرویس پس زمینه استفاده کنیم. \n- این برنامه بدون تبلیغات است. تجزیه و تحلیل و دادههای خرابی فقط با رضایت صریح کاربر در اولین استفاده از برنامه اتفاق میافتد."
|
||||
"full_description": "هدف اصلی هیدیفای ارائه یک کلاینت ضدفیلتر ایمن، کاربرپسند و کارآمد است. این به شما امکان میدهد تا با استفاده از مجوز سرویس VPN، تمام ترافیک یا ترافیک برنامهی انتخابی را به یک سرور راه دور مورد نظر خود هدایت کنید.\n\nتوجه: ما هیچ سروری ارائه نمیدهیم. کاربران موظف هستند با استفاده از سرورهای خود، هاست یا سرورهای مورد اعتماد، فعالیتهای آنلاین خود را خصوصی نگه دارند. \n\nما از این سرورها پشتیبانی میکنیم:\n- لینک اشتراک V2Ray/XRay معمولی\n- لینک اشتراک کلش\n- لینک اشتراک Sing-Box\n\nویژگیهای منحصر به فرد این برنامه چیست؟\n- کاربر پسند \n- بهینه و سریع \n- انتخاب کمترین پینگ به صورت خودکار\n- نمایش اطلاعات استفاده کاربر\n- به راحتی لینک فرعی را با یک کلیک با استفاده از دیپ لینک وارد کنید \n- رایگان و بدون تبلیغات \n- بهراحتی لینک های فرعی کاربر را تغییر دهید \n- بیشتر و بیشتر\n\nپشتیبانی از:\n- همهی پروتکلهای پشتیبانیشده توسط Sing-Box- VLESS + XTLS Reality، Vision- VMess- Trojan- ShadowSocks- Reality- WireGuard- V2Ray- Hysteria2- TUICv5- SSH- ShadowTLS\n\nکد منبع در Based on open-source project موجود بوده و هستهی برنامه مبتنی بر منبع باز Sing-Box است.\n\nتوضیحات مجوز:\n- VPN Service: از آنجا که هدف این برنامه ارائهی یک کلاینت ضدفیلتر ایمن، کاربر پسند و کارآمد است، ما به این مجوز نیاز داریم تا بتوانیم ترافیک را از طریق تونل به سرور راه دور هدایت کنیم.\n- QUERY ALL PACKAGES: این مجوز برای اجازه دادن به کاربران برای گنجاندن یا حذف برنامههای کاربردی خاص برای تونلزدن استفاده میشود.\n- RECEIVE BOOT COMPLETED: این مجوز را میتوان از تنظیمات برنامه فعال یا غیرفعال کرد تا این برنامه پس از شروع به کار دستگاه فعال شود.\n- POST NOTIFICATIONS: این مجوز ضروری است زیرا برای اطمینان از عملکرد یکسره VPN نیاز است از یک سرویس پس زمینه استفاده کنیم. \n- این برنامه بدون تبلیغات است. تجزیه و تحلیل و دادههای خرابی فقط با رضایت صریح کاربر در اولین استفاده از برنامه اتفاق میافتد."
|
||||
},
|
||||
"connection": {
|
||||
"tapToConnect": "برای اتصال ضربه بزنید",
|
||||
|
||||
@@ -334,7 +334,7 @@
|
||||
"play": {
|
||||
"title": "Umbrix (aperçu)",
|
||||
"short_description": "Auto, SSH, VLESS, VMess, cheval de Troie, Reality, Sing-Box, Clash, XRay, Shadowsocks",
|
||||
"full_description": "L'objectif principal de Umbrix est de fournir un client de tunneling sécurisé, convivial et efficace. Il vous permet d'acheminer tout le trafic ou le trafic d'applications sélectionnées vers un serveur distant de votre choix, en utilisant l'autorisation du service VPN.<inlang-LineFeed>\nRemarque : Nous ne fournissons aucun serveur ; les utilisateurs sont tenus de garantir que leurs activités en ligne restent privées en utilisant leur propre serveur auto-hébergé ou des serveurs de confiance.<inlang-LineFeed>\nNous prenons en charge les serveurs avec :\n- Lien d'abonnement normal V2Ray/XRay\n- Lien d'abonnement Clash\n- Lien d'abonnement à Sing-Box<inlang-LineFeed>\nQuelles sont nos caractéristiques uniques ?\n- Convivial\n- Optimisé et rapide\n- Sélectionnez automatiquement le plus bas Ping\n- Afficher les informations d'utilisation de l'utilisateur\n- Importez facilement des sous-liens en un seul clic grâce au deeplinking\n- Gratuit et sans publicité\n- Changez facilement de sous-liens utilisateur\n- De plus en plus<inlang-LineFeed>\nSoutien:\n- Tous les protocoles pris en charge par Sing-Box\n- VLESS + XTLS Réalité, Vision\n-VMess\n- Cheval de Troie\n- Chaussettes Shadow\n- Réalité\n- WireGuard\n-V2Ray\n- Hystérie2\n-TUICv5\n-SSH\n-OmbreTLS<inlang-LineFeed><inlang-LineFeed>\nLe code source existe sur https://github.com/hiddify/Hiddify-Next\nLe cœur de l'application est basé sur Sing-Box open source.<inlang-LineFeed>\nDescription de l'autorisation :\n- Service VPN : L'objectif de cette application étant de fournir un client de tunneling sécurisé, convivial et efficace, nous avons besoin de cette autorisation pour pouvoir acheminer le trafic via un tunnel vers le serveur distant.\n- REQUÊTER TOUS LES PAQUETS : cette autorisation est utilisée pour permettre aux utilisateurs d'inclure ou d'exclure des applications spécifiques pour le tunneling.\n- RECEVOIR LE BOOT TERMINÉ : Cette autorisation peut être activée ou désactivée à partir des paramètres de l'application pour activer cette application au démarrage de l'appareil.\n- POST NOTIFICATIONS : Cette autorisation est essentielle car nous utilisons un service de premier plan pour assurer le fonctionnement continu du service VPN.\n- Cette application est exempte de publicités. Les données d'analyse et de crash n'ont lieu qu'avec le consentement explicite de l'utilisateur lors de la première utilisation de l'application."
|
||||
"full_description": "L'objectif principal de Umbrix est de fournir un client de tunneling sécurisé, convivial et efficace. Il vous permet d'acheminer tout le trafic ou le trafic d'applications sélectionnées vers un serveur distant de votre choix, en utilisant l'autorisation du service VPN.<inlang-LineFeed>\nRemarque : Nous ne fournissons aucun serveur ; les utilisateurs sont tenus de garantir que leurs activités en ligne restent privées en utilisant leur propre serveur auto-hébergé ou des serveurs de confiance.<inlang-LineFeed>\nNous prenons en charge les serveurs avec :\n- Lien d'abonnement normal V2Ray/XRay\n- Lien d'abonnement Clash\n- Lien d'abonnement à Sing-Box<inlang-LineFeed>\nQuelles sont nos caractéristiques uniques ?\n- Convivial\n- Optimisé et rapide\n- Sélectionnez automatiquement le plus bas Ping\n- Afficher les informations d'utilisation de l'utilisateur\n- Importez facilement des sous-liens en un seul clic grâce au deeplinking\n- Gratuit et sans publicité\n- Changez facilement de sous-liens utilisateur\n- De plus en plus<inlang-LineFeed>\nSoutien:\n- Tous les protocoles pris en charge par Sing-Box\n- VLESS + XTLS Réalité, Vision\n-VMess\n- Cheval de Troie\n- Chaussettes Shadow\n- Réalité\n- WireGuard\n-V2Ray\n- Hystérie2\n-TUICv5\n-SSH\n-OmbreTLS<inlang-LineFeed><inlang-LineFeed>\nLe code source existe sur Based on open-source project\nLe cœur de l'application est basé sur Sing-Box open source.<inlang-LineFeed>\nDescription de l'autorisation :\n- Service VPN : L'objectif de cette application étant de fournir un client de tunneling sécurisé, convivial et efficace, nous avons besoin de cette autorisation pour pouvoir acheminer le trafic via un tunnel vers le serveur distant.\n- REQUÊTER TOUS LES PAQUETS : cette autorisation est utilisée pour permettre aux utilisateurs d'inclure ou d'exclure des applications spécifiques pour le tunneling.\n- RECEVOIR LE BOOT TERMINÉ : Cette autorisation peut être activée ou désactivée à partir des paramètres de l'application pour activer cette application au démarrage de l'appareil.\n- POST NOTIFICATIONS : Cette autorisation est essentielle car nous utilisons un service de premier plan pour assurer le fonctionnement continu du service VPN.\n- Cette application est exempte de publicités. Les données d'analyse et de crash n'ont lieu qu'avec le consentement explicite de l'utilisateur lors de la première utilisation de l'application."
|
||||
},
|
||||
"connection": {
|
||||
"tapToConnect": "Appuyez pour vous connecter",
|
||||
|
||||
@@ -334,7 +334,7 @@
|
||||
"play": {
|
||||
"title": "Umbrix (Pré-visualização)",
|
||||
"short_description": "Auto, SSH, VLESS, VMess, Trojan, Reality, Sing-Box, Clash, XRay, Shadowsocks",
|
||||
"full_description": "O principal objetivo do Umbrix é fornecer um cliente de tunelamento seguro, fácil de usar e eficiente. Ele permite que você direcione todo o tráfego ou tráfego de aplicativo selecionado para um servidor remoto de sua escolha, utilizando a permissão do serviço VPN.<inlang-LineFeed>\nNota: Não fornecemos nenhum servidor; os usuários são obrigados a garantir que suas atividades online permaneçam privadas usando seu próprio servidor auto-hospedado ou servidores confiáveis.<inlang-LineFeed>\nOferecemos suporte a servidores com:\n- Link de assinatura V2Ray/XRay normal\n- Link de assinatura do Clash\n- Link de assinatura do Sing-Box<inlang-LineFeed>\nQuais são os nossos recursos exclusivos?\n- Amigo do usuário\n- Otimizado e rápido\n- Selecione automaticamente o LowerPing\n- Mostrar informações de uso do usuário\n- Importe facilmente sublinks com um clique usando deeplinking\n- Gratuito e sem anúncios\n- Alterne facilmente sublinks de usuários\n- Mais e mais<inlang-LineFeed>\nApoiar:\n- Todos os protocolos suportados pelo Sing-Box\n- VLESS + XTLS Realidade, Visão\n- VMess\n- Trojan\n- ShadowSocks\n- Realidade\n- WireGuard\n-V2Ray\n- Histeria2\n-TUICv5\n-SSH\n- ShadowTLS<inlang-LineFeed><inlang-LineFeed>\nO código-fonte existe em https://github.com/hiddify/Hiddify-Next\nO núcleo do aplicativo é baseado no Sing-Box de código aberto.<inlang-LineFeed>\nDescrição da permissão:\n- Serviço VPN: Como o objetivo desta aplicação é fornecer um cliente de tunelamento seguro, fácil de usar e eficiente, precisamos dessa permissão para poder rotear o tráfego via túnel para o servidor remoto.\n- CONSULTAR TODOS OS PACOTES: Esta permissão é usada para permitir que os usuários incluam ou excluam aplicativos específicos para tunelamento.\n- RECEBER BOOT COMPLETED: Esta permissão pode ser habilitada ou desabilitada nas configurações do aplicativo para ativar este aplicativo na inicialização do dispositivo.\n- PÓS NOTIFICAÇÕES: Esta permissão é essencial, pois empregamos um serviço de primeiro plano para garantir a operação contínua do serviço VPN.\n- Este aplicativo está livre de anúncios. A análise e os dados de travamento só ocorrem com o consentimento explícito do usuário na primeira utilização do aplicativo."
|
||||
"full_description": "O principal objetivo do Umbrix é fornecer um cliente de tunelamento seguro, fácil de usar e eficiente. Ele permite que você direcione todo o tráfego ou tráfego de aplicativo selecionado para um servidor remoto de sua escolha, utilizando a permissão do serviço VPN.<inlang-LineFeed>\nNota: Não fornecemos nenhum servidor; os usuários são obrigados a garantir que suas atividades online permaneçam privadas usando seu próprio servidor auto-hospedado ou servidores confiáveis.<inlang-LineFeed>\nOferecemos suporte a servidores com:\n- Link de assinatura V2Ray/XRay normal\n- Link de assinatura do Clash\n- Link de assinatura do Sing-Box<inlang-LineFeed>\nQuais são os nossos recursos exclusivos?\n- Amigo do usuário\n- Otimizado e rápido\n- Selecione automaticamente o LowerPing\n- Mostrar informações de uso do usuário\n- Importe facilmente sublinks com um clique usando deeplinking\n- Gratuito e sem anúncios\n- Alterne facilmente sublinks de usuários\n- Mais e mais<inlang-LineFeed>\nApoiar:\n- Todos os protocolos suportados pelo Sing-Box\n- VLESS + XTLS Realidade, Visão\n- VMess\n- Trojan\n- ShadowSocks\n- Realidade\n- WireGuard\n-V2Ray\n- Histeria2\n-TUICv5\n-SSH\n- ShadowTLS<inlang-LineFeed><inlang-LineFeed>\nO código-fonte existe em Based on open-source project\nO núcleo do aplicativo é baseado no Sing-Box de código aberto.<inlang-LineFeed>\nDescrição da permissão:\n- Serviço VPN: Como o objetivo desta aplicação é fornecer um cliente de tunelamento seguro, fácil de usar e eficiente, precisamos dessa permissão para poder rotear o tráfego via túnel para o servidor remoto.\n- CONSULTAR TODOS OS PACOTES: Esta permissão é usada para permitir que os usuários incluam ou excluam aplicativos específicos para tunelamento.\n- RECEBER BOOT COMPLETED: Esta permissão pode ser habilitada ou desabilitada nas configurações do aplicativo para ativar este aplicativo na inicialização do dispositivo.\n- PÓS NOTIFICAÇÕES: Esta permissão é essencial, pois empregamos um serviço de primeiro plano para garantir a operação contínua do serviço VPN.\n- Este aplicativo está livre de anúncios. A análise e os dados de travamento só ocorrem com o consentimento explícito do usuário na primeira utilização do aplicativo."
|
||||
},
|
||||
"connection": {
|
||||
"tapToConnect": "Toque para conectar",
|
||||
|
||||
@@ -377,7 +377,7 @@
|
||||
"play": {
|
||||
"title": "Umbrix (Предварительная версия)",
|
||||
"short_description": "Автовыбор, SSH, VLESS, VMess, Trojan, Reality, Sing-Box, Clash, Xray, Shadowsocks",
|
||||
"full_description": "Основная цель Umbrix — предоставить безопасный, удобный и эффективный клиент туннелирования. Он позволяет направлять весь трафик или трафик выбранного приложения на указанный Вами удалённый сервер.\n\nПримечание: мы не предоставляем серверы, пользователи должны сами обеспечивать конфиденциальность своих действий в Интернете, используя собственный сервер или доверенные серверы. \nПоддерживаются сервера с:\n- Обычной ссылкой на подписку V2ray/Xray\n- Ссылкой на подписку Clash\n- Ссылкой на подписку на Sing–Box\n\nВ чём уникальные особенности? \n- Удобство\n- Оптимизация и скорость\n- Автоматический выбор минимальной задержки\n- Отображение информации об использовании\n- Простой импорт подписок одним щелчком мыши\n- Бесплатно и без рекламы\n- Простое переключение подписок\n- И многое другое...\n\nПоддерживаются:\n- Все протоколы, поддерживаемые Sing-Box\n- VLESS + XTLS Reality, Vision\n- VMESS\n- Trojan\n- ShoadowSocks\n- Reality\n- V2ray\n- Hystria2\n- TUIC\n- SSH\n- ShadowTLS\n\nИсходный код доступен по адресу https://github.com/hiddify/Hiddify-Next\nЯдро приложения основано на открытом исходном коде Sing–Box.\n\nОписание разрешений:\n- СЛУЖБА VPN: поскольку целью данного приложения является предоставление безопасного, удобного и эффективного клиента туннелирования, это разрешение необходимо, чтобы иметь возможность направлять трафик через туннель на удалённый сервер.\n- ЗАПРОС ВСЕХ ПАКЕТОВ: это разрешение позволяет добавлять или удалять определённые приложения из списка для туннелирования.\n- ИНФОРМИРОВАНИЕ О ЗАВЕРШЕНИИ ЗАГРУЗКИ: это разрешение можно включить или отключить в настройках приложения, чтобы (де)активировать запуск приложения при загрузке устройства.\n- ПОСТОЯННОЕ УВЕДОМЛЕНИЕ: это разрешение необходимо, так как используется приоритетная служба для обеспечения непрерывной работы VPN.\n- Приложение не содержит рекламы. Сбор аналитики и данных о сбоях происходят только с явного согласия пользователя при первом использовании приложения."
|
||||
"full_description": "Основная цель Umbrix — предоставить безопасный, удобный и эффективный клиент туннелирования. Он позволяет направлять весь трафик или трафик выбранного приложения на указанный Вами удалённый сервер.\n\nПримечание: мы не предоставляем серверы, пользователи должны сами обеспечивать конфиденциальность своих действий в Интернете, используя собственный сервер или доверенные серверы. \nПоддерживаются сервера с:\n- Обычной ссылкой на подписку V2ray/Xray\n- Ссылкой на подписку Clash\n- Ссылкой на подписку на Sing–Box\n\nВ чём уникальные особенности? \n- Удобство\n- Оптимизация и скорость\n- Автоматический выбор минимальной задержки\n- Отображение информации об использовании\n- Простой импорт подписок одним щелчком мыши\n- Бесплатно и без рекламы\n- Простое переключение подписок\n- И многое другое...\n\nПоддерживаются:\n- Все протоколы, поддерживаемые Sing-Box\n- VLESS + XTLS Reality, Vision\n- VMESS\n- Trojan\n- ShoadowSocks\n- Reality\n- V2ray\n- Hystria2\n- TUIC\n- SSH\n- ShadowTLS\n\nИсходный код доступен по адресу Based on open-source project\nЯдро приложения основано на открытом исходном коде Sing–Box.\n\nОписание разрешений:\n- СЛУЖБА VPN: поскольку целью данного приложения является предоставление безопасного, удобного и эффективного клиента туннелирования, это разрешение необходимо, чтобы иметь возможность направлять трафик через туннель на удалённый сервер.\n- ЗАПРОС ВСЕХ ПАКЕТОВ: это разрешение позволяет добавлять или удалять определённые приложения из списка для туннелирования.\n- ИНФОРМИРОВАНИЕ О ЗАВЕРШЕНИИ ЗАГРУЗКИ: это разрешение можно включить или отключить в настройках приложения, чтобы (де)активировать запуск приложения при загрузке устройства.\n- ПОСТОЯННОЕ УВЕДОМЛЕНИЕ: это разрешение необходимо, так как используется приоритетная служба для обеспечения непрерывной работы VPN.\n- Приложение не содержит рекламы. Сбор аналитики и данных о сбоях происходят только с явного согласия пользователя при первом использовании приложения."
|
||||
},
|
||||
"connection": {
|
||||
"tapToConnect": "Нажмите для подключения",
|
||||
|
||||
@@ -334,7 +334,7 @@
|
||||
"play": {
|
||||
"title": "Umbrix (Önizleme)",
|
||||
"short_description": "Otomatik, SSH, VLESS, Vmess, Trojan, Reality, Sing-Box, Clash, Xray, Shadowsocks",
|
||||
"full_description": "Umbrix'in temel hedefi güvenli, kullanıcı dostu ve verimli bir tünel istemcisi sağlamaktır. VPN Hizmeti iznini kullanarak tüm trafiği veya seçilen uygulama trafiğini seçtiğiniz uzak bir sunucuya yönlendirmenizi sağlar. Not: Herhangi bir sunucu sağlamıyoruz; kullanıcıların kendi barındırılan sunucularını veya güvenilir sunucularını kullanarak çevrimiçi etkinliklerinin gizli kalmasını sağlamaları gerekir. Sunucuları aşağıdakilerle destekliyoruz: - Normal V2ray/Xray Abonelik Bağlantısı - Clash Abonelik Bağlantısı - Sing-Box Abonelik Bağlantısı Benzersiz özelliklerimiz nelerdir? - Kullanıcı Dostu - Optimize Edilmiş ve Hızlı - En Düşük Ping'i otomatik olarak seçin - Kullanıcı kullanım bilgilerini gösterin - Derin bağlantı kullanarak tek tıklamayla alt bağlantıyı kolayca içe aktarın - Ücretsiz ve ADS Yok - Kullanıcı alt bağlantılarını kolayca değiştirin - giderek daha fazla Destek: - Sing-Box tarafından desteklenen tüm Protokoller - VLESS + xtls gerçeklik, vizyon - VMESS - Trojan - ShoadowSocks - Reality - V2ray - Hystria2 - TUIC - SSH - ShadowTLS Kaynak kodu https://github.com/hiddify/Hiddify-Next adresinde mevcuttur. Uygulama çekirdeği açık tabanlıdır. kaynak şarkı kutusu. İzin Açıklaması: - VPN Hizmeti: Bu uygulamanın amacı güvenli, kullanıcı dostu ve verimli bir tünel istemcisi sağlamak olduğundan, trafiği tünel aracılığıyla uzak sunucuya yönlendirebilmek için bu izne ihtiyacımız var. - TÜM PAKETLERİ SORGULAYIN: Bu izin, kullanıcıların tünelleme için belirli uygulamaları dahil etmesine veya hariç tutmasına izin vermek için kullanılır. - ALMA ÖNYÜKLEME TAMAMLANDI: Bu izin, cihaz önyüklemesi sırasında bu uygulamayı etkinleştirmek için uygulama ayarlarından etkinleştirilebilir veya devre dışı bırakılabilir. - BİLDİRİMLER SONRASI: VPN hizmetinin sürekli çalışmasını sağlamak için bir ön plan hizmeti kullandığımız için bu izin önemlidir. - Bu uygulama reklam içermez. Analitik ve kilitlenme verileri yalnızca uygulamanın ilk kullanımında kullanıcının açık rızası ile gerçekleşir."
|
||||
"full_description": "Umbrix'in temel hedefi güvenli, kullanıcı dostu ve verimli bir tünel istemcisi sağlamaktır. VPN Hizmeti iznini kullanarak tüm trafiği veya seçilen uygulama trafiğini seçtiğiniz uzak bir sunucuya yönlendirmenizi sağlar. Not: Herhangi bir sunucu sağlamıyoruz; kullanıcıların kendi barındırılan sunucularını veya güvenilir sunucularını kullanarak çevrimiçi etkinliklerinin gizli kalmasını sağlamaları gerekir. Sunucuları aşağıdakilerle destekliyoruz: - Normal V2ray/Xray Abonelik Bağlantısı - Clash Abonelik Bağlantısı - Sing-Box Abonelik Bağlantısı Benzersiz özelliklerimiz nelerdir? - Kullanıcı Dostu - Optimize Edilmiş ve Hızlı - En Düşük Ping'i otomatik olarak seçin - Kullanıcı kullanım bilgilerini gösterin - Derin bağlantı kullanarak tek tıklamayla alt bağlantıyı kolayca içe aktarın - Ücretsiz ve ADS Yok - Kullanıcı alt bağlantılarını kolayca değiştirin - giderek daha fazla Destek: - Sing-Box tarafından desteklenen tüm Protokoller - VLESS + xtls gerçeklik, vizyon - VMESS - Trojan - ShoadowSocks - Reality - V2ray - Hystria2 - TUIC - SSH - ShadowTLS Kaynak kodu Based on open-source project adresinde mevcuttur. Uygulama çekirdeği açık tabanlıdır. kaynak şarkı kutusu. İzin Açıklaması: - VPN Hizmeti: Bu uygulamanın amacı güvenli, kullanıcı dostu ve verimli bir tünel istemcisi sağlamak olduğundan, trafiği tünel aracılığıyla uzak sunucuya yönlendirebilmek için bu izne ihtiyacımız var. - TÜM PAKETLERİ SORGULAYIN: Bu izin, kullanıcıların tünelleme için belirli uygulamaları dahil etmesine veya hariç tutmasına izin vermek için kullanılır. - ALMA ÖNYÜKLEME TAMAMLANDI: Bu izin, cihaz önyüklemesi sırasında bu uygulamayı etkinleştirmek için uygulama ayarlarından etkinleştirilebilir veya devre dışı bırakılabilir. - BİLDİRİMLER SONRASI: VPN hizmetinin sürekli çalışmasını sağlamak için bir ön plan hizmeti kullandığımız için bu izin önemlidir. - Bu uygulama reklam içermez. Analitik ve kilitlenme verileri yalnızca uygulamanın ilk kullanımında kullanıcının açık rızası ile gerçekleşir."
|
||||
},
|
||||
"connection": {
|
||||
"tapToConnect": "Bağlanmak için dokunun",
|
||||
|
||||
@@ -336,7 +336,7 @@
|
||||
"play": {
|
||||
"title": "Umbrix(預覽)",
|
||||
"short_description": "自動、SSH、VLESS、Vmess、Trojan、Reality、Sing-Box、Clash、Xray、Shadowsocks",
|
||||
"full_description": "Umbrix 的主要目標是提供安全、使用者友好且高效率的隧道用戶端。它使您能夠利用 VPN 服務權限將所有流量或選定的應用程式流量路由到您選擇的遠端伺服器。\n\n註:我們不提供任何伺服器;使用者需要使用自己的自託管伺服器或受信任的伺服器來確保其線上活動的隱私。\n\n我們透過以下方式支援伺服器:\n - 普通 V2ray/Xray 訂閱連結\n - Clash 訂閱連結\n - Sing-Box 訂閱連結\n\n 我們的獨特功能是什麼?\n - 使用者友善\n - 最佳化且快速\n - 自動選擇最低延遲\n - 顯示使用者使用資訊\n - 使用一鍵連結輕鬆導入\n - 免費且無廣告\n - 輕鬆切換線路\n - 等等\n 支援:\n - Sing-Box 支援的所有協定 \n - VLESS + XTLS Reality、Vision 協定 \n - VMESS\n - Trojan\n - ShadowSocks\n - Reality\n - WireGuard\n - V2ray\n - Hystria2\n - TUIC \n - SSH\n - ShadowTLS\n\n\n 原始碼位於 https://github.com/hiddify/Hiddify-Next\n 應用程式核心基於開源的 Sing-Box。\n\n權限說明:\n\n - VPN 服務:由於此應用程式的目標是提供安全性、使用者友好且高效的隧道用戶端,因此我們需要此權限才能透過隧道將流量路由到遠端伺服器。\n - 獲取應用程式列表:此權限用於允許使用者包含或排除隧道的特定應用程式。\n - 接收啟動廣播:可以從應用程式設定中啟用或停用此權限,以在裝置啟動時啟動此應用程式。\n - 傳送通知:此權限至關重要,因為我們使用前台服務來確保 VPN 服務的持續運作。\n - 該應用程式沒有廣告。分析和崩潰數據僅在用戶首次使用應用程式時明確同意的情況下才會出現。"
|
||||
"full_description": "Umbrix 的主要目標是提供安全、使用者友好且高效率的隧道用戶端。它使您能夠利用 VPN 服務權限將所有流量或選定的應用程式流量路由到您選擇的遠端伺服器。\n\n註:我們不提供任何伺服器;使用者需要使用自己的自託管伺服器或受信任的伺服器來確保其線上活動的隱私。\n\n我們透過以下方式支援伺服器:\n - 普通 V2ray/Xray 訂閱連結\n - Clash 訂閱連結\n - Sing-Box 訂閱連結\n\n 我們的獨特功能是什麼?\n - 使用者友善\n - 最佳化且快速\n - 自動選擇最低延遲\n - 顯示使用者使用資訊\n - 使用一鍵連結輕鬆導入\n - 免費且無廣告\n - 輕鬆切換線路\n - 等等\n 支援:\n - Sing-Box 支援的所有協定 \n - VLESS + XTLS Reality、Vision 協定 \n - VMESS\n - Trojan\n - ShadowSocks\n - Reality\n - WireGuard\n - V2ray\n - Hystria2\n - TUIC \n - SSH\n - ShadowTLS\n\n\n 原始碼位於 Based on open-source project\n 應用程式核心基於開源的 Sing-Box。\n\n權限說明:\n\n - VPN 服務:由於此應用程式的目標是提供安全性、使用者友好且高效的隧道用戶端,因此我們需要此權限才能透過隧道將流量路由到遠端伺服器。\n - 獲取應用程式列表:此權限用於允許使用者包含或排除隧道的特定應用程式。\n - 接收啟動廣播:可以從應用程式設定中啟用或停用此權限,以在裝置啟動時啟動此應用程式。\n - 傳送通知:此權限至關重要,因為我們使用前台服務來確保 VPN 服務的持續運作。\n - 該應用程式沒有廣告。分析和崩潰數據僅在用戶首次使用應用程式時明確同意的情況下才會出現。"
|
||||
},
|
||||
"connection": {
|
||||
"tapToConnect": "點擊以連線",
|
||||
|
||||
32
docker-compose.yml
Normal file
32
docker-compose.yml
Normal file
@@ -0,0 +1,32 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
update-server:
|
||||
image: php:8.3-cli
|
||||
container_name: umbrix-update-server
|
||||
working_dir: /var/www
|
||||
command: php -S 0.0.0.0:8000
|
||||
ports:
|
||||
- "8000:8000"
|
||||
volumes:
|
||||
# Код сервера (read-only)
|
||||
- ./update-server:/var/www:ro
|
||||
# Файлы обновлений (read-write, persistent)
|
||||
- umbrix-downloads:/var/www/downloads
|
||||
# Логи (read-write)
|
||||
- umbrix-logs:/var/www/logs
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- umbrix-network
|
||||
|
||||
networks:
|
||||
umbrix-network:
|
||||
driver: bridge
|
||||
|
||||
volumes:
|
||||
# Постоянное хранилище для APK файлов
|
||||
umbrix-downloads:
|
||||
driver: local
|
||||
# Логи сервера
|
||||
umbrix-logs:
|
||||
driver: local
|
||||
523
docs/BOTTOM_SHEET_BUTTONS.md
Normal file
523
docs/BOTTOM_SHEET_BUTTONS.md
Normal file
@@ -0,0 +1,523 @@
|
||||
# 🎨 Кнопки Bottom Sheet - Расположение и Иконки
|
||||
|
||||
## 📍 Где находятся эти кнопки?
|
||||
|
||||
**Файл:** [lib/features/profile/add/add_profile_modal.dart](../lib/features/profile/add/add_profile_modal.dart)
|
||||
|
||||
**Класс:** `AddProfileModal` (extends `HookConsumerWidget`)
|
||||
|
||||
---
|
||||
|
||||
## 🖼️ Структура кнопок
|
||||
|
||||
### 1. **"Добавить из буфера обмена"** (Clipboard)
|
||||
|
||||
```dart
|
||||
_Button(
|
||||
key: const ValueKey("add_from_clipboard_button"),
|
||||
label: t.profile.add.fromClipboard,
|
||||
icon: FluentIcons.clipboard_paste_24_regular, // ← ИКОНКА
|
||||
size: buttonWidth,
|
||||
onTap: () async {
|
||||
final captureResult = await Clipboard.getData(Clipboard.kTextPlain).then((value) => value?.text ?? '');
|
||||
if (addProfileState.isLoading) return;
|
||||
ref.read(addProfileProvider.notifier).add(captureResult);
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
**Иконка:** `FluentIcons.clipboard_paste_24_regular`
|
||||
**Перевод:** `t.profile.add.fromClipboard` → "Добавить из буфера обмена"
|
||||
**Цвет:** `theme.colorScheme.primary` (основной цвет темы)
|
||||
|
||||
---
|
||||
|
||||
### 2. **"Сканировать QR-код"** (QR Code)
|
||||
|
||||
```dart
|
||||
_Button(
|
||||
key: const ValueKey("add_by_qr_code_button"),
|
||||
label: t.profile.add.scanQr,
|
||||
icon: FluentIcons.qr_code_24_regular, // ← ИКОНКА
|
||||
size: buttonWidth,
|
||||
onTap: () async {
|
||||
final cr = await const QRCodeScannerScreen().open(context);
|
||||
if (cr == null) return;
|
||||
if (addProfileState.isLoading) return;
|
||||
ref.read(addProfileProvider.notifier).add(cr);
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
**Иконка:** `FluentIcons.qr_code_24_regular`
|
||||
**Перевод:** `t.profile.add.scanQr` → "Сканировать QR-код"
|
||||
**Цвет:** `theme.colorScheme.primary`
|
||||
**Условие:** Показывается только на мобильных (`!PlatformUtils.isDesktop`)
|
||||
|
||||
---
|
||||
|
||||
### 3. **"Добавить WARP"**
|
||||
|
||||
```dart
|
||||
Material(
|
||||
key: const ValueKey("add_warp_button"),
|
||||
elevation: 8,
|
||||
color: theme.colorScheme.surface,
|
||||
surfaceTintColor: theme.colorScheme.surfaceTint,
|
||||
shadowColor: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: InkWell(
|
||||
onTap: () async {
|
||||
await addProfileModal(context, ref);
|
||||
},
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
FluentIcons.add_24_regular, // ← ИКОНКА
|
||||
color: theme.colorScheme.primary,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
t.profile.add.addWarp, // "Добавить WARP"
|
||||
style: theme.textTheme.labelLarge?.copyWith(
|
||||
color: theme.colorScheme.primary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
**Иконка:** `FluentIcons.add_24_regular`
|
||||
**Перевод:** `t.profile.add.addWarp` → "Добавить WARP"
|
||||
**Стиль:** Полная ширина кнопка с текстом и иконкой
|
||||
|
||||
---
|
||||
|
||||
### 4. **"Ввести вручную"**
|
||||
|
||||
```dart
|
||||
_Button(
|
||||
key: const ValueKey("add_manually_button"),
|
||||
label: t.profile.add.manually,
|
||||
icon: FluentIcons.add_24_regular, // ← ИКОНКА
|
||||
size: buttonWidth,
|
||||
onTap: () async {
|
||||
context.pop();
|
||||
await const NewProfileRoute().push(context);
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
**Иконка:** `FluentIcons.add_24_regular`
|
||||
**Перевод:** `t.profile.add.manually` → "Ввести вручную"
|
||||
**Условие:**
|
||||
- На мобильных → показывается как отдельная кнопка внизу
|
||||
- На Desktop → показывается вместо кнопки "Сканировать QR-код"
|
||||
|
||||
---
|
||||
|
||||
## 🎨 Откуда берутся иконки?
|
||||
|
||||
### Пакет: **fluentui_system_icons**
|
||||
|
||||
**pubspec.yaml:**
|
||||
```yaml
|
||||
dependencies:
|
||||
fluentui_system_icons: ^1.1.229
|
||||
```
|
||||
|
||||
**Импорт:**
|
||||
```dart
|
||||
import 'package:fluentui_system_icons/fluentui_system_icons.dart';
|
||||
```
|
||||
|
||||
### Используемые иконки:
|
||||
|
||||
| Иконка | Код | Описание |
|
||||
|--------|-----|----------|
|
||||
| 📋 | `FluentIcons.clipboard_paste_24_regular` | Буфер обмена |
|
||||
| 📷 | `FluentIcons.qr_code_24_regular` | QR-код |
|
||||
| ➕ | `FluentIcons.add_24_regular` | Добавить/Плюс |
|
||||
|
||||
---
|
||||
|
||||
## 🎨 Как иконки меняют цвет?
|
||||
|
||||
### Автоматическая смена цвета через Theme
|
||||
|
||||
```dart
|
||||
Icon(
|
||||
FluentIcons.clipboard_paste_24_regular,
|
||||
size: buttonWidth / 3,
|
||||
color: theme.colorScheme.primary, // ← ЦВЕТ ИЗ ТЕМЫ
|
||||
)
|
||||
```
|
||||
|
||||
**Откуда берётся цвет:**
|
||||
- `theme.colorScheme.primary` - основной цвет темы
|
||||
- Меняется автоматически при смене темы (светлая/тёмная)
|
||||
- Определяется в настройках темы приложения
|
||||
|
||||
---
|
||||
|
||||
## 🎨 Где определяется тема?
|
||||
|
||||
**Путь к настройкам темы:**
|
||||
```dart
|
||||
// Тема определяется в:
|
||||
lib/core/preferences/
|
||||
lib/features/config_option/
|
||||
```
|
||||
|
||||
**Как работает смена цвета:**
|
||||
|
||||
1. **Пользователь выбирает тему** (светлую/тёмную/автоматическую)
|
||||
2. **Flutter Theme система** применяет `ColorScheme`
|
||||
3. **ColorScheme.primary** меняется на цвет из палитры темы
|
||||
4. **Icon виджеты** автоматически перерисовываются с новым цветом
|
||||
|
||||
**Пример:**
|
||||
```dart
|
||||
// Светлая тема
|
||||
colorScheme: ColorScheme.light(
|
||||
primary: Color(0xFF007AFF), // Синий
|
||||
)
|
||||
|
||||
// Тёмная тема
|
||||
colorScheme: ColorScheme.dark(
|
||||
primary: Color(0xFF0A84FF), // Светло-синий
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📦 Формат иконок
|
||||
|
||||
### FluentUI System Icons - это векторные иконки
|
||||
|
||||
**Технические детали:**
|
||||
- **Формат:** IconData (Flutter встроенный формат)
|
||||
- **Источник:** Microsoft Fluent Design System
|
||||
- **Размер:** Масштабируемые (векторные, не растровые)
|
||||
- **Цвет:** Одноцветные, цвет задаётся программно
|
||||
- **Вариации:** `_regular`, `_filled` (обычные и заполненные)
|
||||
|
||||
**НЕ PNG, НЕ SVG файлы!** Это встроенные векторные глифы в шрифте.
|
||||
|
||||
---
|
||||
|
||||
## 🎨 Как изменить иконки?
|
||||
|
||||
### Вариант 1: Использовать другую иконку из того же пакета
|
||||
|
||||
```dart
|
||||
// Было:
|
||||
icon: FluentIcons.clipboard_paste_24_regular,
|
||||
|
||||
// Стало:
|
||||
icon: FluentIcons.copy_24_regular, // Другая иконка
|
||||
```
|
||||
|
||||
**Посмотреть все доступные иконки:**
|
||||
https://github.com/microsoft/fluentui-system-icons/tree/main/fonts
|
||||
|
||||
### Вариант 2: Использовать Material Icons
|
||||
|
||||
```dart
|
||||
// Изменить импорт
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
// Использовать Material иконку
|
||||
icon: Icons.content_paste,
|
||||
```
|
||||
|
||||
### Вариант 3: Использовать свои SVG иконки
|
||||
|
||||
**1. Добавьте пакет flutter_svg:**
|
||||
```yaml
|
||||
dependencies:
|
||||
flutter_svg: ^2.0.10
|
||||
```
|
||||
|
||||
**2. Сохраните SVG в assets:**
|
||||
```
|
||||
assets/images/icons/clipboard.svg
|
||||
assets/images/icons/qr_code.svg
|
||||
```
|
||||
|
||||
**3. Используйте в коде:**
|
||||
```dart
|
||||
// Вместо Icon виджета
|
||||
SvgPicture.asset(
|
||||
'assets/images/icons/clipboard.svg',
|
||||
width: size / 3,
|
||||
height: size / 3,
|
||||
colorFilter: ColorFilter.mode(
|
||||
theme.colorScheme.primary,
|
||||
BlendMode.srcIn,
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Как изменить размер иконок?
|
||||
|
||||
```dart
|
||||
Icon(
|
||||
FluentIcons.clipboard_paste_24_regular,
|
||||
size: buttonWidth / 3, // ← РАЗМЕР
|
||||
color: theme.colorScheme.primary,
|
||||
)
|
||||
```
|
||||
|
||||
**Текущий размер:** `buttonWidth / 3`
|
||||
- buttonWidth рассчитывается из ширины экрана
|
||||
- Примерно 48-56 пикселей для иконки
|
||||
|
||||
**Чтобы изменить:**
|
||||
```dart
|
||||
// Фиксированный размер
|
||||
size: 64,
|
||||
|
||||
// Относительный размер
|
||||
size: buttonWidth / 2, // Больше
|
||||
size: buttonWidth / 4, // Меньше
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎨 Как изменить цвет иконок?
|
||||
|
||||
### Вариант 1: Использовать другой цвет из темы
|
||||
|
||||
```dart
|
||||
Icon(
|
||||
FluentIcons.clipboard_paste_24_regular,
|
||||
color: theme.colorScheme.secondary, // Вторичный цвет
|
||||
// или:
|
||||
// color: theme.colorScheme.tertiary,
|
||||
// color: theme.colorScheme.error,
|
||||
// color: theme.colorScheme.onSurface,
|
||||
)
|
||||
```
|
||||
|
||||
### Вариант 2: Использовать кастомный цвет
|
||||
|
||||
```dart
|
||||
Icon(
|
||||
FluentIcons.clipboard_paste_24_regular,
|
||||
color: Color(0xFF00FF00), // Зелёный
|
||||
// или:
|
||||
// color: Colors.red,
|
||||
// color: Colors.amber,
|
||||
)
|
||||
```
|
||||
|
||||
### Вариант 3: Градиент (сложнее)
|
||||
|
||||
Для градиента нужно использовать `ShaderMask`:
|
||||
|
||||
```dart
|
||||
ShaderMask(
|
||||
shaderCallback: (Rect bounds) {
|
||||
return LinearGradient(
|
||||
colors: [Colors.blue, Colors.purple],
|
||||
).createShader(bounds);
|
||||
},
|
||||
child: Icon(
|
||||
FluentIcons.clipboard_paste_24_regular,
|
||||
size: buttonWidth / 3,
|
||||
color: Colors.white, // Базовый цвет (будет заменён градиентом)
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📱 Адаптивность кнопок
|
||||
|
||||
### Мобильные (Android/iOS):
|
||||
```dart
|
||||
if (!PlatformUtils.isDesktop)
|
||||
_Button(
|
||||
label: t.profile.add.scanQr,
|
||||
icon: FluentIcons.qr_code_24_regular,
|
||||
)
|
||||
```
|
||||
|
||||
**Показывается:**
|
||||
- ✅ Добавить из буфера обмена
|
||||
- ✅ Сканировать QR-код
|
||||
- ✅ Добавить WARP
|
||||
- ✅ Ввести вручную
|
||||
|
||||
### Desktop (Windows/macOS/Linux):
|
||||
```dart
|
||||
else
|
||||
_Button(
|
||||
label: t.profile.add.manually,
|
||||
icon: FluentIcons.add_24_regular,
|
||||
)
|
||||
```
|
||||
|
||||
**Показывается:**
|
||||
- ✅ Добавить из буфера обмена
|
||||
- ✅ Ввести вручную (вместо QR)
|
||||
- ✅ Добавить WARP
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Класс _Button
|
||||
|
||||
**Виджет для квадратных кнопок:**
|
||||
|
||||
```dart
|
||||
class _Button extends StatelessWidget {
|
||||
final String label; // Текст кнопки
|
||||
final IconData icon; // Иконка (FluentIcons.*)
|
||||
final double size; // Размер кнопки (квадрат)
|
||||
final VoidCallback onTap; // Действие при нажатии
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
width: size,
|
||||
height: size,
|
||||
child: Material(
|
||||
elevation: 8, // Тень
|
||||
color: theme.colorScheme.surface,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(icon, size: size / 3, color: color),
|
||||
const Gap(16),
|
||||
Text(label, style: labelStyle),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Свойства:**
|
||||
- Квадратная форма (`width = height = size`)
|
||||
- Иконка вверху (1/3 размера кнопки)
|
||||
- Текст внизу
|
||||
- Скругленные углы (8px)
|
||||
- Тень (elevation: 8)
|
||||
- Ripple эффект при нажатии (InkWell)
|
||||
|
||||
---
|
||||
|
||||
## 🎨 Цветовая схема по умолчанию
|
||||
|
||||
**Где определяется primary цвет:**
|
||||
|
||||
```dart
|
||||
// Вероятно в:
|
||||
lib/core/preferences/general_preferences_provider.dart
|
||||
lib/core/theme/
|
||||
```
|
||||
|
||||
**Umbrix использует:**
|
||||
- **Primary color:** Бирюзовый/голубой (#00BCD4 или похожий)
|
||||
- **Surface:** Фон карточек
|
||||
- **OnSurface:** Текст на фоне
|
||||
|
||||
---
|
||||
|
||||
## 💡 Быстрые изменения
|
||||
|
||||
### Изменить цвет всех иконок на красный:
|
||||
|
||||
```dart
|
||||
Icon(
|
||||
icon,
|
||||
size: size / 3,
|
||||
color: Colors.red, // ← ВСЕ ИКОНКИ СТАНУТ КРАСНЫМИ
|
||||
)
|
||||
```
|
||||
|
||||
### Изменить размер иконок на больший:
|
||||
|
||||
```dart
|
||||
Icon(
|
||||
icon,
|
||||
size: size / 2, // ← БЫЛО /3, СТАЛО /2 (больше)
|
||||
color: color,
|
||||
)
|
||||
```
|
||||
|
||||
### Использовать заполненные (filled) иконки:
|
||||
|
||||
```dart
|
||||
// Было:
|
||||
icon: FluentIcons.clipboard_paste_24_regular,
|
||||
|
||||
// Стало:
|
||||
icon: FluentIcons.clipboard_paste_24_filled, // Заполненная версия
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Где ещё используются FluentIcons?
|
||||
|
||||
**Поиск по проекту:**
|
||||
```bash
|
||||
grep -r "FluentIcons\." lib/ | wc -l
|
||||
# Результат: Много! Используются по всему приложению
|
||||
```
|
||||
|
||||
**Примеры файлов:**
|
||||
- `lib/features/home/widget/home_page.dart`
|
||||
- `lib/features/proxy/overview/proxies_overview_page.dart`
|
||||
- `lib/features/common/adaptive_root_scaffold.dart`
|
||||
|
||||
---
|
||||
|
||||
## 📚 Полезные ссылки
|
||||
|
||||
1. **Fluent UI Icons Gallery:**
|
||||
https://aka.ms/fluentui-system-icons
|
||||
|
||||
2. **Flutter Icon класс:**
|
||||
https://api.flutter.dev/flutter/widgets/Icon-class.html
|
||||
|
||||
3. **ColorScheme документация:**
|
||||
https://api.flutter.dev/flutter/material/ColorScheme-class.html
|
||||
|
||||
4. **Пакет fluentui_system_icons:**
|
||||
https://pub.dev/packages/fluentui_system_icons
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Резюме
|
||||
|
||||
**Кнопки находятся:** `lib/features/profile/add/add_profile_modal.dart`
|
||||
|
||||
**Иконки:**
|
||||
- Формат: IconData из пакета `fluentui_system_icons`
|
||||
- НЕ файлы PNG/SVG, а векторные глифы в шрифте
|
||||
- Цвет: `theme.colorScheme.primary` (меняется с темой)
|
||||
- Размер: Масштабируемые, задаются программно
|
||||
|
||||
**Смена цвета:**
|
||||
- Автоматическая через Theme системы Flutter
|
||||
- `colorScheme.primary` берётся из настроек темы
|
||||
- Светлая/тёмная тема → разные оттенки
|
||||
|
||||
**Чтобы изменить:**
|
||||
1. Цвет → поменять `color: theme.colorScheme.primary` на другой
|
||||
2. Размер → изменить `size: buttonWidth / 3`
|
||||
3. Иконку → заменить `FluentIcons.clipboard_paste_24_regular` на другую
|
||||
4. Свою иконку → использовать SVG через flutter_svg пакет
|
||||
@@ -1005,7 +1005,7 @@
|
||||
CURRENT_PROJECT_VERSION = 2050720505205031;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 2.5.72.5.52.5.31.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.hiddify.ios.RunnerTests;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.umbrix.app.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
@@ -1023,7 +1023,7 @@
|
||||
CURRENT_PROJECT_VERSION = 2050720505205031;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 2.5.72.5.52.5.31.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.hiddify.ios.RunnerTests;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.umbrix.app.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||
@@ -1039,7 +1039,7 @@
|
||||
CURRENT_PROJECT_VERSION = 2050720505205031;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 2.5.72.5.52.5.31.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.hiddify.ios.RunnerTests;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.umbrix.app.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>BASE_BUNDLE_IDENTIFIER</key>
|
||||
<string>$(BASE_BUNDLE_IDENTIFIER)</string>
|
||||
<string>com.umbrix.app</string>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Hiddify</string>
|
||||
<string>Umbrix</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
|
||||
@@ -4,26 +4,26 @@ import 'dart:io';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_displaymode/flutter_displaymode.dart';
|
||||
import 'package:hiddify/core/analytics/analytics_controller.dart';
|
||||
import 'package:hiddify/core/app_info/app_info_provider.dart';
|
||||
import 'package:hiddify/core/directories/directories_provider.dart';
|
||||
import 'package:hiddify/core/logger/logger.dart';
|
||||
import 'package:hiddify/core/logger/logger_controller.dart';
|
||||
import 'package:hiddify/core/model/environment.dart';
|
||||
import 'package:hiddify/core/preferences/general_preferences.dart';
|
||||
import 'package:hiddify/core/preferences/preferences_migration.dart';
|
||||
import 'package:hiddify/core/preferences/preferences_provider.dart';
|
||||
import 'package:hiddify/features/app/widget/app.dart';
|
||||
import 'package:hiddify/features/auto_start/notifier/auto_start_notifier.dart';
|
||||
import 'package:hiddify/features/common/custom_splash_screen.dart';
|
||||
import 'package:hiddify/features/deep_link/notifier/deep_link_notifier.dart';
|
||||
import 'package:hiddify/features/log/data/log_data_providers.dart';
|
||||
import 'package:hiddify/features/profile/data/profile_data_providers.dart';
|
||||
import 'package:hiddify/features/profile/notifier/active_profile_notifier.dart';
|
||||
import 'package:hiddify/features/system_tray/notifier/system_tray_notifier.dart';
|
||||
import 'package:hiddify/features/window/notifier/window_notifier.dart';
|
||||
import 'package:hiddify/singbox/service/singbox_service_provider.dart';
|
||||
import 'package:hiddify/utils/utils.dart';
|
||||
import 'package:umbrix/core/analytics/analytics_controller.dart';
|
||||
import 'package:umbrix/core/app_info/app_info_provider.dart';
|
||||
import 'package:umbrix/core/directories/directories_provider.dart';
|
||||
import 'package:umbrix/core/logger/logger.dart';
|
||||
import 'package:umbrix/core/logger/logger_controller.dart';
|
||||
import 'package:umbrix/core/model/environment.dart';
|
||||
import 'package:umbrix/core/preferences/general_preferences.dart';
|
||||
import 'package:umbrix/core/preferences/preferences_migration.dart';
|
||||
import 'package:umbrix/core/preferences/preferences_provider.dart';
|
||||
import 'package:umbrix/features/app/widget/app.dart';
|
||||
import 'package:umbrix/features/auto_start/notifier/auto_start_notifier.dart';
|
||||
import 'package:umbrix/features/common/custom_splash_screen.dart';
|
||||
import 'package:umbrix/features/deep_link/notifier/deep_link_notifier.dart';
|
||||
import 'package:umbrix/features/log/data/log_data_providers.dart';
|
||||
import 'package:umbrix/features/profile/data/profile_data_providers.dart';
|
||||
import 'package:umbrix/features/profile/notifier/active_profile_notifier.dart';
|
||||
import 'package:umbrix/features/system_tray/notifier/system_tray_notifier.dart';
|
||||
import 'package:umbrix/features/window/notifier/window_notifier.dart';
|
||||
import 'package:umbrix/singbox/service/singbox_service_provider.dart';
|
||||
import 'package:umbrix/utils/utils.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:hiddify/core/analytics/analytics_filter.dart';
|
||||
import 'package:hiddify/core/analytics/analytics_logger.dart';
|
||||
import 'package:hiddify/core/app_info/app_info_provider.dart';
|
||||
import 'package:hiddify/core/logger/logger_controller.dart';
|
||||
import 'package:hiddify/core/model/environment.dart';
|
||||
import 'package:hiddify/core/preferences/preferences_provider.dart';
|
||||
import 'package:hiddify/utils/custom_loggers.dart';
|
||||
import 'package:umbrix/core/analytics/analytics_filter.dart';
|
||||
import 'package:umbrix/core/analytics/analytics_logger.dart';
|
||||
import 'package:umbrix/core/app_info/app_info_provider.dart';
|
||||
import 'package:umbrix/core/logger/logger_controller.dart';
|
||||
import 'package:umbrix/core/model/environment.dart';
|
||||
import 'package:umbrix/core/preferences/preferences_provider.dart';
|
||||
import 'package:umbrix/utils/custom_loggers.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:hiddify/core/model/failures.dart';
|
||||
import 'package:umbrix/core/model/failures.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'package:hiddify/utils/sentry_utils.dart';
|
||||
import 'package:umbrix/utils/sentry_utils.dart';
|
||||
import 'package:loggy/loggy.dart';
|
||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:hiddify/core/model/app_info_entity.dart';
|
||||
import 'package:hiddify/core/model/environment.dart';
|
||||
import 'package:umbrix/core/model/app_info_entity.dart';
|
||||
import 'package:umbrix/core/model/environment.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
|
||||
@@ -2,15 +2,15 @@ import 'package:drift/drift.dart';
|
||||
// ignore: depend_on_referenced_packages
|
||||
import 'package:drift_dev/api/migrations.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:hiddify/core/database/connection/database_connection.dart';
|
||||
import 'package:hiddify/core/database/converters/duration_converter.dart';
|
||||
import 'package:hiddify/core/database/schema_versions.dart';
|
||||
import 'package:hiddify/core/database/tables/database_tables.dart';
|
||||
// import 'package:hiddify/features/geo_asset/data/geo_asset_data_mapper.dart';
|
||||
// import 'package:hiddify/features/geo_asset/model/default_geo_assets.dart';
|
||||
import 'package:hiddify/features/geo_asset/model/geo_asset_entity.dart';
|
||||
import 'package:hiddify/features/profile/model/profile_entity.dart';
|
||||
import 'package:hiddify/utils/custom_loggers.dart';
|
||||
import 'package:umbrix/core/database/connection/database_connection.dart';
|
||||
import 'package:umbrix/core/database/converters/duration_converter.dart';
|
||||
import 'package:umbrix/core/database/schema_versions.dart';
|
||||
import 'package:umbrix/core/database/tables/database_tables.dart';
|
||||
// import 'package:umbrix/features/geo_asset/data/geo_asset_data_mapper.dart';
|
||||
// import 'package:umbrix/features/geo_asset/model/default_geo_assets.dart';
|
||||
import 'package:umbrix/features/geo_asset/model/geo_asset_entity.dart';
|
||||
import 'package:umbrix/features/profile/model/profile_entity.dart';
|
||||
import 'package:umbrix/utils/custom_loggers.dart';
|
||||
|
||||
part 'app_database.g.dart';
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import 'dart:io';
|
||||
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:drift/native.dart';
|
||||
import 'package:hiddify/core/directories/directories_provider.dart';
|
||||
import 'package:umbrix/core/directories/directories_provider.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
LazyDatabase openConnection() {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'package:hiddify/core/database/app_database.dart';
|
||||
import 'package:umbrix/core/database/app_database.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'database_provider.g.dart';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:hiddify/core/database/converters/duration_converter.dart';
|
||||
import 'package:hiddify/features/geo_asset/model/geo_asset_entity.dart';
|
||||
import 'package:hiddify/features/profile/model/profile_entity.dart';
|
||||
import 'package:umbrix/core/database/converters/duration_converter.dart';
|
||||
import 'package:umbrix/features/geo_asset/model/geo_asset_entity.dart';
|
||||
import 'package:umbrix/features/profile/model/profile_entity.dart';
|
||||
|
||||
@DataClassName('ProfileEntry')
|
||||
class ProfileEntries extends Table {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:hiddify/core/model/directories.dart';
|
||||
import 'package:hiddify/utils/custom_loggers.dart';
|
||||
import 'package:umbrix/core/model/directories.dart';
|
||||
import 'package:umbrix/utils/custom_loggers.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
@@ -10,13 +10,12 @@ part 'directories_provider.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class AppDirectories extends _$AppDirectories with InfraLogger {
|
||||
final _methodChannel = const MethodChannel("com.hiddify.app/platform");
|
||||
final _methodChannel = const MethodChannel("com.umbrix.app/platform");
|
||||
|
||||
@override
|
||||
Future<Directories> build() async {
|
||||
final Directories dirs;
|
||||
if (Platform.isIOS) {
|
||||
|
||||
final paths = await _methodChannel.invokeMethod<Map>("get_paths");
|
||||
loggy.debug("paths: $paths");
|
||||
dirs = (
|
||||
@@ -26,8 +25,7 @@ class AppDirectories extends _$AppDirectories with InfraLogger {
|
||||
);
|
||||
} else {
|
||||
final baseDir = await getApplicationSupportDirectory();
|
||||
final workingDir =
|
||||
Platform.isAndroid ? await getExternalStorageDirectory() : baseDir;
|
||||
final workingDir = Platform.isAndroid ? await getExternalStorageDirectory() : baseDir;
|
||||
final tempDir = await getTemporaryDirectory();
|
||||
dirs = (
|
||||
baseDir: baseDir,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:hiddify/core/preferences/preferences_provider.dart';
|
||||
import 'package:umbrix/core/preferences/preferences_provider.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import 'dart:io';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:dio/io.dart';
|
||||
import 'package:dio_smart_retry/dio_smart_retry.dart';
|
||||
import 'package:hiddify/utils/custom_loggers.dart';
|
||||
import 'package:umbrix/utils/custom_loggers.dart';
|
||||
|
||||
class DioHttpClient with InfraLogger {
|
||||
final Map<String, Dio> _dio = {};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:hiddify/core/app_info/app_info_provider.dart';
|
||||
import 'package:hiddify/core/http_client/dio_http_client.dart';
|
||||
import 'package:hiddify/features/config_option/data/config_option_repository.dart';
|
||||
import 'package:umbrix/core/app_info/app_info_provider.dart';
|
||||
import 'package:umbrix/core/http_client/dio_http_client.dart';
|
||||
import 'package:umbrix/features/config_option/data/config_option_repository.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'http_client_provider.g.dart';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:hiddify/gen/fonts.gen.dart';
|
||||
import 'package:hiddify/gen/translations.g.dart';
|
||||
import 'package:umbrix/gen/fonts.gen.dart';
|
||||
import 'package:umbrix/gen/translations.g.dart';
|
||||
|
||||
extension AppLocaleX on AppLocale {
|
||||
String get preferredFontFamily => this == AppLocale.fa ? FontFamily.shabnam : (!Platform.isWindows ? "" : FontFamily.emoji);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:hiddify/core/preferences/preferences_provider.dart';
|
||||
import 'package:hiddify/gen/translations.g.dart';
|
||||
import 'package:hiddify/utils/custom_loggers.dart';
|
||||
import 'package:umbrix/core/preferences/preferences_provider.dart';
|
||||
import 'package:umbrix/gen/translations.g.dart';
|
||||
import 'package:umbrix/utils/custom_loggers.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'locale_preferences.g.dart';
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'package:hiddify/core/localization/locale_preferences.dart';
|
||||
import 'package:hiddify/gen/translations.g.dart';
|
||||
import 'package:umbrix/core/localization/locale_preferences.dart';
|
||||
import 'package:umbrix/gen/translations.g.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
export 'package:hiddify/gen/translations.g.dart';
|
||||
export 'package:umbrix/gen/translations.g.dart';
|
||||
|
||||
part 'translations.g.dart';
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:hiddify/core/logger/custom_logger.dart';
|
||||
import 'package:hiddify/utils/custom_loggers.dart';
|
||||
import 'package:umbrix/core/logger/custom_logger.dart';
|
||||
import 'package:umbrix/utils/custom_loggers.dart';
|
||||
import 'package:loggy/loggy.dart';
|
||||
|
||||
class LoggerController extends LoggyPrinter with InfraLogger {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:hiddify/core/model/environment.dart';
|
||||
import 'package:umbrix/core/model/environment.dart';
|
||||
|
||||
part 'app_info_entity.freezed.dart';
|
||||
|
||||
|
||||
@@ -9,6 +9,20 @@ abstract class Constants {
|
||||
static const termsAndConditionsUrl = "https://umbrix.net/terms.html";
|
||||
static const cfWarpPrivacyPolicy = "https://www.cloudflare.com/application/privacypolicy/";
|
||||
static const cfWarpTermsOfService = "https://www.cloudflare.com/application/terms/";
|
||||
|
||||
// ===== НАСТРОЙКИ СЕРВЕРА ОБНОВЛЕНИЙ =====
|
||||
|
||||
// Собственный сервер обновлений (для приватного репозитория)
|
||||
// 📝 ИНСТРУКЦИЯ: Замените на URL вашего API сервера
|
||||
// Пример: "https://api.umbrix.net/api/latest"
|
||||
// 🧪 Для тестирования в эмуляторе используйте: "http://10.0.2.2:8000/api.php"
|
||||
// См. документацию в папке: update-server/README.md
|
||||
static const customUpdateServerUrl = "http://10.0.2.2:8000/api.php";
|
||||
|
||||
// Использовать собственный сервер обновлений вместо GitHub
|
||||
// true = использовать customUpdateServerUrl (для приватного репозитория)
|
||||
// false = использовать GitHub Releases (для публичного репозитория)
|
||||
static const useCustomUpdateServer = true;
|
||||
}
|
||||
|
||||
const kAnimationDuration = Duration(milliseconds: 250);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:hiddify/core/localization/translations.dart';
|
||||
import 'package:umbrix/core/localization/translations.dart';
|
||||
|
||||
typedef PresentableError = ({String type, String? message});
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:dart_mappable/dart_mappable.dart';
|
||||
import 'package:dartx/dartx.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:hiddify/core/localization/translations.dart';
|
||||
import 'package:umbrix/core/localization/translations.dart';
|
||||
|
||||
part 'optional_range.mapper.dart';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'package:hiddify/core/localization/translations.dart';
|
||||
import 'package:umbrix/core/localization/translations.dart';
|
||||
|
||||
enum Region {
|
||||
ir,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:hiddify/core/model/failures.dart';
|
||||
import 'package:hiddify/features/common/adaptive_root_scaffold.dart';
|
||||
import 'package:hiddify/utils/utils.dart';
|
||||
import 'package:umbrix/core/model/failures.dart';
|
||||
import 'package:umbrix/features/common/adaptive_root_scaffold.dart';
|
||||
import 'package:umbrix/utils/utils.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:toastification/toastification.dart';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'package:hiddify/gen/translations.g.dart';
|
||||
import 'package:umbrix/gen/translations.g.dart';
|
||||
|
||||
enum ActionsAtClosing {
|
||||
ask,
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:hiddify/core/app_info/app_info_provider.dart';
|
||||
import 'package:hiddify/core/model/environment.dart';
|
||||
import 'package:hiddify/core/preferences/actions_at_closing.dart';
|
||||
// import 'package:hiddify/core/model/region.dart';
|
||||
import 'package:hiddify/core/preferences/preferences_provider.dart';
|
||||
import 'package:hiddify/core/utils/preferences_utils.dart';
|
||||
import 'package:hiddify/features/connection/model/connection_status.dart';
|
||||
import 'package:hiddify/features/connection/notifier/connection_notifier.dart';
|
||||
import 'package:hiddify/features/per_app_proxy/model/per_app_proxy_mode.dart';
|
||||
import 'package:hiddify/features/profile/notifier/active_profile_notifier.dart';
|
||||
import 'package:hiddify/utils/platform_utils.dart';
|
||||
import 'package:umbrix/core/app_info/app_info_provider.dart';
|
||||
import 'package:umbrix/core/model/environment.dart';
|
||||
import 'package:umbrix/core/preferences/actions_at_closing.dart';
|
||||
// import 'package:umbrix/core/model/region.dart';
|
||||
import 'package:umbrix/core/preferences/preferences_provider.dart';
|
||||
import 'package:umbrix/core/utils/preferences_utils.dart';
|
||||
import 'package:umbrix/features/connection/model/connection_status.dart';
|
||||
import 'package:umbrix/features/connection/notifier/connection_notifier.dart';
|
||||
import 'package:umbrix/features/per_app_proxy/model/per_app_proxy_mode.dart';
|
||||
import 'package:umbrix/features/profile/notifier/active_profile_notifier.dart';
|
||||
import 'package:umbrix/utils/platform_utils.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'general_preferences.g.dart';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'package:hiddify/utils/utils.dart';
|
||||
import 'package:umbrix/utils/utils.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
class PreferencesMigration with InfraLogger {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hiddify/core/preferences/general_preferences.dart';
|
||||
import 'package:hiddify/core/router/routes.dart';
|
||||
import 'package:hiddify/features/deep_link/notifier/deep_link_notifier.dart';
|
||||
import 'package:hiddify/utils/utils.dart';
|
||||
import 'package:umbrix/core/preferences/general_preferences.dart';
|
||||
import 'package:umbrix/core/router/routes.dart';
|
||||
import 'package:umbrix/features/deep_link/notifier/deep_link_notifier.dart';
|
||||
import 'package:umbrix/utils/utils.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||
|
||||
@@ -50,22 +50,26 @@ GoRouter router(RouterRef ref) {
|
||||
}
|
||||
|
||||
final tabLocations = [
|
||||
const HomeRoute().location,
|
||||
const ProxiesRoute().location,
|
||||
const PerAppProxyRoute().location,
|
||||
const ConfigOptionsRoute().location,
|
||||
const SettingsRoute().location,
|
||||
const AboutRoute().location,
|
||||
const HomeRoute().location, // 0: Главная
|
||||
const ProxiesRoute().location, // 1: Локации
|
||||
const PerAppProxyRoute().location, // 2: Исключения
|
||||
const SettingsRoute().location, // 3: Настройки
|
||||
const AboutRoute().location, // 4: О программе
|
||||
];
|
||||
|
||||
int getCurrentIndex(BuildContext context) {
|
||||
final String location = GoRouterState.of(context).uri.path;
|
||||
|
||||
// Проверяем точное совпадение для главной
|
||||
if (location == const HomeRoute().location) return 0;
|
||||
var index = 0;
|
||||
for (final tab in tabLocations.sublist(1)) {
|
||||
index++;
|
||||
if (location.startsWith(tab)) return index;
|
||||
}
|
||||
|
||||
// Проверяем остальные маршруты по порядку
|
||||
// ВАЖНО: более длинные пути проверяем раньше!
|
||||
if (location.startsWith(const PerAppProxyRoute().location)) return 2; // /settings/per-app-proxy
|
||||
if (location.startsWith(const ProxiesRoute().location)) return 1; // /proxies
|
||||
if (location.startsWith(const SettingsRoute().location)) return 3; // /settings
|
||||
if (location.startsWith(const AboutRoute().location)) return 4; // /about
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import 'package:flutter/material.dart';
|
||||
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/widget/quick_settings_modal.dart';
|
||||
import 'package:umbrix/core/router/app_router.dart';
|
||||
import 'package:umbrix/features/common/adaptive_root_scaffold.dart';
|
||||
import 'package:umbrix/features/config_option/overview/config_options_page.dart';
|
||||
import 'package:umbrix/features/config_option/widget/quick_settings_modal.dart';
|
||||
|
||||
import 'package:hiddify/features/home/widget/home_page.dart';
|
||||
import 'package:hiddify/features/intro/widget/intro_page.dart';
|
||||
import 'package:hiddify/features/log/overview/logs_overview_page.dart';
|
||||
import 'package:hiddify/features/per_app_proxy/overview/per_app_proxy_page.dart';
|
||||
import 'package:hiddify/features/profile/add/add_profile_modal.dart';
|
||||
import 'package:hiddify/features/profile/details/profile_details_page.dart';
|
||||
import 'package:hiddify/features/profile/overview/profiles_overview_page.dart';
|
||||
import 'package:hiddify/features/proxy/overview/proxies_overview_page.dart';
|
||||
import 'package:hiddify/features/settings/about/about_page.dart';
|
||||
import 'package:hiddify/features/settings/overview/settings_overview_page.dart';
|
||||
import 'package:hiddify/utils/utils.dart';
|
||||
import 'package:umbrix/features/home/widget/home_page.dart';
|
||||
import 'package:umbrix/features/intro/widget/intro_page.dart';
|
||||
import 'package:umbrix/features/log/overview/logs_overview_page.dart';
|
||||
import 'package:umbrix/features/per_app_proxy/overview/per_app_proxy_page.dart';
|
||||
import 'package:umbrix/features/profile/add/add_profile_modal.dart';
|
||||
import 'package:umbrix/features/profile/details/profile_details_page.dart';
|
||||
import 'package:umbrix/features/profile/overview/profiles_overview_page.dart';
|
||||
import 'package:umbrix/features/proxy/overview/proxies_overview_page.dart';
|
||||
import 'package:umbrix/features/settings/about/about_page.dart';
|
||||
import 'package:umbrix/features/settings/overview/settings_overview_page.dart';
|
||||
import 'package:umbrix/utils/utils.dart';
|
||||
|
||||
part 'routes.g.dart';
|
||||
|
||||
@@ -54,12 +54,7 @@ GlobalKey<NavigatorState>? _dynamicRootKey = useMobileRouter ? rootNavigatorKey
|
||||
TypedGoRoute<SettingsRoute>(
|
||||
path: "settings",
|
||||
name: SettingsRoute.name,
|
||||
routes: [
|
||||
TypedGoRoute<PerAppProxyRoute>(
|
||||
path: "per-app-proxy",
|
||||
name: PerAppProxyRoute.name,
|
||||
),
|
||||
],
|
||||
routes: [],
|
||||
),
|
||||
TypedGoRoute<LogsOverviewRoute>(
|
||||
path: "logs",
|
||||
@@ -75,6 +70,10 @@ GlobalKey<NavigatorState>? _dynamicRootKey = useMobileRouter ? rootNavigatorKey
|
||||
path: "/proxies",
|
||||
name: ProxiesRoute.name,
|
||||
),
|
||||
TypedGoRoute<PerAppProxyRoute>(
|
||||
path: "/settings/per-app-proxy",
|
||||
name: PerAppProxyRoute.name,
|
||||
),
|
||||
],
|
||||
)
|
||||
class MobileWrapperRoute extends ShellRouteData {
|
||||
@@ -118,6 +117,10 @@ class MobileWrapperRoute extends ShellRouteData {
|
||||
path: "/proxies",
|
||||
name: ProxiesRoute.name,
|
||||
),
|
||||
TypedGoRoute<PerAppProxyRoute>(
|
||||
path: "/settings/per-app-proxy",
|
||||
name: PerAppProxyRoute.name,
|
||||
),
|
||||
TypedGoRoute<ConfigOptionsRoute>(
|
||||
path: "/config-options",
|
||||
name: ConfigOptionsRoute.name,
|
||||
@@ -180,9 +183,18 @@ class ProxiesRoute extends GoRouteData {
|
||||
|
||||
@override
|
||||
Page<void> buildPage(BuildContext context, GoRouterState state) {
|
||||
return const NoTransitionPage(
|
||||
final interceptBackToHome = !PlatformUtils.isDesktop;
|
||||
return NoTransitionPage(
|
||||
name: name,
|
||||
child: ProxiesOverviewPage(),
|
||||
child: PopScope<void>(
|
||||
canPop: !interceptBackToHome,
|
||||
onPopInvokedWithResult: (didPop, result) {
|
||||
if (!didPop && interceptBackToHome) {
|
||||
const HomeRoute().go(context);
|
||||
}
|
||||
},
|
||||
child: const ProxiesOverviewPage(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -335,14 +347,20 @@ class PerAppProxyRoute extends GoRouteData {
|
||||
const PerAppProxyRoute();
|
||||
static const name = "Per-app Proxy";
|
||||
|
||||
static final GlobalKey<NavigatorState> $parentNavigatorKey = rootNavigatorKey;
|
||||
|
||||
@override
|
||||
Page<void> buildPage(BuildContext context, GoRouterState state) {
|
||||
return const MaterialPage(
|
||||
fullscreenDialog: true,
|
||||
final interceptBackToHome = !PlatformUtils.isDesktop;
|
||||
return NoTransitionPage(
|
||||
name: name,
|
||||
child: PerAppProxyPage(),
|
||||
child: PopScope<void>(
|
||||
canPop: !interceptBackToHome,
|
||||
onPopInvokedWithResult: (didPop, result) {
|
||||
if (!didPop && interceptBackToHome) {
|
||||
const HomeRoute().go(context);
|
||||
}
|
||||
},
|
||||
child: const PerAppProxyPage(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'dart:io';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:hiddify/core/telegram_config.dart';
|
||||
import 'package:hiddify/utils/utils.dart';
|
||||
import 'package:umbrix/core/telegram_config.dart';
|
||||
import 'package:umbrix/utils/utils.dart';
|
||||
|
||||
/// Сервис для анонимной отправки логов в Telegram
|
||||
class TelegramLogger with InfraLogger {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hiddify/core/theme/app_theme_mode.dart';
|
||||
import 'package:hiddify/core/theme/theme_extensions.dart';
|
||||
import 'package:umbrix/core/theme/app_theme_mode.dart';
|
||||
import 'package:umbrix/core/theme/theme_extensions.dart';
|
||||
|
||||
class AppTheme {
|
||||
AppTheme(this.mode, this.fontFamily);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hiddify/core/localization/translations.dart';
|
||||
import 'package:umbrix/core/localization/translations.dart';
|
||||
|
||||
enum AppThemeMode {
|
||||
system,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:hiddify/core/preferences/preferences_provider.dart';
|
||||
import 'package:hiddify/core/theme/app_theme_mode.dart';
|
||||
import 'package:umbrix/core/preferences/preferences_provider.dart';
|
||||
import 'package:umbrix/core/theme/app_theme_mode.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'theme_preferences.g.dart';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:fpdart/fpdart.dart';
|
||||
import 'package:hiddify/utils/utils.dart';
|
||||
import 'package:umbrix/utils/utils.dart';
|
||||
import 'package:rxdart/rxdart.dart';
|
||||
|
||||
mixin ExceptionHandler implements LoggerMixin {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:hiddify/core/preferences/preferences_provider.dart';
|
||||
import 'package:hiddify/utils/custom_loggers.dart';
|
||||
import 'package:umbrix/core/preferences/preferences_provider.dart';
|
||||
import 'package:umbrix/utils/custom_loggers.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:fluentui_system_icons/fluentui_system_icons.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hiddify/utils/platform_utils.dart';
|
||||
import 'package:umbrix/utils/platform_utils.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:wolt_modal_sheet/wolt_modal_sheet.dart';
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hiddify/core/model/constants.dart';
|
||||
import 'package:umbrix/core/model/constants.dart';
|
||||
|
||||
class AnimatedText extends Text {
|
||||
const AnimatedText(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hiddify/core/model/failures.dart';
|
||||
import 'package:umbrix/core/model/failures.dart';
|
||||
|
||||
class CustomAlertDialog extends StatelessWidget {
|
||||
const CustomAlertDialog({
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_animate/flutter_animate.dart';
|
||||
import 'package:hiddify/core/widget/skeleton_widget.dart';
|
||||
import 'package:umbrix/core/widget/skeleton_widget.dart';
|
||||
|
||||
class ShimmerSkeleton extends StatelessWidget {
|
||||
const ShimmerSkeleton({
|
||||
|
||||
@@ -2,20 +2,20 @@ import 'package:accessibility_tools/accessibility_tools.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:hiddify/core/localization/locale_extensions.dart';
|
||||
import 'package:hiddify/core/localization/locale_preferences.dart';
|
||||
import 'package:hiddify/core/localization/translations.dart';
|
||||
import 'package:hiddify/core/model/constants.dart';
|
||||
import 'package:hiddify/core/router/router.dart';
|
||||
import 'package:hiddify/core/theme/app_theme.dart';
|
||||
import 'package:hiddify/core/theme/theme_preferences.dart';
|
||||
import 'package:hiddify/features/app_update/notifier/app_update_notifier.dart';
|
||||
import 'package:hiddify/features/connection/widget/connection_wrapper.dart';
|
||||
import 'package:hiddify/features/profile/notifier/profiles_update_notifier.dart';
|
||||
import 'package:hiddify/features/shortcut/shortcut_wrapper.dart';
|
||||
import 'package:hiddify/features/system_tray/widget/system_tray_wrapper.dart';
|
||||
import 'package:hiddify/features/window/widget/window_wrapper.dart';
|
||||
import 'package:hiddify/utils/utils.dart';
|
||||
import 'package:umbrix/core/localization/locale_extensions.dart';
|
||||
import 'package:umbrix/core/localization/locale_preferences.dart';
|
||||
import 'package:umbrix/core/localization/translations.dart';
|
||||
import 'package:umbrix/core/model/constants.dart';
|
||||
import 'package:umbrix/core/router/router.dart';
|
||||
import 'package:umbrix/core/theme/app_theme.dart';
|
||||
import 'package:umbrix/core/theme/theme_preferences.dart';
|
||||
import 'package:umbrix/features/app_update/notifier/app_update_notifier.dart';
|
||||
import 'package:umbrix/features/connection/widget/connection_wrapper.dart';
|
||||
import 'package:umbrix/features/profile/notifier/profiles_update_notifier.dart';
|
||||
import 'package:umbrix/features/shortcut/shortcut_wrapper.dart';
|
||||
import 'package:umbrix/features/system_tray/widget/system_tray_wrapper.dart';
|
||||
import 'package:umbrix/features/window/widget/window_wrapper.dart';
|
||||
import 'package:umbrix/utils/utils.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
|
||||
bool _debugAccessibility = false;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:hiddify/core/http_client/http_client_provider.dart';
|
||||
import 'package:hiddify/features/app_update/data/app_update_repository.dart';
|
||||
import 'package:umbrix/core/http_client/http_client_provider.dart';
|
||||
import 'package:umbrix/features/app_update/data/app_update_repository.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'app_update_data_providers.g.dart';
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import 'package:fpdart/fpdart.dart';
|
||||
import 'package:hiddify/core/http_client/dio_http_client.dart';
|
||||
import 'package:hiddify/core/model/constants.dart';
|
||||
import 'package:hiddify/core/model/environment.dart';
|
||||
import 'package:hiddify/core/utils/exception_handler.dart';
|
||||
import 'package:hiddify/features/app_update/data/github_release_parser.dart';
|
||||
import 'package:hiddify/features/app_update/model/app_update_failure.dart';
|
||||
import 'package:hiddify/features/app_update/model/remote_version_entity.dart';
|
||||
import 'package:hiddify/utils/utils.dart';
|
||||
import 'package:umbrix/core/http_client/dio_http_client.dart';
|
||||
import 'package:umbrix/core/model/constants.dart';
|
||||
import 'package:umbrix/core/model/environment.dart';
|
||||
import 'package:umbrix/core/utils/exception_handler.dart';
|
||||
import 'package:umbrix/features/app_update/data/github_release_parser.dart';
|
||||
import 'package:umbrix/features/app_update/model/app_update_failure.dart';
|
||||
import 'package:umbrix/features/app_update/model/remote_version_entity.dart';
|
||||
import 'package:umbrix/utils/utils.dart';
|
||||
|
||||
abstract interface class AppUpdateRepository {
|
||||
TaskEither<AppUpdateFailure, RemoteVersionEntity> getLatestVersion({
|
||||
@@ -15,9 +15,7 @@ abstract interface class AppUpdateRepository {
|
||||
});
|
||||
}
|
||||
|
||||
class AppUpdateRepositoryImpl
|
||||
with ExceptionHandler, InfraLogger
|
||||
implements AppUpdateRepository {
|
||||
class AppUpdateRepositoryImpl with ExceptionHandler, InfraLogger implements AppUpdateRepository {
|
||||
AppUpdateRepositoryImpl({required this.httpClient});
|
||||
|
||||
final DioHttpClient httpClient;
|
||||
@@ -32,25 +30,86 @@ class AppUpdateRepositoryImpl
|
||||
if (!release.allowCustomUpdateChecker) {
|
||||
throw Exception("custom update checkers are not supported");
|
||||
}
|
||||
final response =
|
||||
await httpClient.get<List>(Constants.githubReleasesApiUrl);
|
||||
if (response.statusCode != 200 || response.data == null) {
|
||||
loggy.warning("failed to fetch latest version info");
|
||||
return left(const AppUpdateFailure.unexpected());
|
||||
}
|
||||
|
||||
final releases = response.data!.map(
|
||||
(e) => GithubReleaseParser.parse(e as Map<String, dynamic>),
|
||||
);
|
||||
late RemoteVersionEntity latest;
|
||||
if (includePreReleases) {
|
||||
latest = releases.first;
|
||||
// Выбираем источник обновлений: собственный сервер или GitHub
|
||||
if (Constants.useCustomUpdateServer) {
|
||||
return _getVersionFromCustomServer(includePreReleases);
|
||||
} else {
|
||||
latest = releases.firstWhere((e) => e.preRelease == false);
|
||||
return _getVersionFromGitHub(includePreReleases);
|
||||
}
|
||||
return right(latest);
|
||||
},
|
||||
AppUpdateFailure.unexpected,
|
||||
);
|
||||
}
|
||||
|
||||
/// Получение версии с собственного сервера обновлений
|
||||
/// Формат ответа:
|
||||
/// {
|
||||
/// "version": "2.5.8",
|
||||
/// "build_number": "258",
|
||||
/// "is_prerelease": false,
|
||||
/// "download_url": "https://your-server.com/downloads/umbrix-2.5.8.apk",
|
||||
/// "release_notes": "Что нового в этой версии",
|
||||
/// "published_at": "2026-01-16T10:00:00Z"
|
||||
/// }
|
||||
Future<Either<AppUpdateFailure, RemoteVersionEntity>> _getVersionFromCustomServer(
|
||||
bool includePreReleases,
|
||||
) async {
|
||||
try {
|
||||
final url = includePreReleases ? "${Constants.customUpdateServerUrl}?include_prerelease=true" : Constants.customUpdateServerUrl;
|
||||
|
||||
final response = await httpClient.get<Map<String, dynamic>>(url);
|
||||
|
||||
if (response.statusCode != 200 || response.data == null) {
|
||||
loggy.warning("failed to fetch version from custom server");
|
||||
return left(const AppUpdateFailure.unexpected());
|
||||
}
|
||||
|
||||
final data = response.data!;
|
||||
final version = RemoteVersionEntity(
|
||||
version: data['version'] as String,
|
||||
buildNumber: data['build_number'] as String,
|
||||
releaseTag: data['version'] as String,
|
||||
preRelease: data['is_prerelease'] as bool? ?? false,
|
||||
url: data['download_url'] as String,
|
||||
publishedAt: DateTime.parse(data['published_at'] as String),
|
||||
flavor: Environment.prod,
|
||||
);
|
||||
|
||||
return right(version);
|
||||
} catch (e, stackTrace) {
|
||||
loggy.warning("error fetching from custom server", e, stackTrace);
|
||||
return left(const AppUpdateFailure.unexpected());
|
||||
}
|
||||
}
|
||||
|
||||
/// Получение версии из GitHub Releases (публичный репозиторий)
|
||||
Future<Either<AppUpdateFailure, RemoteVersionEntity>> _getVersionFromGitHub(
|
||||
bool includePreReleases,
|
||||
) async {
|
||||
try {
|
||||
final response = await httpClient.get<List>(Constants.githubReleasesApiUrl);
|
||||
|
||||
if (response.statusCode != 200 || response.data == null) {
|
||||
loggy.warning("failed to fetch latest version info from GitHub");
|
||||
return left(const AppUpdateFailure.unexpected());
|
||||
}
|
||||
|
||||
final releases = response.data!.map(
|
||||
(e) => GithubReleaseParser.parse(e as Map<String, dynamic>),
|
||||
);
|
||||
|
||||
late RemoteVersionEntity latest;
|
||||
if (includePreReleases) {
|
||||
latest = releases.first;
|
||||
} else {
|
||||
latest = releases.firstWhere((e) => e.preRelease == false);
|
||||
}
|
||||
|
||||
return right(latest);
|
||||
} catch (e, stackTrace) {
|
||||
loggy.warning("error fetching from GitHub", e, stackTrace);
|
||||
return left(const AppUpdateFailure.unexpected());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:dartx/dartx.dart';
|
||||
import 'package:hiddify/core/model/environment.dart';
|
||||
import 'package:hiddify/features/app_update/model/remote_version_entity.dart';
|
||||
import 'package:umbrix/core/model/environment.dart';
|
||||
import 'package:umbrix/features/app_update/model/remote_version_entity.dart';
|
||||
|
||||
abstract class GithubReleaseParser {
|
||||
static RemoteVersionEntity parse(Map<String, dynamic> json) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:hiddify/core/localization/translations.dart';
|
||||
import 'package:hiddify/core/model/failures.dart';
|
||||
import 'package:umbrix/core/localization/translations.dart';
|
||||
import 'package:umbrix/core/model/failures.dart';
|
||||
|
||||
part 'app_update_failure.freezed.dart';
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:hiddify/core/model/environment.dart';
|
||||
import 'package:umbrix/core/model/environment.dart';
|
||||
|
||||
part 'remote_version_entity.freezed.dart';
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:hiddify/core/app_info/app_info_provider.dart';
|
||||
import 'package:hiddify/core/localization/locale_preferences.dart';
|
||||
import 'package:hiddify/core/preferences/preferences_provider.dart';
|
||||
import 'package:hiddify/core/utils/preferences_utils.dart';
|
||||
import 'package:hiddify/features/app_update/data/app_update_data_providers.dart';
|
||||
import 'package:hiddify/features/app_update/model/app_update_failure.dart';
|
||||
import 'package:hiddify/features/app_update/model/remote_version_entity.dart';
|
||||
import 'package:hiddify/features/app_update/notifier/app_update_state.dart';
|
||||
import 'package:hiddify/utils/utils.dart';
|
||||
import 'package:umbrix/core/app_info/app_info_provider.dart';
|
||||
import 'package:umbrix/core/localization/locale_preferences.dart';
|
||||
import 'package:umbrix/core/preferences/preferences_provider.dart';
|
||||
import 'package:umbrix/core/utils/preferences_utils.dart';
|
||||
import 'package:umbrix/features/app_update/data/app_update_data_providers.dart';
|
||||
import 'package:umbrix/features/app_update/model/app_update_failure.dart';
|
||||
import 'package:umbrix/features/app_update/model/remote_version_entity.dart';
|
||||
import 'package:umbrix/features/app_update/notifier/app_update_state.dart';
|
||||
import 'package:umbrix/utils/utils.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:upgrader/upgrader.dart';
|
||||
import 'package:version/version.dart';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:hiddify/features/app_update/model/app_update_failure.dart';
|
||||
import 'package:hiddify/features/app_update/model/remote_version_entity.dart';
|
||||
import 'package:umbrix/features/app_update/model/app_update_failure.dart';
|
||||
import 'package:umbrix/features/app_update/model/remote_version_entity.dart';
|
||||
|
||||
part 'app_update_state.freezed.dart';
|
||||
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
import 'dart:io';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hiddify/core/localization/translations.dart';
|
||||
import 'package:hiddify/features/app_update/model/remote_version_entity.dart';
|
||||
import 'package:hiddify/features/app_update/notifier/app_update_notifier.dart';
|
||||
import 'package:hiddify/utils/utils.dart';
|
||||
import 'package:umbrix/core/localization/translations.dart';
|
||||
import 'package:umbrix/features/app_update/model/remote_version_entity.dart';
|
||||
import 'package:umbrix/features/app_update/notifier/app_update_notifier.dart';
|
||||
import 'package:umbrix/utils/utils.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:open_file/open_file.dart';
|
||||
|
||||
class NewVersionDialog extends HookConsumerWidget with PresLogger {
|
||||
NewVersionDialog(
|
||||
@@ -35,6 +40,54 @@ class NewVersionDialog extends HookConsumerWidget with PresLogger {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final t = ref.watch(translationsProvider);
|
||||
final theme = Theme.of(context);
|
||||
final isDownloading = useState(false);
|
||||
final downloadProgress = useState(0.0);
|
||||
|
||||
Future<void> downloadAndInstallUpdate() async {
|
||||
// Для Android - просто открываем браузер (в production - ссылка на Google Play)
|
||||
if (Platform.isAndroid) {
|
||||
await UriUtils.tryLaunch(Uri.parse(newVersion.url));
|
||||
if (context.mounted) context.pop();
|
||||
return;
|
||||
}
|
||||
|
||||
// Для Desktop (Windows/macOS/Linux) - скачиваем с прогресс-баром
|
||||
try {
|
||||
isDownloading.value = true;
|
||||
downloadProgress.value = 0.0;
|
||||
|
||||
final tempDir = await getTemporaryDirectory();
|
||||
String fileExt = '';
|
||||
if (Platform.isWindows)
|
||||
fileExt = '.exe';
|
||||
else if (Platform.isMacOS)
|
||||
fileExt = '.dmg';
|
||||
else if (Platform.isLinux) fileExt = '.AppImage';
|
||||
|
||||
final savePath = '${tempDir.path}/umbrix-${newVersion.version}$fileExt';
|
||||
final file = File(savePath);
|
||||
if (await file.exists()) await file.delete();
|
||||
|
||||
final dio = Dio();
|
||||
await dio.download(newVersion.url, savePath, onReceiveProgress: (received, total) {
|
||||
if (total != -1) downloadProgress.value = received / total;
|
||||
});
|
||||
|
||||
loggy.info('Update downloaded to: $savePath');
|
||||
final result = await OpenFile.open(savePath);
|
||||
|
||||
if (result.type != ResultType.done && context.mounted) {
|
||||
CustomToast.error('Не удалось открыть: ${result.message}').show(context);
|
||||
} else if (context.mounted) {
|
||||
context.pop();
|
||||
}
|
||||
} catch (e, st) {
|
||||
loggy.error('Download failed', e, st);
|
||||
if (context.mounted) CustomToast.error('Ошибка: $e').show(context);
|
||||
} finally {
|
||||
isDownloading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
return AlertDialog(
|
||||
title: Text(t.appUpdate.dialogTitle),
|
||||
@@ -44,56 +97,35 @@ class NewVersionDialog extends HookConsumerWidget with PresLogger {
|
||||
children: [
|
||||
Text(t.appUpdate.updateMsg),
|
||||
const Gap(8),
|
||||
Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: "${t.appUpdate.currentVersionLbl}: ",
|
||||
style: theme.textTheme.bodySmall,
|
||||
),
|
||||
TextSpan(
|
||||
text: currentVersion,
|
||||
style: theme.textTheme.labelMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: "${t.appUpdate.newVersionLbl}: ",
|
||||
style: theme.textTheme.bodySmall,
|
||||
),
|
||||
TextSpan(
|
||||
text: newVersion.presentVersion,
|
||||
style: theme.textTheme.labelMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Text.rich(TextSpan(children: [
|
||||
TextSpan(text: "${t.appUpdate.currentVersionLbl}: ", style: theme.textTheme.bodySmall),
|
||||
TextSpan(text: currentVersion, style: theme.textTheme.labelMedium),
|
||||
])),
|
||||
Text.rich(TextSpan(children: [
|
||||
TextSpan(text: "${t.appUpdate.newVersionLbl}: ", style: theme.textTheme.bodySmall),
|
||||
TextSpan(text: newVersion.presentVersion, style: theme.textTheme.labelMedium),
|
||||
])),
|
||||
if (isDownloading.value) ...[
|
||||
const Gap(16),
|
||||
LinearProgressIndicator(value: downloadProgress.value),
|
||||
const Gap(8),
|
||||
Text('Скачивание: ${(downloadProgress.value * 100).toStringAsFixed(0)}%', style: theme.textTheme.bodySmall),
|
||||
],
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
if (canIgnore)
|
||||
if (canIgnore && !isDownloading.value)
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
await ref
|
||||
.read(appUpdateNotifierProvider.notifier)
|
||||
.ignoreRelease(newVersion);
|
||||
await ref.read(appUpdateNotifierProvider.notifier).ignoreRelease(newVersion);
|
||||
if (context.mounted) context.pop();
|
||||
},
|
||||
child: Text(t.appUpdate.ignoreBtnTxt),
|
||||
),
|
||||
if (!isDownloading.value) TextButton(onPressed: context.pop, child: Text(t.appUpdate.laterBtnTxt)),
|
||||
TextButton(
|
||||
onPressed: context.pop,
|
||||
child: Text(t.appUpdate.laterBtnTxt),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
await UriUtils.tryLaunch(Uri.parse(newVersion.url));
|
||||
},
|
||||
child: Text(t.appUpdate.updateNowBtnTxt),
|
||||
onPressed: isDownloading.value ? null : downloadAndInstallUpdate,
|
||||
child: Text(isDownloading.value ? 'Скачивание...' : t.appUpdate.updateNowBtnTxt),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:hiddify/core/app_info/app_info_provider.dart';
|
||||
import 'package:hiddify/utils/utils.dart';
|
||||
import 'package:umbrix/core/app_info/app_info_provider.dart';
|
||||
import 'package:umbrix/utils/utils.dart';
|
||||
import 'package:launch_at_startup/launch_at_startup.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import 'package:fluentui_system_icons/fluentui_system_icons.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_adaptive_scaffold/flutter_adaptive_scaffold.dart';
|
||||
import 'package:hiddify/gen/assets.gen.dart';
|
||||
import 'package:hiddify/core/localization/translations.dart';
|
||||
import 'package:hiddify/core/router/router.dart';
|
||||
import 'package:hiddify/features/stats/widget/side_bar_stats_overview.dart';
|
||||
import 'package:hiddify/core/router/routes.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:umbrix/gen/assets.gen.dart';
|
||||
import 'package:umbrix/core/localization/translations.dart';
|
||||
import 'package:umbrix/core/router/router.dart';
|
||||
import 'package:umbrix/features/stats/widget/side_bar_stats_overview.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:hiddify/core/theme/theme_preferences.dart';
|
||||
import 'package:hiddify/core/theme/app_theme_mode.dart';
|
||||
import 'package:hiddify/core/localization/locale_preferences.dart';
|
||||
import 'package:hiddify/core/localization/locale_extensions.dart';
|
||||
import 'package:umbrix/core/theme/theme_preferences.dart';
|
||||
import 'package:umbrix/core/theme/app_theme_mode.dart';
|
||||
import 'package:umbrix/core/localization/locale_preferences.dart';
|
||||
import 'package:umbrix/core/localization/locale_extensions.dart';
|
||||
import 'package:umbrix/utils/utils.dart';
|
||||
|
||||
abstract interface class RootScaffold {
|
||||
static final stateKey = GlobalKey<ScaffoldState>();
|
||||
@@ -27,6 +28,10 @@ class AdaptiveRootScaffold extends HookConsumerWidget {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final t = ref.watch(translationsProvider);
|
||||
|
||||
final interceptBackToHome = !PlatformUtils.isDesktop;
|
||||
final proxiesLocation = const ProxiesRoute().location;
|
||||
final perAppProxyLocation = const PerAppProxyRoute().location;
|
||||
|
||||
final selectedIndex = getCurrentIndex(context);
|
||||
|
||||
final destinations = [
|
||||
@@ -63,8 +68,8 @@ class AdaptiveRootScaffold extends HookConsumerWidget {
|
||||
switchTab(index, context);
|
||||
},
|
||||
destinations: destinations,
|
||||
drawerDestinationRange: useMobileRouter ? (3, null) : (0, null),
|
||||
bottomDestinationRange: (0, 3),
|
||||
drawerDestinationRange: (3, null), // Настройки и О программе всегда в drawer
|
||||
bottomDestinationRange: (0, 3), // Первые 3 пункта в bottom nav
|
||||
useBottomSheet: useMobileRouter,
|
||||
sidebarTrailing: const Expanded(
|
||||
child: Align(
|
||||
@@ -72,7 +77,27 @@ class AdaptiveRootScaffold extends HookConsumerWidget {
|
||||
child: SideBarStatsOverview(),
|
||||
),
|
||||
),
|
||||
body: navigator,
|
||||
body: BackButtonListener(
|
||||
onBackButtonPressed: () async {
|
||||
if (!interceptBackToHome) return false;
|
||||
final location = GoRouterState.of(context).uri.path;
|
||||
final shouldGoHome = location.startsWith(proxiesLocation) || location.startsWith(perAppProxyLocation);
|
||||
|
||||
assert(() {
|
||||
debugPrint(
|
||||
'BACK_INTERCEPT AdaptiveRootScaffold location=$location shouldGoHome=$shouldGoHome',
|
||||
);
|
||||
return true;
|
||||
}());
|
||||
|
||||
if (shouldGoHome) {
|
||||
const HomeRoute().go(context);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
child: navigator,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -111,43 +136,42 @@ class _CustomAdaptiveScaffold extends HookConsumerWidget {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return Scaffold(
|
||||
key: RootScaffold.stateKey,
|
||||
drawer: Breakpoints.small.isActive(context)
|
||||
? Drawer(
|
||||
width: (MediaQuery.sizeOf(context).width * 0.88).clamp(1, 304),
|
||||
drawer: Drawer(
|
||||
width: (MediaQuery.sizeOf(context).width * 0.88).clamp(1, 304),
|
||||
child: Column(
|
||||
children: [
|
||||
// Логотип и название приложения
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 32),
|
||||
child: Column(
|
||||
children: [
|
||||
// Логотип и название приложения
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 32),
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
width: 80,
|
||||
height: 80,
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
color: Theme.of(context).colorScheme.primaryContainer,
|
||||
),
|
||||
child: Assets.images.umbrixLogo.image(
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Umbrix',
|
||||
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
width: 80,
|
||||
height: 80,
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
color: Theme.of(context).colorScheme.primaryContainer,
|
||||
),
|
||||
child: Assets.images.umbrixLogo.image(
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
),
|
||||
// Список пунктов меню
|
||||
Expanded(
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
children: [
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Umbrix',
|
||||
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// Список пунктов меню
|
||||
Expanded(
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
children: [
|
||||
// О программе
|
||||
Builder(
|
||||
builder: (context) {
|
||||
@@ -190,29 +214,11 @@ class _CustomAdaptiveScaffold extends HookConsumerWidget {
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
body: AdaptiveLayout(
|
||||
primaryNavigation: SlotLayout(
|
||||
config: <Breakpoint, SlotLayoutConfig>{
|
||||
Breakpoints.medium: SlotLayout.from(
|
||||
key: const Key('primaryNavigation'),
|
||||
builder: (_) => AdaptiveScaffold.standardNavigationRail(
|
||||
selectedIndex: selectedIndex,
|
||||
destinations: destinations.map((dest) => AdaptiveScaffold.toRailDestination(dest)).toList(),
|
||||
onDestinationSelected: onSelectedIndexChange,
|
||||
),
|
||||
),
|
||||
Breakpoints.large: SlotLayout.from(
|
||||
key: const Key('primaryNavigation1'),
|
||||
builder: (_) => AdaptiveScaffold.standardNavigationRail(
|
||||
extended: true,
|
||||
selectedIndex: selectedIndex,
|
||||
destinations: destinations.map((dest) => AdaptiveScaffold.toRailDestination(dest)).toList(),
|
||||
onDestinationSelected: onSelectedIndexChange,
|
||||
trailing: sidebarTrailing,
|
||||
),
|
||||
),
|
||||
// Убираем боковую навигацию для Desktop
|
||||
},
|
||||
),
|
||||
body: SlotLayout(
|
||||
@@ -226,14 +232,12 @@ class _CustomAdaptiveScaffold extends HookConsumerWidget {
|
||||
},
|
||||
),
|
||||
),
|
||||
// AdaptiveLayout bottom sheet has accessibility issues
|
||||
bottomNavigationBar: useBottomSheet && Breakpoints.small.isActive(context)
|
||||
? NavigationBar(
|
||||
selectedIndex: selectedWithOffset(bottomDestinationRange) ?? 0,
|
||||
destinations: destinationsSlice(bottomDestinationRange),
|
||||
onDestinationSelected: (index) => selectWithOffset(index, bottomDestinationRange),
|
||||
)
|
||||
: null,
|
||||
// Нижняя навигация - первые 3 пункта для всех платформ
|
||||
bottomNavigationBar: NavigationBar(
|
||||
selectedIndex: selectedWithOffset(bottomDestinationRange) ?? 0,
|
||||
destinations: destinationsSlice(bottomDestinationRange),
|
||||
onDestinationSelected: (index) => selectWithOffset(index, bottomDestinationRange),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import 'package:fluentui_system_icons/fluentui_system_icons.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hiddify/core/analytics/analytics_controller.dart';
|
||||
import 'package:hiddify/core/localization/locale_extensions.dart';
|
||||
import 'package:hiddify/core/localization/locale_preferences.dart';
|
||||
import 'package:hiddify/core/localization/translations.dart';
|
||||
import 'package:hiddify/core/model/region.dart';
|
||||
import 'package:hiddify/core/preferences/actions_at_closing.dart';
|
||||
import 'package:hiddify/core/preferences/general_preferences.dart';
|
||||
import 'package:hiddify/core/theme/app_theme_mode.dart';
|
||||
import 'package:hiddify/core/theme/theme_preferences.dart';
|
||||
import 'package:hiddify/features/config_option/data/config_option_repository.dart';
|
||||
import 'package:umbrix/core/analytics/analytics_controller.dart';
|
||||
import 'package:umbrix/core/localization/locale_extensions.dart';
|
||||
import 'package:umbrix/core/localization/locale_preferences.dart';
|
||||
import 'package:umbrix/core/localization/translations.dart';
|
||||
import 'package:umbrix/core/model/region.dart';
|
||||
import 'package:umbrix/core/preferences/actions_at_closing.dart';
|
||||
import 'package:umbrix/core/preferences/general_preferences.dart';
|
||||
import 'package:umbrix/core/theme/app_theme_mode.dart';
|
||||
import 'package:umbrix/core/theme/theme_preferences.dart';
|
||||
import 'package:umbrix/features/config_option/data/config_option_repository.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
|
||||
class LocalePrefTile extends ConsumerWidget {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hiddify/core/router/router.dart';
|
||||
import 'package:hiddify/features/common/adaptive_root_scaffold.dart';
|
||||
import 'package:hiddify/utils/utils.dart';
|
||||
import 'package:umbrix/core/router/router.dart';
|
||||
import 'package:umbrix/features/common/adaptive_root_scaffold.dart';
|
||||
import 'package:umbrix/utils/utils.dart';
|
||||
|
||||
bool showDrawerButton(BuildContext context) {
|
||||
if (!useMobileRouter) return true;
|
||||
|
||||
@@ -4,8 +4,8 @@ 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:umbrix/core/localization/translations.dart';
|
||||
import 'package:umbrix/utils/utils.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:mobile_scanner/mobile_scanner.dart';
|
||||
// import 'package:permission_handler/permission_handler.dart';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:hiddify/core/preferences/preferences_provider.dart';
|
||||
import 'package:hiddify/features/config_option/data/config_option_repository.dart';
|
||||
import 'package:umbrix/core/preferences/preferences_provider.dart';
|
||||
import 'package:umbrix/features/config_option/data/config_option_repository.dart';
|
||||
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import 'package:dartx/dartx.dart';
|
||||
import 'package:fpdart/fpdart.dart';
|
||||
import 'package:hiddify/core/model/optional_range.dart';
|
||||
import 'package:hiddify/core/model/region.dart';
|
||||
import 'package:hiddify/core/preferences/general_preferences.dart';
|
||||
import 'package:hiddify/core/utils/exception_handler.dart';
|
||||
import 'package:hiddify/core/utils/json_converters.dart';
|
||||
import 'package:hiddify/core/utils/preferences_utils.dart';
|
||||
import 'package:hiddify/features/config_option/model/config_option_failure.dart';
|
||||
import 'package:umbrix/core/model/optional_range.dart';
|
||||
import 'package:umbrix/core/model/region.dart';
|
||||
import 'package:umbrix/core/preferences/general_preferences.dart';
|
||||
import 'package:umbrix/core/utils/exception_handler.dart';
|
||||
import 'package:umbrix/core/utils/json_converters.dart';
|
||||
import 'package:umbrix/core/utils/preferences_utils.dart';
|
||||
import 'package:umbrix/features/config_option/model/config_option_failure.dart';
|
||||
|
||||
import 'package:hiddify/features/log/model/log_level.dart';
|
||||
import 'package:hiddify/singbox/model/singbox_config_enum.dart';
|
||||
import 'package:hiddify/singbox/model/singbox_config_option.dart';
|
||||
import 'package:hiddify/singbox/model/singbox_rule.dart';
|
||||
import 'package:hiddify/utils/utils.dart';
|
||||
import 'package:umbrix/features/log/model/log_level.dart';
|
||||
import 'package:umbrix/singbox/model/singbox_config_enum.dart';
|
||||
import 'package:umbrix/singbox/model/singbox_config_option.dart';
|
||||
import 'package:umbrix/singbox/model/singbox_rule.dart';
|
||||
import 'package:umbrix/utils/utils.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:hiddify/core/localization/translations.dart';
|
||||
import 'package:hiddify/core/model/failures.dart';
|
||||
import 'package:umbrix/core/localization/translations.dart';
|
||||
import 'package:umbrix/core/model/failures.dart';
|
||||
|
||||
part 'config_option_failure.freezed.dart';
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:hiddify/features/config_option/data/config_option_repository.dart';
|
||||
import 'package:hiddify/features/connection/data/connection_data_providers.dart';
|
||||
import 'package:hiddify/features/connection/notifier/connection_notifier.dart';
|
||||
import 'package:hiddify/utils/custom_loggers.dart';
|
||||
import 'package:umbrix/features/config_option/data/config_option_repository.dart';
|
||||
import 'package:umbrix/features/connection/data/connection_data_providers.dart';
|
||||
import 'package:umbrix/features/connection/notifier/connection_notifier.dart';
|
||||
import 'package:umbrix/utils/custom_loggers.dart';
|
||||
import 'package:json_path/json_path.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:hiddify/core/preferences/preferences_provider.dart';
|
||||
import 'package:hiddify/features/config_option/data/config_option_repository.dart';
|
||||
import 'package:hiddify/features/config_option/model/config_option_failure.dart';
|
||||
import 'package:hiddify/singbox/service/singbox_service_provider.dart';
|
||||
import 'package:hiddify/utils/utils.dart';
|
||||
import 'package:umbrix/core/preferences/preferences_provider.dart';
|
||||
import 'package:umbrix/features/config_option/data/config_option_repository.dart';
|
||||
import 'package:umbrix/features/config_option/model/config_option_failure.dart';
|
||||
import 'package:umbrix/singbox/service/singbox_service_provider.dart';
|
||||
import 'package:umbrix/utils/utils.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import 'package:dartx/dartx.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:hiddify/core/localization/translations.dart';
|
||||
import 'package:hiddify/core/notification/in_app_notification_controller.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';
|
||||
import 'package:hiddify/features/common/nested_app_bar.dart';
|
||||
import 'package:hiddify/features/config_option/data/config_option_repository.dart';
|
||||
import 'package:hiddify/features/config_option/notifier/config_option_notifier.dart';
|
||||
import 'package:hiddify/features/config_option/widget/preference_tile.dart';
|
||||
import 'package:hiddify/features/settings/widgets/sections_widgets.dart';
|
||||
import 'package:hiddify/features/settings/widgets/settings_input_dialog.dart';
|
||||
import 'package:hiddify/singbox/model/singbox_config_enum.dart';
|
||||
import 'package:hiddify/utils/utils.dart';
|
||||
import 'package:umbrix/core/localization/translations.dart';
|
||||
import 'package:umbrix/core/notification/in_app_notification_controller.dart';
|
||||
import 'package:umbrix/core/widget/adaptive_icon.dart';
|
||||
import 'package:umbrix/core/widget/tip_card.dart';
|
||||
import 'package:umbrix/features/common/confirmation_dialogs.dart';
|
||||
import 'package:umbrix/features/common/nested_app_bar.dart';
|
||||
import 'package:umbrix/features/config_option/data/config_option_repository.dart';
|
||||
import 'package:umbrix/features/config_option/notifier/config_option_notifier.dart';
|
||||
import 'package:umbrix/features/config_option/widget/preference_tile.dart';
|
||||
import 'package:umbrix/features/settings/widgets/sections_widgets.dart';
|
||||
import 'package:umbrix/features/settings/widgets/settings_input_dialog.dart';
|
||||
import 'package:umbrix/singbox/model/singbox_config_enum.dart';
|
||||
import 'package:umbrix/utils/utils.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:humanizer/humanizer.dart';
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hiddify/core/localization/translations.dart';
|
||||
import 'package:hiddify/core/model/constants.dart';
|
||||
import 'package:hiddify/core/model/optional_range.dart';
|
||||
import 'package:hiddify/core/widget/custom_alert_dialog.dart';
|
||||
import 'package:hiddify/features/config_option/data/config_option_repository.dart';
|
||||
import 'package:hiddify/features/config_option/notifier/warp_option_notifier.dart';
|
||||
import 'package:hiddify/features/config_option/widget/preference_tile.dart';
|
||||
import 'package:hiddify/singbox/model/singbox_config_enum.dart';
|
||||
import 'package:hiddify/utils/uri_utils.dart';
|
||||
import 'package:hiddify/utils/validators.dart';
|
||||
import 'package:umbrix/core/localization/translations.dart';
|
||||
import 'package:umbrix/core/model/constants.dart';
|
||||
import 'package:umbrix/core/model/optional_range.dart';
|
||||
import 'package:umbrix/core/widget/custom_alert_dialog.dart';
|
||||
import 'package:umbrix/features/config_option/data/config_option_repository.dart';
|
||||
import 'package:umbrix/features/config_option/notifier/warp_option_notifier.dart';
|
||||
import 'package:umbrix/features/config_option/widget/preference_tile.dart';
|
||||
import 'package:umbrix/singbox/model/singbox_config_enum.dart';
|
||||
import 'package:umbrix/utils/uri_utils.dart';
|
||||
import 'package:umbrix/utils/validators.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
|
||||
class WarpOptionsTiles extends HookConsumerWidget {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hiddify/core/utils/preferences_utils.dart';
|
||||
import 'package:hiddify/features/settings/widgets/widgets.dart';
|
||||
import 'package:umbrix/core/utils/preferences_utils.dart';
|
||||
import 'package:umbrix/features/settings/widgets/widgets.dart';
|
||||
|
||||
class ValuePreferenceWidget<T> extends StatelessWidget {
|
||||
const ValuePreferenceWidget({
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import 'package:fluentui_system_icons/fluentui_system_icons.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:hiddify/core/localization/translations.dart';
|
||||
import 'package:hiddify/core/router/router.dart';
|
||||
import 'package:hiddify/features/config_option/data/config_option_repository.dart';
|
||||
import 'package:hiddify/features/config_option/notifier/warp_option_notifier.dart';
|
||||
import 'package:hiddify/features/settings/experimental_features_page.dart';
|
||||
import 'package:hiddify/singbox/model/singbox_config_enum.dart';
|
||||
import 'package:umbrix/core/localization/translations.dart';
|
||||
import 'package:umbrix/core/router/router.dart';
|
||||
import 'package:umbrix/features/config_option/data/config_option_repository.dart';
|
||||
import 'package:umbrix/features/config_option/notifier/warp_option_notifier.dart';
|
||||
import 'package:umbrix/features/settings/experimental_features_page.dart';
|
||||
import 'package:umbrix/singbox/model/singbox_config_enum.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
|
||||
class QuickSettingsModal extends HookConsumerWidget {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import 'package:hiddify/core/directories/directories_provider.dart';
|
||||
import 'package:hiddify/features/config_option/data/config_option_data_providers.dart';
|
||||
import 'package:hiddify/features/connection/data/connection_platform_source.dart';
|
||||
import 'package:hiddify/features/connection/data/connection_repository.dart';
|
||||
import 'package:umbrix/core/directories/directories_provider.dart';
|
||||
import 'package:umbrix/features/config_option/data/config_option_data_providers.dart';
|
||||
import 'package:umbrix/features/connection/data/connection_platform_source.dart';
|
||||
import 'package:umbrix/features/connection/data/connection_repository.dart';
|
||||
|
||||
import 'package:hiddify/features/profile/data/profile_data_providers.dart';
|
||||
import 'package:hiddify/singbox/service/singbox_service_provider.dart';
|
||||
import 'package:umbrix/features/profile/data/profile_data_providers.dart';
|
||||
import 'package:umbrix/singbox/service/singbox_service_provider.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'connection_data_providers.g.dart';
|
||||
|
||||
@@ -2,9 +2,9 @@ import 'dart:async';
|
||||
import 'dart:ffi';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:hiddify/core/utils/ffi_utils.dart';
|
||||
import 'package:hiddify/utils/custom_loggers.dart';
|
||||
import 'package:hiddify/utils/utils.dart';
|
||||
import 'package:umbrix/core/utils/ffi_utils.dart';
|
||||
import 'package:umbrix/utils/custom_loggers.dart';
|
||||
import 'package:umbrix/utils/utils.dart';
|
||||
import 'package:posix/posix.dart';
|
||||
import 'package:win32/win32.dart';
|
||||
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import 'package:fpdart/fpdart.dart';
|
||||
import 'package:hiddify/core/model/directories.dart';
|
||||
import 'package:hiddify/core/utils/exception_handler.dart';
|
||||
import 'package:hiddify/features/config_option/data/config_option_repository.dart';
|
||||
import 'package:hiddify/features/connection/data/connection_platform_source.dart';
|
||||
import 'package:hiddify/features/connection/model/connection_failure.dart';
|
||||
import 'package:hiddify/features/connection/model/connection_status.dart';
|
||||
import 'package:umbrix/core/model/directories.dart';
|
||||
import 'package:umbrix/core/utils/exception_handler.dart';
|
||||
import 'package:umbrix/features/config_option/data/config_option_repository.dart';
|
||||
import 'package:umbrix/features/connection/data/connection_platform_source.dart';
|
||||
import 'package:umbrix/features/connection/model/connection_failure.dart';
|
||||
import 'package:umbrix/features/connection/model/connection_status.dart';
|
||||
|
||||
import 'package:hiddify/features/profile/data/profile_path_resolver.dart';
|
||||
import 'package:hiddify/singbox/model/singbox_config_option.dart';
|
||||
import 'package:hiddify/singbox/model/singbox_status.dart';
|
||||
import 'package:hiddify/singbox/service/singbox_service.dart';
|
||||
import 'package:hiddify/utils/utils.dart';
|
||||
import 'package:umbrix/features/profile/data/profile_path_resolver.dart';
|
||||
import 'package:umbrix/singbox/model/singbox_config_option.dart';
|
||||
import 'package:umbrix/singbox/model/singbox_status.dart';
|
||||
import 'package:umbrix/singbox/service/singbox_service.dart';
|
||||
import 'package:umbrix/utils/utils.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
abstract interface class ConnectionRepository {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:hiddify/core/localization/translations.dart';
|
||||
import 'package:hiddify/core/model/failures.dart';
|
||||
import 'package:umbrix/core/localization/translations.dart';
|
||||
import 'package:umbrix/core/model/failures.dart';
|
||||
|
||||
part 'connection_failure.freezed.dart';
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:hiddify/core/localization/translations.dart';
|
||||
import 'package:hiddify/features/connection/model/connection_failure.dart';
|
||||
import 'package:umbrix/core/localization/translations.dart';
|
||||
import 'package:umbrix/features/connection/model/connection_failure.dart';
|
||||
|
||||
part 'connection_status.freezed.dart';
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:hiddify/core/haptic/haptic_service.dart';
|
||||
import 'package:hiddify/core/preferences/general_preferences.dart';
|
||||
import 'package:hiddify/features/connection/data/connection_data_providers.dart';
|
||||
import 'package:hiddify/features/connection/data/connection_repository.dart';
|
||||
import 'package:hiddify/features/connection/model/connection_status.dart';
|
||||
import 'package:hiddify/features/profile/model/profile_entity.dart';
|
||||
import 'package:hiddify/features/profile/notifier/active_profile_notifier.dart';
|
||||
import 'package:hiddify/utils/utils.dart';
|
||||
import 'package:umbrix/core/haptic/haptic_service.dart';
|
||||
import 'package:umbrix/core/preferences/general_preferences.dart';
|
||||
import 'package:umbrix/features/connection/data/connection_data_providers.dart';
|
||||
import 'package:umbrix/features/connection/data/connection_repository.dart';
|
||||
import 'package:umbrix/features/connection/model/connection_status.dart';
|
||||
import 'package:umbrix/features/profile/model/profile_entity.dart';
|
||||
import 'package:umbrix/features/profile/notifier/active_profile_notifier.dart';
|
||||
import 'package:umbrix/utils/utils.dart';
|
||||
import 'package:in_app_review/in_app_review.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:rxdart/rxdart.dart';
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hiddify/core/localization/translations.dart';
|
||||
import 'package:hiddify/core/notification/in_app_notification_controller.dart';
|
||||
import 'package:hiddify/features/config_option/notifier/config_option_notifier.dart';
|
||||
import 'package:hiddify/features/connection/notifier/connection_notifier.dart';
|
||||
import 'package:hiddify/features/profile/notifier/active_profile_notifier.dart';
|
||||
import 'package:hiddify/utils/custom_loggers.dart';
|
||||
import 'package:umbrix/core/localization/translations.dart';
|
||||
import 'package:umbrix/core/notification/in_app_notification_controller.dart';
|
||||
import 'package:umbrix/features/config_option/notifier/config_option_notifier.dart';
|
||||
import 'package:umbrix/features/connection/notifier/connection_notifier.dart';
|
||||
import 'package:umbrix/features/profile/notifier/active_profile_notifier.dart';
|
||||
import 'package:umbrix/utils/custom_loggers.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
|
||||
class ConnectionWrapper extends StatefulHookConsumerWidget {
|
||||
|
||||
@@ -2,9 +2,9 @@ import 'package:fluentui_system_icons/fluentui_system_icons.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:hiddify/core/localization/translations.dart';
|
||||
import 'package:hiddify/core/router/routes.dart';
|
||||
import 'package:hiddify/core/utils/preferences_utils.dart';
|
||||
import 'package:umbrix/core/localization/translations.dart';
|
||||
import 'package:umbrix/core/router/routes.dart';
|
||||
import 'package:umbrix/core/utils/preferences_utils.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
|
||||
bool _testExperimentalNotice = false;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:hiddify/utils/utils.dart';
|
||||
import 'package:umbrix/utils/utils.dart';
|
||||
import 'package:protocol_handler/protocol_handler.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:hiddify/core/database/app_database.dart';
|
||||
import 'package:hiddify/features/geo_asset/model/geo_asset_entity.dart';
|
||||
import 'package:umbrix/core/database/app_database.dart';
|
||||
import 'package:umbrix/features/geo_asset/model/geo_asset_entity.dart';
|
||||
|
||||
extension GeoAssetEntityMapper on GeoAssetEntity {
|
||||
GeoAssetEntriesCompanion toEntry() {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// import 'package:hiddify/core/database/database_provider.dart';
|
||||
// import 'package:hiddify/core/directories/directories_provider.dart';
|
||||
// import 'package:hiddify/core/http_client/http_client_provider.dart';
|
||||
// import 'package:hiddify/features/geo_asset/data/geo_asset_data_source.dart';
|
||||
// import 'package:hiddify/features/geo_asset/data/geo_asset_path_resolver.dart';
|
||||
// import 'package:hiddify/features/geo_asset/data/geo_asset_repository.dart';
|
||||
// import 'package:umbrix/core/database/database_provider.dart';
|
||||
// import 'package:umbrix/core/directories/directories_provider.dart';
|
||||
// import 'package:umbrix/core/http_client/http_client_provider.dart';
|
||||
// import 'package:umbrix/features/geo_asset/data/geo_asset_data_source.dart';
|
||||
// import 'package:umbrix/features/geo_asset/data/geo_asset_path_resolver.dart';
|
||||
// import 'package:umbrix/features/geo_asset/data/geo_asset_repository.dart';
|
||||
// import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
// part 'geo_asset_data_providers.g.dart';
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:hiddify/core/database/app_database.dart';
|
||||
import 'package:hiddify/core/database/tables/database_tables.dart';
|
||||
import 'package:hiddify/features/geo_asset/model/geo_asset_entity.dart';
|
||||
import 'package:hiddify/utils/custom_loggers.dart';
|
||||
import 'package:umbrix/core/database/app_database.dart';
|
||||
import 'package:umbrix/core/database/tables/database_tables.dart';
|
||||
import 'package:umbrix/features/geo_asset/model/geo_asset_entity.dart';
|
||||
import 'package:umbrix/utils/custom_loggers.dart';
|
||||
|
||||
part 'geo_asset_data_source.g.dart';
|
||||
|
||||
|
||||
@@ -4,17 +4,17 @@
|
||||
// import 'package:drift/drift.dart';
|
||||
// import 'package:flutter/services.dart';
|
||||
// import 'package:fpdart/fpdart.dart';
|
||||
// import 'package:hiddify/core/database/app_database.dart';
|
||||
// import 'package:hiddify/core/http_client/dio_http_client.dart';
|
||||
// import 'package:hiddify/core/utils/exception_handler.dart';
|
||||
// import 'package:hiddify/features/geo_asset/data/geo_asset_data_mapper.dart';
|
||||
// import 'package:hiddify/features/geo_asset/data/geo_asset_data_source.dart';
|
||||
// import 'package:hiddify/features/geo_asset/data/geo_asset_path_resolver.dart';
|
||||
// import 'package:hiddify/features/geo_asset/model/default_geo_assets.dart';
|
||||
// import 'package:hiddify/features/geo_asset/model/geo_asset_entity.dart';
|
||||
// import 'package:hiddify/features/geo_asset/model/geo_asset_failure.dart';
|
||||
// import 'package:hiddify/gen/assets.gen.dart';
|
||||
// import 'package:hiddify/utils/custom_loggers.dart';
|
||||
// import 'package:umbrix/core/database/app_database.dart';
|
||||
// import 'package:umbrix/core/http_client/dio_http_client.dart';
|
||||
// import 'package:umbrix/core/utils/exception_handler.dart';
|
||||
// import 'package:umbrix/features/geo_asset/data/geo_asset_data_mapper.dart';
|
||||
// import 'package:umbrix/features/geo_asset/data/geo_asset_data_source.dart';
|
||||
// import 'package:umbrix/features/geo_asset/data/geo_asset_path_resolver.dart';
|
||||
// import 'package:umbrix/features/geo_asset/model/default_geo_assets.dart';
|
||||
// import 'package:umbrix/features/geo_asset/model/geo_asset_entity.dart';
|
||||
// import 'package:umbrix/features/geo_asset/model/geo_asset_failure.dart';
|
||||
// import 'package:umbrix/gen/assets.gen.dart';
|
||||
// import 'package:umbrix/utils/custom_loggers.dart';
|
||||
// import 'package:rxdart/rxdart.dart';
|
||||
// import 'package:watcher/watcher.dart';
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user