# Баланс — Flutter / Material 3 Личный бюджет с локальным хранилищем и синхронизацией с существующим `BalanceServer`. Сервер и его протокол не переписаны. CloudKit удалён по запросу: данные синхронизируются только через Go-сервер. ## Возможности - Доходы и расходы: создание, редактирование, удаление, дата/время, заметки, поиск, фильтр типа. - Общий баланс, ручная корректировка отдельной операцией без влияния на аналитику. - Аналитика за 1, 3 и 12 календарных месяцев, доходы/расходы, результат, сбережения, средний расход, крупнейшая категория, динамика и структура расходов. - Месячные бюджеты по категориям, остаток и превышение; выбор месяца. - Встроенные и собственные категории, 50 значков Material с совместимыми серверными именами, 30 эмодзи и собственный эмодзи, 24 цвета. Переименование обновляет связанные записи; удаление сохраняет историю. - RUB, EUR, USD, SEK; системная, светлая и тёмная тема; CSV-экспорт. - Локальная работа без аккаунта; сервер, регистрация, вход, выход, refresh, ручная и автоматическая синхронизация, полная пересинхронизация. - Транзакционное сохранение данных и курсора, очередь изменений и удалений, повтор после сетевого сбоя; отдельные хранилища аккаунтов. - Адаптивная навигация: нижняя панель на телефоне, боковая на широком экране. ![Интерфейс на компьютере](docs/desktop.png) ## Платформы | Платформа | Реализация / сборка | | --- | --- | | Android | Flutter, Android SDK и Java; APK/AAB | | iPhone, iPad | Один iOS Flutter target; сборка на macOS с Xcode | | macOS | Flutter runner; сборка на macOS с Xcode | | Windows | Flutter runner; сборка на Windows с Visual Studio C++ Desktop | | Linux | Flutter runner; GTK3, libsecret, jsoncpp, Clang, CMake, Ninja | | Web | Flutter JavaScript release; отдельная статическая раздача | | Apple Watch | Сохранён полноценный **нативный SwiftUI** target `BalanceWatch` в `../Balance/Balance.xcodeproj` | Flutter не предоставляет официального watchOS runner. Часы используют исходный независимый клиент и напрямую тот же Go-сервер: обзор, аналитика, операции, категории, бюджеты, корректировка и настройки сохранены. Интерфейс Material 3 реализован во Flutter; часы используют SwiftUI. Не добавлен фиктивный `flutter build watchos`. Проверенный SDK: Flutter **3.47.8**, Dart **3.13.7**. Зависимости закреплены в `pubspec.lock`. Точные выполненные проверки и ограничения среды перечислены в [VALIDATION.md](VALIDATION.md). ## Запуск ```sh cd BalanceFlutter flutter pub get flutter run -d chrome # или устройство из flutter devices flutter analyze flutter test ``` Linux зависимости (Ubuntu): ```sh sudo apt-get install clang cmake ninja-build pkg-config libgtk-3-dev libsecret-1-dev libjsoncpp-dev ``` Сборка из корня Flutter-проекта: ```sh bash scripts/build.sh web bash scripts/build.sh android bash scripts/build.sh ios # macOS; без подписи, для проверки сборки bash scripts/build.sh macos # macOS bash scripts/build.sh windows # Windows, Git Bash bash scripts/build.sh linux bash scripts/build.sh watchos # macOS; исходный native Watch target, simulator flutter build appbundle # Android публикация ``` iPad использует тот же iOS target. В Xcode откройте `ios/Runner.xcworkspace`, выберите Team, свой Bundle ID и устройство. Для macOS — `macos/Runner.xcworkspace`. Для часов — `../Balance/Balance.xcodeproj`, схема `BalanceWatch`, Team и уникальный Bundle ID. Сборка на симулятор без подписи не заменяет проверку на устройстве и настройку distribution signing. Перед публикацией настройте собственные идентификаторы пакетов и Android release keystore. Пример настроек подписи находится в `android/key.properties.example`; реальные ключи и пароли не коммитьте. Подпись и публикация в магазины не выполнялись. CI `.github/workflows/flutter.yml` содержит анализатор, тесты, отдельные сборки шести Flutter targets и нативного Watch. Это конфигурация для запуска в GitHub Actions; на Gitea её потребуется запускать совместимым runner либо перенести шаги в Gitea Actions. ## Подключение существующего сервера и перенос данных 1. Синхронизируйте старое приложение со своим работающим Go-сервером. 2. В новом приложении откройте **Настройки → Адрес сервера**, сохраните HTTPS-адрес. 3. Войдите с тем же аккаунтом и выполните синхронизацию. 4. Сравните общий баланс, операции, категории, бюджеты и корректировки, затем переходите на новый клиент. Сохраняются UUID, даты обновления, типы, снимки оформления категорий и флаг корректировки. Flutter не читает напрямую старые SwiftData/Room sandbox. CSV — отчёт, а не полный резервный формат, через него нельзя без потерь перенести бюджеты и корректировки. При первом подключении нового аккаунта текущие локальные записи переносятся в него. Последующие аккаунты и серверы имеют отдельные базы; выход не удаляет их данные. Валюта и тема остаются настройками устройства. Валюта меняет формат, обменных курсов и пересчёта нет. Внутренние суммы Flutter хранятся целыми сотыми долями валюты, серверный формат `amount`/`limit` остаётся числовым. Для старых записей с более чем двумя десятичными знаками используется округление до сотых. Интервалы везде `[начало, конец)`, чтобы полночь следующего месяца не учитывалась дважды. ## Web и обратный прокси `flutter build web` создаёт `build/web`. Раздавайте его отдельной статикой, например Nginx, с fallback на `index.html`. Go-сервер продолжает отдавать свой встроенный старый Web-интерфейс; новая Flutter-сборка не заменяет его автоматически. Укажите origin Flutter-сайта в существующей настройке сервера `BALANCE_CORS_ORIGINS`. Например: `https://balance.example.org`. API можно проксировать под `/api/` с удалением префикса; тогда адрес сервера в приложении — `https://balance.example.org/api`. ```nginx server { listen 443 ssl; server_name balance.example.org; # ssl_certificate /...; ssl_certificate_key /...; root /var/www/balance; location / { try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080/; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } } ``` Не переносите CSP заголовок встроенного Go Web-клиента на Flutter-статику: это другой frontend. На Web используйте HTTPS; browser storage доступен тому же origin и не обладает изоляцией системного Keychain/Keystore. При недоступном защищённом хранилище сессия сохраняется только в памяти, приложение показывает предупреждение. Финансовые записи остаются локально в IndexedDB. ## Автоматическая синхронизация На всех платформах синхронизация запускается при входе/открытии, возвращении в приложение и раз в две минуты, пока оно активно. Android дополнительно регистрирует WorkManager-задачу раз в 15 минут при наличии сети. ОС может откладывать её по энергосбережению или после принудительной остановки приложения. Фоновая задача выполняет полный push/pull и обновление токенов. Общая нативная блокировка защищает сессию и хранилище между открытым приложением и фоновым Flutter engine. Проверены компиляция и тесты блокировки; выполнение задачи после закрытия приложения ещё нужно проверить на Android-устройстве. Web использует Web Locks API для защиты общей базы и refresh-токенов между вкладками. Требуется современный браузер и HTTPS (или localhost). На desktop рекомендуется один запущенный экземпляр приложения: межпроцессная координация desktop-клиентов отдельно не реализована. Roboto и CanvasKit раздаются вместе с Web-сборкой: сборочный скрипт использует `--no-web-resources-cdn`. Стандартный сгенерированный Flutter service worker кэширует оболочку; после первого открытия Web может работать с локальными данными без API, а синхронизация требует связи с сервером. Реальная установка PWA и восстановление без сети в браузере в этой среде не проверялись. ## Устройство проекта - `lib/domain/finance.dart`: модели, расчёты, категории, CSV. - `lib/services/app_store.dart`: CRUD, очередь, сессии, атомарное применение страниц. - `lib/services/sync_api.dart`: существующий wire-протокол сервера. - `lib/services/storage*.dart`: Sembast / IndexedDB. - `lib/ui/`: интерфейс и формы Material 3. - `test/`: расчёты, хранилище, HTTP-протокол, формы и адаптивные экраны. - `tool/integration_sync.dart`: проверка с настоящим Go HTTP-сервером. - [SYNC_PROTOCOL.md](SYNC_PROTOCOL.md), [FEATURE_PARITY.md](FEATURE_PARITY.md): протокол и аудит переноса. Для Linux в пользовательской сессии нужен работающий Secret Service, например GNOME Keyring или KWallet с поддержкой Secret Service, и библиотека `libsecret-1-0`: через него `flutter_secure_storage` сохраняет токены. Если служба недоступна или связка ключей заблокирована, приложение сохраняет сессию только в памяти и показывает предупреждение; локальные финансовые записи сохраняются отдельно в Sembast. Для сборки нужны `clang cmake ninja-build pkg-config libgtk-3-dev libsecret-1-dev libjsoncpp-dev`.