From 94e79e56306ba89a882f84cd2c6831ea25aa75af Mon Sep 17 00:00:00 2001 From: evgeny Date: Mon, 19 Oct 2026 15:20:25 +0200 Subject: [PATCH] Color Android member phone actions using live call presence --- .../main/java/com/utun/chat/data/ChatRepository.kt | 3 +++ .../com/utun/chat/ui/screens/MemberListScreen.kt | 6 +++++- .../test/java/com/utun/chat/data/MemberAvatarTest.kt | 12 ++++++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/ChatRepository.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/ChatRepository.kt index a63f8d73..cb8de07d 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/ChatRepository.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/ChatRepository.kt @@ -79,6 +79,7 @@ data class ChatMember(val nodeId: Long, val name: String, val online: Boolean = val isModer: Boolean = false, val isStorage: Boolean = false, val radioActive: Boolean = false, val radioMuted: Boolean = false, + val phoneStatusKnown: Boolean = false, val phoneBusy: Boolean = false, val isVerified: Boolean = false, val isDeleted: Boolean = false, val nodeType: Int = 0, val rtt: Int = 0, val isSelf: Boolean = false, @@ -216,6 +217,8 @@ fun MemberWire.toChatMember(previous: ChatMember = ChatMember(nodeId, name)): Ch isAdmin = (flags and 2) != 0, isSuper = (flags and 1) != 0, isModer = (flags and 4) != 0, isStorage = storage, isDeleted = (flags and 8) != 0, radioActive = (flags and 0x10) != 0, radioMuted = (flags and 0x20) != 0, + phoneStatusKnown = online != 0 && (flags and 0x40) != 0, + phoneBusy = online != 0 && (flags and 0xC0) == 0xC0, rtt = if (rtt == 0xFFFF) 0 else rtt, isSelf = isSelf, avatarServer = avatarServer, avatarFile = avatarFile, storageRole = storageRole ) diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/MemberListScreen.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/MemberListScreen.kt index b4e98a96..6797999a 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/MemberListScreen.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/MemberListScreen.kt @@ -278,7 +278,11 @@ private fun MemberListItem( PortableRadioIcon(label, tint = if (member.radioActive) Color(0xFF4CAF50) else LocalContentColor.current) } IconButton(onClick = onCall, enabled = member.online, modifier = actionModifier) { - Icon(Icons.Filled.Call, stringResource(R.string.call)) + val status = if (!member.online || !member.phoneStatusKnown) R.string.phone_status_unknown + else if (member.phoneBusy) R.string.phone_status_busy else R.string.phone_status_free + Icon(Icons.Filled.Call, stringResource(R.string.call) + " · " + stringResource(status), + tint = if (!member.online || !member.phoneStatusKnown) LocalContentColor.current + else if (member.phoneBusy) Color(0xFFE53935) else Color(0xFF4CAF50)) } } } diff --git a/tools/chatgui-android/app/src/test/java/com/utun/chat/data/MemberAvatarTest.kt b/tools/chatgui-android/app/src/test/java/com/utun/chat/data/MemberAvatarTest.kt index 822e7258..0fee79da 100644 --- a/tools/chatgui-android/app/src/test/java/com/utun/chat/data/MemberAvatarTest.kt +++ b/tools/chatgui-android/app/src/test/java/com/utun/chat/data/MemberAvatarTest.kt @@ -6,6 +6,18 @@ import java.nio.ByteBuffer import java.nio.ByteOrder class MemberAvatarTest { + @Test fun phonePresenceRequiresAnOnlinePeerAndAnExplicitKnownFlag() { + for ((flags, known, busy) in listOf(Triple(0, false, false), Triple(0x40, true, false), + Triple(0xC0, true, true), Triple(0x80, false, false))) { + val bytes = ByteArray(MEMBER_WIRE_SIZE) + bytes[8] = 1; bytes[10] = flags.toByte() + val online = requireNotNull(parseMemberWire(bytes)).toChatMember() + assertEquals(known, online.phoneStatusKnown); assertEquals(busy, online.phoneBusy) + bytes[8] = 0 + val offline = requireNotNull(parseMemberWire(bytes)).toChatMember(online) + assertFalse(offline.phoneStatusKnown); assertFalse(offline.phoneBusy) + } + } @Test fun snapshotStringsStopAtFirstNullBeforeDirtyPadding() { for (name in listOf("", "0x06B9", "Без имени", "x".repeat(63))) { val bytes = ByteArray(MEMBER_WIRE_SIZE)