From 545b5e0d680c42e70d356bf0544d8e49ca331fce Mon Sep 17 00:00:00 2001 From: root Date: Thu, 2 Oct 2025 17:09:36 +0000 Subject: [PATCH] v0.0.2 --- CI_CD_README.md | 304 ------------- DEVELOPMENT_SUMMARY.md | 408 ------------------ MERGE_REQUEST_DESCRIPTION.md | 201 --------- android/torrentengine/LICENSE | 201 +++++++++ android/torrentengine/README.md | 78 +--- .../neomovies/torrentengine/TorrentEngine.kt | 8 +- lib/data/api/api_client.dart | 358 +++------------ 7 files changed, 278 insertions(+), 1280 deletions(-) delete mode 100644 CI_CD_README.md delete mode 100644 DEVELOPMENT_SUMMARY.md delete mode 100644 MERGE_REQUEST_DESCRIPTION.md create mode 100644 android/torrentengine/LICENSE diff --git a/CI_CD_README.md b/CI_CD_README.md deleted file mode 100644 index a61c8ca..0000000 --- a/CI_CD_README.md +++ /dev/null @@ -1,304 +0,0 @@ -# 🚀 CI/CD Configuration для NeoMovies Mobile - -## 📋 Обзор - -Автоматическая сборка APK и TorrentEngine модуля с оптимизацией использования RAM. - ---- - -## 🏗️ Конфигурации - -### 1. **GitLab CI/CD** (`.gitlab-ci.yml`) - -Основная конфигурация для GitLab: - -#### **Stages:** -- **build** - Сборка APK и AAR -- **test** - Анализ кода и тесты -- **deploy** - Публикация релизов - -#### **Jobs:** - -| Job | Описание | Артефакты | Ветки | -|-----|----------|-----------|-------| -| `build:torrent-engine` | Сборка TorrentEngine AAR | `*.aar` | dev, feature/*, MR | -| `build:apk-debug` | Сборка Debug APK | `app-debug.apk` | dev, feature/*, MR | -| `build:apk-release` | Сборка Release APK | `app-arm64-v8a-release.apk` | только dev | -| `test:flutter-analyze` | Анализ Dart кода | - | dev, MR | -| `test:android-lint` | Android Lint | HTML отчеты | dev, MR | -| `deploy:release` | Публикация релиза | - | только tags (manual) | - -### 2. **GitHub Actions** (`.github/workflows/build.yml`) - -Альтернативная конфигурация для GitHub: - -#### **Workflows:** - -| Workflow | Триггер | Описание | -|----------|---------|----------| -| `build-torrent-engine` | push, PR | Сборка AAR модуля | -| `build-debug-apk` | push, PR | Debug APK для тестирования | -| `build-release-apk` | push to dev | Release APK (split-per-abi) | -| `code-quality` | push, PR | Flutter analyze + Android Lint | - ---- - -## ⚙️ Оптимизация RAM - -### **gradle.properties** - -```properties -# Уменьшено с 4GB до 2GB -org.gradle.jvmargs=-Xmx2G -XX:MaxMetaspaceSize=1G - -# Kotlin daemon с ограничением -kotlin.daemon.jvmargs=-Xmx1G -XX:MaxMetaspaceSize=512m - -# Включены оптимизации -org.gradle.parallel=true -org.gradle.caching=true -org.gradle.configureondemand=true -``` - -### **CI переменные** - -```bash -# В CI используется еще меньше RAM -GRADLE_OPTS="-Xmx1536m -XX:MaxMetaspaceSize=512m" -``` - ---- - -## 📦 Артефакты - -### **TorrentEngine AAR:** -- Путь: `android/torrentengine/build/outputs/aar/` -- Файл: `torrentengine-release.aar` -- Срок хранения: 7 дней -- Размер: ~5-10 MB - -### **Debug APK:** -- Путь: `build/app/outputs/flutter-apk/` -- Файл: `app-debug.apk` -- Срок хранения: 7 дней -- Размер: ~50-80 MB - -### **Release APK:** -- Путь: `build/app/outputs/flutter-apk/` -- Файл: `app-arm64-v8a-release.apk` -- Срок хранения: 30 дней -- Размер: ~30-50 MB (split-per-abi) - ---- - -## 🚦 Триггеры сборки - -### **GitLab:** - -**Автоматически запускается при:** -- Push в `dev` ветку -- Push в `feature/torrent-engine-integration` -- Создание Merge Request -- Push тега (для deploy) - -**Ручной запуск:** -- Web UI → Pipelines → Run Pipeline -- Выбрать ветку и нажать "Run pipeline" - -### **GitHub:** - -**Автоматически запускается при:** -- Push в `dev` или `feature/torrent-engine-integration` -- Pull Request в `dev` - -**Ручной запуск:** -- Actions → Build NeoMovies Mobile → Run workflow - ---- - -## 🔧 Настройка GitLab Instance Runners - -### **Рекомендуется: Использовать GitLab Instance Runners (SaaS)** - -GitLab предоставляет 112+ бесплатных shared runners для всех проектов! - -**Как включить:** - -1. Перейдите в **Settings → CI/CD → Runners** -2. Найдите секцию **"Instance runners"** -3. Нажмите **"Enable instance runners for this project"** -4. Готово! ✅ - -**Доступные теги для Instance Runners:** - -| Тег | RAM | CPU | Описание | -|-----|-----|-----|----------| -| `saas-linux-small-amd64` | 2 GB | 1 core | Легкие задачи | -| `saas-linux-medium-amd64` | 4 GB | 2 cores | **Рекомендуется для Android** | -| `saas-linux-large-amd64` | 8 GB | 4 cores | Тяжелые сборки | -| `docker` | varies | varies | Любой Docker runner | - -**Наша конфигурация использует:** -- TorrentEngine: `saas-linux-medium-amd64` (4GB, 2 cores) -- Остальные jobs: `docker` (автоматический выбор) - ---- - -### **Альтернатива: Локальный Runner (не требуется)** - -Только если нужна кастомная конфигурация: - -```bash -# 1. Установка GitLab Runner -curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh | sudo bash -sudo apt-get install gitlab-runner - -# 2. Регистрация Runner -sudo gitlab-runner register \ - --url https://gitlab.com/ \ - --registration-token YOUR_TOKEN \ - --executor docker \ - --docker-image mingc/android-build-box:latest \ - --tag-list docker,android - -# 3. Запуск -sudo gitlab-runner start -``` - ---- - -## 📊 Время сборки (примерно) - -| Job | Время | RAM | CPU | -|-----|-------|-----|-----| -| TorrentEngine | ~5-10 мин | 1.5GB | 2 cores | -| Debug APK | ~15-20 мин | 2GB | 2 cores | -| Release APK | ~20-30 мин | 2GB | 2 cores | -| Flutter Analyze | ~2-3 мин | 512MB | 1 core | -| Android Lint | ~5-8 мин | 1GB | 2 cores | - ---- - -## 🐳 Docker образы - -### **mingc/android-build-box:latest** - -Включает: -- Android SDK (latest) -- Flutter SDK -- Java 17 -- Gradle -- Git, curl, wget - -Размер: ~8GB - ---- - -## 🔍 Кэширование - -Для ускорения сборок используется кэширование: - -```yaml -cache: - paths: - - .gradle/ # Gradle dependencies - - .pub-cache/ # Flutter packages - - android/.gradle/ # Android build cache - - build/ # Flutter build cache -``` - -**Эффект:** -- Первая сборка: ~25 минут -- Последующие: ~10-15 минут (с кэшем) - ---- - -## 📝 Логи и отладка - -### **Просмотр логов GitLab:** - -1. Перейти в **CI/CD → Pipelines** -2. Выбрать pipeline -3. Кликнуть на job для просмотра логов - -### **Отладка локально:** - -```bash -# Тестирование сборки TorrentEngine -cd android -./gradlew :torrentengine:assembleRelease \ - --no-daemon \ - --parallel \ - --stacktrace - -# Тестирование Flutter APK -flutter build apk --debug --verbose -``` - ---- - -## 🚨 Troubleshooting - -### **Gradle daemon crashed:** - -**Проблема:** `Gradle build daemon disappeared unexpectedly` - -**Решение:** -```bash -# Увеличить RAM в gradle.properties -org.gradle.jvmargs=-Xmx3G - -# Или отключить daemon -./gradlew --no-daemon -``` - -### **Out of memory:** - -**Проблема:** `OutOfMemoryError: Java heap space` - -**Решение:** -```bash -# Увеличить heap в CI -GRADLE_OPTS="-Xmx2048m -XX:MaxMetaspaceSize=768m" -``` - -### **LibTorrent4j native libraries not found:** - -**Проблема:** Нативные библиотеки не найдены - -**Решение:** -- Убедиться что все архитектуры включены в `build.gradle.kts` -- Проверить `splits.abi` конфигурацию - ---- - -## 📚 Дополнительные ресурсы - -- [GitLab CI/CD Docs](https://docs.gitlab.com/ee/ci/) -- [GitHub Actions Docs](https://docs.github.com/actions) -- [Flutter CI/CD Guide](https://docs.flutter.dev/deployment/cd) -- [Gradle Performance](https://docs.gradle.org/current/userguide/performance.html) - ---- - -## 🎯 Следующие шаги - -1. **Настроить GitLab Runner** (если еще не настроен) -2. **Запушить изменения** в dev ветку -3. **Проверить Pipeline** в GitLab CI/CD -4. **Скачать артефакты** после успешной сборки -5. **Протестировать APK** на реальном устройстве - ---- - -## 📞 Поддержка - -При проблемах с CI/CD: -1. Проверьте логи pipeline -2. Убедитесь что Runner активен -3. Проверьте доступность Docker образа -4. Создайте issue с логами ошибки - ---- - -**Создано с ❤️ для NeoMovies Mobile** diff --git a/DEVELOPMENT_SUMMARY.md b/DEVELOPMENT_SUMMARY.md deleted file mode 100644 index d387325..0000000 --- a/DEVELOPMENT_SUMMARY.md +++ /dev/null @@ -1,408 +0,0 @@ -# 📝 Development Summary - NeoMovies Mobile - -## 🎯 Выполненные задачи - -### 1. ⚡ Торрент Движок (TorrentEngine Library) - -Создана **полноценная библиотека для работы с торрентами** как отдельный модуль Android: - -#### 📦 Структура модуля: -``` -android/torrentengine/ -├── build.gradle.kts # Конфигурация с LibTorrent4j -├── proguard-rules.pro # ProGuard правила -├── consumer-rules.pro # Consumer ProGuard rules -├── README.md # Подробная документация -└── src/main/ - ├── AndroidManifest.xml # Permissions и Service - └── java/com/neomovies/torrentengine/ - ├── TorrentEngine.kt # Главный API класс - ├── models/ - │ └── TorrentInfo.kt # Модели данных (TorrentInfo, TorrentFile, etc.) - ├── database/ - │ ├── TorrentDao.kt # Room DAO - │ ├── TorrentDatabase.kt - │ └── Converters.kt # Type converters - └── service/ - └── TorrentService.kt # Foreground service -``` - -#### ✨ Возможности TorrentEngine: - -1. **Загрузка из magnet-ссылок** - - Автоматическое получение метаданных - - Парсинг файлов и их размеров - - Поддержка DHT и LSD - -2. **Управление файлами** - - Выбор файлов ДО начала загрузки - - Изменение приоритетов В ПРОЦЕССЕ загрузки - - Фильтрация по типу (видео, аудио и т.д.) - - 5 уровней приоритета: DONT_DOWNLOAD, LOW, NORMAL, HIGH, MAXIMUM - -3. **Foreground Service с уведомлением** - - Постоянное уведомление (не удаляется пока активны торренты) - - Отображение скорости загрузки/отдачи - - Список активных торрентов с прогрессом - - Кнопки управления (Pause All) - -4. **Персистентность (Room Database)** - - Автоматическое сохранение состояния - - Восстановление торрентов после перезагрузки - - Реактивные Flow для мониторинга изменений - -5. **Полная статистика** - - Скорость загрузки/отдачи (real-time) - - Количество пиров и сидов - - Прогресс загрузки (%) - - ETA (время до завершения) - - Share ratio (отдано/скачано) - -6. **Контроль раздач** - - `addTorrent()` - добавить торрент - - `pauseTorrent()` - поставить на паузу - - `resumeTorrent()` - возобновить - - `removeTorrent()` - удалить (с файлами или без) - - `setFilePriority()` - изменить приоритет файла - - `setFilePriorities()` - массовое изменение приоритетов - -#### 📚 Использование: - -```kotlin -// Инициализация -val torrentEngine = TorrentEngine.getInstance(context) -torrentEngine.startStatsUpdater() - -// Добавление торрента -val infoHash = torrentEngine.addTorrent(magnetUri, savePath) - -// Мониторинг (реактивно) -torrentEngine.getAllTorrentsFlow().collect { torrents -> - torrents.forEach { torrent -> - println("${torrent.name}: ${torrent.progress * 100}%") - } -} - -// Изменение приоритетов файлов -torrent.files.forEachIndexed { index, file -> - if (file.isVideo()) { - torrentEngine.setFilePriority(infoHash, index, FilePriority.HIGH) - } -} - -// Управление -torrentEngine.pauseTorrent(infoHash) -torrentEngine.resumeTorrent(infoHash) -torrentEngine.removeTorrent(infoHash, deleteFiles = true) -``` - -### 2. 🔄 Новый API Client (NeoMoviesApiClient) - -Полностью переписан API клиент для работы с **новым Go-based бэкендом (neomovies-api)**: - -#### 📍 Файл: `lib/data/api/neomovies_api_client.dart` - -#### 🆕 Новые возможности: - -**Аутентификация:** -- ✅ `register()` - регистрация с отправкой кода на email -- ✅ `verifyEmail()` - подтверждение email кодом -- ✅ `resendVerificationCode()` - повторная отправка кода -- ✅ `login()` - вход по email/password -- ✅ `getGoogleOAuthUrl()` - URL для Google OAuth -- ✅ `refreshToken()` - обновление JWT токена -- ✅ `getProfile()` - получение профиля -- ✅ `deleteAccount()` - удаление аккаунта - -**Фильмы:** -- ✅ `getPopularMovies()` - популярные фильмы -- ✅ `getTopRatedMovies()` - топ рейтинг -- ✅ `getUpcomingMovies()` - скоро выйдут -- ✅ `getNowPlayingMovies()` - сейчас в кино -- ✅ `getMovieById()` - детали фильма -- ✅ `getMovieRecommendations()` - рекомендации -- ✅ `searchMovies()` - поиск фильмов - -**Сериалы:** -- ✅ `getPopularTvShows()` - популярные сериалы -- ✅ `getTopRatedTvShows()` - топ сериалы -- ✅ `getTvShowById()` - детали сериала -- ✅ `getTvShowRecommendations()` - рекомендации -- ✅ `searchTvShows()` - поиск сериалов - -**Избранное:** -- ✅ `getFavorites()` - список избранного -- ✅ `addFavorite()` - добавить в избранное -- ✅ `removeFavorite()` - удалить из избранного - -**Реакции (новое!):** -- ✅ `getReactionCounts()` - количество лайков/дизлайков -- ✅ `setReaction()` - поставить like/dislike -- ✅ `getMyReactions()` - мои реакции - -**Торренты (новое!):** -- ✅ `searchTorrents()` - поиск торрентов через RedAPI - - По IMDb ID - - Фильтры: quality, season, episode - - Поддержка фильмов и сериалов - -**Плееры (новое!):** -- ✅ `getAllohaPlayer()` - Alloha embed URL -- ✅ `getLumexPlayer()` - Lumex embed URL -- ✅ `getVibixPlayer()` - Vibix embed URL - -#### 🔧 Пример использования: - -```dart -final apiClient = NeoMoviesApiClient(http.Client()); - -// Регистрация с email verification -await apiClient.register( - email: 'user@example.com', - password: 'password123', - name: 'John Doe', -); - -// Подтверждение кода -final authResponse = await apiClient.verifyEmail( - email: 'user@example.com', - code: '123456', -); - -// Поиск торрентов -final torrents = await apiClient.searchTorrents( - imdbId: 'tt1234567', - type: 'movie', - quality: '1080p', -); - -// Получить плеер -final player = await apiClient.getAllohaPlayer('tt1234567'); -``` - -### 3. 📊 Новые модели данных - -Созданы модели для новых фич: - -#### `PlayerResponse` (`lib/data/models/player/player_response.dart`): -```dart -class PlayerResponse { - final String? embedUrl; - final String? playerType; - final String? error; -} -``` - -### 4. 📖 Документация - -Создана подробная документация: -- **`android/torrentengine/README.md`** - полное руководство по TorrentEngine - - Описание всех возможностей - - Примеры использования - - API reference - - Интеграция с Flutter - - Известные проблемы - ---- - -## 🚀 Что готово к использованию - -### ✅ TorrentEngine Library -- Полностью функциональный торрент движок -- Можно использовать как отдельную библиотеку -- Готов к интеграции с Flutter через MethodChannel -- Все основные функции реализованы - -### ✅ NeoMoviesApiClient -- Полная поддержка нового API -- Все endpoints реализованы -- Готов к замене старого ApiClient - -### ✅ База для дальнейшей разработки -- Структура модуля torrentengine создана -- Build конфигурация готова -- ProGuard правила настроены -- Permissions объявлены - ---- - -## 📋 Следующие шаги - -### 1. Интеграция TorrentEngine с Flutter - -Создать MethodChannel в `MainActivity.kt`: - -```kotlin -class MainActivity: FlutterActivity() { - private val TORRENT_CHANNEL = "com.neomovies/torrent" - - override fun configureFlutterEngine(flutterEngine: FlutterEngine) { - super.configureFlutterEngine(flutterEngine) - - val torrentEngine = TorrentEngine.getInstance(applicationContext) - - MethodChannel(flutterEngine.dartExecutor.binaryMessenger, TORRENT_CHANNEL) - .setMethodCallHandler { call, result -> - when (call.method) { - "addTorrent" -> { - val magnetUri = call.argument("magnetUri")!! - val savePath = call.argument("savePath")!! - - CoroutineScope(Dispatchers.IO).launch { - try { - val hash = torrentEngine.addTorrent(magnetUri, savePath) - withContext(Dispatchers.Main) { - result.success(hash) - } - } catch (e: Exception) { - withContext(Dispatchers.Main) { - result.error("ERROR", e.message, null) - } - } - } - } - "getTorrents" -> { - CoroutineScope(Dispatchers.IO).launch { - try { - val torrents = torrentEngine.getAllTorrents() - val torrentsJson = torrents.map { /* convert to map */ } - withContext(Dispatchers.Main) { - result.success(torrentsJson) - } - } catch (e: Exception) { - withContext(Dispatchers.Main) { - result.error("ERROR", e.message, null) - } - } - } - } - // ... другие методы - } - } - } -} -``` - -Создать Dart wrapper: - -```dart -class TorrentEngineService { - static const platform = MethodChannel('com.neomovies/torrent'); - - Future addTorrent(String magnetUri, String savePath) async { - return await platform.invokeMethod('addTorrent', { - 'magnetUri': magnetUri, - 'savePath': savePath, - }); - } - - Future>> getTorrents() async { - final List result = await platform.invokeMethod('getTorrents'); - return result.cast>(); - } -} -``` - -### 2. Замена старого API клиента - -В файлах сервисов и репозиториев заменить: -```dart -// Старое -final apiClient = ApiClient(http.Client()); - -// Новое -final apiClient = NeoMoviesApiClient(http.Client()); -``` - -### 3. Создание UI для новых фич - -**Email Verification Screen:** -- Ввод кода подтверждения -- Кнопка "Отправить код повторно" -- Таймер обратного отсчета - -**Torrent List Screen:** -- Список активных торрентов -- Прогресс бар для каждого -- Скорость загрузки/отдачи -- Кнопки pause/resume/delete - -**File Selection Screen:** -- Список файлов в торренте -- Checkbox для выбора файлов -- Slider для приоритета -- Отображение размера файлов - -**Player Selection Screen:** -- Выбор плеера (Alloha/Lumex/Vibix) -- WebView для отображения плеера - -**Reactions UI:** -- Кнопки like/dislike -- Счетчики реакций -- Анимации при клике - -### 4. Тестирование - -1. **Компиляция проекта:** - ```bash - cd neomovies_mobile - flutter pub get - flutter build apk --debug - ``` - -2. **Тестирование TorrentEngine:** - - Добавление magnet-ссылки - - Получение метаданных - - Выбор файлов - - Изменение приоритетов в процессе загрузки - - Проверка уведомления - - Pause/Resume/Delete - -3. **Тестирование API:** - - Регистрация и email verification - - Логин - - Поиск торрентов - - Получение плееров - - Реакции - ---- - -## 💡 Преимущества нового решения - -### TorrentEngine: -✅ Отдельная библиотека - можно использовать в других проектах -✅ LibTorrent4j - надежный и производительный -✅ Foreground service - стабильная работа в фоне -✅ Room database - надежное хранение состояния -✅ Flow API - реактивные обновления UI -✅ Полный контроль - все функции доступны - -### NeoMoviesApiClient: -✅ Go backend - в 3x быстрее Node.js -✅ Меньше потребление памяти - 50% экономия -✅ Email verification - безопасная регистрация -✅ Google OAuth - удобный вход -✅ Торрент поиск - интеграция с RedAPI -✅ Множество плееров - выбор для пользователя -✅ Реакции - вовлечение пользователей - ---- - -## 🎉 Итоги - -**Создано:** -- ✅ Полноценная библиотека TorrentEngine (700+ строк кода) -- ✅ Новый API клиент NeoMoviesApiClient (450+ строк) -- ✅ Модели данных для новых фич -- ✅ Подробная документация -- ✅ ProGuard правила -- ✅ Готовая структура для интеграции - -**Готово к:** -- ⚡ Компиляции и тестированию -- 📱 Интеграции с Flutter -- 🚀 Деплою в production - -**Следующий шаг:** -Интеграция TorrentEngine с Flutter через MethodChannel и создание UI для торрент менеджера. diff --git a/MERGE_REQUEST_DESCRIPTION.md b/MERGE_REQUEST_DESCRIPTION.md deleted file mode 100644 index 6ff5b0e..0000000 --- a/MERGE_REQUEST_DESCRIPTION.md +++ /dev/null @@ -1,201 +0,0 @@ -# 🚀 Add TorrentEngine Library and New API Client - -## 📝 Описание - -Полная реализация торрент движка на Kotlin с использованием LibTorrent4j и интеграция с Flutter приложением через MethodChannel. Также добавлен новый API клиент для работы с обновленным Go-based бэкендом. - ---- - -## ✨ Новые возможности - -### 1. **TorrentEngine Library** (Kotlin) - -Полноценный торрент движок как отдельный модуль Android: - -#### 🎯 **Основные функции:** -- ✅ Загрузка из magnet-ссылок с автоматическим извлечением метаданных -- ✅ Выбор файлов ДО и ВО ВРЕМЯ загрузки -- ✅ Управление приоритетами файлов (5 уровней: DONT_DOWNLOAD → MAXIMUM) -- ✅ Foreground Service с постоянным уведомлением -- ✅ Room Database для персистентности состояния -- ✅ Реактивные Flow API для мониторинга изменений -- ✅ Полная статистика (скорость, пиры, сиды, прогресс, ETA) -- ✅ Pause/Resume/Remove с опциональным удалением файлов - -#### 📦 **Структура модуля:** -``` -android/torrentengine/ -├── TorrentEngine.kt # Главный API класс (500+ строк) -├── TorrentService.kt # Foreground service с уведомлением -├── models/TorrentInfo.kt # Модели данных -├── database/ # Room DAO и Database -│ ├── TorrentDao.kt -│ ├── TorrentDatabase.kt -│ └── Converters.kt -├── build.gradle.kts # LibTorrent4j dependencies -├── AndroidManifest.xml # Permissions и Service -├── README.md # Полная документация -└── proguard-rules.pro # ProGuard правила -``` - -#### 🔧 **Использование:** -```kotlin -val engine = TorrentEngine.getInstance(context) -val hash = engine.addTorrent(magnetUri, savePath) -engine.setFilePriority(hash, fileIndex, FilePriority.HIGH) -engine.pauseTorrent(hash) -engine.resumeTorrent(hash) -engine.removeTorrent(hash, deleteFiles = true) -``` - -### 2. **MethodChannel Integration** (Kotlin ↔ Flutter) - -Полная интеграция TorrentEngine с Flutter через MethodChannel в `MainActivity.kt`: - -#### 📡 **Доступные методы:** -- `addTorrent(magnetUri, savePath)` → infoHash -- `getTorrents()` → List (JSON) -- `getTorrent(infoHash)` → TorrentInfo (JSON) -- `pauseTorrent(infoHash)` → success -- `resumeTorrent(infoHash)` → success -- `removeTorrent(infoHash, deleteFiles)` → success -- `setFilePriority(infoHash, fileIndex, priority)` → success - -### 3. **NeoMoviesApiClient** (Dart) - -Новый API клиент для работы с Go-based бэкендом: - -#### 🆕 **Новые endpoints:** - -**Аутентификация:** -- Email verification flow (register → verify → login) -- Google OAuth URL -- Token refresh - -**Торренты:** -- Поиск через RedAPI по IMDb ID -- Фильтры по качеству, сезону, эпизоду - -**Плееры:** -- Alloha, Lumex, Vibix embed URLs - -**Реакции:** -- Лайки/дизлайки -- Счетчики реакций -- Мои реакции - ---- - -## 🔄 Измененные файлы - -### Android: -- `android/settings.gradle.kts` - добавлен модуль `:torrentengine` -- `android/app/build.gradle.kts` - обновлены зависимости, Java 17 -- `android/app/src/main/kotlin/.../MainActivity.kt` - интеграция TorrentEngine - -### Flutter: -- `pubspec.yaml` - исправлен конфликт `build_runner` -- `lib/data/api/neomovies_api_client.dart` - новый API клиент (450+ строк) -- `lib/data/models/player/player_response.dart` - модель ответа плеера - -### Документация: -- `android/torrentengine/README.md` - подробная документация по TorrentEngine -- `DEVELOPMENT_SUMMARY.md` - полный отчет о проделанной работе - ---- - -## 🏗️ Технические детали - -### Зависимости: - -**TorrentEngine:** -- LibTorrent4j 2.1.0-28 (arm64, arm, x86, x86_64) -- Room 2.6.1 -- Kotlin Coroutines 1.9.0 -- Gson 2.11.0 - -**App:** -- Обновлен Java до версии 17 -- Обновлены AndroidX библиотеки -- Исправлен конфликт build_runner (2.4.13) - -### Permissions: -- INTERNET, ACCESS_NETWORK_STATE -- WRITE/READ_EXTERNAL_STORAGE -- MANAGE_EXTERNAL_STORAGE (Android 11+) -- FOREGROUND_SERVICE, FOREGROUND_SERVICE_DATA_SYNC -- POST_NOTIFICATIONS -- WAKE_LOCK - ---- - -## ✅ Что работает - -✅ **Структура TorrentEngine модуля создана** -✅ **LibTorrent4j интегрирован** -✅ **Room database настроена** -✅ **Foreground Service реализован** -✅ **MethodChannel для Flutter готов** -✅ **Новый API клиент написан** -✅ **Все файлы закоммичены и запушены** - ---- - -## 📋 Следующие шаги - -### Для полного завершения требуется: - -1. **Сборка APK** - необходима более мощная среда для полной компиляции с LibTorrent4j -2. **Flutter интеграция** - создать Dart wrapper для MethodChannel -3. **UI для торрентов** - экраны списка торрентов, выбора файлов -4. **Тестирование** - проверка работы на реальном устройстве - -### Дополнительно: -- Исправить ошибки анализатора Dart (отсутствующие модели плеера) -- Сгенерировать код для `player_response.g.dart` -- Добавить модель `TorrentItem` для API клиента - ---- - -## 📊 Статистика - -- **Создано файлов:** 16 -- **Изменено файлов:** 4 -- **Добавлено строк кода:** ~2700+ -- **Kotlin код:** ~1500 строк -- **Dart код:** ~500 строк -- **Документация:** ~700 строк - ---- - -## 🎉 Итоги - -Создана **полноценная библиотека для работы с торрентами**, которая: -- Может использоваться как отдельный модуль в любых Android проектах -- Предоставляет все необходимые функции для торрент-клиента -- Интегрирована с Flutter через MethodChannel -- Имеет подробную документацию с примерами - -Также создан **новый API клиент** для работы с обновленным бэкендом с поддержкой новых фич: -- Email verification -- Google OAuth -- Torrent search -- Multiple players -- Reactions system - ---- - -## 🔗 Ссылки - -- **Branch:** `feature/torrent-engine-integration` -- **Commit:** 1b28c5d -- **Документация:** `android/torrentengine/README.md` -- **Отчет:** `DEVELOPMENT_SUMMARY.md` - ---- - -## 👤 Author - -**Droid (Factory AI Assistant)** - -Создано с использованием LibTorrent4j, Room, Kotlin Coroutines, и Flutter MethodChannel. diff --git a/android/torrentengine/LICENSE b/android/torrentengine/LICENSE new file mode 100644 index 0000000..a0a0753 --- /dev/null +++ b/android/torrentengine/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2025 NeoMovies + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/android/torrentengine/README.md b/android/torrentengine/README.md index 6e17195..380dfb3 100644 --- a/android/torrentengine/README.md +++ b/android/torrentengine/README.md @@ -1,20 +1,8 @@ # TorrentEngine Library -Мощная библиотека для Android, обеспечивающая полноценную работу с торрентами через LibTorrent4j. +Либа для моего клиента и других независимых проектов где нужен простой торрент движок. -## 🎯 Возможности - -- ✅ **Загрузка из magnet-ссылок** - получение метаданных и загрузка файлов -- ✅ **Выбор файлов** - возможность выбирать какие файлы загружать до и во время загрузки -- ✅ **Управление приоритетами** - изменение приоритета файлов в активной раздаче -- ✅ **Фоновый сервис** - непрерывная работа в фоне с foreground уведомлением -- ✅ **Постоянное уведомление** - нельзя закрыть пока активны загрузки -- ✅ **Персистентность** - сохранение состояния в Room database -- ✅ **Реактивность** - Flow API для мониторинга изменений -- ✅ **Полная статистика** - скорость, пиры, сиды, прогресс, ETA -- ✅ **Pause/Resume/Remove** - полный контроль над раздачами - -## 📦 Установка +## Установка ### 1. Добавьте модуль в `settings.gradle.kts`: @@ -38,7 +26,7 @@ dependencies { ``` -## 🚀 Использование +## Использование ### Инициализация @@ -127,7 +115,7 @@ lifecycleScope.launch { } ``` -## 📊 Модели данных +## Модели данных ### TorrentInfo @@ -180,7 +168,7 @@ enum class FilePriority(val value: Int) { } ``` -## 🔔 Foreground Service +## Foreground Service Сервис автоматически запускается при добавлении торрента и показывает постоянное уведомление с: - Количеством активных торрентов @@ -190,12 +178,10 @@ enum class FilePriority(val value: Int) { Уведомление **нельзя закрыть** пока есть активные торренты. -## 💾 Персистентность +## Персистентность Все торренты сохраняются в Room database и автоматически восстанавливаются при перезапуске приложения. -## 🔧 Расширенные возможности - ### Проверка видео файлов ```kotlin @@ -215,54 +201,6 @@ val selectedCount = torrent.getSelectedFilesCount() val selectedSize = torrent.getSelectedSize() ``` -## 📱 Интеграция с Flutter +[Apache License 2.0](LICENSE). -Создайте MethodChannel для вызова из Flutter: - -```kotlin -class TorrentEngineChannel(private val context: Context) { - private val torrentEngine = TorrentEngine.getInstance(context) - private val channel = "com.neomovies/torrent" - - fun setupMethodChannel(flutterEngine: FlutterEngine) { - MethodChannel(flutterEngine.dartExecutor.binaryMessenger, channel) - .setMethodCallHandler { call, result -> - when (call.method) { - "addTorrent" -> { - val magnetUri = call.argument("magnetUri")!! - val savePath = call.argument("savePath")!! - - CoroutineScope(Dispatchers.IO).launch { - try { - val hash = torrentEngine.addTorrent(magnetUri, savePath) - result.success(hash) - } catch (e: Exception) { - result.error("ERROR", e.message, null) - } - } - } - // ... другие методы - } - } - } -} -``` - -## 📄 Лицензия - -MIT License - используйте свободно в любых проектах! - -## 🤝 Вклад - -Библиотека разработана как универсальное решение для работы с торрентами в Android. -Может использоваться в любых проектах без ограничений. - -## 🐛 Известные проблемы - -- LibTorrent4j требует минимум Android 5.0 (API 21) -- Для Android 13+ нужно запрашивать POST_NOTIFICATIONS permission -- Foreground service требует отображения уведомления - -## 📞 Поддержка - -При возникновении проблем создайте issue с описанием и логами. +Made with <3 by Erno/Foxix \ No newline at end of file diff --git a/android/torrentengine/src/main/java/com/neomovies/torrentengine/TorrentEngine.kt b/android/torrentengine/src/main/java/com/neomovies/torrentengine/TorrentEngine.kt index 166fed9..2393575 100644 --- a/android/torrentengine/src/main/java/com/neomovies/torrentengine/TorrentEngine.kt +++ b/android/torrentengine/src/main/java/com/neomovies/torrentengine/TorrentEngine.kt @@ -16,13 +16,7 @@ import java.io.File /** * Main TorrentEngine class - the core of the torrent library - * This is the main API that applications should use - * - * Usage: - * ``` - * val engine = TorrentEngine.getInstance(context) - * engine.addTorrent(magnetUri, savePath) - * ``` + * This is the main API that applications should use. */ class TorrentEngine private constructor(private val context: Context) { private val TAG = "TorrentEngine" diff --git a/lib/data/api/api_client.dart b/lib/data/api/api_client.dart index 3be723d..366c603 100644 --- a/lib/data/api/api_client.dart +++ b/lib/data/api/api_client.dart @@ -1,332 +1,110 @@ -import 'dart:convert'; -import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:http/http.dart' as http; -import 'package:neomovies_mobile/data/models/auth_response.dart'; -import 'package:neomovies_mobile/data/models/favorite.dart'; import 'package:neomovies_mobile/data/models/movie.dart'; +import 'package:neomovies_mobile/data/models/favorite.dart'; import 'package:neomovies_mobile/data/models/reaction.dart'; +import 'package:neomovies_mobile/data/models/auth_response.dart'; import 'package:neomovies_mobile/data/models/user.dart'; +import 'package:neomovies_mobile/data/api/neomovies_api_client.dart'; // новый клиент class ApiClient { - final http.Client _client; - final String _baseUrl = dotenv.env['API_URL']!; + final NeoMoviesApiClient _neoClient; - ApiClient(this._client); + ApiClient(http.Client client) + : _neoClient = NeoMoviesApiClient(client); - Future> getPopularMovies({int page = 1}) async { - return _fetchMovies('/movies/popular', page: page); + // ---- Movies ---- + Future> getPopularMovies({int page = 1}) { + return _neoClient.getPopularMovies(page: page); } - Future> getTopRatedMovies({int page = 1}) async { - return _fetchMovies('/movies/top-rated', page: page); + Future> getTopRatedMovies({int page = 1}) { + return _neoClient.getTopRatedMovies(page: page); } - Future> getUpcomingMovies({int page = 1}) async { - return _fetchMovies('/movies/upcoming', page: page); + Future> getUpcomingMovies({int page = 1}) { + return _neoClient.getUpcomingMovies(page: page); } - Future getMovieById(String id) async { - return _fetchMovieDetail('/movies/$id'); + Future getMovieById(String id) { + return _neoClient.getMovieById(id); } - Future getTvById(String id) async { - return _fetchMovieDetail('/tv/$id'); + Future getTvById(String id) { + return _neoClient.getTvShowById(id); } - // Получение IMDB ID для фильмов - Future getMovieImdbId(int movieId) async { - try { - final uri = Uri.parse('$_baseUrl/movies/$movieId/external-ids'); - final response = await _client.get(uri).timeout(const Duration(seconds: 30)); - - if (response.statusCode == 200) { - final data = json.decode(response.body); - return data['imdb_id'] as String?; - } else { - print('Failed to get movie IMDB ID: ${response.statusCode}'); - return null; - } - } catch (e) { - print('Error getting movie IMDB ID: $e'); - return null; - } + // ---- Search ---- + Future> searchMovies(String query, {int page = 1}) { + return _neoClient.search(query, page: page); } - // Получение IMDB ID для сериалов - Future getTvImdbId(int showId) async { - try { - final uri = Uri.parse('$_baseUrl/tv/$showId/external-ids'); - final response = await _client.get(uri).timeout(const Duration(seconds: 30)); - - if (response.statusCode == 200) { - final data = json.decode(response.body); - return data['imdb_id'] as String?; - } else { - print('Failed to get TV IMDB ID: ${response.statusCode}'); - return null; - } - } catch (e) { - print('Error getting TV IMDB ID: $e'); - return null; - } + // ---- Favorites ---- + Future> getFavorites() { + return _neoClient.getFavorites(); } - // Универсальный метод получения IMDB ID - Future getImdbId(int mediaId, String mediaType) async { - if (mediaType == 'tv') { - return getTvImdbId(mediaId); - } else { - return getMovieImdbId(mediaId); - } - } - - Future> searchMovies(String query, {int page = 1}) async { - final moviesUri = Uri.parse('$_baseUrl/movies/search?query=${Uri.encodeQueryComponent(query)}&page=$page'); - final tvUri = Uri.parse('$_baseUrl/tv/search?query=${Uri.encodeQueryComponent(query)}&page=$page'); - - final responses = await Future.wait([ - _client.get(moviesUri), - _client.get(tvUri), - ]); - - List combined = []; - - for (final response in responses) { - if (response.statusCode == 200) { - final decoded = json.decode(response.body); - List listData; - if (decoded is List) { - listData = decoded; - } else if (decoded is Map && decoded['results'] is List) { - listData = decoded['results']; - } else { - listData = []; - } - combined.addAll(listData.map((json) => Movie.fromJson(json))); - } else { - // ignore non-200 but log maybe - } - } - - if (combined.isEmpty) { - throw Exception('Failed to search movies/tv'); - } - return combined; - } - - Future _fetchMovieDetail(String path) async { - final uri = Uri.parse('$_baseUrl$path'); - final response = await _client.get(uri); - - if (response.statusCode == 200) { - final data = json.decode(response.body); - return Movie.fromJson(data); - } else { - throw Exception('Failed to load media details: ${response.statusCode}'); - } - } - - // Favorites - Future> getFavorites() async { - final response = await _client.get(Uri.parse('$_baseUrl/favorites')); - - if (response.statusCode == 200) { - final List data = json.decode(response.body); - return data.map((json) => Favorite.fromJson(json)).toList(); - } else { - throw Exception('Failed to fetch favorites'); - } - } - - Future addFavorite(String mediaId, String mediaType, String title, String posterPath) async { - final response = await _client.post( - Uri.parse('$_baseUrl/favorites/$mediaId?mediaType=$mediaType'), - body: json.encode({ - 'title': title, - 'posterPath': posterPath, - }), + Future addFavorite( + String mediaId, + String mediaType, + String title, + String posterPath, + ) { + return _neoClient.addFavorite( + mediaId: mediaId, + mediaType: mediaType, + title: title, + posterPath: posterPath, ); - - if (response.statusCode != 201 && response.statusCode != 200) { - throw Exception('Failed to add favorite'); - } } - Future removeFavorite(String mediaId) async { - final response = await _client.delete( - Uri.parse('$_baseUrl/favorites/$mediaId'), + Future removeFavorite(String mediaId) { + return _neoClient.removeFavorite(mediaId); + } + + // ---- Reactions ---- + Future> getReactionCounts( + String mediaType, String mediaId) { + return _neoClient.getReactionCounts( + mediaType: mediaType, + mediaId: mediaId, ); - - if (response.statusCode != 200) { - throw Exception('Failed to remove favorite'); - } } - // Reactions - Future> getReactionCounts(String mediaType, String mediaId) async { - final response = await _client.get( - Uri.parse('$_baseUrl/reactions/$mediaType/$mediaId/counts'), + Future setReaction( + String mediaType, String mediaId, String reactionType) { + return _neoClient.setReaction( + mediaType: mediaType, + mediaId: mediaId, + reactionType: reactionType, ); - - print('REACTION COUNTS RESPONSE (${response.statusCode}): ${response.body}'); - - if (response.statusCode == 200) { - final decoded = json.decode(response.body); - print('PARSED: $decoded'); - - if (decoded is Map) { - final mapSrc = decoded.containsKey('data') && decoded['data'] is Map - ? decoded['data'] as Map - : decoded; - - print('MAPPING: $mapSrc'); - return mapSrc.map((k, v) { - int count; - if (v is num) { - count = v.toInt(); - } else if (v is String) { - count = int.tryParse(v) ?? 0; - } else { - count = 0; - } - return MapEntry(k, count); - }); - } - if (decoded is List) { - // list of {type,count} - Map res = {}; - for (var item in decoded) { - if (item is Map && item['type'] != null) { - res[item['type'].toString()] = (item['count'] as num?)?.toInt() ?? 0; - } - } - return res; - } - return {}; - } else { - throw Exception('Failed to fetch reactions counts'); - } } - Future getMyReaction(String mediaType, String mediaId) async { - final response = await _client.get( - Uri.parse('$_baseUrl/reactions/$mediaType/$mediaId/my-reaction'), - ); - - if (response.statusCode == 200) { - final decoded = json.decode(response.body); - if (decoded == null || (decoded is String && decoded.isEmpty)) { - return UserReaction(reactionType: null); - } - return UserReaction.fromJson(decoded as Map); - } else if (response.statusCode == 404) { - return UserReaction(reactionType: 'none'); // No reaction found - } else { - throw Exception('Failed to fetch user reaction'); - } + Future> getMyReactions() { + return _neoClient.getMyReactions(); } - Future setReaction(String mediaType, String mediaId, String reactionType) async { - final response = await _client.post( - Uri.parse('$_baseUrl/reactions'), - headers: {'Content-Type': 'application/json'}, - body: json.encode({'mediaId': '${mediaType}_${mediaId}', 'type': reactionType}), - ); - - if (response.statusCode != 201 && response.statusCode != 200 && response.statusCode != 204) { - throw Exception('Failed to set reaction: ${response.statusCode} ${response.body}'); - } + // ---- Auth ---- + Future register(String name, String email, String password) { + return _neoClient.register( + name: name, + email: email, + password: password, + ).then((_) {}); // старый код ничего не возвращал } - // --- Auth Methods --- - - Future register(String name, String email, String password) async { - final uri = Uri.parse('$_baseUrl/auth/register'); - final response = await _client.post( - uri, - headers: {'Content-Type': 'application/json'}, - body: json.encode({'name': name, 'email': email, 'password': password}), - ); - - if (response.statusCode == 201 || response.statusCode == 200) { - final decoded = json.decode(response.body) as Map; - if (decoded['success'] == true || decoded.containsKey('token')) { - // registration succeeded; nothing further to return - return; - } else { - throw Exception('Failed to register: ${decoded['message'] ?? 'Unknown error'}'); - } - } else { - throw Exception('Failed to register: ${response.statusCode} ${response.body}'); - } + Future login(String email, String password) { + return _neoClient.login(email: email, password: password); } - Future login(String email, String password) async { - final uri = Uri.parse('$_baseUrl/auth/login'); - final response = await _client.post( - uri, - headers: {'Content-Type': 'application/json'}, - body: json.encode({'email': email, 'password': password}), - ); - - if (response.statusCode == 200) { - return AuthResponse.fromJson(json.decode(response.body)); - } else { - throw Exception('Failed to login: ${response.body}'); - } + Future verify(String email, String code) { + return _neoClient.verifyEmail(email: email, code: code).then((_) {}); } - Future verify(String email, String code) async { - final uri = Uri.parse('$_baseUrl/auth/verify'); - final response = await _client.post( - uri, - headers: {'Content-Type': 'application/json'}, - body: json.encode({'email': email, 'code': code}), - ); - - if (response.statusCode != 200) { - throw Exception('Failed to verify code: ${response.body}'); - } + Future resendCode(String email) { + return _neoClient.resendVerificationCode(email); } - Future resendCode(String email) async { - final uri = Uri.parse('$_baseUrl/auth/resend-code'); - final response = await _client.post( - uri, - headers: {'Content-Type': 'application/json'}, - body: json.encode({'email': email}), - ); - - if (response.statusCode != 200) { - throw Exception('Failed to resend code: ${response.body}'); - } + Future deleteAccount() { + return _neoClient.deleteAccount(); } - - Future deleteAccount() async { - final uri = Uri.parse('$_baseUrl/auth/profile'); - final response = await _client.delete(uri); - - if (response.statusCode != 200) { - throw Exception('Failed to delete account: ${response.body}'); - } - } - - // --- Movie Methods --- - - Future> _fetchMovies(String endpoint, {int page = 1}) async { - final uri = Uri.parse('$_baseUrl$endpoint').replace(queryParameters: { - 'page': page.toString(), - }); - final response = await _client.get(uri); - - if (response.statusCode == 200) { - final List data = json.decode(response.body)['results']; - if (data == null) { - return []; - } - return data.map((json) => Movie.fromJson(json)).toList(); - } else { - throw Exception('Failed to load movies from $endpoint'); - } - } -} +} \ No newline at end of file