From e5bbe3a553dfba72d52575a06c90a65e7fda957e Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Thu, 17 Aug 2023 11:44:19 +0200 Subject: [PATCH] Reworked presence badge --- app/src/main/assets/linphonerc_factory | 2 + .../org/linphone/contacts/ContactsManager.kt | 44 ++++++++++++++++++- .../java/org/linphone/core/CoreContext.kt | 9 ++-- .../contacts/adapter/ContactsListAdapter.kt | 3 +- .../main/contacts/model/ContactAvatarModel.kt | 17 ++++--- .../viewmodel/ContactsListViewModel.kt | 15 ++++--- .../linphone/ui/main/model/AccountModel.kt | 4 ++ .../org/linphone/utils/DataBindingUtils.kt | 22 ++++++---- .../java/org/linphone/utils/LinphoneUtils.kt | 25 +++++++++++ app/src/main/res/drawable/led_away.xml | 5 +++ app/src/main/res/drawable/led_background.xml | 5 +++ .../main/res/drawable/led_do_not_disturb.xml | 5 +++ .../main/res/drawable/led_not_registered.xml | 5 +++ app/src/main/res/drawable/led_online.xml | 5 +++ app/src/main/res/layout/account_list_cell.xml | 7 ++- app/src/main/res/layout/call_fragment.xml | 19 +++++--- app/src/main/res/layout/call_list_cell.xml | 13 ++++++ .../layout/contact_favourite_list_cell.xml | 19 +++++--- app/src/main/res/layout/contact_fragment.xml | 19 +++++--- app/src/main/res/layout/contact_list_cell.xml | 20 ++++++--- app/src/main/res/values/colors.xml | 2 + app/src/main/res/values/dimen.xml | 6 +++ 22 files changed, 220 insertions(+), 51 deletions(-) create mode 100644 app/src/main/res/drawable/led_away.xml create mode 100644 app/src/main/res/drawable/led_background.xml create mode 100644 app/src/main/res/drawable/led_do_not_disturb.xml create mode 100644 app/src/main/res/drawable/led_not_registered.xml create mode 100644 app/src/main/res/drawable/led_online.xml diff --git a/app/src/main/assets/linphonerc_factory b/app/src/main/assets/linphonerc_factory index 23e6571e4..bc4a6334e 100644 --- a/app/src/main/assets/linphonerc_factory +++ b/app/src/main/assets/linphonerc_factory @@ -21,6 +21,8 @@ use_cpim=1 zrtp_key_agreements_suites=MS_ZRTP_KEY_AGREEMENT_K255_KYB512 chat_messages_aggregation_delay=1000 chat_messages_aggregation=1 +update_presence_model_timestamp_before_publish_expires_refresh=1 +rls_uri=sips:rls@sip.linphone.org [sound] #remove this property for any application that is not Linphone public version itself diff --git a/app/src/main/java/org/linphone/contacts/ContactsManager.kt b/app/src/main/java/org/linphone/contacts/ContactsManager.kt index 3e29dd00d..b042f8f0c 100644 --- a/app/src/main/java/org/linphone/contacts/ContactsManager.kt +++ b/app/src/main/java/org/linphone/contacts/ContactsManager.kt @@ -21,16 +21,45 @@ package org.linphone.contacts import androidx.loader.app.LoaderManager import org.linphone.LinphoneApplication.Companion.coreContext +import org.linphone.core.Core +import org.linphone.core.CoreListenerStub import org.linphone.core.Friend +import org.linphone.core.FriendList +import org.linphone.core.FriendListListenerStub import org.linphone.core.tools.Log import org.linphone.ui.main.MainActivity import org.linphone.utils.LinphoneUtils class ContactsManager { + companion object { + const val TAG = "[Contacts Manager]" + } val localFriends = arrayListOf() private val listeners = arrayListOf() + private val friendListListener: FriendListListenerStub = object : FriendListListenerStub() { + override fun onPresenceReceived(list: FriendList, friends: Array) { + // Core thread + Log.i("$TAG Presence received") + for (listener in listeners) { + listener.onContactsLoaded() + } + } + } + + private val coreListener: CoreListenerStub = object : CoreListenerStub() { + override fun onFriendListCreated(core: Core, friendList: FriendList) { + // Core thread + friendList.addListener(friendListListener) + } + + override fun onFriendListRemoved(core: Core, friendList: FriendList) { + // Core thread + friendList.removeListener(friendListListener) + } + } + fun loadContacts(activity: MainActivity) { // UI thread val manager = LoaderManager.getInstance(activity) @@ -73,7 +102,7 @@ class ContactsManager { fun updateLocalContacts() { // Core thread - Log.i("[Contacts Manager] Updating local contact(s)") + Log.i("$TAG Updating local contact(s)") localFriends.clear() for (account in coreContext.core.accountList) { @@ -84,7 +113,7 @@ class ContactsManager { friend.address = address Log.i( - "[Contacts Manager] Local contact created for account [${address.asString()}] and picture [${friend.photo}]" + "$TAG Local contact created for account [${address.asString()}] and picture [${friend.photo}]" ) localFriends.add(friend) } @@ -92,11 +121,22 @@ class ContactsManager { fun onCoreStarted() { // Core thread + val core = coreContext.core + core.addListener(coreListener) + for (list in core.friendsLists) { + list.addListener(friendListListener) + } + updateLocalContacts() } fun onCoreStopped() { // Core thread + val core = coreContext.core + core.removeListener(coreListener) + for (list in core.friendsLists) { + list.removeListener(friendListListener) + } } } diff --git a/app/src/main/java/org/linphone/core/CoreContext.kt b/app/src/main/java/org/linphone/core/CoreContext.kt index b7620a76c..e604428f7 100644 --- a/app/src/main/java/org/linphone/core/CoreContext.kt +++ b/app/src/main/java/org/linphone/core/CoreContext.kt @@ -32,11 +32,12 @@ import org.linphone.LinphoneApplication.Companion.corePreferences import org.linphone.contacts.ContactsManager import org.linphone.core.tools.Log import org.linphone.ui.voip.VoipActivity +import org.linphone.utils.LinphoneUtils class CoreContext(val context: Context) : HandlerThread("Core Thread") { lateinit var core: Core - lateinit var emojiCompat: EmojiCompat + val emojiCompat: EmojiCompat val contactsManager = ContactsManager() @@ -96,6 +97,7 @@ class CoreContext(val context: Context) : HandlerThread("Core Thread") { ) computeUserAgent() + core.start() contactsManager.onCoreStarted() @@ -213,9 +215,8 @@ class CoreContext(val context: Context) : HandlerThread("Core Thread") { } private fun computeUserAgent() { - // TODO FIXME - val deviceName: String = "Linphone6" - val appName: String = "Linphone Android" + val deviceName = LinphoneUtils.getDeviceName(context) + 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) diff --git a/app/src/main/java/org/linphone/ui/main/contacts/adapter/ContactsListAdapter.kt b/app/src/main/java/org/linphone/ui/main/contacts/adapter/ContactsListAdapter.kt index 5ddb0fb30..63b819af7 100644 --- a/app/src/main/java/org/linphone/ui/main/contacts/adapter/ContactsListAdapter.kt +++ b/app/src/main/java/org/linphone/ui/main/contacts/adapter/ContactsListAdapter.kt @@ -122,6 +122,7 @@ private class ContactDiffCallback : DiffUtil.ItemCallback() } override fun areContentsTheSame(oldItem: ContactAvatarModel, newItem: ContactAvatarModel): Boolean { - return oldItem.showFirstLetter.value == newItem.showFirstLetter.value + return oldItem.showFirstLetter.value == newItem.showFirstLetter.value && + oldItem.presenceStatus.value == newItem.presenceStatus.value } } diff --git a/app/src/main/java/org/linphone/ui/main/contacts/model/ContactAvatarModel.kt b/app/src/main/java/org/linphone/ui/main/contacts/model/ContactAvatarModel.kt index 7a1fa25c4..1188e7be8 100644 --- a/app/src/main/java/org/linphone/ui/main/contacts/model/ContactAvatarModel.kt +++ b/app/src/main/java/org/linphone/ui/main/contacts/model/ContactAvatarModel.kt @@ -26,9 +26,14 @@ import androidx.lifecycle.MutableLiveData import org.linphone.core.ConsolidatedPresence import org.linphone.core.Friend import org.linphone.core.FriendListenerStub +import org.linphone.core.tools.Log import org.linphone.utils.LinphoneUtils class ContactAvatarModel(val friend: Friend) { + companion object { + const val TAG = "[Contact Avatar Model]" + } + val id = friend.refKey val avatar = MutableLiveData() @@ -47,19 +52,21 @@ class ContactAvatarModel(val friend: Friend) { private val friendListener = object : FriendListenerStub() { override fun onPresenceReceived(fr: Friend) { + Log.d( + "$TAG Presence received for friend [${fr.name}]: [${friend.consolidatedPresence}]" + ) presenceStatus.postValue(fr.consolidatedPresence) } } init { // Core thread - name.postValue(friend.name) - presenceStatus.postValue(friend.consolidatedPresence) - avatar.postValue(getAvatarUri()) - friend.addListener(friendListener) - presenceStatus.postValue(ConsolidatedPresence.Offline) + name.postValue(friend.name) + presenceStatus.postValue(friend.consolidatedPresence) + Log.d("$TAG Friend [${friend.name}] presence status is [${friend.consolidatedPresence}]") + avatar.postValue(getAvatarUri()) } fun destroy() { diff --git a/app/src/main/java/org/linphone/ui/main/contacts/viewmodel/ContactsListViewModel.kt b/app/src/main/java/org/linphone/ui/main/contacts/viewmodel/ContactsListViewModel.kt index 606c1ee85..4050c293f 100644 --- a/app/src/main/java/org/linphone/ui/main/contacts/viewmodel/ContactsListViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/contacts/viewmodel/ContactsListViewModel.kt @@ -32,6 +32,10 @@ import org.linphone.core.tools.Log import org.linphone.ui.main.contacts.model.ContactAvatarModel class ContactsListViewModel : ViewModel() { + companion object { + const val TAG = "[Contacts List ViewModel]" + } + val contactsList = MutableLiveData>() val favourites = MutableLiveData>() @@ -48,7 +52,7 @@ class ContactsListViewModel : ViewModel() { private val magicSearchListener = object : MagicSearchListenerStub() { override fun onSearchResultsReceived(magicSearch: MagicSearch) { // Core thread - Log.i("[Contacts] Magic search contacts available") + Log.i("$TAG Magic search contacts available") processMagicSearchResults(magicSearch.lastSearch) } } @@ -56,6 +60,7 @@ class ContactsListViewModel : ViewModel() { private val contactsListener = object : ContactsListener { override fun onContactsLoaded() { // Core thread + Log.i("$TAG Contacts have been (re)loaded, updating list") applyFilter( currentFilter, "", @@ -92,7 +97,7 @@ class ContactsListViewModel : ViewModel() { fun processMagicSearchResults(results: Array) { // Core thread - Log.i("[Contacts List] Processing ${results.size} results") + Log.i("$TAG Processing ${results.size} results") contactsList.value.orEmpty().forEach(ContactAvatarModel::destroy) val list = arrayListOf() @@ -107,7 +112,7 @@ class ContactsListViewModel : ViewModel() { currentLetter = friend.name?.get(0).toString() ContactAvatarModel(friend) } else { - Log.w("[Contacts] SearchResult [$result] has no Friend!") + Log.w("$TAG SearchResult [$result] has no Friend!") val fakeFriend = createFriendFromSearchResult(result) currentLetter = fakeFriend.name?.get(0).toString() @@ -129,7 +134,7 @@ class ContactsListViewModel : ViewModel() { favourites.postValue(favouritesList) contactsList.postValue(list) - Log.i("[Contacts] Processed ${results.size} results") + Log.i("$TAG Processed ${results.size} results") } fun applyFilter(filter: String) { @@ -163,7 +168,7 @@ class ContactsListViewModel : ViewModel() { previousFilter = filter Log.i( - "[Contacts] Asking Magic search for contacts matching filter [$filter], domain [$domain] and in sources [$sources]" + "$TAG Asking Magic search for contacts matching filter [$filter], domain [$domain] and in sources [$sources]" ) magicSearch.getContactsListAsync( filter, diff --git a/app/src/main/java/org/linphone/ui/main/model/AccountModel.kt b/app/src/main/java/org/linphone/ui/main/model/AccountModel.kt index 281a931a8..5eb11b5bc 100644 --- a/app/src/main/java/org/linphone/ui/main/model/AccountModel.kt +++ b/app/src/main/java/org/linphone/ui/main/model/AccountModel.kt @@ -81,6 +81,10 @@ class AccountModel(private val account: Account) { } } + fun openMenu() { + // UI thread + } + fun refreshRegister() { // UI thread coreContext.postOnCoreThread { core -> diff --git a/app/src/main/java/org/linphone/utils/DataBindingUtils.kt b/app/src/main/java/org/linphone/utils/DataBindingUtils.kt index c5be397b3..a5ee587fa 100644 --- a/app/src/main/java/org/linphone/utils/DataBindingUtils.kt +++ b/app/src/main/java/org/linphone/utils/DataBindingUtils.kt @@ -131,7 +131,7 @@ fun AppCompatTextView.setDrawableTint(color: Int) { @BindingAdapter("coilContact") fun loadContactPictureWithCoil2(imageView: ImageView, contact: ContactData?) { - // UI thread ! + // UI thread if (contact == null) { imageView.load(R.drawable.contact_avatar) } else { @@ -142,18 +142,24 @@ fun loadContactPictureWithCoil2(imageView: ImageView, contact: ContactData?) { } } +@BindingAdapter("presenceIcon") +fun ImageView.setPresenceIcon(presence: ConsolidatedPresence?) { + // UI thread + val icon = when (presence) { + ConsolidatedPresence.Online -> R.drawable.led_online + ConsolidatedPresence.DoNotDisturb -> R.drawable.led_do_not_disturb + ConsolidatedPresence.Busy -> R.drawable.led_away + else -> R.drawable.led_not_registered + } + setImageResource(icon) +} + @BindingAdapter("contactAvatar") fun AvatarView.loadContactPicture(contact: ContactAvatarModel?) { - // UI thread ! + // UI thread if (contact == null) { loadImage(R.drawable.contact_avatar) } else { - indicatorColor = when (contact.presenceStatus.value) { - ConsolidatedPresence.Online -> R.color.green_online - else -> R.color.blue_outgoing_message - } - indicatorEnabled = contact.presenceStatus.value != ConsolidatedPresence.Offline - val uri = contact.avatar.value loadImage( data = uri, diff --git a/app/src/main/java/org/linphone/utils/LinphoneUtils.kt b/app/src/main/java/org/linphone/utils/LinphoneUtils.kt index ae91a2a57..fac253196 100644 --- a/app/src/main/java/org/linphone/utils/LinphoneUtils.kt +++ b/app/src/main/java/org/linphone/utils/LinphoneUtils.kt @@ -19,6 +19,10 @@ */ package org.linphone.utils +import android.bluetooth.BluetoothAdapter +import android.content.Context +import android.os.Build +import android.provider.Settings import androidx.annotation.IntegerRes import androidx.emoji2.text.EmojiCompat import java.util.Locale @@ -137,5 +141,26 @@ class LinphoneUtils { fun getChatRoomId(chatRoom: ChatRoom): String { return getChatRoomId(chatRoom.localAddress, chatRoom.peerAddress) } + + fun getDeviceName(context: Context): String { + var name = Settings.Global.getString( + context.contentResolver, + Settings.Global.DEVICE_NAME + ) + if (name == null) { + val adapter = BluetoothAdapter.getDefaultAdapter() + name = adapter?.name + } + if (name == null) { + name = Settings.Secure.getString( + context.contentResolver, + "bluetooth_name" + ) + } + if (name == null) { + name = Build.MANUFACTURER + " " + Build.MODEL + } + return name + } } } diff --git a/app/src/main/res/drawable/led_away.xml b/app/src/main/res/drawable/led_away.xml new file mode 100644 index 000000000..b9df77ee1 --- /dev/null +++ b/app/src/main/res/drawable/led_away.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/led_background.xml b/app/src/main/res/drawable/led_background.xml new file mode 100644 index 000000000..74e69848c --- /dev/null +++ b/app/src/main/res/drawable/led_background.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/led_do_not_disturb.xml b/app/src/main/res/drawable/led_do_not_disturb.xml new file mode 100644 index 000000000..ead9d2698 --- /dev/null +++ b/app/src/main/res/drawable/led_do_not_disturb.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/led_not_registered.xml b/app/src/main/res/drawable/led_not_registered.xml new file mode 100644 index 000000000..cba3b5854 --- /dev/null +++ b/app/src/main/res/drawable/led_not_registered.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/led_online.xml b/app/src/main/res/drawable/led_online.xml new file mode 100644 index 000000000..5293f0745 --- /dev/null +++ b/app/src/main/res/drawable/led_online.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/layout/account_list_cell.xml b/app/src/main/res/layout/account_list_cell.xml index 0a457edc2..6e443d68a 100644 --- a/app/src/main/res/layout/account_list_cell.xml +++ b/app/src/main/res/layout/account_list_cell.xml @@ -101,13 +101,16 @@ app:layout_constraintBottom_toBottomOf="parent"/> + + + @@ -48,6 +49,18 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> + + + @@ -37,16 +38,22 @@ app:avatarViewInitialsTextSize="16sp" app:avatarViewInitialsTextStyle="bold" app:avatarViewShape="circle" - app:avatarViewBorderWidth="0dp" - app:avatarViewIndicatorEnabled="true" - app:avatarViewIndicatorBorderColor="@color/white" - app:avatarViewIndicatorSizeCriteria="7" - app:avatarViewIndicatorBorderSizeCriteria="8" - app:avatarViewIndicatorPosition="bottomRight" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" /> + + + + + @@ -47,22 +48,29 @@ android:layout_marginBottom="5dp" android:adjustViewBounds="true" contactAvatar="@{model}" + app:avatarViewInitials="SB" app:avatarViewPlaceholder="@drawable/contact_avatar" app:avatarViewInitialsBackgroundColor="@color/blue_outgoing_message" app:avatarViewInitialsTextColor="@color/gray_9" app:avatarViewInitialsTextSize="16sp" app:avatarViewInitialsTextStyle="bold" app:avatarViewShape="circle" - app:avatarViewBorderWidth="0dp" - app:avatarViewIndicatorEnabled="true" - app:avatarViewIndicatorBorderColor="@color/white" - app:avatarViewIndicatorSizeCriteria="7" - app:avatarViewIndicatorBorderSizeCriteria="8" - app:avatarViewIndicatorPosition="bottomRight" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toEndOf="@id/header" app:layout_constraintTop_toTopOf="parent" /> + + #DFECF2 #F4F4F7 #4AA8FF + #FFA645 + #E1E1E1 #FFEACB #FFB266 #22334D diff --git a/app/src/main/res/values/dimen.xml b/app/src/main/res/values/dimen.xml index f9bf994c0..675752921 100644 --- a/app/src/main/res/values/dimen.xml +++ b/app/src/main/res/values/dimen.xml @@ -10,6 +10,12 @@ 50dp 100dp 120dp + 12dp + 2dp + 3dp + 22dp + 3dp + 5dp 55dp