diff --git a/app/src/main/java/com/rosetta/messenger/data/MessageRepository.kt b/app/src/main/java/com/rosetta/messenger/data/MessageRepository.kt index 7db6dbd..9c9add4 100644 --- a/app/src/main/java/com/rosetta/messenger/data/MessageRepository.kt +++ b/app/src/main/java/com/rosetta/messenger/data/MessageRepository.kt @@ -613,6 +613,22 @@ class MessageRepository private constructor(private val context: Context) { } + /** + * Наблюдать за онлайн статусом пользователя + */ + fun observeUserOnlineStatus(publicKey: String): Flow> { + val account = currentAccount ?: return flowOf(false to 0L) + + return dialogDao.observeOnlineStatus(account, publicKey) + .map { info -> + if (info != null) { + (info.isOnline == 1) to info.lastSeen + } else { + false to 0L + } + } + } + /** * Обновить информацию о пользователе в диалоге (имя, username, verified) * Вызывается когда приходит ответ на PacketSearch diff --git a/app/src/main/java/com/rosetta/messenger/database/MessageEntities.kt b/app/src/main/java/com/rosetta/messenger/database/MessageEntities.kt index 3b47796..9ecdfc6 100644 --- a/app/src/main/java/com/rosetta/messenger/database/MessageEntities.kt +++ b/app/src/main/java/com/rosetta/messenger/database/MessageEntities.kt @@ -478,6 +478,22 @@ interface DialogDao { """) suspend fun updateOnlineStatus(account: String, opponentKey: String, isOnline: Int, lastSeen: Long) + /** + * Получить онлайн статус пользователя + */ + @Query(""" + SELECT is_online, last_seen + FROM dialogs + WHERE account = :account AND opponent_key = :opponentKey + LIMIT 1 + """) + fun observeOnlineStatus(account: String, opponentKey: String): Flow + + data class OnlineStatusInfo( + @ColumnInfo(name = "is_online") val isOnline: Int, + @ColumnInfo(name = "last_seen") val lastSeen: Long + ) + /** * Удалить диалог */ diff --git a/app/src/main/java/com/rosetta/messenger/ui/settings/OtherProfileScreen.kt b/app/src/main/java/com/rosetta/messenger/ui/settings/OtherProfileScreen.kt index defe24b..7113670 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/settings/OtherProfileScreen.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/settings/OtherProfileScreen.kt @@ -15,7 +15,9 @@ import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material.icons.outlined.Block import androidx.compose.material3.* import androidx.compose.runtime.* +import com.rosetta.messenger.data.MessageRepository import com.rosetta.messenger.ui.onboarding.PrimaryBlue +import com.rosetta.messenger.ui.components.VerifiedBadge import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -60,6 +62,13 @@ fun OtherProfileScreen( val avatarColors = getAvatarColor(user.publicKey, isDarkTheme) val context = LocalContext.current + // 🟢 Наблюдаем за онлайн статусом пользователя в реальном времени + val messageRepository = remember { MessageRepository.getInstance(context) } + val onlineStatus by messageRepository.observeUserOnlineStatus(user.publicKey) + .collectAsState(initial = false to 0L) + val isOnline = onlineStatus.first + val lastSeen = onlineStatus.second + // Scroll state for collapsing header animation val density = LocalDensity.current val statusBarHeight = WindowInsets.statusBars.asPaddingValues().calculateTopPadding() @@ -153,6 +162,9 @@ fun OtherProfileScreen( name = user.title.ifEmpty { "Unknown User" }, username = user.username, publicKey = user.publicKey, + verified = user.verified, + isOnline = isOnline, + lastSeen = lastSeen, avatarColors = avatarColors, collapseProgress = collapseProgress, onBack = onBack, @@ -172,6 +184,9 @@ private fun CollapsingOtherProfileHeader( name: String, username: String, publicKey: String, + verified: Int, + isOnline: Boolean, + lastSeen: Long, avatarColors: AvatarColors, collapseProgress: Float, onBack: () -> Unit, @@ -309,7 +324,7 @@ private fun CollapsingOtherProfileHeader( } // ═══════════════════════════════════════════════════════════ - // 📝 TEXT BLOCK - Name + Online, always centered + // 📝 TEXT BLOCK - Name + Verified + Online, always centered // ═══════════════════════════════════════════════════════════ Column( modifier = Modifier @@ -323,24 +338,37 @@ private fun CollapsingOtherProfileHeader( }, horizontalAlignment = Alignment.CenterHorizontally ) { - Text( - text = name, - fontSize = nameFontSize, - fontWeight = FontWeight.SemiBold, - color = Color.White, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - modifier = Modifier.widthIn(max = 220.dp), - textAlign = TextAlign.Center - ) + // Name + Verified Badge + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center + ) { + Text( + text = name, + fontSize = nameFontSize, + fontWeight = FontWeight.SemiBold, + color = Color.White, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + textAlign = TextAlign.Center + ) + + if (verified > 0) { + Spacer(modifier = Modifier.width(4.dp)) + VerifiedBadge( + verified = verified, + size = (nameFontSize.value * 0.8f).toInt() + ) + } + } Spacer(modifier = Modifier.height(2.dp)) - // Online text - always centered + // Online/Offline status Text( - text = "online", + text = if (isOnline) "online" else "offline", fontSize = onlineFontSize, - color = Color(0xFF4CAF50) + color = if (isOnline) Color(0xFF4CAF50) else Color.White.copy(alpha = 0.6f) ) } }