Added chat notifications as bubbles
This commit is contained in:
parent
a757b097fa
commit
b047ebae13
12 changed files with 406 additions and 12 deletions
|
@ -121,6 +121,12 @@
|
||||||
android:launchMode="singleTop"
|
android:launchMode="singleTop"
|
||||||
android:noHistory="true" />
|
android:noHistory="true" />
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".activities.chat_bubble.ChatBubbleActivity"
|
||||||
|
android:allowEmbedded="true"
|
||||||
|
android:documentLaunchMode="always"
|
||||||
|
android:resizeableActivity="true" />
|
||||||
|
|
||||||
<!-- Services -->
|
<!-- Services -->
|
||||||
|
|
||||||
<service
|
<service
|
||||||
|
|
|
@ -0,0 +1,144 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2010-2021 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.chat_bubble
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.databinding.DataBindingUtil
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import org.linphone.LinphoneApplication.Companion.coreContext
|
||||||
|
import org.linphone.R
|
||||||
|
import org.linphone.activities.GenericActivity
|
||||||
|
import org.linphone.activities.main.MainActivity
|
||||||
|
import org.linphone.activities.main.chat.adapters.ChatMessagesListAdapter
|
||||||
|
import org.linphone.activities.main.chat.viewmodels.*
|
||||||
|
import org.linphone.activities.main.viewmodels.ListTopBarViewModel
|
||||||
|
import org.linphone.core.ChatRoom
|
||||||
|
import org.linphone.core.Factory
|
||||||
|
import org.linphone.core.tools.Log
|
||||||
|
import org.linphone.databinding.ChatBubbleActivityBinding
|
||||||
|
|
||||||
|
class ChatBubbleActivity : GenericActivity() {
|
||||||
|
private lateinit var binding: ChatBubbleActivityBinding
|
||||||
|
private lateinit var viewModel: ChatRoomViewModel
|
||||||
|
private lateinit var listViewModel: ChatMessagesListViewModel
|
||||||
|
private lateinit var chatSendingViewModel: ChatMessageSendingViewModel
|
||||||
|
private lateinit var adapter: ChatMessagesListAdapter
|
||||||
|
|
||||||
|
private val observer = object : RecyclerView.AdapterDataObserver() {
|
||||||
|
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
|
||||||
|
if (positionStart == adapter.itemCount - 1) {
|
||||||
|
adapter.notifyItemChanged(positionStart - 1) // For grouping purposes
|
||||||
|
scrollToBottom()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
binding = DataBindingUtil.setContentView(this, R.layout.chat_bubble_activity)
|
||||||
|
binding.lifecycleOwner = this
|
||||||
|
|
||||||
|
val localSipUri = intent.getStringExtra("LocalSipUri")
|
||||||
|
val remoteSipUri = intent.getStringExtra("RemoteSipUri")
|
||||||
|
var chatRoom: ChatRoom? = null
|
||||||
|
|
||||||
|
if (localSipUri != null && remoteSipUri != null) {
|
||||||
|
Log.i("[Chat Bubble] Found local [$localSipUri] & remote [$remoteSipUri] addresses in arguments")
|
||||||
|
val localAddress = Factory.instance().createAddress(localSipUri)
|
||||||
|
val remoteSipAddress = Factory.instance().createAddress(remoteSipUri)
|
||||||
|
chatRoom = coreContext.core.searchChatRoom(
|
||||||
|
null, localAddress, remoteSipAddress, arrayOfNulls(
|
||||||
|
0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
chatRoom ?: return
|
||||||
|
chatRoom.markAsRead()
|
||||||
|
|
||||||
|
viewModel = ViewModelProvider(
|
||||||
|
this,
|
||||||
|
ChatRoomViewModelFactory(chatRoom)
|
||||||
|
)[ChatRoomViewModel::class.java]
|
||||||
|
binding.viewModel = viewModel
|
||||||
|
|
||||||
|
listViewModel = ViewModelProvider(
|
||||||
|
this,
|
||||||
|
ChatMessagesListViewModelFactory(chatRoom)
|
||||||
|
)[ChatMessagesListViewModel::class.java]
|
||||||
|
|
||||||
|
chatSendingViewModel = ViewModelProvider(
|
||||||
|
this,
|
||||||
|
ChatMessageSendingViewModelFactory(chatRoom)
|
||||||
|
)[ChatMessageSendingViewModel::class.java]
|
||||||
|
binding.chatSendingViewModel = chatSendingViewModel
|
||||||
|
|
||||||
|
val listSelectionViewModel = ViewModelProvider(this).get(ListTopBarViewModel::class.java)
|
||||||
|
adapter = ChatMessagesListAdapter(listSelectionViewModel, this)
|
||||||
|
// SubmitList is done on a background thread
|
||||||
|
// We need this adapter data observer to know when to scroll
|
||||||
|
binding.chatMessagesList.adapter = adapter
|
||||||
|
adapter.registerAdapterDataObserver(observer)
|
||||||
|
|
||||||
|
// Disable context menu on each message
|
||||||
|
adapter.disableContextMenu()
|
||||||
|
|
||||||
|
val layoutManager = LinearLayoutManager(this)
|
||||||
|
layoutManager.stackFromEnd = true
|
||||||
|
binding.chatMessagesList.layoutManager = layoutManager
|
||||||
|
|
||||||
|
listViewModel.events.observe(this, { events ->
|
||||||
|
adapter.submitList(events)
|
||||||
|
})
|
||||||
|
|
||||||
|
chatSendingViewModel.textToSend.observe(this, {
|
||||||
|
chatSendingViewModel.onTextToSendChanged(it)
|
||||||
|
})
|
||||||
|
|
||||||
|
binding.setOpenAppClickListener {
|
||||||
|
val intent = Intent(this, MainActivity::class.java)
|
||||||
|
intent.putExtra("RemoteSipUri", remoteSipUri)
|
||||||
|
intent.putExtra("LocalSipUri", localSipUri)
|
||||||
|
intent.putExtra("Chat", true)
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK and Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.setCloseBubbleClickListener {
|
||||||
|
val notificationId = coreContext.notificationsManager.getChatNotificationIdForSipUri(viewModel.chatRoom.peerAddress.asStringUriOnly())
|
||||||
|
coreContext.notificationsManager.cancel(notificationId)
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.setSendMessageClickListener {
|
||||||
|
chatSendingViewModel.sendMessage()
|
||||||
|
binding.message.text?.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun scrollToBottom() {
|
||||||
|
if (adapter.itemCount > 0) {
|
||||||
|
binding.chatMessagesList.scrollToPosition(adapter.itemCount - 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -83,6 +83,8 @@ class ChatMessagesListAdapter(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var contextMenuDisabled: Boolean = false
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
return when (viewType) {
|
return when (viewType) {
|
||||||
EventLog.Type.ConferenceChatMessage.toInt() -> createChatMessageViewHolder(parent)
|
EventLog.Type.ConferenceChatMessage.toInt() -> createChatMessageViewHolder(parent)
|
||||||
|
@ -119,6 +121,10 @@ class ChatMessagesListAdapter(
|
||||||
return eventLog.type.toInt()
|
return eventLog.type.toInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun disableContextMenu() {
|
||||||
|
contextMenuDisabled = true
|
||||||
|
}
|
||||||
|
|
||||||
inner class ChatMessageViewHolder(
|
inner class ChatMessageViewHolder(
|
||||||
private val binding: ChatMessageListCellBinding
|
private val binding: ChatMessageListCellBinding
|
||||||
) : RecyclerView.ViewHolder(binding.root), PopupMenu.OnMenuItemClickListener {
|
) : RecyclerView.ViewHolder(binding.root), PopupMenu.OnMenuItemClickListener {
|
||||||
|
@ -176,6 +182,8 @@ class ChatMessagesListAdapter(
|
||||||
|
|
||||||
executePendingBindings()
|
executePendingBindings()
|
||||||
|
|
||||||
|
if (contextMenuDisabled) return
|
||||||
|
|
||||||
setContextMenuClickListener {
|
setContextMenuClickListener {
|
||||||
val popup = PopupMenu(root.context, background)
|
val popup = PopupMenu(root.context, background)
|
||||||
popup.setOnMenuItemClickListener(this@ChatMessageViewHolder)
|
popup.setOnMenuItemClickListener(this@ChatMessageViewHolder)
|
||||||
|
|
|
@ -72,8 +72,6 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var chatRoomAddress: String? = null
|
|
||||||
|
|
||||||
override fun getLayoutId(): Int {
|
override fun getLayoutId(): Int {
|
||||||
return R.layout.chat_room_detail_fragment
|
return R.layout.chat_room_detail_fragment
|
||||||
}
|
}
|
||||||
|
@ -121,7 +119,6 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
|
||||||
|
|
||||||
Compatibility.setLocusIdInContentCaptureSession(binding.root, chatRoom)
|
Compatibility.setLocusIdInContentCaptureSession(binding.root, chatRoom)
|
||||||
|
|
||||||
chatRoomAddress = chatRoom.peerAddress.asStringUriOnly()
|
|
||||||
isSecure = chatRoom.currentParams.encryptionEnabled()
|
isSecure = chatRoom.currentParams.encryptionEnabled()
|
||||||
|
|
||||||
viewModel = ViewModelProvider(
|
viewModel = ViewModelProvider(
|
||||||
|
@ -328,7 +325,7 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
|
||||||
super.onResume()
|
super.onResume()
|
||||||
|
|
||||||
// Prevent notifications for this chat room to be displayed
|
// Prevent notifications for this chat room to be displayed
|
||||||
coreContext.notificationsManager.currentlyDisplayedChatRoomAddress = chatRoomAddress
|
coreContext.notificationsManager.currentlyDisplayedChatRoomAddress = viewModel.chatRoom.peerAddress.asStringUriOnly()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
package org.linphone.compatibility
|
package org.linphone.compatibility
|
||||||
|
|
||||||
import android.annotation.TargetApi
|
import android.annotation.TargetApi
|
||||||
|
import android.app.NotificationManager
|
||||||
import android.content.ContentValues
|
import android.content.ContentValues
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
@ -53,6 +54,14 @@ class Api29Compatibility {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun canChatMessageChannelBubble(context: Context): Boolean {
|
||||||
|
val notificationManager: NotificationManager =
|
||||||
|
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
|
val bubblesAllowed = notificationManager.areBubblesAllowed()
|
||||||
|
Log.i("[Notifications Manager] Bubbles notifications are ${if (bubblesAllowed) "allowed" else "forbidden"}")
|
||||||
|
return bubblesAllowed
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun addImageToMediaStore(context: Context, content: Content): Boolean {
|
suspend fun addImageToMediaStore(context: Context, content: Content): Boolean {
|
||||||
val filePath = content.filePath
|
val filePath = content.filePath
|
||||||
if (filePath == null) {
|
if (filePath == null) {
|
||||||
|
|
|
@ -167,6 +167,13 @@ class Compatibility {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun canChatMessageChannelBubble(context: Context): Boolean {
|
||||||
|
if (Version.sdkAboveOrEqual(Version.API29_ANDROID_10)) {
|
||||||
|
return Api29Compatibility.canChatMessageChannelBubble(context)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun addImageToMediaStore(context: Context, content: Content): Boolean {
|
suspend fun addImageToMediaStore(context: Context, content: Content): Boolean {
|
||||||
if (Version.sdkAboveOrEqual(Version.API29_ANDROID_10)) {
|
if (Version.sdkAboveOrEqual(Version.API29_ANDROID_10)) {
|
||||||
return Api29Compatibility.addImageToMediaStore(context, content)
|
return Api29Compatibility.addImageToMediaStore(context, content)
|
||||||
|
|
|
@ -136,7 +136,7 @@ open class Contact : Comparable<Contact> {
|
||||||
if (bm == null) IconCompat.createWithResource(
|
if (bm == null) IconCompat.createWithResource(
|
||||||
coreContext.context,
|
coreContext.context,
|
||||||
R.drawable.avatar
|
R.drawable.avatar
|
||||||
) else IconCompat.createWithBitmap(bm)
|
) else IconCompat.createWithAdaptiveBitmap(bm)
|
||||||
if (icon != null) {
|
if (icon != null) {
|
||||||
personBuilder.setIcon(icon)
|
personBuilder.setIcon(icon)
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,7 +71,7 @@ class NativeContact(val nativeId: String, private val lookupKey: String? = null)
|
||||||
if (bm == null) IconCompat.createWithResource(
|
if (bm == null) IconCompat.createWithResource(
|
||||||
coreContext.context,
|
coreContext.context,
|
||||||
R.drawable.avatar
|
R.drawable.avatar
|
||||||
) else IconCompat.createWithBitmap(bm)
|
) else IconCompat.createWithAdaptiveBitmap(bm)
|
||||||
if (icon != null) {
|
if (icon != null) {
|
||||||
personBuilder.setIcon(icon)
|
personBuilder.setIcon(icon)
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,6 +42,7 @@ import org.linphone.R
|
||||||
import org.linphone.activities.call.CallActivity
|
import org.linphone.activities.call.CallActivity
|
||||||
import org.linphone.activities.call.IncomingCallActivity
|
import org.linphone.activities.call.IncomingCallActivity
|
||||||
import org.linphone.activities.call.OutgoingCallActivity
|
import org.linphone.activities.call.OutgoingCallActivity
|
||||||
|
import org.linphone.activities.chat_bubble.ChatBubbleActivity
|
||||||
import org.linphone.activities.main.MainActivity
|
import org.linphone.activities.main.MainActivity
|
||||||
import org.linphone.compatibility.Compatibility
|
import org.linphone.compatibility.Compatibility
|
||||||
import org.linphone.contact.Contact
|
import org.linphone.contact.Contact
|
||||||
|
@ -68,7 +69,8 @@ private class NotifiableMessage(
|
||||||
val time: Long,
|
val time: Long,
|
||||||
val senderAvatar: Bitmap? = null,
|
val senderAvatar: Bitmap? = null,
|
||||||
var filePath: Uri? = null,
|
var filePath: Uri? = null,
|
||||||
var fileMime: String? = null
|
var fileMime: String? = null,
|
||||||
|
val isOutgoing: Boolean = false
|
||||||
)
|
)
|
||||||
|
|
||||||
class NotificationsManager(private val context: Context) {
|
class NotificationsManager(private val context: Context) {
|
||||||
|
@ -149,6 +151,10 @@ class NotificationsManager(private val context: Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (corePreferences.chatRoomShortcuts) {
|
||||||
|
Log.i("[Notifications Manager] Ensure chat room shortcut exists for bubble notification")
|
||||||
|
Compatibility.createShortcutsToChatRooms(context)
|
||||||
|
}
|
||||||
displayIncomingChatNotification(room, message)
|
displayIncomingChatNotification(room, message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -240,6 +246,14 @@ class NotificationsManager(private val context: Context) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getChatNotificationIdForSipUri(sipUri: String): Int {
|
||||||
|
val notifiable: Notifiable? = chatNotificationsMap[sipUri]
|
||||||
|
if (notifiable != null) {
|
||||||
|
return notifiable.notificationId
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
/* Service related */
|
/* Service related */
|
||||||
|
|
||||||
fun startForeground() {
|
fun startForeground() {
|
||||||
|
@ -529,8 +543,13 @@ class NotificationsManager(private val context: Context) {
|
||||||
.setArguments(args)
|
.setArguments(args)
|
||||||
.createPendingIntent()
|
.createPendingIntent()
|
||||||
|
|
||||||
|
val target = Intent(context, ChatBubbleActivity::class.java)
|
||||||
|
target.putExtra("RemoteSipUri", peerAddress)
|
||||||
|
target.putExtra("LocalSipUri", localAddress)
|
||||||
|
val bubbleIntent = PendingIntent.getActivity(context, notifiable.notificationId, target, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||||
|
|
||||||
val id = LinphoneUtils.getChatRoomId(localAddress, peerAddress)
|
val id = LinphoneUtils.getChatRoomId(localAddress, peerAddress)
|
||||||
val notification = createMessageNotification(notifiable, pendingIntent, id)
|
val notification = createMessageNotification(notifiable, pendingIntent, bubbleIntent, id)
|
||||||
notify(notifiable.notificationId, notification)
|
notify(notifiable.notificationId, notification)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -548,7 +567,7 @@ class NotificationsManager(private val context: Context) {
|
||||||
text += content.name
|
text += content.name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val notifiableMessage = NotifiableMessage(text, contact, displayName, message.time, senderAvatar = roundPicture)
|
val notifiableMessage = NotifiableMessage(text, contact, displayName, message.time, senderAvatar = roundPicture, isOutgoing = message.isOutgoing)
|
||||||
notifiable.messages.add(notifiableMessage)
|
notifiable.messages.add(notifiableMessage)
|
||||||
|
|
||||||
for (content in message.contents) {
|
for (content in message.contents) {
|
||||||
|
@ -601,7 +620,8 @@ class NotificationsManager(private val context: Context) {
|
||||||
message.textContent.orEmpty(),
|
message.textContent.orEmpty(),
|
||||||
null,
|
null,
|
||||||
notifiable.myself ?: LinphoneUtils.getDisplayName(message.fromAddress),
|
notifiable.myself ?: LinphoneUtils.getDisplayName(message.fromAddress),
|
||||||
System.currentTimeMillis()
|
System.currentTimeMillis(),
|
||||||
|
isOutgoing = true
|
||||||
)
|
)
|
||||||
notifiable.messages.add(reply)
|
notifiable.messages.add(reply)
|
||||||
|
|
||||||
|
@ -623,12 +643,14 @@ class NotificationsManager(private val context: Context) {
|
||||||
private fun createMessageNotification(
|
private fun createMessageNotification(
|
||||||
notifiable: Notifiable,
|
notifiable: Notifiable,
|
||||||
pendingIntent: PendingIntent,
|
pendingIntent: PendingIntent,
|
||||||
|
bubbleIntent: PendingIntent,
|
||||||
id: String
|
id: String
|
||||||
): Notification {
|
): Notification {
|
||||||
val me = Person.Builder().setName(notifiable.myself).build()
|
val me = Person.Builder().setName(notifiable.myself).build()
|
||||||
val style = NotificationCompat.MessagingStyle(me)
|
val style = NotificationCompat.MessagingStyle(me)
|
||||||
val largeIcon: Bitmap? = notifiable.messages.last().senderAvatar
|
val largeIcon: Bitmap? = notifiable.messages.last().senderAvatar
|
||||||
|
|
||||||
|
var lastPerson: Person? = null
|
||||||
for (message in notifiable.messages) {
|
for (message in notifiable.messages) {
|
||||||
val contact = message.contact
|
val contact = message.contact
|
||||||
val person = if (contact != null) {
|
val person = if (contact != null) {
|
||||||
|
@ -637,7 +659,7 @@ class NotificationsManager(private val context: Context) {
|
||||||
val builder = Person.Builder().setName(message.sender)
|
val builder = Person.Builder().setName(message.sender)
|
||||||
val userIcon =
|
val userIcon =
|
||||||
if (message.senderAvatar != null) {
|
if (message.senderAvatar != null) {
|
||||||
IconCompat.createWithBitmap(message.senderAvatar)
|
IconCompat.createWithAdaptiveBitmap(message.senderAvatar)
|
||||||
} else {
|
} else {
|
||||||
IconCompat.createWithResource(context, R.drawable.avatar)
|
IconCompat.createWithResource(context, R.drawable.avatar)
|
||||||
}
|
}
|
||||||
|
@ -645,6 +667,11 @@ class NotificationsManager(private val context: Context) {
|
||||||
builder.build()
|
builder.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We don't want to see our own avatar
|
||||||
|
if (!message.isOutgoing) {
|
||||||
|
lastPerson = person
|
||||||
|
}
|
||||||
|
|
||||||
val msg = if (!corePreferences.hideChatMessageContentInNotification) {
|
val msg = if (!corePreferences.hideChatMessageContentInNotification) {
|
||||||
NotificationCompat.MessagingStyle.Message(message.message, message.time, person)
|
NotificationCompat.MessagingStyle.Message(message.message, message.time, person)
|
||||||
} else {
|
} else {
|
||||||
|
@ -663,6 +690,10 @@ class NotificationsManager(private val context: Context) {
|
||||||
}
|
}
|
||||||
style.isGroupConversation = notifiable.isGroup
|
style.isGroupConversation = notifiable.isGroup
|
||||||
|
|
||||||
|
val icon = lastPerson?.icon ?: IconCompat.createWithResource(context, R.drawable.avatar)
|
||||||
|
val bubble = NotificationCompat.BubbleMetadata.Builder(bubbleIntent, icon)
|
||||||
|
.build()
|
||||||
|
|
||||||
val notificationBuilder = NotificationCompat.Builder(context, context.getString(R.string.notification_channel_chat_id))
|
val notificationBuilder = NotificationCompat.Builder(context, context.getString(R.string.notification_channel_chat_id))
|
||||||
.setSmallIcon(R.drawable.topbar_chat_notification)
|
.setSmallIcon(R.drawable.topbar_chat_notification)
|
||||||
.setAutoCancel(true)
|
.setAutoCancel(true)
|
||||||
|
@ -680,11 +711,16 @@ class NotificationsManager(private val context: Context) {
|
||||||
.addAction(getMarkMessageAsReadAction(notifiable))
|
.addAction(getMarkMessageAsReadAction(notifiable))
|
||||||
.setShortcutId(id)
|
.setShortcutId(id)
|
||||||
.setLocusId(LocusIdCompat(id))
|
.setLocusId(LocusIdCompat(id))
|
||||||
|
|
||||||
if (corePreferences.markAsReadUponChatMessageNotificationDismissal) {
|
if (corePreferences.markAsReadUponChatMessageNotificationDismissal) {
|
||||||
Log.i("[Notifications Manager] Chat room will be marked as read when notification will be dismissed")
|
Log.i("[Notifications Manager] Chat room will be marked as read when notification will be dismissed")
|
||||||
notificationBuilder
|
notificationBuilder
|
||||||
.setDeleteIntent(getMarkMessageAsReadPendingIntent(notifiable))
|
.setDeleteIntent(getMarkMessageAsReadPendingIntent(notifiable))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Compatibility.canChatMessageChannelBubble(context)) {
|
||||||
|
notificationBuilder.bubbleMetadata = bubble
|
||||||
|
}
|
||||||
return notificationBuilder.build()
|
return notificationBuilder.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
184
app/src/main/res/layout/chat_bubble_activity.xml
Normal file
184
app/src/main/res/layout/chat_bubble_activity.xml
Normal file
|
@ -0,0 +1,184 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layout xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<data>
|
||||||
|
<import type="android.view.View"/>
|
||||||
|
<variable
|
||||||
|
name="closeBubbleClickListener"
|
||||||
|
type="android.view.View.OnClickListener"/>
|
||||||
|
<variable
|
||||||
|
name="openAppClickListener"
|
||||||
|
type="android.view.View.OnClickListener"/>
|
||||||
|
<variable
|
||||||
|
name="sendMessageClickListener"
|
||||||
|
type="android.view.View.OnClickListener"/>
|
||||||
|
<variable
|
||||||
|
name="viewModel"
|
||||||
|
type="org.linphone.activities.main.chat.viewmodels.ChatRoomViewModel" />
|
||||||
|
<variable
|
||||||
|
name="chatSendingViewModel"
|
||||||
|
type="org.linphone.activities.main.chat.viewmodels.ChatMessageSendingViewModel" />
|
||||||
|
</data>
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/top_bar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="60dp"
|
||||||
|
android:background="?attr/lightToolbarBackgroundColor"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<!-- Disabled for now -->
|
||||||
|
<ImageView
|
||||||
|
android:contentDescription="@string/content_description_close_bubble"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:onClick="@{closeBubbleClickListener}"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="0.2"
|
||||||
|
android:layout_margin="5dp"
|
||||||
|
android:background="?attr/button_background_drawable"
|
||||||
|
android:src="@drawable/menu_delete_default"
|
||||||
|
app:tint="?attr/drawableTintColor" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_weight="0.4"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingLeft="5dp">
|
||||||
|
|
||||||
|
<org.linphone.views.MarqueeTextView
|
||||||
|
android:text="@{viewModel.oneToOneChatRoom ? (viewModel.contact.fullName ?? viewModel.displayName) : viewModel.subject}"
|
||||||
|
style="@style/toolbar_small_title_font"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:singleLine="true" />
|
||||||
|
|
||||||
|
<org.linphone.views.MarqueeTextView
|
||||||
|
android:text="@{viewModel.participants}"
|
||||||
|
android:visibility="@{viewModel.oneToOneChatRoom ? View.GONE : View.VISIBLE}"
|
||||||
|
style="@style/toolbar_small_title_font"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:singleLine="true" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Disabled for now -->
|
||||||
|
<ImageView
|
||||||
|
android:onClick="@{openAppClickListener}"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:contentDescription="@string/content_description_open_app"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="0.2"
|
||||||
|
android:layout_margin="5dp"
|
||||||
|
android:background="?attr/button_background_drawable"
|
||||||
|
app:tint="?attr/colorPrimary"
|
||||||
|
android:src="@drawable/linphone_logo"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/footer"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentBottom="true"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:background="?attr/backgroundColor"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<org.linphone.activities.main.chat.views.RichEditText
|
||||||
|
android:id="@+id/message"
|
||||||
|
android:enabled="@{!chatSendingViewModel.isReadOnly}"
|
||||||
|
android:text="@={chatSendingViewModel.textToSend}"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
android:layout_marginBottom="5dp"
|
||||||
|
android:layout_marginLeft="5dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:background="@drawable/resizable_text_field"
|
||||||
|
android:imeOptions="flagNoExtractUi"
|
||||||
|
android:inputType="textShortMessage|textMultiLine|textAutoComplete|textAutoCorrect|textCapSentences"
|
||||||
|
android:maxLines="6"
|
||||||
|
android:padding="5dp"
|
||||||
|
android:textColor="@color/black_color"
|
||||||
|
android:textCursorDrawable="@null" />
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/send_message"
|
||||||
|
android:onClick="@{sendMessageClickListener}"
|
||||||
|
android:enabled="@{chatSendingViewModel.sendMessageEnabled && !chatSendingViewModel.isReadOnly}"
|
||||||
|
android:contentDescription="@string/content_description_send_message"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="5dp"
|
||||||
|
android:src="@drawable/chat_send_message" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:visibility="@{viewModel.chatRoom.ephemeralEnabled() ? View.VISIBLE : View.GONE, default=gone}"
|
||||||
|
android:enabled="@{chatSendingViewModel.sendMessageEnabled && !chatSendingViewModel.isReadOnly}"
|
||||||
|
android:contentDescription="@string/content_description_ephemeral_message"
|
||||||
|
android:clickable="false"
|
||||||
|
android:layout_width="20dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:adjustViewBounds="true"
|
||||||
|
android:layout_alignRight="@id/send_message"
|
||||||
|
android:layout_alignBottom="@id/send_message"
|
||||||
|
android:padding="5dp"
|
||||||
|
android:src="@drawable/ephemeral_messages" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/remote_composing"
|
||||||
|
android:text="@{viewModel.composingList, default=@string/chat_remote_is_composing}"
|
||||||
|
android:visibility="@{viewModel.remoteIsComposing ? View.VISIBLE : View.INVISIBLE}"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_above="@id/footer"
|
||||||
|
android:layout_marginLeft="5dp"
|
||||||
|
style="@style/standard_small_text_font" />
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/chat_messages_list"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_above="@id/remote_composing"
|
||||||
|
android:layout_below="@+id/top_bar"
|
||||||
|
android:cacheColorHint="@color/transparent_color"
|
||||||
|
android:choiceMode="multipleChoice"
|
||||||
|
android:divider="@android:color/transparent"
|
||||||
|
android:listSelector="@color/transparent_color"
|
||||||
|
android:transcriptMode="normal" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:src="@{viewModel.securityLevelIcon, default=@drawable/security_alert_indicator}"
|
||||||
|
android:visibility="@{viewModel.encryptedChatRoom ? View.VISIBLE : View.GONE}"
|
||||||
|
android:contentDescription="@{viewModel.securityLevelContentDescription}"
|
||||||
|
android:adjustViewBounds="true"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@id/top_bar"
|
||||||
|
android:layout_alignParentRight="true"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginRight="8dp" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
</layout>
|
|
@ -185,7 +185,8 @@
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center_vertical"
|
android:layout_gravity="center_vertical"
|
||||||
android:layout_margin="5dp"
|
android:layout_marginTop="5dp"
|
||||||
|
android:layout_marginBottom="5dp"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:background="@drawable/resizable_text_field"
|
android:background="@drawable/resizable_text_field"
|
||||||
android:imeOptions="flagNoExtractUi"
|
android:imeOptions="flagNoExtractUi"
|
||||||
|
|
|
@ -707,4 +707,6 @@
|
||||||
<string name="content_description_toggle_call_stats">Show or hide call statistics</string>
|
<string name="content_description_toggle_call_stats">Show or hide call statistics</string>
|
||||||
<string name="content_description_chat_message_video_attachment">Video attachment</string>
|
<string name="content_description_chat_message_video_attachment">Video attachment</string>
|
||||||
<string name="content_description_take_screenshot">Take a screenshot of received video</string>
|
<string name="content_description_take_screenshot">Take a screenshot of received video</string>
|
||||||
|
<string name="content_description_close_bubble">Close notification bubble</string>
|
||||||
|
<string name="content_description_open_app">Open conversation in app instead of bubble</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Loading…
Reference in a new issue