More work on conversation

This commit is contained in:
Sylvain Berfini 2023-10-11 17:02:23 +02:00
parent db6f71c8cb
commit b8d8e877d7
6 changed files with 112 additions and 42 deletions

View file

@ -115,19 +115,19 @@ class ConversationFragment : GenericFragment() {
val layoutManager = LinearLayoutManager(requireContext())
binding.eventsList.layoutManager = layoutManager
viewModel.events.observe(viewLifecycleOwner) {
viewModel.events.observe(viewLifecycleOwner) { items ->
val currentCount = adapter.itemCount
adapter.submitList(it)
Log.i("$TAG Events (messages) list updated with [${it.size}] items")
if (currentCount < it.size) {
binding.eventsList.scrollToPosition(it.size - 1)
}
adapter.submitList(items)
Log.i("$TAG Events (messages) list updated with [${items.size}] items")
(view.parent as? ViewGroup)?.doOnPreDraw {
startPostponedEnterTransition()
sharedViewModel.openSlidingPaneEvent.value = Event(true)
}
if (currentCount < items.size) {
binding.eventsList.scrollToPosition(items.size - 1)
}
}
val emojisBottomSheetBehavior = BottomSheetBehavior.from(binding.emojiPicker)

View file

@ -24,6 +24,7 @@ import androidx.annotation.WorkerThread
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.R
import org.linphone.core.Address
import org.linphone.core.ChatRoom
import org.linphone.core.ChatRoomListenerStub
@ -34,7 +35,9 @@ import org.linphone.core.tools.Log
import org.linphone.ui.main.chat.model.EventLogModel
import org.linphone.ui.main.contacts.model.ContactAvatarModel
import org.linphone.ui.main.contacts.model.GroupAvatarModel
import org.linphone.utils.AppUtils
import org.linphone.utils.Event
import org.linphone.utils.LinphoneUtils
class ConversationViewModel @UiThread constructor() : ViewModel() {
companion object {
@ -55,6 +58,8 @@ class ConversationViewModel @UiThread constructor() : ViewModel() {
val isReadOnly = MutableLiveData<Boolean>()
val composingLabel = MutableLiveData<String>()
val textToSend = MutableLiveData<String>()
val chatRoomFoundEvent = MutableLiveData<Event<Boolean>>()
@ -64,6 +69,7 @@ class ConversationViewModel @UiThread constructor() : ViewModel() {
private val avatarsMap = hashMapOf<String, ContactAvatarModel>()
private val chatRoomListener = object : ChatRoomListenerStub() {
@WorkerThread
override fun onChatMessageSending(chatRoom: ChatRoom, eventLog: EventLog) {
val message = eventLog.chatMessage
Log.i("$TAG Chat message [$message] is being sent")
@ -77,10 +83,45 @@ class ConversationViewModel @UiThread constructor() : ViewModel() {
events.postValue(list)
}
@WorkerThread
override fun onChatMessageSent(chatRoom: ChatRoom, eventLog: EventLog) {
val message = eventLog.chatMessage
Log.i("$TAG Chat message [$message] has been sent")
}
@WorkerThread
override fun onIsComposingReceived(
chatRoom: ChatRoom,
remoteAddress: Address,
isComposing: Boolean
) {
Log.i(
"$TAG Remote [${remoteAddress.asStringUriOnly()}] is ${if (isComposing) "composing" else "no longer composing"}"
)
computeComposingLabel()
}
@WorkerThread
override fun onChatMessagesReceived(chatRoom: ChatRoom, eventLogs: Array<out EventLog>) {
Log.i("$TAG Received [${eventLogs.size}] new message(s)")
chatRoom.markAsRead()
computeComposingLabel()
val list = arrayListOf<EventLogModel>()
list.addAll(events.value.orEmpty())
for (eventLog in eventLogs) {
val address = if (eventLog.type == EventLog.Type.ConferenceChatMessage) {
eventLog.chatMessage?.fromAddress
} else {
eventLog.participantAddress
}
val avatarModel = getAvatarModelForAddress(address)
list.add(EventLogModel(eventLog, avatarModel))
}
events.postValue(list)
}
}
override fun onCleared() {
@ -147,6 +188,8 @@ class ConversationViewModel @UiThread constructor() : ViewModel() {
@WorkerThread
private fun configureChatRoom() {
computeComposingLabel()
isGroup.postValue(
!chatRoom.hasCapability(ChatRoom.Capabilities.OneToOne.toInt()) && chatRoom.hasCapability(
ChatRoom.Capabilities.Conference.toInt()
@ -202,20 +245,47 @@ class ConversationViewModel @UiThread constructor() : ViewModel() {
return ContactAvatarModel(fakeFriend)
}
val key = address.asStringUriOnly()
val clone = address.clone()
clone.clean()
val key = clone.asStringUriOnly()
val foundInMap = if (avatarsMap.keys.contains(key)) avatarsMap[key] else null
if (foundInMap != null) return foundInMap
val friend = coreContext.contactsManager.findContactByAddress(address)
val friend = coreContext.contactsManager.findContactByAddress(clone)
val avatar = if (friend != null) {
ContactAvatarModel(friend)
} else {
val fakeFriend = coreContext.core.createFriend()
fakeFriend.address = address
fakeFriend.address = clone
ContactAvatarModel(fakeFriend)
}
avatarsMap[address.asStringUriOnly()] = avatar
avatarsMap[key] = avatar
return avatar
}
@WorkerThread
private fun computeComposingLabel() {
var composingFriends = arrayListOf<String>()
var label = ""
for (address in chatRoom.composingAddresses) {
val avatar = getAvatarModelForAddress(address)
val name = avatar.name.value ?: LinphoneUtils.getDisplayName(address)
composingFriends.add(name)
label += "$name, "
}
if (composingFriends.size > 0) {
label = label.dropLast(2)
val format = AppUtils.getStringWithPlural(
R.plurals.conversation_composing_label,
composingFriends.size,
label
)
composingLabel.postValue(format)
} else {
composingLabel.postValue("")
}
}
}

View file

@ -36,6 +36,7 @@ import androidx.annotation.ColorRes
import androidx.annotation.DimenRes
import androidx.annotation.DrawableRes
import androidx.annotation.MainThread
import androidx.annotation.PluralsRes
import androidx.annotation.StringRes
import androidx.annotation.UiThread
import androidx.core.content.ContextCompat
@ -85,6 +86,11 @@ class AppUtils {
return coreContext.context.getString(id, args)
}
@AnyThread
fun getStringWithPlural(@PluralsRes id: Int, count: Int, value: String): String {
return coreContext.context.resources.getQuantityString(id, count, value)
}
@AnyThread @ColorInt
fun getColor(@ColorRes colorId: Int): Int {
return ContextCompat.getColor(

View file

@ -228,12 +228,6 @@ fun AvatarView.loadAccountAvatar(account: AccountModel?) {
loadImage(
data = uri,
onStart = {
// Use initials as placeholder
val initials = account.initials.value.orEmpty()
if (initials != "+") {
avatarInitials = initials
}
if (account.showTrust.value == true) {
avatarBorderColor =
resources.getColor(R.color.blue_info_500, context.theme)
@ -243,9 +237,12 @@ fun AvatarView.loadAccountAvatar(account: AccountModel?) {
avatarBorderWidth = AppUtils.getDimension(R.dimen.zero).toInt()
}
},
onSuccess = { _, _ ->
// If loading is successful, remove initials otherwise image won't be visible
avatarInitials = ""
onError = { _, _ ->
// Use initials as fallback
val initials = account.initials.value.orEmpty()
if (initials != "+") {
avatarInitials = initials
}
}
)
}
@ -253,12 +250,6 @@ fun AvatarView.loadAccountAvatar(account: AccountModel?) {
loadImage(
data = account.avatar.value,
onStart = {
// Use initials as placeholder
val initials = account.initials.value.orEmpty()
if (initials != "+") {
avatarInitials = initials
}
if (account.showTrust.value == true) {
avatarBorderColor = resources.getColor(R.color.blue_info_500, context.theme)
avatarBorderWidth = AppUtils.getDimension(R.dimen.avatar_trust_border_width).toInt()
@ -266,9 +257,12 @@ fun AvatarView.loadAccountAvatar(account: AccountModel?) {
avatarBorderWidth = AppUtils.getDimension(R.dimen.zero).toInt()
}
},
onSuccess = { _, _ ->
// If loading is successful, remove initials otherwise image won't be visible
avatarInitials = ""
onError = { _, _ ->
// Use initials as fallback
val initials = account.initials.value.orEmpty()
if (initials != "+") {
avatarInitials = initials
}
}
)
}
@ -285,12 +279,6 @@ fun AvatarView.loadContactAvatar(contact: ContactAvatarModel?) {
loadImage(
data = uri,
onStart = {
// Use initials as placeholder
val initials = contact.initials
if (initials != "+") {
avatarInitials = initials
}
when (contact.trust.value) {
ChatRoom.SecurityLevel.Unsafe -> {
avatarBorderColor =
@ -309,12 +297,13 @@ fun AvatarView.loadContactAvatar(contact: ContactAvatarModel?) {
}
}
},
onSuccess = { _, _ ->
// If loading is successful, remove initials otherwise image won't be visible
avatarInitials = ""
},
onError = { _, result ->
Log.e("[Contact Avatar Model] Can't load data: ${result.throwable}")
// Use initials as fallback
val initials = contact.initials
if (initials != "+") {
avatarInitials = initials
}
}
)
}

View file

@ -154,7 +154,7 @@
android:id="@+id/events_list"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginBottom="16dp"
android:layout_marginBottom="5dp"
app:layout_constraintTop_toBottomOf="@id/title"
app:layout_constraintBottom_toTopOf="@id/composing" />
@ -164,10 +164,11 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginBottom="12dp"
android:layout_marginBottom="5dp"
android:text="@{viewModel.composingLabel, default=`John Doe is composing...`}"
android:textSize="12sp"
android:textColor="@color/gray_main2_400"
android:visibility="gone"
android:visibility="@{viewModel.composingLabel.length() == 0 ? View.GONE : View.VISIBLE}"
app:layout_constraintTop_toBottomOf="@id/events_list"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"

View file

@ -330,6 +330,10 @@
<string name="new_conversation_create_group">Create a group conversation</string>
<string name="new_conversation_no_contact">No contact for the moment…</string>
<string name="conversation_text_field_hint">Say something…</string>
<plurals name="conversation_composing_label">
<item quantity="one">%s is composing…</item>
<item quantity="other">%s are composing…</item>
</plurals>
<string name="meetings_list_empty">No meeting for the moment…</string>
<string name="meeting_schedule_title">New meeting</string>