Show channel member inviter in Android details
This commit is contained in:
1 parent
b6fd489b8e
commit
93c0c5bdfc
6 files changed
+104
-12
No files matched your search
@@ -92,7 +92,8 @@ data class MemberDetail(val nodeId: Long, val name: String = "",
|
||||
val directPresence: Boolean = false, val directUp: Boolean = false,
|
||||
val indirectPresence: Boolean = false, val indirectUp: Boolean = false,
|
||||
val bgpPresence: Boolean = false, val bgpRtt: Int = 0,
|
||||
val bgpNodes: Int = 0)
|
||||
val bgpNodes: Int = 0, val channelId: String = "",
|
||||
val inviterId: Long? = null, val inviterName: String = "")
|
||||
|
||||
data class MemberAddr(val family: Int = 0, val protocol: String = "", val address: String = "",
|
||||
val port: Int = 0, val socketId: Int = 0, val addrType: Int = 0, val rtt: Int = 0) {
|
||||
@@ -459,7 +460,9 @@ class ChatRepository {
|
||||
online = obj.optInt("on", 0) != 0,
|
||||
lastSeen = obj.optLong("ls", 0),
|
||||
created = obj.optLong("cr", 0),
|
||||
addresses = addrs
|
||||
addresses = addrs, channelId = channelId,
|
||||
inviterId = if (obj.isNull("inviter_id")) null else obj.getLong("inviter_id"),
|
||||
inviterName = obj.optString("inviter_name", "")
|
||||
)
|
||||
} catch (e: Exception) { Log.w("utun-gui", "getMemberDetail error", e) }
|
||||
return null
|
||||
|
||||
@@ -320,6 +320,16 @@ private fun MemberDetailCard(
|
||||
Text(stringResource(R.string.offline), fontSize = 13.sp, lineHeight = 16.sp, color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||
}
|
||||
}
|
||||
val inviter = when (detail.inviterId) {
|
||||
null -> stringResource(R.string.member_inviter_unknown)
|
||||
0L -> stringResource(R.string.member_inviter_root)
|
||||
else -> {
|
||||
val id = "[${detail.inviterId.toString(16).padStart(16, '0')}]"
|
||||
if (detail.inviterName.isNotEmpty()) "${detail.inviterName} $id" else id
|
||||
}
|
||||
}
|
||||
Text(stringResource(R.string.member_inviter, inviter), fontSize = 13.sp, lineHeight = 16.sp,
|
||||
color = MaterialTheme.colorScheme.onSurface)
|
||||
}
|
||||
if (!isSelf) TextButton(onClick = onConnect, enabled = !detail.directUp) { Text(stringResource(R.string.connect)) }
|
||||
TextButton(onClick = onEdit, enabled = canEdit) { Text(stringResource(R.string.edit)) }
|
||||
|
||||
@@ -134,6 +134,8 @@ class ChatViewModel : ViewModel() {
|
||||
|
||||
private val _selectedMember = MutableStateFlow<MemberDetail?>(null)
|
||||
val selectedMember: StateFlow<MemberDetail?> = _selectedMember
|
||||
private var memberDetailTarget: Pair<String, Long>? = null
|
||||
private var memberDetailJob: kotlinx.coroutines.Job? = null
|
||||
|
||||
private val _memberLinks = MutableStateFlow<MemberLinks>(MemberLinks())
|
||||
val memberLinks: StateFlow<MemberLinks> = _memberLinks
|
||||
@@ -313,6 +315,7 @@ class ChatViewModel : ViewModel() {
|
||||
val chId = String(data, 1, chLen)
|
||||
val cur = _currentChannel.value
|
||||
if (cur != null && cur.id == chId) refreshMembers(chId)
|
||||
refreshSelectedMemberDetail(chId)
|
||||
}
|
||||
}
|
||||
8 -> if (repo != null) refreshChannels() /* CHANNEL_PEERS_ONLINE */
|
||||
@@ -502,8 +505,9 @@ class ChatViewModel : ViewModel() {
|
||||
}
|
||||
}
|
||||
val sel = _selectedMember.value
|
||||
if (sel != null && sel.nodeId == w.nodeId)
|
||||
if (sel != null && sel.channelId == chId && sel.nodeId == w.nodeId)
|
||||
_selectedMember.value = sel.copy(online = w.online != 0)
|
||||
refreshSelectedMemberDetail(chId, w.nodeId)
|
||||
}
|
||||
25 -> { /* MEMBER_REMOVED: [ch_id_len:1][ch_id:var][node_id:8] */
|
||||
if (data == null || data.size < 10) return
|
||||
@@ -515,8 +519,10 @@ class ChatViewModel : ViewModel() {
|
||||
val cur = _currentChannel.value
|
||||
if (cur != null && cur.id == String(data, 1, cl))
|
||||
_members.value = _members.value.filter { it.nodeId != nodeId }
|
||||
if (_selectedMember.value?.nodeId == nodeId)
|
||||
if (memberDetailTarget == (chId to nodeId)) {
|
||||
memberDetailJob?.cancel(); memberDetailTarget = null
|
||||
_selectedMember.value = null
|
||||
} else refreshSelectedMemberDetail(chId, nodeId)
|
||||
}
|
||||
22 -> { /* MEMBER_DETAIL: бинарный снапшот links+sockets */
|
||||
if (data == null || data.size < 12) return
|
||||
@@ -936,6 +942,9 @@ class ChatViewModel : ViewModel() {
|
||||
|
||||
fun selectChannel(channel: Channel) {
|
||||
val r = repo ?: return
|
||||
if (_currentChannel.value?.id != channel.id) {
|
||||
memberDetailJob?.cancel(); memberDetailTarget = null; _selectedMember.value = null
|
||||
}
|
||||
_currentChannel.value = channel
|
||||
_radioActive.value = (radioChannelId == channel.id)
|
||||
if (radioChannelId != channel.id) _radioTalking.value = null
|
||||
@@ -1097,10 +1106,21 @@ class ChatViewModel : ViewModel() {
|
||||
}
|
||||
|
||||
fun showMemberDetail(channelId: String, nodeId: Long) {
|
||||
memberDetailTarget = channelId to nodeId
|
||||
refreshSelectedMemberDetail(channelId)
|
||||
}
|
||||
|
||||
/* Перечитываем родителя при изменении участника или его инвайтера, сохраняя актуальный live-статус. */
|
||||
private fun refreshSelectedMemberDetail(channelId: String, changedNodeId: Long? = null) {
|
||||
val target = memberDetailTarget ?: return
|
||||
if (target.first != channelId || _currentChannel.value?.id != channelId) return
|
||||
if (changedNodeId != null && changedNodeId != target.second && changedNodeId != _selectedMember.value?.inviterId) return
|
||||
val r = repo ?: return
|
||||
val ns = _nodeStatuses.value[nodeId]
|
||||
viewModelScope.launch {
|
||||
val d = withContext(Dispatchers.IO) { r.getMemberDetail(channelId, nodeId) }
|
||||
memberDetailJob?.cancel()
|
||||
memberDetailJob = viewModelScope.launch {
|
||||
val d = withContext(Dispatchers.IO) { r.getMemberDetail(channelId, target.second) }
|
||||
if (memberDetailTarget != target || _currentChannel.value?.id != channelId) return@launch
|
||||
val ns = _nodeStatuses.value[target.second]
|
||||
if (d != null && ns != null) _selectedMember.value = d.copy(
|
||||
online = ns.online, bestRtt = ns.bestRtt,
|
||||
directPresence = ns.directPresence, directUp = ns.directUp,
|
||||
@@ -1116,6 +1136,7 @@ class ChatViewModel : ViewModel() {
|
||||
val r = repo ?: return
|
||||
r.setMemberNick(channelId, nodeId, nick)
|
||||
refreshMembers(channelId)
|
||||
refreshSelectedMemberDetail(channelId, nodeId)
|
||||
}
|
||||
|
||||
fun setMemberFlags(channelId: String, nodeId: Long, admin: Boolean, supernode: Boolean, moder: Boolean, storage: Boolean,
|
||||
@@ -1440,6 +1461,7 @@ class ChatViewModel : ViewModel() {
|
||||
}
|
||||
|
||||
fun clearState() {
|
||||
memberDetailJob?.cancel(); memberDetailTarget = null; _selectedMember.value = null
|
||||
memberRadios.clear()
|
||||
dismissPmInvite(); _linkJoin.value = null; _joinedChannel.value = null
|
||||
_attachmentPreparations.value = emptyMap()
|
||||
|
||||
Reference in new issue
Block a user