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:
Umbrix Developer
2026-01-17 13:09:20 +03:00
parent ec5ebbd54b
commit 76a374950f
245 changed files with 7931 additions and 1315 deletions

1
.gitignore vendored
View File

@@ -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
View 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
View 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
View 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
View 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
```

View File

@@ -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
View 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
View 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

View File

@@ -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

View File

@@ -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)
}
}
}

View File

@@ -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)
}
}

View File

@@ -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)
}
}

View File

@@ -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": "انقر للاتصال",

View File

@@ -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": "بۆ پەیوەندیکردن بکوتە",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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": "برای اتصال ضربه بزنید",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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- Ссылкой на подписку на SingBox\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Ядро приложения основано на открытом исходном коде SingBox.\n\nОписание разрешений:\n- СЛУЖБА VPN: поскольку целью данного приложения является предоставление безопасного, удобного и эффективного клиента туннелирования, это разрешение необходимо, чтобы иметь возможность направлять трафик через туннель на удалённый сервер.\n- ЗАПРОС ВСЕХ ПАКЕТОВ: это разрешение позволяет добавлять или удалять определённые приложения из списка для туннелирования.\n- ИНФОРМИРОВАНИЕ О ЗАВЕРШЕНИИ ЗАГРУЗКИ: это разрешение можно включить или отключить в настройках приложения, чтобы (де)активировать запуск приложения при загрузке устройства.\n- ПОСТОЯННОЕ УВЕДОМЛЕНИЕ: это разрешение необходимо, так как используется приоритетная служба для обеспечения непрерывной работы VPN.\n- Приложение не содержит рекламы. Сбор аналитики и данных о сбоях происходят только с явного согласия пользователя при первом использовании приложения."
"full_description": "Основная цель Umbrix — предоставить безопасный, удобный и эффективный клиент туннелирования. Он позволяет направлять весь трафик или трафик выбранного приложения на указанный Вами удалённый сервер.\n\nПримечание: мы не предоставляем серверы, пользователи должны сами обеспечивать конфиденциальность своих действий в Интернете, используя собственный сервер или доверенные серверы. \nПоддерживаются сервера с:\n- Обычной ссылкой на подписку V2ray/Xray\n- Ссылкой на подписку Clash\n- Ссылкой на подписку на SingBox\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Ядро приложения основано на открытом исходном коде SingBox.\n\nОписание разрешений:\n- СЛУЖБА VPN: поскольку целью данного приложения является предоставление безопасного, удобного и эффективного клиента туннелирования, это разрешение необходимо, чтобы иметь возможность направлять трафик через туннель на удалённый сервер.\n- ЗАПРОС ВСЕХ ПАКЕТОВ: это разрешение позволяет добавлять или удалять определённые приложения из списка для туннелирования.\n- ИНФОРМИРОВАНИЕ О ЗАВЕРШЕНИИ ЗАГРУЗКИ: это разрешение можно включить или отключить в настройках приложения, чтобы (де)активировать запуск приложения при загрузке устройства.\n- ПОСТОЯННОЕ УВЕДОМЛЕНИЕ: это разрешение необходимо, так как используется приоритетная служба для обеспечения непрерывной работы VPN.\n- Приложение не содержит рекламы. Сбор аналитики и данных о сбоях происходят только с явного согласия пользователя при первом использовании приложения."
},
"connection": {
"tapToConnect": "Нажмите для подключения",

View File

@@ -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",

View File

@@ -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
View 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

View 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 пакет

View File

@@ -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";

View File

@@ -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>

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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() {

View File

@@ -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';

View File

@@ -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 {

View File

@@ -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,

View File

@@ -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';

View File

@@ -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 = {};

View File

@@ -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';

View File

@@ -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);

View File

@@ -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';

View File

@@ -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';

View File

@@ -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 {

View File

@@ -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';

View File

@@ -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);

View File

@@ -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});

View File

@@ -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';

View File

@@ -1,4 +1,4 @@
import 'package:hiddify/core/localization/translations.dart';
import 'package:umbrix/core/localization/translations.dart';
enum Region {
ir,

View File

@@ -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';

View File

@@ -1,4 +1,4 @@
import 'package:hiddify/gen/translations.g.dart';
import 'package:umbrix/gen/translations.g.dart';
enum ActionsAtClosing {
ask,

View File

@@ -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';

View File

@@ -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 {

View File

@@ -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;
}

View File

@@ -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(),
),
);
}
}

View File

@@ -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 {

View File

@@ -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);

View File

@@ -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,

View File

@@ -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';

View File

@@ -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 {

View File

@@ -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';

View File

@@ -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';

View File

@@ -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(

View File

@@ -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({

View File

@@ -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({

View File

@@ -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;

View File

@@ -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';

View File

@@ -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());
}
}
}

View File

@@ -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) {

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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),
),
],
);

View File

@@ -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';

View File

@@ -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),
),
);
}
}

View File

@@ -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 {

View File

@@ -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;

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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 {

View File

@@ -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({

View File

@@ -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 {

View File

@@ -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';

View File

@@ -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';

View File

@@ -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 {

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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 {

View File

@@ -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;

View File

@@ -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';

View File

@@ -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() {

View File

@@ -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';

View File

@@ -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';

View File

@@ -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