Продолжение рефакторинга
This commit is contained in:
531
Architecture.md
531
Architecture.md
@@ -1,17 +1,18 @@
|
||||
# Rosetta Android — Architecture
|
||||
|
||||
> Документ описывает **текущую** архитектуру `rosetta-android` (ветка `dev`) по коду, без идеализаций.
|
||||
> Документ отражает текущее состояние `rosetta-android` (ветка `dev`) по коду на 2026-04-18.
|
||||
|
||||
## 1. Архитектурный стиль
|
||||
## 1. Архитектурный профиль
|
||||
|
||||
Приложение построено как **layered + feature-oriented** архитектура:
|
||||
- UI на Jetpack Compose (`MainActivity` + `ui/*`).
|
||||
- Бизнес-оркестрация в singleton-сервисах (`ProtocolManager`, `CallManager`, `TransportManager`, `UpdateManager`).
|
||||
- Data слой через репозитории (`MessageRepository`, `GroupRepository`, `AvatarRepository`, `AccountManager`).
|
||||
- Persistence через Room (`RosettaDatabase`).
|
||||
- Crypto изолирован в `crypto/*`.
|
||||
Приложение сейчас устроено как layered + service-oriented архитектура:
|
||||
- UI: `MainActivity` + Compose-экраны + ViewModel.
|
||||
- DI: Hilt (`@HiltAndroidApp`, `@AndroidEntryPoint`, модули в `di/AppContainer.kt`).
|
||||
- Runtime orchestration: `ProtocolManager`, `CallManager`, `TransportManager`, `UpdateManager`.
|
||||
- Session/Identity runtime state: `SessionStore`, `SessionReducer`, `IdentityStore`.
|
||||
- Data: `MessageRepository`, `GroupRepository`, `AccountManager`, `PreferencesManager`.
|
||||
- Persistence: Room (`RosettaDatabase`) + DataStore/SharedPreferences.
|
||||
|
||||
DI-контейнера (Hilt/Koin) сейчас нет: зависимости поднимаются через `object`, `getInstance(...)`, singleton-инициализацию.
|
||||
`ProtocolManager` остается крупным runtime orchestrator-объектом, но критичные зоны уже вынесены в отдельные сервисы.
|
||||
|
||||
---
|
||||
|
||||
@@ -19,136 +20,220 @@ DI-контейнера (Hilt/Koin) сейчас нет: зависимости
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph UI["UI Layer (Compose + ViewModel)"]
|
||||
A1["MainActivity"]
|
||||
A2["ui/chats/*"]
|
||||
A3["ui/auth/*"]
|
||||
A4["ui/settings/*"]
|
||||
subgraph ENTRY["Android Entry Points"]
|
||||
E1["RosettaApplication"]
|
||||
E2["MainActivity"]
|
||||
E3["RosettaFirebaseMessagingService"]
|
||||
E4["IncomingCallActivity / CallForegroundService"]
|
||||
end
|
||||
|
||||
subgraph SVC["Service Layer (Singleton orchestrators)"]
|
||||
B1["ProtocolManager"]
|
||||
B2["CallManager"]
|
||||
B3["TransportManager"]
|
||||
B4["UpdateManager"]
|
||||
B5["RosettaFirebaseMessagingService"]
|
||||
subgraph DI["Hilt Singleton Graph"]
|
||||
D1["ProtocolGateway"]
|
||||
D2["SessionCoordinator"]
|
||||
D3["IdentityGateway"]
|
||||
D4["AccountManager / PreferencesManager"]
|
||||
D5["MessageRepository / GroupRepository"]
|
||||
D6["UiEntryPoint + UiDependencyAccess"]
|
||||
end
|
||||
|
||||
subgraph DATA["Data Layer"]
|
||||
C1["MessageRepository"]
|
||||
C2["GroupRepository"]
|
||||
C3["AvatarRepository"]
|
||||
C4["AccountManager / PreferencesManager"]
|
||||
C5["DraftManager / ForwardManager"]
|
||||
subgraph SESSION["Session / Identity Runtime"]
|
||||
S1["SessionStore"]
|
||||
S2["SessionReducer"]
|
||||
S3["IdentityStore"]
|
||||
S4["AppSessionCoordinator"]
|
||||
end
|
||||
|
||||
subgraph DB["Persistence Layer"]
|
||||
D1["Room: RosettaDatabase"]
|
||||
D2["DAO: message/dialog/group/etc"]
|
||||
subgraph NET["Network Runtime"]
|
||||
N1["ProtocolManager"]
|
||||
N2["Protocol"]
|
||||
N3["PacketSubscriptionRegistry"]
|
||||
N4["ReadyPacketGate"]
|
||||
end
|
||||
|
||||
subgraph NET["Network Layer"]
|
||||
E1["Protocol (WebSocket)"]
|
||||
E2["Packet* codec"]
|
||||
E3["OkHttp HTTP (transport/update)"]
|
||||
E4["WebRTC"]
|
||||
subgraph DATA["Data + Persistence"]
|
||||
R1["MessageRepository"]
|
||||
R2["GroupRepository"]
|
||||
R3["Room: RosettaDatabase"]
|
||||
end
|
||||
|
||||
subgraph CRYPTO["Crypto Layer"]
|
||||
F1["CryptoManager"]
|
||||
F2["MessageCrypto"]
|
||||
F3["XChaCha20E2EE (calls)"]
|
||||
end
|
||||
|
||||
UI --> SVC
|
||||
UI --> DATA
|
||||
SVC --> DATA
|
||||
SVC --> NET
|
||||
DATA --> DB
|
||||
DATA --> CRYPTO
|
||||
SVC --> CRYPTO
|
||||
ENTRY --> DI
|
||||
DI --> SESSION
|
||||
DI --> NET
|
||||
DI --> DATA
|
||||
SESSION --> NET
|
||||
DATA --> NET
|
||||
DATA --> R3
|
||||
NET --> N2
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Главные модули и ответственность
|
||||
## 3. DI и composition root
|
||||
|
||||
### 3.1 `MainActivity` (composition root)
|
||||
### 3.1 Hilt
|
||||
- `RosettaApplication` помечен `@HiltAndroidApp`.
|
||||
- Entry points уровня Android-компонентов: `MainActivity`, `IncomingCallActivity`, `CallForegroundService`, `RosettaFirebaseMessagingService`.
|
||||
- Основные модули:
|
||||
- `AppDataModule`: `AccountManager`, `PreferencesManager`.
|
||||
- `AppGatewayModule`: биндинги `ProtocolGateway`, `SessionCoordinator`, `IdentityGateway`.
|
||||
|
||||
`MainActivity` — главный orchestration entrypoint приложения:
|
||||
- поднимает `ProtocolManager.initialize(...)`, `CallManager.initialize(...)`;
|
||||
- управляет auth-гейтингом, онбордингом и основным nav-stack (`Screen`);
|
||||
- привязывает текущий аккаунт к runtime-сервисам;
|
||||
- триггерит fast reconnect на `onResume`;
|
||||
- следит за разрешениями (уведомления, fullscreen intent и т.д.).
|
||||
|
||||
### 3.2 `RosettaApplication`
|
||||
|
||||
На старте процесса инициализирует глобальные подсистемы:
|
||||
- crash reporting,
|
||||
- draft manager,
|
||||
- transport manager,
|
||||
- update manager.
|
||||
|
||||
### 3.3 Репозитории
|
||||
|
||||
#### `MessageRepository`
|
||||
Ключевой data-центр для чатов:
|
||||
- инициализация аккаунт-контекста (`publicKey/privateKey`);
|
||||
- отправка сообщений (optimistic insert + сетевой send);
|
||||
- обработка входящих `PacketMessage`/`PacketDelivery`/`PacketRead`;
|
||||
- обновление `dialogs`, `messages`, `message_search_index`;
|
||||
- синк timestamp (`accounts_sync_times`);
|
||||
- шина событий для UI (`newMessageEvents`, `deliveryStatusEvents`).
|
||||
|
||||
#### `GroupRepository`
|
||||
- хранение и операции по группам,
|
||||
- интеграция с `PacketGroup*`.
|
||||
|
||||
#### `AvatarRepository`
|
||||
- кэш/история аватаров (Room + file storage),
|
||||
- реактивная выдача аватаров через `Flow`.
|
||||
|
||||
#### `AccountManager`
|
||||
- хранение аккаунтов в DataStore,
|
||||
- last logged public/private hash в SharedPreferences для быстрых синхронных чтений.
|
||||
### 3.2 UI bridge для не-Hilt классов
|
||||
Часть UI/VM пока получает зависимости через `UiDependencyAccess` -> `UiEntryPoint`.
|
||||
Это transitional-слой, чтобы не тащить singleton `getInstance(...)` в UI и постепенно перейти на чистый constructor injection.
|
||||
|
||||
---
|
||||
|
||||
## 4. Сетевой стек
|
||||
## 4. Session lifecycle: единый source of truth
|
||||
|
||||
## 4.1 `Protocol` (низкий уровень)
|
||||
### 4.1 Модель состояния
|
||||
`SessionState`:
|
||||
- `LoggedOut`
|
||||
- `AuthInProgress(publicKey?, reason)`
|
||||
- `Ready(account, reason)`
|
||||
|
||||
`Protocol` отвечает за:
|
||||
- WebSocket lifecycle (`DISCONNECTED/CONNECTING/CONNECTED/HANDSHAKING/DEVICE_VERIFICATION_REQUIRED/AUTHENTICATED`),
|
||||
- heartbeat,
|
||||
- reconnect/backoff,
|
||||
- packet encode/decode,
|
||||
- `waitPacket/unwaitPacket` обработчики,
|
||||
- queue pre-handshake пакетов.
|
||||
### 4.2 Модель событий
|
||||
`SessionAction`:
|
||||
- `LoggedOut`
|
||||
- `AuthInProgress`
|
||||
- `Ready`
|
||||
- `SyncFromCachedAccount`
|
||||
|
||||
Ключевые механизмы устойчивости:
|
||||
- `lifecycleMutex` для serialized lifecycle операций,
|
||||
- `connectionGeneration` для игнора stale callbacks старых сокетов,
|
||||
- guard `isConnecting` от параллельного `connect()`.
|
||||
### 4.3 Контур изменения состояния
|
||||
- Только `SessionStore` владеет `MutableStateFlow<SessionState>`.
|
||||
- Только `SessionReducer` вычисляет next-state.
|
||||
- `SessionCoordinator`/`AppSessionCoordinator` больше не мутируют состояние напрямую, а делают `dispatch(action)`.
|
||||
- `SessionStore.dispatch(...)` синхронно обновляет `IdentityStore` для консистентности account/profile/auth-runtime.
|
||||
|
||||
## 4.2 `ProtocolManager` (верхний уровень)
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["AuthFlow / MainActivity / Unlock / SetPassword"] --> B["SessionCoordinator.dispatch(action)"]
|
||||
B --> C["SessionStore.dispatch(action)"]
|
||||
C --> D["SessionReducer.reduce(current, action)"]
|
||||
D --> E["StateFlow<SessionState>"]
|
||||
C --> F["IdentityStore sync"]
|
||||
```
|
||||
|
||||
`ProtocolManager` — orchestration-слой над `Protocol`:
|
||||
- единая точка для UI/Data слоёв (`send`, `authenticate`, `reconnect`, `waitPacket` и т.д.);
|
||||
- bootstrap после auth (sync, own-profile resolve, push subscribe);
|
||||
- маршрутизация входящих packet-ов в репозитории/подсистемы;
|
||||
- call signaling bridge для `CallManager`;
|
||||
- управление typed caches (`SearchUser`, user info cache).
|
||||
### 4.4 State machine
|
||||
|
||||
Новый runtime-дизайн connection orchestration:
|
||||
- `ProtocolConnectionModels.kt` — `ConnectionLifecycleState`, `ConnectionEvent`, bootstrap context;
|
||||
- `ProtocolConnectionSupervisor.kt` — actor/event-loop для serialized событий;
|
||||
- `ReadyPacketGate.kt` — очередь пакетов до состояния `READY` (TTL + max size).
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> LoggedOut
|
||||
LoggedOut --> AuthInProgress: dispatch(AuthInProgress)
|
||||
AuthInProgress --> Ready: dispatch(Ready)
|
||||
AuthInProgress --> LoggedOut: dispatch(LoggedOut)
|
||||
Ready --> LoggedOut: dispatch(LoggedOut)
|
||||
Ready --> Ready: dispatch(SyncFromCachedAccount(account))
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Lifecycle состояния соединения (верхний уровень)
|
||||
## 5. Network orchestration после декомпозиции
|
||||
|
||||
`ProtocolManager` теперь делегирует отдельные зоны ответственности:
|
||||
- `ConnectionOrchestrator`: connect/reconnect/authenticate + network-aware поведение.
|
||||
- `BootstrapCoordinator`: пересчет lifecycle (`AUTHENTICATED`/`BOOTSTRAPPING`/`READY`) и работа с `ReadyPacketGate`.
|
||||
- `PacketRouter`: user/search cache + resolve/search continuation routing.
|
||||
- `OwnProfileSyncService`: применение собственного профиля из search и синхронизация `IdentityStore`.
|
||||
- `RetryQueueService`: retry очереди отправки `PacketMessage`.
|
||||
- `PacketSubscriptionRegistry`: централизованные подписки на пакеты и fan-out.
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
PM["ProtocolManager"] --> CO["ConnectionOrchestrator"]
|
||||
PM --> BC["BootstrapCoordinator"]
|
||||
PM --> PR["PacketRouter"]
|
||||
PM --> OPS["OwnProfileSyncService"]
|
||||
PM --> RQ["RetryQueueService"]
|
||||
PM --> PSR["PacketSubscriptionRegistry"]
|
||||
PM --> SUP["ProtocolConnectionSupervisor"]
|
||||
PM --> RPG["ReadyPacketGate"]
|
||||
PM --> P["Protocol (WebSocket + packet codec)"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Централизация packet-subscriptions
|
||||
|
||||
Проблема дублирующихся low-level подписок закрыта через `PacketSubscriptionRegistry`:
|
||||
- На каждый `packetId` создается один bus и один bridge на `Protocol.waitPacket(...)`.
|
||||
- Дальше packet fan-out идет в:
|
||||
- callback API (`waitPacket/unwaitPacket`),
|
||||
- `SharedFlow` (`packetFlow(packetId)`).
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Feature as Feature/Service
|
||||
participant PM as ProtocolManager
|
||||
participant REG as PacketSubscriptionRegistry
|
||||
participant P as Protocol
|
||||
|
||||
Feature->>PM: waitPacket(0x03, callback)
|
||||
PM->>REG: addCallback(0x03, callback)
|
||||
REG->>P: waitPacket(0x03, protocolBridge) [once per packetId]
|
||||
|
||||
P-->>REG: Packet(0x03)
|
||||
REG-->>Feature: callback(packet)
|
||||
REG-->>Feature: packetFlow(0x03).emit(packet)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Отправка сообщений: use-cases + retry
|
||||
|
||||
Из `ChatViewModel` выделены use-cases:
|
||||
- `SendTextMessageUseCase`
|
||||
- `SendMediaMessageUseCase`
|
||||
- `SendForwardUseCase`
|
||||
|
||||
Текущий поток:
|
||||
1. ViewModel готовит command и шифрованный payload.
|
||||
2. UseCase собирает `PacketMessage`.
|
||||
3. UseCase вызывает `protocolGateway.sendMessageWithRetry(packet)`.
|
||||
4. `ProtocolManager` регистрирует пакет в `RetryQueueService` и отправляет в сеть.
|
||||
5. Если lifecycle еще не `READY`, пакет попадает в `ReadyPacketGate` и flush после `READY`.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
VM["ChatViewModel"] --> UC["Send*UseCase"]
|
||||
UC --> GW["ProtocolGateway.sendMessageWithRetry"]
|
||||
GW --> PM["ProtocolManager"]
|
||||
PM --> RQ["RetryQueueService"]
|
||||
PM --> RG["ReadyPacketGate"]
|
||||
PM --> P["Protocol.sendPacket"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Auth/bootstrap: фактический runtime flow
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant UI as Auth UI (SetPassword/Unlock)
|
||||
participant SC as SessionCoordinatorImpl
|
||||
participant SS as SessionStore
|
||||
participant PG as ProtocolGateway
|
||||
participant PM as ProtocolManager
|
||||
participant AM as AccountManager
|
||||
|
||||
UI->>SC: bootstrapAuthenticatedSession(account, reason)
|
||||
SC->>SS: dispatch(AuthInProgress)
|
||||
SC->>PG: initializeAccount(public, private)
|
||||
SC->>PG: connect()
|
||||
SC->>PG: authenticate(public, privateHash)
|
||||
SC->>PG: reconnectNowIfNeeded(...)
|
||||
SC->>AM: setCurrentAccount(public)
|
||||
SC->>SS: dispatch(Ready)
|
||||
|
||||
PM-->>PM: HANDSHAKE -> AUTHENTICATED -> BOOTSTRAPPING
|
||||
PM-->>PM: SyncCompleted + OwnProfileResolved
|
||||
PM-->>PM: connectionLifecycleState = READY
|
||||
```
|
||||
|
||||
Важно: `SessionState.Ready` (app-session готова) и `connectionLifecycleState = READY` (сеть готова) — это разные state-модели.
|
||||
|
||||
---
|
||||
|
||||
## 9. Состояния соединения (network lifecycle)
|
||||
|
||||
`ProtocolManager.connectionLifecycleState`:
|
||||
- `DISCONNECTED`
|
||||
@@ -159,12 +244,6 @@ flowchart TB
|
||||
- `READY`
|
||||
- `DEVICE_VERIFICATION_REQUIRED`
|
||||
|
||||
Переход в `READY` происходит только когда одновременно выполнено:
|
||||
- аккаунт инициализирован,
|
||||
- протокол аутентифицирован,
|
||||
- sync завершён,
|
||||
- own-profile резолвнут (или истёк fallback timeout).
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> DISCONNECTED
|
||||
@@ -180,203 +259,33 @@ stateDiagram-v2
|
||||
|
||||
---
|
||||
|
||||
## 6. Поток auth / session bootstrap
|
||||
## 10. Ключевые файлы новой архитектуры
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant UI as AuthFlow/MainActivity
|
||||
participant PM as ProtocolManager
|
||||
participant P as Protocol
|
||||
participant MR as MessageRepository
|
||||
|
||||
UI->>PM: initializeAccount(public, private)
|
||||
UI->>PM: connect()
|
||||
UI->>PM: authenticate(public, privateHash)
|
||||
PM->>P: connect + startHandshake
|
||||
P-->>PM: AUTHENTICATED
|
||||
PM->>PM: onAuthenticated()
|
||||
PM->>PM: subscribePushTokenIfAvailable()
|
||||
PM->>PM: requestSynchronize()
|
||||
PM->>MR: initialize(...)
|
||||
PM-->>PM: SyncCompleted + OwnProfileResolved
|
||||
PM-->>UI: connectionLifecycleState = READY
|
||||
```
|
||||
|
||||
Критично: отправка пользовательских пакетов до `READY` не теряется, а попадает в `ReadyPacketGate`.
|
||||
|
||||
---
|
||||
|
||||
## 7. Поток сообщений (send/receive/delivery)
|
||||
|
||||
## 7.1 Отправка
|
||||
1. UI (`ChatViewModel`) вызывает отправку через `MessageRepository.sendMessage(...)`.
|
||||
2. Репозиторий делает optimistic insert в `messages` (`WAITING`).
|
||||
3. Формируется `PacketMessage`.
|
||||
4. Отправка идёт в `ProtocolManager.send(...)`.
|
||||
5. Если состояние не `READY`, пакет ставится в `ReadyPacketGate`.
|
||||
6. После `READY` очередь сбрасывается в `Protocol.sendPacket(...)`.
|
||||
|
||||
## 7.2 Подтверждение доставки
|
||||
1. Сервер присылает `PacketDelivery (0x08)`.
|
||||
2. `ProtocolManager` маршрутизирует в `MessageRepository.handleDelivery(...)`.
|
||||
3. Репозиторий обновляет статус в Room.
|
||||
4. UI получает апдейт через `Flow`/события.
|
||||
|
||||
## 7.3 Входящие
|
||||
1. `PacketMessage (0x06)` приходит в `Protocol`.
|
||||
2. `ProtocolManager` dispatch → `MessageRepository.handleIncomingMessage(...)`.
|
||||
3. Сообщение сохраняется в Room, обновляется `dialogs` и индексы поиска.
|
||||
4. UI реактивно перерисовывается.
|
||||
|
||||
---
|
||||
|
||||
## 8. Sync-пайплайн
|
||||
|
||||
Используется пакет `PacketSync (0x19)` и режимы:
|
||||
- `BATCH_START`
|
||||
- поток синк-пакетов (`MESSAGE/READ/DELIVERY/...`)
|
||||
- `BATCH_END`
|
||||
- `NOT_NEEDED`
|
||||
|
||||
Особенности:
|
||||
- есть последовательная очередь inbound задач для сохранения порядка обработки;
|
||||
- after-sync hooks: retry waiting messages, request missing user info;
|
||||
- sync timestamp хранится в `accounts_sync_times`.
|
||||
|
||||
---
|
||||
|
||||
## 9. Calls (WebRTC + signaling)
|
||||
|
||||
`CallManager` использует:
|
||||
- signaling пакеты: `PacketSignalPeer (0x1A)`, `PacketWebRTC (0x1B)`;
|
||||
- ICE: `PacketIceServers (0x1C)`;
|
||||
- WebRTC stack (`PeerConnectionFactory`, `PeerConnection`, audio track);
|
||||
- E2EE голоса через `XChaCha20E2EE` обвязки sender/receiver.
|
||||
|
||||
Основные фазы звонка:
|
||||
- `IDLE` → `INCOMING`/`OUTGOING` → `CONNECTING` → `ACTIVE` → `IDLE`.
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> IDLE
|
||||
IDLE --> OUTGOING
|
||||
IDLE --> INCOMING
|
||||
OUTGOING --> CONNECTING
|
||||
INCOMING --> CONNECTING
|
||||
CONNECTING --> ACTIVE
|
||||
ACTIVE --> IDLE
|
||||
OUTGOING --> IDLE
|
||||
INCOMING --> IDLE
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. Transport (вложения)
|
||||
|
||||
`TransportManager`:
|
||||
- получает transport server через `PacketRequestTransport (0x0F)` (desktop parity);
|
||||
- upload/download через OkHttp;
|
||||
- resumable download (HTTP Range);
|
||||
- трекает состояния прогресса через `StateFlow` (`uploading`, `downloading`);
|
||||
- поддерживает cancel/pause/resume через `FileDownloadManager`.
|
||||
|
||||
---
|
||||
|
||||
## 11. Push Notifications
|
||||
|
||||
`RosettaFirebaseMessagingService`:
|
||||
- обрабатывает `onNewToken` и подписку токена через `ProtocolManager`;
|
||||
- dedup пушей;
|
||||
- маршрутизация типов (`personal_message`, `group_message`, `call`, `read`);
|
||||
- очистка уведомлений по read events;
|
||||
- wake-up reconnect при silent push.
|
||||
|
||||
---
|
||||
|
||||
## 12. Обновления (SDU)
|
||||
|
||||
`UpdateManager`:
|
||||
1. запрашивает update server через `PacketRequestUpdate (0x0A)`;
|
||||
2. ходит на SDU HTTP endpoint;
|
||||
3. скачивает APK через `DownloadManager`;
|
||||
4. ведёт update state machine (`Idle/Checking/UpdateAvailable/Downloading/ReadyToInstall/Error`).
|
||||
|
||||
---
|
||||
|
||||
## 13. Persistence (Room)
|
||||
|
||||
`RosettaDatabase` (version `17`) включает:
|
||||
- `messages` — сообщения,
|
||||
- `dialogs` — диалоги + денормализованные поля для быстрых списков,
|
||||
- `message_search_index` — локальный индекс поиска,
|
||||
- `groups` — группы,
|
||||
- `pinned_messages` — закрепы,
|
||||
- `avatar_cache` — аватары,
|
||||
- `blacklist` — blacklist,
|
||||
- `accounts_sync_times` — sync cursor,
|
||||
- `encrypted_accounts` — аккаунты (legacy Room account storage).
|
||||
|
||||
Есть длинная цепочка миграций (4→17) с оптимизациями под производительность и денормализацию.
|
||||
|
||||
---
|
||||
|
||||
## 14. Crypto
|
||||
|
||||
- `CryptoManager`:
|
||||
- seed phrase / keypair,
|
||||
- PBKDF2-derived key caching,
|
||||
- encrypt/decrypt для локального хранения.
|
||||
|
||||
- `MessageCrypto`:
|
||||
- message-level XChaCha20-Poly1305,
|
||||
- ECDH/AES-обмен ключом для payload,
|
||||
- attachment decrypt logic.
|
||||
|
||||
Crypto и network связаны через `MessageRepository`/`ProtocolManager`.
|
||||
|
||||
---
|
||||
|
||||
## 15. Наблюдаемость и диагностика
|
||||
|
||||
- wire/protocol логи через `ProtocolManager.addLog(...)` + trace file;
|
||||
- debug logs доступны в UI;
|
||||
- отдельные диагностические логи для звонков (`CallManager` breadcrumbs).
|
||||
|
||||
---
|
||||
|
||||
## 16. Текущие архитектурные сильные стороны
|
||||
|
||||
- Реактивная модель состояния (`StateFlow`) на большинстве критических путей.
|
||||
- Сильная декомпозиция packet протокола (`Packet*`).
|
||||
- Наличие ready-gate и serialized supervisor снижает race-condition в соединении.
|
||||
- Room + денормализация ускоряют списки чатов/поиск.
|
||||
|
||||
---
|
||||
|
||||
## 17. Текущие архитектурные риски
|
||||
|
||||
- `MainActivity` остаётся очень крупным composition root.
|
||||
- `ProtocolManager` и `MessageRepository` всё ещё крупные “god objects”.
|
||||
- Отсутствие DI усложняет управляемость зависимостей/тестируемость.
|
||||
- Часть жизненного цикла связана через runtime singleton state, что повышает риск регрессий при эволюции.
|
||||
|
||||
---
|
||||
|
||||
## 18. Карта ключевых файлов
|
||||
|
||||
- `app/src/main/java/com/rosetta/messenger/MainActivity.kt`
|
||||
- `app/src/main/java/com/rosetta/messenger/RosettaApplication.kt`
|
||||
- `app/src/main/java/com/rosetta/messenger/network/Protocol.kt`
|
||||
- `app/src/main/java/com/rosetta/messenger/di/AppContainer.kt`
|
||||
- `app/src/main/java/com/rosetta/messenger/di/UiEntryPoint.kt`
|
||||
- `app/src/main/java/com/rosetta/messenger/session/AppSessionCoordinator.kt`
|
||||
- `app/src/main/java/com/rosetta/messenger/session/SessionStore.kt`
|
||||
- `app/src/main/java/com/rosetta/messenger/session/SessionReducer.kt`
|
||||
- `app/src/main/java/com/rosetta/messenger/session/SessionAction.kt`
|
||||
- `app/src/main/java/com/rosetta/messenger/session/IdentityStore.kt`
|
||||
- `app/src/main/java/com/rosetta/messenger/network/ProtocolManager.kt`
|
||||
- `app/src/main/java/com/rosetta/messenger/network/PacketSubscriptionRegistry.kt`
|
||||
- `app/src/main/java/com/rosetta/messenger/network/ProtocolConnectionModels.kt`
|
||||
- `app/src/main/java/com/rosetta/messenger/network/ProtocolConnectionSupervisor.kt`
|
||||
- `app/src/main/java/com/rosetta/messenger/network/ReadyPacketGate.kt`
|
||||
- `app/src/main/java/com/rosetta/messenger/network/CallManager.kt`
|
||||
- `app/src/main/java/com/rosetta/messenger/network/TransportManager.kt`
|
||||
- `app/src/main/java/com/rosetta/messenger/push/RosettaFirebaseMessagingService.kt`
|
||||
- `app/src/main/java/com/rosetta/messenger/update/UpdateManager.kt`
|
||||
- `app/src/main/java/com/rosetta/messenger/data/MessageRepository.kt`
|
||||
- `app/src/main/java/com/rosetta/messenger/database/RosettaDatabase.kt`
|
||||
- `app/src/main/java/com/rosetta/messenger/crypto/CryptoManager.kt`
|
||||
- `app/src/main/java/com/rosetta/messenger/crypto/MessageCrypto.kt`
|
||||
- `app/src/main/java/com/rosetta/messenger/network/connection/ConnectionOrchestrator.kt`
|
||||
- `app/src/main/java/com/rosetta/messenger/network/connection/BootstrapCoordinator.kt`
|
||||
- `app/src/main/java/com/rosetta/messenger/network/connection/PacketRouter.kt`
|
||||
- `app/src/main/java/com/rosetta/messenger/network/connection/OwnProfileSyncService.kt`
|
||||
- `app/src/main/java/com/rosetta/messenger/network/connection/RetryQueueService.kt`
|
||||
- `app/src/main/java/com/rosetta/messenger/ui/chats/usecase/SendTextMessageUseCase.kt`
|
||||
- `app/src/main/java/com/rosetta/messenger/ui/chats/usecase/SendMediaMessageUseCase.kt`
|
||||
- `app/src/main/java/com/rosetta/messenger/ui/chats/usecase/SendForwardUseCase.kt`
|
||||
|
||||
---
|
||||
|
||||
## 11. Что осталось как технический долг
|
||||
|
||||
- `ProtocolManager` все еще содержит много cross-cutting логики и требует дальнейшей декомпозиции.
|
||||
- Не весь UI перешел на constructor injection (`UiDependencyAccess` пока нужен для части ViewModel).
|
||||
- Часть data-слоя напрямую знает о network singleton (`ProtocolManager`) и требует окончательного разрыва через интерфейсы.
|
||||
|
||||
@@ -2657,7 +2657,7 @@ fun MessageInputBar(
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Icon(
|
||||
imageVector = if (recordMode == RecordMode.VOICE) TablerIcons.Microphone else TablerIcons.Video,
|
||||
imageVector = if (recordMode == RecordMode.VOICE) ThinMicrophoneIcon else TablerIcons.Video,
|
||||
contentDescription = null,
|
||||
tint = Color.White,
|
||||
modifier = Modifier.size(19.dp)
|
||||
@@ -2995,7 +2995,7 @@ fun MessageInputBar(
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Icon(
|
||||
imageVector = if (recordMode == RecordMode.VOICE) TablerIcons.Microphone else TablerIcons.Video,
|
||||
imageVector = if (recordMode == RecordMode.VOICE) ThinMicrophoneIcon else TablerIcons.Video,
|
||||
contentDescription = "Record message",
|
||||
tint = Color(0xFF8E8E93).copy(alpha = 0.6f),
|
||||
modifier = Modifier.size(24.dp)
|
||||
|
||||
@@ -116,3 +116,61 @@ val TelegramSendIcon: ImageVector
|
||||
horizontalLineToRelative(16f)
|
||||
}
|
||||
}.build()
|
||||
|
||||
/** Thin microphone icon matching the visual weight of Telegram-style input icons */
|
||||
val ThinMicrophoneIcon: ImageVector
|
||||
get() = ImageVector.Builder(
|
||||
name = "ThinMicrophone",
|
||||
defaultWidth = 24.dp,
|
||||
defaultHeight = 24.dp,
|
||||
viewportWidth = 24f,
|
||||
viewportHeight = 24f
|
||||
).apply {
|
||||
// Microphone capsule
|
||||
path(
|
||||
fill = null,
|
||||
stroke = SolidColor(Color.White),
|
||||
strokeLineWidth = 1.5f,
|
||||
strokeLineCap = StrokeCap.Round,
|
||||
strokeLineJoin = StrokeJoin.Round
|
||||
) {
|
||||
moveTo(9f, 5f)
|
||||
arcToRelative(3f, 3f, 0f, isMoreThanHalf = false, isPositiveArc = true, dx1 = 6f, dy1 = 0f)
|
||||
verticalLineToRelative(5f)
|
||||
arcToRelative(3f, 3f, 0f, isMoreThanHalf = false, isPositiveArc = true, dx1 = -6f, dy1 = 0f)
|
||||
close()
|
||||
}
|
||||
// U-shaped curve below capsule
|
||||
path(
|
||||
fill = null,
|
||||
stroke = SolidColor(Color.White),
|
||||
strokeLineWidth = 1.5f,
|
||||
strokeLineCap = StrokeCap.Round,
|
||||
strokeLineJoin = StrokeJoin.Round
|
||||
) {
|
||||
moveTo(5f, 10f)
|
||||
arcToRelative(7f, 7f, 0f, isMoreThanHalf = false, isPositiveArc = false, dx1 = 14f, dy1 = 0f)
|
||||
}
|
||||
// Vertical stem
|
||||
path(
|
||||
fill = null,
|
||||
stroke = SolidColor(Color.White),
|
||||
strokeLineWidth = 1.5f,
|
||||
strokeLineCap = StrokeCap.Round,
|
||||
strokeLineJoin = StrokeJoin.Round
|
||||
) {
|
||||
moveTo(12f, 17f)
|
||||
verticalLineToRelative(4f)
|
||||
}
|
||||
// Horizontal base
|
||||
path(
|
||||
fill = null,
|
||||
stroke = SolidColor(Color.White),
|
||||
strokeLineWidth = 1.5f,
|
||||
strokeLineCap = StrokeCap.Round,
|
||||
strokeLineJoin = StrokeJoin.Round
|
||||
) {
|
||||
moveTo(8f, 21f)
|
||||
horizontalLineToRelative(8f)
|
||||
}
|
||||
}.build()
|
||||
|
||||
Reference in New Issue
Block a user