# Avatar Implementation Guide ## Обзор Реализована полная система аватаров для Rosetta Messenger Android, совместимая с desktop версией: - ✅ P2P доставка аватаров (PacketAvatar 0x0C) - ✅ Multi-layer кэширование (Memory + SQLite + Encrypted Files) - ✅ ChaCha20-Poly1305 шифрование для передачи - ✅ Password-based шифрование для хранения - ✅ Трекинг доставки аватаров - ✅ UI компоненты (AvatarImage, AvatarPlaceholder) ## Архитектура ### 1. Crypto Layer (CryptoManager.kt) ```kotlin // Шифрование для P2P передачи val result = CryptoManager.chacha20Encrypt(base64Image) // result.key, result.nonce, result.ciphertext // Шифрование для локального хранения (пароль "rosetta-a") val encrypted = CryptoManager.encryptWithPassword(data, "rosetta-a") ``` ### 2. Database Layer - **avatar_cache**: Хранит пути к зашифрованным файлам - **avatar_delivery**: Трекинг доставки (кому отправлен аватар) ### 3. File Storage (AvatarFileManager.kt) ```kotlin // Сохранение val path = AvatarFileManager.saveAvatar(context, base64Image, publicKey) // Путь: "a/md5hash" // Чтение val base64Image = AvatarFileManager.readAvatar(context, path) // Конвертация изображения в PNG Base64 val base64Png = AvatarFileManager.imagePrepareForNetworkTransfer(context, imageBytes) ``` ### 4. Repository Layer (AvatarRepository.kt) ```kotlin val avatarRepository = AvatarRepository( context = context, avatarDao = database.avatarDao(), currentPublicKey = myPublicKey, currentPrivateKey = myPrivateKey, protocolManager = ProtocolManager ) // Получить аватары пользователя val avatars: StateFlow> = avatarRepository.getAvatars(publicKey) // Изменить свой аватар avatarRepository.changeMyAvatar(base64Image) // Отправить аватар контакту avatarRepository.sendAvatarTo(contactPublicKey) // Обработать входящий аватар avatarRepository.handleIncomingAvatar(packetAvatar) ``` ### 5. Network Layer (PacketAvatar) ```kotlin class PacketAvatar : Packet() { var privateKey: String = "" // Hash приватного ключа var fromPublicKey: String = "" // Отправитель var toPublicKey: String = "" // Получатель var chachaKey: String = "" // RSA-encrypted ChaCha20 key+nonce var blob: String = "" // ChaCha20-encrypted avatar data override fun getPacketId(): Int = 0x0C } ``` ### 6. UI Layer (AvatarImage.kt) ```kotlin @Composable fun AvatarImage( publicKey: String, avatarRepository: AvatarRepository?, size: Dp = 40.dp, isDarkTheme: Boolean, onClick: (() -> Unit)? = null, showOnlineIndicator: Boolean = false, isOnline: Boolean = false ) ``` ## Интеграция ### Шаг 1: Инициализация в Application/Activity ```kotlin class MainActivity : ComponentActivity() { private lateinit var avatarRepository: AvatarRepository override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val database = RosettaDatabase.getDatabase(applicationContext) // После авторизации пользователя avatarRepository = AvatarRepository( context = applicationContext, avatarDao = database.avatarDao(), currentPublicKey = currentAccount.publicKey, currentPrivateKey = currentAccount.privateKey, protocolManager = ProtocolManager ) // Передаем в ProtocolManager для обработки входящих пакетов ProtocolManager.setAvatarRepository(avatarRepository) } } ``` ### Шаг 2: Обновление ProtocolManager Добавьте в ProtocolManager: ```kotlin object ProtocolManager { private var avatarRepository: AvatarRepository? = null fun setAvatarRepository(repository: AvatarRepository) { avatarRepository = repository } // В setupPacketHandlers() обработчик уже добавлен: waitPacket(0x0C) { packet -> scope.launch(Dispatchers.IO) { avatarRepository?.handleIncomingAvatar(packet as PacketAvatar) } } } ``` ### Шаг 3: Использование в UI #### Отображение аватара ```kotlin @Composable fun ChatListItem( publicKey: String, avatarRepository: AvatarRepository?, isDarkTheme: Boolean ) { Row { AvatarImage( publicKey = publicKey, avatarRepository = avatarRepository, size = 48.dp, isDarkTheme = isDarkTheme, showOnlineIndicator = true, isOnline = user.isOnline ) // ... остальной контент } } ``` #### Смена аватара ```kotlin @Composable fun ProfileScreen( avatarRepository: AvatarRepository, viewModel: ProfileViewModel ) { val launcher = rememberLauncherForActivityResult( contract = ActivityResultContracts.GetContent() ) { uri: Uri? -> uri?.let { viewModel.uploadAvatar(it, avatarRepository) } } IconButton(onClick = { launcher.launch("image/*") }) { Icon(Icons.Default.Edit, "Change Avatar") } } ``` #### ViewModel для загрузки ```kotlin class ProfileViewModel : ViewModel() { fun uploadAvatar(uri: Uri, avatarRepository: AvatarRepository) { viewModelScope.launch { try { // Читаем файл val inputStream = context.contentResolver.openInputStream(uri) val imageBytes = inputStream?.readBytes() inputStream?.close() // Конвертируем в PNG Base64 val base64Png = AvatarFileManager.imagePrepareForNetworkTransfer( context, imageBytes!! ) // Сохраняем avatarRepository.changeMyAvatar(base64Png) // Показываем успех _uiState.value = _uiState.value.copy( showSuccess = true, successMessage = "Avatar updated" ) } catch (e: Exception) { _uiState.value = _uiState.value.copy( showError = true, errorMessage = e.message ) } } } } ``` ### Шаг 4: Автоматическая доставка аватаров #### В ChatDetailScreen ```kotlin LaunchedEffect(opponentPublicKey) { // Проверяем нужно ли отправить аватар val isDelivered = avatarRepository.isAvatarDelivered(opponentPublicKey) val hasAvatar = avatarRepository.getLatestAvatar(currentPublicKey) != null if (!isDelivered && hasAvatar) { // Показываем prompt или отправляем автоматически avatarRepository.sendAvatarTo(opponentPublicKey) } } ``` ## Тестирование ### 1. Локальное тестирование ```kotlin // В тестовом классе @Test fun testAvatarEncryptionRoundTrip() { val originalData = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==" // Encrypt val result = CryptoManager.chacha20Encrypt(originalData) // Decrypt val decrypted = CryptoManager.chacha20Decrypt( result.ciphertext, result.nonce, result.key ) assertEquals(originalData, decrypted) } @Test fun testPasswordEncryption() { val data = "test image data" val password = "rosetta-a" val encrypted = CryptoManager.encryptWithPassword(data, password) val decrypted = CryptoManager.decryptWithPassword(encrypted, password) assertEquals(data, decrypted) } ``` ### 2. P2P тестирование 1. Установите приложение на 2 устройства/эмулятора 2. Авторизуйтесь с разными аккаунтами 3. Установите аватар на первом устройстве 4. Откройте чат с первым пользователем на втором устройстве 5. Проверьте что аватар отобразился ### 3. Проверка в Logcat ```bash adb logcat -s Protocol:D AvatarRepository:D ``` Ожидаемые логи: ``` Protocol: 🖼️ Received avatar from 02a1b2c3... AvatarRepository: Received avatar from 02a1b2c3... AvatarRepository: Saved avatar for 02a1b2c3... ``` ## Производительность ### Memory Management - Memory cache ограничен (хранит только активные диалоги) - Используйте `avatarRepository.clearMemoryCache()` при выходе из чата - Файлы хранятся зашифрованными (экономия памяти) ### Network - Аватары отправляются только 1 раз каждому контакту - Используется трекинг доставки (avatar_delivery таблица) - Chunking не реализован (лимит ~5MB на аватар) ### Database - WAL mode включен для производительности - Индексы на (public_key, timestamp) для быстрого поиска - Автоматическое удаление старых аватаров (хранится MAX_AVATAR_HISTORY = 5) ## Troubleshooting ### Аватары не отображаются 1. Проверьте что AvatarRepository инициализирован 2. Проверьте логи декрипции файлов 3. Проверьте наличие файлов в `context.filesDir/avatars/` ### Аватары не отправляются 1. Проверьте что PacketAvatar (0x0C) зарегистрирован в Protocol.kt 2. Проверьте что ProtocolManager имеет ссылку на AvatarRepository 3. Проверьте статус соединения (AUTHENTICATED) ### Ошибки шифрования 1. Проверьте версию Google Tink (должна быть 1.10.0+) 2. Проверьте что пароль "rosetta-a" используется везде 3. Проверьте совместимость с desktop (тестируйте кросс-платформенно) ## Будущие улучшения ### Приоритет Высокий - [ ] Интеграция AvatarRepository в MainActivity - [ ] Image Picker в AvatarPicker composable - [ ] Avatar upload UI в ProfileScreen - [ ] Automatic delivery prompt (как в desktop) ### Приоритет Средний - [ ] Avatar compression перед upload - [ ] Multiple avatar sizes (thumbnail, full) - [ ] Avatar history viewer в профиле - [ ] Group avatars support ### Приоритет Низкий - [ ] Chunking для больших файлов (>5MB) - [ ] Coil disk cache integration - [ ] Avatar delete functionality - [ ] System avatars preloading ## Совместимость ### Desktop версия ✅ ChaCha20-Poly1305 шифрование ✅ PBKDF2+AES для хранения ✅ MD5 path generation ✅ PacketAvatar структура ✅ Delivery tracking ### React Native версия ⚠️ Требует тестирования (скорее всего совместимо) ### Telegram версия ❌ Не совместимо (другой протокол)