mirror of
https://gitlab.linphone.org/BC/public/linphone-android.git
synced 2026-01-17 11:28:06 +00:00
More work on conversation
This commit is contained in:
parent
db6f71c8cb
commit
b8d8e877d7
6 changed files with 112 additions and 42 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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("")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue