Using RFC 9078 feature from SDK: IM reactions
This commit is contained in:
parent
bfcdf19869
commit
8249f2c3a6
22 changed files with 717 additions and 27 deletions
20
CHANGELOG.md
20
CHANGELOG.md
|
@ -10,6 +10,11 @@ Group changes to describe their impact on the project, as follows:
|
|||
Fixed for any bug fixes.
|
||||
Security to invite users to upgrade in case of vulnerabilities.
|
||||
|
||||
## [5.2.0] - Unreleased
|
||||
|
||||
### Added
|
||||
- Chat messages emoji "reactions"
|
||||
|
||||
## [5.1.0] - Unreleased
|
||||
|
||||
### Added
|
||||
|
@ -34,7 +39,20 @@ Group changes to describe their impact on the project, as follows:
|
|||
### Fixed
|
||||
- Messages not marked as reply in basic chat room if sending more than 1 content
|
||||
|
||||
## [5.0.10] - 2023-01-04
|
||||
## [5.0.11] - 2023-05-09
|
||||
|
||||
### Fixed
|
||||
- Wrong call displayed when hanging up a call while an incoming one is ringing
|
||||
- Crash related to call history
|
||||
- Crash due to wrongly format string
|
||||
- Add/remove missing listener on FriendLists created after Core has been created
|
||||
|
||||
### Changed
|
||||
- Improved GSM call interruption
|
||||
- Updated translations
|
||||
|
||||
## [5.0.10] - 2023-04-04
|
||||
|
||||
### Fixed
|
||||
- Plain copy of encrypted files (when VFS is enabled) not cleaned
|
||||
- Avatar display issue if contact's "initials" contains more than 1 emoji or an emoji + a character
|
||||
|
|
|
@ -27,6 +27,7 @@ import android.view.LayoutInflater
|
|||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.PopupWindow
|
||||
import android.widget.TextView
|
||||
import androidx.databinding.DataBindingUtil
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
|
@ -108,6 +109,10 @@ class ChatMessagesListAdapter(
|
|||
MutableLiveData<Event<ChatMessage>>()
|
||||
}
|
||||
|
||||
val showReactionsListEvent: MutableLiveData<Event<ChatMessage>> by lazy {
|
||||
MutableLiveData<Event<ChatMessage>>()
|
||||
}
|
||||
|
||||
val errorEvent: MutableLiveData<Event<Int>> by lazy {
|
||||
MutableLiveData<Event<Int>>()
|
||||
}
|
||||
|
@ -153,6 +158,10 @@ class ChatMessagesListAdapter(
|
|||
callConferenceEvent.value = Event(Pair(address, subject))
|
||||
}
|
||||
|
||||
override fun onShowReactionsList(chatMessage: ChatMessage) {
|
||||
showReactionsListEvent.value = Event(chatMessage)
|
||||
}
|
||||
|
||||
override fun onError(messageId: Int) {
|
||||
errorEvent.value = Event(messageId)
|
||||
}
|
||||
|
@ -363,7 +372,7 @@ class ChatMessagesListAdapter(
|
|||
)
|
||||
|
||||
val itemSize = AppUtils.getDimension(R.dimen.chat_message_popup_item_height).toInt()
|
||||
var totalSize = itemSize * 7
|
||||
var totalSize = itemSize * 8
|
||||
if (chatMessage.chatRoom.hasCapability(
|
||||
ChatRoom.Capabilities.OneToOne.toInt()
|
||||
)
|
||||
|
@ -410,6 +419,13 @@ class ChatMessagesListAdapter(
|
|||
// Elevation is for showing a shadow around the popup
|
||||
popupWindow.elevation = 20f
|
||||
|
||||
popupView.setEmojiClickListener {
|
||||
val emoji = it as? TextView
|
||||
if (emoji != null) {
|
||||
reactToMessage(emoji.text.toString())
|
||||
popupWindow.dismiss()
|
||||
}
|
||||
}
|
||||
popupView.setResendClickListener {
|
||||
resendMessage()
|
||||
popupWindow.dismiss()
|
||||
|
@ -448,6 +464,17 @@ class ChatMessagesListAdapter(
|
|||
}
|
||||
}
|
||||
|
||||
private fun reactToMessage(reaction: String) {
|
||||
val chatMessage = binding.data?.chatMessage
|
||||
if (chatMessage != null) {
|
||||
Log.i(
|
||||
"[Chat Message Data] Reacting to message [$chatMessage] with [$reaction] emoji"
|
||||
)
|
||||
val reactionMessage = chatMessage.createReaction(reaction)
|
||||
reactionMessage.send()
|
||||
}
|
||||
}
|
||||
|
||||
private fun resendMessage() {
|
||||
val chatMessage = binding.data?.chatMessage
|
||||
if (chatMessage != null) {
|
||||
|
|
|
@ -521,5 +521,7 @@ interface OnContentClickedListener {
|
|||
|
||||
fun onCallConference(address: String, subject: String?)
|
||||
|
||||
fun onShowReactionsList(chatMessage: ChatMessage)
|
||||
|
||||
fun onError(messageId: Int)
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ import org.linphone.contact.ContactsUpdatedListenerStub
|
|||
import org.linphone.contact.GenericContactData
|
||||
import org.linphone.core.ChatMessage
|
||||
import org.linphone.core.ChatMessageListenerStub
|
||||
import org.linphone.core.ChatMessageReaction
|
||||
import org.linphone.core.tools.Log
|
||||
import org.linphone.utils.AppUtils
|
||||
import org.linphone.utils.Event
|
||||
|
@ -71,6 +72,8 @@ class ChatMessageData(val chatMessage: ChatMessage) : GenericContactData(chatMes
|
|||
MutableLiveData<Event<Boolean>>()
|
||||
}
|
||||
|
||||
val reactions = MutableLiveData<ArrayList<String>>()
|
||||
|
||||
var hasPreviousMessage = false
|
||||
var hasNextMessage = false
|
||||
|
||||
|
@ -85,6 +88,13 @@ class ChatMessageData(val chatMessage: ChatMessage) : GenericContactData(chatMes
|
|||
override fun onEphemeralMessageTimerStarted(message: ChatMessage) {
|
||||
updateEphemeralTimer()
|
||||
}
|
||||
|
||||
override fun onNewMessageReaction(message: ChatMessage, reaction: ChatMessageReaction) {
|
||||
Log.i(
|
||||
"[Chat Message Data] New reaction to display [${reaction.body}] from [${reaction.fromAddress.asStringUriOnly()}]"
|
||||
)
|
||||
updateReactionsList()
|
||||
}
|
||||
}
|
||||
|
||||
private val contactsListener = object : ContactsUpdatedListenerStub() {
|
||||
|
@ -122,6 +132,8 @@ class ChatMessageData(val chatMessage: ChatMessage) : GenericContactData(chatMes
|
|||
if (contact.value == null) {
|
||||
coreContext.contactsManager.addListener(contactsListener)
|
||||
}
|
||||
|
||||
updateReactionsList()
|
||||
}
|
||||
|
||||
override fun destroy() {
|
||||
|
@ -179,6 +191,10 @@ class ChatMessageData(val chatMessage: ChatMessage) : GenericContactData(chatMes
|
|||
}
|
||||
}
|
||||
|
||||
fun showReactionsList() {
|
||||
contentListener?.onShowReactionsList(chatMessage)
|
||||
}
|
||||
|
||||
private fun updateChatMessageState(state: ChatMessage.State) {
|
||||
sendInProgress.value = when (state) {
|
||||
ChatMessage.State.InProgress, ChatMessage.State.FileTransferInProgress, ChatMessage.State.FileTransferDone -> true
|
||||
|
@ -265,6 +281,23 @@ class ChatMessageData(val chatMessage: ChatMessage) : GenericContactData(chatMes
|
|||
contents.value = list
|
||||
}
|
||||
|
||||
fun updateReactionsList() {
|
||||
val reactionsList = arrayListOf<String>()
|
||||
val allReactions = chatMessage.reactions
|
||||
|
||||
if (allReactions.isNotEmpty()) {
|
||||
for (reaction in allReactions) {
|
||||
val body = reaction.body
|
||||
if (!reactionsList.contains(body)) {
|
||||
reactionsList.add(body)
|
||||
}
|
||||
}
|
||||
reactionsList.add(allReactions.size.toString())
|
||||
}
|
||||
|
||||
reactions.value = reactionsList
|
||||
}
|
||||
|
||||
private fun updateEphemeralTimer() {
|
||||
if (chatMessage.isEphemeral) {
|
||||
if (chatMessage.ephemeralExpireTime == 0L) {
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2022 Belledonne Communications SARL.
|
||||
*
|
||||
* This file is part of linphone-android
|
||||
* (see https://www.linphone.org).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.linphone.activities.main.chat.data
|
||||
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import org.linphone.contact.GenericContactData
|
||||
import org.linphone.core.ChatMessageReaction
|
||||
|
||||
class ChatMessageReactionData(
|
||||
chatMessageReaction: ChatMessageReaction
|
||||
) : GenericContactData(chatMessageReaction.fromAddress) {
|
||||
|
||||
val reaction = MutableLiveData<String>()
|
||||
|
||||
init {
|
||||
reaction.value = chatMessageReaction.body
|
||||
}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2022 Belledonne Communications SARL.
|
||||
*
|
||||
* This file is part of linphone-android
|
||||
* (see https://www.linphone.org).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.linphone.activities.main.chat.data
|
||||
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import org.linphone.core.ChatMessage
|
||||
import org.linphone.core.ChatMessageListenerStub
|
||||
import org.linphone.core.ChatMessageReaction
|
||||
import org.linphone.core.tools.Log
|
||||
|
||||
class ChatMessageReactionsListData(private val chatMessage: ChatMessage) {
|
||||
val reactions = MutableLiveData<ArrayList<ChatMessageReaction>>()
|
||||
|
||||
val filteredReactions = MutableLiveData<ArrayList<ChatMessageReactionData>>()
|
||||
|
||||
val reactionsMap = HashMap<String, Int>()
|
||||
|
||||
val listener = object : ChatMessageListenerStub() {
|
||||
override fun onNewMessageReaction(message: ChatMessage, reaction: ChatMessageReaction) {
|
||||
val address = reaction.fromAddress
|
||||
Log.i(
|
||||
"[Chat Message Reactions List] Reaction received [${reaction.body}] from [${address.asStringUriOnly()}]"
|
||||
)
|
||||
updateReactionsList(message)
|
||||
}
|
||||
}
|
||||
|
||||
private var filter = ""
|
||||
|
||||
init {
|
||||
chatMessage.addListener(listener)
|
||||
|
||||
updateReactionsList(chatMessage)
|
||||
}
|
||||
|
||||
fun onDestroy() {
|
||||
chatMessage.removeListener(listener)
|
||||
}
|
||||
|
||||
fun updateFilteredReactions(newFilter: String) {
|
||||
filter = newFilter
|
||||
filteredReactions.value.orEmpty().forEach(ChatMessageReactionData::destroy)
|
||||
|
||||
val reactionsList = arrayListOf<ChatMessageReactionData>()
|
||||
for (reaction in reactions.value.orEmpty()) {
|
||||
if (filter.isEmpty() || filter == reaction.body) {
|
||||
val data = ChatMessageReactionData(reaction)
|
||||
reactionsList.add(data)
|
||||
}
|
||||
}
|
||||
filteredReactions.value = reactionsList
|
||||
}
|
||||
|
||||
private fun updateReactionsList(chatMessage: ChatMessage) {
|
||||
reactionsMap.clear()
|
||||
|
||||
val reactionsList = arrayListOf<ChatMessageReaction>()
|
||||
for (reaction in chatMessage.reactions) {
|
||||
val body = reaction.body
|
||||
val count = if (reactionsMap.containsKey(body)) {
|
||||
reactionsMap[body] ?: 1
|
||||
} else {
|
||||
1
|
||||
}
|
||||
reactionsMap[body] = count
|
||||
reactionsList.add(reaction)
|
||||
}
|
||||
reactions.value = reactionsList
|
||||
|
||||
updateFilteredReactions(filter)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2022 Belledonne Communications SARL.
|
||||
*
|
||||
* This file is part of linphone-android
|
||||
* (see https://www.linphone.org).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.linphone.activities.main.chat.fragments
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import org.linphone.R
|
||||
import org.linphone.activities.main.chat.data.ChatMessageReactionsListData
|
||||
import org.linphone.core.ChatMessage
|
||||
import org.linphone.databinding.ChatMessageReactionsListDialogBinding
|
||||
import org.linphone.utils.AppUtils
|
||||
|
||||
class ChatMessageReactionsListDialogFragment(
|
||||
private val chatMessage: ChatMessage
|
||||
) : BottomSheetDialogFragment() {
|
||||
companion object {
|
||||
const val TAG = "ChatMessageReactionsListDialogFragment"
|
||||
}
|
||||
|
||||
private lateinit var data: ChatMessageReactionsListData
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
val binding = ChatMessageReactionsListDialogBinding.inflate(layoutInflater)
|
||||
binding.lifecycleOwner = viewLifecycleOwner
|
||||
|
||||
data = ChatMessageReactionsListData(chatMessage)
|
||||
binding.data = data
|
||||
|
||||
data.reactions.observe(viewLifecycleOwner) {
|
||||
binding.tabs.removeAllTabs()
|
||||
binding.tabs.addTab(
|
||||
binding.tabs.newTab().setText(
|
||||
AppUtils.getStringWithPlural(
|
||||
R.plurals.chat_message_reactions_count,
|
||||
it.orEmpty().size
|
||||
)
|
||||
).setId(0)
|
||||
)
|
||||
|
||||
var index = 1
|
||||
data.reactionsMap.forEach { (key, value) ->
|
||||
binding.tabs.addTab(
|
||||
binding.tabs.newTab().setText("$key $value").setId(index).setTag(key)
|
||||
)
|
||||
index += 1
|
||||
}
|
||||
}
|
||||
|
||||
binding.tabs.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
|
||||
override fun onTabSelected(tab: TabLayout.Tab) {
|
||||
if (tab.id == 0) {
|
||||
data.updateFilteredReactions("")
|
||||
} else {
|
||||
data.updateFilteredReactions(tab.tag.toString())
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTabUnselected(tab: TabLayout.Tab) {
|
||||
}
|
||||
|
||||
override fun onTabReselected(tab: TabLayout.Tab) {
|
||||
}
|
||||
})
|
||||
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
data.onDestroy()
|
||||
super.onDestroy()
|
||||
}
|
||||
}
|
|
@ -631,6 +631,18 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
|
|||
}
|
||||
}
|
||||
|
||||
adapter.showReactionsListEvent.observe(
|
||||
viewLifecycleOwner
|
||||
) {
|
||||
it.consume { message ->
|
||||
val modalBottomSheet = ChatMessageReactionsListDialogFragment(message)
|
||||
modalBottomSheet.show(
|
||||
parentFragmentManager,
|
||||
ChatMessageReactionsListDialogFragment.TAG
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
adapter.errorEvent.observe(
|
||||
viewLifecycleOwner
|
||||
) {
|
||||
|
|
|
@ -217,6 +217,16 @@ class ContactsManager(private val context: Context) {
|
|||
return null
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun isAddressMyself(address: Address): Boolean {
|
||||
for (friend in localFriends) {
|
||||
if (friend.address?.weakEqual(address) == true) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun addListener(listener: ContactsUpdatedListener) {
|
||||
contactsUpdatedListeners.add(listener)
|
||||
|
|
|
@ -189,6 +189,70 @@ class NotificationsManager(private val context: Context) {
|
|||
}
|
||||
}
|
||||
|
||||
override fun onNewMessageReaction(
|
||||
core: Core,
|
||||
chatRoom: ChatRoom,
|
||||
message: ChatMessage,
|
||||
reaction: ChatMessageReaction
|
||||
) {
|
||||
val address = reaction.fromAddress
|
||||
val defaultAccountAddress = core.defaultAccount?.params?.identityAddress
|
||||
// Do not notify our own reactions, it won't be done anyway since the chat room is very likely to be currently displayed
|
||||
if (defaultAccountAddress != null && defaultAccountAddress.weakEqual(address)) return
|
||||
|
||||
Log.i(
|
||||
"[Notifications Manager] Reaction received [${reaction.body}] from [${address.asStringUriOnly()}] for chat message [$message]"
|
||||
)
|
||||
if (corePreferences.disableChat) return
|
||||
|
||||
if (corePreferences.preventInterfaceFromShowingUp) {
|
||||
Log.w("[Notifications Manager] We were asked to not show the chat notifications")
|
||||
return
|
||||
}
|
||||
|
||||
if (currentlyDisplayedChatRoomAddress == chatRoom.peerAddress.asStringUriOnly()) {
|
||||
Log.i(
|
||||
"[Notifications Manager] Chat room is currently displayed, do not notify received reaction"
|
||||
)
|
||||
// Mark as read is now done in the DetailChatRoomFragment
|
||||
return
|
||||
}
|
||||
|
||||
val id = LinphoneUtils.getChatRoomId(chatRoom.localAddress, chatRoom.peerAddress)
|
||||
val mute = corePreferences.chatRoomMuted(id)
|
||||
if (mute) {
|
||||
Log.i("[Notifications Manager] Chat room $id has been muted")
|
||||
return
|
||||
}
|
||||
|
||||
if (coreContext.contactsManager.isAddressMyself(address)) {
|
||||
Log.i(
|
||||
"[Notifications Manager] Reaction has been sent by ourselves, do not notify it"
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if (corePreferences.chatRoomShortcuts) {
|
||||
if (ShortcutsHelper.isShortcutToChatRoomAlreadyCreated(context, chatRoom)) {
|
||||
Log.i("[Notifications Manager] Chat room shortcut already exists")
|
||||
} else {
|
||||
Log.i(
|
||||
"[Notifications Manager] Ensure chat room shortcut exists for bubble notification"
|
||||
)
|
||||
ShortcutsHelper.createShortcutsToChatRooms(context)
|
||||
}
|
||||
}
|
||||
|
||||
val notifiable = createChatReactionNotifiable(chatRoom, reaction.body, address, message)
|
||||
if (notifiable.messages.isNotEmpty()) {
|
||||
displayChatNotifiable(chatRoom, notifiable)
|
||||
} else {
|
||||
Log.e(
|
||||
"[Notifications Manager] Notifiable is empty but we should have displayed the reaction!"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onChatRoomRead(core: Core, chatRoom: ChatRoom) {
|
||||
val address = chatRoom.peerAddress.asStringUriOnly()
|
||||
val notifiable = chatNotificationsMap[address]
|
||||
|
@ -798,6 +862,37 @@ class NotificationsManager(private val context: Context) {
|
|||
notifiable.isGroup = true
|
||||
notifiable.groupTitle = room.subject
|
||||
}
|
||||
return notifiable
|
||||
}
|
||||
|
||||
private fun createChatReactionNotifiable(
|
||||
room: ChatRoom,
|
||||
reaction: String,
|
||||
from: Address,
|
||||
message: ChatMessage
|
||||
): Notifiable {
|
||||
val notifiable = getNotifiableForRoom(room)
|
||||
|
||||
val friend = coreContext.contactsManager.findContactByAddress(from)
|
||||
val roundPicture = ImageUtils.getRoundBitmapFromUri(context, friend?.getThumbnailUri())
|
||||
val displayName = friend?.name ?: LinphoneUtils.getDisplayName(from)
|
||||
|
||||
val originalMessage = LinphoneUtils.getTextDescribingMessage(message)
|
||||
val text = AppUtils.getString(R.string.chat_message_reaction_received).format(
|
||||
displayName,
|
||||
reaction,
|
||||
originalMessage
|
||||
)
|
||||
|
||||
val notifiableMessage = NotifiableMessage(
|
||||
text,
|
||||
friend,
|
||||
displayName,
|
||||
message.time,
|
||||
senderAvatar = roundPicture,
|
||||
isOutgoing = false
|
||||
)
|
||||
notifiable.messages.add(notifiableMessage)
|
||||
|
||||
return notifiable
|
||||
}
|
||||
|
@ -812,6 +907,13 @@ class NotificationsManager(private val context: Context) {
|
|||
notifiable.remoteAddress = room.peerAddress.asStringUriOnly()
|
||||
|
||||
chatNotificationsMap[address] = notifiable
|
||||
|
||||
if (room.hasCapability(ChatRoom.Capabilities.OneToOne.toInt())) {
|
||||
notifiable.isGroup = false
|
||||
} else {
|
||||
notifiable.isGroup = true
|
||||
notifiable.groupTitle = room.subject
|
||||
}
|
||||
}
|
||||
return notifiable
|
||||
}
|
||||
|
@ -819,26 +921,7 @@ class NotificationsManager(private val context: Context) {
|
|||
private fun getNotifiableMessage(message: ChatMessage, friend: Friend?): NotifiableMessage {
|
||||
val roundPicture = ImageUtils.getRoundBitmapFromUri(context, friend?.getThumbnailUri())
|
||||
val displayName = friend?.name ?: LinphoneUtils.getDisplayName(message.fromAddress)
|
||||
var text = ""
|
||||
|
||||
val firstContent = message.contents.firstOrNull()
|
||||
text = if (firstContent?.isIcalendar == true) {
|
||||
AppUtils.getString(R.string.conference_invitation_received_notification)
|
||||
} else if (firstContent?.isVoiceRecording == true) {
|
||||
AppUtils.getString(R.string.chat_message_voice_recording_received_notification)
|
||||
} else {
|
||||
message.contents.find { content -> content.isText }?.utf8Text ?: ""
|
||||
}
|
||||
|
||||
if (text.isEmpty()) {
|
||||
for (content in message.contents) {
|
||||
if (text.isNotEmpty()) {
|
||||
text += ", "
|
||||
}
|
||||
text += content.name
|
||||
}
|
||||
}
|
||||
|
||||
val text = LinphoneUtils.getTextDescribingMessage(message)
|
||||
val notifiableMessage = NotifiableMessage(
|
||||
text,
|
||||
friend,
|
||||
|
|
|
@ -39,6 +39,7 @@ import androidx.core.view.doOnLayout
|
|||
import androidx.databinding.*
|
||||
import androidx.emoji2.emojipicker.EmojiPickerView
|
||||
import androidx.emoji2.emojipicker.EmojiViewItem
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import coil.dispose
|
||||
import coil.load
|
||||
import coil.request.CachePolicy
|
||||
|
@ -50,7 +51,6 @@ import org.linphone.BR
|
|||
import org.linphone.LinphoneApplication.Companion.coreContext
|
||||
import org.linphone.LinphoneApplication.Companion.corePreferences
|
||||
import org.linphone.R
|
||||
import org.linphone.activities.GenericActivity
|
||||
import org.linphone.activities.main.settings.SettingListener
|
||||
import org.linphone.activities.voip.data.ConferenceParticipantDeviceData
|
||||
import org.linphone.activities.voip.views.ScrollDotsView
|
||||
|
@ -258,7 +258,9 @@ fun setListener(view: SeekBar, lambda: (Any) -> Unit) {
|
|||
fun setInflatedViewStubLifecycleOwner(view: View, enable: Boolean) {
|
||||
val binding = DataBindingUtil.bind<ViewDataBinding>(view)
|
||||
// This is a bit hacky...
|
||||
binding?.lifecycleOwner = view.context as GenericActivity
|
||||
if (view.context is LifecycleOwner) {
|
||||
binding?.lifecycleOwner = view.context as? LifecycleOwner
|
||||
}
|
||||
}
|
||||
|
||||
@BindingAdapter("entries")
|
||||
|
@ -296,7 +298,9 @@ private fun <T> setEntries(
|
|||
binding.setVariable(BR.parent, parent)
|
||||
|
||||
// This is a bit hacky...
|
||||
binding.lifecycleOwner = viewGroup.context as GenericActivity
|
||||
if (viewGroup.context is LifecycleOwner) {
|
||||
binding.lifecycleOwner = viewGroup.context as? LifecycleOwner
|
||||
}
|
||||
|
||||
viewGroup.addView(binding.root)
|
||||
}
|
||||
|
|
|
@ -309,5 +309,32 @@ class LinphoneUtils {
|
|||
|
||||
return null
|
||||
}
|
||||
|
||||
fun getTextDescribingMessage(message: ChatMessage): String {
|
||||
// If message contains text, then use that
|
||||
var text = message.contents.find { content -> content.isText }?.utf8Text ?: ""
|
||||
|
||||
if (text.isEmpty()) {
|
||||
val firstContent = message.contents.firstOrNull()
|
||||
if (firstContent?.isIcalendar == true) {
|
||||
text = AppUtils.getString(
|
||||
R.string.conference_invitation_notification_short_desc
|
||||
)
|
||||
} else if (firstContent?.isVoiceRecording == true) {
|
||||
text = AppUtils.getString(
|
||||
R.string.chat_message_voice_recording_notification_short_desc
|
||||
)
|
||||
} else {
|
||||
for (content in message.contents) {
|
||||
if (text.isNotEmpty()) {
|
||||
text += ", "
|
||||
}
|
||||
text += content.name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return text
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
6
app/src/main/res/drawable/shape_dialog_handle.xml
Normal file
6
app/src/main/res/drawable/shape_dialog_handle.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
|
||||
<corners android:radius="5dp"/>
|
||||
<size android:height="5dp" android:width="30dp"/>
|
||||
<solid android:color="@color/voip_light_gray"/>
|
||||
</shape>
|
|
@ -89,6 +89,7 @@
|
|||
|
||||
<LinearLayout
|
||||
android:id="@+id/background"
|
||||
android:background="@drawable/chat_bubble_outgoing_full"
|
||||
backgroundImage="@{data.backgroundRes, default=@drawable/chat_bubble_outgoing_full}"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -171,7 +172,7 @@
|
|||
android:longClickable="true"
|
||||
android:onClick="@{clickListener}"
|
||||
android:onLongClick="@{contextMenuClickListener}"
|
||||
android:text="@{data.text}"
|
||||
android:text="@{data.text, default=`Lorem Ipsum: dolor sit amet`}"
|
||||
android:textColor="@color/dark_grey_color"
|
||||
android:textSize="@{data.isTextEmoji ? @dimen/chat_message_emoji_font_size : @dimen/chat_message_text_font_size, default=@dimen/chat_message_text_font_size}"
|
||||
android:textStyle="normal"
|
||||
|
@ -196,6 +197,25 @@
|
|||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:onClick="@{() -> data.showReactionsList()}"
|
||||
android:id="@+id/reactions"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/chat_bubble_outgoing_full"
|
||||
backgroundImage="@{data.backgroundRes, default=@drawable/chat_bubble_outgoing_full}"
|
||||
android:padding="2dp"
|
||||
android:layout_marginTop="-10dp"
|
||||
android:layout_marginStart="5dp"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:layout_below="@id/background"
|
||||
android:layout_alignLeft="@{data.chatMessage.outgoing || selectionListViewModel.isEditionEnabled ? 0 : @id/background}"
|
||||
android:layout_alignRight="@{data.chatMessage.outgoing || selectionListViewModel.isEditionEnabled ? @id/background : 0}"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
entries="@{data.reactions}"
|
||||
layout="@{@layout/chat_message_reaction}" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/time"
|
||||
android:layout_width="wrap_content"
|
||||
|
@ -206,7 +226,7 @@
|
|||
android:layout_marginTop="7dp"
|
||||
android:fontFamily="sans-serif"
|
||||
android:lineSpacingExtra="0sp"
|
||||
android:text="@{data.chatMessage.outgoing ? data.time : data.time + ` - ` + (data.contact.name ?? data.displayName)}"
|
||||
android:text="@{data.chatMessage.outgoing ? data.time : data.time + ` - ` + (data.contact.name ?? data.displayName), default=`07/11 13:19 - John Doe`}"
|
||||
android:textColor="@color/chat_bubble_text_color"
|
||||
android:textSize="13sp"
|
||||
android:textStyle="normal"
|
||||
|
|
|
@ -4,6 +4,9 @@
|
|||
|
||||
<data>
|
||||
<import type="android.view.View" />
|
||||
<variable
|
||||
name="emojiClickListener"
|
||||
type="View.OnClickListener" />
|
||||
<variable
|
||||
name="resendClickListener"
|
||||
type="View.OnClickListener" />
|
||||
|
@ -51,6 +54,77 @@
|
|||
android:orientation="vertical"
|
||||
android:background="?attr/backgroundColor">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/chat_message_popup_item_height">
|
||||
|
||||
<TextView
|
||||
style="@style/chat_message_emoji_reaction_font"
|
||||
android:onClick="@{emojiClickListener}"
|
||||
android:id="@+id/love"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="10dp"
|
||||
android:text="@string/emoji_love"
|
||||
app:layout_constraintEnd_toStartOf="@id/thumbs_up"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"/>
|
||||
|
||||
<TextView
|
||||
style="@style/chat_message_emoji_reaction_font"
|
||||
android:onClick="@{emojiClickListener}"
|
||||
android:id="@+id/thumbs_up"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="10dp"
|
||||
android:text="@string/emoji_thumbs_up"
|
||||
app:layout_constraintEnd_toStartOf="@id/laughing"
|
||||
app:layout_constraintStart_toEndOf="@id/love"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent" />
|
||||
|
||||
<TextView
|
||||
style="@style/chat_message_emoji_reaction_font"
|
||||
android:onClick="@{emojiClickListener}"
|
||||
android:id="@+id/laughing"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="10dp"
|
||||
android:text="@string/emoji_laughing"
|
||||
app:layout_constraintEnd_toStartOf="@id/surprised"
|
||||
app:layout_constraintStart_toEndOf="@id/thumbs_up"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent" />
|
||||
|
||||
<TextView
|
||||
style="@style/chat_message_emoji_reaction_font"
|
||||
android:onClick="@{emojiClickListener}"
|
||||
android:id="@+id/surprised"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="10dp"
|
||||
android:text="@string/emoji_surprised"
|
||||
app:layout_constraintEnd_toStartOf="@id/tear"
|
||||
app:layout_constraintStart_toEndOf="@id/laughing"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent" />
|
||||
|
||||
<TextView
|
||||
style="@style/chat_message_emoji_reaction_font"
|
||||
android:onClick="@{emojiClickListener}"
|
||||
android:id="@+id/tear"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="10dp"
|
||||
android:text="@string/emoji_tear"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/surprised"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/chat_message_popup_item_height"
|
||||
|
|
19
app/src/main/res/layout/chat_message_reaction.xml
Normal file
19
app/src/main/res/layout/chat_message_reaction.xml
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<data>
|
||||
<variable
|
||||
name="data"
|
||||
type="String" />
|
||||
</data>
|
||||
|
||||
<TextView
|
||||
android:textSize="@{data.matches(`\\d+`) ? @dimen/chat_message_emoji_reactions_count_font_size : @dimen/chat_message_emoji_reaction_font_size, default=@dimen/chat_message_emoji_reaction_font_size}"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="2dp"
|
||||
android:layout_marginEnd="2dp"
|
||||
android:text="@{data, default=@string/emoji_love}"/>
|
||||
|
||||
</layout>
|
53
app/src/main/res/layout/chat_message_reactions_list_cell.xml
Normal file
53
app/src/main/res/layout/chat_message_reactions_list_cell.xml
Normal file
|
@ -0,0 +1,53 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<data>
|
||||
<import type="android.view.View" />
|
||||
<variable
|
||||
name="data"
|
||||
type="org.linphone.activities.main.chat.data.ChatMessageReactionData" />
|
||||
</data>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="10dp">
|
||||
|
||||
<ImageView
|
||||
coilContact="@{data}"
|
||||
android:id="@+id/avatar"
|
||||
android:layout_width="@dimen/contact_avatar_size"
|
||||
android:layout_height="@dimen/contact_avatar_size"
|
||||
android:background="@drawable/generated_avatar_bg"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/voip_single_contact_avatar"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"/>
|
||||
|
||||
<TextView
|
||||
style="@style/contact_name_list_cell_font"
|
||||
android:id="@+id/contact"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="10dp"
|
||||
android:text="@{data.contact.name ?? data.displayName}"
|
||||
app:layout_constraintStart_toEndOf="@id/avatar"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"/>
|
||||
|
||||
<TextView
|
||||
style="@style/chat_message_emoji_reaction_font"
|
||||
android:id="@+id/reaction"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:text="@{data.reaction}"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</layout>
|
|
@ -0,0 +1,54 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<data>
|
||||
<import type="android.view.View" />
|
||||
<variable
|
||||
name="data"
|
||||
type="org.linphone.activities.main.chat.data.ChatMessageReactionsListData" />
|
||||
</data>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="?attr/backgroundColor">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:src="@drawable/shape_dialog_handle" />
|
||||
|
||||
<com.google.android.material.tabs.TabLayout
|
||||
android:id="@+id/tabs"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_marginStart="2dp"
|
||||
android:layout_marginEnd="2dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:background="?dividerColor" />
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
entries="@{data.filteredReactions}"
|
||||
layout="@{@layout/chat_message_reactions_list_cell}" />
|
||||
|
||||
</ScrollView>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</layout>
|
|
@ -785,4 +785,9 @@
|
|||
<string name="assistant_alternative_way_create_account">Pour créer un compte avec votre email :</string>
|
||||
<string name="assistant_no_push_warning">Votre périphérique ne semble pas supporter les notifications \'push\'.\n\nVous ne pourrez donc pas créer des comptes dans l\'application mais vous pouvez toujours le faire sur notre site internet :</string>
|
||||
<string name="assistant_create_email_account_not_validated">Votre compte n\'est pas activé, veuillez cliquer sur le lien que vous avez reçu par courriel</string>
|
||||
<string name="chat_message_reaction_received">%s a réagi par %s à : %s</string>
|
||||
<string name="chat_message_one_reaction">%s réaction</string>
|
||||
<string name="chat_message_many_reactions">%s réactions</string>
|
||||
<string name="conference_invitation_notification_short_desc">invitation à une conférence</string>
|
||||
<string name="chat_message_voice_recording_notification_short_desc">message vocal</string>
|
||||
</resources>
|
|
@ -89,4 +89,6 @@
|
|||
<dimen name="chat_room_emoji_picker_height">290dp</dimen>
|
||||
<dimen name="chat_message_text_font_size">15sp</dimen>
|
||||
<dimen name="chat_message_emoji_font_size">45sp</dimen>
|
||||
<dimen name="chat_message_emoji_reaction_font_size">20sp</dimen>
|
||||
<dimen name="chat_message_emoji_reactions_count_font_size">14sp</dimen>
|
||||
</resources>
|
|
@ -234,6 +234,7 @@
|
|||
<string name="chat_message_abort_removal">Abort</string>
|
||||
<string name="chat_message_download_already_in_progress">Please wait for first download to finish before starting a new one</string>
|
||||
<string name="chat_message_voice_recording_received_notification">You have received a voice message</string>
|
||||
<string name="chat_message_voice_recording_notification_short_desc">voice message</string>
|
||||
<string name="chat_message_voice_recording">Voice message</string>
|
||||
<string name="chat_room_presence_online">Online</string>
|
||||
<string name="chat_room_presence_last_seen_online_today">Online today at</string>
|
||||
|
@ -242,6 +243,20 @@
|
|||
<string name="chat_room_presence_away">Away</string>
|
||||
<string name="chat_room_presence_do_not_disturb">Do not disturb</string>
|
||||
|
||||
<!-- Chat reactions -->
|
||||
<string name="chat_message_reaction_received">%s has reacted by %s to: %s</string>
|
||||
<string name="emoji_love" translatable="false">❤️</string>
|
||||
<string name="emoji_thumbs_up" translatable="false">👍</string>
|
||||
<string name="emoji_laughing" translatable="false">😂</string>
|
||||
<string name="emoji_surprised" translatable="false">😮</string>
|
||||
<string name="emoji_tear" translatable="false">😢</string>
|
||||
<plurals name="chat_message_reactions_count" translatable="false">
|
||||
<item quantity="one">@string/chat_message_one_reaction</item>
|
||||
<item quantity="other">@string/chat_message_many_reactions</item>
|
||||
</plurals>
|
||||
<string name="chat_message_one_reaction">%s reaction</string>
|
||||
<string name="chat_message_many_reactions">%s reactions</string>
|
||||
|
||||
<!-- Recordings -->
|
||||
<string name="recordings_empty_list">No recordings</string>
|
||||
<string name="recordings_export">Export recording using…</string>
|
||||
|
@ -308,6 +323,7 @@
|
|||
<string name="conference_info_removed">Meeting info has been deleted</string>
|
||||
<string name="conference_empty">You are currently alone in this group call</string>
|
||||
<string name="conference_invitation_received_notification">You have been invited to a meeting</string>
|
||||
<string name="conference_invitation_notification_short_desc">meeting invitation</string>
|
||||
<string name="conference_invitation">Meeting invitation</string>
|
||||
<string name="conference_low_bandwidth">Low bandwidth detected, disabling video</string>
|
||||
<string name="conference_first_to_join">You\'re the first to join the group call</string>
|
||||
|
|
|
@ -353,6 +353,10 @@
|
|||
<item name="android:fontFamily">sans-serif</item>
|
||||
</style>
|
||||
|
||||
<style name="chat_message_emoji_reaction_font">
|
||||
<item name="android:textSize">@dimen/chat_message_emoji_reaction_font_size</item>
|
||||
</style>
|
||||
|
||||
<!-- Dialog related -->
|
||||
|
||||
<style name="dialog_title_font" parent="@android:style/TextAppearance.Medium">
|
||||
|
|
Loading…
Reference in a new issue