Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
260 changes: 241 additions & 19 deletions app/src/main/java/com/mezon/mobile/home/DialogsController.kt

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions app/src/main/java/com/mezon/mobile/home/chat/ChatAdapter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ class ChatAdapter(
var pollBridge: com.mezon.mobile.home.chat.poll.ChatPollBridge? = null
var shareContactOnlineResolver: ((Long) -> Boolean)? = null
var displayRoleResolver: ((Long) -> UserDisplayRole?)? = null
var senderAvatarResolver: ((MessageEntity) -> String)? = null
var senderUsernameResolver: ((MessageEntity) -> String)? = null
var onTopicClick: ((topicId: Long, rootMessageId: Long) -> Unit)? = null
var topicCreatorResolver: ((Long) -> Pair<String, String>?)? = null
var topicLastMessageIdResolver: ((Long) -> Long)? = null
Expand Down Expand Up @@ -244,6 +246,8 @@ class ChatAdapter(
holder.cell.isChannelPrivate = isChannelPrivate
val msg = messages[idx]
holder.cell.displayRoleResolver = displayRoleResolver
holder.cell.senderAvatarResolver = senderAvatarResolver
holder.cell.senderUsernameResolver = senderUsernameResolver
holder.cell.onTopicClick = onTopicClick
holder.cell.topicCreatorResolver = topicCreatorResolver
holder.cell.topicLastMessageIdResolver = topicLastMessageIdResolver
Expand Down
165 changes: 116 additions & 49 deletions app/src/main/java/com/mezon/mobile/home/chat/ChatFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ import com.mezon.mobile.home.ClanMember
import com.mezon.mobile.home.LOAD_TYPE_INITIAL
import com.mezon.mobile.home.DialogsController
import com.mezon.mobile.home.MemberResolver
import com.mezon.mobile.home.messages.DirectMessage
import com.mezon.mobile.home.messages.isSelfDmChannel
import com.mezon.mobile.home.UserClanController
import com.mezon.mobile.home.friends.FRIEND_STATE_BLOCKED
import com.mezon.mobile.home.friends.FriendController
Expand Down Expand Up @@ -490,7 +492,7 @@ open class ChatFragment : BaseFragment() {
userClanController.loadChannelMembers(clanId, channelId, CHANNEL_TYPE_CHANNEL)
Log.d(TAG, "onFragmentCreate loadChannelMembers channel clanId=$clanId channelId=$channelId")
}
} else if (channelType == CHANNEL_TYPE_GROUP) {
} else if (channelType == CHANNEL_TYPE_DM || channelType == CHANNEL_TYPE_GROUP) {
dialogsController.loadDmParticipants(channelId)
}
if (!isTopicMode) {
Expand Down Expand Up @@ -815,7 +817,9 @@ open class ChatFragment : BaseFragment() {
}
}


if (clanId == 0L && channelType == CHANNEL_TYPE_DM) {
refreshWelcomeFromDialog()
}
}
}

Expand Down Expand Up @@ -1082,6 +1086,9 @@ open class ChatFragment : BaseFragment() {
refreshDmHeaderTitleFromDialog()
refreshWelcomeFromDialog()
}
if ((mask and NotificationCenter.UPDATE_MASK_AVATAR) != 0) {
refreshMessageSenderAvatars()
}
if (
clanId != 0L &&
!isTopicMode &&
Expand Down Expand Up @@ -1233,6 +1240,10 @@ open class ChatFragment : BaseFragment() {
if (loadedChannelId == targetChannelId) {
checkSuggestionTrigger()
}
if (loadedChannelId == channelId && (channelType == CHANNEL_TYPE_DM || channelType == CHANNEL_TYPE_GROUP)) {
refreshMessageSenderAvatars()
refreshWelcomeFromDialog()
}
}

observe(NotificationCenter.closeChats) { _, _, args ->
Expand Down Expand Up @@ -1269,11 +1280,20 @@ open class ChatFragment : BaseFragment() {
if (clanId == 0L && (channelType == CHANNEL_TYPE_DM || channelType == CHANNEL_TYPE_GROUP)) {
refreshDmHeaderTitleFromDialog()
refreshWelcomeFromDialog()
refreshMessageSenderAvatars()
}
if (clanId != 0L || channelType != CHANNEL_TYPE_DM) return@observe
actionBar?.let { setupDmHeaderCallMenu(it) }
}

observe(NotificationCenter.userDataLoaded) { _, _, _ ->
if (isPaused) return@observe
if (clanId == 0L && channelType == CHANNEL_TYPE_DM) {
refreshWelcomeFromDialog()
refreshMessageSenderAvatars()
}
}

observe(NotificationCenter.jumpToMessage) { _, _, args ->
val targetChannelId = args.getOrNull(0) as? Long ?: return@observe
val targetMessageId = args.getOrNull(1) as? Long ?: return@observe
Expand Down Expand Up @@ -2065,6 +2085,8 @@ open class ChatFragment : BaseFragment() {
adapter.isChannelPrivate = resolveChannelPrivate()
adapter.currentUserId = StartupCache.userId
adapter.displayRoleResolver = chatDisplayRoleResolver()
adapter.senderAvatarResolver = { msg -> resolveMessageSenderAvatar(msg) }
adapter.senderUsernameResolver = { msg -> resolveMessageSenderUsername(msg) }
adapter.onTopicClick = { tid, rootId ->
if (!isTopicMode) openTopicDiscussion(tid, rootId)
}
Expand Down Expand Up @@ -3489,6 +3511,27 @@ open class ChatFragment : BaseFragment() {
else roleController.resolveHighestDisplayRole(clanId, userId, chatClanCreatorId())
}

private fun resolveMessageSenderAvatar(msg: MessageEntity): String {
if (msg.senderAvatar.isNotBlank()) return msg.senderAvatar
val member = memberResolver.resolveMember(msg.senderId, clanId, channelId, channelType) ?: return ""
return member.clanAvatar.ifBlank { member.avatarUrl }
}

private fun resolveMessageSenderUsername(msg: MessageEntity): String {
val member = memberResolver.resolveMember(msg.senderId, clanId, channelId, channelType)
if (member != null) {
return member.username.ifBlank { msg.senderUsername }.ifBlank {
member.displayName.ifBlank { member.clanNick }.ifBlank { msg.senderName }
}
}
return msg.senderUsername.ifBlank { msg.senderName }
}

private fun refreshMessageSenderAvatars() {
if (isPaused || fragmentView == null) return
updateVisibleRows(NotificationCenter.UPDATE_MASK_AVATAR)
}

private fun refreshChatDisplayRoleCache(refreshUi: Boolean = true) {
if (clanId == 0L) return
displayRoleCacheRefreshJob?.cancel()
Expand Down Expand Up @@ -3742,12 +3785,78 @@ open class ChatFragment : BaseFragment() {
val myId = chatController.getCurrentUserId()
if (myId == 0L) return false
val participants = dialogsController.getParticipants(channelId)
if (participants.isNotEmpty()) {
return participants.all { it.userId == myId }
val dm = dialogsController.getDialog(channelId)
return dm?.isSelfDmChannel(myId, participants, userController.username) == true
}

private fun resolveWelcomeAvatarUrl(dm: DirectMessage?): String {
dialogsController.getDialog(channelId)?.avatarUrl?.takeIf { it.isNotBlank() }?.let { return it }

val myId = chatController.getCurrentUserId()
val peerId = dm?.otherUserId?.takeIf { it != 0L && it != myId }
?: dmHeaderCallOtherUserId()?.takeIf { it != myId }
?: 0L

if (peerId != 0L) {
messages.firstOrNull { it.senderId == peerId && it.senderAvatar.isNotBlank() }?.senderAvatar?.let { return it }
memberResolver.resolveMember(peerId, 0L, channelId, CHANNEL_TYPE_DM)?.let { member ->
val avatar = member.clanAvatar.ifBlank { member.avatarUrl }
if (avatar.isNotBlank()) return avatar
}
}

messages.firstOrNull { it.senderId != myId && it.senderAvatar.isNotBlank() }?.senderAvatar?.let { return it }
return if (isDmSelfOnlyChat()) {
messages.firstOrNull { it.senderAvatar.isNotBlank() }?.senderAvatar.orEmpty()
} else {
""
}
val dm = dialogsController.getDialog(channelId) ?: return false
if (dm.otherUserId == 0L) return false
return dm.otherUserId == myId
}

private fun refreshWelcomeFromDialog() {
if (clanId != 0L || !::adapter.isInitialized) return
if (channelType != CHANNEL_TYPE_DM && channelType != CHANNEL_TYPE_GROUP) return
val dm = dialogsController.getDialog(channelId)
val nextChannelName = when (channelType) {
CHANNEL_TYPE_DM, CHANNEL_TYPE_GROUP ->
dm?.displayName?.ifBlank { dm.label }?.ifBlank { channelName } ?: channelName
else -> adapter.channelName
}
val nextAvatarUrl = resolveWelcomeAvatarUrl(dm)
val nextAvatarId = if (channelType == CHANNEL_TYPE_DM) {
when {
isDmSelfOnlyChat() -> userController.userId.takeIf { it != 0L } ?: channelId
else -> dm?.otherUserId?.takeIf { it != 0L } ?: channelId
}
} else {
channelId
}
val nextPlaceholderKey = dm?.avatarPlaceholderKey() ?: channelName
val nextPeerUsername = if (channelType == CHANNEL_TYPE_DM) dm?.username.orEmpty() else ""
if (nextAvatarUrl == lastWelcomeAvatarUrl &&
nextAvatarId == lastWelcomeAvatarId &&
nextPlaceholderKey == lastWelcomePlaceholderKey &&
nextPeerUsername == lastWelcomePeerUsername &&
nextChannelName == lastWelcomeChannelName
) {
return
}
lastWelcomeAvatarUrl = nextAvatarUrl
lastWelcomeAvatarId = nextAvatarId
lastWelcomePlaceholderKey = nextPlaceholderKey
lastWelcomePeerUsername = nextPeerUsername
lastWelcomeChannelName = nextChannelName
if ((channelType == CHANNEL_TYPE_DM || channelType == CHANNEL_TYPE_GROUP) && nextChannelName.isNotEmpty()) {
adapter.channelName = nextChannelName
}
adapter.welcomeAvatarUrl = nextAvatarUrl
adapter.welcomeAvatarId = nextAvatarId
adapter.welcomePlaceholderKey = nextPlaceholderKey
adapter.welcomePeerUsername = nextPeerUsername
if (nextAvatarUrl.isNotBlank() && dm?.avatarUrl.isNullOrBlank()) {
dialogsController.noteDmAvatarHint(channelId, nextAvatarUrl)
}
adapter.notifyWelcomeCellChanged()
}

private fun refreshDmHeaderTitleFromDialog() {
Expand Down Expand Up @@ -3844,48 +3953,6 @@ open class ChatFragment : BaseFragment() {
return fallbackName
}

private fun refreshWelcomeFromDialog() {
if (clanId != 0L || !::adapter.isInitialized) return
if (channelType != CHANNEL_TYPE_DM && channelType != CHANNEL_TYPE_GROUP) return
val dm = dialogsController.getDialog(channelId)
val nextChannelName = if (channelType == CHANNEL_TYPE_DM) {
dm?.displayName?.ifBlank { dm.label }?.ifBlank { channelName } ?: channelName
} else if (channelType == CHANNEL_TYPE_GROUP) {
dm?.displayName?.ifBlank { dm.label }?.ifBlank { channelName } ?: channelName
} else {
adapter.channelName
}
val nextAvatarUrl = dm?.avatarUrl.orEmpty()
val nextAvatarId = if (channelType == CHANNEL_TYPE_DM) {
dm?.otherUserId?.takeIf { it != 0L } ?: channelId
} else {
channelId
}
val nextPlaceholderKey = dm?.avatarPlaceholderKey() ?: channelName
val nextPeerUsername = if (channelType == CHANNEL_TYPE_DM) dm?.username.orEmpty() else ""
if (nextAvatarUrl == lastWelcomeAvatarUrl &&
nextAvatarId == lastWelcomeAvatarId &&
nextPlaceholderKey == lastWelcomePlaceholderKey &&
nextPeerUsername == lastWelcomePeerUsername &&
nextChannelName == lastWelcomeChannelName
) {
return
}
lastWelcomeAvatarUrl = nextAvatarUrl
lastWelcomeAvatarId = nextAvatarId
lastWelcomePlaceholderKey = nextPlaceholderKey
lastWelcomePeerUsername = nextPeerUsername
lastWelcomeChannelName = nextChannelName
if ((channelType == CHANNEL_TYPE_DM || channelType == CHANNEL_TYPE_GROUP) && nextChannelName.isNotEmpty()) {
adapter.channelName = nextChannelName
}
adapter.welcomeAvatarUrl = nextAvatarUrl
adapter.welcomeAvatarId = nextAvatarId
adapter.welcomePlaceholderKey = nextPlaceholderKey
adapter.welcomePeerUsername = nextPeerUsername
adapter.notifyWelcomeCellChanged()
}

private fun dmHeaderCallOtherUserId(): Long? {
val myId = chatController.getCurrentUserId()
dialogsController.getParticipants(channelId).firstOrNull { it.userId != myId }?.let { return it.userId }
Expand Down
33 changes: 23 additions & 10 deletions app/src/main/java/com/mezon/mobile/home/chat/ChatMessageCell.kt
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,8 @@ class ChatMessageCell(context: Context, private val theme: ThemeColors) : BaseCe
var topicCreatorResolver: ((Long) -> Pair<String, String>?)? = null
var topicLastMessageIdResolver: ((Long) -> Long)? = null
var topicBadgeResolver: ((Long) -> Int)? = null
var senderAvatarResolver: ((MessageEntity) -> String)? = null
var senderUsernameResolver: ((MessageEntity) -> String)? = null
var onTopicClick: ((topicId: Long, rootMessageId: Long) -> Unit)? = null
var topicButtonEnabled = true
private var shareContactParsed: ShareContactData? = null
Expand Down Expand Up @@ -517,9 +519,8 @@ class ChatMessageCell(context: Context, private val theme: ThemeColors) : BaseCe
buildLayouts(msg)
if (!isCombined) {
val isAnon = msg.senderId == ANONYMOUS_USER_ID
val avatarUsername = if (isAnon) "Anonymous" else msg.senderName.ifBlank { msg.senderUsername }
avatarDrawable.setInfo(msg.senderId, avatarUsername)
if (isAnon) loadAnonymousAvatar() else loadAvatar(msg.senderAvatar)
avatarDrawable.setInfo(msg.senderId, resolveSenderAvatarPlaceholder(msg))
if (isAnon) loadAnonymousAvatar() else loadAvatar(resolveSenderAvatar(msg))
}
if (drawPhotoImage) loadPhotoImage(msg) else clearPhotoReceivers()
buildTopicButton(msg, width)
Expand Down Expand Up @@ -568,18 +569,19 @@ class ChatMessageCell(context: Context, private val theme: ThemeColors) : BaseCe
rebuildLayout = true
}
val isAnon = msg.senderId == ANONYMOUS_USER_ID
val avatarUsername = if (isAnon) "Anonymous" else msg.senderName.ifBlank { msg.senderUsername }
avatarDrawable.setInfo(msg.senderId, avatarUsername)
avatarDrawable.setInfo(msg.senderId, resolveSenderAvatarPlaceholder(msg))
needInvalidate = true
}

if ((mask and NotificationCenter.UPDATE_MASK_AVATAR) != 0) {
if (!isCombined && messageEntity?.senderAvatar != msg.senderAvatar) {
if (!isCombined) {
Comment thread
kind6688 marked this conversation as resolved.
val isAnon = msg.senderId == ANONYMOUS_USER_ID
val avatarUsername = if (isAnon) "Anonymous" else msg.senderName.ifBlank { msg.senderUsername }
avatarDrawable.setInfo(msg.senderId, avatarUsername)
if (isAnon) loadAnonymousAvatar() else loadAvatar(msg.senderAvatar)
needInvalidate = true
val nextUrl = if (isAnon) "" else resolveSenderAvatar(msg)
if (nextUrl != currentAvatarUrl || !avatarDrawable.hasPhoto()) {
avatarDrawable.setInfo(msg.senderId, resolveSenderAvatarPlaceholder(msg))
if (isAnon) loadAnonymousAvatar() else loadAvatar(nextUrl)
needInvalidate = true
}
}
}

Expand Down Expand Up @@ -1865,6 +1867,17 @@ class ChatMessageCell(context: Context, private val theme: ThemeColors) : BaseCe
private var avatarLoadStartTime = 0L
private var avatarFallbackVisible = false

private fun resolveSenderAvatar(msg: MessageEntity): String {
val resolved = senderAvatarResolver?.invoke(msg).orEmpty()
return resolved.ifBlank { msg.senderAvatar }
}

private fun resolveSenderAvatarPlaceholder(msg: MessageEntity): String {
if (msg.senderId == ANONYMOUS_USER_ID) return "Anonymous"
val resolved = senderUsernameResolver?.invoke(msg).orEmpty()
return resolved.ifBlank { msg.senderUsername }.ifBlank { msg.senderName }
}

private fun loadAvatar(url: String) {
if (url == currentAvatarUrl && avatarDrawable.hasPhoto()) return
currentAvatarUrl = url
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,16 @@ class WelcomeMessageCell(context: Context, private val theme: ThemeColors) : Vie
private val isThread: Boolean get() = !isDM && channelType == CHANNEL_TYPE_THREAD
private val showDmAvatar: Boolean get() = isDM

private var lastLoadedAvatarUrl: String? = null

fun update(msg: MessageEntity) {
messageEntity = msg
iconDrawable = resolveChannelIcon()
if (avatarUrl != lastLoadedAvatarUrl) {
avatarLoadState.cancel()
avatarLoadState.loadKey = null
lastLoadedAvatarUrl = avatarUrl
}
loadAvatar()
requestLayout()
invalidate()
Expand Down Expand Up @@ -91,7 +98,7 @@ class WelcomeMessageCell(context: Context, private val theme: ThemeColors) : Vie
avatarDrawable.setPhoto(null)
return
}
val placeholderKey = avatarPlaceholderKey.ifEmpty { channelName }
val placeholderKey = avatarPlaceholderKey.ifEmpty { peerUsername.ifEmpty { channelName } }
val avatarId = if (avatarUserId != 0L) avatarUserId else channelName.hashCode().toLong()
loadChannelAvatar(
context,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ import com.mezon.mobile.home.UserClanController
import com.mezon.mobile.home.clans.ChannelController
import com.mezon.mobile.home.clans.ChannelPermissionController
import com.mezon.mobile.home.clans.PermissionPolicy

import com.mezon.mobile.home.messages.isSelfDmChannel
import com.mezon.mobile.home.clans.CHANNEL_TYPE_APP
import com.mezon.mobile.home.clans.CHANNEL_TYPE_STREAMING
import com.mezon.mobile.home.profile.UserController
Expand Down Expand Up @@ -124,8 +124,12 @@ class ChannelInfoFragment : BaseFragment() {
private val isDm get() = channelType == CHANNEL_TYPE_DM || channelType == CHANNEL_TYPE_GROUP
private val isSelfDm: Boolean
get() = channelType == CHANNEL_TYPE_DM && run {
val participants = dialogsController.getParticipants(channelId)
participants.size <= 1 || participants.all { it.userId == userController.userId }
val dm = dialogsController.getDialog(channelId)
dm?.isSelfDmChannel(
userController.userId,
dialogsController.getParticipants(channelId),
userController.username
) == true
}

override fun onFragmentCreate(): Boolean {
Expand Down Expand Up @@ -163,6 +167,11 @@ class ChannelInfoFragment : BaseFragment() {
refreshDmHeader()
}

observe(NotificationCenter.userDataLoaded) { _, _, _ ->
if (isPaused) return@observe
if (isDm) refreshDmHeader()
}

observe(NotificationCenter.pinMessagesDidLoad) { _, _, args ->
if (isPaused) return@observe
val ch = args.firstOrNull() as? Long ?: return@observe
Expand Down Expand Up @@ -341,8 +350,8 @@ class ChannelInfoFragment : BaseFragment() {
val avatarContainer = FrameLayout(context)

val dm = dialogsController.getDialog(channelId)
val avatarUrl = dm?.avatarUrl ?: ""
val displayName = dm?.displayName?.ifBlank { dm.label } ?: channelName
val avatarUrl = dm?.avatarUrl.orEmpty()

val placeholderKey = dm?.avatarPlaceholderKey() ?: displayName
val av = DmHeaderAvatarView(context).apply {
Expand Down
Loading