feat: Implement avatar management system with P2P delivery

- Added AvatarRepository for handling avatar storage, retrieval, and delivery.
- Created AvatarCacheEntity and AvatarDeliveryEntity for database storage.
- Introduced PacketAvatar for P2P avatar transfer between clients.
- Enhanced RosettaDatabase to include avatar-related tables and migration.
- Developed AvatarFileManager for file operations related to avatars.
- Implemented AvatarImage composable for displaying user avatars.
- Updated ProfileScreen to support avatar selection and updating.
- Added functionality for handling incoming avatar packets in ProtocolManager.
This commit is contained in:
k1ngsterr1
2026-01-23 03:04:27 +05:00
parent 6fdad7a4c1
commit b08bea2c14
12 changed files with 1670 additions and 18 deletions

View File

@@ -0,0 +1,129 @@
package com.rosetta.messenger.database
import androidx.room.*
import kotlinx.coroutines.flow.Flow
/**
* Entity для кэша аватаров - хранит пути к зашифрованным файлам
* Совместимо с desktop версией (AvatarProvider)
*/
@Entity(
tableName = "avatar_cache",
indices = [
Index(value = ["public_key", "timestamp"])
]
)
data class AvatarCacheEntity(
@PrimaryKey(autoGenerate = true)
val id: Long = 0,
@ColumnInfo(name = "public_key")
val publicKey: String,
@ColumnInfo(name = "avatar")
val avatar: String, // Путь к файлу (формат: "a/md5hash")
@ColumnInfo(name = "timestamp")
val timestamp: Long // Unix timestamp
)
/**
* Entity для трекинга доставки аватаров
* Отслеживает кому уже был отправлен текущий аватар
*/
@Entity(
tableName = "avatar_delivery",
indices = [
Index(value = ["public_key", "account"], unique = true)
]
)
data class AvatarDeliveryEntity(
@PrimaryKey(autoGenerate = true)
val id: Long = 0,
@ColumnInfo(name = "public_key")
val publicKey: String, // Публичный ключ получателя
@ColumnInfo(name = "account")
val account: String // Публичный ключ отправителя (мой аккаунт)
)
/**
* DAO для работы с аватарами
*/
@Dao
interface AvatarDao {
// ============ Avatar Cache ============
/**
* Получить все аватары пользователя (отсортированные по времени)
*/
@Query("SELECT * FROM avatar_cache WHERE public_key = :publicKey ORDER BY timestamp DESC")
fun getAvatars(publicKey: String): Flow<List<AvatarCacheEntity>>
/**
* Получить последний аватар пользователя
*/
@Query("SELECT * FROM avatar_cache WHERE public_key = :publicKey ORDER BY timestamp DESC LIMIT 1")
suspend fun getLatestAvatar(publicKey: String): AvatarCacheEntity?
/**
* Получить последний аватар пользователя как Flow
*/
@Query("SELECT * FROM avatar_cache WHERE public_key = :publicKey ORDER BY timestamp DESC LIMIT 1")
fun getLatestAvatarFlow(publicKey: String): Flow<AvatarCacheEntity?>
/**
* Сохранить новый аватар
*/
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAvatar(avatar: AvatarCacheEntity)
/**
* Удалить все аватары пользователя (при смене аватара)
*/
@Query("DELETE FROM avatar_cache WHERE public_key = :publicKey")
suspend fun deleteAvatars(publicKey: String)
/**
* Удалить старые аватары (оставить только N последних)
*/
@Query("""
DELETE FROM avatar_cache
WHERE public_key = :publicKey
AND id NOT IN (
SELECT id FROM avatar_cache
WHERE public_key = :publicKey
ORDER BY timestamp DESC
LIMIT :keepCount
)
""")
suspend fun deleteOldAvatars(publicKey: String, keepCount: Int = 5)
// ============ Avatar Delivery ============
/**
* Проверить доставлен ли аватар контакту
*/
@Query("SELECT COUNT(*) > 0 FROM avatar_delivery WHERE public_key = :publicKey AND account = :account")
suspend fun isAvatarDelivered(publicKey: String, account: String): Boolean
/**
* Отметить аватар как доставленный
*/
@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun markAvatarDelivered(delivery: AvatarDeliveryEntity)
/**
* Удалить все записи о доставке для аккаунта (при смене аватара)
*/
@Query("DELETE FROM avatar_delivery WHERE account = :account")
suspend fun clearDeliveryForAccount(account: String)
/**
* Получить список контактов, которым доставлен аватар
*/
@Query("SELECT public_key FROM avatar_delivery WHERE account = :account")
suspend fun getDeliveredContacts(account: String): List<String>
}