Improvements

This commit is contained in:
Sylvain Berfini 2023-06-23 20:21:18 +02:00
parent c77fae435f
commit 7b71f32d18
15 changed files with 267 additions and 59 deletions

1
.idea/gradle.xml generated
View file

@ -7,6 +7,7 @@
<option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="Embedded JDK" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />

View file

@ -258,6 +258,7 @@ class ContactLoader : LoaderManager.LoaderCallbacks<Cursor> {
fl.updateSubscriptions()
Log.i("[Contacts Loader] Subscription(s) updated")
coreContext.contactsManager.onContactsLoaded()
}
} catch (sde: StaleDataException) {
Log.e("[Contacts Loader] State Data Exception: $sde")

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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<ContactsListener>()
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()
}

View file

@ -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()
}

View file

@ -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)

View file

@ -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) {

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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
}
}

View file

@ -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)
}

View file

@ -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<Event<Int>>()
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)
}

View file

@ -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 {

View file

@ -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<SearchResult>) {

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromXDelta="100%"
android:toXDelta="0%"
android:duration="@android:integer/config_mediumAnimTime"/>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromXDelta="0%"
android:toXDelta="100%"
android:duration="@android:integer/config_mediumAnimTime"/>

View file

@ -0,0 +1,30 @@
<?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" />
</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.constraintlayout.widget.ConstraintLayout>
</layout>

View file

@ -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" />
<action
android:id="@+id/action_conversationsFragment_to_conversationFragment"
app:destination="@id/conversationFragment"
app:enterAnim="@anim/slide_in_right"
app:popExitAnim="@anim/slide_out_right" />
</fragment>
<fragment
android:id="@+id/newConversationFragment"
android:name="org.linphone.ui.conversations.NewConversationFragment"
android:label="NewConversationFragment"
tools:layout="@layout/new_conversation_fragment" />
tools:layout="@layout/new_conversation_fragment" >
<action
android:id="@+id/action_newConversationFragment_to_conversationFragment"
app:destination="@id/conversationFragment" />
</fragment>
<fragment
android:id="@+id/conversationFragment"
android:name="org.linphone.ui.conversations.ConversationFragment"
android:label="ConversationFragment"
tools:layout="@layout/conversation_fragment" />
</navigation>