diff --git a/.idea/gradle.xml b/.idea/gradle.xml index a2d7c2133..a0de2a152 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -7,6 +7,7 @@ + diff --git a/app/src/main/java/org/linphone/contacts/ContactLoader.kt b/app/src/main/java/org/linphone/contacts/ContactLoader.kt index c4c8f6d8b..4db74b3dd 100644 --- a/app/src/main/java/org/linphone/contacts/ContactLoader.kt +++ b/app/src/main/java/org/linphone/contacts/ContactLoader.kt @@ -258,6 +258,7 @@ class ContactLoader : LoaderManager.LoaderCallbacks { fl.updateSubscriptions() Log.i("[Contacts Loader] Subscription(s) updated") + coreContext.contactsManager.onContactsLoaded() } } catch (sde: StaleDataException) { Log.e("[Contacts Loader] State Data Exception: $sde") diff --git a/app/src/main/java/org/linphone/contacts/ContactsManager.kt b/app/src/main/java/org/linphone/contacts/ContactsManager.kt new file mode 100644 index 000000000..e75e92d95 --- /dev/null +++ b/app/src/main/java/org/linphone/contacts/ContactsManager.kt @@ -0,0 +1,67 @@ +/* + * 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.contacts + +import androidx.loader.app.LoaderManager +import org.linphone.LinphoneApplication.Companion.coreContext +import org.linphone.ui.MainActivity + +class ContactsManager { + private val listeners = arrayListOf() + + fun loadContacts(activity: MainActivity) { + val manager = LoaderManager.getInstance(activity) + manager.restartLoader(0, null, ContactLoader()) + } + + fun addListener(listener: ContactsListener) { + if (coreContext.isReady()) { + coreContext.postOnCoreThread { + listeners.add(listener) + } + } + } + + fun removeListener(listener: ContactsListener) { + if (coreContext.isReady()) { + coreContext.postOnCoreThread { + listeners.remove(listener) + } + } + } + + fun onContactsLoaded() { + coreContext.postOnCoreThread { + for (listener in listeners) { + listener.onContactsLoaded() + } + } + } + + fun onCoreStarted() { + } + + fun onCoreStopped() { + } +} + +interface ContactsListener { + fun onContactsLoaded() +} diff --git a/app/src/main/java/org/linphone/core/CoreContext.kt b/app/src/main/java/org/linphone/core/CoreContext.kt index 445f8b849..15677d280 100644 --- a/app/src/main/java/org/linphone/core/CoreContext.kt +++ b/app/src/main/java/org/linphone/core/CoreContext.kt @@ -27,12 +27,14 @@ import android.os.Looper import java.util.* import org.linphone.BuildConfig import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.R +import org.linphone.contacts.ContactsManager import org.linphone.core.tools.Log class CoreContext(val context: Context) : HandlerThread("Core Thread") { lateinit var core: Core + val contactsManager = ContactsManager() + @SuppressLint("HandlerLeak") private lateinit var coreThread: Handler @@ -69,11 +71,14 @@ class CoreContext(val context: Context) : HandlerThread("Core Thread") { computeUserAgent() core.start() + contactsManager.onCoreStarted() + Looper.loop() } override fun destroy() { core.stop() + contactsManager.onCoreStopped() quitSafely() } diff --git a/app/src/main/java/org/linphone/ui/MainActivity.kt b/app/src/main/java/org/linphone/ui/MainActivity.kt index ade507308..c384b9571 100644 --- a/app/src/main/java/org/linphone/ui/MainActivity.kt +++ b/app/src/main/java/org/linphone/ui/MainActivity.kt @@ -27,13 +27,11 @@ import androidx.core.content.ContextCompat import androidx.core.view.WindowCompat import androidx.databinding.DataBindingUtil import androidx.lifecycle.ViewModelProvider -import androidx.loader.app.LoaderManager import androidx.navigation.findNavController import androidx.navigation.ui.setupWithNavController import com.google.android.material.navigation.NavigationBarView import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.R -import org.linphone.contacts.ContactLoader import org.linphone.databinding.ActivityMainBinding class MainActivity : AppCompatActivity() { @@ -54,8 +52,7 @@ class MainActivity : AppCompatActivity() { ) if (checkSelfPermission(Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED) { - val manager = LoaderManager.getInstance(this) - manager.restartLoader(0, null, ContactLoader()) + coreContext.contactsManager.loadContacts(this) } while (!coreContext.isReady()) { @@ -99,8 +96,7 @@ class MainActivity : AppCompatActivity() { grantResults: IntArray ) { if (requestCode == CONTACTS_PERMISSION_REQUEST && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - val manager = LoaderManager.getInstance(this) - manager.restartLoader(0, null, ContactLoader()) + coreContext.contactsManager.loadContacts(this) } super.onRequestPermissionsResult(requestCode, permissions, grantResults) diff --git a/app/src/main/java/org/linphone/ui/conversations/ChatRoomData.kt b/app/src/main/java/org/linphone/ui/conversations/ChatRoomData.kt index 39bb8ac48..d1cc9cf85 100644 --- a/app/src/main/java/org/linphone/ui/conversations/ChatRoomData.kt +++ b/app/src/main/java/org/linphone/ui/conversations/ChatRoomData.kt @@ -65,6 +65,14 @@ class ChatRoomData(val chatRoom: ChatRoom) { chatRoom.hasCapability(ChatRoom.Capabilities.OneToOne.toInt()) } + 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, @@ -96,7 +104,42 @@ class ChatRoomData(val chatRoom: ChatRoom) { 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(areNotificationsMuted()) + } + + 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) @@ -121,35 +164,7 @@ class ChatRoomData(val chatRoom: ChatRoom) { } } } - subject.postValue( - chatRoom.subject ?: LinphoneUtils.getDisplayName(chatRoom.peerAddress) - ) - - lastMessageImdnIcon.postValue(R.drawable.imdn_sent) - showLastMessageImdnIcon.postValue(false) 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(areNotificationsMuted()) - } - - fun onCleared() { - coreContext.postOnCoreThread { core -> - chatRoom.removeListener(chatRoomListener) - } - } - - fun onClicked() { - chatRoomDataListener?.onClicked() - } - - fun onLongClicked(): Boolean { - chatRoomDataListener?.onLongClicked() - return true } private fun computeLastMessageImdnIcon(message: ChatMessage) { diff --git a/app/src/main/java/org/linphone/ui/conversations/ConversationFragment.kt b/app/src/main/java/org/linphone/ui/conversations/ConversationFragment.kt new file mode 100644 index 000000000..7846d4c78 --- /dev/null +++ b/app/src/main/java/org/linphone/ui/conversations/ConversationFragment.kt @@ -0,0 +1,46 @@ +/* + * 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.conversations + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import org.linphone.databinding.ConversationFragmentBinding + +class ConversationFragment : Fragment() { + private lateinit var binding: ConversationFragmentBinding + + 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) + + binding.lifecycleOwner = viewLifecycleOwner + } +} diff --git a/app/src/main/java/org/linphone/ui/conversations/ConversationsFragment.kt b/app/src/main/java/org/linphone/ui/conversations/ConversationsFragment.kt index 4df1c5c14..91fde1188 100644 --- a/app/src/main/java/org/linphone/ui/conversations/ConversationsFragment.kt +++ b/app/src/main/java/org/linphone/ui/conversations/ConversationsFragment.kt @@ -91,6 +91,9 @@ class ConversationsFragment : Fragment() { adapter.chatRoomClickedEvent.observe(viewLifecycleOwner) { it.consume { data -> + findNavController().navigate( + R.id.action_conversationsFragment_to_conversationFragment + ) } } @@ -123,16 +126,12 @@ class ConversationsFragment : Fragment() { } binding.setOnNewConversationClicked { - goToNewConversation() + findNavController().navigate( + R.id.action_conversationsFragment_to_newConversationFragment + ) } } - private fun goToNewConversation() { - findNavController().navigate( - R.id.action_conversationsFragment_to_newConversationFragment - ) - } - private fun scrollToTop() { binding.conversationsList.scrollToPosition(0) } diff --git a/app/src/main/java/org/linphone/ui/conversations/ConversationsListViewModel.kt b/app/src/main/java/org/linphone/ui/conversations/ConversationsListViewModel.kt index ccb6ac77d..b87fbdfd1 100644 --- a/app/src/main/java/org/linphone/ui/conversations/ConversationsListViewModel.kt +++ b/app/src/main/java/org/linphone/ui/conversations/ConversationsListViewModel.kt @@ -23,6 +23,7 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import java.util.ArrayList import org.linphone.LinphoneApplication.Companion.coreContext +import org.linphone.contacts.ContactsListener import org.linphone.core.ChatMessage import org.linphone.core.ChatRoom import org.linphone.core.Core @@ -36,6 +37,14 @@ class ConversationsListViewModel : ViewModel() { val notifyItemChangedEvent = MutableLiveData>() + private val contactsListener = object : ContactsListener { + override fun onContactsLoaded() { + for (chatRoomData in chatRoomsList.value.orEmpty()) { + chatRoomData.contactLookup() + } + } + } + private val coreListener = object : CoreListenerStub() { override fun onChatRoomStateChanged( core: Core, @@ -85,10 +94,12 @@ class ConversationsListViewModel : ViewModel() { coreContext.postOnCoreThread { core -> core.addListener(coreListener) } + coreContext.contactsManager.addListener(contactsListener) updateChatRoomsList() } override fun onCleared() { + coreContext.contactsManager.removeListener(contactsListener) coreContext.postOnCoreThread { core -> core.removeListener(coreListener) } diff --git a/app/src/main/java/org/linphone/ui/conversations/NewConversationFragment.kt b/app/src/main/java/org/linphone/ui/conversations/NewConversationFragment.kt index bd32b4a09..0d9c111da 100644 --- a/app/src/main/java/org/linphone/ui/conversations/NewConversationFragment.kt +++ b/app/src/main/java/org/linphone/ui/conversations/NewConversationFragment.kt @@ -27,6 +27,7 @@ import androidx.core.view.doOnPreDraw import androidx.fragment.app.Fragment 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.NewConversationFragmentBinding @@ -75,7 +76,10 @@ class NewConversationFragment : Fragment() { viewModel.filter.observe( viewLifecycleOwner ) { - viewModel.applyFilter(it.orEmpty().trim()) + val filter = it.orEmpty().trim() + coreContext.postOnCoreThread { + viewModel.applyFilter(filter) + } } binding.setCancelClickListener { diff --git a/app/src/main/java/org/linphone/ui/conversations/NewConversationViewModel.kt b/app/src/main/java/org/linphone/ui/conversations/NewConversationViewModel.kt index 335d16423..bfb1354ca 100644 --- a/app/src/main/java/org/linphone/ui/conversations/NewConversationViewModel.kt +++ b/app/src/main/java/org/linphone/ui/conversations/NewConversationViewModel.kt @@ -23,6 +23,7 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.contacts.ContactData +import org.linphone.contacts.ContactsListener import org.linphone.core.MagicSearch import org.linphone.core.MagicSearchListenerStub import org.linphone.core.SearchResult @@ -46,13 +47,25 @@ class NewConversationViewModel : ViewModel() { } } + private val contactsListener = object : ContactsListener { + override fun onContactsLoaded() { + applyFilter(filter.value.orEmpty().trim()) + } + } + init { - magicSearch.addListener(magicSearchListener) - applyFilter("") + coreContext.postOnCoreThread { + magicSearch.addListener(magicSearchListener) + coreContext.contactsManager.addListener(contactsListener) + applyFilter("") + } } override fun onCleared() { - magicSearch.removeListener(magicSearchListener) + coreContext.postOnCoreThread { + coreContext.contactsManager.removeListener(contactsListener) + magicSearch.removeListener(magicSearchListener) + } super.onCleared() } @@ -63,20 +76,16 @@ class NewConversationViewModel : ViewModel() { (previousFilter.length == filterValue.length && previousFilter != filterValue) ) ) { - coreContext.postOnCoreThread { core -> - magicSearch.resetSearchCache() - } + magicSearch.resetSearchCache() } previousFilter = filterValue - coreContext.postOnCoreThread { core -> - magicSearch.getContactsListAsync( - filterValue, - "", - MagicSearch.Source.Friends.toInt(), - MagicSearch.Aggregation.Friend - ) - } + magicSearch.getContactsListAsync( + filterValue, + "", + MagicSearch.Source.Friends.toInt(), + MagicSearch.Aggregation.Friend + ) } private fun processMagicSearchResults(results: Array) { diff --git a/app/src/main/res/anim/slide_in_right.xml b/app/src/main/res/anim/slide_in_right.xml new file mode 100644 index 000000000..c2bb88c7f --- /dev/null +++ b/app/src/main/res/anim/slide_in_right.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/app/src/main/res/anim/slide_out_right.xml b/app/src/main/res/anim/slide_out_right.xml new file mode 100644 index 000000000..701ad3291 --- /dev/null +++ b/app/src/main/res/anim/slide_out_right.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/app/src/main/res/layout/conversation_fragment.xml b/app/src/main/res/layout/conversation_fragment.xml new file mode 100644 index 000000000..09b036f0e --- /dev/null +++ b/app/src/main/res/layout/conversation_fragment.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/main_nav_graph.xml b/app/src/main/res/navigation/main_nav_graph.xml index 1a7cfb7db..abb822169 100644 --- a/app/src/main/res/navigation/main_nav_graph.xml +++ b/app/src/main/res/navigation/main_nav_graph.xml @@ -14,14 +14,28 @@ android:id="@+id/action_conversationsFragment_to_newConversationFragment" app:destination="@id/newConversationFragment" app:enterAnim="@anim/slide_in" - app:launchSingleTop="true" app:popExitAnim="@anim/slide_out" /> + + tools:layout="@layout/new_conversation_fragment" > + + + + \ No newline at end of file