# ADR-005: Клиентское приложение — Flutter 3 (Android + Max Mini App)

**Дата:** 2026-03-28
**Статус:** Proposed
**Обновляет:** ADR-001 раздел 2 (уточняет стратегию распространения)

---

## Контекст

ADR-001 принял решение использовать Flutter 3 для мобильного приложения арендатора. Это решение **подтверждается** с дополнением: Flutter Web используется как Max Mini App для мгновенного доступа из мессенджера.

Telegram не может быть использован как платформа. Мессенджер Max (VK, 107M пользователей, 77M DAU, обязательная предустановка с 01.09.2025) — основной канал распространения в РФ.

### Почему Flutter, а не React + Kotlin гибрид

| Критерий | Flutter | React (Mini App) + Kotlin (Android) |
|----------|---------|--------------------------------------|
| Кодовая база | **1 (Dart)** | 2 (TypeScript + Kotlin) |
| Android с BLE/NFC | Из коробки (зрелые плагины) | Нативный Kotlin |
| Max Mini App | **Flutter Web** (тот же код) | React (отдельный код) |
| iOS (будущее) | **Бесплатно** (тот же код) | Третий стек |
| Offline | Полноценный (SQLite, Hive) | WebView — ограниченный |
| Производительность | Нативная компиляция (Skia/Impeller) | WebView — медленнее |
| BLE | flutter_blue_plus (зрелый) | Android BLE API (ручная работа) |
| Поддержка | 1 команда, 1 стек | 2 стека, 2× сложность |

---

## Решение

**Flutter 3 — единая кодовая база для всех каналов:**

1. **Android-приложение** (основной) — нативная компиляция, BLE/NFC, offline, публикация в RuStore + Google Play
2. **Flutter Web → Max Mini App** (дополнительный) — мгновенный доступ для 77M пользователей Max без установки
3. **iOS** (будущее, фаза 3) — из того же кода

### Технологический стек

| Слой | Инструмент | Обоснование |
|------|-----------|-------------|
| Фреймворк | **Flutter 3** (Dart) | Единый код → Android + Web (Max) + iOS |
| UI | **Material 3** + кастомная тема | Адаптивный дизайн, поддержка тёмной темы |
| State management | **Riverpod** или **BLoC** | Реактивное управление состоянием |
| HTTP | **Dio** | REST API к бэкенду, interceptors, retry |
| BLE | **flutter_blue_plus** | Proximity verification, идентификация постамата (Android) |
| NFC | **nfc_manager** | Fallback для BLE (Android) |
| Offline storage | **Hive** или **Isar** | Кэш каталога, активные аренды |
| Push | **Firebase Cloud Messaging (FCM)** | Бесплатно, Android + iOS |
| Auth | SMS OTP (FlashCall) + Max Login | Zvonok.com 0.25 руб/звонок |
| Платежи | СБП QR (Т-Банк 0.4%) | Отображение QR в приложении |
| Max Mini App | **Flutter Web** build | Тот же код, запускается в WebView Max |
| Max Bot | Go SDK (бэкенд) | Уведомления через Max Bot API |

### Экраны приложения

```
1. Каталог оборудования
   - Список категорий
   - Карточки оборудования с фото
   - Фильтры (доступность, цена, локация)
   - Поиск

2. Карточка оборудования
   - Фото, описание, характеристики
   - Выбор постамата на карте
   - Выбор периода аренды
   - Кнопка "Забронировать"

3. Оплата
   - Сумма, детали бронирования
   - СБП QR-код (Т-Банк API)
   - Статус оплаты в реальном времени (SSE/polling)

4. Активная аренда
   - Статус аренды (state machine)
   - Показать полку с предметом, подтвердить получение
   - Таймер аренды
   - Фото-верификация возврата:
     - Кнопка "Сфотографировать возврат" (камера телефона)
   - QR-сканер постамата (идентификация)

5. История аренд
   - Список прошлых аренд, чеки

6. Профиль
   - Данные пользователя
   - Привязка карты (опционально)
   - Настройки уведомлений
```

---

## Каналы распространения

### Фаза 1: MVP (месяц 1-2)

**Android-приложение (Flutter → APK)**
- Публикация в **RuStore** (бесплатно) + **Google Play** ($25)
- Полный функционал: BLE, NFC, offline, push
- Авторизация: SMS OTP через FlashCall (0.25 руб/звонок)

### Фаза 2: Расширение (месяц 3-4)

**Max Mini App (Flutter Web)**
- Тот же код, `flutter build web`
- Публикация через модерацию Max (верифицированное юрлицо)
- Ограничения: нет BLE/NFC, нет offline (серверная модель подтверждения выдачи/возврата)
- Авторизация: Max Login (бесплатно)
- Уведомления: Max Bot API (бесплатно)

### Фаза 3: Масштабирование (месяц 5+)

**iOS (Flutter → IPA)**
- Тот же код, `flutter build ios`
- Публикация в App Store ($99/год)

---

## Взаимодействие с постаматом

### Android: BLE (proximity) + серверная модель

```
┌─────────────────┐                              ┌──────────┐
│  Android App    │           BLE                │  ESP32   │
│  (Flutter)      │ ────────────────────────────→│ Постамат │
│                 │  proximity verification      │          │
│                 │  + идентификация постамата   │          │
│                 │ ←────────────────────────────│          │
│                 │  postomat_id, shelf_info     │          │
└────────┬────────┘                              └──────────┘
         │
         │  Серверная модель: HTTPS → подтверждение выдачи/возврата
         ▼
┌──────────────┐     MQTT      ┌──────────┐
│  Go Backend  │ ────────────→ │  ESP32   │
│  (PocketBase)│ ←──────────── │ Постамат │
└──────────────┘   telemetry   └──────────┘
```

### Max Mini App: только серверная модель

```
┌─────────────┐     HTTPS      ┌──────────────┐     MQTT      ┌──────────┐
│  Max        │ ──────────────→│  Go Backend  │ ────────────→ │  ESP32   │
│  Mini App   │     REST API   │  (PocketBase)│   NanoMQ      │ Постамат │
│  (Flutter   │ ←──────────────│              │ ←──────────── │          │
│   Web)      │   SSE/polling  │  подтверждение│  telemetry   │          │
└─────────────┘                │  выдачи/возврата              │          │
                               └──────────────┘               └──────────┘
```

---

## Авторизация

### Android: SMS OTP (FlashCall)

```
1. Пользователь вводит номер телефона
2. Бэкенд → Zvonok.com API → звонок на телефон
3. Последние N цифр номера = OTP-код
4. Flutter автоматически перехватывает входящий вызов
5. Бэкенд выдаёт JWT

Стоимость: 0.25 руб/звонок (Zvonok.com)
```

### Max Mini App: Max Login (бесплатно)

```
1. Пользователь открывает Mini App в Max
2. MAX Bridge передаёт initData с user info
3. Бэкенд валидирует HMAC подпись
4. Бэкенд выдаёт JWT

Стоимость: 0 руб
```

### Единый пользователь

Привязка по номеру телефона: пользователь, зарегистрированный через SMS в Android-приложении, автоматически авторизуется в Max Mini App (и наоборот) если номер телефона совпадает.

---

## Оплата

Единый метод для всех каналов — **СБП QR через Т-Банк (0.4%)**:

```
Приложение → POST /api/payments/create → Go Service → Т-Банк Init API
                                                            ↓
Приложение ← QR-код (SVG/PNG) ← Go Service ← Т-Банк GetQR
                                                            ↓
Пользователь сканирует QR банковским приложением
                                                            ↓
Go Service ← webhook (PaymentNotification) ← Т-Банк
```

- Go SDK: `nikita-vanyasin/tinkoff` (sbp.go, QR-генерация, webhooks)
- Комиссия: **0.4%** (минимум рынка)

---

## Уведомления

| Канал | Метод | Стоимость |
|-------|-------|-----------|
| **Android** | FCM push | Бесплатно |
| **Max Mini App** | Max Bot API (sendMessage) | Бесплатно |
| **Оба** | SMS (критичные: просрочка) | 2-3 руб/SMS (fallback) |

Max Bot (Go SDK, github.com/max-messenger) отправляет уведомления пользователям Max.
FCM — для пользователей Android-приложения.

---

## Адаптация Flutter Web для Max Mini App

### Platform Adapter (Dart)

```dart
abstract class PlatformAdapter {
  Future<UserInfo?> authenticate();
  void showMainButton(String text, VoidCallback onTap);
  void hapticFeedback(HapticType type);
  bool get supportsBLE;       // proximity verification, идентификация
  bool get supportsNFC;
  bool get supportsCamera;    // фото-верификация возврата
  bool get supportsOffline;
  String get platform; // 'android' | 'max_web' | 'ios'
}

class AndroidAdapter extends PlatformAdapter {
  // Нативный Flutter: BLE (proximity), NFC, FCM, SMS OTP, камера
  bool get supportsBLE => true;
  bool get supportsNFC => true;
  bool get supportsCamera => true;
  bool get supportsOffline => true;
}

class MaxWebAdapter extends PlatformAdapter {
  // Flutter Web + MAX Bridge (dart:js_interop)
  // window.WebApp.initData для авторизации
  bool get supportsBLE => false;
  bool get supportsNFC => false;
  bool get supportsCamera => true;   // камера доступна через браузер
  bool get supportsOffline => false;
}
```

### Условная логика в UI

```dart
// Фото-верификация возврата — камера доступна на обоих платформах
if (platformAdapter.supportsCamera) {
  // Android и Max Web: "Сфотографировать возврат"
  showPhotoVerificationButton();
}

// BLE proximity — только Android (идентификация постамата)
if (platformAdapter.supportsBLE) {
  showBLEProximityIndicator();
}
```

### Сборка

```bash
# Android APK
flutter build apk --release

# Max Mini App (Flutter Web)
flutter build web --release
# Деплой статики на VPS, регистрация в Max Developer Portal

# iOS (будущее)
flutter build ios --release
```

---

## Последствия

### Положительные

- **1 кодовая база** (Dart) → Android + Max Mini App + iOS
- **BLE/NFC** для Android — proximity verification, идентификация постамата
- **Max Mini App** — мгновенный доступ для 77M пользователей без установки
- **iOS бесплатно** в будущем из того же кода
- **Offline** на Android — кэш каталога, активные аренды
- **Нативная производительность** — Skia/Impeller, не WebView

### Отрицательные / Риски

| Риск | Вероятность | Митигация |
|------|------------|-----------|
| Flutter Web в Max WebView — возможны баги | Средняя | Тестирование, fallback на серверную модель |
| Flutter Web тяжелее React для Mini App | Средняя | Оптимизация (deferred loading, tree shaking) |
| Dart — не Go (основной стек бэкенда) | N/A | Flutter — фронтенд, бэкенд остаётся Go |
| App Store модерация (iOS) | Низкая | Только в фазе 3, не блокирует MVP |
| Max Mini App модерация (юрлицо) | N/A | Регистрация ИП/ООО |

---

## Оценка трудозатрат

| Компонент | Дни (с AI +30%) | Стоимость |
|-----------|----------------|-----------|
| Flutter UI (6 экранов) | 12 | 164K |
| BLE/NFC модули | 3 | 41K |
| Интеграция с бэкендом (REST, SSE) | 4 | 55K |
| Platform Adapter (Android + Max Web) | 2 | 27K |
| Авторизация (SMS OTP + Max Login) | 2 | 27K |
| Оплата (СБП QR отображение) | 2 | 27K |
| Тестирование + публикация | 3 | 41K |
| **Итого** | **28** | **382K** |

Сравнение:
- Flutter (1 код → 2 канала): **28 дней, 382K руб**
- React + Kotlin гибрид: ~35 дней, 477K руб
- Экономия: **7 дней, 95K руб**

---

## Альтернативы (рассмотрены и отвергнуты)

### React (Max Mini App) + Kotlin (Android)

- **Против:** 2 стека, двойная поддержка, WebView-гибрид — худший UX
- **За:** React ближе к веб-экосистеме Max — но Flutter Web работает в Max WebView так же

### Только нативный Android (Kotlin + Compose)

- **Против:** нет Max Mini App (упускаем 77M), нет iOS, нет переиспользования
- **За:** максимальная производительность — но Flutter уже даёт нативную компиляцию

### React Native

- **Против:** менее стабильный BLE, нет зрелого Web-билда для Max
- **За:** JavaScript экосистема — но Dart не хуже для фронтенда

---

## Связанные решения

- **ADR-001 §2** — подтверждается (Flutter 3), дополняется стратегией Max Mini App
- **ADR-001 §3** — headless-постамат: BLE для Android, серверная модель для Max
- **ADR-003** — аппаратная платформа: BLE нужен для Android-приложения + сервисного доступа

---

## Ссылки

### Flutter
- [Flutter](https://flutter.dev/)
- [flutter_blue_plus](https://pub.dev/packages/flutter_blue_plus)
- [nfc_manager](https://pub.dev/packages/nfc_manager)
- [Riverpod](https://riverpod.dev/)
- [Flutter Web](https://flutter.dev/multi-platform/web)

### Max Platform
- [Max для разработчиков](https://dev.max.ru/)
- [Max Bot API](https://dev.max.ru/docs-api)
- [MAX Bridge API (Mini Apps)](https://dev.max.ru/docs/webapps/bridge)
- [Max Bot Go SDK](https://github.com/max-messenger)

### Оплата и Auth
- [nikita-vanyasin/tinkoff Go SDK](https://github.com/nikita-vanyasin/tinkoff)
- [Zvonok.com FlashCall](https://zvonok.com/)
- [RuStore для разработчиков](https://developer.rustore.ru/)
