diff --git a/README.md b/README.md index 29f1ea5..37fd7b9 100644 --- a/README.md +++ b/README.md @@ -1,103 +1,85 @@ # Ignis App -Мобильное приложение для управления умными лампами WiZ через self-hosted сервер [Ignis Core](https://git.akokos.ru/artem.kokos/ignis-core). +Android-клиент для self-hosted backend [Ignis Core](https://git.akokos.ru/artem.kokos/ignis-core). Приложение управляет группами ламп WiZ, расписаниями, API-ключами и гео-автоматизацией ухода из дома. -## Возможности +## Что умеет -- **Мульти-дом** -- поддержка нескольких серверов Ignis (квартира, дача, друзья). Каждый дом -- отдельный сервер со своим URL и API-ключом. -- **Группы ламп** -- создание, удаление и управление. При создании группы есть product-валидация, автогенерация `ID`, предупреждение о конфликтах по устройствам и более честный перескан сети. -- **Управление освещением:** - - Включение/выключение - - Яркость 10--100% с шагом 10% - - Цветовая температура 2700--6500K с шагом 100K - - RGB-цвет через HSV-пикер - - Сцены (загружаются с сервера, отображаются с человекочитаемыми названиями) - - Таймер "включить на 4 часа" -- **Расписания** -- одноразовые таймеры с выбором даты/времени и повторяющиеся задачи с выбором дней недели. Просмотр, создание, валидация и отмена активных задач. -- **API-ключи** -- просмотр, создание, отзыв и повторная активация гостевых ключей для администраторов с отдельным UX для только что созданного ключа. -- **Статистика и лог событий** -- просмотр сводки по группам и последних событий сервера. -- **Геофенс и расстояния** -- live-дистанция до дома в UI и опциональное автовыключение света при уходе. Геофенс работает для текущего активного дома, показывает диагностический статус и использует cooldown/re-arm поведение. -- **Устойчивость к ошибкам** -- гранулярные состояния загрузки (`LoadState`), централизованная обработка сетевых сбоев, soft-ошибки при управлении ползунками без спама в UI. +- несколько домов с отдельными URL и API-ключами; +- управление группами света: `on/off`, яркость, температура, RGB, сцены; +- таймер "включить на 4 часа"; +- одноразовые и повторяющиеся расписания; +- статистика и лог событий; +- управление гостевыми API-ключами для администратора; +- расстояние до дома и автовыключение света по geofence. + +## Гео-автоматизация + +Для активного дома приложение может зарегистрировать системный Android geofence. После подтверждённого `EXIT` запускается короткая фоновая задача, которая проверяет группы и выключает только те, что реально включены. + +Это не polling каждые 15 минут. Основной триггер здесь событийный: +- geofence регистрируется нативно через Android geofencing API; +- сетевое выключение выполняется отдельным one-off worker; +- при отсутствии координат или выключенной опции geofence не армится. ## Стек -- Flutter 3.x / Dart -- Riverpod -- управление состоянием -- Dio -- HTTP-клиент -- SharedPreferences -- локальное хранение несекретных настроек -- Flutter Secure Storage -- безопасное хранение API-ключей -- Geolocator -- геолокация -- Workmanager -- периодические фоновые задачи -- Flutter Local Notifications -- локальные уведомления +- Flutter / Dart +- Riverpod +- Dio +- SharedPreferences +- flutter_secure_storage +- Geolocator +- Android Geofencing API +- Android WorkManager -## Структура проекта +## Структура ```text lib/ -├── app/ -│ ├── app_bootstrap.dart -- bootstrap приложения и навигация -│ ├── build_info.dart -- метаданные сборки (дата, git hash) -│ ├── error_message.dart -- форматирование ошибок API и сети -│ └── load_state.dart -- универсальный стейт загрузки (idle/loading/data/error) -├── main.dart -- точка входа, тема, роутер -├── models/ -│ ├── api_key_info.dart -- типизированная модель API-ключа -│ ├── auth_info.dart -- информация об авторизации -│ ├── event_log_item.dart -- лог событий -│ ├── home_config.dart -- несекретная конфигурация сервера -│ ├── ignis_device.dart -- устройство умного дома -│ ├── ignis_group.dart -- группа устройств и её состояние -│ ├── ignis_scene.dart -- сцена освещения -│ ├── schedule_task.dart -- задача расписания -│ └── stats_summary.dart -- статистика -├── services/ -│ ├── api_client.dart -- HTTP-клиент к Ignis Core API -│ ├── credentials_storage.dart -- безопасное хранение ключей -│ ├── geofence_worker.dart -- фоновая логика геофенса -│ └── settings_service.dart -- хранение списка "домов" -├── features/ -│ ├── api_keys/providers/ -- управление гостевыми API-ключами -│ ├── auth/providers/ -- auth/me и auth-state -│ ├── groups/ -- валидация и логика форм групп -│ ├── homes/ -- дома, геолокация, geofence sync/runtime -│ ├── remote/providers/ -- polling групп, устройства, сцены, control errors -│ ├── schedules/ -- логика и providers расписаний -│ ├── shared/providers/ -- базовые core providers -│ └── stats/providers/ -- статистика и лог событий -├── providers/ -│ └── providers.dart -- compatibility barrel для публичных provider-экспортов -├── screens/ -│ ├── api_keys_screen.dart -- экран гостевых API-ключей -│ ├── event_log_screen.dart -- последние события сервера -│ ├── homes_screen.dart -- список домов, distance/geofence статус -│ ├── home_edit_screen.dart -- создание и редактирование дома -│ ├── remote_screen.dart -- основной экран управления светом -│ ├── group_edit_screen.dart -- создание группы с выбором устройств -│ ├── schedules_screen.dart -- создание и просмотр расписаний -│ └── stats_screen.dart -- статистика по командам -└── widgets/ - ├── build_info_text.dart -- лейбл с версией сборки - ├── group_card.dart - ├── load_error_view.dart -- универсальный виджет ошибок и retry - └── color_picker.dart +├── app/ # bootstrap, build info, error/load helpers +├── features/ # feature-level providers and logic +│ ├── api_keys/ +│ ├── auth/ +│ ├── groups/ +│ ├── homes/ +│ ├── remote/ +│ ├── schedules/ +│ ├── shared/ +│ └── stats/ +├── models/ # typed domain models +├── providers/ # public provider barrel +├── screens/ # UI screens +├── services/ # API client, settings, credentials +└── widgets/ # reusable UI widgets + +android/app/src/main/kotlin/ru/akokos/ignis_app/ +├── MainActivity.kt +├── GeofenceAutomationManager.kt +├── GeofenceBroadcastReceiver.kt +├── GeofenceExitWorker.kt +└── GeofenceRestoreReceiver.kt ``` -## Сборка +## Запуск ```bash -# Зависимости flutter pub get - -# Debug-запуск flutter run - -# Release APK (с пробросом build info) -flutter build apk --release --dart-define=IGNIS_BUILD_DATE="$(date -u +'%Y-%m-%dT%H:%M:%SZ')" --dart-define=IGNIS_GIT_SHA="$(git rev-parse --short HEAD)" ``` -APK: `build/app/outputs/flutter-apk/app-release.apk` +## Release APK -> Сейчас release APK подписывается debug-ключом из Flutter-шаблона. Для личной установки на телефон этого достаточно, для настоящего релиза подпись нужно заменить. +```bash +flutter build apk --release \ + --dart-define=IGNIS_BUILD_DATE="$(date -u +'%Y-%m-%dT%H:%M:%SZ')" \ + --dart-define=IGNIS_GIT_SHA="$(git rev-parse --short HEAD)" +``` + +Артефакт: + +```text +build/app/outputs/flutter-apk/app-release.apk +``` ## Проверки @@ -106,40 +88,28 @@ flutter analyze flutter test ``` -Текущий baseline зелёный: `flutter analyze`, `flutter test` и release APK сборка проходят штатно. - -Дополнительно тестами уже прикрыты: -- typed parsing/load-state для основных backend-ответов; -- geofence distance/runtime логика; -- чистая логика форм расписаний и групп; -- provider-мутаторы для расписаний, таймера 4h и API-ключей; -- widget-сценарии форм домов, групп, расписаний и API-ключей; -- widget-сценарии `RemoteScreen`, `GroupCard` и error/retry-потоков. - -Сейчас baseline клиента закрывается примерно `60` тестами и уже ловит regressions не только в helper-логике, но и в основных пользовательских сценариях. +Сейчас тестами прикрыты: +- parsing и load-state основных backend-ответов; +- сериализация `HomeConfig` и geofence radius; +- синхронизация активного дома с geofence automation; +- form logic для домов, групп и расписаний; +- provider-мутаторы расписаний, API-ключей и group control; +- widget-сценарии форм, `GroupCard` и error/retry потоков. ## Настройка -При первом запуске приложение попросит добавить "дом" -- указать адрес сервера Ignis и API-ключ. После этого откроется пульт управления группами. +1. Добавить дом: адрес сервера Ignis и API-ключ. +2. При необходимости задать координаты дома. +3. Включить "выключать свет при уходе". +4. Выдать Android-разрешения на геолокацию, включая background location. -Если задать координаты дома, экран домов начнёт показывать расстояние до активного дома. Если дополнительно включить автовыключение при уходе и выдать Android фоновые разрешения на геолокацию и уведомления, приложение сможет в фоне выключать свет при удалении от текущего активного дома. +API-ключи хранятся отдельно от списка домов в `flutter_secure_storage`. Старые ключи из `SharedPreferences` мигрируются автоматически. -Для добавления второго дома: кнопка "домик" в левом верхнем углу пульта -> экран домов -> кнопка "+". +## Ограничения -API-ключи хранятся отдельно от конфигурации домов в `flutter_secure_storage`. Старые ключи из `SharedPreferences` мигрируются автоматически. - -## API - -Приложение работает с [Ignis Core API](https://git.akokos.ru/artem.kokos/ignis-core) -- self-hosted бэкенд на FastAPI (контракт OpenAPI 3.1.0). -Авторизация происходит через заголовок `X-API-Key`. -Доменный слой на стороне клиента полностью типизирован. - -## Текущие ограничения - -- Целевая платформа сейчас Android. -- Release APK пока подписывается debug-ключом из Flutter-шаблона. -- Build info в APK показывает дату сборки и короткий git hash текущего `HEAD`. Если сборка делается поверх незакоммиченного рабочего дерева, hash будет от последнего коммита, а не от локальных незакоммиченных изменений. -- Android-specific поведение реального background execution, уведомлений, runtime permissions и OEM battery restrictions пока подтверждается в основном ручными проверками на устройстве, а не automated integration-тестами. +- целевая платформа сейчас Android; +- реальное поведение background execution, geofence delivery и OEM battery restrictions подтверждается в основном ручными проверками на устройстве; +- force-stop приложения со стороны Android может ломать автоподъём фоновой логики до следующего ручного запуска. ## Лицензия diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 1f40572..24bf39a 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -46,4 +46,6 @@ flutter { dependencies { coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4") -} \ No newline at end of file + implementation("com.google.android.gms:play-services-location:21.3.0") + implementation("androidx.work:work-runtime-ktx:2.10.2") +} diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index f818d03..b85799d 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -3,7 +3,7 @@ - + - + + + + + + +