Android CI / Build and Test (push) Failing after 21m18s
iOS CI / Build and Test SwiftUI App (push) Canceled after 0s
Build Unsigned iOS and macOS Apps / Build Unsigned iOS IPA (push) Canceled after 0s
Build Unsigned iOS and macOS Apps / Build macOS ZIP (push) Canceled after 0s
129 lines
13 KiB
Markdown
129 lines
13 KiB
Markdown
# Баланс — Flutter / Material 3
|
||
|
||
Личный бюджет с локальным хранилищем и синхронизацией с существующим `BalanceServer`. Сервер и его протокол не переписаны. CloudKit удалён по запросу: данные синхронизируются только через Go-сервер.
|
||
|
||
## Возможности
|
||
|
||
- Доходы и расходы: создание, редактирование, удаление, дата/время, заметки, поиск, фильтр типа.
|
||
- Общий баланс, ручная корректировка отдельной операцией без влияния на аналитику.
|
||
- Аналитика за 1, 3 и 12 календарных месяцев, доходы/расходы, результат, сбережения, средний расход, крупнейшая категория, динамика и структура расходов.
|
||
- Месячные бюджеты по категориям, остаток и превышение; выбор месяца.
|
||
- Встроенные и собственные категории, 50 значков Material с совместимыми серверными именами, 30 эмодзи и собственный эмодзи, 24 цвета. Переименование обновляет связанные записи; удаление сохраняет историю.
|
||
- RUB, EUR, USD, SEK; системная, светлая и тёмная тема; CSV-экспорт.
|
||
- Локальная работа без аккаунта; сервер, регистрация, вход, выход, refresh, ручная и автоматическая синхронизация, полная пересинхронизация.
|
||
- Транзакционное сохранение данных и курсора, очередь изменений и удалений, повтор после сетевого сбоя; отдельные хранилища аккаунтов.
|
||
- Адаптивная навигация: нижняя панель на телефоне, боковая на широком экране.
|
||
|
||

|
||
|
||
## Платформы
|
||
|
||
| Платформа | Реализация / сборка |
|
||
| --- | --- |
|
||
| 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`.
|