Files
please-pay-me/context/2026-09-20_00-40-00_mobile-app-assembled.md
2026-09-21 04:06:43 +03:00

73 lines
5.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
```