feat(proj): init

This commit is contained in:
vl.arkhangelskii
2026-09-21 04:06:43 +03:00
commit c956b94983
1076 changed files with 50876 additions and 0 deletions
@@ -0,0 +1,72 @@
# 2026-09-20 — Мобильное приложение Please Pay Me собрано на iOS-ките
## Запрос
«Собери теперь приложение please-pay-me, используя эти компоненты» — то есть поверх
Cupertino-кита из прошлой сессии сделать рабочее приложение против существующего API.
## Что сделано
### Слой данных (`lib/data/`)
- `models/``Budget`, `BudgetStatus` (+ вычисляемые `spentProgress` / `dailyProgress`),
`Expense`, `ExpensesPage`, `Job`, `UpcomingPay`, `WeekendPolicy`, `AuthUser`, `AuthSession`.
Зеркалят `web/src/types.ts`, парсятся через `models/json.dart` — толерантные
коэрсеры (`int`/`double`/`string`), потому что API отдаёт числа из C#-рекордов.
- `api/api_client.dart` — транспорт на `http`: Bearer-токен через `tokenProvider`,
маппинг `detail`/`title` в `ApiException`, отдельная обработка 401 с колбэком
`onUnauthorized` (разлогинивает сессию).
- `repositories/` — интерфейсы `BudgetRepository` / `ExpenseRepository` /
`JobRepository` / `UserRepository` + REST-реализации на все ручки `MeController`.
- `demo/demo_backend.dart` — in-memory бэкенд, повторяющий математику конверта
(`dailyLimit = remaining / daysLeft`). Используется в демо-режиме, превью и тестах;
`DemoBackend.empty()` — для пустых состояний.
### Ядро (`lib/core/`)
- `config/app_config.dart``PPM_API_BASE_URL`, `PPM_WEB_URL`, `PPM_DEMO` через
`--dart-define`; пустой адрес API автоматически включает демо-режим.
- `storage/session_storage.dart` — интерфейс + `SharedPreferences` и in-memory реализации.
- `state/async_value.dart` — sealed `AsyncLoading/AsyncData/AsyncError` с `map(...)`,
чтобы экраны матчились по состоянию, а не жонглировали тремя nullable-полями.
- `format/formatters.dart` — деньги (`1 234,50 ₽`), русские плюрали, «Сегодня/Вчера».
### Состояние (`lib/features/*/`)
- `SessionController` — владеет авторизацией и выдаёт репозитории (REST или demo),
так что остальное приложение не знает про токены. `sessionKey` пересобирает
фичевые контроллеры при входе/выходе.
- `BudgetsController` — источник правды по конвертам (список, выбранный, мутации
с авто-перезагрузкой, возврат текста ошибки вместо исключения).
- `JournalController` — пагинация + группировка по дням + скоуп «текущий/все».
- `JobsController` — работы и ближайшая выплата.
### Экраны (`lib/features/`, `lib/app/`)
Пять вкладок: Обзор, Журнал, Бюджеты, Работа, Профиль (+ экран входа и сплэш).
Формы — модальные шиты: новая/редактирование траты, бюджета, работы; все они
принимают `onSubmit`, возвращающий текст ошибки, и не знают про репозитории.
`_SessionGate` разводит `restoring / signedOut / signedIn`.
### Кит
Удалены демо-экраны `lib/ui/screens/*`; добавлены `AppEmptyState`, `AppErrorView`,
`AppLoadingView`, `showAppDatePicker`, `showAppFormSheet`.
### Widgetbook
Папка Screens теперь показывает **настоящие экраны** приложения на `DemoScope`
(demo-бэкенд + провайдеры), включая пустые состояния, формы и полный таб-бар.
### Авторизация — известное ограничение
Telegram Login Widget работает только в вебе. Мобильный клиент принимает JWT из
`localStorage.ppm_session_jwt` веб-кабинета и валидирует его через `GET /api/me`.
Нативный вход (WebView с Telegram-виджетом либо one-time code через бота)
не делался — это отдельное продуктовое решение.
## Тесты
- `mobile`: 32 теста — парсинг моделей, форматтеры, все контроллеры на demo-бэкенде,
виджет-сценарии (вход, табы, пустые состояния, запись траты end-to-end).
- `mobile/widgetbook`: 100 — каждый use-case рендерится в light и dark.
- `flutter analyze` по обоим пакетам — 0 issues.
- `flutter build windows --debug` проходит (проверка плагинов shared_preferences/url_launcher).
## Запуск
```powershell
cd mobile
.\run.ps1 # демо-данные
.\run.ps1 -ApiBaseUrl https://ppm.example.com
```