# Please Pay Me — Flutter mobile Мобильный кабинет для бюджета «от зарплаты до зарплаты». UI построен на iOS-ките (Cupertino, Apple HIG), данные — REST API из `src/PleasePayMe.Api`. ## Запуск ```powershell cd mobile copy .env.example .env # один раз # отредактируй PPM_API_BASE_URL в .env .\run.ps1 .\run.ps1 -Device chrome .\run.ps1 -Target widgetbook ``` `run.ps1` читает `mobile/.env` и передаёт его во Flutter как `--dart-define-from-file`. Если файла нет — копирует `.env.example`. ```bash export PATH="$HOME/flutter/bin:$PATH" cd mobile flutter pub get flutter run -d windows --dart-define-from-file=.env ``` ### Конфигурация (`mobile/.env`) | Переменная | Назначение | | --- | --- | | `PPM_API_BASE_URL` | адрес API; если пусто — `https://please-pay-me.ru` | | `PPM_WEB_URL` | веб-кабинет; если пусто — тот же origin, что API | | `PPM_DEMO` | `true` — принудительный in-memory backend | ### Авторизация На iOS и Android: - **Telegram** — кабинет в WebView, JWT забирается из `localStorage.ppm_session_jwt` через канал `PpmAuth`. - **Яндекс** — WebView на `oauth.yandex.ru`. Код возвращается на `{кабинет}/` (как Callback URL в кабинете Яндекса), приложение шлёт его в `POST /api/auth/yandex`. Client secret живёт только на API. Приложение проверяет JWT через `GET /api/me` и кладёт сессию в `SharedPreferences`. На Windows / в браузере WebView нет: остаётся ручной ввод токена. Альтернатива на любой платформе — «Демо-режим» (`DemoBackend` без сети). В кабинете Яндекса должен быть Redirect URI `https://<домен>/` (для продакшена — `https://please-pay-me.ru/`). BotFather → `/setdomain` для Telegram — тот же домен. ## Экраны | Вкладка | Что делает | | --- | --- | | Обзор | остаток бюджета, дневной лимит, трата в один тап, ближайшая выплата | | Журнал | операции по дням, фильтр «текущий / все бюджеты», подгрузка страниц | | Бюджеты | выбор активного конверта, создание, редактирование, архив, удаление | | Работа | оклад, дни выплат, правило выходных, график ближайших зарплат | | Профиль | пользователь, режим подключения, выход | ## Структура ``` mobile/ ├── lib/ │ ├── app/ # CupertinoApp, session gate, таб-бар │ ├── core/ # config, storage, AsyncValue, форматтеры │ ├── data/ │ │ ├── api/ # ApiClient (http + маппинг ошибок) │ │ ├── models/ # Budget/Expense/Job/AuthUser + JSON-хелперы │ │ ├── repositories/ # интерфейсы + REST-реализации │ │ └── demo/ # in-memory backend (превью, тесты, демо-режим) │ ├── features/ # overview / journal / budgets / work / profile / auth │ ├── theme/ # токены Apple HIG + CupertinoThemeData │ └── ui/ # дизайн-система (atoms → molecules → navigation) └── widgetbook/ # переносимый каталог компонентов и экранов ``` Слои связаны через интерфейсы репозиториев: экраны знают только `BudgetRepository` / `ExpenseRepository` / `JobRepository`, а `SessionController` подставляет REST- или demo-реализацию. Поэтому и Widgetbook, и виджет-тесты гоняют настоящие экраны без сети. ## Тесты ```bash cd mobile && flutter test # модели, форматтеры, контроллеры, сквозной сценарий cd mobile/widgetbook && flutter test # рендер каждого use-case в light и dark ```