mirror of
https://gitlab.linphone.org/BC/public/linphone-android.git
synced 2026-01-17 11:28:06 +00:00
Removed legacy chat views
This commit is contained in:
parent
548a597843
commit
e3022f42ab
34 changed files with 9 additions and 2940 deletions
|
|
@ -1,73 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 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.contacts
|
||||
|
||||
import android.content.ContentUris
|
||||
import android.net.Uri
|
||||
import android.provider.ContactsContract
|
||||
import androidx.annotation.WorkerThread
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import org.linphone.core.*
|
||||
|
||||
class ContactData(val friend: Friend) {
|
||||
val presenceStatus = MutableLiveData<ConsolidatedPresence>()
|
||||
|
||||
val name = MutableLiveData<String>()
|
||||
|
||||
val avatar = getAvatarUri()
|
||||
|
||||
private val friendListener = object : FriendListenerStub() {
|
||||
@WorkerThread
|
||||
override fun onPresenceReceived(fr: Friend) {
|
||||
presenceStatus.postValue(fr.consolidatedPresence)
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
name.postValue(friend.name)
|
||||
presenceStatus.postValue(friend.consolidatedPresence)
|
||||
|
||||
friend.addListener(friendListener)
|
||||
|
||||
presenceStatus.postValue(ConsolidatedPresence.Offline)
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
fun onDestroy() {
|
||||
friend.removeListener(friendListener)
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private fun getAvatarUri(): Uri? {
|
||||
val refKey = friend.refKey
|
||||
if (refKey != null) {
|
||||
val lookupUri = ContentUris.withAppendedId(
|
||||
ContactsContract.Contacts.CONTENT_URI,
|
||||
refKey.toLong()
|
||||
)
|
||||
return Uri.withAppendedPath(
|
||||
lookupUri,
|
||||
ContactsContract.Contacts.Photo.CONTENT_DIRECTORY
|
||||
)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 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.contacts
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.databinding.DataBindingUtil
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.linphone.R
|
||||
import org.linphone.databinding.ContactSelectionCellBinding
|
||||
|
||||
class ContactsSelectionAdapter(
|
||||
private val viewLifecycleOwner: LifecycleOwner
|
||||
) : ListAdapter<ContactData, RecyclerView.ViewHolder>(ContactDataDiffCallback()) {
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||
val binding: ContactSelectionCellBinding = DataBindingUtil.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
R.layout.contact_selection_cell,
|
||||
parent,
|
||||
false
|
||||
)
|
||||
return ViewHolder(binding)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||
(holder as ViewHolder).bind(getItem(position))
|
||||
}
|
||||
|
||||
inner class ViewHolder(
|
||||
private val binding: ContactSelectionCellBinding
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
fun bind(contactData: ContactData) {
|
||||
with(binding) {
|
||||
data = contactData
|
||||
|
||||
lifecycleOwner = viewLifecycleOwner
|
||||
|
||||
executePendingBindings()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ContactDataDiffCallback : DiffUtil.ItemCallback<ContactData>() {
|
||||
override fun areItemsTheSame(
|
||||
oldItem: ContactData,
|
||||
newItem: ContactData
|
||||
): Boolean {
|
||||
return oldItem.friend.refKey == newItem.friend.refKey
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(
|
||||
oldItem: ContactData,
|
||||
newItem: ContactData
|
||||
): Boolean {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
@ -335,8 +335,8 @@ class CoreContext @UiThread constructor(val context: Context) : HandlerThread("C
|
|||
val appName = context.getString(org.linphone.R.string.app_name)
|
||||
val androidVersion = BuildConfig.VERSION_NAME
|
||||
val userAgent = "$appName/$androidVersion ($deviceName) LinphoneSDK"
|
||||
val sdkVersion = context.getString(org.linphone.core.R.string.linphone_sdk_version)
|
||||
val sdkBranch = context.getString(org.linphone.core.R.string.linphone_sdk_branch)
|
||||
val sdkVersion = context.getString(R.string.linphone_sdk_version)
|
||||
val sdkBranch = context.getString(R.string.linphone_sdk_branch)
|
||||
val sdkUserAgent = "$sdkVersion ($sdkBranch)"
|
||||
core.setUserAgent(userAgent, sdkUserAgent)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -124,19 +124,6 @@ class CallsFragment : GenericFragment() {
|
|||
}
|
||||
}
|
||||
|
||||
sharedViewModel.navigateToConversationsEvent.observe(viewLifecycleOwner) {
|
||||
it.consume {
|
||||
if (findNavController().currentDestination?.id == R.id.callsFragment) {
|
||||
// To prevent any previously seen contact to show up when navigating back to here later
|
||||
binding.callsNavContainer.findNavController().popBackStack()
|
||||
|
||||
val action =
|
||||
CallsFragmentDirections.actionCallsFragmentToConversationsFragment()
|
||||
findNavController().navigate(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sharedViewModel.navigateToContactsEvent.observe(viewLifecycleOwner) {
|
||||
it.consume {
|
||||
if (findNavController().currentDestination?.id == R.id.callsFragment) {
|
||||
|
|
|
|||
|
|
@ -134,18 +134,6 @@ class ContactsFragment : GenericFragment() {
|
|||
}
|
||||
}
|
||||
|
||||
sharedViewModel.navigateToConversationsEvent.observe(viewLifecycleOwner) {
|
||||
it.consume {
|
||||
if (findNavController().currentDestination?.id == R.id.contactsFragment) {
|
||||
// To prevent any previously seen contact to show up when navigating back to here later
|
||||
binding.contactsNavContainer.findNavController().popBackStack()
|
||||
|
||||
val action = ContactsFragmentDirections.actionContactsFragmentToConversationsFragment()
|
||||
findNavController().navigate(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sharedViewModel.navigateToCallsEvent.observe(viewLifecycleOwner) {
|
||||
it.consume {
|
||||
if (findNavController().currentDestination?.id == R.id.contactsFragment) {
|
||||
|
|
|
|||
|
|
@ -134,11 +134,11 @@ class ContactViewModel @UiThread constructor() : ViewModel() {
|
|||
|
||||
val organization = friend.organization
|
||||
if (!organization.isNullOrEmpty()) {
|
||||
company.postValue(organization!!)
|
||||
company.postValue(organization)
|
||||
}
|
||||
val jobTitle = friend.jobTitle
|
||||
if (!jobTitle.isNullOrEmpty()) {
|
||||
title.postValue(jobTitle!!)
|
||||
title.postValue(jobTitle)
|
||||
}
|
||||
|
||||
val addressesAndNumbers = friend.getListOfSipAddressesAndPhoneNumbers(listener)
|
||||
|
|
@ -208,7 +208,7 @@ class ContactViewModel @UiThread constructor() : ViewModel() {
|
|||
val vCard = friend.vcard?.asVcard4String()
|
||||
if (!vCard.isNullOrEmpty()) {
|
||||
Log.i("$TAG Friend has been successfully dumped as vCard string")
|
||||
val fileName = friend.name.orEmpty().replace(" ", "_").toLowerCase(
|
||||
val fileName = friend.name.orEmpty().replace(" ", "_").lowercase(
|
||||
Locale.getDefault()
|
||||
)
|
||||
val file = FileUtils.getFileStorageCacheDir(
|
||||
|
|
|
|||
|
|
@ -192,7 +192,7 @@ class ContactsListViewModel @UiThread constructor() : AbstractTopBarViewModel()
|
|||
val vCard = friend.vcard?.asVcard4String()
|
||||
if (!vCard.isNullOrEmpty()) {
|
||||
Log.i("$TAG Friend has been successfully dumped as vCard string")
|
||||
val fileName = friend.name.orEmpty().replace(" ", "_").toLowerCase(
|
||||
val fileName = friend.name.orEmpty().replace(" ", "_").lowercase(
|
||||
Locale.getDefault()
|
||||
)
|
||||
val file = FileUtils.getFileStorageCacheDir(
|
||||
|
|
|
|||
|
|
@ -1,100 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2023 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.ui.main.conversations
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.doOnPreDraw
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.navigation.navGraphViewModels
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import org.linphone.R
|
||||
import org.linphone.databinding.ConversationFragmentBinding
|
||||
import org.linphone.ui.main.conversations.adapter.ChatEventLogsListAdapter
|
||||
import org.linphone.ui.main.conversations.viewmodel.ConversationViewModel
|
||||
|
||||
class ConversationFragment : Fragment() {
|
||||
private lateinit var binding: ConversationFragmentBinding
|
||||
private val viewModel: ConversationViewModel by navGraphViewModels(
|
||||
R.id.main_nav_graph
|
||||
)
|
||||
private lateinit var adapter: ChatEventLogsListAdapter
|
||||
|
||||
override fun onDestroyView() {
|
||||
binding.messagesList.adapter = null
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
binding = ConversationFragmentBinding.inflate(layoutInflater)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
postponeEnterTransition()
|
||||
|
||||
binding.lifecycleOwner = viewLifecycleOwner
|
||||
binding.viewModel = viewModel
|
||||
|
||||
val localSipUri = arguments?.getString("localSipUri")
|
||||
?: savedInstanceState?.getString("localSipUri")
|
||||
val remoteSipUri = arguments?.getString("remoteSipUri")
|
||||
?: savedInstanceState?.getString("remoteSipUri")
|
||||
if (localSipUri != null && remoteSipUri != null) {
|
||||
viewModel.loadChatRoom(localSipUri, remoteSipUri)
|
||||
} else {
|
||||
// Chat room not found, going back
|
||||
// TODO FIXME : show error
|
||||
(view.parent as? ViewGroup)?.doOnPreDraw {
|
||||
requireActivity().onBackPressedDispatcher.onBackPressed()
|
||||
}
|
||||
}
|
||||
arguments?.clear()
|
||||
|
||||
adapter = ChatEventLogsListAdapter(viewLifecycleOwner)
|
||||
binding.messagesList.setHasFixedSize(false)
|
||||
binding.messagesList.adapter = adapter
|
||||
|
||||
val layoutManager = LinearLayoutManager(requireContext())
|
||||
binding.messagesList.layoutManager = layoutManager
|
||||
|
||||
viewModel.events.observe(
|
||||
viewLifecycleOwner
|
||||
) {
|
||||
adapter.submitList(it)
|
||||
|
||||
(view.parent as? ViewGroup)?.doOnPreDraw {
|
||||
startPostponedEnterTransition()
|
||||
binding.messagesList.scrollToPosition(adapter.itemCount - 1)
|
||||
}
|
||||
}
|
||||
|
||||
binding.setBackClickListener {
|
||||
requireActivity().onBackPressedDispatcher.onBackPressed()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,90 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2023 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.ui.main.conversations
|
||||
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import org.linphone.LinphoneApplication.Companion.coreContext
|
||||
import org.linphone.core.ChatRoom
|
||||
import org.linphone.databinding.ChatRoomMenuBinding
|
||||
|
||||
class ConversationMenuDialogFragment(
|
||||
private val chatRoom: ChatRoom,
|
||||
private val onDismiss: (() -> Unit)? = null
|
||||
) : BottomSheetDialogFragment() {
|
||||
companion object {
|
||||
const val TAG = "ConversationMenuDialogFragment"
|
||||
}
|
||||
|
||||
override fun onCancel(dialog: DialogInterface) {
|
||||
onDismiss?.invoke()
|
||||
super.onCancel(dialog)
|
||||
}
|
||||
|
||||
override fun onDismiss(dialog: DialogInterface) {
|
||||
onDismiss?.invoke()
|
||||
super.onDismiss(dialog)
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
val view = ChatRoomMenuBinding.inflate(layoutInflater)
|
||||
|
||||
view.isMuted = chatRoom.muted
|
||||
view.isRead = chatRoom.unreadMessagesCount == 0
|
||||
|
||||
view.setMarkAsReadClickListener {
|
||||
coreContext.postOnCoreThread {
|
||||
chatRoom.markAsRead()
|
||||
}
|
||||
dismiss()
|
||||
}
|
||||
|
||||
view.setMuteClickListener {
|
||||
coreContext.postOnCoreThread {
|
||||
chatRoom.muted = true
|
||||
}
|
||||
dismiss()
|
||||
}
|
||||
|
||||
view.setUnMuteClickListener {
|
||||
coreContext.postOnCoreThread {
|
||||
chatRoom.muted = false
|
||||
}
|
||||
dismiss()
|
||||
}
|
||||
|
||||
view.setDeleteClickListener {
|
||||
coreContext.postOnCoreThread { core ->
|
||||
core.deleteChatRoom(chatRoom)
|
||||
}
|
||||
dismiss()
|
||||
}
|
||||
|
||||
return view.root
|
||||
}
|
||||
}
|
||||
|
|
@ -1,174 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2023 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.ui.main.conversations
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.animation.Animation
|
||||
import android.view.animation.AnimationUtils
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.core.view.doOnPreDraw
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.navigation.navGraphViewModels
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.transition.AutoTransition
|
||||
import org.linphone.R
|
||||
import org.linphone.databinding.ConversationsFragmentBinding
|
||||
import org.linphone.ui.main.conversations.adapter.ConversationsListAdapter
|
||||
import org.linphone.ui.main.conversations.viewmodel.ConversationsListViewModel
|
||||
|
||||
class ConversationsFragment : Fragment() {
|
||||
private lateinit var binding: ConversationsFragmentBinding
|
||||
private val listViewModel: ConversationsListViewModel by navGraphViewModels(
|
||||
R.id.main_nav_graph
|
||||
)
|
||||
private lateinit var adapter: ConversationsListAdapter
|
||||
|
||||
private val observer = object : RecyclerView.AdapterDataObserver() {
|
||||
override fun onChanged() {
|
||||
scrollToTop()
|
||||
}
|
||||
|
||||
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
|
||||
if (positionStart == 0 && itemCount == 1) {
|
||||
scrollToTop()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) {
|
||||
scrollToTop()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateAnimation(transit: Int, enter: Boolean, nextAnim: Int): Animation? {
|
||||
if (findNavController().currentDestination?.id == R.id.newConversationFragment ||
|
||||
findNavController().currentDestination?.id == R.id.conversationFragment
|
||||
) {
|
||||
// Holds fragment in place while (new) conversation fragment slides over it
|
||||
return AnimationUtils.loadAnimation(activity, R.anim.hold)
|
||||
}
|
||||
return super.onCreateAnimation(transit, enter, nextAnim)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
binding.conversationsList.adapter = null
|
||||
adapter.unregisterAdapterDataObserver(observer)
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
binding = ConversationsFragmentBinding.inflate(layoutInflater)
|
||||
sharedElementEnterTransition = AutoTransition()
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
postponeEnterTransition()
|
||||
|
||||
binding.lifecycleOwner = viewLifecycleOwner
|
||||
binding.viewModel = listViewModel
|
||||
|
||||
adapter = ConversationsListAdapter(viewLifecycleOwner)
|
||||
adapter.registerAdapterDataObserver(observer)
|
||||
binding.conversationsList.setHasFixedSize(true)
|
||||
binding.conversationsList.adapter = adapter
|
||||
|
||||
adapter.chatRoomClickedEvent.observe(viewLifecycleOwner) {
|
||||
it.consume { data ->
|
||||
val bundle = bundleOf()
|
||||
bundle.putString("localSipUri", data.localSipUri)
|
||||
bundle.putString("remoteSipUri", data.remoteSipUri)
|
||||
|
||||
if (findNavController().currentDestination?.id == R.id.conversationsFragment) {
|
||||
findNavController().navigate(
|
||||
R.id.action_conversationsFragment_to_conversationFragment,
|
||||
bundle
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
adapter.chatRoomLongClickedEvent.observe(viewLifecycleOwner) {
|
||||
it.consume { data ->
|
||||
val modalBottomSheet = ConversationMenuDialogFragment(data.chatRoom) {
|
||||
adapter.resetSelection()
|
||||
}
|
||||
modalBottomSheet.show(parentFragmentManager, ConversationMenuDialogFragment.TAG)
|
||||
}
|
||||
}
|
||||
|
||||
val layoutManager = LinearLayoutManager(requireContext())
|
||||
binding.conversationsList.layoutManager = layoutManager
|
||||
|
||||
listViewModel.chatRoomsList.observe(
|
||||
viewLifecycleOwner
|
||||
) {
|
||||
adapter.submitList(it)
|
||||
|
||||
(view.parent as? ViewGroup)?.doOnPreDraw {
|
||||
startPostponedEnterTransition()
|
||||
}
|
||||
}
|
||||
|
||||
listViewModel.notifyItemChangedEvent.observe(viewLifecycleOwner) {
|
||||
it.consume { index ->
|
||||
adapter.notifyItemChanged(index)
|
||||
}
|
||||
}
|
||||
|
||||
/*listViewModel.focusSearchBarEvent.observe(viewLifecycleOwner) {
|
||||
it.consume { show ->
|
||||
if (show) {
|
||||
// To automatically open keyboard
|
||||
binding.topBar.search.showKeyboard(requireActivity().window)
|
||||
} else {
|
||||
binding.topBar.search.hideKeyboard()
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
binding.setOnNewConversationClicked {
|
||||
if (findNavController().currentDestination?.id == R.id.conversationsFragment) {
|
||||
val action = ConversationsFragmentDirections.actionConversationsFragmentToNewConversationFragment()
|
||||
findNavController().navigate(action)
|
||||
}
|
||||
}
|
||||
|
||||
binding.setOnContactsClicked {
|
||||
if (findNavController().currentDestination?.id == R.id.conversationsFragment) {
|
||||
val action = ConversationsFragmentDirections.actionConversationsFragmentToContactsFragment()
|
||||
findNavController().navigate(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun scrollToTop() {
|
||||
binding.conversationsList.scrollToPosition(0)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,110 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2023 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.ui.main.conversations
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.animation.Animation
|
||||
import android.view.animation.AnimationUtils
|
||||
import androidx.core.view.doOnPreDraw
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.navigation.navGraphViewModels
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import org.linphone.LinphoneApplication.Companion.coreContext
|
||||
import org.linphone.R
|
||||
import org.linphone.contacts.ContactsSelectionAdapter
|
||||
import org.linphone.databinding.ConversationStartFragmentBinding
|
||||
import org.linphone.ui.main.conversations.viewmodel.NewConversationViewModel
|
||||
|
||||
class NewConversationFragment : Fragment() {
|
||||
private lateinit var binding: ConversationStartFragmentBinding
|
||||
private lateinit var adapter: ContactsSelectionAdapter
|
||||
private val viewModel: NewConversationViewModel by navGraphViewModels(
|
||||
R.id.main_nav_graph
|
||||
)
|
||||
|
||||
override fun onCreateAnimation(transit: Int, enter: Boolean, nextAnim: Int): Animation? {
|
||||
if (findNavController().currentDestination?.id == R.id.conversationFragment) {
|
||||
// Holds fragment in place while created conversation fragment slides over it
|
||||
return AnimationUtils.loadAnimation(activity, R.anim.hold)
|
||||
}
|
||||
return super.onCreateAnimation(transit, enter, nextAnim)
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
binding = ConversationStartFragmentBinding.inflate(layoutInflater)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
postponeEnterTransition()
|
||||
|
||||
binding.lifecycleOwner = viewLifecycleOwner
|
||||
binding.viewModel = viewModel
|
||||
|
||||
adapter = ContactsSelectionAdapter(viewLifecycleOwner)
|
||||
binding.contactsList.adapter = adapter
|
||||
binding.contactsList.setHasFixedSize(true)
|
||||
|
||||
val layoutManager = LinearLayoutManager(requireContext())
|
||||
binding.contactsList.layoutManager = layoutManager
|
||||
|
||||
viewModel.contactsList.observe(
|
||||
viewLifecycleOwner
|
||||
) {
|
||||
adapter.submitList(it)
|
||||
|
||||
(view.parent as? ViewGroup)?.doOnPreDraw {
|
||||
startPostponedEnterTransition()
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.filter.observe(
|
||||
viewLifecycleOwner
|
||||
) {
|
||||
val filter = it.orEmpty().trim()
|
||||
coreContext.postOnCoreThread {
|
||||
viewModel.applyFilter(filter)
|
||||
}
|
||||
}
|
||||
|
||||
binding.setBackClickListener {
|
||||
requireActivity().onBackPressedDispatcher.onBackPressed()
|
||||
}
|
||||
|
||||
viewModel.goToChatRoom.observe(viewLifecycleOwner) {
|
||||
it.consume {
|
||||
if (findNavController().currentDestination?.id == R.id.newConversationFragment) {
|
||||
findNavController().navigate(
|
||||
R.id.action_newConversationFragment_to_conversationFragment
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,152 +0,0 @@
|
|||
package org.linphone.ui.main.conversations.adapter
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.databinding.DataBindingUtil
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.linphone.R
|
||||
import org.linphone.core.ChatMessage
|
||||
import org.linphone.databinding.ChatBubbleIncomingBinding
|
||||
import org.linphone.databinding.ChatBubbleOutgoingBinding
|
||||
import org.linphone.databinding.ChatEventBinding
|
||||
import org.linphone.ui.main.conversations.data.ChatMessageData
|
||||
import org.linphone.ui.main.conversations.data.EventData
|
||||
import org.linphone.ui.main.conversations.data.EventLogData
|
||||
|
||||
class ChatEventLogsListAdapter(
|
||||
private val viewLifecycleOwner: LifecycleOwner
|
||||
) : ListAdapter<EventLogData, RecyclerView.ViewHolder>(EventLogDiffCallback()) {
|
||||
companion object {
|
||||
const val INCOMING_CHAT_MESSAGE = 1
|
||||
const val OUTGOING_CHAT_MESSAGE = 2
|
||||
const val EVENT = 3
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||
return when (viewType) {
|
||||
INCOMING_CHAT_MESSAGE -> createIncomingChatBubble(parent)
|
||||
OUTGOING_CHAT_MESSAGE -> createOutgoingChatBubble(parent)
|
||||
else -> createEvent(parent)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemViewType(position: Int): Int {
|
||||
val data = getItem(position)
|
||||
if (data.data is ChatMessageData) {
|
||||
if (data.data.isOutgoing) {
|
||||
return OUTGOING_CHAT_MESSAGE
|
||||
}
|
||||
return INCOMING_CHAT_MESSAGE
|
||||
}
|
||||
return EVENT
|
||||
}
|
||||
|
||||
private fun createIncomingChatBubble(parent: ViewGroup): IncomingBubbleViewHolder {
|
||||
val binding: ChatBubbleIncomingBinding = DataBindingUtil.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
R.layout.chat_bubble_incoming,
|
||||
parent,
|
||||
false
|
||||
)
|
||||
return IncomingBubbleViewHolder(binding)
|
||||
}
|
||||
|
||||
private fun createOutgoingChatBubble(parent: ViewGroup): OutgoingBubbleViewHolder {
|
||||
val binding: ChatBubbleOutgoingBinding = DataBindingUtil.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
R.layout.chat_bubble_outgoing,
|
||||
parent,
|
||||
false
|
||||
)
|
||||
return OutgoingBubbleViewHolder(binding)
|
||||
}
|
||||
|
||||
private fun createEvent(parent: ViewGroup): EventViewHolder {
|
||||
val binding: ChatEventBinding = DataBindingUtil.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
R.layout.chat_event,
|
||||
parent,
|
||||
false
|
||||
)
|
||||
return EventViewHolder(binding)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||
val eventLog = getItem(position)
|
||||
when (holder) {
|
||||
is IncomingBubbleViewHolder -> holder.bind(eventLog.data as ChatMessageData)
|
||||
is OutgoingBubbleViewHolder -> holder.bind(eventLog.data as ChatMessageData)
|
||||
is EventViewHolder -> holder.bind(eventLog.data as EventData)
|
||||
}
|
||||
}
|
||||
|
||||
inner class IncomingBubbleViewHolder(
|
||||
val binding: ChatBubbleIncomingBinding
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
fun bind(chatMessageData: ChatMessageData) {
|
||||
with(binding) {
|
||||
data = chatMessageData
|
||||
|
||||
lifecycleOwner = viewLifecycleOwner
|
||||
executePendingBindings()
|
||||
|
||||
// To ensure the measure is right since we do some computation for proper multi-line wrap_content
|
||||
text.forceLayout()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inner class OutgoingBubbleViewHolder(
|
||||
val binding: ChatBubbleOutgoingBinding
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
fun bind(chatMessageData: ChatMessageData) {
|
||||
with(binding) {
|
||||
data = chatMessageData
|
||||
|
||||
lifecycleOwner = viewLifecycleOwner
|
||||
executePendingBindings()
|
||||
|
||||
// To ensure the measure is right since we do some computation for proper multi-line wrap_content
|
||||
text.forceLayout()
|
||||
}
|
||||
}
|
||||
}
|
||||
inner class EventViewHolder(
|
||||
val binding: ChatEventBinding
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
fun bind(eventData: EventData) {
|
||||
with(binding) {
|
||||
data = eventData
|
||||
|
||||
lifecycleOwner = viewLifecycleOwner
|
||||
executePendingBindings()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class EventLogDiffCallback : DiffUtil.ItemCallback<EventLogData>() {
|
||||
override fun areItemsTheSame(oldItem: EventLogData, newItem: EventLogData): Boolean {
|
||||
return if (oldItem.isEvent && newItem.isEvent) {
|
||||
oldItem.notifyId == newItem.notifyId
|
||||
} else if (!oldItem.isEvent && !newItem.isEvent) {
|
||||
val oldData = (oldItem.data as ChatMessageData)
|
||||
val newData = (newItem.data as ChatMessageData)
|
||||
oldData.id.isNotEmpty() && oldData.id == newData.id
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: EventLogData, newItem: EventLogData): Boolean {
|
||||
return if (oldItem.isEvent && newItem.isEvent) {
|
||||
true
|
||||
} else {
|
||||
val newData = (newItem.data as ChatMessageData)
|
||||
newData.state.value == ChatMessage.State.Displayed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,86 +0,0 @@
|
|||
package org.linphone.ui.main.conversations.adapter
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.databinding.DataBindingUtil
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.linphone.R
|
||||
import org.linphone.databinding.ChatRoomListCellBinding
|
||||
import org.linphone.ui.main.conversations.data.ChatRoomData
|
||||
import org.linphone.ui.main.conversations.data.ChatRoomDataListener
|
||||
import org.linphone.utils.Event
|
||||
|
||||
class ConversationsListAdapter(
|
||||
private val viewLifecycleOwner: LifecycleOwner
|
||||
) : ListAdapter<ChatRoomData, RecyclerView.ViewHolder>(ConversationDiffCallback()) {
|
||||
val chatRoomClickedEvent: MutableLiveData<Event<ChatRoomData>> by lazy {
|
||||
MutableLiveData<Event<ChatRoomData>>()
|
||||
}
|
||||
|
||||
val chatRoomLongClickedEvent: MutableLiveData<Event<ChatRoomData>> by lazy {
|
||||
MutableLiveData<Event<ChatRoomData>>()
|
||||
}
|
||||
|
||||
var selectedAdapterPosition = -1
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||
val binding: ChatRoomListCellBinding = DataBindingUtil.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
R.layout.chat_room_list_cell,
|
||||
parent,
|
||||
false
|
||||
)
|
||||
return ViewHolder(binding)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||
(holder as ViewHolder).bind(getItem(position))
|
||||
}
|
||||
|
||||
fun resetSelection() {
|
||||
notifyItemChanged(selectedAdapterPosition)
|
||||
selectedAdapterPosition = -1
|
||||
}
|
||||
|
||||
inner class ViewHolder(
|
||||
val binding: ChatRoomListCellBinding
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
fun bind(chatRoomData: ChatRoomData) {
|
||||
with(binding) {
|
||||
data = chatRoomData
|
||||
|
||||
lifecycleOwner = viewLifecycleOwner
|
||||
|
||||
binding.root.isSelected = bindingAdapterPosition == selectedAdapterPosition
|
||||
|
||||
chatRoomData.chatRoomDataListener = object : ChatRoomDataListener() {
|
||||
override fun onClicked() {
|
||||
chatRoomClickedEvent.value = Event(chatRoomData)
|
||||
}
|
||||
|
||||
override fun onLongClicked() {
|
||||
selectedAdapterPosition = bindingAdapterPosition
|
||||
binding.root.isSelected = true
|
||||
chatRoomLongClickedEvent.value = Event(chatRoomData)
|
||||
}
|
||||
}
|
||||
|
||||
executePendingBindings()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ConversationDiffCallback : DiffUtil.ItemCallback<ChatRoomData>() {
|
||||
override fun areItemsTheSame(oldItem: ChatRoomData, newItem: ChatRoomData): Boolean {
|
||||
return oldItem.id == newItem.id
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: ChatRoomData, newItem: ChatRoomData): Boolean {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
|
@ -1,89 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2023 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.ui.main.conversations.data
|
||||
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import org.linphone.R
|
||||
import org.linphone.contacts.ContactData
|
||||
import org.linphone.core.ChatMessage
|
||||
import org.linphone.core.ChatMessageListenerStub
|
||||
import org.linphone.utils.TimestampUtils
|
||||
|
||||
class ChatMessageData(private val chatMessage: ChatMessage) {
|
||||
val id = chatMessage.messageId
|
||||
|
||||
val isOutgoing = chatMessage.isOutgoing
|
||||
|
||||
val contactData = MutableLiveData<ContactData>()
|
||||
|
||||
val state = MutableLiveData<ChatMessage.State>()
|
||||
|
||||
val text = MutableLiveData<String>()
|
||||
|
||||
val time = MutableLiveData<String>()
|
||||
|
||||
val imdnIcon = MutableLiveData<Int>()
|
||||
|
||||
private val chatMessageListener = object : ChatMessageListenerStub() {
|
||||
override fun onMsgStateChanged(message: ChatMessage, state: ChatMessage.State) {
|
||||
this@ChatMessageData.state.postValue(state)
|
||||
computeImdnIcon()
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
state.postValue(chatMessage.state)
|
||||
chatMessage.addListener(chatMessageListener)
|
||||
|
||||
computeImdnIcon()
|
||||
time.postValue(TimestampUtils.toString(chatMessage.time))
|
||||
for (content in chatMessage.contents) {
|
||||
if (content.isText) {
|
||||
text.postValue(content.utf8Text)
|
||||
}
|
||||
// TODO FIXME
|
||||
}
|
||||
contactLookup()
|
||||
}
|
||||
|
||||
fun destroy() {
|
||||
chatMessage.removeListener(chatMessageListener)
|
||||
}
|
||||
|
||||
fun contactLookup() {
|
||||
val remoteAddress = chatMessage.fromAddress
|
||||
val friend = chatMessage.chatRoom.core.findFriend(remoteAddress)
|
||||
if (friend != null) {
|
||||
contactData.postValue(ContactData(friend))
|
||||
}
|
||||
}
|
||||
|
||||
private fun computeImdnIcon() {
|
||||
imdnIcon.postValue(
|
||||
when (chatMessage.state) {
|
||||
ChatMessage.State.DeliveredToUser -> R.drawable.imdn_delivered
|
||||
ChatMessage.State.Displayed -> R.drawable.imdn_read
|
||||
ChatMessage.State.InProgress -> R.drawable.imdn_sent
|
||||
// TODO FIXME
|
||||
else -> R.drawable.imdn_sent
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,248 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2023 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.ui.main.conversations.data
|
||||
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import java.lang.StringBuilder
|
||||
import org.linphone.LinphoneApplication.Companion.coreContext
|
||||
import org.linphone.R
|
||||
import org.linphone.contacts.ContactData
|
||||
import org.linphone.core.*
|
||||
import org.linphone.core.tools.Log
|
||||
import org.linphone.utils.LinphoneUtils
|
||||
import org.linphone.utils.TimestampUtils
|
||||
|
||||
class ChatRoomData(val chatRoom: ChatRoom) {
|
||||
val id = LinphoneUtils.getChatRoomId(chatRoom)
|
||||
|
||||
val localSipUri = chatRoom.localAddress.asString()
|
||||
val remoteSipUri = chatRoom.peerAddress.asString()
|
||||
|
||||
val isOneToOne = chatRoom.hasCapability(ChatRoom.Capabilities.OneToOne.toInt())
|
||||
|
||||
val contactName = MutableLiveData<String>()
|
||||
|
||||
val subject = MutableLiveData<String>()
|
||||
|
||||
val lastMessage = MutableLiveData<String>()
|
||||
|
||||
val unreadChatCount = MutableLiveData<Int>()
|
||||
|
||||
val isComposing = MutableLiveData<Boolean>()
|
||||
|
||||
val isSecure = MutableLiveData<Boolean>()
|
||||
|
||||
val isSecureVerified = MutableLiveData<Boolean>()
|
||||
|
||||
val isEphemeral = MutableLiveData<Boolean>()
|
||||
|
||||
val isMuted = MutableLiveData<Boolean>()
|
||||
|
||||
val lastUpdate = MutableLiveData<String>()
|
||||
|
||||
val showLastMessageImdnIcon = MutableLiveData<Boolean>()
|
||||
|
||||
val lastMessageImdnIcon = MutableLiveData<Int>()
|
||||
|
||||
val contactData = MutableLiveData<ContactData>()
|
||||
|
||||
var chatRoomDataListener: ChatRoomDataListener? = null
|
||||
|
||||
private val coreListener = object : CoreListenerStub() {
|
||||
override fun onChatRoomRead(core: Core, chatRoom: ChatRoom) {
|
||||
if (chatRoom == this@ChatRoomData.chatRoom) {
|
||||
unreadChatCount.postValue(chatRoom.unreadMessagesCount)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val chatRoomListener = object : ChatRoomListenerStub() {
|
||||
override fun onIsComposingReceived(
|
||||
chatRoom: ChatRoom,
|
||||
remoteAddress: Address,
|
||||
composing: Boolean
|
||||
) {
|
||||
isComposing.postValue(composing)
|
||||
}
|
||||
|
||||
override fun onMessagesReceived(chatRoom: ChatRoom, chatMessages: Array<out ChatMessage>) {
|
||||
unreadChatCount.postValue(chatRoom.unreadMessagesCount)
|
||||
computeLastMessage()
|
||||
}
|
||||
|
||||
override fun onChatMessageSent(chatRoom: ChatRoom, eventLog: EventLog) {
|
||||
computeLastMessage()
|
||||
}
|
||||
|
||||
override fun onEphemeralMessageDeleted(chatRoom: ChatRoom, eventLog: EventLog) {
|
||||
computeLastMessage()
|
||||
}
|
||||
|
||||
override fun onSubjectChanged(chatRoom: ChatRoom, eventLog: EventLog) {
|
||||
subject.postValue(
|
||||
chatRoom.subject ?: LinphoneUtils.getDisplayName(chatRoom.peerAddress)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
chatRoom.addListener(chatRoomListener)
|
||||
coreContext.core.addListener(coreListener)
|
||||
|
||||
lastMessageImdnIcon.postValue(R.drawable.imdn_sent)
|
||||
showLastMessageImdnIcon.postValue(false)
|
||||
|
||||
contactLookup()
|
||||
subject.postValue(
|
||||
chatRoom.subject ?: LinphoneUtils.getDisplayName(chatRoom.peerAddress)
|
||||
)
|
||||
computeLastMessage()
|
||||
|
||||
unreadChatCount.postValue(chatRoom.unreadMessagesCount)
|
||||
isComposing.postValue(chatRoom.isRemoteComposing)
|
||||
isSecure.postValue(chatRoom.securityLevel == ChatRoom.SecurityLevel.Encrypted)
|
||||
isSecureVerified.postValue(chatRoom.securityLevel == ChatRoom.SecurityLevel.Safe)
|
||||
isEphemeral.postValue(chatRoom.isEphemeralEnabled)
|
||||
isMuted.postValue(chatRoom.muted)
|
||||
}
|
||||
|
||||
fun onCleared() {
|
||||
coreContext.postOnCoreThread { core ->
|
||||
chatRoom.removeListener(chatRoomListener)
|
||||
core.removeListener(coreListener)
|
||||
}
|
||||
}
|
||||
|
||||
fun onClicked() {
|
||||
chatRoomDataListener?.onClicked()
|
||||
}
|
||||
|
||||
fun onLongClicked(): Boolean {
|
||||
chatRoomDataListener?.onLongClicked()
|
||||
return true
|
||||
}
|
||||
|
||||
fun contactLookup() {
|
||||
if (chatRoom.hasCapability(ChatRoom.Capabilities.Basic.toInt())) {
|
||||
val remoteAddress = chatRoom.peerAddress
|
||||
val friend = chatRoom.core.findFriend(remoteAddress)
|
||||
if (friend != null) {
|
||||
contactData.postValue(ContactData(friend))
|
||||
}
|
||||
contactName.postValue(friend?.name ?: LinphoneUtils.getDisplayName(remoteAddress))
|
||||
} else {
|
||||
if (chatRoom.hasCapability(ChatRoom.Capabilities.OneToOne.toInt())) {
|
||||
val first = chatRoom.participants.firstOrNull()
|
||||
if (first != null) {
|
||||
val remoteAddress = first.address
|
||||
val friend = chatRoom.core.findFriend(remoteAddress)
|
||||
if (friend != null) {
|
||||
contactData.postValue(ContactData(friend))
|
||||
}
|
||||
contactName.postValue(
|
||||
friend?.name ?: LinphoneUtils.getDisplayName(remoteAddress)
|
||||
)
|
||||
} else {
|
||||
Log.e("[Chat Room Data] No participant in the chat room!")
|
||||
}
|
||||
}
|
||||
}
|
||||
computeLastMessage()
|
||||
}
|
||||
|
||||
private fun computeLastMessageImdnIcon(message: ChatMessage) {
|
||||
val state = message.state
|
||||
showLastMessageImdnIcon.postValue(
|
||||
if (message.isOutgoing) {
|
||||
when (state) {
|
||||
ChatMessage.State.DeliveredToUser, ChatMessage.State.Displayed,
|
||||
ChatMessage.State.NotDelivered, ChatMessage.State.FileTransferError -> true
|
||||
else -> false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
)
|
||||
|
||||
lastMessageImdnIcon.postValue(
|
||||
when (state) {
|
||||
ChatMessage.State.DeliveredToUser -> R.drawable.imdn_delivered
|
||||
ChatMessage.State.Displayed -> R.drawable.imdn_read
|
||||
ChatMessage.State.InProgress -> R.drawable.imdn_sent
|
||||
// TODO FIXME
|
||||
else -> R.drawable.imdn_sent
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun computeLastMessage() {
|
||||
val lastUpdateTime = chatRoom.lastUpdateTime
|
||||
lastUpdate.postValue(TimestampUtils.toString(lastUpdateTime, true))
|
||||
|
||||
val builder = StringBuilder()
|
||||
|
||||
val message = chatRoom.lastMessageInHistory
|
||||
if (message != null) {
|
||||
val senderAddress = message.fromAddress.clone()
|
||||
senderAddress.clean()
|
||||
|
||||
if (message.isOutgoing && message.state != ChatMessage.State.Displayed) {
|
||||
message.addListener(object : ChatMessageListenerStub() {
|
||||
override fun onMsgStateChanged(message: ChatMessage, state: ChatMessage.State) {
|
||||
computeLastMessageImdnIcon(message)
|
||||
|
||||
if (state == ChatMessage.State.Displayed) {
|
||||
message.removeListener(this)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
computeLastMessageImdnIcon(message)
|
||||
|
||||
if (!isOneToOne) {
|
||||
val sender = chatRoom.core.findFriend(senderAddress)
|
||||
builder.append(sender?.name ?: LinphoneUtils.getDisplayName(senderAddress))
|
||||
builder.append(": ")
|
||||
}
|
||||
|
||||
for (content in message.contents) {
|
||||
if (content.isFile || content.isFileTransfer) {
|
||||
builder.append(content.name + " ")
|
||||
} else if (content.isText) {
|
||||
builder.append(content.utf8Text + " ")
|
||||
}
|
||||
}
|
||||
builder.trim()
|
||||
}
|
||||
|
||||
val text = builder.toString()
|
||||
if (text.length > 128) { // This brings a huge performance improvement when scrolling
|
||||
lastMessage.postValue(text.substring(0, 128))
|
||||
} else {
|
||||
lastMessage.postValue(text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract class ChatRoomDataListener {
|
||||
abstract fun onClicked()
|
||||
|
||||
abstract fun onLongClicked()
|
||||
}
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2023 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.ui.main.conversations.data
|
||||
|
||||
import org.linphone.core.EventLog
|
||||
|
||||
class EventData(val eventLog: EventLog) {
|
||||
fun destroy() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
fun contactLookup() {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
/*
|
||||
* 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.ui.main.conversations.data
|
||||
|
||||
import org.linphone.core.EventLog
|
||||
|
||||
class EventLogData(val eventLog: EventLog) {
|
||||
val type: EventLog.Type = eventLog.type
|
||||
|
||||
val isEvent = type != EventLog.Type.ConferenceChatMessage
|
||||
|
||||
val data = if (isEvent) {
|
||||
EventData(eventLog)
|
||||
} else {
|
||||
ChatMessageData(eventLog.chatMessage!!)
|
||||
}
|
||||
|
||||
val notifyId = eventLog.notifyId
|
||||
|
||||
fun destroy() {
|
||||
when (data) {
|
||||
is EventData -> data.destroy()
|
||||
is ChatMessageData -> data.destroy()
|
||||
}
|
||||
}
|
||||
|
||||
fun contactLookup() {
|
||||
when (data) {
|
||||
is EventData -> data.contactLookup()
|
||||
is ChatMessageData -> data.contactLookup()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,77 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 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.ui.main.conversations.view
|
||||
|
||||
import android.content.Context
|
||||
import android.text.Layout
|
||||
import android.text.method.LinkMovementMethod
|
||||
import android.util.AttributeSet
|
||||
import androidx.appcompat.widget.AppCompatTextView
|
||||
import kotlin.math.ceil
|
||||
import kotlin.math.max
|
||||
import kotlin.math.round
|
||||
|
||||
/**
|
||||
* The purpose of this class is to have a TextView declared with wrap_content as width that won't
|
||||
* fill it's parent if it is multi line.
|
||||
*/
|
||||
class MultiLineWrapContentWidthTextView : AppCompatTextView {
|
||||
constructor(context: Context) : super(context)
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
|
||||
|
||||
constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet?,
|
||||
defStyleAttr: Int
|
||||
) : super(context, attrs, defStyleAttr)
|
||||
|
||||
override fun setText(text: CharSequence?, type: BufferType?) {
|
||||
super.setText(text, type)
|
||||
// Required for PatternClickableSpan
|
||||
movementMethod = LinkMovementMethod.getInstance()
|
||||
}
|
||||
|
||||
override fun onMeasure(widthSpec: Int, heightSpec: Int) {
|
||||
super.onMeasure(widthSpec, heightSpec)
|
||||
|
||||
if (layout != null && layout.lineCount >= 2) {
|
||||
val maxLineWidth = ceil(getMaxLineWidth(layout)).toInt() - paddingStart - paddingEnd
|
||||
if (maxLineWidth < measuredWidth) {
|
||||
super.onMeasure(
|
||||
MeasureSpec.makeMeasureSpec(
|
||||
maxLineWidth,
|
||||
MeasureSpec.getMode(widthSpec)
|
||||
),
|
||||
heightSpec
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getMaxLineWidth(layout: Layout): Float {
|
||||
var maxWidth = 0.0f
|
||||
val lines = layout.lineCount
|
||||
for (i in 0 until lines) {
|
||||
maxWidth = max(maxWidth, layout.getLineWidth(i))
|
||||
}
|
||||
return round(maxWidth)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,194 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2023 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.ui.main.conversations.viewmodel
|
||||
|
||||
import androidx.annotation.WorkerThread
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import org.linphone.LinphoneApplication.Companion.coreContext
|
||||
import org.linphone.contacts.ContactData
|
||||
import org.linphone.contacts.ContactsManager.ContactsListener
|
||||
import org.linphone.core.Address
|
||||
import org.linphone.core.ChatRoom
|
||||
import org.linphone.core.ChatRoomListenerStub
|
||||
import org.linphone.core.EventLog
|
||||
import org.linphone.core.Factory
|
||||
import org.linphone.core.tools.Log
|
||||
import org.linphone.ui.main.conversations.data.EventLogData
|
||||
import org.linphone.utils.LinphoneUtils
|
||||
|
||||
class ConversationViewModel @WorkerThread constructor() : ViewModel() {
|
||||
private lateinit var chatRoom: ChatRoom
|
||||
|
||||
val events = MutableLiveData<ArrayList<EventLogData>>()
|
||||
|
||||
val contactName = MutableLiveData<String>()
|
||||
|
||||
val contactData = MutableLiveData<ContactData>()
|
||||
|
||||
val subject = MutableLiveData<String>()
|
||||
|
||||
val isComposing = MutableLiveData<Boolean>()
|
||||
|
||||
val isOneToOne = MutableLiveData<Boolean>()
|
||||
|
||||
private val contactsListener = object : ContactsListener {
|
||||
@WorkerThread
|
||||
override fun onContactsLoaded() {
|
||||
contactLookup()
|
||||
events.value.orEmpty().forEach(EventLogData::contactLookup)
|
||||
}
|
||||
}
|
||||
|
||||
private val chatRoomListener = object : ChatRoomListenerStub() {
|
||||
override fun onIsComposingReceived(
|
||||
chatRoom: ChatRoom,
|
||||
remoteAddress: Address,
|
||||
composing: Boolean
|
||||
) {
|
||||
isComposing.postValue(composing)
|
||||
}
|
||||
|
||||
override fun onChatMessagesReceived(chatRoom: ChatRoom, eventLogs: Array<out EventLog>) {
|
||||
for (eventLog in eventLogs) {
|
||||
addChatMessageEventLog(eventLog)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onChatMessageSending(chatRoom: ChatRoom, eventLog: EventLog) {
|
||||
val position = events.value.orEmpty().size
|
||||
|
||||
if (eventLog.type == EventLog.Type.ConferenceChatMessage) {
|
||||
val chatMessage = eventLog.chatMessage
|
||||
chatMessage ?: return
|
||||
chatMessage.userData = position
|
||||
}
|
||||
|
||||
addChatMessageEventLog(eventLog)
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
coreContext.contactsManager.addListener(contactsListener)
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
coreContext.postOnCoreThread {
|
||||
coreContext.contactsManager.removeListener(contactsListener)
|
||||
if (::chatRoom.isInitialized) {
|
||||
chatRoom.removeListener(chatRoomListener)
|
||||
}
|
||||
events.value.orEmpty().forEach(EventLogData::destroy)
|
||||
}
|
||||
}
|
||||
|
||||
fun loadChatRoom(localSipUri: String, remoteSipUri: String) {
|
||||
coreContext.postOnCoreThread { core ->
|
||||
val localAddress = Factory.instance().createAddress(localSipUri)
|
||||
val remoteSipAddress = Factory.instance().createAddress(remoteSipUri)
|
||||
|
||||
val found = core.searchChatRoom(
|
||||
null,
|
||||
localAddress,
|
||||
remoteSipAddress,
|
||||
arrayOfNulls(
|
||||
0
|
||||
)
|
||||
)
|
||||
if (found != null) {
|
||||
chatRoom = found
|
||||
chatRoom.addListener(chatRoomListener)
|
||||
|
||||
isOneToOne.postValue(chatRoom.hasCapability(ChatRoom.Capabilities.OneToOne.toInt()))
|
||||
subject.postValue(chatRoom.subject)
|
||||
isComposing.postValue(chatRoom.isRemoteComposing)
|
||||
contactLookup()
|
||||
|
||||
val list = arrayListOf<EventLogData>()
|
||||
list.addAll(events.value.orEmpty())
|
||||
|
||||
for (eventLog in chatRoom.getHistoryEvents(0)) {
|
||||
list.add(EventLogData(eventLog))
|
||||
}
|
||||
|
||||
events.postValue(list)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun contactLookup() {
|
||||
if (chatRoom.hasCapability(ChatRoom.Capabilities.Basic.toInt())) {
|
||||
val remoteAddress = chatRoom.peerAddress
|
||||
val friend = chatRoom.core.findFriend(remoteAddress)
|
||||
if (friend != null) {
|
||||
contactData.postValue(ContactData(friend))
|
||||
}
|
||||
contactName.postValue(friend?.name ?: LinphoneUtils.getDisplayName(remoteAddress))
|
||||
} else {
|
||||
if (chatRoom.hasCapability(ChatRoom.Capabilities.OneToOne.toInt())) {
|
||||
val first = chatRoom.participants.firstOrNull()
|
||||
if (first != null) {
|
||||
val remoteAddress = first.address
|
||||
val friend = chatRoom.core.findFriend(remoteAddress)
|
||||
if (friend != null) {
|
||||
contactData.postValue(ContactData(friend))
|
||||
}
|
||||
contactName.postValue(
|
||||
friend?.name ?: LinphoneUtils.getDisplayName(remoteAddress)
|
||||
)
|
||||
} else {
|
||||
Log.e("[Conversation View Model] No participant in the chat room!")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun addEvent(eventLog: EventLog) {
|
||||
val list = arrayListOf<EventLogData>()
|
||||
list.addAll(events.value.orEmpty())
|
||||
|
||||
val found = list.find { data -> data.eventLog == eventLog }
|
||||
if (found == null) {
|
||||
list.add(EventLogData(eventLog))
|
||||
}
|
||||
|
||||
events.postValue(list)
|
||||
}
|
||||
|
||||
private fun addChatMessageEventLog(eventLog: EventLog) {
|
||||
if (eventLog.type == EventLog.Type.ConferenceChatMessage) {
|
||||
val chatMessage = eventLog.chatMessage
|
||||
chatMessage ?: return
|
||||
chatMessage.userData = events.value.orEmpty().size
|
||||
|
||||
val existingEvent = events.value.orEmpty().find { data ->
|
||||
data.eventLog.type == EventLog.Type.ConferenceChatMessage && data.eventLog.chatMessage?.messageId == chatMessage.messageId
|
||||
}
|
||||
if (existingEvent != null) {
|
||||
Log.w(
|
||||
"[Chat Messages] Found already present chat message, don't add it it's probably the result of an auto download or an aggregated message received before but notified after the conversation was displayed"
|
||||
)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
addEvent(eventLog)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,191 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2023 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.ui.main.conversations.viewmodel
|
||||
|
||||
import androidx.annotation.WorkerThread
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import java.util.ArrayList
|
||||
import org.linphone.LinphoneApplication.Companion.coreContext
|
||||
import org.linphone.contacts.ContactsManager.ContactsListener
|
||||
import org.linphone.core.ChatMessage
|
||||
import org.linphone.core.ChatRoom
|
||||
import org.linphone.core.Core
|
||||
import org.linphone.core.CoreListenerStub
|
||||
import org.linphone.core.tools.Log
|
||||
import org.linphone.ui.main.conversations.data.ChatRoomData
|
||||
import org.linphone.utils.Event
|
||||
import org.linphone.utils.LinphoneUtils
|
||||
|
||||
class ConversationsListViewModel : ViewModel() {
|
||||
val chatRoomsList = MutableLiveData<ArrayList<ChatRoomData>>()
|
||||
|
||||
val notifyItemChangedEvent = MutableLiveData<Event<Int>>()
|
||||
|
||||
private val contactsListener = object : ContactsListener {
|
||||
@WorkerThread
|
||||
override fun onContactsLoaded() {
|
||||
for (chatRoomData in chatRoomsList.value.orEmpty()) {
|
||||
chatRoomData.contactLookup()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val coreListener = object : CoreListenerStub() {
|
||||
override fun onChatRoomStateChanged(
|
||||
core: Core,
|
||||
chatRoom: ChatRoom,
|
||||
state: ChatRoom.State?
|
||||
) {
|
||||
Log.i(
|
||||
"[Conversations List] Chat room [${LinphoneUtils.getChatRoomId(chatRoom)}] state changed [$state]"
|
||||
)
|
||||
when (state) {
|
||||
ChatRoom.State.Created -> {
|
||||
addChatRoomToList(chatRoom)
|
||||
}
|
||||
ChatRoom.State.Deleted -> {
|
||||
removeChatRoomFromList(chatRoom)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onMessageSent(core: Core, chatRoom: ChatRoom, message: ChatMessage) {
|
||||
onChatRoomMessageEvent(chatRoom)
|
||||
}
|
||||
|
||||
override fun onMessagesReceived(
|
||||
core: Core,
|
||||
chatRoom: ChatRoom,
|
||||
messages: Array<out ChatMessage>
|
||||
) {
|
||||
onChatRoomMessageEvent(chatRoom)
|
||||
}
|
||||
|
||||
override fun onChatRoomRead(core: Core, chatRoom: ChatRoom) {
|
||||
notifyChatRoomUpdate(chatRoom)
|
||||
}
|
||||
|
||||
override fun onChatRoomEphemeralMessageDeleted(core: Core, chatRoom: ChatRoom) {
|
||||
notifyChatRoomUpdate(chatRoom)
|
||||
}
|
||||
|
||||
override fun onChatRoomSubjectChanged(core: Core, chatRoom: ChatRoom) {
|
||||
notifyChatRoomUpdate(chatRoom)
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
coreContext.postOnCoreThread { core ->
|
||||
core.addListener(coreListener)
|
||||
coreContext.contactsManager.addListener(contactsListener)
|
||||
}
|
||||
|
||||
coreContext.postOnCoreThread { core ->
|
||||
updateChatRoomsList()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
coreContext.postOnCoreThread { core ->
|
||||
coreContext.contactsManager.removeListener(contactsListener)
|
||||
core.removeListener(coreListener)
|
||||
}
|
||||
super.onCleared()
|
||||
}
|
||||
|
||||
private fun addChatRoomToList(chatRoom: ChatRoom) {
|
||||
val index = findChatRoomIndex(chatRoom)
|
||||
if (index != -1) {
|
||||
Log.w("[Conversations List] Chat room already exists in list, do not add it again")
|
||||
return
|
||||
}
|
||||
|
||||
val list = arrayListOf<ChatRoomData>()
|
||||
|
||||
val data = ChatRoomData(chatRoom)
|
||||
list.add(data)
|
||||
list.addAll(chatRoomsList.value.orEmpty())
|
||||
list.sortByDescending { data -> data.chatRoom.lastUpdateTime }
|
||||
|
||||
chatRoomsList.postValue(list)
|
||||
}
|
||||
|
||||
private fun removeChatRoomFromList(chatRoom: ChatRoom) {
|
||||
val list = arrayListOf<ChatRoomData>()
|
||||
|
||||
for (data in chatRoomsList.value.orEmpty()) {
|
||||
if (LinphoneUtils.getChatRoomId(chatRoom) != LinphoneUtils.getChatRoomId(
|
||||
data.chatRoom
|
||||
)
|
||||
) {
|
||||
list.add(data)
|
||||
}
|
||||
}
|
||||
|
||||
chatRoomsList.postValue(list)
|
||||
}
|
||||
|
||||
private fun findChatRoomIndex(chatRoom: ChatRoom): Int {
|
||||
val id = LinphoneUtils.getChatRoomId(chatRoom)
|
||||
for ((index, data) in chatRoomsList.value.orEmpty().withIndex()) {
|
||||
if (id == data.id) {
|
||||
return index
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
private fun notifyChatRoomUpdate(chatRoom: ChatRoom) {
|
||||
when (val index = findChatRoomIndex(chatRoom)) {
|
||||
-1 -> updateChatRoomsList()
|
||||
else -> notifyItemChangedEvent.postValue(Event(index))
|
||||
}
|
||||
}
|
||||
|
||||
private fun onChatRoomMessageEvent(chatRoom: ChatRoom) {
|
||||
when (findChatRoomIndex(chatRoom)) {
|
||||
-1 -> updateChatRoomsList()
|
||||
0 -> notifyItemChangedEvent.postValue(Event(0))
|
||||
else -> reorderChatRoomsList()
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateChatRoomsList() {
|
||||
Log.i("[Conversations List] Updating chat rooms list")
|
||||
chatRoomsList.value.orEmpty().forEach(ChatRoomData::onCleared)
|
||||
|
||||
val list = arrayListOf<ChatRoomData>()
|
||||
val chatRooms = coreContext.core.chatRooms
|
||||
for (chatRoom in chatRooms) {
|
||||
list.add(ChatRoomData(chatRoom))
|
||||
}
|
||||
chatRoomsList.postValue(list)
|
||||
}
|
||||
|
||||
private fun reorderChatRoomsList() {
|
||||
Log.i("[Conversations List] Re-ordering chat rooms list")
|
||||
val list = arrayListOf<ChatRoomData>()
|
||||
list.addAll(chatRoomsList.value.orEmpty())
|
||||
list.sortByDescending { data -> data.chatRoom.lastUpdateTime }
|
||||
chatRoomsList.postValue(list)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,124 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2023 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.ui.main.conversations.viewmodel
|
||||
|
||||
import androidx.annotation.WorkerThread
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import org.linphone.LinphoneApplication.Companion.coreContext
|
||||
import org.linphone.contacts.ContactData
|
||||
import org.linphone.contacts.ContactsManager.ContactsListener
|
||||
import org.linphone.core.MagicSearch
|
||||
import org.linphone.core.MagicSearchListenerStub
|
||||
import org.linphone.core.SearchResult
|
||||
import org.linphone.core.tools.Log
|
||||
import org.linphone.utils.Event
|
||||
|
||||
class NewConversationViewModel : ViewModel() {
|
||||
val contactsList = MutableLiveData<ArrayList<ContactData>>()
|
||||
|
||||
val groupEnabled = MutableLiveData<Boolean>()
|
||||
|
||||
val goToChatRoom: MutableLiveData<Event<Pair<String, String>>> by lazy {
|
||||
MutableLiveData<Event<Pair<String, String>>>()
|
||||
}
|
||||
|
||||
val filter = MutableLiveData<String>()
|
||||
private var previousFilter = "NotSet"
|
||||
|
||||
private val magicSearch: MagicSearch by lazy {
|
||||
val magicSearch = coreContext.core.createMagicSearch()
|
||||
magicSearch.limitedSearch = false
|
||||
magicSearch
|
||||
}
|
||||
|
||||
private val magicSearchListener = object : MagicSearchListenerStub() {
|
||||
override fun onSearchResultsReceived(magicSearch: MagicSearch) {
|
||||
processMagicSearchResults(magicSearch.lastSearch)
|
||||
}
|
||||
}
|
||||
|
||||
private val contactsListener = object : ContactsListener {
|
||||
@WorkerThread
|
||||
override fun onContactsLoaded() {
|
||||
applyFilter(filter.value.orEmpty().trim())
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
coreContext.postOnCoreThread {
|
||||
magicSearch.addListener(magicSearchListener)
|
||||
coreContext.contactsManager.addListener(contactsListener)
|
||||
|
||||
applyFilter("")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
coreContext.postOnCoreThread {
|
||||
coreContext.contactsManager.removeListener(contactsListener)
|
||||
magicSearch.removeListener(magicSearchListener)
|
||||
}
|
||||
super.onCleared()
|
||||
}
|
||||
|
||||
fun applyFilter(filterValue: String) {
|
||||
Log.i("[New Conversation ViewModel] Filtering contacts using [$filterValue]")
|
||||
if (previousFilter.isNotEmpty() && (
|
||||
previousFilter.length > filterValue.length ||
|
||||
(previousFilter.length == filterValue.length && previousFilter != filterValue)
|
||||
)
|
||||
) {
|
||||
magicSearch.resetSearchCache()
|
||||
}
|
||||
previousFilter = filterValue
|
||||
|
||||
magicSearch.getContactsListAsync(
|
||||
filterValue,
|
||||
"",
|
||||
MagicSearch.Source.Friends.toInt(),
|
||||
MagicSearch.Aggregation.Friend
|
||||
)
|
||||
}
|
||||
|
||||
fun createGroup() {
|
||||
goToChatRoom.value = Event(Pair("", ""))
|
||||
}
|
||||
|
||||
fun enableGroupSelection() {
|
||||
groupEnabled.value = true
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private fun processMagicSearchResults(results: Array<SearchResult>) {
|
||||
Log.i("[New Conversation ViewModel] [${results.size}] matching results")
|
||||
contactsList.value.orEmpty().forEach(ContactData::onDestroy)
|
||||
|
||||
val list = arrayListOf<ContactData>()
|
||||
for (searchResult in results) {
|
||||
val friend = searchResult.friend
|
||||
if (friend != null) {
|
||||
val data = ContactData(friend)
|
||||
list.add(data)
|
||||
}
|
||||
}
|
||||
contactsList.postValue(list)
|
||||
}
|
||||
}
|
||||
|
|
@ -83,9 +83,7 @@ class BottomNavBarFragment : Fragment() {
|
|||
}
|
||||
|
||||
binding.setOnConversationsClicked {
|
||||
if (sharedViewModel.currentlyDisplayedFragment.value != R.id.conversationsFragment) {
|
||||
sharedViewModel.navigateToConversationsEvent.value = Event(true)
|
||||
}
|
||||
// TODO
|
||||
}
|
||||
|
||||
binding.setOnMeetingsClicked {
|
||||
|
|
@ -95,7 +93,6 @@ class BottomNavBarFragment : Fragment() {
|
|||
sharedViewModel.currentlyDisplayedFragment.observe(viewLifecycleOwner) {
|
||||
viewModel.contactsSelected.value = it == R.id.contactsFragment
|
||||
viewModel.callsSelected.value = it == R.id.callsFragment
|
||||
viewModel.conversationsSelected.value = it == R.id.conversationsFragment
|
||||
}
|
||||
|
||||
sharedViewModel.resetMissedCallsCountEvent.observe(viewLifecycleOwner) {
|
||||
|
|
|
|||
|
|
@ -52,7 +52,6 @@ import io.getstream.avatarview.AvatarView
|
|||
import io.getstream.avatarview.coil.loadImage
|
||||
import org.linphone.BR
|
||||
import org.linphone.R
|
||||
import org.linphone.contacts.ContactData
|
||||
import org.linphone.core.ConsolidatedPresence
|
||||
import org.linphone.core.tools.Log
|
||||
import org.linphone.ui.main.MainActivity
|
||||
|
|
@ -182,20 +181,6 @@ fun loadPictureWithCoil(imageView: ImageView, file: String?) {
|
|||
}
|
||||
}
|
||||
|
||||
@UiThread
|
||||
@BindingAdapter("coilContact")
|
||||
fun loadContactPictureWithCoil2(imageView: ImageView, contact: ContactData?) {
|
||||
Log.i("[Data Binding Utils] Loading contact picture [${contact?.avatar}] with coil")
|
||||
if (contact == null) {
|
||||
imageView.load(R.drawable.contact_avatar)
|
||||
} else {
|
||||
imageView.load(contact.avatar) {
|
||||
transformations(CircleCropTransformation())
|
||||
error(R.drawable.contact_avatar)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@UiThread
|
||||
@BindingAdapter("presenceIcon")
|
||||
fun ImageView.setPresenceIcon(presence: ConsolidatedPresence?) {
|
||||
|
|
|
|||
|
|
@ -173,7 +173,8 @@ class TimestampUtils {
|
|||
}
|
||||
|
||||
val millis = if (timestampInSecs) timestamp * 1000 else timestamp
|
||||
return dateFormat.format(Date(millis)).capitalize(Locale.getDefault())
|
||||
return dateFormat.format(Date(millis))
|
||||
.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() }
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
|
|
|
|||
|
|
@ -1,77 +0,0 @@
|
|||
<?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.ui.main.conversations.data.ChatMessageData" />
|
||||
</data>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="5dp"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_marginEnd="10dp">
|
||||
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:background="@drawable/shape_received_message_bubble_background"
|
||||
app:layout_constraintStart_toStartOf="@id/text"
|
||||
app:layout_constraintTop_toTopOf="@id/text"
|
||||
app:layout_constraintEnd_toEndOf="@id/end_barrier"
|
||||
app:layout_constraintBottom_toBottomOf="@id/timestamp"/>
|
||||
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
android:id="@+id/end_barrier"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
app:constraint_referenced_ids="text, timestamp"
|
||||
app:barrierDirection="right" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/avatar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/icon_size"
|
||||
android:adjustViewBounds="true"
|
||||
android:src="@drawable/contact_avatar"
|
||||
coilContact="@{data.contactData}"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"/>
|
||||
|
||||
<org.linphone.ui.main.conversations.view.MultiLineWrapContentWidthTextView
|
||||
android:id="@+id/text"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@{data.text, default=`Lorem Ipsum`}"
|
||||
android:textSize="14sp"
|
||||
android:textColor="#D9000000"
|
||||
android:paddingTop="5dp"
|
||||
android:paddingEnd="10dp"
|
||||
android:paddingStart="10dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginStart="8dp"
|
||||
app:layout_constraintBottom_toTopOf="@id/timestamp"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/avatar"/>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/timestamp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@{data.time, default=`15h42`}"
|
||||
android:textSize="12sp"
|
||||
android:textColor="#73000000"
|
||||
android:layout_marginStart="18dp"
|
||||
android:paddingBottom="5dp"
|
||||
android:paddingEnd="10dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/avatar" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</layout>
|
||||
|
|
@ -1,77 +0,0 @@
|
|||
<?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.ui.main.conversations.data.ChatMessageData" />
|
||||
</data>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="5dp"
|
||||
android:layout_marginEnd="10dp">
|
||||
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:background="@drawable/shape_sent_message_bubble_background"
|
||||
app:layout_constraintStart_toStartOf="@id/start_barrier"
|
||||
app:layout_constraintTop_toTopOf="@id/text"
|
||||
app:layout_constraintEnd_toEndOf="@id/text"
|
||||
app:layout_constraintBottom_toBottomOf="@id/timestamp"/>
|
||||
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
android:id="@+id/start_barrier"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
app:constraint_referenced_ids="text, imdn_status"
|
||||
app:barrierDirection="left" />
|
||||
|
||||
<org.linphone.ui.main.conversations.view.MultiLineWrapContentWidthTextView
|
||||
android:id="@+id/text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@{data.text, default=`Lorem Ipsum`}"
|
||||
android:textSize="14sp"
|
||||
android:textColor="#D9000000"
|
||||
android:paddingTop="5dp"
|
||||
android:paddingEnd="10dp"
|
||||
android:paddingStart="10dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:gravity="end"
|
||||
app:layout_constraintBottom_toTopOf="@id/timestamp"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/timestamp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@{data.time, default=`15h42`}"
|
||||
android:textSize="12sp"
|
||||
android:textColor="#73000000"
|
||||
android:layout_marginEnd="18dp"
|
||||
android:paddingBottom="5dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imdn_status"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@{data.imdnIcon, default=@drawable/imdn_read}"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:paddingStart="10dp"
|
||||
app:layout_constraintEnd_toStartOf="@id/timestamp"
|
||||
app:layout_constraintBottom_toBottomOf="@id/timestamp"
|
||||
app:layout_constraintTop_toTopOf="@id/timestamp"/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</layout>
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
<?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.ui.main.conversations.data.EventData" />
|
||||
</data>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="5dp"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_marginEnd="10dp">
|
||||
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="1dp"
|
||||
android:background="#EEEEEE"
|
||||
android:layout_marginEnd="20dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/event_text"
|
||||
app:layout_constraintTop_toTopOf="@id/event_text"
|
||||
app:layout_constraintBottom_toBottomOf="@id/event_text"/>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/event_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="#73000000"
|
||||
android:textSize="12sp"
|
||||
android:text="John Doe has joined the chat"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"/>
|
||||
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="1dp"
|
||||
android:background="#EEEEEE"
|
||||
android:layout_marginStart="20dp"
|
||||
app:layout_constraintStart_toEndOf="@id/event_text"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/event_text"
|
||||
app:layout_constraintBottom_toBottomOf="@id/event_text"/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</layout>
|
||||
|
|
@ -1,118 +0,0 @@
|
|||
<?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" />
|
||||
<import type="android.graphics.Typeface" />
|
||||
<variable
|
||||
name="data"
|
||||
type="org.linphone.ui.main.conversations.data.ChatRoomData" />
|
||||
</data>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:onClick="@{() -> data.onClicked()}"
|
||||
android:onLongClick="@{() -> data.onLongClicked()}"
|
||||
android:layout_marginTop="5dp"
|
||||
android:layout_marginBottom="5dp"
|
||||
android:background="@drawable/cell_background">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/avatar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="36dp"
|
||||
android:src="@{data.isOneToOne ? @drawable/contact_avatar : @drawable/group_avatar, default=@drawable/contact_avatar}"
|
||||
coilContact="@{data.contactData}"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_marginTop="5dp"
|
||||
android:layout_marginBottom="5dp"
|
||||
android:adjustViewBounds="true"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:text="@{data.isOneToOne ? data.contactName : data.subject, default=`John Doe`}"
|
||||
android:textColor="#000000"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="@{data.unreadChatCount > 0 ? Typeface.BOLD : Typeface.NORMAL, default=normal}"
|
||||
android:maxLines="1"
|
||||
android:ellipsize="end"
|
||||
app:layout_constraintBottom_toTopOf="@id/subtitle"
|
||||
app:layout_constraintStart_toEndOf="@id/avatar"
|
||||
app:layout_constraintEnd_toStartOf="@id/date_time"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/subtitle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:text="@{data.isComposing ? `... est en train d'écrire` : data.lastMessage, default=`Lorem Ipsum`}"
|
||||
android:textColor="@{data.isComposing ? @color/primary_color : data.unreadChatCount > 0 ? @color/black : @color/gray_4, default=@color/gray_4}"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="@{data.unreadChatCount > 0 ? Typeface.BOLD : Typeface.NORMAL, default=normal}"
|
||||
android:maxLines="1"
|
||||
android:ellipsize="end"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/avatar"
|
||||
app:layout_constraintEnd_toStartOf="@id/end_subtitle_barrier"
|
||||
app:layout_constraintTop_toBottomOf="@id/title" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/date_time"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@{data.lastUpdate, default=`16:45`}"
|
||||
android:textColor="#9AABB5"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginEnd="10dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/title"
|
||||
app:layout_constraintBottom_toBottomOf="@id/title" />
|
||||
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
android:id="@+id/end_subtitle_barrier"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:constraint_referenced_ids="imdn, unread"
|
||||
app:barrierDirection="left" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imdn"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@{data.lastMessageImdnIcon, default=@drawable/imdn_read}"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:visibility="@{data.showLastMessageImdnIcon ? View.VISIBLE : View.GONE}"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/subtitle"
|
||||
app:layout_constraintBottom_toBottomOf="@id/subtitle"/>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/unread"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:background="@drawable/shape_conversation_cell_unread_count_background"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center"
|
||||
android:singleLine="true"
|
||||
android:text="@{String.valueOf(data.unreadChatCount), default=`1`}"
|
||||
android:textSize="10sp"
|
||||
android:textColor="@color/white"
|
||||
android:visibility="@{data.unreadChatCount == 0 ? View.GONE : View.VISIBLE}"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/subtitle"
|
||||
app:layout_constraintBottom_toBottomOf="@id/subtitle" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</layout>
|
||||
|
|
@ -1,135 +0,0 @@
|
|||
<?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="markAsReadClickListener"
|
||||
type="View.OnClickListener" />
|
||||
<variable
|
||||
name="muteClickListener"
|
||||
type="View.OnClickListener" />
|
||||
<variable
|
||||
name="unMuteClickListener"
|
||||
type="View.OnClickListener" />
|
||||
<variable
|
||||
name="callClickListener"
|
||||
type="View.OnClickListener" />
|
||||
<variable
|
||||
name="deleteClickListener"
|
||||
type="View.OnClickListener" />
|
||||
<variable
|
||||
name="isMuted"
|
||||
type="Boolean" />
|
||||
<variable
|
||||
name="isRead"
|
||||
type="Boolean" />
|
||||
</data>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/separator">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/mark_as_read"
|
||||
android:onClick="@{markAsReadClickListener}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Marquer comme lu"
|
||||
android:padding="20dp"
|
||||
android:visibility="@{isRead ? View.GONE : View.VISIBLE}"
|
||||
android:textSize="17sp"
|
||||
android:textColor="@color/gray_1"
|
||||
android:gravity="center"
|
||||
android:background="@color/gray_2"
|
||||
android:layout_marginBottom="1dp"
|
||||
app:layout_constraintBottom_toTopOf="@id/mute"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"/>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/mute"
|
||||
android:onClick="@{muteClickListener}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Mettre en sourdine"
|
||||
android:padding="20dp"
|
||||
android:visibility="@{isMuted ? View.GONE : View.VISIBLE}"
|
||||
android:textSize="17sp"
|
||||
android:textColor="@color/gray_1"
|
||||
android:gravity="center"
|
||||
android:background="@color/gray_2"
|
||||
android:layout_marginBottom="1dp"
|
||||
app:layout_constraintBottom_toTopOf="@id/unmute"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/unmute"
|
||||
android:onClick="@{unMuteClickListener}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Enlever la sourdine"
|
||||
android:padding="20dp"
|
||||
android:visibility="@{isMuted ? View.VISIBLE : View.GONE, default=gone}"
|
||||
android:textSize="17sp"
|
||||
android:textColor="@color/gray_1"
|
||||
android:gravity="center"
|
||||
android:background="@color/gray_2"
|
||||
android:layout_marginBottom="1dp"
|
||||
app:layout_constraintBottom_toTopOf="@id/call"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/call"
|
||||
android:onClick="@{callClickListener}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Appeler"
|
||||
android:padding="20dp"
|
||||
android:textSize="17sp"
|
||||
android:textColor="@color/gray_1"
|
||||
android:gravity="center"
|
||||
android:background="@color/gray_2"
|
||||
android:layout_marginBottom="1dp"
|
||||
app:layout_constraintBottom_toTopOf="@id/delete"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/delete"
|
||||
android:onClick="@{deleteClickListener}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Supprimer la conversation"
|
||||
android:padding="20dp"
|
||||
android:textSize="17sp"
|
||||
android:textColor="@color/red_danger"
|
||||
android:gravity="center"
|
||||
android:background="@color/gray_2"
|
||||
android:layout_marginBottom="1dp"
|
||||
app:layout_constraintBottom_toTopOf="@id/leave"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"/>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/leave"
|
||||
android:onClick="@{deleteClickListener}"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Quitter la conversation"
|
||||
android:padding="20dp"
|
||||
android:textSize="17sp"
|
||||
android:textColor="@color/gray_1"
|
||||
android:gravity="center"
|
||||
android:background="@color/gray_2"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</layout>
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
<?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"/>
|
||||
<import type="org.linphone.core.ConsolidatedPresence"/>
|
||||
<variable
|
||||
name="data"
|
||||
type="org.linphone.contacts.ContactData" />
|
||||
</data>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/avatar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="36dp"
|
||||
android:src="@drawable/contact_avatar"
|
||||
coilContact="@{data}"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_marginTop="5dp"
|
||||
android:layout_marginBottom="5dp"
|
||||
android:adjustViewBounds="true"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
style="@style/default_text_style"
|
||||
android:id="@+id/title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:text="@{data.name, default=`John Doe`}"
|
||||
android:textColor="@color/black"
|
||||
android:textSize="14sp"
|
||||
android:maxLines="1"
|
||||
android:ellipsize="end"
|
||||
app:layout_constraintBottom_toBottomOf="@id/avatar"
|
||||
app:layout_constraintStart_toEndOf="@id/avatar"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</layout>
|
||||
|
|
@ -1,202 +0,0 @@
|
|||
<?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="backClickListener"
|
||||
type="View.OnClickListener" />
|
||||
<variable
|
||||
name="viewModel"
|
||||
type="org.linphone.ui.main.conversations.viewmodel.ConversationViewModel" />
|
||||
</data>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/white">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/back"
|
||||
android:onClick="@{backClickListener}"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@drawable/caret_left"
|
||||
android:padding="20dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/avatar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="36dp"
|
||||
android:layout_marginTop="5dp"
|
||||
android:layout_marginBottom="5dp"
|
||||
android:adjustViewBounds="true"
|
||||
android:src="@{viewModel.isOneToOne ? @drawable/contact_avatar : @drawable/group_avatar, default=@drawable/contact_avatar}"
|
||||
coilContact="@{viewModel.contactData}"
|
||||
app:layout_constraintStart_toEndOf="@id/back"
|
||||
app:layout_constraintTop_toTopOf="@id/back"
|
||||
app:layout_constraintBottom_toBottomOf="@id/back"/>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:text="@{viewModel.isOneToOne ? viewModel.contactName : viewModel.subject, default=`John Doe`}"
|
||||
android:textColor="@color/black"
|
||||
android:textSize="14sp"
|
||||
android:maxLines="1"
|
||||
android:ellipsize="end"
|
||||
app:layout_constraintStart_toEndOf="@id/avatar"
|
||||
app:layout_constraintEnd_toStartOf="@id/phone_call"
|
||||
app:layout_constraintTop_toTopOf="@id/avatar" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/presence"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:text="Online"
|
||||
android:textColor="@color/green_online"
|
||||
android:textSize="14sp"
|
||||
android:textStyle=""
|
||||
android:maxLines="1"
|
||||
android:ellipsize="end"
|
||||
app:layout_constraintStart_toEndOf="@id/avatar"
|
||||
app:layout_constraintEnd_toStartOf="@id/phone_call"
|
||||
app:layout_constraintTop_toBottomOf="@id/title"
|
||||
app:layout_constraintBottom_toBottomOf="@id/avatar"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/info"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:src="@drawable/info"
|
||||
android:padding="10dp"
|
||||
android:layout_marginEnd="5dp"
|
||||
app:layout_constraintTop_toTopOf="@id/back"
|
||||
app:layout_constraintBottom_toBottomOf="@id/back"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/video_call"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:src="@drawable/video_camera"
|
||||
android:padding="10dp"
|
||||
app:layout_constraintTop_toTopOf="@id/back"
|
||||
app:layout_constraintBottom_toBottomOf="@id/back"
|
||||
app:layout_constraintEnd_toStartOf="@id/info" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/phone_call"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:src="@drawable/phone"
|
||||
android:padding="10dp"
|
||||
app:layout_constraintTop_toTopOf="@id/back"
|
||||
app:layout_constraintBottom_toBottomOf="@id/back"
|
||||
app:layout_constraintEnd_toStartOf="@id/video_call" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/messages_list"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="5dp"
|
||||
android:layout_marginBottom="5dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/back"
|
||||
app:layout_constraintBottom_toTopOf="@id/messages_bottom_barrier"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
android:id="@+id/messages_bottom_barrier"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:constraint_referenced_ids="composing_text, bottom_background"
|
||||
app:barrierDirection="top" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/composing_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="... est en train d’écrire"
|
||||
android:textSize="12sp"
|
||||
android:textColor="#73000000"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_marginBottom="15dp"
|
||||
android:visibility="@{viewModel.isComposing ? View.VISIBLE : View.GONE}"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintBottom_toTopOf="@id/bottom_background" />
|
||||
|
||||
<View
|
||||
android:id="@+id/bottom_background"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:background="@color/gray_7"
|
||||
android:layout_marginTop="-15dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/message"
|
||||
app:layout_constraintBottom_toBottomOf="parent" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/emoji_picker"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:text="🙂"
|
||||
android:textSize="18sp"
|
||||
android:padding="5dp"
|
||||
android:gravity="center"
|
||||
android:layout_marginStart="10dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/message"
|
||||
app:layout_constraintBottom_toBottomOf="@id/message" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/attach_file"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:src="@drawable/plus_circle"
|
||||
android:padding="5dp"
|
||||
android:layout_marginStart="10dp"
|
||||
app:layout_constraintStart_toEndOf="@id/emoji_picker"
|
||||
app:layout_constraintTop_toTopOf="@id/message"
|
||||
app:layout_constraintBottom_toBottomOf="@id/message" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/message"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="Dites quelque chose..."
|
||||
android:layout_marginBottom="15dp"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:paddingStart="24dp"
|
||||
android:paddingTop="5dp"
|
||||
android:paddingBottom="5dp"
|
||||
android:maxLines="5"
|
||||
android:textSize="14sp"
|
||||
android:background="@drawable/edit_text_background"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/attach_file"
|
||||
app:layout_constraintEnd_toStartOf="@id/voice_message"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/voice_message"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:src="@drawable/voice_message"
|
||||
android:padding="5dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/message"
|
||||
app:layout_constraintBottom_toBottomOf="@id/message" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</layout>
|
||||
|
|
@ -1,145 +0,0 @@
|
|||
<?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="backClickListener"
|
||||
type="View.OnClickListener" />
|
||||
<variable
|
||||
name="viewModel"
|
||||
type="org.linphone.ui.main.conversations.viewmodel.NewConversationViewModel" />
|
||||
</data>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@android:color/transparent">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:src="@drawable/shape_white_background"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/cancel"
|
||||
android:onClick="@{backClickListener}"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Annuler"
|
||||
android:textSize="15sp"
|
||||
android:padding="10dp"
|
||||
android:textColor="@color/gray_1"
|
||||
android:layout_marginStart="15dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/title"
|
||||
app:layout_constraintBottom_toBottomOf="@id/subtitle"/>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/create_group"
|
||||
android:onClick="@{() -> viewModel.createGroup()}"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Créer"
|
||||
android:textSize="15sp"
|
||||
android:padding="10dp"
|
||||
android:textColor="@color/primary_color"
|
||||
android:layout_marginEnd="15dp"
|
||||
android:visibility="@{viewModel.groupEnabled ? View.VISIBLE : View.GONE}"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/title"
|
||||
app:layout_constraintBottom_toBottomOf="@id/subtitle"/>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Nouvelle conversation"
|
||||
android:textSize="18sp"
|
||||
android:textColor="@color/black"
|
||||
android:layout_marginTop="22dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"/>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/subtitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Chiffrée de bout en bout"
|
||||
android:textSize="14sp"
|
||||
android:textColor="@color/gray_1"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/title"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/search_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="30dp"
|
||||
android:layout_marginStart="20dp"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:padding="10dp"
|
||||
android:drawableStart="@drawable/magnifying_glass"
|
||||
android:drawablePadding="10dp"
|
||||
android:background="@drawable/edit_text_background"
|
||||
android:hint="Rechercher des contacts"
|
||||
android:text="@={viewModel.filter}"
|
||||
android:textSize="14sp"
|
||||
android:inputType="textPersonName|textNoSuggestions"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/subtitle" />
|
||||
|
||||
<ImageView
|
||||
android:onClick="@{() -> viewModel.enableGroupSelection()}"
|
||||
android:id="@+id/new_group_avatar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@drawable/users_three"
|
||||
android:padding="10dp"
|
||||
android:background="@drawable/shape_orange_round"
|
||||
android:layout_marginTop="30dp"
|
||||
android:layout_marginStart="20dp"
|
||||
android:visibility="@{viewModel.filter.length() > 0 || viewModel.groupEnabled ? View.GONE : View.VISIBLE}"
|
||||
app:layout_constraintTop_toBottomOf="@id/search_bar"
|
||||
app:layout_constraintStart_toStartOf="parent"/>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:onClick="@{() -> viewModel.enableGroupSelection()}"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Créer un nouveau groupe"
|
||||
android:textSize="16sp"
|
||||
android:padding="5dp"
|
||||
android:layout_marginStart="7dp"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:drawableEnd="@drawable/caret_right"
|
||||
android:drawablePadding="5dp"
|
||||
android:visibility="@{viewModel.filter.length() > 0 || viewModel.groupEnabled ? View.GONE : View.VISIBLE}"
|
||||
app:layout_constraintStart_toEndOf="@id/new_group_avatar"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/new_group_avatar"
|
||||
app:layout_constraintBottom_toBottomOf="@id/new_group_avatar"/>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/contacts_list"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="20dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/new_group_avatar"
|
||||
app:layout_constraintBottom_toBottomOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</layout>
|
||||
|
|
@ -1,107 +0,0 @@
|
|||
<?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"
|
||||
xmlns:bind="http://schemas.android.com/tools">
|
||||
|
||||
<data>
|
||||
<import type="android.view.View" />
|
||||
<variable
|
||||
name="onNewConversationClicked"
|
||||
type="View.OnClickListener" />
|
||||
<variable
|
||||
name="onContactsClicked"
|
||||
type="View.OnClickListener" />
|
||||
<variable
|
||||
name="viewModel"
|
||||
type="org.linphone.ui.main.conversations.viewmodel.ConversationsListViewModel" />
|
||||
</data>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/primary_color">
|
||||
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/top_bar"
|
||||
android:name="org.linphone.ui.main.fragment.TopBarFragment"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="@dimen/top_bar_height"
|
||||
bind:layout="@layout/top_search_bar"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"/>
|
||||
|
||||
<ImageView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="17dp"
|
||||
android:src="@drawable/shape_white_background"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/top_bar"
|
||||
app:layout_constraintBottom_toBottomOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/sort_by_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="15dp"
|
||||
android:text="Trier par"
|
||||
android:textSize="12sp"
|
||||
android:textColor="@color/gray_5"
|
||||
app:layout_constraintStart_toStartOf="@id/top_bar"
|
||||
app:layout_constraintTop_toTopOf="@id/sort_by"
|
||||
app:layout_constraintBottom_toBottomOf="@id/sort_by"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/sort_by"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:text="Récents"
|
||||
android:textSize="14sp"
|
||||
android:textColor="@color/blue_filter"
|
||||
android:drawablePadding="5dp"
|
||||
app:layout_constraintStart_toEndOf="@id/sort_by_label"
|
||||
app:layout_constraintTop_toBottomOf="@id/top_bar"
|
||||
app:drawableEndCompat="@drawable/caret_down" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/conversationsList"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/sort_by"
|
||||
app:layout_constraintBottom_toTopOf="@id/bottom_nav_bar" />
|
||||
|
||||
<include
|
||||
bind:onContactsClicked="@{onContactsClicked}"
|
||||
android:id="@+id/bottom_nav_bar"
|
||||
layout="@layout/bottom_nav_bar"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"/>
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/new_conversation"
|
||||
android:onClick="@{onNewConversationClicked}"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end|bottom"
|
||||
android:layout_margin="16dp"
|
||||
android:src="@drawable/plus_circle"
|
||||
app:tint="@color/primary_color"
|
||||
app:backgroundTint="@color/white"
|
||||
app:layout_constraintBottom_toTopOf="@id/bottom_nav_bar"
|
||||
app:layout_constraintEnd_toEndOf="parent"/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</layout>
|
||||
|
|
@ -5,67 +5,11 @@
|
|||
android:id="@+id/main_nav_graph"
|
||||
app:startDestination="@id/callsFragment">
|
||||
|
||||
<fragment
|
||||
android:id="@+id/conversationsFragment"
|
||||
android:name="org.linphone.ui.main.conversations.ConversationsFragment"
|
||||
android:label="ConversationsFragment"
|
||||
tools:layout="@layout/conversations_fragment">
|
||||
<action
|
||||
android:id="@+id/action_conversationsFragment_to_newConversationFragment"
|
||||
app:destination="@id/newConversationFragment"
|
||||
app:enterAnim="@anim/slide_in"
|
||||
app:popExitAnim="@anim/slide_out"
|
||||
app:launchSingleTop="true" />
|
||||
<action
|
||||
android:id="@+id/action_conversationsFragment_to_conversationFragment"
|
||||
app:destination="@id/conversationFragment"
|
||||
app:enterAnim="@anim/slide_in_right"
|
||||
app:popExitAnim="@anim/slide_out_right" />
|
||||
<action
|
||||
android:id="@+id/action_conversationsFragment_to_contactsFragment"
|
||||
app:destination="@id/contactsFragment"
|
||||
app:popUpTo="@id/contactsFragment"
|
||||
app:popUpToInclusive="true"
|
||||
app:launchSingleTop="true" />
|
||||
<action
|
||||
android:id="@+id/action_conversationsFragment_to_callsFragment"
|
||||
app:destination="@id/callsFragment" />
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/newConversationFragment"
|
||||
android:name="org.linphone.ui.main.conversations.NewConversationFragment"
|
||||
android:label="NewConversationFragment"
|
||||
tools:layout="@layout/conversation_start_fragment" >
|
||||
<action
|
||||
android:id="@+id/action_newConversationFragment_to_conversationFragment"
|
||||
app:destination="@id/conversationFragment"
|
||||
app:enterAnim="@anim/slide_in_right"
|
||||
app:popExitAnim="@anim/slide_out_right"
|
||||
app:popUpTo="@id/conversationsFragment" />
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/conversationFragment"
|
||||
android:name="org.linphone.ui.main.conversations.ConversationFragment"
|
||||
android:label="ConversationFragment"
|
||||
tools:layout="@layout/conversation_fragment" >
|
||||
<argument
|
||||
android:name="localSipUri"
|
||||
app:argType="string" />
|
||||
<argument
|
||||
android:name="remoteSipUri"
|
||||
app:argType="string" />
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/contactsFragment"
|
||||
android:name="org.linphone.ui.main.contacts.fragment.ContactsFragment"
|
||||
android:label="ContactsFragment"
|
||||
tools:layout="@layout/contacts_fragment">
|
||||
<action
|
||||
android:id="@+id/action_contactsFragment_to_conversationsFragment"
|
||||
app:destination="@id/conversationsFragment" />
|
||||
<action
|
||||
android:id="@+id/action_contactsFragment_to_callsFragment"
|
||||
app:destination="@id/callsFragment"
|
||||
|
|
@ -85,9 +29,6 @@
|
|||
app:launchSingleTop="true"
|
||||
app:popUpTo="@id/callsFragment"
|
||||
app:popUpToInclusive="true" />
|
||||
<action
|
||||
android:id="@+id/action_callsFragment_to_conversationsFragment"
|
||||
app:destination="@id/conversationsFragment" />
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue