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.
|
Fixed for any bug fixes.
|
||||||
Security to invite users to upgrade in case of vulnerabilities.
|
Security to invite users to upgrade in case of vulnerabilities.
|
||||||
|
|
||||||
|
## [5.2.0] - Unreleased
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Chat messages emoji "reactions"
|
||||||
|
|
||||||
## [5.1.0] - Unreleased
|
## [5.1.0] - Unreleased
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
@ -34,7 +39,20 @@ Group changes to describe their impact on the project, as follows:
|
||||||
### Fixed
|
### Fixed
|
||||||
- Messages not marked as reply in basic chat room if sending more than 1 content
|
- 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
|
### Fixed
|
||||||
- Plain copy of encrypted files (when VFS is enabled) not cleaned
|
- 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
|
- 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.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.PopupWindow
|
import android.widget.PopupWindow
|
||||||
|
import android.widget.TextView
|
||||||
import androidx.databinding.DataBindingUtil
|
import androidx.databinding.DataBindingUtil
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
@ -108,6 +109,10 @@ class ChatMessagesListAdapter(
|
||||||
MutableLiveData<Event<ChatMessage>>()
|
MutableLiveData<Event<ChatMessage>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val showReactionsListEvent: MutableLiveData<Event<ChatMessage>> by lazy {
|
||||||
|
MutableLiveData<Event<ChatMessage>>()
|
||||||
|
}
|
||||||
|
|
||||||
val errorEvent: MutableLiveData<Event<Int>> by lazy {
|
val errorEvent: MutableLiveData<Event<Int>> by lazy {
|
||||||
MutableLiveData<Event<Int>>()
|
MutableLiveData<Event<Int>>()
|
||||||
}
|
}
|
||||||
|
@ -153,6 +158,10 @@ class ChatMessagesListAdapter(
|
||||||
callConferenceEvent.value = Event(Pair(address, subject))
|
callConferenceEvent.value = Event(Pair(address, subject))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onShowReactionsList(chatMessage: ChatMessage) {
|
||||||
|
showReactionsListEvent.value = Event(chatMessage)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onError(messageId: Int) {
|
override fun onError(messageId: Int) {
|
||||||
errorEvent.value = Event(messageId)
|
errorEvent.value = Event(messageId)
|
||||||
}
|
}
|
||||||
|
@ -363,7 +372,7 @@ class ChatMessagesListAdapter(
|
||||||
)
|
)
|
||||||
|
|
||||||
val itemSize = AppUtils.getDimension(R.dimen.chat_message_popup_item_height).toInt()
|
val itemSize = AppUtils.getDimension(R.dimen.chat_message_popup_item_height).toInt()
|
||||||
var totalSize = itemSize * 7
|
var totalSize = itemSize * 8
|
||||||
if (chatMessage.chatRoom.hasCapability(
|
if (chatMessage.chatRoom.hasCapability(
|
||||||
ChatRoom.Capabilities.OneToOne.toInt()
|
ChatRoom.Capabilities.OneToOne.toInt()
|
||||||
)
|
)
|
||||||
|
@ -410,6 +419,13 @@ class ChatMessagesListAdapter(
|
||||||
// Elevation is for showing a shadow around the popup
|
// Elevation is for showing a shadow around the popup
|
||||||
popupWindow.elevation = 20f
|
popupWindow.elevation = 20f
|
||||||
|
|
||||||
|
popupView.setEmojiClickListener {
|
||||||
|
val emoji = it as? TextView
|
||||||
|
if (emoji != null) {
|
||||||
|
reactToMessage(emoji.text.toString())
|
||||||
|
popupWindow.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
popupView.setResendClickListener {
|
popupView.setResendClickListener {
|
||||||
resendMessage()
|
resendMessage()
|
||||||
popupWindow.dismiss()
|
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() {
|
private fun resendMessage() {
|
||||||
val chatMessage = binding.data?.chatMessage
|
val chatMessage = binding.data?.chatMessage
|
||||||
if (chatMessage != null) {
|
if (chatMessage != null) {
|
||||||
|
|
|
@ -521,5 +521,7 @@ interface OnContentClickedListener {
|
||||||
|
|
||||||
fun onCallConference(address: String, subject: String?)
|
fun onCallConference(address: String, subject: String?)
|
||||||
|
|
||||||
|
fun onShowReactionsList(chatMessage: ChatMessage)
|
||||||
|
|
||||||
fun onError(messageId: Int)
|
fun onError(messageId: Int)
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@ import org.linphone.contact.ContactsUpdatedListenerStub
|
||||||
import org.linphone.contact.GenericContactData
|
import org.linphone.contact.GenericContactData
|
||||||
import org.linphone.core.ChatMessage
|
import org.linphone.core.ChatMessage
|
||||||
import org.linphone.core.ChatMessageListenerStub
|
import org.linphone.core.ChatMessageListenerStub
|
||||||
|
import org.linphone.core.ChatMessageReaction
|
||||||
import org.linphone.core.tools.Log
|
import org.linphone.core.tools.Log
|
||||||
import org.linphone.utils.AppUtils
|
import org.linphone.utils.AppUtils
|
||||||
import org.linphone.utils.Event
|
import org.linphone.utils.Event
|
||||||
|
@ -71,6 +72,8 @@ class ChatMessageData(val chatMessage: ChatMessage) : GenericContactData(chatMes
|
||||||
MutableLiveData<Event<Boolean>>()
|
MutableLiveData<Event<Boolean>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val reactions = MutableLiveData<ArrayList<String>>()
|
||||||
|
|
||||||
var hasPreviousMessage = false
|
var hasPreviousMessage = false
|
||||||
var hasNextMessage = false
|
var hasNextMessage = false
|
||||||
|
|
||||||
|
@ -85,6 +88,13 @@ class ChatMessageData(val chatMessage: ChatMessage) : GenericContactData(chatMes
|
||||||
override fun onEphemeralMessageTimerStarted(message: ChatMessage) {
|
override fun onEphemeralMessageTimerStarted(message: ChatMessage) {
|
||||||
updateEphemeralTimer()
|
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() {
|
private val contactsListener = object : ContactsUpdatedListenerStub() {
|
||||||
|
@ -122,6 +132,8 @@ class ChatMessageData(val chatMessage: ChatMessage) : GenericContactData(chatMes
|
||||||
if (contact.value == null) {
|
if (contact.value == null) {
|
||||||
coreContext.contactsManager.addListener(contactsListener)
|
coreContext.contactsManager.addListener(contactsListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateReactionsList()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun destroy() {
|
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) {
|
private fun updateChatMessageState(state: ChatMessage.State) {
|
||||||
sendInProgress.value = when (state) {
|
sendInProgress.value = when (state) {
|
||||||
ChatMessage.State.InProgress, ChatMessage.State.FileTransferInProgress, ChatMessage.State.FileTransferDone -> true
|
ChatMessage.State.InProgress, ChatMessage.State.FileTransferInProgress, ChatMessage.State.FileTransferDone -> true
|
||||||
|
@ -265,6 +281,23 @@ class ChatMessageData(val chatMessage: ChatMessage) : GenericContactData(chatMes
|
||||||
contents.value = list
|
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() {
|
private fun updateEphemeralTimer() {
|
||||||
if (chatMessage.isEphemeral) {
|
if (chatMessage.isEphemeral) {
|
||||||
if (chatMessage.ephemeralExpireTime == 0L) {
|
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(
|
adapter.errorEvent.observe(
|
||||||
viewLifecycleOwner
|
viewLifecycleOwner
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -217,6 +217,16 @@ class ContactsManager(private val context: Context) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun isAddressMyself(address: Address): Boolean {
|
||||||
|
for (friend in localFriends) {
|
||||||
|
if (friend.address?.weakEqual(address) == true) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun addListener(listener: ContactsUpdatedListener) {
|
fun addListener(listener: ContactsUpdatedListener) {
|
||||||
contactsUpdatedListeners.add(listener)
|
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) {
|
override fun onChatRoomRead(core: Core, chatRoom: ChatRoom) {
|
||||||
val address = chatRoom.peerAddress.asStringUriOnly()
|
val address = chatRoom.peerAddress.asStringUriOnly()
|
||||||
val notifiable = chatNotificationsMap[address]
|
val notifiable = chatNotificationsMap[address]
|
||||||
|
@ -798,6 +862,37 @@ class NotificationsManager(private val context: Context) {
|
||||||
notifiable.isGroup = true
|
notifiable.isGroup = true
|
||||||
notifiable.groupTitle = room.subject
|
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
|
return notifiable
|
||||||
}
|
}
|
||||||
|
@ -812,6 +907,13 @@ class NotificationsManager(private val context: Context) {
|
||||||
notifiable.remoteAddress = room.peerAddress.asStringUriOnly()
|
notifiable.remoteAddress = room.peerAddress.asStringUriOnly()
|
||||||
|
|
||||||
chatNotificationsMap[address] = notifiable
|
chatNotificationsMap[address] = notifiable
|
||||||
|
|
||||||
|
if (room.hasCapability(ChatRoom.Capabilities.OneToOne.toInt())) {
|
||||||
|
notifiable.isGroup = false
|
||||||
|
} else {
|
||||||
|
notifiable.isGroup = true
|
||||||
|
notifiable.groupTitle = room.subject
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return notifiable
|
return notifiable
|
||||||
}
|
}
|
||||||
|
@ -819,26 +921,7 @@ class NotificationsManager(private val context: Context) {
|
||||||
private fun getNotifiableMessage(message: ChatMessage, friend: Friend?): NotifiableMessage {
|
private fun getNotifiableMessage(message: ChatMessage, friend: Friend?): NotifiableMessage {
|
||||||
val roundPicture = ImageUtils.getRoundBitmapFromUri(context, friend?.getThumbnailUri())
|
val roundPicture = ImageUtils.getRoundBitmapFromUri(context, friend?.getThumbnailUri())
|
||||||
val displayName = friend?.name ?: LinphoneUtils.getDisplayName(message.fromAddress)
|
val displayName = friend?.name ?: LinphoneUtils.getDisplayName(message.fromAddress)
|
||||||
var text = ""
|
val text = LinphoneUtils.getTextDescribingMessage(message)
|
||||||
|
|
||||||
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 notifiableMessage = NotifiableMessage(
|
val notifiableMessage = NotifiableMessage(
|
||||||
text,
|
text,
|
||||||
friend,
|
friend,
|
||||||
|
|
|
@ -39,6 +39,7 @@ import androidx.core.view.doOnLayout
|
||||||
import androidx.databinding.*
|
import androidx.databinding.*
|
||||||
import androidx.emoji2.emojipicker.EmojiPickerView
|
import androidx.emoji2.emojipicker.EmojiPickerView
|
||||||
import androidx.emoji2.emojipicker.EmojiViewItem
|
import androidx.emoji2.emojipicker.EmojiViewItem
|
||||||
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import coil.dispose
|
import coil.dispose
|
||||||
import coil.load
|
import coil.load
|
||||||
import coil.request.CachePolicy
|
import coil.request.CachePolicy
|
||||||
|
@ -50,7 +51,6 @@ import org.linphone.BR
|
||||||
import org.linphone.LinphoneApplication.Companion.coreContext
|
import org.linphone.LinphoneApplication.Companion.coreContext
|
||||||
import org.linphone.LinphoneApplication.Companion.corePreferences
|
import org.linphone.LinphoneApplication.Companion.corePreferences
|
||||||
import org.linphone.R
|
import org.linphone.R
|
||||||
import org.linphone.activities.GenericActivity
|
|
||||||
import org.linphone.activities.main.settings.SettingListener
|
import org.linphone.activities.main.settings.SettingListener
|
||||||
import org.linphone.activities.voip.data.ConferenceParticipantDeviceData
|
import org.linphone.activities.voip.data.ConferenceParticipantDeviceData
|
||||||
import org.linphone.activities.voip.views.ScrollDotsView
|
import org.linphone.activities.voip.views.ScrollDotsView
|
||||||
|
@ -258,7 +258,9 @@ fun setListener(view: SeekBar, lambda: (Any) -> Unit) {
|
||||||
fun setInflatedViewStubLifecycleOwner(view: View, enable: Boolean) {
|
fun setInflatedViewStubLifecycleOwner(view: View, enable: Boolean) {
|
||||||
val binding = DataBindingUtil.bind<ViewDataBinding>(view)
|
val binding = DataBindingUtil.bind<ViewDataBinding>(view)
|
||||||
// This is a bit hacky...
|
// This is a bit hacky...
|
||||||
binding?.lifecycleOwner = view.context as GenericActivity
|
if (view.context is LifecycleOwner) {
|
||||||
|
binding?.lifecycleOwner = view.context as? LifecycleOwner
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@BindingAdapter("entries")
|
@BindingAdapter("entries")
|
||||||
|
@ -296,7 +298,9 @@ private fun <T> setEntries(
|
||||||
binding.setVariable(BR.parent, parent)
|
binding.setVariable(BR.parent, parent)
|
||||||
|
|
||||||
// This is a bit hacky...
|
// 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)
|
viewGroup.addView(binding.root)
|
||||||
}
|
}
|
||||||
|
|
|
@ -309,5 +309,32 @@ class LinphoneUtils {
|
||||||
|
|
||||||
return null
|
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
|
<LinearLayout
|
||||||
android:id="@+id/background"
|
android:id="@+id/background"
|
||||||
|
android:background="@drawable/chat_bubble_outgoing_full"
|
||||||
backgroundImage="@{data.backgroundRes, default=@drawable/chat_bubble_outgoing_full}"
|
backgroundImage="@{data.backgroundRes, default=@drawable/chat_bubble_outgoing_full}"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
@ -171,7 +172,7 @@
|
||||||
android:longClickable="true"
|
android:longClickable="true"
|
||||||
android:onClick="@{clickListener}"
|
android:onClick="@{clickListener}"
|
||||||
android:onLongClick="@{contextMenuClickListener}"
|
android:onLongClick="@{contextMenuClickListener}"
|
||||||
android:text="@{data.text}"
|
android:text="@{data.text, default=`Lorem Ipsum: dolor sit amet`}"
|
||||||
android:textColor="@color/dark_grey_color"
|
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: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"
|
android:textStyle="normal"
|
||||||
|
@ -196,6 +197,25 @@
|
||||||
|
|
||||||
</LinearLayout>
|
</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
|
<TextView
|
||||||
android:id="@+id/time"
|
android:id="@+id/time"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
@ -206,7 +226,7 @@
|
||||||
android:layout_marginTop="7dp"
|
android:layout_marginTop="7dp"
|
||||||
android:fontFamily="sans-serif"
|
android:fontFamily="sans-serif"
|
||||||
android:lineSpacingExtra="0sp"
|
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:textColor="@color/chat_bubble_text_color"
|
||||||
android:textSize="13sp"
|
android:textSize="13sp"
|
||||||
android:textStyle="normal"
|
android:textStyle="normal"
|
||||||
|
|
|
@ -4,6 +4,9 @@
|
||||||
|
|
||||||
<data>
|
<data>
|
||||||
<import type="android.view.View" />
|
<import type="android.view.View" />
|
||||||
|
<variable
|
||||||
|
name="emojiClickListener"
|
||||||
|
type="View.OnClickListener" />
|
||||||
<variable
|
<variable
|
||||||
name="resendClickListener"
|
name="resendClickListener"
|
||||||
type="View.OnClickListener" />
|
type="View.OnClickListener" />
|
||||||
|
@ -51,6 +54,77 @@
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:background="?attr/backgroundColor">
|
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
|
<TextView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="@dimen/chat_message_popup_item_height"
|
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_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_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="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>
|
</resources>
|
|
@ -89,4 +89,6 @@
|
||||||
<dimen name="chat_room_emoji_picker_height">290dp</dimen>
|
<dimen name="chat_room_emoji_picker_height">290dp</dimen>
|
||||||
<dimen name="chat_message_text_font_size">15sp</dimen>
|
<dimen name="chat_message_text_font_size">15sp</dimen>
|
||||||
<dimen name="chat_message_emoji_font_size">45sp</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>
|
</resources>
|
|
@ -234,6 +234,7 @@
|
||||||
<string name="chat_message_abort_removal">Abort</string>
|
<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_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_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_message_voice_recording">Voice message</string>
|
||||||
<string name="chat_room_presence_online">Online</string>
|
<string name="chat_room_presence_online">Online</string>
|
||||||
<string name="chat_room_presence_last_seen_online_today">Online today at</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_away">Away</string>
|
||||||
<string name="chat_room_presence_do_not_disturb">Do not disturb</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 -->
|
<!-- Recordings -->
|
||||||
<string name="recordings_empty_list">No recordings</string>
|
<string name="recordings_empty_list">No recordings</string>
|
||||||
<string name="recordings_export">Export recording using…</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_info_removed">Meeting info has been deleted</string>
|
||||||
<string name="conference_empty">You are currently alone in this group call</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_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_invitation">Meeting invitation</string>
|
||||||
<string name="conference_low_bandwidth">Low bandwidth detected, disabling video</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>
|
<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>
|
<item name="android:fontFamily">sans-serif</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="chat_message_emoji_reaction_font">
|
||||||
|
<item name="android:textSize">@dimen/chat_message_emoji_reaction_font_size</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
<!-- Dialog related -->
|
<!-- Dialog related -->
|
||||||
|
|
||||||
<style name="dialog_title_font" parent="@android:style/TextAppearance.Medium">
|
<style name="dialog_title_font" parent="@android:style/TextAppearance.Medium">
|
||||||
|
|
Loading…
Reference in a new issue