From c12ddf43a2742ea4d9980bce96810ab5abcfe1e5 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Fri, 6 Oct 2023 14:58:59 +0200 Subject: [PATCH] Started conversation layout --- .../ui/call/fragment/ActiveCallFragment.kt | 2 +- .../chat/fragment/ConversationFragment.kt | 100 ++++++++ .../chat/fragment/ConversationsFragment.kt | 15 ++ .../fragment/ConversationsListFragment.kt | 8 +- .../ui/main/chat/model/ConversationModel.kt | 4 + .../chat/viewmodel/ConversationViewModel.kt | 97 ++++++++ .../main/history/fragment/HistoryFragment.kt | 2 +- .../ui/main/viewmodel/SharedMainViewModel.kt | 4 + app/src/main/res/drawable/paperclip.xml | 9 + .../main/res/layout-land/chat_fragment.xml | 44 ++-- .../res/layout-land/contacts_fragment.xml | 44 ++-- .../main/res/layout-land/history_fragment.xml | 44 ++-- .../res/layout/chat_conversation_fragment.xml | 228 ++++++++++++++++++ app/src/main/res/layout/chat_list_cell.xml | 2 +- .../main/res/navigation/chat_nav_graph.xml | 21 +- app/src/main/res/values/strings.xml | 1 + 16 files changed, 532 insertions(+), 93 deletions(-) create mode 100644 app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationFragment.kt create mode 100644 app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationViewModel.kt create mode 100644 app/src/main/res/drawable/paperclip.xml create mode 100644 app/src/main/res/layout/chat_conversation_fragment.xml diff --git a/app/src/main/java/org/linphone/ui/call/fragment/ActiveCallFragment.kt b/app/src/main/java/org/linphone/ui/call/fragment/ActiveCallFragment.kt index 42417be67..a9e9074e4 100644 --- a/app/src/main/java/org/linphone/ui/call/fragment/ActiveCallFragment.kt +++ b/app/src/main/java/org/linphone/ui/call/fragment/ActiveCallFragment.kt @@ -96,7 +96,7 @@ class ActiveCallFragment : GenericCallFragment() { // Holds fragment in place while new contact fragment slides over it return AnimationUtils.loadAnimation(activity, R.anim.hold) } - return AnimationUtils.loadAnimation(activity, R.anim.hold) + return super.onCreateAnimation(transit, enter, nextAnim) } override fun onCreateView( diff --git a/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationFragment.kt b/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationFragment.kt new file mode 100644 index 000000000..8a5f6c9a8 --- /dev/null +++ b/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationFragment.kt @@ -0,0 +1,100 @@ +/* + * 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 . + */ +package org.linphone.ui.main.chat.fragment + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.lifecycle.ViewModelProvider +import androidx.navigation.fragment.findNavController +import androidx.navigation.fragment.navArgs +import org.linphone.core.tools.Log +import org.linphone.databinding.ChatConversationFragmentBinding +import org.linphone.ui.main.chat.viewmodel.ConversationViewModel +import org.linphone.ui.main.fragment.GenericFragment +import org.linphone.utils.Event + +class ConversationFragment : GenericFragment() { + companion object { + private const val TAG = "[Conversation Fragment]" + } + + private lateinit var binding: ChatConversationFragmentBinding + + private lateinit var viewModel: ConversationViewModel + + private val args: ConversationFragmentArgs by navArgs() + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + binding = ChatConversationFragmentBinding.inflate(layoutInflater) + return binding.root + } + + override fun goBack(): Boolean { + sharedViewModel.closeSlidingPaneEvent.value = Event(true) + // If not done, when going back to ConversationsFragment this fragment will be created again + return findNavController().popBackStack() + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + postponeEnterTransition() + + binding.lifecycleOwner = viewLifecycleOwner + + viewModel = ViewModelProvider(this)[ConversationViewModel::class.java] + binding.viewModel = viewModel + + val localSipUri = args.localSipUri + val remoteSipUri = args.remoteSipUri + Log.i( + "$TAG Looking up for conversation with local SIP URI [$localSipUri] and remote SIP URI [$remoteSipUri]" + ) + viewModel.findChatRoom(localSipUri, remoteSipUri) + + binding.setBackClickListener { + goBack() + } + + viewModel.chatRoomFoundEvent.observe(viewLifecycleOwner) { + it.consume { found -> + if (found) { + Log.i( + "$TAG Found matching chat room for local SIP URI [$localSipUri] and remote SIP URI [$remoteSipUri]" + ) + startPostponedEnterTransition() + sharedViewModel.openSlidingPaneEvent.value = Event(true) + } else { + Log.e("$TAG Failed to find chat room, going back") + goBack() + } + } + } + + sharedViewModel.isSlidingPaneSlideable.observe(viewLifecycleOwner) { slideable -> + viewModel.showBackButton.value = slideable + } + } +} diff --git a/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationsFragment.kt b/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationsFragment.kt index 39076038f..adebf3294 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationsFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationsFragment.kt @@ -101,6 +101,21 @@ class ConversationsFragment : GenericFragment() { } } + sharedViewModel.showConversationEvent.observe(viewLifecycleOwner) { + it.consume { pair -> + val localSipUri = pair.first + val remoteSipUri = pair.second + Log.i( + "$TAG Navigating to conversation fragment with local SIP URI [$localSipUri] and remote SIP URI [$remoteSipUri]" + ) + val action = ConversationFragmentDirections.actionGlobalConversationFragment( + localSipUri, + remoteSipUri + ) + binding.chatNavContainer.findNavController().navigate(action) + } + } + sharedViewModel.navigateToContactsEvent.observe(viewLifecycleOwner) { it.consume { if (findNavController().currentDestination?.id == R.id.conversationsFragment) { diff --git a/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationsListFragment.kt b/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationsListFragment.kt index e71ce3bca..ce707c83d 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationsListFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationsListFragment.kt @@ -65,10 +65,6 @@ class ConversationsListFragment : AbstractTopBarFragment() { binding.lifecycleOwner = viewLifecycleOwner binding.viewModel = listViewModel - binding.setOnNewConversationClicked { - // TODO: open start conversation fragment - } - adapter = ConversationsListAdapter(viewLifecycleOwner) binding.conversationsList.setHasFixedSize(true) binding.conversationsList.adapter = adapter @@ -113,7 +109,9 @@ class ConversationsListFragment : AbstractTopBarFragment() { adapter.conversationClickedEvent.observe(viewLifecycleOwner) { it.consume { model -> Log.i("$TAG Show conversation with ID [${model.id}]") - // TODO + sharedViewModel.showConversationEvent.value = Event( + Pair(model.localSipUri, model.remoteSipUri) + ) } } diff --git a/app/src/main/java/org/linphone/ui/main/chat/model/ConversationModel.kt b/app/src/main/java/org/linphone/ui/main/chat/model/ConversationModel.kt index 6bf27cbd3..7d33c1743 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/model/ConversationModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/model/ConversationModel.kt @@ -40,6 +40,10 @@ class ConversationModel @WorkerThread constructor(private val chatRoom: ChatRoom val id = LinphoneUtils.getChatRoomId(chatRoom) + val localSipUri = chatRoom.localAddress.asStringUriOnly() + + val remoteSipUri = chatRoom.peerAddress.asStringUriOnly() + val isGroup = !chatRoom.hasCapability(Capabilities.OneToOne.toInt()) val lastUpdateTime = MutableLiveData() diff --git a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationViewModel.kt b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationViewModel.kt new file mode 100644 index 000000000..953539711 --- /dev/null +++ b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationViewModel.kt @@ -0,0 +1,97 @@ +/* + * 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 . + */ +package org.linphone.ui.main.chat.viewmodel + +import androidx.annotation.UiThread +import androidx.annotation.WorkerThread +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import org.linphone.LinphoneApplication.Companion.coreContext +import org.linphone.core.ChatRoom +import org.linphone.core.Factory +import org.linphone.core.tools.Log +import org.linphone.ui.main.contacts.model.ContactAvatarModel +import org.linphone.utils.Event + +class ConversationViewModel @UiThread constructor() : ViewModel() { + companion object { + private const val TAG = "[Conversation ViewModel]" + } + + val showBackButton = MutableLiveData() + + val avatarModel = MutableLiveData() + + val chatRoomFoundEvent = MutableLiveData>() + + private lateinit var chatRoom: ChatRoom + + @UiThread + fun findChatRoom(localSipUri: String, remoteSipUri: String) { + coreContext.postOnCoreThread { core -> + Log.i( + "$TAG Looking for chat room with local SIP URI [$localSipUri] and remote SIP URI [$remoteSipUri]" + ) + + val localAddress = Factory.instance().createAddress(localSipUri) + val remoteAddress = Factory.instance().createAddress(remoteSipUri) + if (localAddress != null && remoteAddress != null) { + val found = core.searchChatRoom( + null, + localAddress, + remoteAddress, + arrayOfNulls( + 0 + ) + ) + if (found != null) { + chatRoom = found + configureChatRoom() + chatRoomFoundEvent.postValue(Event(true)) + } else { + Log.e("Failed to find chat room given local & remote addresses!") + chatRoomFoundEvent.postValue(Event(false)) + } + } else { + Log.e("Failed to parse local or remote SIP URI as Address!") + chatRoomFoundEvent.postValue(Event(false)) + } + } + } + + @WorkerThread + private fun configureChatRoom() { + val address = if (chatRoom.hasCapability(ChatRoom.Capabilities.Basic.toInt())) { + chatRoom.peerAddress + } else { + val firstParticipant = chatRoom.participants.firstOrNull() + firstParticipant?.address ?: chatRoom.peerAddress + } + + val friend = coreContext.contactsManager.findContactByAddress(address) + if (friend != null) { + avatarModel.postValue(ContactAvatarModel(friend)) + } else { + val fakeFriend = coreContext.core.createFriend() + fakeFriend.address = address + avatarModel.postValue(ContactAvatarModel(fakeFriend)) + } + } +} diff --git a/app/src/main/java/org/linphone/ui/main/history/fragment/HistoryFragment.kt b/app/src/main/java/org/linphone/ui/main/history/fragment/HistoryFragment.kt index 3bcfb5340..c72e5b42b 100644 --- a/app/src/main/java/org/linphone/ui/main/history/fragment/HistoryFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/history/fragment/HistoryFragment.kt @@ -58,7 +58,7 @@ class HistoryFragment : GenericFragment() { // Holds fragment in place while new contact fragment slides over it return AnimationUtils.loadAnimation(activity, R.anim.hold) } - return AnimationUtils.loadAnimation(activity, R.anim.hold) + return super.onCreateAnimation(transit, enter, nextAnim) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { diff --git a/app/src/main/java/org/linphone/ui/main/viewmodel/SharedMainViewModel.kt b/app/src/main/java/org/linphone/ui/main/viewmodel/SharedMainViewModel.kt index 4eaccb346..f4165e544 100644 --- a/app/src/main/java/org/linphone/ui/main/viewmodel/SharedMainViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/viewmodel/SharedMainViewModel.kt @@ -100,4 +100,8 @@ class SharedMainViewModel @UiThread constructor() : ViewModel() { val showStartConversationEvent: MutableLiveData> by lazy { MutableLiveData>() } + + val showConversationEvent: MutableLiveData>> by lazy { + MutableLiveData>>() + } } diff --git a/app/src/main/res/drawable/paperclip.xml b/app/src/main/res/drawable/paperclip.xml new file mode 100644 index 000000000..8320d0255 --- /dev/null +++ b/app/src/main/res/drawable/paperclip.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout-land/chat_fragment.xml b/app/src/main/res/layout-land/chat_fragment.xml index a9a66f7e9..35d35ed0d 100644 --- a/app/src/main/res/layout-land/chat_fragment.xml +++ b/app/src/main/res/layout-land/chat_fragment.xml @@ -6,37 +6,27 @@ - - + + + android:layout_height="match_parent" + android:layout_weight="1" + app:defaultNavHost="false" + app:navGraph="@navigation/chat_nav_graph"/> - - - - - - - + \ No newline at end of file diff --git a/app/src/main/res/layout-land/contacts_fragment.xml b/app/src/main/res/layout-land/contacts_fragment.xml index 47a7e8bf3..82213983c 100644 --- a/app/src/main/res/layout-land/contacts_fragment.xml +++ b/app/src/main/res/layout-land/contacts_fragment.xml @@ -6,37 +6,27 @@ - - + + + android:layout_height="match_parent" + android:layout_weight="1" + app:defaultNavHost="false" + app:navGraph="@navigation/contacts_nav_graph"/> - - - - - - - + \ No newline at end of file diff --git a/app/src/main/res/layout-land/history_fragment.xml b/app/src/main/res/layout-land/history_fragment.xml index 558de2058..39f7e1818 100644 --- a/app/src/main/res/layout-land/history_fragment.xml +++ b/app/src/main/res/layout-land/history_fragment.xml @@ -6,37 +6,27 @@ - - + + + android:layout_height="match_parent" + android:layout_weight="1" + app:defaultNavHost="false" + app:navGraph="@navigation/history_nav_graph"/> - - - - - - - + \ No newline at end of file diff --git a/app/src/main/res/layout/chat_conversation_fragment.xml b/app/src/main/res/layout/chat_conversation_fragment.xml new file mode 100644 index 000000000..711a4ae43 --- /dev/null +++ b/app/src/main/res/layout/chat_conversation_fragment.xml @@ -0,0 +1,228 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/chat_list_cell.xml b/app/src/main/res/layout/chat_list_cell.xml index 971cd8a1a..827070291 100644 --- a/app/src/main/res/layout/chat_list_cell.xml +++ b/app/src/main/res/layout/chat_list_cell.xml @@ -118,7 +118,7 @@ android:id="@+id/notifications_count" android:layout_width="24dp" android:layout_height="24dp" - android:layout_marginEnd="32dp" + android:layout_marginEnd="16dp" android:gravity="center" android:background="@drawable/shape_red_round" android:text="@{String.valueOf(model.unreadMessageCount), default=`1`}" diff --git a/app/src/main/res/navigation/chat_nav_graph.xml b/app/src/main/res/navigation/chat_nav_graph.xml index d0fda79b8..851aa32e4 100644 --- a/app/src/main/res/navigation/chat_nav_graph.xml +++ b/app/src/main/res/navigation/chat_nav_graph.xml @@ -12,8 +12,21 @@ tools:layout="@layout/empty_fragment"/> + android:id="@+id/conversationFragment" + android:name="org.linphone.ui.main.chat.fragment.ConversationFragment" + android:label="ConversationFragment" + tools:layout="@layout/chat_conversation_fragment"> + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b1dfec863..0f89dcf53 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -329,6 +329,7 @@ Search contact Create a group conversation No contact for the moment… + Say something… Operation in progress, please wait