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

5.3 KiB
Raw Blame History

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.dartPPM_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).

Запуск

cd mobile
.\run.ps1                                     # демо-данные
.\run.ps1 -ApiBaseUrl https://ppm.example.com