From a5b8a8a68391042b4787e33f8d540094667e9858 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Mon, 27 Jan 2025 17:03:47 +0100 Subject: [PATCH 01/89] Show git describe in startup logs --- app/src/main/java/org/linphone/contacts/ContactsManager.kt | 3 +++ app/src/main/java/org/linphone/core/CoreContext.kt | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/linphone/contacts/ContactsManager.kt b/app/src/main/java/org/linphone/contacts/ContactsManager.kt index aa179add7..40fba44c0 100644 --- a/app/src/main/java/org/linphone/contacts/ContactsManager.kt +++ b/app/src/main/java/org/linphone/contacts/ContactsManager.kt @@ -575,10 +575,12 @@ class ContactsManager @WorkerThread fun onCoreStarted(core: Core) { + Log.i("$TAG Core has been started") loadContactsOnlyFromDefaultDirectory = corePreferences.fetchContactsFromDefaultDirectory core.addListener(coreListener) for (list in core.friendsLists) { + Log.i("$TAG Found existing friend list [${list.displayName}]") list.addListener(friendListListener) } @@ -604,6 +606,7 @@ class ContactsManager @WorkerThread fun onCoreStopped(core: Core) { + Log.w("$TAG Core has been stopped") coroutineScope.cancel() core.removeListener(coreListener) diff --git a/app/src/main/java/org/linphone/core/CoreContext.kt b/app/src/main/java/org/linphone/core/CoreContext.kt index f7bbfb102..27cf540d6 100644 --- a/app/src/main/java/org/linphone/core/CoreContext.kt +++ b/app/src/main/java/org/linphone/core/CoreContext.kt @@ -415,7 +415,9 @@ class CoreContext } Log.i("=========================================") Log.i("==== Linphone-android information dump ====") - Log.i("VERSION=${BuildConfig.VERSION_NAME} / ${BuildConfig.VERSION_CODE}") + val gitVersion = AppUtils.getString(org.linphone.R.string.linphone_app_version) + val gitBranch = AppUtils.getString(org.linphone.R.string.linphone_app_branch) + Log.i("VERSION=${BuildConfig.VERSION_NAME} / ${BuildConfig.VERSION_CODE} ($gitVersion from $gitBranch branch)") Log.i("PACKAGE=${BuildConfig.APPLICATION_ID}") Log.i("BUILD TYPE=${BuildConfig.BUILD_TYPE}") Log.i("=========================================") From 37c23066f03c760da11b0a4f6361a362d1c24f44 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Tue, 28 Jan 2025 16:55:49 +0100 Subject: [PATCH 02/89] Fixed missing composing notifications --- .../chat/fragment/ConversationFragment.kt | 27 ++++++++++--------- .../SendMessageInConversationViewModel.kt | 7 +++++ 2 files changed, 21 insertions(+), 13 deletions(-) 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 index 79796012b..d9410b9c1 100644 --- 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 @@ -275,15 +275,21 @@ open class ConversationFragment : SlidingPaneChildFragment() { } override fun afterTextChanged(p0: Editable?) { - sendMessageViewModel.closeParticipantsList() + if (viewModel.isGroup.value == true) { + sendMessageViewModel.closeParticipantsList() - val split = p0.toString().split(" ") - for (part in split) { - if (part == "@") { - Log.i("$TAG '@' found, opening participants list") - sendMessageViewModel.openParticipantsList() + val split = p0.toString().split(" ") + for (part in split) { + if (part == "@") { + Log.i("$TAG '@' found, opening participants list") + sendMessageViewModel.openParticipantsList() + } } } + + if (p0.toString().isNotEmpty()) { + sendMessageViewModel.notifyChatMessageIsBeingComposed() + } } } @@ -786,13 +792,6 @@ open class ConversationFragment : SlidingPaneChildFragment() { } } - viewModel.isGroup.observe(viewLifecycleOwner) { group -> - if (group) { - Log.i("$TAG Adding text observer to message sending area") - binding.sendArea.messageToSend.addTextChangedListener(textObserver) - } - } - viewModel.messageDeletedEvent.observe(viewLifecycleOwner) { it.consume { val message = getString(R.string.conversation_message_deleted_toast) @@ -946,6 +945,8 @@ open class ConversationFragment : SlidingPaneChildFragment() { } } + binding.sendArea.messageToSend.addTextChangedListener(textObserver) + scrollListener = object : ConversationScrollListener(layoutManager) { @UiThread override fun onLoadMore(totalItemsCount: Int) { diff --git a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/SendMessageInConversationViewModel.kt b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/SendMessageInConversationViewModel.kt index cd9d6ccad..2c1a18e80 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/SendMessageInConversationViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/SendMessageInConversationViewModel.kt @@ -321,6 +321,13 @@ class SendMessageInConversationViewModel } } + @UiThread + fun notifyChatMessageIsBeingComposed() { + coreContext.postOnCoreThread { + chatRoom.compose() + } + } + @UiThread fun openParticipantsList() { isParticipantsListOpen.value = true From ec68e931c41c053c580d67582dc6bb2a52b56c30 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Tue, 28 Jan 2025 17:45:09 +0100 Subject: [PATCH 03/89] Fixed contacts not reladed when bodyless friendlist presence is received --- .../main/java/org/linphone/contacts/ContactsManager.kt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/src/main/java/org/linphone/contacts/ContactsManager.kt b/app/src/main/java/org/linphone/contacts/ContactsManager.kt index 40fba44c0..1f9afffa1 100644 --- a/app/src/main/java/org/linphone/contacts/ContactsManager.kt +++ b/app/src/main/java/org/linphone/contacts/ContactsManager.kt @@ -143,6 +143,14 @@ class ContactsManager } private val friendListListener: FriendListListenerStub = object : FriendListListenerStub() { + @WorkerThread + override fun onPresenceReceived(friendList: FriendList, friends: Array) { + if (friendList.isSubscriptionBodyless) { + Log.i("$TAG Bodyless friendlist [${friendList.displayName}] presence received") + notifyContactsListChanged() + } + } + @WorkerThread override fun onNewSipAddressDiscovered( friendList: FriendList, From e9385b5c077a4fb3850b50f14ae8892961445c64 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Wed, 29 Jan 2025 09:27:34 +0100 Subject: [PATCH 04/89] Removed debug APK from GitLab artefacts and reduced artefacts expire --- .gitlab-ci-files/job-android.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci-files/job-android.yml b/.gitlab-ci-files/job-android.yml index f1ede3e0f..946909ca4 100644 --- a/.gitlab-ci-files/job-android.yml +++ b/.gitlab-ci-files/job-android.yml @@ -24,13 +24,12 @@ job-android: artifacts: paths: - - ./app/build/outputs/apk/debug/linphone-android-debug-*.apk - ./app/build/outputs/apk/release/linphone-android-release-*.apk when: always - expire_in: 1 week + expire_in: 1 day .scheduled-job-android: extends: job-android only: - - schedules \ No newline at end of file + - schedules From 0f3aea191fe91626d0e6eab8fed623c43617c9c4 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Wed, 29 Jan 2025 10:56:37 +0100 Subject: [PATCH 05/89] Improved Telecom Call Control Callback disconnect causes --- .../telecom/TelecomCallControlCallback.kt | 53 ++++++++++++++++--- 1 file changed, 47 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/org/linphone/telecom/TelecomCallControlCallback.kt b/app/src/main/java/org/linphone/telecom/TelecomCallControlCallback.kt index 1e3f692ca..1addc4c6b 100644 --- a/app/src/main/java/org/linphone/telecom/TelecomCallControlCallback.kt +++ b/app/src/main/java/org/linphone/telecom/TelecomCallControlCallback.kt @@ -34,6 +34,7 @@ import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.core.AudioDevice import org.linphone.core.Call import org.linphone.core.CallListenerStub +import org.linphone.core.Reason import org.linphone.core.tools.Log import org.linphone.utils.AudioUtils import org.linphone.utils.Event @@ -74,18 +75,39 @@ class TelecomCallControlCallback( } } } else if (state == Call.State.End) { + val reason = call.reason + val direction = call.dir scope.launch { - Log.i("$TAG Disconnecting call because it has ended") - callControl.disconnect(DisconnectCause(DisconnectCause.LOCAL)) + val disconnectCause = when (reason) { + Reason.NotAnswered -> DisconnectCause.REMOTE + Reason.Declined -> DisconnectCause.REJECTED + Reason.Busy -> { + if (direction == Call.Dir.Incoming) { + DisconnectCause.MISSED + } else { + DisconnectCause.BUSY + } + } + else -> DisconnectCause.LOCAL + } + Log.i("$TAG Disconnecting [${if (direction == Call.Dir.Incoming)"incoming" else "outgoing"}] call with cause [${disconnectCauseToString(disconnectCause)}] because it has ended with reason [$reason]") + try { + callControl.disconnect(DisconnectCause(disconnectCause)) + } catch (ise: IllegalArgumentException) { + Log.e("$TAG Couldn't disconnect call control with cause [${disconnectCauseToString(disconnectCause)}]: $ise") + } } } else if (state == Call.State.Error) { + val reason = call.reason scope.launch { - Log.w("$TAG Disconnecting call due to error [$message]") + // For some reason DisconnectCause.ERROR or DisconnectCause.BUSY triggers an IllegalArgumentException with following message + // Valid DisconnectCause codes are limited to [DisconnectCause.LOCAL, DisconnectCause.REMOTE, DisconnectCause.MISSED, or DisconnectCause.REJECTED] + val disconnectCause = DisconnectCause.REJECTED + Log.w("$TAG Disconnecting call with cause [${disconnectCauseToString(disconnectCause)}] due to error [$message] and reason [$reason]") try { - // For some reason DisconnectCause.ERROR triggers an IllegalArgumentException - callControl.disconnect(DisconnectCause(DisconnectCause.REJECTED)) + callControl.disconnect(DisconnectCause(disconnectCause)) } catch (ise: IllegalArgumentException) { - Log.e("$TAG Couldn't terminate call control with REJECTED cause: $ise") + Log.e("$TAG Couldn't disconnect call control with cause [${disconnectCauseToString(disconnectCause)}]: $ise") } } } else if (state == Call.State.Pausing) { @@ -260,4 +282,23 @@ class TelecomCallControlCallback( } return false } + + private fun disconnectCauseToString(cause: Int): String { + return when (cause) { + DisconnectCause.UNKNOWN -> "UNKNOWN" + DisconnectCause.ERROR -> "ERROR" + DisconnectCause.LOCAL -> "LOCAL" + DisconnectCause.REMOTE -> "REMOTE" + DisconnectCause.CANCELED -> "CANCELED" + DisconnectCause.MISSED -> "MISSED" + DisconnectCause.REJECTED -> "REJECTED" + DisconnectCause.BUSY -> "BUSY" + DisconnectCause.RESTRICTED -> "RESTRICTED" + DisconnectCause.OTHER -> "OTHER" + DisconnectCause.CONNECTION_MANAGER_NOT_SUPPORTED -> "CONNECTION_MANAGER_NOT_SUPPORTED" + DisconnectCause.ANSWERED_ELSEWHERE -> "ANSWERED_ELSEWHERE" + DisconnectCause.CALL_PULLED -> "CALL_PULLED" + else -> "UNEXPECTED: $cause" + } + } } From 6121040bda2e6768de4972340f66eb8816a4993c Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Wed, 29 Jan 2025 15:38:45 +0100 Subject: [PATCH 06/89] Fixed missing conversations until tab was left & opened again --- .../chat/viewmodel/ConversationsListViewModel.kt | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationsListViewModel.kt b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationsListViewModel.kt index e5e69fb99..996571137 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationsListViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationsListViewModel.kt @@ -76,7 +76,17 @@ class ConversationsListViewModel chatRoom: ChatRoom, messages: Array ) { - reorderChatRooms() + val id = LinphoneUtils.getChatRoomId(chatRoom) + val found = conversations.value.orEmpty().find { + it.id == id + } + if (found == null) { + Log.i("$TAG Message(s) received for a conversation not yet in the list (probably was empty), adding it") + addChatRoom(chatRoom) + } else { + Log.i("$TAG Message(s) received for an existing conversation, re-order them") + reorderChatRooms() + } } } @@ -170,7 +180,8 @@ class ConversationsListViewModel } val hideEmptyChatRooms = coreContext.core.config.getBool("misc", "hide_empty_chat_rooms", true) - if (hideEmptyChatRooms && chatRoom.lastMessageInHistory == null) { + // Hide empty chat rooms only applies to 1-1 conversations + if (hideEmptyChatRooms && !LinphoneUtils.isChatRoomAGroup(chatRoom) && chatRoom.lastMessageInHistory == null) { Log.w("$TAG Chat room with local address [${localAddress.asStringUriOnly()}] and peer address [${peerAddress.asStringUriOnly()}] is empty, not adding it to match Core setting") return } From 28998d4463991a6d3a1fdd3f1e3ee8dcfbc2b55a Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Wed, 29 Jan 2025 15:43:19 +0100 Subject: [PATCH 07/89] Fixed total unread message count if deleted chat room contained unread messages --- .../ui/main/viewmodel/AbstractMainViewModel.kt | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/src/main/java/org/linphone/ui/main/viewmodel/AbstractMainViewModel.kt b/app/src/main/java/org/linphone/ui/main/viewmodel/AbstractMainViewModel.kt index 4706be783..4835ab985 100644 --- a/app/src/main/java/org/linphone/ui/main/viewmodel/AbstractMainViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/viewmodel/AbstractMainViewModel.kt @@ -113,6 +113,17 @@ open class AbstractMainViewModel } } + @WorkerThread + override fun onChatRoomStateChanged( + core: Core, + chatRoom: ChatRoom, + state: ChatRoom.State? + ) { + if (state == ChatRoom.State.Deleted) { + computeUnreadMessagesCount() + } + } + @WorkerThread override fun onMessagesReceived( core: Core, From 5e6186d1150d5d203e0e457413d9e2535346f9c0 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Thu, 30 Jan 2025 09:48:51 +0100 Subject: [PATCH 08/89] Bumped dependencies --- gradle/libs.versions.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3be25fcd6..816f7154a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,7 +3,7 @@ agp = "8.8.0" kotlin = "2.0.21" gmsGoogleServices = "4.4.2" firebaseCrashlytics = "3.0.2" -firebaseBomVersion = "33.7.0" +firebaseBomVersion = "33.8.0" ktlint = "12.1.2" annotations = "1.9.1" @@ -19,7 +19,7 @@ slidingpanelayout = "1.2.0" window = "1.3.0" gridlayout = "1.0.0" securityCryptoKtx = "1.1.0-alpha06" -navigation = "2.8.5" +navigation = "2.8.6" emoji2 = "1.5.0" car = "1.7.0-rc01" flexbox = "3.0.0" From 06252394770c25ded00a908438b5741907d9d4f0 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Thu, 30 Jan 2025 10:55:08 +0100 Subject: [PATCH 09/89] Conference listener onActiveSpeakerParticipantDevice signature has changed --- .../viewmodel/ConferenceViewModel.kt | 38 +++++++++++++------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/org/linphone/ui/call/conference/viewmodel/ConferenceViewModel.kt b/app/src/main/java/org/linphone/ui/call/conference/viewmodel/ConferenceViewModel.kt index 0b8ef5ecf..ea4132f2d 100644 --- a/app/src/main/java/org/linphone/ui/call/conference/viewmodel/ConferenceViewModel.kt +++ b/app/src/main/java/org/linphone/ui/call/conference/viewmodel/ConferenceViewModel.kt @@ -134,22 +134,36 @@ class ConferenceViewModel @WorkerThread override fun onActiveSpeakerParticipantDevice( conference: Conference, - participantDevice: ParticipantDevice + participantDevice: ParticipantDevice? ) { activeSpeaker.value?.isActiveSpeaker?.postValue(false) - val found = participantDevices.value.orEmpty().find { - it.device.address.equal(participantDevice.address) - } - if (found != null) { - Log.i("$TAG Newly active speaker participant is [${found.name}]") - found.isActiveSpeaker.postValue(true) - activeSpeaker.postValue(found!!) + if (participantDevice != null) { + val found = participantDevices.value.orEmpty().find { + it.device.address.equal(participantDevice.address) + } + if (found != null) { + Log.i("$TAG Newly active speaker participant is [${found.name}]") + found.isActiveSpeaker.postValue(true) + activeSpeaker.postValue(found!!) + } else { + Log.i("$TAG Failed to find actively speaking participant...") + val model = ConferenceParticipantDeviceModel(participantDevice) + model.isActiveSpeaker.postValue(true) + activeSpeaker.postValue(model) + } } else { - Log.i("$TAG Failed to find actively speaking participant...") - val model = ConferenceParticipantDeviceModel(participantDevice) - model.isActiveSpeaker.postValue(true) - activeSpeaker.postValue(model) + Log.w("$TAG Notified active speaker participant device is null, using first one that's not us") + val firstNotUs = participantDevices.value.orEmpty().find { + it.isMe == false + } + if (firstNotUs != null) { + Log.i("$TAG Newly active speaker participant is [${firstNotUs.name}]") + firstNotUs.isActiveSpeaker.postValue(true) + activeSpeaker.postValue(firstNotUs!!) + } else { + Log.i("$TAG No participant device that's not us found, expected if we're alone") + } } } From e5795ea05fb6c133181e68417d98f8d26da75f6e Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Mon, 3 Feb 2025 09:43:17 +0100 Subject: [PATCH 10/89] Prevent crash if composing is sent when chat room not initialized yet --- .../chat/viewmodel/SendMessageInConversationViewModel.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/SendMessageInConversationViewModel.kt b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/SendMessageInConversationViewModel.kt index 2c1a18e80..83c8e9c7d 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/SendMessageInConversationViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/SendMessageInConversationViewModel.kt @@ -191,6 +191,7 @@ class SendMessageInConversationViewModel @UiThread fun configureChatRoom(room: ChatRoom) { + Log.i("$TAG Chat room configured") chatRoom = room coreContext.postOnCoreThread { chatRoom.addListener(chatRoomListener) @@ -324,7 +325,9 @@ class SendMessageInConversationViewModel @UiThread fun notifyChatMessageIsBeingComposed() { coreContext.postOnCoreThread { - chatRoom.compose() + if (::chatRoom.isInitialized) { + chatRoom.compose() + } } } From 1c093882666d7b56fe65509f14df221f183b12bb Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Mon, 3 Feb 2025 11:54:15 +0100 Subject: [PATCH 11/89] Disable username/password fields when account has been created (until SMS is sent), fixed default values not loaded --- .../viewmodel/AccountCreationViewModel.kt | 14 ++++++++++---- .../res/layout/assistant_register_fragment.xml | 4 ++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/org/linphone/ui/assistant/viewmodel/AccountCreationViewModel.kt b/app/src/main/java/org/linphone/ui/assistant/viewmodel/AccountCreationViewModel.kt index c8e86ed21..d5bd307a1 100644 --- a/app/src/main/java/org/linphone/ui/assistant/viewmodel/AccountCreationViewModel.kt +++ b/app/src/main/java/org/linphone/ui/assistant/viewmodel/AccountCreationViewModel.kt @@ -33,6 +33,7 @@ import kotlinx.coroutines.withContext import org.json.JSONException import org.json.JSONObject import org.linphone.LinphoneApplication.Companion.coreContext +import org.linphone.LinphoneApplication.Companion.corePreferences import org.linphone.R import org.linphone.core.Account import org.linphone.core.AccountManagerServices @@ -82,6 +83,8 @@ class AccountCreationViewModel val showPassword = MutableLiveData() + val lockUsernameAndPassword = MutableLiveData() + val createEnabled = MediatorLiveData() val pushNotificationsAvailable = MutableLiveData() @@ -199,12 +202,11 @@ class AccountCreationViewModel if (account != null) { coreContext.core.removeAccount(account) } - createEnabled.postValue(true) } else -> { - createEnabled.postValue(true) } } + createEnabled.postValue(true) } } @@ -258,6 +260,7 @@ class AccountCreationViewModel init { operationInProgress.value = false + lockUsernameAndPassword.value = false coreContext.postOnCoreThread { core -> pushNotificationsAvailable.postValue(LinphoneUtils.arePushNotificationsAvailable(core)) @@ -469,15 +472,16 @@ class AccountCreationViewModel @WorkerThread private fun storeAccountInCore(identity: String) { - val passwordValue = password.value - val core = coreContext.core + core.loadConfigFromXml(corePreferences.linphoneDefaultValuesPath) + val sipIdentity = Factory.instance().createAddress(identity) if (sipIdentity == null) { Log.e("$TAG Failed to create address from SIP Identity [$identity]!") return } + val passwordValue = password.value // We need to have an AuthInfo for newly created account to authorize phone number linking request val authInfo = Factory.instance().createAuthInfo( sipIdentity.username.orEmpty(), @@ -506,6 +510,8 @@ class AccountCreationViewModel accountCreatedAuthInfo = authInfo accountCreated = account + + lockUsernameAndPassword.postValue(true) } @WorkerThread diff --git a/app/src/main/res/layout/assistant_register_fragment.xml b/app/src/main/res/layout/assistant_register_fragment.xml index f240a5bdf..4d1a4a193 100644 --- a/app/src/main/res/layout/assistant_register_fragment.xml +++ b/app/src/main/res/layout/assistant_register_fragment.xml @@ -90,7 +90,7 @@ Date: Mon, 27 Jan 2025 10:38:00 +0100 Subject: [PATCH 12/89] Fixed group call missed notification & in-call alert titles --- .../notifications/NotificationsManager.kt | 14 ++++++++++---- .../linphone/ui/main/viewmodel/MainViewModel.kt | 15 ++++++--------- app/src/main/res/values-fr/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 4 files changed, 18 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/org/linphone/notifications/NotificationsManager.kt b/app/src/main/java/org/linphone/notifications/NotificationsManager.kt index a39aef01d..0e3bd47a3 100644 --- a/app/src/main/java/org/linphone/notifications/NotificationsManager.kt +++ b/app/src/main/java/org/linphone/notifications/NotificationsManager.kt @@ -614,10 +614,16 @@ class NotificationsManager Log.i("$TAG Updating missed calls notification count to $missedCallCount") } else { val remoteAddress = call.callLog.remoteAddress - val friend: Friend? = coreContext.contactsManager.findContactByAddress(remoteAddress) - body = context.getString(R.string.notification_missed_call) - .format(friend?.name ?: LinphoneUtils.getDisplayName(remoteAddress)) - Log.i("$TAG Creating missed call notification") + val conferenceInfo = call.callLog.conferenceInfo + body = if (conferenceInfo != null) { + context.getString(R.string.notification_missed_group_call) + .format(conferenceInfo.subject ?: LinphoneUtils.getDisplayName(remoteAddress)) + } else { + val friend: Friend? = coreContext.contactsManager.findContactByAddress(remoteAddress) + context.getString(R.string.notification_missed_call) + .format(friend?.name ?: LinphoneUtils.getDisplayName(remoteAddress)) + } + Log.i("$TAG Creating missed call notification with title [$body]") } val pendingIntent = NavDeepLinkBuilder(context) diff --git a/app/src/main/java/org/linphone/ui/main/viewmodel/MainViewModel.kt b/app/src/main/java/org/linphone/ui/main/viewmodel/MainViewModel.kt index da7391924..b107cd8fb 100644 --- a/app/src/main/java/org/linphone/ui/main/viewmodel/MainViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/viewmodel/MainViewModel.kt @@ -472,17 +472,14 @@ class MainViewModel val currentCall = core.currentCall ?: core.calls.firstOrNull() if (currentCall != null) { val address = currentCall.callLog.remoteAddress - val contact = coreContext.contactsManager.findContactByAddress(address) - val label = if (contact != null) { - contact.name ?: LinphoneUtils.getDisplayName(address) + val conferenceInfo = LinphoneUtils.getConferenceInfoIfAny(currentCall) + val label = if (conferenceInfo != null) { + conferenceInfo.subject ?: LinphoneUtils.getDisplayName(address) } else { - val conferenceInfo = coreContext.core.findConferenceInformationFromUri( - address - ) - conferenceInfo?.subject ?: LinphoneUtils.getDisplayName( - address - ) + val contact = coreContext.contactsManager.findContactByAddress(address) + contact?.name ?: LinphoneUtils.getDisplayName(address) } + Log.i("$TAG Showing single call alert with label [$label]") addAlert(SINGLE_CALL, label) callsStatus.postValue(LinphoneUtils.callStateToString(currentCall.state)) } diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index fc1f5669c..c88fb10dc 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -54,6 +54,7 @@ Marquer comme lu Répondre Appel manqué de %s + Appel de groupe manqué : %s %s appels manqués Appel manqué &appName; diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d68214cf8..f23abecea 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -92,6 +92,7 @@ Mark as read Reply Missed call from %s + Missed group call: %s %s missed calls Missed call &appName; From 2952b2db017f9147c4228440b6c4008f6aaf120d Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Mon, 3 Feb 2025 14:13:34 +0100 Subject: [PATCH 13/89] Updated CHANGELOG --- CHANGELOG.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4469ba211..55455ece5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ Group changes to describe their impact on the project, as follows: Security to invite users to upgrade in case of vulnerabilities. -## [6.0.0] - 2024-??-?? +## [6.0.0] - 2025-02-?? 6.0.0 release is a complete rework of Linphone Android, with a fully redesigned UI, so it is impossible to list everything here. @@ -20,30 +20,35 @@ Group changes to describe their impact on the project, as follows: - Asymmetrical video : you no longer need to send your own camera feed to receive the one from the remote end of the call, and vice versa. - Improved multi account: you'll only see history, conversations, meetings etc... related to currently selected account, and you can switch the default account in two clicks. - Call transfer: Blind & Attended call transfer have been merged into one: during a call, if you initiate a transfer action, either pick another call to do the attended transfer or select a contact from the list (you can input a SIP URI not already in the suggestions list) to start a blind transfer. +- User can only send up to 12 files in a single chat message. - Settings: a lot of them are gone, the one that are still there have been reworked to increase user friendliness. - Default screen (between contacts, call history, conversations & meetings list) will change depending on where you were when the app was paused or killed, and you will return to that last visited screen on the next startup. - Gradle files have been migrated from Groovy to Kotlin DSL, and dependencies are now in a separated file (libs.versions.toml). - Account creation no longer allows you to use your phone number as username, but it is still required to provide it to receive activation code by SMS. - Minimum supported Android OS version is now 9 (API level 28). - Telecom Manager support is now based on androidx.core.core-telecom package. +- Some settings have changed name and/or section in linphonerc file. ### Added -- Contacts trust: contacts for which all devices have been validated through a ZRTP call with SAS exchange are now highlighted with a blue circle (and with a red one in case of mistrust). That trust is now handled at contact level (instead of conversation level in previous versions). +- Contacts trust: contacts for which all devices have been validated through a ZRTP call with SAS exchange are now highlighted with a blue circle (and with a red one in case of mistrust). That trust is now handled at contact level (instead of conversation level in previous versions). - Media & documents exchanged in a conversation can be easily found through a dedicated screen. - A brand new chat message search feature has been added to conversations. - You can now react to a chat message using any emoji. - If next message is also a voice recording, playback will automatically start after the currently playing one ends. - Chat while in call: a shortcut to a conversation screen with the remote. - Chat while in a conference: if the conference has a text stream enabled, you can chat with the other participants of the conference while it lasts. At the end, you'll find the messages history in the call history (and not in the list of conversations). +- Notification showing upload/download of files shared through chat will let user know the progress and keep the app alive during that process. - Screen sharing in conference: only desktop app starting with 6.0 version is able to start it, but on mobiles you'll be able to see it. - You can choose whatever ringtone you'd like for incoming calls (in Android notification channel settings). - Security focus: security & trust is more visible than ever, and unsecure conversations & calls are even more visible than before. - CardDAV: you can configure as many CardDAV servers you want to synchronize you contacts in Linphone (in addition or in replacement of native addressbook import). - OpenID: when used with a SSO compliant SIP server (such as Flexisip), we support single-sign-on login. - MWI support: display and allow to call your voicemail when you have new messages (if supported by your VoIP provider and properly configured in your account params). +- CCMP support: if you configure a CCMP server URL in your accounts params, it will be used when scheduling meetings & to fetch list of meetings you've organized/been invited to. - Devices list: check on which device your sip.linphone.org account is connected and the last connection date & time (like on subscribe.linphone.org). - Protobuf dependency to allow logging native crashes stack traces at next app startup. - Android 15 startup listener, allowing us to log type of start (cold, warm, etc...) and some other useful info. +- Dialer & in-call numpad show letters under the digit. ### Removed - Dialer: the previous home screen (dialer) has been removed, you'll find it as an input option in the new start call screen. From da385ee6e1722ba55613374b1bf0e8b19ee4faa9 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Mon, 3 Feb 2025 15:52:42 +0100 Subject: [PATCH 14/89] Added short toast when user starts recording a call --- .../fragment/ActiveConferenceCallFragment.kt | 3 ++ .../ui/call/fragment/ActiveCallFragment.kt | 2 ++ .../ui/call/fragment/EndedCallFragment.kt | 1 + .../ui/call/fragment/IncomingCallFragment.kt | 1 + .../ui/call/fragment/OutgoingCallFragment.kt | 1 + .../ui/call/fragment/TransferCallFragment.kt | 2 ++ .../ui/call/viewmodel/CurrentCallViewModel.kt | 28 +++++++++++++++++-- app/src/main/res/values-fr/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 9 files changed, 38 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/linphone/ui/call/conference/fragment/ActiveConferenceCallFragment.kt b/app/src/main/java/org/linphone/ui/call/conference/fragment/ActiveConferenceCallFragment.kt index 078afa3b6..7a011f820 100644 --- a/app/src/main/java/org/linphone/ui/call/conference/fragment/ActiveConferenceCallFragment.kt +++ b/app/src/main/java/org/linphone/ui/call/conference/fragment/ActiveConferenceCallFragment.kt @@ -125,10 +125,13 @@ class ActiveConferenceCallFragment : GenericCallFragment() { callViewModel = requireActivity().run { ViewModelProvider(this)[CurrentCallViewModel::class.java] } + observeToastEvents(callViewModel) + observeToastEvents(callViewModel.conferenceModel) callsViewModel = requireActivity().run { ViewModelProvider(this)[CallsViewModel::class.java] } + observeToastEvents(callsViewModel) binding.lifecycleOwner = viewLifecycleOwner binding.viewModel = callViewModel 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 72ed16cc2..4a188201a 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 @@ -154,10 +154,12 @@ class ActiveCallFragment : GenericCallFragment() { callViewModel = requireActivity().run { ViewModelProvider(this)[CurrentCallViewModel::class.java] } + observeToastEvents(callViewModel) callsViewModel = requireActivity().run { ViewModelProvider(this)[CallsViewModel::class.java] } + observeToastEvents(callsViewModel) binding.lifecycleOwner = viewLifecycleOwner binding.viewModel = callViewModel diff --git a/app/src/main/java/org/linphone/ui/call/fragment/EndedCallFragment.kt b/app/src/main/java/org/linphone/ui/call/fragment/EndedCallFragment.kt index 82f58994c..202ad5527 100644 --- a/app/src/main/java/org/linphone/ui/call/fragment/EndedCallFragment.kt +++ b/app/src/main/java/org/linphone/ui/call/fragment/EndedCallFragment.kt @@ -67,6 +67,7 @@ class EndedCallFragment : GenericCallFragment() { callViewModel = requireActivity().run { ViewModelProvider(this)[CurrentCallViewModel::class.java] } + observeToastEvents(callViewModel) binding.lifecycleOwner = viewLifecycleOwner binding.viewModel = callViewModel diff --git a/app/src/main/java/org/linphone/ui/call/fragment/IncomingCallFragment.kt b/app/src/main/java/org/linphone/ui/call/fragment/IncomingCallFragment.kt index 2f9050bba..ea5f9ad57 100644 --- a/app/src/main/java/org/linphone/ui/call/fragment/IncomingCallFragment.kt +++ b/app/src/main/java/org/linphone/ui/call/fragment/IncomingCallFragment.kt @@ -55,6 +55,7 @@ class IncomingCallFragment : GenericCallFragment() { callViewModel = requireActivity().run { ViewModelProvider(this)[CurrentCallViewModel::class.java] } + observeToastEvents(callViewModel) binding.lifecycleOwner = viewLifecycleOwner binding.viewModel = callViewModel diff --git a/app/src/main/java/org/linphone/ui/call/fragment/OutgoingCallFragment.kt b/app/src/main/java/org/linphone/ui/call/fragment/OutgoingCallFragment.kt index 954ef7ea4..e4b0bdbd3 100644 --- a/app/src/main/java/org/linphone/ui/call/fragment/OutgoingCallFragment.kt +++ b/app/src/main/java/org/linphone/ui/call/fragment/OutgoingCallFragment.kt @@ -56,6 +56,7 @@ class OutgoingCallFragment : GenericCallFragment() { callViewModel = requireActivity().run { ViewModelProvider(this)[CurrentCallViewModel::class.java] } + observeToastEvents(callViewModel) binding.lifecycleOwner = viewLifecycleOwner binding.viewModel = callViewModel diff --git a/app/src/main/java/org/linphone/ui/call/fragment/TransferCallFragment.kt b/app/src/main/java/org/linphone/ui/call/fragment/TransferCallFragment.kt index 1aad00078..dc1209182 100644 --- a/app/src/main/java/org/linphone/ui/call/fragment/TransferCallFragment.kt +++ b/app/src/main/java/org/linphone/ui/call/fragment/TransferCallFragment.kt @@ -115,10 +115,12 @@ class TransferCallFragment : GenericCallFragment() { callViewModel = requireActivity().run { ViewModelProvider(this)[CurrentCallViewModel::class.java] } + observeToastEvents(callViewModel) callsViewModel = requireActivity().run { ViewModelProvider(this)[CallsViewModel::class.java] } + observeToastEvents(callsViewModel) binding.viewModel = viewModel binding.callsViewModel = callsViewModel diff --git a/app/src/main/java/org/linphone/ui/call/viewmodel/CurrentCallViewModel.kt b/app/src/main/java/org/linphone/ui/call/viewmodel/CurrentCallViewModel.kt index a5b7bab9d..0bc677b1b 100644 --- a/app/src/main/java/org/linphone/ui/call/viewmodel/CurrentCallViewModel.kt +++ b/app/src/main/java/org/linphone/ui/call/viewmodel/CurrentCallViewModel.kt @@ -358,7 +358,11 @@ class CurrentCallViewModel videoUpdateInProgress.postValue(false) updateCallDuration() if (corePreferences.automaticallyStartCallRecording) { - isRecording.postValue(call.params.isRecording) + val recording = call.params.isRecording + isRecording.postValue(recording) + if (recording) { + showRecordingToast() + } } // MediaEncryption None & SRTP won't be notified through onEncryptionChanged callback, @@ -855,8 +859,12 @@ class CurrentCallViewModel Log.i("$TAG Starting call recording") currentCall.startRecording() } + val recording = currentCall.params.isRecording isRecording.postValue(recording) + if (recording) { + showRecordingToast() + } } } } @@ -1172,7 +1180,11 @@ class CurrentCallViewModel contact.postValue(model) displayedName.postValue(model.friend.name) - isRecording.postValue(call.params.isRecording) + val recording = call.params.isRecording + isRecording.postValue(recording) + if (recording) { + showRecordingToast() + } val isRemoteRecording = call.remoteParams?.isRecording == true if (isRemoteRecording) { @@ -1471,4 +1483,16 @@ class CurrentCallViewModel } } } + + @AnyThread + private fun showRecordingToast() { + showGreenToastEvent.postValue( + Event( + Pair( + R.string.call_is_being_recorded, + R.drawable.record_fill + ) + ) + ) + } } diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index c88fb10dc..1661a0b6b 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -633,6 +633,7 @@ Appel chiffré de point à point Appel non chiffré Liste des appels + L\'appel est enregistré %s enregistre l\'appel %s appels %s appels en pause diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f23abecea..9d59cdcef 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -673,6 +673,7 @@ Point-to-point encrypted by SRTP Call is not encrypted Calls list + Call is being recorded %s is recording %s calls %s paused calls From 00a7d34509de26c7348a7f49b704dfc5eb77aade Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Mon, 3 Feb 2025 16:33:11 +0100 Subject: [PATCH 15/89] Fixed call history list filter not using resolved contact name --- .../main/history/viewmodel/HistoryListViewModel.kt | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/linphone/ui/main/history/viewmodel/HistoryListViewModel.kt b/app/src/main/java/org/linphone/ui/main/history/viewmodel/HistoryListViewModel.kt index 13ed58d57..c5fd7304c 100644 --- a/app/src/main/java/org/linphone/ui/main/history/viewmodel/HistoryListViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/history/viewmodel/HistoryListViewModel.kt @@ -144,8 +144,8 @@ class HistoryListViewModel val account = LinphoneUtils.getDefaultAccount() val logs = account?.callLogs ?: coreContext.core.callLogs for (callLog in logs) { - if (callLog.remoteAddress.asStringUriOnly().contains(filter)) { - val model = CallLogModel(callLog) + val model = CallLogModel(callLog) + if (isCallLogMatchingFilter(model, filter)) { list.add(model) count += 1 } @@ -158,4 +158,12 @@ class HistoryListViewModel Log.i("$TAG Fetched [${list.size}] call log(s)") callLogs.postValue(list) } + + @WorkerThread + private fun isCallLogMatchingFilter(model: CallLogModel, filter: String): Boolean { + if (filter.isEmpty()) return true + + val friendName = model.avatarModel.friend.name ?: LinphoneUtils.getDisplayName(model.address) + return friendName.contains(filter, ignoreCase = true) || model.address.asStringUriOnly().contains(filter, ignoreCase = true) + } } From 63051ae58e7f38ff98f84cc9c41bf478097b32ae Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Mon, 3 Feb 2025 16:44:28 +0100 Subject: [PATCH 16/89] Show who terminated a call in fragment title --- .../ui/call/fragment/EndedCallFragment.kt | 8 +------ .../ui/call/viewmodel/CurrentCallViewModel.kt | 6 ++--- .../res/layout-land/call_ended_fragment.xml | 24 +------------------ .../main/res/layout/call_ended_fragment.xml | 24 +------------------ app/src/main/res/values-fr/strings.xml | 3 ++- app/src/main/res/values/strings.xml | 3 ++- 6 files changed, 10 insertions(+), 58 deletions(-) diff --git a/app/src/main/java/org/linphone/ui/call/fragment/EndedCallFragment.kt b/app/src/main/java/org/linphone/ui/call/fragment/EndedCallFragment.kt index 202ad5527..2f6baffc8 100644 --- a/app/src/main/java/org/linphone/ui/call/fragment/EndedCallFragment.kt +++ b/app/src/main/java/org/linphone/ui/call/fragment/EndedCallFragment.kt @@ -20,7 +20,6 @@ package org.linphone.ui.call.fragment import android.os.Bundle -import android.os.SystemClock import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -73,11 +72,6 @@ class EndedCallFragment : GenericCallFragment() { binding.viewModel = callViewModel Log.i("$TAG Showing ended call fragment") - - callViewModel.callDuration.observe(viewLifecycleOwner) { duration -> - binding.chronometer.base = SystemClock.elapsedRealtime() - (1000 * duration) - binding.chronometer.stop() // Do not start it and make sure it is stopped - } } override fun onResume() { @@ -85,7 +79,7 @@ class EndedCallFragment : GenericCallFragment() { lifecycleScope.launch { withContext(Dispatchers.IO) { - if (callViewModel.terminatedByUsed) { + if (callViewModel.terminatedByUser) { Log.i( "$TAG Call terminated by user, waiting 1 second before finishing activity" ) diff --git a/app/src/main/java/org/linphone/ui/call/viewmodel/CurrentCallViewModel.kt b/app/src/main/java/org/linphone/ui/call/viewmodel/CurrentCallViewModel.kt index 0bc677b1b..7a56c5295 100644 --- a/app/src/main/java/org/linphone/ui/call/viewmodel/CurrentCallViewModel.kt +++ b/app/src/main/java/org/linphone/ui/call/viewmodel/CurrentCallViewModel.kt @@ -143,7 +143,7 @@ class CurrentCallViewModel val qualityIcon = MutableLiveData() - var terminatedByUsed = false + var terminatedByUser = false val isRemoteRecordingEvent: MutableLiveData>> by lazy { MutableLiveData>>() @@ -616,7 +616,7 @@ class CurrentCallViewModel coreContext.postOnCoreThread { if (::currentCall.isInitialized) { Log.i("$TAG Terminating call [${currentCall.remoteAddress.asStringUriOnly()}]") - terminatedByUsed = true + terminatedByUser = true coreContext.terminateCall(currentCall) } } @@ -1060,7 +1060,7 @@ class CurrentCallViewModel ) contact.value?.destroy() - terminatedByUsed = false + terminatedByUser = false currentCall = call callStatsModel.update(call, call.audioStats) callMediaEncryptionModel.update(call) diff --git a/app/src/main/res/layout-land/call_ended_fragment.xml b/app/src/main/res/layout-land/call_ended_fragment.xml index a3229a2eb..bef0449ff 100644 --- a/app/src/main/res/layout-land/call_ended_fragment.xml +++ b/app/src/main/res/layout-land/call_ended_fragment.xml @@ -33,33 +33,11 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="10dp" - android:text="@string/call_ended" + android:text="@{viewModel.terminatedByUser ? @string/call_locally_ended : @string/call_remotely_ended, default=@string/call_locally_ended}" app:layout_constraintStart_toEndOf="@id/back" app:layout_constraintTop_toTopOf="@id/back" app:layout_constraintBottom_toBottomOf="@id/back"/> - - - - - - - - Appel sortant Appel entrant Appel vidéo entrant - Appel terminé + Vous avez terminé l\'appel + Votre correspondant a terminé l\'appel Appel entrant pour %s Appel vidéo entrant pour %s Transférer %s à… diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9d59cdcef..4a3daa79f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -639,7 +639,8 @@ Outgoing call Incoming call Incoming video call - Call ended + You have ended the call + Correspondent has ended the call Incoming call for %s Incoming video call for %s Transfer %s to… From b057163b431683e50022e45027487eb3b642bcf6 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Mon, 3 Feb 2025 17:15:28 +0100 Subject: [PATCH 17/89] Removed useless back button --- .../res/layout-land/call_ended_fragment.xml | 22 +++++-------------- .../layout-land/call_outgoing_fragment.xml | 16 +------------- .../main/res/layout/call_ended_fragment.xml | 22 +++++-------------- .../res/layout/call_outgoing_fragment.xml | 1 - 4 files changed, 11 insertions(+), 50 deletions(-) diff --git a/app/src/main/res/layout-land/call_ended_fragment.xml b/app/src/main/res/layout-land/call_ended_fragment.xml index bef0449ff..2a8e0554a 100644 --- a/app/src/main/res/layout-land/call_ended_fragment.xml +++ b/app/src/main/res/layout-land/call_ended_fragment.xml @@ -15,38 +15,26 @@ android:layout_height="match_parent" android:background="@color/gray_900"> - - + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent"/> - - + app:layout_constraintTop_toTopOf="parent"/> - - + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent"/> From 536667cfe1e9be7cd464e703d916bcedb394ade7 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Tue, 4 Feb 2025 12:12:12 +0100 Subject: [PATCH 18/89] Prevent attach file button in conversation to be disabled after sending max number of attachments --- .../ui/main/chat/viewmodel/SendMessageInConversationViewModel.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/SendMessageInConversationViewModel.kt b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/SendMessageInConversationViewModel.kt index 83c8e9c7d..eaf106b5b 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/SendMessageInConversationViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/SendMessageInConversationViewModel.kt @@ -319,6 +319,7 @@ class SendMessageInConversationViewModel attachments.postValue(attachmentsList) chatMessageToReplyTo = null + maxNumberOfAttachmentsReached.postValue(false) } } From 74394445c98d01259c555d14bfa545741bebd16a Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Tue, 4 Feb 2025 13:53:49 +0100 Subject: [PATCH 19/89] Improved toast API --- .../java/org/linphone/ui/GenericViewModel.kt | 18 ++++++++ .../viewmodel/AccountCreationViewModel.kt | 9 +--- .../viewmodel/AccountLoginViewModel.kt | 18 +------- .../ThirdPartySipAccountLoginViewModel.kt | 9 +--- .../viewmodel/ConferenceViewModel.kt | 18 +------- .../ui/call/viewmodel/CallsViewModel.kt | 9 +--- .../ui/call/viewmodel/CurrentCallViewModel.kt | 9 +--- .../ui/fileviewer/viewmodel/FileViewModel.kt | 42 ++--------------- .../ConversationForwardMessageFragment.kt | 7 +-- .../AbstractConversationViewModel.kt | 18 +------- .../ConversationForwardMessageViewModel.kt | 27 ++--------- .../viewmodel/ConversationInfoViewModel.kt | 46 ++++--------------- .../chat/viewmodel/ConversationViewModel.kt | 24 ++-------- .../viewmodel/ConversationsListViewModel.kt | 7 +-- .../SendMessageInConversationViewModel.kt | 28 ++--------- .../viewmodel/ContactNewOrEditViewModel.kt | 9 +--- .../viewmodel/ContactsListViewModel.kt | 4 +- .../history/viewmodel/StartCallViewModel.kt | 18 +------- .../viewmodel/ScheduleMeetingViewModel.kt | 36 ++------------- .../RecordingMediaPlayerViewModel.kt | 7 +-- .../viewmodel/AccountProfileViewModel.kt | 9 +--- .../settings/viewmodel/CardDavViewModel.kt | 36 ++------------- .../main/settings/viewmodel/LdapViewModel.kt | 15 +----- .../settings/viewmodel/SettingsViewModel.kt | 2 +- 24 files changed, 70 insertions(+), 355 deletions(-) diff --git a/app/src/main/java/org/linphone/ui/GenericViewModel.kt b/app/src/main/java/org/linphone/ui/GenericViewModel.kt index 5ca903d01..b31e4ef38 100644 --- a/app/src/main/java/org/linphone/ui/GenericViewModel.kt +++ b/app/src/main/java/org/linphone/ui/GenericViewModel.kt @@ -19,6 +19,8 @@ */ package org.linphone.ui +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import org.linphone.utils.Event @@ -41,4 +43,20 @@ open class GenericViewModel : ViewModel() { val showFormattedRedToastEvent: MutableLiveData>> by lazy { MutableLiveData>>() } + + fun showGreenToast(@StringRes message: Int, @DrawableRes icon: Int) { + showGreenToastEvent.postValue(Event(Pair(message, icon))) + } + + fun showFormattedGreenToast(message: String, @DrawableRes icon: Int) { + showFormattedGreenToastEvent.postValue(Event(Pair(message, icon))) + } + + fun showRedToast(@StringRes message: Int, @DrawableRes icon: Int) { + showRedToastEvent.postValue(Event(Pair(message, icon))) + } + + fun showFormattedRedToast(message: String, @DrawableRes icon: Int) { + showFormattedRedToastEvent.postValue(Event(Pair(message, icon))) + } } diff --git a/app/src/main/java/org/linphone/ui/assistant/viewmodel/AccountCreationViewModel.kt b/app/src/main/java/org/linphone/ui/assistant/viewmodel/AccountCreationViewModel.kt index d5bd307a1..493470f9f 100644 --- a/app/src/main/java/org/linphone/ui/assistant/viewmodel/AccountCreationViewModel.kt +++ b/app/src/main/java/org/linphone/ui/assistant/viewmodel/AccountCreationViewModel.kt @@ -168,14 +168,7 @@ class AccountCreationViewModel operationInProgress.postValue(false) if (!errorMessage.isNullOrEmpty()) { - showFormattedRedToastEvent.postValue( - Event( - Pair( - errorMessage, - R.drawable.warning_circle - ) - ) - ) + showFormattedRedToast(errorMessage, R.drawable.warning_circle) } for (parameter in parameterErrors?.keys.orEmpty()) { diff --git a/app/src/main/java/org/linphone/ui/assistant/viewmodel/AccountLoginViewModel.kt b/app/src/main/java/org/linphone/ui/assistant/viewmodel/AccountLoginViewModel.kt index 629d73df5..285b0c42f 100644 --- a/app/src/main/java/org/linphone/ui/assistant/viewmodel/AccountLoginViewModel.kt +++ b/app/src/main/java/org/linphone/ui/assistant/viewmodel/AccountLoginViewModel.kt @@ -175,14 +175,7 @@ open class AccountLoginViewModel val identityAddress = Factory.instance().createAddress(identity) if (identityAddress == null) { Log.e("$TAG Can't parse [$identity] as Address!") - showRedToastEvent.postValue( - Event( - Pair( - R.string.assistant_login_cant_parse_address_toast, - R.drawable.warning_circle - ) - ) - ) + showRedToast(R.string.assistant_login_cant_parse_address_toast, R.drawable.warning_circle) return@postOnCoreThread } @@ -191,14 +184,7 @@ open class AccountLoginViewModel Log.e( "$TAG Address [${identityAddress.asStringUriOnly()}] doesn't contains an username!" ) - showRedToastEvent.postValue( - Event( - Pair( - R.string.assistant_login_address_without_username_toast, - R.drawable.warning_circle - ) - ) - ) + showRedToast(R.string.assistant_login_address_without_username_toast, R.drawable.warning_circle) return@postOnCoreThread } diff --git a/app/src/main/java/org/linphone/ui/assistant/viewmodel/ThirdPartySipAccountLoginViewModel.kt b/app/src/main/java/org/linphone/ui/assistant/viewmodel/ThirdPartySipAccountLoginViewModel.kt index 40a33b649..0e193edd8 100644 --- a/app/src/main/java/org/linphone/ui/assistant/viewmodel/ThirdPartySipAccountLoginViewModel.kt +++ b/app/src/main/java/org/linphone/ui/assistant/viewmodel/ThirdPartySipAccountLoginViewModel.kt @@ -199,14 +199,7 @@ class ThirdPartySipAccountLoginViewModel val identityAddress = Factory.instance().createAddress(identity) if (identityAddress == null) { Log.e("$TAG Can't parse [$identity] as Address!") - showRedToastEvent.postValue( - Event( - Pair( - R.string.assistant_login_cant_parse_address_toast, - R.drawable.warning_circle - ) - ) - ) + showRedToast(R.string.assistant_login_cant_parse_address_toast, R.drawable.warning_circle) return@postOnCoreThread } diff --git a/app/src/main/java/org/linphone/ui/call/conference/viewmodel/ConferenceViewModel.kt b/app/src/main/java/org/linphone/ui/call/conference/viewmodel/ConferenceViewModel.kt index ea4132f2d..6bfc4d5b1 100644 --- a/app/src/main/java/org/linphone/ui/call/conference/viewmodel/ConferenceViewModel.kt +++ b/app/src/main/java/org/linphone/ui/call/conference/viewmodel/ConferenceViewModel.kt @@ -410,14 +410,7 @@ class ConferenceViewModel Log.e( "$TAG Failed to parse SIP URI [$uri] into address, can't add it to the conference!" ) - showRedToastEvent.postValue( - Event( - Pair( - R.string.conference_failed_to_add_participant_invalid_address_toast, - R.drawable.warning_circle - ) - ) - ) + showRedToast(R.string.conference_failed_to_add_participant_invalid_address_toast, R.drawable.warning_circle) } } val addressesArray = arrayOfNulls
(addresses.size) @@ -810,14 +803,7 @@ class ConferenceViewModel "$TAG Too many participant devices for grid layout, switching to active speaker layout" ) setNewLayout(ACTIVE_SPEAKER_LAYOUT) - showRedToastEvent.postValue( - Event( - Pair( - R.string.conference_too_many_participants_for_mosaic_layout_toast, - R.drawable.warning_circle - ) - ) - ) + showRedToast(R.string.conference_too_many_participants_for_mosaic_layout_toast, R.drawable.warning_circle) } } } diff --git a/app/src/main/java/org/linphone/ui/call/viewmodel/CallsViewModel.kt b/app/src/main/java/org/linphone/ui/call/viewmodel/CallsViewModel.kt index db2cd71d1..f0480543c 100644 --- a/app/src/main/java/org/linphone/ui/call/viewmodel/CallsViewModel.kt +++ b/app/src/main/java/org/linphone/ui/call/viewmodel/CallsViewModel.kt @@ -231,14 +231,7 @@ class CallsViewModel val conference = LinphoneUtils.createGroupCall(defaultAccount, subject) if (conference == null) { Log.e("$TAG Failed to create conference!") - showRedToastEvent.postValue( - Event( - Pair( - R.string.conference_failed_to_merge_calls_into_conference_toast, - R.drawable.warning_circle - ) - ) - ) + showRedToast(R.string.conference_failed_to_merge_calls_into_conference_toast, R.drawable.warning_circle) } else { conference.addParticipants(core.calls) } diff --git a/app/src/main/java/org/linphone/ui/call/viewmodel/CurrentCallViewModel.kt b/app/src/main/java/org/linphone/ui/call/viewmodel/CurrentCallViewModel.kt index 7a56c5295..6b6052fef 100644 --- a/app/src/main/java/org/linphone/ui/call/viewmodel/CurrentCallViewModel.kt +++ b/app/src/main/java/org/linphone/ui/call/viewmodel/CurrentCallViewModel.kt @@ -1486,13 +1486,6 @@ class CurrentCallViewModel @AnyThread private fun showRecordingToast() { - showGreenToastEvent.postValue( - Event( - Pair( - R.string.call_is_being_recorded, - R.drawable.record_fill - ) - ) - ) + showGreenToast(R.string.call_is_being_recorded, R.drawable.record_fill) } } diff --git a/app/src/main/java/org/linphone/ui/fileviewer/viewmodel/FileViewModel.kt b/app/src/main/java/org/linphone/ui/fileviewer/viewmodel/FileViewModel.kt index 31e4e6266..a2fb1bb25 100644 --- a/app/src/main/java/org/linphone/ui/fileviewer/viewmodel/FileViewModel.kt +++ b/app/src/main/java/org/linphone/ui/fileviewer/viewmodel/FileViewModel.kt @@ -239,24 +239,10 @@ class FileViewModel Log.i( "$TAG File [$filePath] has been successfully exported to documents" ) - showGreenToastEvent.postValue( - Event( - Pair( - R.string.file_successfully_exported_to_documents_toast, - R.drawable.check - ) - ) - ) + showGreenToast(R.string.file_successfully_exported_to_documents_toast, R.drawable.check) } else { Log.e("$TAG Failed to export file [$filePath] to documents!") - showRedToastEvent.postValue( - Event( - Pair( - R.string.export_file_to_documents_error_toast, - R.drawable.warning_circle - ) - ) - ) + showRedToast(R.string.export_file_to_documents_error_toast, R.drawable.warning_circle) } } } @@ -272,24 +258,13 @@ class FileViewModel Log.i( "$TAG Text has been successfully exported to documents" ) - showGreenToastEvent.postValue( - Event( - Pair( + showGreenToast( R.string.file_successfully_exported_to_documents_toast, R.drawable.check ) - ) - ) } else { Log.e("$TAG Failed to save text to documents!") - showRedToastEvent.postValue( - Event( - Pair( - R.string.export_file_to_documents_error_toast, - R.drawable.warning_circle - ) - ) - ) + showRedToast(R.string.export_file_to_documents_error_toast, R.drawable.warning_circle) } } } @@ -337,14 +312,7 @@ class FileViewModel // TODO FIXME : improve performances ! } catch (e: Exception) { Log.e("$TAG Exception trying to read file [$filePath] as text: $e") - showRedToastEvent.postValue( - Event( - Pair( - R.string.conversation_file_cant_be_opened_error_toast, - R.drawable.warning_circle - ) - ) - ) + showRedToast(R.string.conversation_file_cant_be_opened_error_toast, R.drawable.warning_circle) } } } diff --git a/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationForwardMessageFragment.kt b/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationForwardMessageFragment.kt index 71ceb7b56..a451df537 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationForwardMessageFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationForwardMessageFragment.kt @@ -38,7 +38,6 @@ import org.linphone.ui.main.contacts.model.ContactNumberOrAddressModel import org.linphone.ui.main.contacts.model.NumberOrAddressPickerDialogModel import org.linphone.ui.main.fragment.SlidingPaneChildFragment import org.linphone.utils.DialogUtils -import org.linphone.utils.Event import org.linphone.utils.RecyclerViewHeaderDecoration @UiThread @@ -66,11 +65,7 @@ class ConversationForwardMessageFragment : SlidingPaneChildFragment() { override fun goBack(): Boolean { sharedViewModel.messageToForwardEvent.value?.consume { Log.w("$TAG Cancelling message forward") - viewModel.showRedToastEvent.postValue( - Event( - Pair(R.string.conversation_message_forward_cancelled_toast, R.drawable.forward) - ) - ) + viewModel.showRedToast(R.string.conversation_message_forward_cancelled_toast, R.drawable.forward) } return findNavController().popBackStack() diff --git a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/AbstractConversationViewModel.kt b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/AbstractConversationViewModel.kt index ca647937a..a5a9f1098 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/AbstractConversationViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/AbstractConversationViewModel.kt @@ -173,14 +173,7 @@ abstract class AbstractConversationViewModel : GenericViewModel() { val conference = LinphoneUtils.createGroupCall(account, chatRoom.subject.orEmpty()) if (conference == null) { Log.e("$TAG Failed to create group call!") - showRedToastEvent.postValue( - Event( - Pair( - R.string.conference_failed_to_create_group_call_toast, - R.drawable.warning_circle - ) - ) - ) + showRedToast(R.string.conference_failed_to_create_group_call_toast, R.drawable.warning_circle) return@postOnCoreThread } @@ -199,14 +192,7 @@ abstract class AbstractConversationViewModel : GenericViewModel() { ) if (conference.inviteParticipants(participants, callParams) != 0) { Log.e("$TAG Failed to invite participants into group call!") - showRedToastEvent.postValue( - Event( - Pair( - R.string.conference_failed_to_create_group_call_toast, - R.drawable.warning_circle - ) - ) - ) + showRedToast(R.string.conference_failed_to_create_group_call_toast, R.drawable.warning_circle) } } } diff --git a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationForwardMessageViewModel.kt b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationForwardMessageViewModel.kt index 495c3170e..1ef593232 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationForwardMessageViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationForwardMessageViewModel.kt @@ -102,14 +102,7 @@ class ConversationForwardMessageViewModel Log.e("$TAG Conversation [$id] creation has failed!") chatRoom.removeListener(this) operationInProgress.postValue(false) - showRedToastEvent.postValue( - Event( - Pair( - R.string.conversation_failed_to_create_toast, - R.drawable.warning_circle - ) - ) - ) + showRedToast(R.string.conversation_failed_to_create_toast, R.drawable.warning_circle) } } } @@ -222,14 +215,7 @@ class ConversationForwardMessageViewModel "$TAG Account is in secure mode, can't chat with SIP address of different domain [${remote.asStringUriOnly()}]" ) operationInProgress.postValue(false) - showRedToastEvent.postValue( - Event( - Pair( - R.string.conversation_invalid_participant_due_to_security_mode_toast, - R.drawable.warning_circle - ) - ) - ) + showRedToast(R.string.conversation_invalid_participant_due_to_security_mode_toast, R.drawable.warning_circle) return } @@ -275,14 +261,7 @@ class ConversationForwardMessageViewModel } else { Log.e("$TAG Failed to create 1-1 conversation with [${remote.asStringUriOnly()}]!") operationInProgress.postValue(false) - showRedToastEvent.postValue( - Event( - Pair( - R.string.conversation_failed_to_create_toast, - R.drawable.warning_circle - ) - ) - ) + showRedToast(R.string.conversation_failed_to_create_toast, R.drawable.warning_circle) } } else { Log.w( diff --git a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationInfoViewModel.kt b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationInfoViewModel.kt index e9f1c2724..173c0d8eb 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationInfoViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationInfoViewModel.kt @@ -108,7 +108,7 @@ class ConversationInfoViewModel R.string.conversation_info_participant_added_to_conversation_toast, getParticipant(eventLog) ) - showFormattedGreenToastEvent.postValue(Event(Pair(message, R.drawable.user_circle))) + showFormattedGreenToast(message, R.drawable.user_circle) computeParticipantsList() infoChangedEvent.postValue(Event(true)) @@ -121,7 +121,7 @@ class ConversationInfoViewModel R.string.conversation_info_participant_removed_from_conversation_toast, getParticipant(eventLog) ) - showFormattedGreenToastEvent.postValue(Event(Pair(message, R.drawable.user_circle))) + showFormattedGreenToast(message, R.drawable.user_circle) computeParticipantsList() infoChangedEvent.postValue(Event(true)) @@ -143,7 +143,7 @@ class ConversationInfoViewModel getParticipant(eventLog) ) } - showFormattedGreenToastEvent.postValue(Event(Pair(message, R.drawable.user_circle))) + showFormattedGreenToast(message, R.drawable.user_circle) computeParticipantsList() } @@ -153,9 +153,7 @@ class ConversationInfoViewModel Log.i( "$TAG Conversation [${LinphoneUtils.getChatRoomId(chatRoom)}] has a new subject [${chatRoom.subject}]" ) - showGreenToastEvent.postValue( - Event(Pair(R.string.conversation_subject_changed_toast, R.drawable.check)) - ) + showGreenToast(R.string.conversation_subject_changed_toast, R.drawable.check) subject.postValue(chatRoom.subject) infoChangedEvent.postValue(Event(true)) @@ -166,34 +164,13 @@ class ConversationInfoViewModel Log.i("$TAG Ephemeral event [${eventLog.type}]") when (eventLog.type) { EventLog.Type.ConferenceEphemeralMessageEnabled -> { - showGreenToastEvent.postValue( - Event( - Pair( - R.string.conversation_ephemeral_messages_enabled_toast, - R.drawable.clock_countdown - ) - ) - ) + showGreenToast(R.string.conversation_ephemeral_messages_enabled_toast, R.drawable.clock_countdown) } EventLog.Type.ConferenceEphemeralMessageDisabled -> { - showGreenToastEvent.postValue( - Event( - Pair( - R.string.conversation_ephemeral_messages_disabled_toast, - R.drawable.clock_countdown - ) - ) - ) + showGreenToast(R.string.conversation_ephemeral_messages_disabled_toast, R.drawable.clock_countdown) } else -> { - showGreenToastEvent.postValue( - Event( - Pair( - R.string.conversation_ephemeral_messages_lifetime_changed_toast, - R.drawable.clock_countdown - ) - ) - ) + showGreenToast(R.string.conversation_ephemeral_messages_lifetime_changed_toast, R.drawable.clock_countdown) } } } @@ -431,14 +408,7 @@ class ConversationInfoViewModel val ok = chatRoom.addParticipants(toAddList.toTypedArray()) if (!ok) { Log.w("$TAG Failed to add some/all participants to the group!") - showRedToastEvent.postValue( - Event( - Pair( - R.string.conversation_failed_to_add_participant_to_group_conversation_toast, - R.drawable.warning_circle - ) - ) - ) + showRedToast(R.string.conversation_failed_to_add_participant_to_group_conversation_toast, R.drawable.warning_circle) } } } 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 index 788346f7e..ad5227034 100644 --- 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 @@ -925,11 +925,7 @@ class ConversationViewModel } else { R.string.conversation_search_no_more_match } - showRedToastEvent.postValue( - Event( - Pair(message, R.drawable.magnifying_glass) - ) - ) + showRedToast(message, R.drawable.magnifying_glass) } else { Log.i( "$TAG Found result [${match.chatMessage?.messageId}] while looking up for message with text [$textToSearch] in direction [$direction] starting from message [${latestMatch?.chatMessage?.messageId}]" @@ -971,24 +967,10 @@ class ConversationViewModel Log.i( "$TAG File [$filePath] has been successfully exported to documents" ) - showGreenToastEvent.postValue( - Event( - Pair( - R.string.file_successfully_exported_to_documents_toast, - R.drawable.check - ) - ) - ) + showGreenToast(R.string.file_successfully_exported_to_documents_toast, R.drawable.check) } else { Log.e("$TAG Failed to export file [$filePath] to documents!") - showRedToastEvent.postValue( - Event( - Pair( - R.string.export_file_to_documents_error_toast, - R.drawable.warning_circle - ) - ) - ) + showRedToast(R.string.export_file_to_documents_error_toast, R.drawable.warning_circle) } } } diff --git a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationsListViewModel.kt b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationsListViewModel.kt index 996571137..55313548b 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationsListViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationsListViewModel.kt @@ -33,7 +33,6 @@ import org.linphone.core.Friend import org.linphone.core.tools.Log import org.linphone.ui.main.chat.model.ConversationModel import org.linphone.ui.main.viewmodel.AbstractMainViewModel -import org.linphone.utils.Event import org.linphone.utils.LinphoneUtils class ConversationsListViewModel @@ -231,11 +230,7 @@ class ConversationsListViewModel ) } - showGreenToastEvent.postValue( - Event( - Pair(R.string.conversation_deleted_toast, R.drawable.chat_teardrop_text) - ) - ) + showGreenToast(R.string.conversation_deleted_toast, R.drawable.chat_teardrop_text) } @WorkerThread diff --git a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/SendMessageInConversationViewModel.kt b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/SendMessageInConversationViewModel.kt index eaf106b5b..c225aa2a2 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/SendMessageInConversationViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/SendMessageInConversationViewModel.kt @@ -364,14 +364,7 @@ class SendMessageInConversationViewModel Log.w( "$TAG Max number of attachments [$MAX_FILES_TO_ATTACH] reached, file [$file] won't be attached" ) - showRedToastEvent.postValue( - Event( - Pair( - R.string.conversation_maximum_number_of_attachments_reached, - R.drawable.warning_circle - ) - ) - ) + showRedToast(R.string.conversation_maximum_number_of_attachments_reached, R.drawable.warning_circle) viewModelScope.launch { Log.i("$TAG Deleting temporary file [$file]") FileUtils.deleteFile(file) @@ -434,9 +427,7 @@ class SendMessageInConversationViewModel Log.i("$TAG Sending forwarded message") forwardedMessage.send() - showGreenToastEvent.postValue( - Event(Pair(R.string.conversation_message_forwarded_toast, R.drawable.forward)) - ) + showGreenToast(R.string.conversation_message_forwarded_toast, R.drawable.forward) } } } @@ -591,14 +582,7 @@ class SendMessageInConversationViewModel "$TAG Max duration for voice recording exceeded (${maxVoiceRecordDuration}ms), stopping." ) stopVoiceRecorder() - showRedToastEvent.postValue( - Event( - Pair( - R.string.conversation_voice_recording_max_duration_reached_toast, - R.drawable.warning_circle - ) - ) - ) + showRedToast(R.string.conversation_voice_recording_max_duration_reached_toast, R.drawable.warning_circle) } } }.launchIn(viewModelScope) @@ -663,11 +647,7 @@ class SendMessageInConversationViewModel val lowMediaVolume = AudioUtils.isMediaVolumeLow(context) if (lowMediaVolume) { Log.w("$TAG Media volume is low, notifying user as they may not hear voice message") - showRedToastEvent.postValue( - Event( - Pair(R.string.media_playback_low_volume_warning_toast, R.drawable.speaker_slash) - ) - ) + showRedToast(R.string.media_playback_low_volume_warning_toast, R.drawable.speaker_slash) } if (voiceRecordAudioFocusRequest == null) { diff --git a/app/src/main/java/org/linphone/ui/main/contacts/viewmodel/ContactNewOrEditViewModel.kt b/app/src/main/java/org/linphone/ui/main/contacts/viewmodel/ContactNewOrEditViewModel.kt index 6a82e7d5c..7dab45091 100644 --- a/app/src/main/java/org/linphone/ui/main/contacts/viewmodel/ContactNewOrEditViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/contacts/viewmodel/ContactNewOrEditViewModel.kt @@ -145,14 +145,7 @@ class ContactNewOrEditViewModel val organization = company.value.orEmpty().trim() if (fn.isEmpty() && ln.isEmpty() && organization.isEmpty()) { Log.e("$TAG At least a mandatory field wasn't filled, aborting save") - showRedToastEvent.postValue( - Event( - Pair( - R.string.contact_editor_mandatory_field_not_filled_toast, - R.drawable.warning_circle - ) - ) - ) + showRedToast(R.string.contact_editor_mandatory_field_not_filled_toast, R.drawable.warning_circle) return } 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 a42e96482..13b28858f 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 @@ -242,9 +242,7 @@ class ContactsListViewModel coreContext.contactsManager.contactRemoved(contactModel.friend) contactModel.friend.remove() coreContext.contactsManager.notifyContactsListChanged() - showGreenToastEvent.postValue( - Event(Pair(R.string.contact_deleted_toast, R.drawable.warning_circle)) - ) + showGreenToast(R.string.contact_deleted_toast, R.drawable.warning_circle) } } diff --git a/app/src/main/java/org/linphone/ui/main/history/viewmodel/StartCallViewModel.kt b/app/src/main/java/org/linphone/ui/main/history/viewmodel/StartCallViewModel.kt index 8c284e7ce..1be93d9f1 100644 --- a/app/src/main/java/org/linphone/ui/main/history/viewmodel/StartCallViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/history/viewmodel/StartCallViewModel.kt @@ -187,14 +187,7 @@ class StartCallViewModel val conference = LinphoneUtils.createGroupCall(account, subject.value.orEmpty()) if (conference == null) { Log.e("$TAG Failed to create group call!") - showRedToastEvent.postValue( - Event( - Pair( - R.string.conference_failed_to_create_group_call_toast, - R.drawable.warning_circle - ) - ) - ) + showRedToast(R.string.conference_failed_to_create_group_call_toast, R.drawable.warning_circle) operationInProgress.postValue(false) return@postOnCoreThread } @@ -215,14 +208,7 @@ class StartCallViewModel conference.addListener(conferenceListener) if (conference.inviteParticipants(participants, callParams) != 0) { Log.e("$TAG Failed to invite participants into group call!") - showRedToastEvent.postValue( - Event( - Pair( - R.string.conference_failed_to_create_group_call_toast, - R.drawable.warning_circle - ) - ) - ) + showRedToast(R.string.conference_failed_to_create_group_call_toast, R.drawable.warning_circle) operationInProgress.postValue(false) } } diff --git a/app/src/main/java/org/linphone/ui/main/meetings/viewmodel/ScheduleMeetingViewModel.kt b/app/src/main/java/org/linphone/ui/main/meetings/viewmodel/ScheduleMeetingViewModel.kt index 8f1ffb02d..6b7af73bc 100644 --- a/app/src/main/java/org/linphone/ui/main/meetings/viewmodel/ScheduleMeetingViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/meetings/viewmodel/ScheduleMeetingViewModel.kt @@ -111,14 +111,7 @@ class ScheduleMeetingViewModel when (state) { ConferenceScheduler.State.Error -> { operationInProgress.postValue(false) - showRedToastEvent.postValue( - Event( - Pair( - R.string.meeting_failed_to_schedule_toast, - R.drawable.warning_circle - ) - ) - ) + showRedToast(R.string.meeting_failed_to_schedule_toast, R.drawable.warning_circle) } ConferenceScheduler.State.Ready -> { val conferenceAddress = conferenceScheduler.info?.uri @@ -166,28 +159,14 @@ class ScheduleMeetingViewModel } participants.value.orEmpty().size -> { Log.e("$TAG No invitation sent!") - showRedToastEvent.postValue( - Event( - Pair( - R.string.meeting_failed_to_send_invites_toast, - R.drawable.warning_circle - ) - ) - ) + showRedToast(R.string.meeting_failed_to_send_invites_toast, R.drawable.warning_circle) } else -> { Log.w("$TAG [$failedCount] invitations couldn't have been sent for:") for (failed in failedInvitations.orEmpty()) { Log.w(failed.asStringUriOnly()) } - showRedToastEvent.postValue( - Event( - Pair( - R.string.meeting_failed_to_send_part_of_invites_toast, - R.drawable.warning_circle - ) - ) - ) + showRedToast(R.string.meeting_failed_to_send_part_of_invites_toast, R.drawable.warning_circle) } } @@ -398,14 +377,7 @@ class ScheduleMeetingViewModel Log.e( "$TAG Either no subject was set or no participant was selected, can't schedule meeting." ) - showRedToastEvent.postValue( - Event( - Pair( - R.string.meeting_schedule_mandatory_field_not_filled_toast, - R.drawable.warning_circle - ) - ) - ) + showRedToast(R.string.meeting_schedule_mandatory_field_not_filled_toast, R.drawable.warning_circle) return } diff --git a/app/src/main/java/org/linphone/ui/main/recordings/viewmodel/RecordingMediaPlayerViewModel.kt b/app/src/main/java/org/linphone/ui/main/recordings/viewmodel/RecordingMediaPlayerViewModel.kt index 9d1a017ee..f070e668b 100644 --- a/app/src/main/java/org/linphone/ui/main/recordings/viewmodel/RecordingMediaPlayerViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/recordings/viewmodel/RecordingMediaPlayerViewModel.kt @@ -38,7 +38,6 @@ import org.linphone.core.tools.Log import org.linphone.ui.GenericViewModel import org.linphone.ui.main.recordings.model.RecordingModel import org.linphone.utils.AudioUtils -import org.linphone.utils.Event class RecordingMediaPlayerViewModel @UiThread @@ -122,11 +121,7 @@ class RecordingMediaPlayerViewModel val lowMediaVolume = AudioUtils.isMediaVolumeLow(coreContext.context) if (lowMediaVolume) { Log.w("$TAG Media volume is low, notifying user as they may not hear voice message") - showRedToastEvent.postValue( - Event( - Pair(R.string.media_playback_low_volume_warning_toast, R.drawable.speaker_slash) - ) - ) + showRedToast(R.string.media_playback_low_volume_warning_toast, R.drawable.speaker_slash) } if (player.state == Player.State.Closed) { diff --git a/app/src/main/java/org/linphone/ui/main/settings/viewmodel/AccountProfileViewModel.kt b/app/src/main/java/org/linphone/ui/main/settings/viewmodel/AccountProfileViewModel.kt index becd320b7..617e82747 100644 --- a/app/src/main/java/org/linphone/ui/main/settings/viewmodel/AccountProfileViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/settings/viewmodel/AccountProfileViewModel.kt @@ -156,14 +156,7 @@ class AccountProfileViewModel if (!errorMessage.isNullOrEmpty()) { when (request.type) { AccountManagerServicesRequest.Type.GetDevicesList, AccountManagerServicesRequest.Type.DeleteDevice -> { - showFormattedRedToastEvent.postValue( - Event( - Pair( - errorMessage, - R.drawable.warning_circle - ) - ) - ) + showFormattedRedToast(errorMessage, R.drawable.warning_circle) devicesFetchInProgress.postValue(false) } else -> {} diff --git a/app/src/main/java/org/linphone/ui/main/settings/viewmodel/CardDavViewModel.kt b/app/src/main/java/org/linphone/ui/main/settings/viewmodel/CardDavViewModel.kt index 7ed1f7eb7..c4c26bdd8 100644 --- a/app/src/main/java/org/linphone/ui/main/settings/viewmodel/CardDavViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/settings/viewmodel/CardDavViewModel.kt @@ -81,14 +81,7 @@ class CardDavViewModel when (status) { FriendList.SyncStatus.Successful -> { syncInProgress.postValue(false) - showGreenToastEvent.postValue( - Event( - Pair( - R.string.settings_contacts_carddav_sync_successful_toast, - R.drawable.check - ) - ) - ) + showGreenToast(R.string.settings_contacts_carddav_sync_successful_toast, R.drawable.check) Log.i("$TAG Notifying contacts manager that contacts have changed") coreContext.contactsManager.notifyContactsListChanged() @@ -97,14 +90,7 @@ class CardDavViewModel } FriendList.SyncStatus.Failure -> { syncInProgress.postValue(false) - showRedToastEvent.postValue( - Event( - Pair( - R.string.settings_contacts_carddav_sync_error_toast, - R.drawable.warning_circle - ) - ) - ) + showRedToast(R.string.settings_contacts_carddav_sync_error_toast, R.drawable.warning_circle) if (isEdit.value == false) { Log.e("$TAG Synchronization failed, removing Friend list from Core") friendList.removeListener(this) @@ -166,14 +152,7 @@ class CardDavViewModel } core.removeFriendList(friendList) Log.i("$TAG Removed friends list with display name [$name]") - showGreenToastEvent.postValue( - Event( - Pair( - R.string.settings_contacts_carddav_deleted_toast, - R.drawable.trash_simple - ) - ) - ) + showGreenToast(R.string.settings_contacts_carddav_deleted_toast, R.drawable.trash_simple) Log.i("$TAG Notifying contacts manager that contacts have changed") coreContext.contactsManager.notifyContactsListChanged() @@ -193,14 +172,7 @@ class CardDavViewModel val name = displayName.value.orEmpty().trim() val server = serverUrl.value.orEmpty().trim() if (name.isEmpty() || server.isEmpty()) { - showRedToastEvent.postValue( - Event( - Pair( - R.string.settings_contacts_carddav_mandatory_field_not_filled_toast, - R.drawable.warning_circle - ) - ) - ) + showRedToast(R.string.settings_contacts_carddav_mandatory_field_not_filled_toast, R.drawable.warning_circle) return } diff --git a/app/src/main/java/org/linphone/ui/main/settings/viewmodel/LdapViewModel.kt b/app/src/main/java/org/linphone/ui/main/settings/viewmodel/LdapViewModel.kt index dbfe0395c..45034be08 100644 --- a/app/src/main/java/org/linphone/ui/main/settings/viewmodel/LdapViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/settings/viewmodel/LdapViewModel.kt @@ -139,14 +139,7 @@ class LdapViewModel : GenericViewModel() { val server = serverUrl.value.orEmpty().trim() if (server.isEmpty()) { Log.e("$TAG Server field can't be empty!") - showRedToastEvent.postValue( - Event( - Pair( - R.string.settings_contacts_ldap_empty_server_error_toast, - R.drawable.warning_circle - ) - ) - ) + showRedToast(R.string.settings_contacts_ldap_empty_server_error_toast, R.drawable.warning_circle) return@postOnCoreThread } @@ -181,11 +174,7 @@ class LdapViewModel : GenericViewModel() { ldapServerOperationSuccessfulEvent.postValue(Event(true)) } catch (e: Exception) { Log.e("$TAG Exception while creating LDAP: $e") - showRedToastEvent.postValue( - Event( - Pair(R.string.settings_contacts_ldap_error_toast, R.drawable.warning_circle) - ) - ) + showRedToast(R.string.settings_contacts_ldap_error_toast, R.drawable.warning_circle) } } } diff --git a/app/src/main/java/org/linphone/ui/main/settings/viewmodel/SettingsViewModel.kt b/app/src/main/java/org/linphone/ui/main/settings/viewmodel/SettingsViewModel.kt index ddee54845..daae133a0 100644 --- a/app/src/main/java/org/linphone/ui/main/settings/viewmodel/SettingsViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/settings/viewmodel/SettingsViewModel.kt @@ -332,7 +332,7 @@ class SettingsViewModel Log.i("$TAG VFS has been enabled") } } else { - showRedToastEvent.postValue(Event(Pair(R.string.settings_security_enable_vfs_failure_toast, R.drawable.warning_circle))) + showRedToast(R.string.settings_security_enable_vfs_failure_toast, R.drawable.warning_circle) isVfsEnabled.postValue(false) Log.e("$TAG Failed to enable VFS!") } From 3e91f3e5ff3ada7fbbc51dd330a49ab3ca23e842 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Tue, 4 Feb 2025 15:43:57 +0100 Subject: [PATCH 20/89] Added advanced setting to choose between point to point and end to end encryption when creating a meeting or a group call --- .../java/org/linphone/core/CorePreferences.kt | 10 +++++++ .../viewmodel/ScheduleMeetingViewModel.kt | 2 +- .../settings/viewmodel/SettingsViewModel.kt | 12 ++++++++ .../java/org/linphone/utils/LinphoneUtils.kt | 2 +- .../res/layout/settings_advanced_fragment.xml | 30 ++++++++++++++++++- app/src/main/res/values-fr/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 7 files changed, 55 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/linphone/core/CorePreferences.kt b/app/src/main/java/org/linphone/core/CorePreferences.kt index 1c7aecf2c..7a9fac7c2 100644 --- a/app/src/main/java/org/linphone/core/CorePreferences.kt +++ b/app/src/main/java/org/linphone/core/CorePreferences.kt @@ -142,12 +142,22 @@ class CorePreferences // Conversation related + @get:WorkerThread @set:WorkerThread var markConversationAsReadWhenDismissingMessageNotification: Boolean get() = config.getBool("app", "mark_as_read_notif_dismissal", false) set(value) { config.setBool("app", "mark_as_read_notif_dismissal", value) } + // Conference related + + @get:WorkerThread @set:WorkerThread + var createEndToEndEncryptedMeetingsAndGroupCalls: Boolean + get() = config.getBool("app", "create_e2e_encrypted_conferences", false) + set(value) { + config.setBool("app", "create_e2e_encrypted_conferences", value) + } + // Contacts related @get:WorkerThread @set:WorkerThread diff --git a/app/src/main/java/org/linphone/ui/main/meetings/viewmodel/ScheduleMeetingViewModel.kt b/app/src/main/java/org/linphone/ui/main/meetings/viewmodel/ScheduleMeetingViewModel.kt index 6b7af73bc..160ffd51a 100644 --- a/app/src/main/java/org/linphone/ui/main/meetings/viewmodel/ScheduleMeetingViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/meetings/viewmodel/ScheduleMeetingViewModel.kt @@ -399,7 +399,7 @@ class ScheduleMeetingViewModel conferenceInfo.setCapability(StreamType.Text, true) // Enable end-to-end encryption if client supports it - conferenceInfo.securityLevel = if (LinphoneUtils.isEndToEndEncryptedChatAvailable(core)) { + conferenceInfo.securityLevel = if (corePreferences.createEndToEndEncryptedMeetingsAndGroupCalls) { Log.i("$TAG Requesting EndToEnd security level for conference") Conference.SecurityLevel.EndToEnd } else { diff --git a/app/src/main/java/org/linphone/ui/main/settings/viewmodel/SettingsViewModel.kt b/app/src/main/java/org/linphone/ui/main/settings/viewmodel/SettingsViewModel.kt index daae133a0..9c90b3894 100644 --- a/app/src/main/java/org/linphone/ui/main/settings/viewmodel/SettingsViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/settings/viewmodel/SettingsViewModel.kt @@ -187,6 +187,7 @@ class SettingsViewModel val mediaEncryptionLabels = arrayListOf() private val mediaEncryptionValues = arrayListOf() val mediaEncryptionMandatory = MutableLiveData() + val createEndToEndEncryptedConferences = MutableLiveData() val acceptEarlyMedia = MutableLiveData() val allowOutgoingEarlyMedia = MutableLiveData() @@ -675,6 +676,7 @@ class SettingsViewModel } mediaEncryptionMandatory.postValue(core.isMediaEncryptionMandatory) + createEndToEndEncryptedConferences.postValue(corePreferences.createEndToEndEncryptedMeetingsAndGroupCalls) acceptEarlyMedia.postValue(corePreferences.acceptEarlyMedia) allowOutgoingEarlyMedia.postValue(corePreferences.allowOutgoingEarlyMedia) } @@ -702,6 +704,16 @@ class SettingsViewModel } } + @UiThread + fun toggleConferencesEndToEndEncryption() { + val newValue = createEndToEndEncryptedConferences.value == false + + coreContext.postOnCoreThread { core -> + corePreferences.createEndToEndEncryptedMeetingsAndGroupCalls = newValue + createEndToEndEncryptedConferences.postValue(newValue) + } + } + @UiThread fun toggleAcceptEarlyMedia() { val newValue = acceptEarlyMedia.value == false diff --git a/app/src/main/java/org/linphone/utils/LinphoneUtils.kt b/app/src/main/java/org/linphone/utils/LinphoneUtils.kt index 7e77a3d4d..20837bc79 100644 --- a/app/src/main/java/org/linphone/utils/LinphoneUtils.kt +++ b/app/src/main/java/org/linphone/utils/LinphoneUtils.kt @@ -272,7 +272,7 @@ class LinphoneUtils { conferenceParams.subject = subject // Enable end-to-end encryption if client supports it - conferenceParams.securityLevel = if (isEndToEndEncryptedChatAvailable(core)) { + conferenceParams.securityLevel = if (corePreferences.createEndToEndEncryptedMeetingsAndGroupCalls) { Log.i("$TAG Requesting EndToEnd security level for conference") Conference.SecurityLevel.EndToEnd } else { diff --git a/app/src/main/res/layout/settings_advanced_fragment.xml b/app/src/main/res/layout/settings_advanced_fragment.xml index a16aa76c5..71552ed29 100644 --- a/app/src/main/res/layout/settings_advanced_fragment.xml +++ b/app/src/main/res/layout/settings_advanced_fragment.xml @@ -192,6 +192,34 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@id/media_encryption" /> + + + + + app:layout_constraintTop_toBottomOf="@id/e2e_encrypted_conferences_switch" /> URL du serveur de partage de fichier Chiffrement du média Rendre le chiffrement du média obligatoire + Créer en mode chiffré de bout en bout les réunions et les appels de groupe Accepter l\'early media Autoriser l\'early media pour les appels sortants URL de configuration distante diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4a3daa79f..7d3cad640 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -300,6 +300,7 @@ File sharing server URL Media encryption Media encryption mandatory + Create end-to-end encrypted meetings & group calls Accept early media Allow outgoing early media Remote provisioning URL From 86b35354c340efa58a723a4ec18baebc4618c547 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Thu, 6 Feb 2025 09:34:55 +0100 Subject: [PATCH 21/89] Increased dialpad touch area --- app/src/main/res/layout/call_numpad_digit.xml | 13 ++++++++-- .../layout/call_numpad_digit_with_letters.xml | 13 ++++++++-- .../layout/call_numpad_digit_with_plus.xml | 13 ++++++++-- .../call_numpad_digit_with_voicemail.xml | 13 ++++++++-- .../layout/start_call_numpad_bottom_sheet.xml | 26 +++++++++---------- 5 files changed, 57 insertions(+), 21 deletions(-) diff --git a/app/src/main/res/layout/call_numpad_digit.xml b/app/src/main/res/layout/call_numpad_digit.xml index 9dc680600..63c5bf246 100644 --- a/app/src/main/res/layout/call_numpad_digit.xml +++ b/app/src/main/res/layout/call_numpad_digit.xml @@ -14,8 +14,17 @@ + android:layout_height="@dimen/call_dtmf_button_size"> + + + android:layout_height="@dimen/call_dtmf_button_size"> + + + android:layout_height="@dimen/call_dtmf_button_size"> + + + android:layout_height="@dimen/call_dtmf_button_size"> + + Date: Thu, 6 Feb 2025 09:44:45 +0100 Subject: [PATCH 22/89] Fixed in-call bottom sheets not intercepting clicks --- .../layout-land/call_media_encryption_stats_bottom_sheet.xml | 2 ++ app/src/main/res/layout/call_audio_devices_bottom_sheet.xml | 2 ++ app/src/main/res/layout/call_conference_layout_bottom_sheet.xml | 2 ++ .../res/layout/call_media_encryption_stats_bottom_sheet.xml | 2 ++ 4 files changed, 8 insertions(+) diff --git a/app/src/main/res/layout-land/call_media_encryption_stats_bottom_sheet.xml b/app/src/main/res/layout-land/call_media_encryption_stats_bottom_sheet.xml index e82584286..ec5cc2e90 100644 --- a/app/src/main/res/layout-land/call_media_encryption_stats_bottom_sheet.xml +++ b/app/src/main/res/layout-land/call_media_encryption_stats_bottom_sheet.xml @@ -16,6 +16,8 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@drawable/shape_call_bottom_sheet_background" + android:clickable="true" + android:focusable="true" android:visibility="@{viewModel.fullScreenMode || viewModel.pipMode ? View.INVISIBLE : View.VISIBLE}" app:behavior_hideable="true" app:behavior_peekHeight="0dp" diff --git a/app/src/main/res/layout/call_audio_devices_bottom_sheet.xml b/app/src/main/res/layout/call_audio_devices_bottom_sheet.xml index 40604b974..d110b9aed 100644 --- a/app/src/main/res/layout/call_audio_devices_bottom_sheet.xml +++ b/app/src/main/res/layout/call_audio_devices_bottom_sheet.xml @@ -12,6 +12,8 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" + android:clickable="true" + android:focusable="true" android:background="@drawable/shape_squircle_gray_600_top_call_background" entries="@{devices}" layout="@{@layout/call_audio_device_list_cell}"> diff --git a/app/src/main/res/layout/call_conference_layout_bottom_sheet.xml b/app/src/main/res/layout/call_conference_layout_bottom_sheet.xml index 337e428a1..2473181b3 100644 --- a/app/src/main/res/layout/call_conference_layout_bottom_sheet.xml +++ b/app/src/main/res/layout/call_conference_layout_bottom_sheet.xml @@ -23,6 +23,8 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" + android:clickable="true" + android:focusable="true" android:background="@drawable/shape_squircle_gray_600_top_call_background"> Date: Thu, 6 Feb 2025 17:16:24 +0100 Subject: [PATCH 23/89] Prevent crash if for some reason today is not found --- .../main/meetings/fragment/MeetingsListFragment.kt | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/org/linphone/ui/main/meetings/fragment/MeetingsListFragment.kt b/app/src/main/java/org/linphone/ui/main/meetings/fragment/MeetingsListFragment.kt index 294189f20..f93c2a4f2 100644 --- a/app/src/main/java/org/linphone/ui/main/meetings/fragment/MeetingsListFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/meetings/fragment/MeetingsListFragment.kt @@ -303,11 +303,13 @@ class MeetingsListFragment : AbstractMainFragment() { } val index = listViewModel.meetings.value.orEmpty().indexOf(todayMeeting) Log.i("$TAG 'Today' is at position [$index]") - binding.meetingsList.smoothScrollToPosition(index) // Workaround to have header decoration visible at top - (binding.meetingsList.layoutManager as LinearLayoutManager).scrollToPositionWithOffset( - index, - AppUtils.getDimension(R.dimen.meeting_list_decoration_height).toInt() - ) + if (index > 0) { + binding.meetingsList.smoothScrollToPosition(index) // Workaround to have header decoration visible at top + (binding.meetingsList.layoutManager as LinearLayoutManager).scrollToPositionWithOffset( + index, + AppUtils.getDimension(R.dimen.meeting_list_decoration_height).toInt() + ) + } } private fun showCancelMeetingDialog(meetingModel: MeetingModel) { From bfc435c350cbaa73892caa622bfe6cff7029da47 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Tue, 4 Feb 2025 11:23:28 +0100 Subject: [PATCH 24/89] Ask user confirmation for cancelling meeting if it us the organizer, to delete it if it's only a participant --- .../fragment/ConversationsListFragment.kt | 2 + .../main/meetings/fragment/MeetingFragment.kt | 41 ++++++- .../meetings/fragment/MeetingsListFragment.kt | 53 +++++++-- .../fragment/MeetingsMenuDialogFragment.kt | 2 + .../viewmodel/MeetingWaitingRoomViewModel.kt | 8 ++ .../java/org/linphone/utils/DialogUtils.kt | 17 +++ .../main/res/layout/dialog_cancel_meeting.xml | 2 +- .../main/res/layout/dialog_delete_meeting.xml | 103 ++++++++++++++++++ .../main/res/layout/meeting_popup_menu.xml | 5 +- .../layout/meetings_list_long_press_menu.xml | 5 +- app/src/main/res/values-fr/strings.xml | 6 +- app/src/main/res/values/strings.xml | 6 +- 12 files changed, 228 insertions(+), 22 deletions(-) create mode 100644 app/src/main/res/layout/dialog_delete_meeting.xml 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 8fa661c00..ce968857d 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 @@ -214,6 +214,8 @@ class ConversationsListFragment : AbstractMainFragment() { uri ) findNavController().navigate(action) + } else { + Log.e("$TAG Failed to navigate to meeting waiting room, wrong current destination (expected conversationsListFragment but was something else)") } } } diff --git a/app/src/main/java/org/linphone/ui/main/meetings/fragment/MeetingFragment.kt b/app/src/main/java/org/linphone/ui/main/meetings/fragment/MeetingFragment.kt index e254da21f..12a7bc163 100644 --- a/app/src/main/java/org/linphone/ui/main/meetings/fragment/MeetingFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/meetings/fragment/MeetingFragment.kt @@ -198,7 +198,7 @@ class MeetingFragment : SlidingPaneChildFragment() { viewModel.conferenceCancelledEvent.observe(viewLifecycleOwner) { it.consume { - Log.i("$TAG Meeting has been cancelled successfully, deleting it") + Log.i("$TAG Meeting has been cancelled successfully") (requireActivity() as GenericActivity).showGreenToast( getString(R.string.meeting_info_cancelled_toast), R.drawable.trash_simple @@ -222,11 +222,15 @@ class MeetingFragment : SlidingPaneChildFragment() { true ) + val isUserOrganizer = viewModel.isEditable.value == true && viewModel.isCancelled.value == false + popupView.cancelInsteadOfDelete = isUserOrganizer popupView.setDeleteClickListener { - if (viewModel.isEditable.value == true) { + if (isUserOrganizer) { + // In case we are organizer of the meeting, ask user confirmation before cancelling it showCancelMeetingDialog() } else { - viewModel.delete() + // If we're not organizer, ask user confirmation of removing itself from participants & deleting it locally + showDeleteMeetingDialog() } popupWindow.dismiss() } @@ -283,8 +287,7 @@ class MeetingFragment : SlidingPaneChildFragment() { } private fun showCancelMeetingDialog() { - Log.i("$TAG Meeting is editable, asking whether to cancel it or not before deleting it") - + Log.i("$TAG Meeting is editable, asking whether to cancel it or not") val model = ConfirmationDialogModel() val dialog = DialogUtils.getCancelMeetingDialog(requireContext(), model) @@ -296,7 +299,6 @@ class MeetingFragment : SlidingPaneChildFragment() { model.cancelEvent.observe(viewLifecycleOwner) { it.consume { - viewModel.delete() dialog.dismiss() } } @@ -310,4 +312,31 @@ class MeetingFragment : SlidingPaneChildFragment() { dialog.show() } + + private fun showDeleteMeetingDialog() { + Log.i("$TAG Meeting is not editable or already cancelled, asking whether to delete it or not") + val model = ConfirmationDialogModel() + val dialog = DialogUtils.getDeleteMeetingDialog(requireContext(), model) + + model.dismissEvent.observe(viewLifecycleOwner) { + it.consume { + dialog.dismiss() + } + } + + model.cancelEvent.observe(viewLifecycleOwner) { + it.consume { + dialog.dismiss() + } + } + + model.confirmEvent.observe(viewLifecycleOwner) { + it.consume { + viewModel.delete() + dialog.dismiss() + } + } + + dialog.show() + } } diff --git a/app/src/main/java/org/linphone/ui/main/meetings/fragment/MeetingsListFragment.kt b/app/src/main/java/org/linphone/ui/main/meetings/fragment/MeetingsListFragment.kt index f93c2a4f2..615df6227 100644 --- a/app/src/main/java/org/linphone/ui/main/meetings/fragment/MeetingsListFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/meetings/fragment/MeetingsListFragment.kt @@ -177,6 +177,7 @@ class MeetingsListFragment : AbstractMainFragment() { meetingViewModelBeingCancelled?.delete() meetingViewModelBeingCancelled = null listViewModel.applyFilter() + (requireActivity() as GenericActivity).showGreenToast( getString(R.string.meeting_info_deleted_toast), R.drawable.trash_simple @@ -186,22 +187,25 @@ class MeetingsListFragment : AbstractMainFragment() { adapter.meetingLongClickedEvent.observe(viewLifecycleOwner) { it.consume { model -> + val isUserOrganizer = model.isOrganizer() && !model.isCancelled val modalBottomSheet = MeetingsMenuDialogFragment( + isUserOrganizer, { // onDismiss adapter.resetSelection() }, { // onDelete - if (model.isOrganizer() && !model.isCancelled) { + if (isUserOrganizer) { showCancelMeetingDialog(model) } else { - Log.i("$TAG Deleting meeting [${model.id}]") + showDeleteMeetingDialog(model) + /*Log.i("$TAG Deleting meeting [${model.id}]") model.delete() listViewModel.applyFilter() (requireActivity() as GenericActivity).showGreenToast( getString(R.string.meeting_info_deleted_toast), R.drawable.trash_simple - ) + )*/ } } ) @@ -326,15 +330,7 @@ class MeetingsListFragment : AbstractMainFragment() { model.cancelEvent.observe(viewLifecycleOwner) { it.consume { - Log.i("$TAG Deleting meeting [${meetingModel.id}]") - meetingModel.delete() - listViewModel.applyFilter() - dialog.dismiss() - (requireActivity() as GenericActivity).showGreenToast( - getString(R.string.meeting_info_deleted_toast), - R.drawable.trash_simple - ) } } @@ -349,4 +345,39 @@ class MeetingsListFragment : AbstractMainFragment() { dialog.show() } + + private fun showDeleteMeetingDialog(meetingModel: MeetingModel) { + Log.i("$TAG Meeting is not editable or already cancelled, asking whether to deleting it or not") + + val model = ConfirmationDialogModel() + val dialog = DialogUtils.getDeleteMeetingDialog(requireContext(), model) + + model.dismissEvent.observe(viewLifecycleOwner) { + it.consume { + dialog.dismiss() + } + } + + model.cancelEvent.observe(viewLifecycleOwner) { + it.consume { + dialog.dismiss() + } + } + + model.confirmEvent.observe(viewLifecycleOwner) { + it.consume { + Log.i("$TAG Deleting meeting [${meetingModel.id}]") + meetingModel.delete() + listViewModel.applyFilter() + + dialog.dismiss() + (requireActivity() as GenericActivity).showGreenToast( + getString(R.string.meeting_info_deleted_toast), + R.drawable.trash_simple + ) + } + } + + dialog.show() + } } diff --git a/app/src/main/java/org/linphone/ui/main/meetings/fragment/MeetingsMenuDialogFragment.kt b/app/src/main/java/org/linphone/ui/main/meetings/fragment/MeetingsMenuDialogFragment.kt index 7bd7b72fc..e5529097b 100644 --- a/app/src/main/java/org/linphone/ui/main/meetings/fragment/MeetingsMenuDialogFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/meetings/fragment/MeetingsMenuDialogFragment.kt @@ -33,6 +33,7 @@ import org.linphone.databinding.MeetingsListLongPressMenuBinding @UiThread class MeetingsMenuDialogFragment( + private val isUserOrganizer: Boolean, private val onDismiss: (() -> Unit)? = null, private val onDeleteMeeting: (() -> Unit)? = null ) : BottomSheetDialogFragment() { @@ -64,6 +65,7 @@ class MeetingsMenuDialogFragment( savedInstanceState: Bundle? ): View { val view = MeetingsListLongPressMenuBinding.inflate(layoutInflater) + view.cancelInsteadOfDelete = isUserOrganizer view.setDeleteClickListener { onDeleteMeeting?.invoke() diff --git a/app/src/main/java/org/linphone/ui/main/meetings/viewmodel/MeetingWaitingRoomViewModel.kt b/app/src/main/java/org/linphone/ui/main/meetings/viewmodel/MeetingWaitingRoomViewModel.kt index 92d612156..155cbaa04 100644 --- a/app/src/main/java/org/linphone/ui/main/meetings/viewmodel/MeetingWaitingRoomViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/meetings/viewmodel/MeetingWaitingRoomViewModel.kt @@ -190,6 +190,14 @@ class MeetingWaitingRoomViewModel conferenceInfoFoundEvent.postValue(Event(true)) } else { Log.e("$TAG Conference info with SIP URI [$uri] couldn't be found!") + showRedToastEvent.postValue( + Event( + Pair( + R.string.meeting_info_not_found_toast, + R.drawable.warning_circle + ) + ) + ) conferenceInfoFoundEvent.postValue(Event(false)) } } else { diff --git a/app/src/main/java/org/linphone/utils/DialogUtils.kt b/app/src/main/java/org/linphone/utils/DialogUtils.kt index 4dc0843cb..e41ae3264 100644 --- a/app/src/main/java/org/linphone/utils/DialogUtils.kt +++ b/app/src/main/java/org/linphone/utils/DialogUtils.kt @@ -43,6 +43,7 @@ import org.linphone.databinding.DialogConfirmTurningOnVfsBinding import org.linphone.databinding.DialogContactConfirmTrustCallBinding import org.linphone.databinding.DialogContactTrustProcessBinding import org.linphone.databinding.DialogDeleteContactBinding +import org.linphone.databinding.DialogDeleteMeetingBinding import org.linphone.databinding.DialogKickFromConferenceBinding import org.linphone.databinding.DialogManageAccountInternationalPrefixHelpBinding import org.linphone.databinding.DialogMergeCallsIntoConferenceBinding @@ -513,6 +514,22 @@ class DialogUtils { return getDialog(context, binding) } + @UiThread + fun getDeleteMeetingDialog( + context: Context, + viewModel: ConfirmationDialogModel + ): Dialog { + val binding: DialogDeleteMeetingBinding = DataBindingUtil.inflate( + LayoutInflater.from(context), + R.layout.dialog_delete_meeting, + null, + false + ) + binding.viewModel = viewModel + + return getDialog(context, binding) + } + @UiThread private fun getDialog(context: Context, binding: ViewDataBinding): Dialog { val dialog = Dialog(context, R.style.Theme_LinphoneDialog) diff --git a/app/src/main/res/layout/dialog_cancel_meeting.xml b/app/src/main/res/layout/dialog_cancel_meeting.xml index 326e2cfe6..289c90956 100644 --- a/app/src/main/res/layout/dialog_cancel_meeting.xml +++ b/app/src/main/res/layout/dialog_cancel_meeting.xml @@ -68,7 +68,7 @@ android:layout_marginTop="32dp" android:layout_marginStart="15dp" android:layout_marginEnd="15dp" - android:text="@string/dialog_no" + android:text= "@string/dialog_no" app:layout_constraintStart_toStartOf="@id/dialog_background" app:layout_constraintEnd_toEndOf="@id/dialog_background" app:layout_constraintTop_toBottomOf="@id/message" diff --git a/app/src/main/res/layout/dialog_delete_meeting.xml b/app/src/main/res/layout/dialog_delete_meeting.xml new file mode 100644 index 000000000..3ea8a9076 --- /dev/null +++ b/app/src/main/res/layout/dialog_delete_meeting.xml @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/meeting_popup_menu.xml b/app/src/main/res/layout/meeting_popup_menu.xml index 82454e676..d94aaa109 100644 --- a/app/src/main/res/layout/meeting_popup_menu.xml +++ b/app/src/main/res/layout/meeting_popup_menu.xml @@ -11,6 +11,9 @@ + + Envoyer l\'invitation par message aux participants Rejoindre la réunion Organisateur - Supprimer la réunion Ajouter dans le calendrier Réunion supprimée + Réunion introuvable ! Description Modifier la réunion Annuler la réunion? Voulez-vous annuler la réunion et envoyer une notification aux participants ? + Annuler la réunion + Supprimer la réunion ? + Voulez-vous supprimer la réunion ? + Supprimer la réunion Réunion créée Réunion mise à jour Réunion annulée diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7d3cad640..d7ea62242 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -613,13 +613,17 @@ Send invitation to participants Join the meeting now Organizer - Delete meeting Create calendar event Meeting has been deleted + Meeting cannot be found! Description Edit meeting Cancel the meeting? Do you want to cancel the meeting and send a notification to all participants? + Cancel meeting + Delete the meeting? + Do you want to delete the meeting? + Delete meeting Meeting has been created Meeting has been updated Meeting has been cancelled From 6afd711539f6027dc7aa61dc1351be0f78733f16 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Fri, 7 Feb 2025 10:58:27 +0100 Subject: [PATCH 25/89] Reworked welcome screens --- .../main/res/layout-land/welcome_activity.xml | 130 ++++++++++++++++++ .../main/res/layout-land/welcome_page_1.xml | 63 ++------- .../main/res/layout-land/welcome_page_2.xml | 63 ++------- .../main/res/layout-land/welcome_page_3.xml | 63 ++------- .../res/layout-sw600dp/welcome_activity.xml | 130 ++++++++++++++++++ .../res/layout-sw600dp/welcome_page_1.xml | 58 ++++++++ .../res/layout-sw600dp/welcome_page_2.xml | 58 ++++++++ .../res/layout-sw600dp/welcome_page_3.xml | 58 ++++++++ app/src/main/res/layout/welcome_activity.xml | 45 +++++- app/src/main/res/layout/welcome_page_1.xml | 44 +----- app/src/main/res/layout/welcome_page_2.xml | 44 +----- app/src/main/res/layout/welcome_page_3.xml | 44 +----- app/src/main/res/values-sw600dp/dimen.xml | 4 + 13 files changed, 529 insertions(+), 275 deletions(-) create mode 100644 app/src/main/res/layout-land/welcome_activity.xml create mode 100644 app/src/main/res/layout-sw600dp/welcome_activity.xml create mode 100644 app/src/main/res/layout-sw600dp/welcome_page_1.xml create mode 100644 app/src/main/res/layout-sw600dp/welcome_page_2.xml create mode 100644 app/src/main/res/layout-sw600dp/welcome_page_3.xml create mode 100644 app/src/main/res/values-sw600dp/dimen.xml diff --git a/app/src/main/res/layout-land/welcome_activity.xml b/app/src/main/res/layout-land/welcome_activity.xml new file mode 100644 index 000000000..c1aea87cb --- /dev/null +++ b/app/src/main/res/layout-land/welcome_activity.xml @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-land/welcome_page_1.xml b/app/src/main/res/layout-land/welcome_page_1.xml index 89f90e205..9686448f6 100644 --- a/app/src/main/res/layout-land/welcome_page_1.xml +++ b/app/src/main/res/layout-land/welcome_page_1.xml @@ -9,83 +9,48 @@ android:layout_height="wrap_content" android:layout_width="match_parent"> - - - - - - + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toStartOf="@id/message"/> diff --git a/app/src/main/res/layout-land/welcome_page_2.xml b/app/src/main/res/layout-land/welcome_page_2.xml index aef298efa..88abf58c4 100644 --- a/app/src/main/res/layout-land/welcome_page_2.xml +++ b/app/src/main/res/layout-land/welcome_page_2.xml @@ -9,83 +9,48 @@ android:layout_height="wrap_content" android:layout_width="match_parent"> - - - - - - + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toStartOf="@id/message"/> diff --git a/app/src/main/res/layout-land/welcome_page_3.xml b/app/src/main/res/layout-land/welcome_page_3.xml index 056eec701..f680b344e 100644 --- a/app/src/main/res/layout-land/welcome_page_3.xml +++ b/app/src/main/res/layout-land/welcome_page_3.xml @@ -9,83 +9,48 @@ android:layout_height="wrap_content" android:layout_width="match_parent"> - - - - - - + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toStartOf="@id/message"/> diff --git a/app/src/main/res/layout-sw600dp/welcome_activity.xml b/app/src/main/res/layout-sw600dp/welcome_activity.xml new file mode 100644 index 000000000..0b30531bc --- /dev/null +++ b/app/src/main/res/layout-sw600dp/welcome_activity.xml @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-sw600dp/welcome_page_1.xml b/app/src/main/res/layout-sw600dp/welcome_page_1.xml new file mode 100644 index 000000000..9686448f6 --- /dev/null +++ b/app/src/main/res/layout-sw600dp/welcome_page_1.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-sw600dp/welcome_page_2.xml b/app/src/main/res/layout-sw600dp/welcome_page_2.xml new file mode 100644 index 000000000..88abf58c4 --- /dev/null +++ b/app/src/main/res/layout-sw600dp/welcome_page_2.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-sw600dp/welcome_page_3.xml b/app/src/main/res/layout-sw600dp/welcome_page_3.xml new file mode 100644 index 000000000..f680b344e --- /dev/null +++ b/app/src/main/res/layout-sw600dp/welcome_page_3.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/welcome_activity.xml b/app/src/main/res/layout/welcome_activity.xml index 326cfa60c..49a28d17c 100644 --- a/app/src/main/res/layout/welcome_activity.xml +++ b/app/src/main/res/layout/welcome_activity.xml @@ -28,6 +28,7 @@ android:id="@+id/skip" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_marginTop="10dp" android:layout_marginEnd="16dp" android:paddingTop="13dp" android:paddingBottom="13dp" @@ -35,16 +36,56 @@ android:paddingEnd="20dp" android:text="@string/welcome_carousel_skip" android:textSize="13sp" + android:textColor="?attr/color_text" app:layout_constraintTop_toTopOf="parent" - app:layout_constraintBottom_toTopOf="@id/pager" app:layout_constraintEnd_toEndOf="parent" /> + + + + + + diff --git a/app/src/main/res/layout/welcome_page_1.xml b/app/src/main/res/layout/welcome_page_1.xml index 49fd2d896..eff400451 100644 --- a/app/src/main/res/layout/welcome_page_1.xml +++ b/app/src/main/res/layout/welcome_page_1.xml @@ -9,56 +9,16 @@ android:layout_height="wrap_content" android:layout_width="match_parent"> - - - - - - diff --git a/app/src/main/res/layout/welcome_page_2.xml b/app/src/main/res/layout/welcome_page_2.xml index 530ad6ce0..2cbe1bff6 100644 --- a/app/src/main/res/layout/welcome_page_2.xml +++ b/app/src/main/res/layout/welcome_page_2.xml @@ -9,56 +9,16 @@ android:layout_height="wrap_content" android:layout_width="match_parent"> - - - - - - diff --git a/app/src/main/res/layout/welcome_page_3.xml b/app/src/main/res/layout/welcome_page_3.xml index 0cd31d2c4..40fda8eac 100644 --- a/app/src/main/res/layout/welcome_page_3.xml +++ b/app/src/main/res/layout/welcome_page_3.xml @@ -9,56 +9,16 @@ android:layout_height="wrap_content" android:layout_width="match_parent"> - - - - - - diff --git a/app/src/main/res/values-sw600dp/dimen.xml b/app/src/main/res/values-sw600dp/dimen.xml new file mode 100644 index 000000000..9970d9654 --- /dev/null +++ b/app/src/main/res/values-sw600dp/dimen.xml @@ -0,0 +1,4 @@ + + + 150dp + \ No newline at end of file From 2b75ecdaca0b167ee95f112b786194de6996bb85 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Fri, 7 Feb 2025 13:33:41 +0100 Subject: [PATCH 26/89] Reworked assistant screens for tablets --- app/src/main/res/drawable/assistant_logo.xml | 297 +++++++++++++ .../res/drawable/confirm_sms_code_logo.xml | 201 +++++++++ .../assistant_landing_fragment.xml | 287 +++++++++++++ ...ant_register_confirm_sms_code_fragment.xml | 216 ++++++++++ .../assistant_register_fragment.xml | 389 ++++++++++++++++++ ...third_party_sip_account_login_fragment.xml | 389 ++++++++++++++++++ ...ird_party_sip_account_warning_fragment.xml | 194 +++++++++ ...ant_register_confirm_sms_code_fragment.xml | 2 +- ...third_party_sip_account_login_fragment.xml | 2 +- ...ird_party_sip_account_warning_fragment.xml | 2 +- app/src/main/res/values-fr/strings.xml | 2 + app/src/main/res/values/dimen.xml | 1 + app/src/main/res/values/strings.xml | 2 + 13 files changed, 1981 insertions(+), 3 deletions(-) create mode 100644 app/src/main/res/drawable/assistant_logo.xml create mode 100644 app/src/main/res/drawable/confirm_sms_code_logo.xml create mode 100644 app/src/main/res/layout-sw600dp/assistant_landing_fragment.xml create mode 100644 app/src/main/res/layout-sw600dp/assistant_register_confirm_sms_code_fragment.xml create mode 100644 app/src/main/res/layout-sw600dp/assistant_register_fragment.xml create mode 100644 app/src/main/res/layout-sw600dp/assistant_third_party_sip_account_login_fragment.xml create mode 100644 app/src/main/res/layout-sw600dp/assistant_third_party_sip_account_warning_fragment.xml diff --git a/app/src/main/res/drawable/assistant_logo.xml b/app/src/main/res/drawable/assistant_logo.xml new file mode 100644 index 000000000..f0112e52a --- /dev/null +++ b/app/src/main/res/drawable/assistant_logo.xml @@ -0,0 +1,297 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/confirm_sms_code_logo.xml b/app/src/main/res/drawable/confirm_sms_code_logo.xml new file mode 100644 index 000000000..b42354299 --- /dev/null +++ b/app/src/main/res/drawable/confirm_sms_code_logo.xml @@ -0,0 +1,201 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout-sw600dp/assistant_landing_fragment.xml b/app/src/main/res/layout-sw600dp/assistant_landing_fragment.xml new file mode 100644 index 000000000..20dc81afb --- /dev/null +++ b/app/src/main/res/layout-sw600dp/assistant_landing_fragment.xml @@ -0,0 +1,287 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-sw600dp/assistant_register_confirm_sms_code_fragment.xml b/app/src/main/res/layout-sw600dp/assistant_register_confirm_sms_code_fragment.xml new file mode 100644 index 000000000..5bfa7baf6 --- /dev/null +++ b/app/src/main/res/layout-sw600dp/assistant_register_confirm_sms_code_fragment.xml @@ -0,0 +1,216 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-sw600dp/assistant_register_fragment.xml b/app/src/main/res/layout-sw600dp/assistant_register_fragment.xml new file mode 100644 index 000000000..eacf7aa26 --- /dev/null +++ b/app/src/main/res/layout-sw600dp/assistant_register_fragment.xml @@ -0,0 +1,389 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-sw600dp/assistant_third_party_sip_account_login_fragment.xml b/app/src/main/res/layout-sw600dp/assistant_third_party_sip_account_login_fragment.xml new file mode 100644 index 000000000..e064ddfd1 --- /dev/null +++ b/app/src/main/res/layout-sw600dp/assistant_third_party_sip_account_login_fragment.xml @@ -0,0 +1,389 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-sw600dp/assistant_third_party_sip_account_warning_fragment.xml b/app/src/main/res/layout-sw600dp/assistant_third_party_sip_account_warning_fragment.xml new file mode 100644 index 000000000..a4b570834 --- /dev/null +++ b/app/src/main/res/layout-sw600dp/assistant_third_party_sip_account_warning_fragment.xml @@ -0,0 +1,194 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/assistant_register_confirm_sms_code_fragment.xml b/app/src/main/res/layout/assistant_register_confirm_sms_code_fragment.xml index 92f349c41..629651331 100644 --- a/app/src/main/res/layout/assistant_register_confirm_sms_code_fragment.xml +++ b/app/src/main/res/layout/assistant_register_confirm_sms_code_fragment.xml @@ -55,7 +55,7 @@ android:layout_height="wrap_content" android:layout_marginTop="100dp" android:paddingBottom="30dp" - android:text="@string/assistant_account_register" + android:text="@string/assistant_account_creation_sms_confirmation_title" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"/> diff --git a/app/src/main/res/layout/assistant_third_party_sip_account_login_fragment.xml b/app/src/main/res/layout/assistant_third_party_sip_account_login_fragment.xml index e34911793..db7afa09f 100644 --- a/app/src/main/res/layout/assistant_third_party_sip_account_login_fragment.xml +++ b/app/src/main/res/layout/assistant_third_party_sip_account_login_fragment.xml @@ -57,7 +57,7 @@ android:layout_height="wrap_content" android:layout_marginTop="100dp" android:paddingBottom="27dp" - android:text="@string/assistant_login_third_party_sip_account" + android:text="@string/assistant_login_third_party_sip_account_title" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"/> diff --git a/app/src/main/res/layout/assistant_third_party_sip_account_warning_fragment.xml b/app/src/main/res/layout/assistant_third_party_sip_account_warning_fragment.xml index 7279bb41f..e78d95e31 100644 --- a/app/src/main/res/layout/assistant_third_party_sip_account_warning_fragment.xml +++ b/app/src/main/res/layout/assistant_third_party_sip_account_warning_fragment.xml @@ -47,7 +47,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="100dp" - android:text="@string/assistant_login_third_party_sip_account" + android:text="@string/assistant_login_third_party_sip_account_title" android:textColor="?attr/color_main2_600" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 5d9184ea5..8c533f513 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -102,11 +102,13 @@ Scanner un QR code Ce QR code est invalide ! J\'ai un compte SIP tiers + Compte SIP tiers Single sign on Adresse SIP invalide L\'adresse SIP ne contient pas de nom d\'utilisateur ! Pas encore de compte ? Créer un compte + Confirmez votre numéro On vous a envoyé un code de vérification par SMS au numéro %1$s.\n\nMerci de le saisir ci-dessous : Numéro incorrect ? Créer diff --git a/app/src/main/res/values/dimen.xml b/app/src/main/res/values/dimen.xml index 12b62c7c6..6ca3ee344 100644 --- a/app/src/main/res/values/dimen.xml +++ b/app/src/main/res/values/dimen.xml @@ -14,6 +14,7 @@ 24dp 48dp 100dp + 30dp 30dp 45dp diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d7ea62242..aa82e83a6 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -141,11 +141,13 @@ Scan QR code Invalid QR code! Use a third party SIP account + Third party SIP account Single sign on SIP address is invalid! SIP address doesn\'t contains a username! No account yet? Register + Confirm your phone number We have sent a verification code on your phone number %1$s.\n\nPlease enter the verification code below: Wrong number? Create From 3e5a0c22f800541314cb216e1319afb56d8ea1e4 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Fri, 7 Feb 2025 14:13:43 +0100 Subject: [PATCH 27/89] Forgot to do the same for permissions layout --- .../assistant_landing_fragment.xml | 1 + .../assistant_permissions_fragment.xml | 253 ++++++++++++++++++ ...ant_register_confirm_sms_code_fragment.xml | 1 + .../assistant_register_fragment.xml | 1 + ...third_party_sip_account_login_fragment.xml | 3 +- ...ird_party_sip_account_warning_fragment.xml | 1 + 6 files changed, 258 insertions(+), 2 deletions(-) create mode 100644 app/src/main/res/layout-sw600dp/assistant_permissions_fragment.xml diff --git a/app/src/main/res/layout-sw600dp/assistant_landing_fragment.xml b/app/src/main/res/layout-sw600dp/assistant_landing_fragment.xml index 20dc81afb..bf23d5d9d 100644 --- a/app/src/main/res/layout-sw600dp/assistant_landing_fragment.xml +++ b/app/src/main/res/layout-sw600dp/assistant_landing_fragment.xml @@ -104,6 +104,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="16dp" + android:layout_marginTop="16dp" android:text="@{@string/sip_address + `*`, default=`SIP Address*`}" app:layout_constraintVertical_chainStyle="packed" app:layout_constraintTop_toBottomOf="@id/title" diff --git a/app/src/main/res/layout-sw600dp/assistant_permissions_fragment.xml b/app/src/main/res/layout-sw600dp/assistant_permissions_fragment.xml new file mode 100644 index 000000000..807b060f3 --- /dev/null +++ b/app/src/main/res/layout-sw600dp/assistant_permissions_fragment.xml @@ -0,0 +1,253 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-sw600dp/assistant_register_confirm_sms_code_fragment.xml b/app/src/main/res/layout-sw600dp/assistant_register_confirm_sms_code_fragment.xml index 5bfa7baf6..4155f69bb 100644 --- a/app/src/main/res/layout-sw600dp/assistant_register_confirm_sms_code_fragment.xml +++ b/app/src/main/res/layout-sw600dp/assistant_register_confirm_sms_code_fragment.xml @@ -60,6 +60,7 @@ android:layout_height="wrap_content" android:layout_marginStart="16dp" android:layout_marginEnd="16dp" + android:layout_marginTop="16dp" android:text="@{viewModel.confirmationMessage, default=@string/assistant_account_creation_sms_confirmation_explanation}" android:textSize="14sp" android:textColor="?attr/color_main2_600" diff --git a/app/src/main/res/layout-sw600dp/assistant_register_fragment.xml b/app/src/main/res/layout-sw600dp/assistant_register_fragment.xml index eacf7aa26..01782c0eb 100644 --- a/app/src/main/res/layout-sw600dp/assistant_register_fragment.xml +++ b/app/src/main/res/layout-sw600dp/assistant_register_fragment.xml @@ -73,6 +73,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="16dp" + android:layout_marginTop="16dp" android:text="@{@string/username + `*`}" app:layout_constraintVertical_chainStyle="packed" app:layout_constraintTop_toBottomOf="@id/title" diff --git a/app/src/main/res/layout-sw600dp/assistant_third_party_sip_account_login_fragment.xml b/app/src/main/res/layout-sw600dp/assistant_third_party_sip_account_login_fragment.xml index e064ddfd1..c194179c4 100644 --- a/app/src/main/res/layout-sw600dp/assistant_third_party_sip_account_login_fragment.xml +++ b/app/src/main/res/layout-sw600dp/assistant_third_party_sip_account_login_fragment.xml @@ -23,8 +23,6 @@ android:layout_width="match_parent" android:layout_height="wrap_content"> - - Date: Fri, 7 Feb 2025 15:34:10 +0100 Subject: [PATCH 28/89] Show video preview in calls list & conference participants list fragments --- .../ConferenceParticipantsListFragment.kt | 28 ++++++++++++++++ .../ui/call/fragment/CallsListFragment.kt | 32 +++++++++++++++++++ ..._conference_participants_list_fragment.xml | 13 ++++++++ .../main/res/layout/calls_list_fragment.xml | 13 ++++++++ 4 files changed, 86 insertions(+) diff --git a/app/src/main/java/org/linphone/ui/call/conference/fragment/ConferenceParticipantsListFragment.kt b/app/src/main/java/org/linphone/ui/call/conference/fragment/ConferenceParticipantsListFragment.kt index 74ff2398f..cefc3b11e 100644 --- a/app/src/main/java/org/linphone/ui/call/conference/fragment/ConferenceParticipantsListFragment.kt +++ b/app/src/main/java/org/linphone/ui/call/conference/fragment/ConferenceParticipantsListFragment.kt @@ -30,10 +30,12 @@ import android.view.ViewGroup import android.view.animation.Animation import android.view.animation.AnimationUtils import android.widget.PopupWindow +import androidx.core.view.doOnLayout import androidx.databinding.DataBindingUtil import androidx.lifecycle.ViewModelProvider import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.LinearLayoutManager +import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.R import org.linphone.core.Participant import org.linphone.core.tools.Log @@ -129,6 +131,32 @@ class ConferenceParticipantsListFragment : GenericCallFragment() { showKickParticipantDialog(displayName, participant) } } + + viewModel.isSendingVideo.observe(viewLifecycleOwner) { sending -> + coreContext.postOnCoreThread { core -> + core.nativePreviewWindowId = if (sending) { + Log.i("$TAG We are sending video, setting capture preview surface") + binding.localPreviewVideoSurface + } else { + Log.i("$TAG We are not sending video, clearing capture preview surface") + null + } + } + } + } + + override fun onResume() { + super.onResume() + + (binding.root as? ViewGroup)?.doOnLayout { + setupVideoPreview(binding.localPreviewVideoSurface) + } + } + + override fun onPause() { + super.onPause() + + cleanVideoPreview(binding.localPreviewVideoSurface) } private fun showKickParticipantDialog(displayName: String, participant: Participant) { diff --git a/app/src/main/java/org/linphone/ui/call/fragment/CallsListFragment.kt b/app/src/main/java/org/linphone/ui/call/fragment/CallsListFragment.kt index 7d3c5b621..32caa563a 100644 --- a/app/src/main/java/org/linphone/ui/call/fragment/CallsListFragment.kt +++ b/app/src/main/java/org/linphone/ui/call/fragment/CallsListFragment.kt @@ -23,14 +23,17 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.core.view.doOnLayout import androidx.lifecycle.ViewModelProvider import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.LinearLayoutManager import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.core.tools.Log import org.linphone.databinding.CallsListFragmentBinding import org.linphone.ui.call.adapter.CallsListAdapter import org.linphone.ui.call.viewmodel.CallsViewModel +import org.linphone.ui.call.viewmodel.CurrentCallViewModel import org.linphone.utils.ConfirmationDialogModel import org.linphone.utils.DialogUtils @@ -43,6 +46,8 @@ class CallsListFragment : GenericCallFragment() { private lateinit var viewModel: CallsViewModel + private lateinit var callViewModel: CurrentCallViewModel + private lateinit var adapter: CallsListAdapter private var bottomSheetDialog: BottomSheetDialogFragment? = null @@ -73,6 +78,11 @@ class CallsListFragment : GenericCallFragment() { binding.viewModel = viewModel observeToastEvents(viewModel) + callViewModel = requireActivity().run { + ViewModelProvider(this)[CurrentCallViewModel::class.java] + } + observeToastEvents(callViewModel) + binding.callsList.setHasFixedSize(true) binding.callsList.layoutManager = LinearLayoutManager(requireContext()) @@ -101,6 +111,18 @@ class CallsListFragment : GenericCallFragment() { showMergeCallsIntoConferenceConfirmationDialog() } + callViewModel.isSendingVideo.observe(viewLifecycleOwner) { sending -> + coreContext.postOnCoreThread { core -> + core.nativePreviewWindowId = if (sending) { + Log.i("$TAG We are sending video, setting capture preview surface") + binding.localPreviewVideoSurface + } else { + Log.i("$TAG We are not sending video, clearing capture preview surface") + null + } + } + } + viewModel.calls.observe(viewLifecycleOwner) { Log.i("$TAG Calls list updated with [${it.size}] items") adapter.submitList(it) @@ -113,11 +135,21 @@ class CallsListFragment : GenericCallFragment() { } } + override fun onResume() { + super.onResume() + + (binding.root as? ViewGroup)?.doOnLayout { + setupVideoPreview(binding.localPreviewVideoSurface) + } + } + override fun onPause() { super.onPause() bottomSheetDialog?.dismiss() bottomSheetDialog = null + + cleanVideoPreview(binding.localPreviewVideoSurface) } private fun showMergeCallsIntoConferenceConfirmationDialog() { diff --git a/app/src/main/res/layout/call_conference_participants_list_fragment.xml b/app/src/main/res/layout/call_conference_participants_list_fragment.xml index 5242d263e..bd4736758 100644 --- a/app/src/main/res/layout/call_conference_participants_list_fragment.xml +++ b/app/src/main/res/layout/call_conference_participants_list_fragment.xml @@ -82,6 +82,19 @@ app:layout_constraintTop_toBottomOf="@id/title" app:layout_constraintBottom_toBottomOf="parent"/> + + + + \ No newline at end of file From 7bf9eb8394d3d819c02c7598706fbcbb8e06e777 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Fri, 7 Feb 2025 15:44:17 +0100 Subject: [PATCH 29/89] Prevent starting a group call while already in call --- .../ui/main/history/viewmodel/StartCallViewModel.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/linphone/ui/main/history/viewmodel/StartCallViewModel.kt b/app/src/main/java/org/linphone/ui/main/history/viewmodel/StartCallViewModel.kt index 1be93d9f1..4cce533a7 100644 --- a/app/src/main/java/org/linphone/ui/main/history/viewmodel/StartCallViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/history/viewmodel/StartCallViewModel.kt @@ -149,9 +149,9 @@ class StartCallViewModel @UiThread fun updateGroupCallButtonVisibility() { coreContext.postOnCoreThread { core -> - val hideGroupCall = corePreferences.disableMeetings || !LinphoneUtils.isRemoteConferencingAvailable( - core - ) + val hideGroupCall = corePreferences.disableMeetings || + !LinphoneUtils.isRemoteConferencingAvailable(core) || + core.callsNb > 0 hideGroupCallButton.postValue(hideGroupCall) } } From 9381b459a0ca75b15e6ab3e94f053bc2d4c9ac06 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Fri, 7 Feb 2025 16:31:07 +0100 Subject: [PATCH 30/89] Removed participant joining label in active speaker miniature (no room for it) --- .../call_conference_active_speaker_cell.xml | 18 +++--------------- .../res/layout/call_conference_grid_cell.xml | 2 ++ 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/app/src/main/res/layout/call_conference_active_speaker_cell.xml b/app/src/main/res/layout/call_conference_active_speaker_cell.xml index 99fdeeb11..146a27b12 100644 --- a/app/src/main/res/layout/call_conference_active_speaker_cell.xml +++ b/app/src/main/res/layout/call_conference_active_speaker_cell.xml @@ -60,21 +60,7 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" - app:layout_constraintBottom_toTopOf="@id/joining_label" /> - - + app:layout_constraintBottom_toBottomOf="parent" /> Date: Fri, 7 Feb 2025 17:09:18 +0100 Subject: [PATCH 31/89] Fixed missing chat room if you created one and sent a message in it --- .../chat/viewmodel/ConversationsListViewModel.kt | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationsListViewModel.kt b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationsListViewModel.kt index 55313548b..e2ffd01ee 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationsListViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationsListViewModel.kt @@ -66,7 +66,17 @@ class ConversationsListViewModel @WorkerThread override fun onMessageSent(core: Core, chatRoom: ChatRoom, message: ChatMessage) { - reorderChatRooms() + val id = LinphoneUtils.getChatRoomId(chatRoom) + val found = conversations.value.orEmpty().find { + it.id == id + } + if (found == null) { + Log.i("$TAG Message sent for a conversation not yet in the list (probably was empty), adding it") + addChatRoom(chatRoom) + } else { + Log.i("$TAG Message sent for an existing conversation, re-order them") + reorderChatRooms() + } } @WorkerThread From ea79e9243d78ad53f1409e1aa9a0772798687a1e Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Mon, 10 Feb 2025 09:35:30 +0100 Subject: [PATCH 32/89] Changed account login wording to ask for username instead of SIP URI --- .../main/res/layout-sw600dp/assistant_landing_fragment.xml | 6 +++--- app/src/main/res/layout/assistant_landing_fragment.xml | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/main/res/layout-sw600dp/assistant_landing_fragment.xml b/app/src/main/res/layout-sw600dp/assistant_landing_fragment.xml index bf23d5d9d..78147ea66 100644 --- a/app/src/main/res/layout-sw600dp/assistant_landing_fragment.xml +++ b/app/src/main/res/layout-sw600dp/assistant_landing_fragment.xml @@ -105,7 +105,7 @@ android:layout_height="wrap_content" android:layout_marginEnd="16dp" android:layout_marginTop="16dp" - android:text="@{@string/sip_address + `*`, default=`SIP Address*`}" + android:text="@{@string/username + `*`, default=`Username*`}" app:layout_constraintVertical_chainStyle="packed" app:layout_constraintTop_toBottomOf="@id/title" app:layout_constraintStart_toStartOf="@id/sip_identity" @@ -120,12 +120,12 @@ android:layout_marginEnd="16dp" android:paddingStart="20dp" android:paddingEnd="20dp" - android:text="@={viewModel.sipIdentity, default=`sip:johndoe@sip.linphone.org`}" + android:text="@={viewModel.sipIdentity, default=`johndoe`}" android:textSize="14sp" android:textColor="?attr/color_main2_600" android:background="@drawable/edit_text_background" android:inputType="text|textNoSuggestions" - android:hint="@string/sip_address_hint" + android:hint="@string/username" app:layout_constraintWidth_max="@dimen/text_input_max_width" app:layout_constraintStart_toStartOf="@id/title" app:layout_constraintEnd_toEndOf="@id/title" diff --git a/app/src/main/res/layout/assistant_landing_fragment.xml b/app/src/main/res/layout/assistant_landing_fragment.xml index bf838b03c..cd726cf98 100644 --- a/app/src/main/res/layout/assistant_landing_fragment.xml +++ b/app/src/main/res/layout/assistant_landing_fragment.xml @@ -82,7 +82,7 @@ android:layout_height="wrap_content" android:layout_marginTop="18dp" android:layout_marginEnd="16dp" - android:text="@{@string/sip_address + `*`, default=`SIP Address*`}" + android:text="@{@string/username + `*`, default=`Username*`}" app:layout_constraintTop_toBottomOf="@id/title" app:layout_constraintStart_toStartOf="@id/sip_identity"/> @@ -95,12 +95,12 @@ android:layout_marginEnd="16dp" android:paddingStart="20dp" android:paddingEnd="20dp" - android:text="@={viewModel.sipIdentity, default=`sip:johndoe@sip.linphone.org`}" + android:text="@={viewModel.sipIdentity, default=`johndoe`}" android:textSize="14sp" android:textColor="?attr/color_main2_600" android:background="@drawable/edit_text_background" android:inputType="text|textNoSuggestions" - android:hint="@string/sip_address_hint" + android:hint="@string/username" app:layout_constraintWidth_max="@dimen/text_input_max_width" app:layout_constraintTop_toBottomOf="@id/sip_identity_label" app:layout_constraintStart_toStartOf="parent" From 22b447a67f6aa740826a5a1514286ce4f2f09fd9 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Thu, 6 Feb 2025 10:14:52 +0100 Subject: [PATCH 33/89] Using supported tags for third party accounts --- app/src/main/assets/assistant_linphone_default_values | 1 + app/src/main/assets/assistant_third_party_default_values | 1 + .../java/org/linphone/ui/main/chat/model/MessageDeliveryModel.kt | 1 + 3 files changed, 3 insertions(+) diff --git a/app/src/main/assets/assistant_linphone_default_values b/app/src/main/assets/assistant_linphone_default_values index e682dea1d..06ebab856 100644 --- a/app/src/main/assets/assistant_linphone_default_values +++ b/app/src/main/assets/assistant_linphone_default_values @@ -22,6 +22,7 @@ 1 https://lime.linphone.org/lime-server/lime-server.php c25519 +
stun.linphone.org diff --git a/app/src/main/assets/assistant_third_party_default_values b/app/src/main/assets/assistant_third_party_default_values index c0816ab3d..d19b3f504 100644 --- a/app/src/main/assets/assistant_third_party_default_values +++ b/app/src/main/assets/assistant_third_party_default_values @@ -22,6 +22,7 @@ 0 + outbound
stun.linphone.org diff --git a/app/src/main/java/org/linphone/ui/main/chat/model/MessageDeliveryModel.kt b/app/src/main/java/org/linphone/ui/main/chat/model/MessageDeliveryModel.kt index afd0a7b49..0721366e9 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/model/MessageDeliveryModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/model/MessageDeliveryModel.kt @@ -63,6 +63,7 @@ class MessageDeliveryModel message: ChatMessage, state: ParticipantImdnState ) { + Log.i("$TAG Participant IMDN state changed [${state.state}], updating delivery status") computeDeliveryStatus() } } From ba5786fa0a408984f214f2444ca984708b025dec Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Mon, 10 Feb 2025 10:55:18 +0100 Subject: [PATCH 34/89] Prevent sending multipart SIP message in basic chat rooms --- .../SendMessageInConversationViewModel.kt | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/SendMessageInConversationViewModel.kt b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/SendMessageInConversationViewModel.kt index c225aa2a2..0230335d5 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/SendMessageInConversationViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/SendMessageInConversationViewModel.kt @@ -248,6 +248,8 @@ class SendMessageInConversationViewModel @UiThread fun sendMessage() { coreContext.postOnCoreThread { + val isBasicChatRoom: Boolean = chatRoom.hasCapability(ChatRoom.Capabilities.Basic.toInt()) + val messageToReplyTo = chatMessageToReplyTo val message = if (messageToReplyTo != null) { Log.i("$TAG Sending message as reply to [${messageToReplyTo.messageId}]") @@ -255,10 +257,12 @@ class SendMessageInConversationViewModel } else { chatRoom.createEmptyMessage() } + var contentAdded = false val toSend = textToSend.value.orEmpty().trim() if (toSend.isNotEmpty()) { message.addUtf8TextContent(toSend) + contentAdded = true } if (isVoiceRecording.value == true && voiceMessageRecorder.file != null) { @@ -268,7 +272,14 @@ class SendMessageInConversationViewModel Log.i( "$TAG Voice recording content created, file name is ${content.name} and duration is ${content.fileDuration}" ) - message.addContent(content) + if (isBasicChatRoom && contentAdded) { + val voiceMessage = chatRoom.createEmptyMessage() + voiceMessage.addContent(content) + voiceMessage.send() + } else { + message.addContent(content) + contentAdded = true + } } else { Log.e("$TAG Voice recording content couldn't be created!") } @@ -293,7 +304,14 @@ class SendMessageInConversationViewModel // Let the file body handler take care of the upload content.filePath = attachment.path - message.addFileContent(content) + if (isBasicChatRoom && contentAdded) { + val fileMessage = chatRoom.createEmptyMessage() + fileMessage.addFileContent(content) + fileMessage.send() + } else { + message.addFileContent(content) + contentAdded = true + } } } From fede808df1071ab811d5b28947ce19cdfbb20919 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Mon, 10 Feb 2025 12:03:29 +0100 Subject: [PATCH 35/89] Fixed bluetooth audio device switch while on tablet --- .../ui/call/viewmodel/CurrentCallViewModel.kt | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/linphone/ui/call/viewmodel/CurrentCallViewModel.kt b/app/src/main/java/org/linphone/ui/call/viewmodel/CurrentCallViewModel.kt index 6b6052fef..7f64e4011 100644 --- a/app/src/main/java/org/linphone/ui/call/viewmodel/CurrentCallViewModel.kt +++ b/app/src/main/java/org/linphone/ui/call/viewmodel/CurrentCallViewModel.kt @@ -710,7 +710,11 @@ class CurrentCallViewModel val routeAudioToSpeaker = isSpeakerEnabled.value != true coreContext.postOnCoreThread { core -> + var earpieceFound = false val audioDevices = core.audioDevices + val currentDevice = currentCall.outputAudioDevice + Log.i("$TAG Currently used output audio device is [${currentDevice?.deviceName} (${currentDevice?.type}])") + val list = arrayListOf() for (device in audioDevices) { // Only list output audio devices @@ -718,6 +722,7 @@ class CurrentCallViewModel val name = when (device.type) { AudioDevice.Type.Earpiece -> { + earpieceFound = true AppUtils.getString(R.string.call_audio_device_type_earpiece) } AudioDevice.Type.Speaker -> { @@ -743,7 +748,6 @@ class CurrentCallViewModel } else -> device.deviceName } - val currentDevice = currentCall.outputAudioDevice val isCurrentlyInUse = device.type == currentDevice?.type && device.deviceName == currentDevice.deviceName val model = AudioDeviceModel(device, name, device.type, isCurrentlyInUse, true) { // onSelected @@ -769,8 +773,8 @@ class CurrentCallViewModel Log.i("$TAG Found audio device [${device.id}]") } - if (list.size > 2) { - Log.i("$TAG Found more than two devices, showing list to let user choose") + if (list.size > 2 || (list.size > 1 && !earpieceFound)) { + Log.i("$TAG Found more than two devices (or more than 1 but no earpiece), showing list to let user choose") showAudioDevicesListEvent.postValue(Event(list)) } else { Log.i( @@ -1216,6 +1220,7 @@ class CurrentCallViewModel @WorkerThread private fun updateOutputAudioDevice(audioDevice: AudioDevice?) { + Log.i("$TAG Output audio device updated to [${audioDevice?.deviceName} (${audioDevice?.type})]") isSpeakerEnabled.postValue(audioDevice?.type == AudioDevice.Type.Speaker) isHeadsetEnabled.postValue( audioDevice?.type == AudioDevice.Type.Headphones || audioDevice?.type == AudioDevice.Type.Headset From ff323cea683f397229d7159d2d7e561b9913c262 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Wed, 12 Feb 2025 09:44:49 +0100 Subject: [PATCH 36/89] Also prevent issue if device has no speaker --- .../org/linphone/ui/call/viewmodel/CurrentCallViewModel.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/linphone/ui/call/viewmodel/CurrentCallViewModel.kt b/app/src/main/java/org/linphone/ui/call/viewmodel/CurrentCallViewModel.kt index 7f64e4011..f1f72dbbc 100644 --- a/app/src/main/java/org/linphone/ui/call/viewmodel/CurrentCallViewModel.kt +++ b/app/src/main/java/org/linphone/ui/call/viewmodel/CurrentCallViewModel.kt @@ -711,6 +711,7 @@ class CurrentCallViewModel coreContext.postOnCoreThread { core -> var earpieceFound = false + var speakerFound = false val audioDevices = core.audioDevices val currentDevice = currentCall.outputAudioDevice Log.i("$TAG Currently used output audio device is [${currentDevice?.deviceName} (${currentDevice?.type}])") @@ -726,6 +727,7 @@ class CurrentCallViewModel AppUtils.getString(R.string.call_audio_device_type_earpiece) } AudioDevice.Type.Speaker -> { + speakerFound = true AppUtils.getString(R.string.call_audio_device_type_speaker) } AudioDevice.Type.Headset -> { @@ -773,8 +775,8 @@ class CurrentCallViewModel Log.i("$TAG Found audio device [${device.id}]") } - if (list.size > 2 || (list.size > 1 && !earpieceFound)) { - Log.i("$TAG Found more than two devices (or more than 1 but no earpiece), showing list to let user choose") + if (list.size > 2 || (list.size > 1 && (!earpieceFound || !speakerFound))) { + Log.i("$TAG Found more than two devices (or more than 1 but no earpiece or speaker), showing list to let user choose") showAudioDevicesListEvent.postValue(Event(list)) } else { Log.i( From 690140c2b83943a11cff6df6bd30cb722dd408d4 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Wed, 12 Feb 2025 09:58:23 +0100 Subject: [PATCH 37/89] Prevent attach file icon hidden when creating conversation using keyboard --- .../ui/main/chat/fragment/ConversationFragment.kt | 14 +++++++------- .../SendMessageInConversationViewModel.kt | 2 ++ 2 files changed, 9 insertions(+), 7 deletions(-) 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 index d9410b9c1..9f3628984 100644 --- 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 @@ -212,6 +212,13 @@ open class ConversationFragment : SlidingPaneChildFragment() { .viewTreeObserver .removeOnGlobalLayoutListener(this) + binding.root.setKeyboardInsetListener { keyboardVisible -> + sendMessageViewModel.isKeyboardOpen.value = keyboardVisible + if (keyboardVisible) { + sendMessageViewModel.isEmojiPickerOpen.value = false + } + } + if (::scrollListener.isInitialized) { binding.eventsList.addOnScrollListener(scrollListener) } @@ -938,13 +945,6 @@ open class ConversationFragment : SlidingPaneChildFragment() { } }) - binding.root.setKeyboardInsetListener { keyboardVisible -> - sendMessageViewModel.isKeyboardOpen.value = keyboardVisible - if (keyboardVisible) { - sendMessageViewModel.isEmojiPickerOpen.value = false - } - } - binding.sendArea.messageToSend.addTextChangedListener(textObserver) scrollListener = object : ConversationScrollListener(layoutManager) { diff --git a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/SendMessageInConversationViewModel.kt b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/SendMessageInConversationViewModel.kt index 0230335d5..230a78b56 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/SendMessageInConversationViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/SendMessageInConversationViewModel.kt @@ -154,8 +154,10 @@ class SendMessageInConversationViewModel isFileTransferServerAvailable.postValue(!core.fileTransferServer.isNullOrEmpty()) } + isKeyboardOpen.value = false isEmojiPickerOpen.value = false areFilePickersOpen.value = false + isVoiceRecording.value = false isPlayingVoiceRecord.value = false isCallConversation.value = false maxNumberOfAttachmentsReached.value = false From dd454113e8b3988502b835cfa5ed4fd414d7ab5c Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Wed, 12 Feb 2025 10:13:23 +0100 Subject: [PATCH 38/89] Prevent display issue if account has an empty display name --- app/src/main/java/org/linphone/utils/LinphoneUtils.kt | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/linphone/utils/LinphoneUtils.kt b/app/src/main/java/org/linphone/utils/LinphoneUtils.kt index 20837bc79..18ea91f98 100644 --- a/app/src/main/java/org/linphone/utils/LinphoneUtils.kt +++ b/app/src/main/java/org/linphone/utils/LinphoneUtils.kt @@ -96,7 +96,9 @@ class LinphoneUtils { @WorkerThread fun getDisplayName(address: Address?): String { if (address == null) return "[null]" - if (address.displayName == null) { + + val displayName = address.displayName + if (displayName.isNullOrEmpty()) { val account = coreContext.core.accountList.find { account -> account.params.identityAddress?.asStringUriOnly() == address.asStringUriOnly() } @@ -106,8 +108,13 @@ class LinphoneUtils { return localDisplayName } } + // Do not return an empty display name - return address.displayName ?: address.username ?: address.asString() + return if (displayName.isNullOrEmpty()) { + address.username ?: address.asString() + } else { + displayName + } } @WorkerThread From a210ea67c1dc976626aa45e7df6e98517b4bca9f Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Thu, 13 Feb 2025 09:22:06 +0100 Subject: [PATCH 39/89] Prevent choose address of phone number dialog not being dismissed once a user made a choice --- .../main/contacts/model/ContactNumberOrAddressModel.kt | 9 +++++++++ .../contacts/model/NumberOrAddressPickerDialogModel.kt | 5 +++++ 2 files changed, 14 insertions(+) diff --git a/app/src/main/java/org/linphone/ui/main/contacts/model/ContactNumberOrAddressModel.kt b/app/src/main/java/org/linphone/ui/main/contacts/model/ContactNumberOrAddressModel.kt index ffb1d8b80..eeb397e8f 100644 --- a/app/src/main/java/org/linphone/ui/main/contacts/model/ContactNumberOrAddressModel.kt +++ b/app/src/main/java/org/linphone/ui/main/contacts/model/ContactNumberOrAddressModel.kt @@ -39,15 +39,24 @@ class ContactNumberOrAddressModel ) { val selected = MutableLiveData() + private var actionDoneCallback: (() -> Unit)? = null + + @UiThread + fun setActionDoneCallback(lambda: () -> Unit) { + actionDoneCallback = lambda + } + @UiThread fun onClicked() { listener.onClicked(this) + actionDoneCallback?.invoke() } @UiThread fun onLongPress(): Boolean { selected.value = true listener.onLongPress(this) + actionDoneCallback?.invoke() return true } } diff --git a/app/src/main/java/org/linphone/ui/main/contacts/model/NumberOrAddressPickerDialogModel.kt b/app/src/main/java/org/linphone/ui/main/contacts/model/NumberOrAddressPickerDialogModel.kt index cecf31c79..ff4d21558 100644 --- a/app/src/main/java/org/linphone/ui/main/contacts/model/NumberOrAddressPickerDialogModel.kt +++ b/app/src/main/java/org/linphone/ui/main/contacts/model/NumberOrAddressPickerDialogModel.kt @@ -33,6 +33,11 @@ class NumberOrAddressPickerDialogModel val dismissEvent = MutableLiveData>() init { + for (model in list) { + model.setActionDoneCallback { + dismiss() + } + } sipAddressesAndPhoneNumbers.value = list } From a238ae8db08d30b7a387fd7899febfa0bc96f1f8 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Thu, 13 Feb 2025 10:19:40 +0100 Subject: [PATCH 40/89] Bumped dependencies --- gradle/libs.versions.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 816f7154a..6f558ca1e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,8 +2,8 @@ agp = "8.8.0" kotlin = "2.0.21" gmsGoogleServices = "4.4.2" -firebaseCrashlytics = "3.0.2" -firebaseBomVersion = "33.8.0" +firebaseCrashlytics = "3.0.3" +firebaseBomVersion = "33.9.0" ktlint = "12.1.2" annotations = "1.9.1" @@ -19,7 +19,7 @@ slidingpanelayout = "1.2.0" window = "1.3.0" gridlayout = "1.0.0" securityCryptoKtx = "1.1.0-alpha06" -navigation = "2.8.6" +navigation = "2.8.7" emoji2 = "1.5.0" car = "1.7.0-rc01" flexbox = "3.0.0" From 7ccd42580d89b56a60252bb808a8445587d1467f Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Thu, 13 Feb 2025 10:47:03 +0100 Subject: [PATCH 41/89] Handle remote provisioning failure from QR Code assistant --- .../assistant/fragment/QrCodeScannerFragment.kt | 14 ++++++++------ .../ui/assistant/viewmodel/QrCodeViewModel.kt | 15 +++++++++++++-- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/org/linphone/ui/assistant/fragment/QrCodeScannerFragment.kt b/app/src/main/java/org/linphone/ui/assistant/fragment/QrCodeScannerFragment.kt index e11e3201c..3bca100db 100644 --- a/app/src/main/java/org/linphone/ui/assistant/fragment/QrCodeScannerFragment.kt +++ b/app/src/main/java/org/linphone/ui/assistant/fragment/QrCodeScannerFragment.kt @@ -85,17 +85,19 @@ class QrCodeScannerFragment : GenericFragment() { viewModel.qrCodeFoundEvent.observe(viewLifecycleOwner) { it.consume { isValid -> - if (!isValid) { - (requireActivity() as GenericActivity).showRedToast( - getString(R.string.assistant_qr_code_invalid_toast), - R.drawable.warning_circle - ) - } else { + if (isValid) { requireActivity().finish() } } } + viewModel.onErrorEvent.observe(viewLifecycleOwner) { + it.consume { + // Core has restarted but something went wrong, restart video capture + enableQrCodeVideoScanner() + } + } + if (!isCameraPermissionGranted()) { if (ActivityCompat.shouldShowRequestPermissionRationale(requireActivity(), Manifest.permission.CAMERA)) { Log.w("$TAG CAMERA permission wasn't granted yet, asking for it now") diff --git a/app/src/main/java/org/linphone/ui/assistant/viewmodel/QrCodeViewModel.kt b/app/src/main/java/org/linphone/ui/assistant/viewmodel/QrCodeViewModel.kt index 69146fabd..f5a0da65d 100644 --- a/app/src/main/java/org/linphone/ui/assistant/viewmodel/QrCodeViewModel.kt +++ b/app/src/main/java/org/linphone/ui/assistant/viewmodel/QrCodeViewModel.kt @@ -30,6 +30,7 @@ import org.linphone.core.CoreListenerStub import org.linphone.core.tools.Log import org.linphone.ui.GenericViewModel import org.linphone.utils.Event +import org.linphone.R class QrCodeViewModel @UiThread @@ -40,12 +41,18 @@ class QrCodeViewModel val qrCodeFoundEvent = MutableLiveData>() + val onErrorEvent = MutableLiveData>() + private val coreListener = object : CoreListenerStub() { @WorkerThread override fun onConfiguringStatus(core: Core, status: ConfiguringState, message: String?) { Log.i("$TAG Configuring state is [$status]") if (status == ConfiguringState.Successful) { qrCodeFoundEvent.postValue(Event(true)) + } else if (status == ConfiguringState.Failed) { + Log.e("$TAG Failure applying remote provisioning: $message") + showRedToast(R.string.remote_provisioning_config_failed_toast, R.drawable.warning_circle) + onErrorEvent.postValue(Event(true)) } } @@ -53,16 +60,20 @@ class QrCodeViewModel override fun onQrcodeFound(core: Core, result: String?) { Log.i("$TAG QR Code found: [$result]") if (result == null) { - qrCodeFoundEvent.postValue(Event(false)) + showRedToast(R.string.assistant_qr_code_invalid_toast, R.drawable.warning_circle) } else { val isValidUrl = Patterns.WEB_URL.matcher(result).matches() if (!isValidUrl) { Log.e("$TAG The content of the QR Code doesn't seem to be a valid web URL") - qrCodeFoundEvent.postValue(Event(false)) + showRedToast(R.string.assistant_qr_code_invalid_toast, R.drawable.warning_circle) } else { Log.i( "$TAG QR code URL set, restarting the Core to apply configuration changes" ) + core.nativePreviewWindowId = null + core.isVideoPreviewEnabled = false + core.isQrcodeVideoPreviewEnabled = false + core.provisioningUri = result coreContext.core.stop() Log.i("$TAG Core has been stopped, restarting it") From 7e0c6a23a9ad212bd3879e31593f981a5edf0d4d Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Thu, 13 Feb 2025 11:29:26 +0100 Subject: [PATCH 42/89] Added hourglass icon for chat message in delivery pending state --- app/src/main/java/org/linphone/utils/LinphoneUtils.kt | 3 +++ app/src/main/res/drawable/hourglass.xml | 9 +++++++++ 2 files changed, 12 insertions(+) create mode 100644 app/src/main/res/drawable/hourglass.xml diff --git a/app/src/main/java/org/linphone/utils/LinphoneUtils.kt b/app/src/main/java/org/linphone/utils/LinphoneUtils.kt index 18ea91f98..7ab86b83d 100644 --- a/app/src/main/java/org/linphone/utils/LinphoneUtils.kt +++ b/app/src/main/java/org/linphone/utils/LinphoneUtils.kt @@ -404,6 +404,9 @@ class LinphoneUtils { ChatMessage.State.InProgress, ChatMessage.State.FileTransferInProgress -> { R.drawable.animated_in_progress } + ChatMessage.State.PendingDelivery -> { + R.drawable.hourglass + } else -> { R.drawable.animated_in_progress } diff --git a/app/src/main/res/drawable/hourglass.xml b/app/src/main/res/drawable/hourglass.xml new file mode 100644 index 000000000..8761682e1 --- /dev/null +++ b/app/src/main/res/drawable/hourglass.xml @@ -0,0 +1,9 @@ + + + From 40e9dfc522a5b0cb5571041e50194a136d357246 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Thu, 13 Feb 2025 11:33:44 +0100 Subject: [PATCH 43/89] Fixed small UI issue if SIP URI is too long --- app/src/main/res/layout/contact_number_address_list_cell.xml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/layout/contact_number_address_list_cell.xml b/app/src/main/res/layout/contact_number_address_list_cell.xml index be0864074..0ed2f201b 100644 --- a/app/src/main/res/layout/contact_number_address_list_cell.xml +++ b/app/src/main/res/layout/contact_number_address_list_cell.xml @@ -35,14 +35,16 @@ Date: Thu, 13 Feb 2025 11:46:43 +0100 Subject: [PATCH 44/89] Improved logs --- .../chat/viewmodel/ConversationForwardMessageViewModel.kt | 2 +- .../ui/main/chat/viewmodel/StartConversationViewModel.kt | 5 +++-- .../ui/main/fragment/GenericAddressPickerFragment.kt | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationForwardMessageViewModel.kt b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationForwardMessageViewModel.kt index 1ef593232..1e65b9081 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationForwardMessageViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationForwardMessageViewModel.kt @@ -149,7 +149,7 @@ class ConversationForwardMessageViewModel val friend = model.friend if (friend == null) { - Log.i("$TAG Friend is null, using address [${model.address}]") + Log.i("$TAG Friend is null, using address [${model.address.asStringUriOnly()}]") onAddressSelected(model.address) return@postOnCoreThread } diff --git a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/StartConversationViewModel.kt b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/StartConversationViewModel.kt index 9ef149325..d80d28cba 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/StartConversationViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/StartConversationViewModel.kt @@ -240,7 +240,8 @@ class StartConversationViewModel val chatRoom = core.createChatRoom(params, localAddress, participants) if (chatRoom != null) { if (chatParams.backend == ChatRoom.Backend.FlexisipChat) { - if (chatRoom.state == ChatRoom.State.Created) { + val state = chatRoom.state + if (state == ChatRoom.State.Created) { val id = LinphoneUtils.getChatRoomId(chatRoom) Log.i("$TAG 1-1 conversation [$id] has been created") operationInProgress.postValue(false) @@ -253,7 +254,7 @@ class StartConversationViewModel ) ) } else { - Log.i("$TAG Conversation isn't in Created state yet, wait for it") + Log.i("$TAG Conversation isn't in Created state yet (state is [$state]), wait for it") chatRoom.addListener(chatRoomListener) } } else { diff --git a/app/src/main/java/org/linphone/ui/main/fragment/GenericAddressPickerFragment.kt b/app/src/main/java/org/linphone/ui/main/fragment/GenericAddressPickerFragment.kt index 65bebfd45..23c599f57 100644 --- a/app/src/main/java/org/linphone/ui/main/fragment/GenericAddressPickerFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/fragment/GenericAddressPickerFragment.kt @@ -167,7 +167,7 @@ abstract class GenericAddressPickerFragment : GenericMainFragment() { coreContext.postOnCoreThread { core -> val friend = model.friend if (friend == null) { - Log.i("$TAG Friend is null, using address [${model.address}]") + Log.i("$TAG Friend is null, using address [${model.address.asStringUriOnly()}]") val fakeFriend = core.createFriend() fakeFriend.addAddress(model.address) onAddressSelected(model.address, fakeFriend) From 8c1889b1810281c1f67344478eb04d35ed7848ba Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Thu, 13 Feb 2025 11:59:59 +0100 Subject: [PATCH 45/89] Fixed duplicated listeners when doing remove provisioning --- app/src/main/java/org/linphone/core/CoreContext.kt | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/org/linphone/core/CoreContext.kt b/app/src/main/java/org/linphone/core/CoreContext.kt index 27cf540d6..3395fdf04 100644 --- a/app/src/main/java/org/linphone/core/CoreContext.kt +++ b/app/src/main/java/org/linphone/core/CoreContext.kt @@ -163,6 +163,8 @@ class CoreContext // Wait for GlobalState.ON as some settings modification won't be saved // in RC file if Core isn't ON onCoreStarted() + } else if (state == GlobalState.Shutdown) { + onCoreStopped() } } @@ -500,6 +502,14 @@ class CoreContext Log.i("$TAG Started contacts, telecom & notifications managers") } + @WorkerThread + private fun onCoreStopped() { + Log.w("$TAG Core is being shut down, notifying managers so they can remove their listeners and do some cleanup if needed") + contactsManager.onCoreStopped(core) + telecomManager.onCoreStopped(core) + notificationsManager.onCoreStopped(core) + } + @WorkerThread private fun destroyCore() { if (!::core.isInitialized) { @@ -522,10 +532,6 @@ class CoreContext core.stop() - contactsManager.onCoreStopped(core) - telecomManager.onCoreStopped(core) - notificationsManager.onCoreStopped(core) - // It's very unlikely the process will survive until the Core reaches GlobalStateOff sadly Log.w("$TAG Core has been shut down") exitProcess(0) From e292b0d7e84635d8bf77d583d774d7d9052d7c14 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Thu, 13 Feb 2025 14:17:28 +0100 Subject: [PATCH 46/89] Fixed some scenario where account(s) reload isn't done --- .../ui/main/viewmodel/AbstractMainViewModel.kt | 18 +++++------------- .../DefaultAccountChangedViewModel.kt | 14 ++++++++++++++ .../ui/main/viewmodel/DrawerMenuViewModel.kt | 14 ++++++++++---- 3 files changed, 29 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/org/linphone/ui/main/viewmodel/AbstractMainViewModel.kt b/app/src/main/java/org/linphone/ui/main/viewmodel/AbstractMainViewModel.kt index 4835ab985..1b3b82b5c 100644 --- a/app/src/main/java/org/linphone/ui/main/viewmodel/AbstractMainViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/viewmodel/AbstractMainViewModel.kt @@ -28,9 +28,9 @@ import org.linphone.core.Account import org.linphone.core.Call import org.linphone.core.ChatMessage import org.linphone.core.ChatRoom -import org.linphone.core.ConfiguringState import org.linphone.core.Core import org.linphone.core.CoreListenerStub +import org.linphone.core.GlobalState import org.linphone.core.tools.Log import org.linphone.ui.GenericViewModel import org.linphone.ui.main.model.AccountModel @@ -139,18 +139,10 @@ open class AbstractMainViewModel } @WorkerThread - override fun onConfiguringStatus(core: Core, status: ConfiguringState?, message: String?) { - if (status != ConfiguringState.Skipped) { - account.value?.destroy() - - val defaultAccount = core.defaultAccount - if (defaultAccount != null) { - Log.i("$TAG Configuring status is [$status], reload default account") - account.postValue(AccountModel(defaultAccount)) - defaultAccountChangedEvent.postValue(Event(true)) - } else { - Log.w("$TAG Configuring status is [$status] but no default account was found!") - } + override fun onGlobalStateChanged(core: Core, state: GlobalState?, message: String) { + if (core.globalState == GlobalState.On) { + Log.i("$TAG Global state is [${core.globalState}], reload account info") + configure() } } diff --git a/app/src/main/java/org/linphone/ui/main/viewmodel/DefaultAccountChangedViewModel.kt b/app/src/main/java/org/linphone/ui/main/viewmodel/DefaultAccountChangedViewModel.kt index 82f579b18..9862d7139 100644 --- a/app/src/main/java/org/linphone/ui/main/viewmodel/DefaultAccountChangedViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/viewmodel/DefaultAccountChangedViewModel.kt @@ -25,10 +25,16 @@ import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.core.Account import org.linphone.core.Core import org.linphone.core.CoreListenerStub +import org.linphone.core.GlobalState +import org.linphone.core.tools.Log import org.linphone.ui.GenericViewModel import org.linphone.utils.Event open class DefaultAccountChangedViewModel : GenericViewModel() { + companion object { + private const val TAG = "[Default Account Changed ViewModel]" + } + val defaultAccountChangedEvent: MutableLiveData> by lazy { MutableLiveData>() } @@ -38,6 +44,14 @@ open class DefaultAccountChangedViewModel : GenericViewModel() { override fun onDefaultAccountChanged(core: Core, account: Account?) { defaultAccountChangedEvent.postValue(Event(true)) } + + @WorkerThread + override fun onGlobalStateChanged(core: Core, state: GlobalState?, message: String) { + if (core.globalState == GlobalState.On) { + Log.i("$TAG Global state is [${core.globalState}], reload default account") + defaultAccountChangedEvent.postValue(Event(true)) + } + } } init { diff --git a/app/src/main/java/org/linphone/ui/main/viewmodel/DrawerMenuViewModel.kt b/app/src/main/java/org/linphone/ui/main/viewmodel/DrawerMenuViewModel.kt index fef062b8c..54cd3452f 100644 --- a/app/src/main/java/org/linphone/ui/main/viewmodel/DrawerMenuViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/viewmodel/DrawerMenuViewModel.kt @@ -26,9 +26,9 @@ import androidx.lifecycle.MutableLiveData import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.LinphoneApplication.Companion.corePreferences import org.linphone.core.Account -import org.linphone.core.ConfiguringState import org.linphone.core.Core import org.linphone.core.CoreListenerStub +import org.linphone.core.GlobalState import org.linphone.core.tools.Log import org.linphone.ui.GenericViewModel import org.linphone.ui.main.model.AccountModel @@ -109,11 +109,11 @@ class DrawerMenuViewModel } @WorkerThread - override fun onConfiguringStatus(core: Core, status: ConfiguringState?, message: String?) { - if (status != ConfiguringState.Skipped) { + override fun onGlobalStateChanged(core: Core, state: GlobalState?, message: String) { + if (core.globalState == GlobalState.On) { accounts.value.orEmpty().forEach(AccountModel::destroy) - Log.i("$TAG Configuring status is [$status], reload accounts & shortcuts") + Log.i("$TAG Global state is [${core.globalState}], reload accounts & shortcuts") computeAccountsList() computeShortcuts() } @@ -180,6 +180,12 @@ class DrawerMenuViewModel showAccountPopupMenuEvent.postValue(Event(Pair(view, account))) } list.add(model) + + if (account == coreContext.core.defaultAccount) { + defaultAccountChangedEvent.postValue( + Event(account.params.identityAddress?.asStringUriOnly() ?: "") + ) + } } accounts.postValue(list) From ca08ed68bed806140abb57a7a13ed7accebde285 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Mon, 17 Feb 2025 09:27:09 +0100 Subject: [PATCH 47/89] Updated coil to 3.1.0 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6f558ca1e..aca3e0e0e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -25,7 +25,7 @@ car = "1.7.0-rc01" flexbox = "3.0.0" material = "1.12.0" protobuf = "3.25.5" -coil = "3.0.4" +coil = "3.1.0" dotsIndicator = "5.1.0" photoview = "2.3.0" openidAppauth = "0.11.1" From 17511a4c26f4f3602392aa731a1d4005f5e7c817 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Thu, 13 Feb 2025 16:02:01 +0100 Subject: [PATCH 48/89] Refactored app to use chatRoom.identifier from SDK instead of computing it from local & peer address --- .../compatibility/Api29Compatibility.kt | 6 +- .../linphone/compatibility/Compatibility.kt | 5 +- .../notifications/NotificationsManager.kt | 29 ++++---- .../fragment/ActiveConferenceCallFragment.kt | 11 +-- .../viewmodel/ConferenceViewModel.kt | 14 ++-- .../ui/call/fragment/ActiveCallFragment.kt | 9 +-- .../ui/call/viewmodel/CurrentCallViewModel.kt | 56 ++++----------- .../ui/fileviewer/MediaViewerActivity.kt | 7 +- .../viewmodel/MediaListViewModel.kt | 2 +- .../java/org/linphone/ui/main/MainActivity.kt | 52 ++++++-------- ...ationsContactsAndSuggestionsListAdapter.kt | 2 +- .../ConversationDocumentsListFragment.kt | 12 ++-- .../ConversationForwardMessageFragment.kt | 12 +--- .../chat/fragment/ConversationFragment.kt | 23 +++---- .../chat/fragment/ConversationInfoFragment.kt | 15 ++-- .../fragment/ConversationMediaListFragment.kt | 12 ++-- .../fragment/ConversationsListFragment.kt | 28 +++----- .../fragment/StartConversationFragment.kt | 6 +- .../ui/main/chat/model/ConversationModel.kt | 2 +- .../AbstractConversationViewModel.kt | 38 +++-------- .../ConversationDocumentsListViewModel.kt | 2 +- .../ConversationForwardMessageViewModel.kt | 57 +++------------- .../viewmodel/ConversationInfoViewModel.kt | 12 ++-- .../ConversationMediaListViewModel.kt | 2 +- .../chat/viewmodel/ConversationViewModel.kt | 2 +- .../viewmodel/ConversationsListViewModel.kt | 6 +- .../viewmodel/StartConversationViewModel.kt | 68 ++++--------------- .../main/contacts/fragment/ContactFragment.kt | 6 +- .../contacts/viewmodel/ContactViewModel.kt | 43 +++--------- .../main/history/fragment/HistoryFragment.kt | 15 ++-- .../history/viewmodel/HistoryViewModel.kt | 56 ++++----------- .../ConversationContactOrSuggestionModel.kt | 2 +- .../viewmodel/AddressSelectionViewModel.kt | 4 +- .../ui/main/viewmodel/SharedMainViewModel.kt | 5 +- .../java/org/linphone/utils/LinphoneUtils.kt | 36 +--------- .../java/org/linphone/utils/ShortcutUtils.kt | 22 +++--- .../main/res/navigation/call_nav_graph.xml | 5 +- .../main/res/navigation/chat_nav_graph.xml | 20 ++---- .../main/res/navigation/history_nav_graph.xml | 5 +- 39 files changed, 208 insertions(+), 501 deletions(-) diff --git a/app/src/main/java/org/linphone/compatibility/Api29Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Api29Compatibility.kt index d7bcd9d9a..6ef9f7163 100644 --- a/app/src/main/java/org/linphone/compatibility/Api29Compatibility.kt +++ b/app/src/main/java/org/linphone/compatibility/Api29Compatibility.kt @@ -27,7 +27,6 @@ import android.view.View import android.view.contentcapture.ContentCaptureContext import android.view.contentcapture.ContentCaptureSession import androidx.annotation.RequiresApi -import org.linphone.utils.LinphoneUtils @RequiresApi(Build.VERSION_CODES.Q) class Api29Compatibility { @@ -59,11 +58,10 @@ class Api29Compatibility { return intent.getStringExtra(Intent.EXTRA_LOCUS_ID) } - fun setLocusIdInContentCaptureSession(root: View, localSipUri: String, remoteSipUri: String) { + fun setLocusIdInContentCaptureSession(root: View, conversationId: String) { val session: ContentCaptureSession? = root.contentCaptureSession if (session != null) { - val id = LinphoneUtils.getChatRoomId(localSipUri, remoteSipUri) - session.contentCaptureContext = ContentCaptureContext.forLocusId(id) + session.contentCaptureContext = ContentCaptureContext.forLocusId(conversationId) } } } diff --git a/app/src/main/java/org/linphone/compatibility/Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Compatibility.kt index f10ad8e00..ee005791d 100644 --- a/app/src/main/java/org/linphone/compatibility/Compatibility.kt +++ b/app/src/main/java/org/linphone/compatibility/Compatibility.kt @@ -159,12 +159,11 @@ class Compatibility { return null } - fun setLocusIdInContentCaptureSession(root: View, localSipUri: String, remoteSipUri: String) { + fun setLocusIdInContentCaptureSession(root: View, conversationId: String) { if (Version.sdkAboveOrEqual(Version.API29_ANDROID_10)) { return Api29Compatibility.setLocusIdInContentCaptureSession( root, - localSipUri, - remoteSipUri + conversationId ) } } diff --git a/app/src/main/java/org/linphone/notifications/NotificationsManager.kt b/app/src/main/java/org/linphone/notifications/NotificationsManager.kt index 0e3bd47a3..0cac92344 100644 --- a/app/src/main/java/org/linphone/notifications/NotificationsManager.kt +++ b/app/src/main/java/org/linphone/notifications/NotificationsManager.kt @@ -71,6 +71,8 @@ import org.linphone.core.MediaDirection import org.linphone.core.tools.Log import org.linphone.ui.call.CallActivity import org.linphone.ui.main.MainActivity +import org.linphone.ui.main.MainActivity.Companion.ARGUMENTS_CHAT +import org.linphone.ui.main.MainActivity.Companion.ARGUMENTS_CONVERSATION_ID import org.linphone.utils.AppUtils import org.linphone.utils.FileUtils import org.linphone.utils.LinphoneUtils @@ -262,7 +264,7 @@ class NotificationsManager Log.i("$TAG Received ${messages.size} aggregated messages") if (corePreferences.disableChat) return - val id = LinphoneUtils.getChatRoomId(chatRoom) + val id = LinphoneUtils.getConversationId(chatRoom) if (currentlyDisplayedChatRoomId.isNotEmpty() && id == currentlyDisplayedChatRoomId) { Log.i( "$TAG Do not notify received messages for currently displayed conversation [$id]" @@ -301,7 +303,7 @@ class NotificationsManager "$TAG Reaction received [${reaction.body}] from [${address.asStringUriOnly()}] for message [$message]" ) - val id = LinphoneUtils.getChatRoomId(chatRoom) + val id = LinphoneUtils.getConversationId(chatRoom) /*if (id == currentlyDisplayedChatRoomId) { Log.i( "$TAG Do not notify received reaction for currently displayed conversation [$id]" @@ -340,7 +342,7 @@ class NotificationsManager if (corePreferences.disableChat) return if (chatRoom.muted) { - val id = LinphoneUtils.getChatRoomId(chatRoom) + val id = LinphoneUtils.getConversationId(chatRoom) Log.i("$TAG Conversation $id has been muted") return } @@ -370,7 +372,7 @@ class NotificationsManager val notification = createMessageNotification( notifiable, pendingIntent, - LinphoneUtils.getChatRoomId(chatRoom), + LinphoneUtils.getConversationId(chatRoom), me ) notify(notifiable.notificationId, notification, CHAT_TAG) @@ -389,7 +391,7 @@ class NotificationsManager @WorkerThread override fun onChatRoomRead(core: Core, chatRoom: ChatRoom) { Log.i( - "$TAG Conversation [${LinphoneUtils.getChatRoomId(chatRoom)}] has been marked as read, removing notification if any" + "$TAG Conversation [${LinphoneUtils.getConversationId(chatRoom)}] has been marked as read, removing notification if any" ) dismissChatNotification(chatRoom) } @@ -786,7 +788,7 @@ class NotificationsManager val address = chatRoom.peerAddress.asStringUriOnly() var notifiable: Notifiable? = chatNotificationsMap[address] if (notifiable == null) { - notifiable = Notifiable(LinphoneUtils.getChatRoomId(chatRoom).hashCode()) + notifiable = Notifiable(LinphoneUtils.getConversationId(chatRoom).hashCode()) notifiable.myself = LinphoneUtils.getDisplayName(chatRoom.localAddress) notifiable.localIdentity = chatRoom.localAddress.asStringUriOnly() notifiable.remoteAddress = chatRoom.peerAddress.asStringUriOnly() @@ -834,7 +836,7 @@ class NotificationsManager val notification = createMessageNotification( notifiable, pendingIntent, - LinphoneUtils.getChatRoomId(chatRoom), + LinphoneUtils.getConversationId(chatRoom), me ) notify(notifiable.notificationId, notification, CHAT_TAG) @@ -899,7 +901,7 @@ class NotificationsManager val notification = createMessageNotification( notifiable, pendingIntent, - LinphoneUtils.getChatRoomId(chatRoom), + LinphoneUtils.getConversationId(chatRoom), me ) notify(notifiable.notificationId, notification, CHAT_TAG) @@ -928,7 +930,7 @@ class NotificationsManager val notification = createMessageNotification( notifiable, pendingIntent, - LinphoneUtils.getChatRoomId(chatRoom), + LinphoneUtils.getConversationId(chatRoom), me ) Log.i( @@ -1295,7 +1297,7 @@ class NotificationsManager return true } else { val previousNotificationId = previousChatNotifications.find { id -> - id == LinphoneUtils.getChatRoomId(chatRoom).hashCode() + id == LinphoneUtils.getConversationId(chatRoom).hashCode() } if (previousNotificationId != null) { Log.i( @@ -1362,7 +1364,7 @@ class NotificationsManager val notification = createMessageNotification( notifiable, pendingIntent, - LinphoneUtils.getChatRoomId(chatRoom), + LinphoneUtils.getConversationId(chatRoom), me ) notify(notifiable.notificationId, notification, CHAT_TAG) @@ -1585,9 +1587,8 @@ class NotificationsManager @WorkerThread private fun getChatRoomPendingIntent(chatRoom: ChatRoom, notificationId: Int): PendingIntent { val args = Bundle() - args.putBoolean("Chat", true) - args.putString("RemoteSipUri", chatRoom.peerAddress.asStringUriOnly()) - args.putString("LocalSipUri", chatRoom.localAddress.asStringUriOnly()) + args.putBoolean(ARGUMENTS_CHAT, true) + args.putString(ARGUMENTS_CONVERSATION_ID, LinphoneUtils.getConversationId(chatRoom)) // Not using NavDeepLinkBuilder to prevent stacking a ConversationsListFragment above another one return TaskStackBuilder.create(context).run { diff --git a/app/src/main/java/org/linphone/ui/call/conference/fragment/ActiveConferenceCallFragment.kt b/app/src/main/java/org/linphone/ui/call/conference/fragment/ActiveConferenceCallFragment.kt index 7a011f820..342263f3a 100644 --- a/app/src/main/java/org/linphone/ui/call/conference/fragment/ActiveConferenceCallFragment.kt +++ b/app/src/main/java/org/linphone/ui/call/conference/fragment/ActiveConferenceCallFragment.kt @@ -227,17 +227,12 @@ class ActiveConferenceCallFragment : GenericCallFragment() { } callViewModel.conferenceModel.goToConversationEvent.observe(viewLifecycleOwner) { - it.consume { pair -> + it.consume { conversationId -> if (findNavController().currentDestination?.id == R.id.activeConferenceCallFragment) { - val localSipUri = pair.first - val remoteSipUri = pair.second - Log.i( - "$TAG Display conversation with local SIP URI [$localSipUri] and remote SIP URI [$remoteSipUri]" - ) + Log.i("$TAG Display conversation with conversation ID [$conversationId]") val action = ActiveConferenceCallFragmentDirections.actionActiveConferenceCallFragmentToInCallConversationFragment( - localSipUri, - remoteSipUri + conversationId ) findNavController().navigate(action) } diff --git a/app/src/main/java/org/linphone/ui/call/conference/viewmodel/ConferenceViewModel.kt b/app/src/main/java/org/linphone/ui/call/conference/viewmodel/ConferenceViewModel.kt index 6bfc4d5b1..65d4c76e9 100644 --- a/app/src/main/java/org/linphone/ui/call/conference/viewmodel/ConferenceViewModel.kt +++ b/app/src/main/java/org/linphone/ui/call/conference/viewmodel/ConferenceViewModel.kt @@ -39,6 +39,7 @@ import org.linphone.ui.call.conference.model.ConferenceParticipantModel import org.linphone.ui.call.conference.view.GridBoxLayout import org.linphone.utils.AppUtils import org.linphone.utils.Event +import org.linphone.utils.LinphoneUtils class ConferenceViewModel @UiThread @@ -91,8 +92,8 @@ class ConferenceViewModel MutableLiveData>>() } - val goToConversationEvent: MutableLiveData>> by lazy { - MutableLiveData>>() + val goToConversationEvent: MutableLiveData> by lazy { + MutableLiveData>() } private lateinit var conference: Conference @@ -369,14 +370,7 @@ class ConferenceViewModel Log.i("$TAG Navigating to conference's conversation") val chatRoom = conference.chatRoom if (chatRoom != null) { - goToConversationEvent.postValue( - Event( - Pair( - chatRoom.localAddress.asStringUriOnly(), - chatRoom.peerAddress.asStringUriOnly() - ) - ) - ) + goToConversationEvent.postValue(Event(LinphoneUtils.getConversationId(chatRoom))) } else { Log.e( "$TAG No chat room available for current conference [${conference.conferenceAddress?.asStringUriOnly()}]" 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 4a188201a..7cd1a9137 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 @@ -371,17 +371,14 @@ class ActiveCallFragment : GenericCallFragment() { } callViewModel.goToConversationEvent.observe(viewLifecycleOwner) { - it.consume { pair -> + it.consume { conversationId -> if (findNavController().currentDestination?.id == R.id.activeCallFragment) { - val localSipUri = pair.first - val remoteSipUri = pair.second Log.i( - "$TAG Display conversation with local SIP URI [$localSipUri] and remote SIP URI [$remoteSipUri]" + "$TAG Display conversation with conversation ID [$conversationId]" ) val action = ActiveCallFragmentDirections.actionActiveCallFragmentToInCallConversationFragment( - localSipUri, - remoteSipUri + conversationId ) findNavController().navigate(action) } diff --git a/app/src/main/java/org/linphone/ui/call/viewmodel/CurrentCallViewModel.kt b/app/src/main/java/org/linphone/ui/call/viewmodel/CurrentCallViewModel.kt index f1f72dbbc..483bf1303 100644 --- a/app/src/main/java/org/linphone/ui/call/viewmodel/CurrentCallViewModel.kt +++ b/app/src/main/java/org/linphone/ui/call/viewmodel/CurrentCallViewModel.kt @@ -199,8 +199,8 @@ class CurrentCallViewModel val operationInProgress = MutableLiveData() - val goToConversationEvent: MutableLiveData>> by lazy { - MutableLiveData>>() + val goToConversationEvent: MutableLiveData> by lazy { + MutableLiveData>() } val chatRoomCreationErrorEvent: MutableLiveData> by lazy { @@ -394,21 +394,14 @@ class CurrentCallViewModel val state = chatRoom.state if (state == ChatRoom.State.Instantiated) return - val id = LinphoneUtils.getChatRoomId(chatRoom) + val id = LinphoneUtils.getConversationId(chatRoom) Log.i("$TAG Conversation [$id] (${chatRoom.subject}) state changed: [$state]") if (state == ChatRoom.State.Created) { Log.i("$TAG Conversation [$id] successfully created") chatRoom.removeListener(this) operationInProgress.postValue(false) - goToConversationEvent.postValue( - Event( - Pair( - chatRoom.localAddress.asStringUriOnly(), - chatRoom.peerAddress.asStringUriOnly() - ) - ) - ) + goToConversationEvent.postValue(Event(LinphoneUtils.getConversationId(chatRoom))) } else if (state == ChatRoom.State.CreationFailed) { Log.e("$TAG Conversation [$id] creation has failed!") chatRoom.removeListener(this) @@ -935,17 +928,10 @@ class CurrentCallViewModel if (existingConversation != null) { Log.i( "$TAG Found existing conversation [${ - LinphoneUtils.getChatRoomId(existingConversation) + LinphoneUtils.getConversationId(existingConversation) }], going to it" ) - goToConversationEvent.postValue( - Event( - Pair( - existingConversation.localAddress.asStringUriOnly(), - existingConversation.peerAddress.asStringUriOnly() - ) - ) - ) + goToConversationEvent.postValue(Event(LinphoneUtils.getConversationId(existingConversation))) } else { Log.i("$TAG No existing conversation was found, let's create it") createCurrentCallConversation(currentCall) @@ -1372,37 +1358,23 @@ class CurrentCallViewModel operationInProgress.postValue(true) val params = getChatRoomParams(call) ?: return // TODO: show error to user - val conversation = core.createChatRoom(params, localAddress, participants) - if (conversation != null) { + val chatRoom = core.createChatRoom(params, localAddress, participants) + if (chatRoom != null) { if (params.chatParams?.backend == ChatRoom.Backend.FlexisipChat) { - if (conversation.state == ChatRoom.State.Created) { - val id = LinphoneUtils.getChatRoomId(conversation) + if (chatRoom.state == ChatRoom.State.Created) { + val id = LinphoneUtils.getConversationId(chatRoom) Log.i("$TAG 1-1 conversation [$id] has been created") operationInProgress.postValue(false) - goToConversationEvent.postValue( - Event( - Pair( - conversation.localAddress.asStringUriOnly(), - conversation.peerAddress.asStringUriOnly() - ) - ) - ) + goToConversationEvent.postValue(Event(LinphoneUtils.getConversationId(chatRoom))) } else { Log.i("$TAG Conversation isn't in Created state yet, wait for it") - conversation.addListener(chatRoomListener) + chatRoom.addListener(chatRoomListener) } } else { - val id = LinphoneUtils.getChatRoomId(conversation) + val id = LinphoneUtils.getConversationId(chatRoom) Log.i("$TAG Conversation successfully created [$id]") operationInProgress.postValue(false) - goToConversationEvent.postValue( - Event( - Pair( - conversation.localAddress.asStringUriOnly(), - conversation.peerAddress.asStringUriOnly() - ) - ) - ) + goToConversationEvent.postValue(Event(LinphoneUtils.getConversationId(chatRoom))) } } else { Log.e( diff --git a/app/src/main/java/org/linphone/ui/fileviewer/MediaViewerActivity.kt b/app/src/main/java/org/linphone/ui/fileviewer/MediaViewerActivity.kt index 6cc4e8d55..95b831db0 100644 --- a/app/src/main/java/org/linphone/ui/fileviewer/MediaViewerActivity.kt +++ b/app/src/main/java/org/linphone/ui/fileviewer/MediaViewerActivity.kt @@ -101,12 +101,11 @@ class MediaViewerActivity : GenericActivity() { val originalPath = args.getString("originalPath", "") viewModel.initTempModel(path, timestamp, isEncrypted, originalPath) - val localSipUri = args.getString("localSipUri").orEmpty() - val remoteSipUri = args.getString("remoteSipUri").orEmpty() + val conversationId = args.getString("conversationId").orEmpty() Log.i( - "$TAG Looking up for conversation with local SIP URI [$localSipUri] and remote SIP URI [$remoteSipUri] trying to display file [$path]" + "$TAG Looking up for conversation with conversation ID [$conversationId] trying to display file [$path]" ) - viewModel.findChatRoom(null, localSipUri, remoteSipUri) + viewModel.findChatRoom(null, conversationId) viewModel.mediaList.observe(this) { updateMediaList(path, it) diff --git a/app/src/main/java/org/linphone/ui/fileviewer/viewmodel/MediaListViewModel.kt b/app/src/main/java/org/linphone/ui/fileviewer/viewmodel/MediaListViewModel.kt index 4943e44da..364c498a9 100644 --- a/app/src/main/java/org/linphone/ui/fileviewer/viewmodel/MediaListViewModel.kt +++ b/app/src/main/java/org/linphone/ui/fileviewer/viewmodel/MediaListViewModel.kt @@ -68,7 +68,7 @@ class MediaListViewModel @WorkerThread private fun loadMediaList() { val list = arrayListOf() - val chatRoomId = LinphoneUtils.getChatRoomId(chatRoom) + val chatRoomId = LinphoneUtils.getConversationId(chatRoom) Log.i("$TAG Loading media contents for conversation [$chatRoomId]") val media = chatRoom.mediaContents diff --git a/app/src/main/java/org/linphone/ui/main/MainActivity.kt b/app/src/main/java/org/linphone/ui/main/MainActivity.kt index cdf83555f..891de4c80 100644 --- a/app/src/main/java/org/linphone/ui/main/MainActivity.kt +++ b/app/src/main/java/org/linphone/ui/main/MainActivity.kt @@ -86,6 +86,9 @@ class MainActivity : GenericActivity() { private const val HISTORY_FRAGMENT_ID = 2 private const val CHAT_FRAGMENT_ID = 3 private const val MEETINGS_FRAGMENT_ID = 4 + + const val ARGUMENTS_CHAT = "Chat" + const val ARGUMENTS_CONVERSATION_ID = "ConversationId" } private lateinit var binding: MainActivityBinding @@ -514,14 +517,9 @@ class MainActivity : GenericActivity() { private fun handleLocusOrShortcut(id: String) { Log.i("$TAG Found locus ID [$id]") - val pair = LinphoneUtils.getLocalAndPeerSipUrisFromChatRoomId(id) - if (pair != null) { - val localSipUri = pair.first - val remoteSipUri = pair.second - Log.i( - "$TAG Navigating to conversation with local [$localSipUri] and peer [$remoteSipUri] addresses, computed from shortcut ID" - ) - sharedViewModel.showConversationEvent.value = Event(pair) + if (id.isNotEmpty()) { + Log.i("$TAG Navigating to conversation with ID [$id], computed from shortcut ID") + sharedViewModel.showConversationEvent.value = Event(id) } } @@ -547,22 +545,18 @@ class MainActivity : GenericActivity() { } } } else { - if (intent.hasExtra("Chat")) { + if (intent.hasExtra(ARGUMENTS_CHAT)) { Log.i("$TAG Intent has [Chat] extra") coreContext.postOnMainThread { try { Log.i("$TAG Trying to go to Conversations fragment") val args = intent.extras - val localSipUri = args?.getString("LocalSipUri", "") - val remoteSipUri = args?.getString("RemoteSipUri", "") - if (remoteSipUri.isNullOrEmpty() || localSipUri.isNullOrEmpty()) { - Log.w("$TAG Found [Chat] extra but no local and/or remote SIP URI!") + val conversationId = args?.getString(ARGUMENTS_CONVERSATION_ID, "") + if (conversationId.isNullOrEmpty()) { + Log.w("$TAG Found [Chat] extra but no conversation ID!") } else { - Log.i( - "$TAG Found [Chat] extra with local [$localSipUri] and peer [$remoteSipUri] addresses" - ) - val pair = Pair(localSipUri, remoteSipUri) - sharedViewModel.showConversationEvent.value = Event(pair) + Log.i("$TAG Found [Chat] extra with conversation ID [$conversationId]") + sharedViewModel.showConversationEvent.value = Event(conversationId) } args?.clear() @@ -665,25 +659,23 @@ class MainActivity : GenericActivity() { Log.i( "$TAG App is already started and in debug fragment, navigating to conversations list" ) - val pair = parseShortcutIfAny(intent) - if (pair != null) { + val conversationId = parseShortcutIfAny(intent) + if (conversationId != null) { Log.i( - "$TAG Navigating from debug to conversation with local [${pair.first}] and peer [${pair.second}] addresses, computed from shortcut ID" + "$TAG Navigating from debug to conversation with ID [$conversationId], computed from shortcut ID" ) - sharedViewModel.showConversationEvent.value = Event(pair) + sharedViewModel.showConversationEvent.value = Event(conversationId) } val action = DebugFragmentDirections.actionDebugFragmentToConversationsListFragment() findNavController().navigate(action) } else { - val pair = parseShortcutIfAny(intent) - if (pair != null) { - val localSipUri = pair.first - val remoteSipUri = pair.second + val conversationId = parseShortcutIfAny(intent) + if (conversationId != null) { Log.i( - "$TAG Navigating to conversation with local [$localSipUri] and peer [$remoteSipUri] addresses, computed from shortcut ID" + "$TAG Navigating to conversation with conversation ID [$conversationId] addresses, computed from shortcut ID" ) - sharedViewModel.showConversationEvent.value = Event(pair) + sharedViewModel.showConversationEvent.value = Event(conversationId) } if (findNavController().currentDestination?.id == R.id.conversationsListFragment) { @@ -698,11 +690,11 @@ class MainActivity : GenericActivity() { } } - private fun parseShortcutIfAny(intent: Intent): Pair? { + private fun parseShortcutIfAny(intent: Intent): String? { val shortcutId = intent.getStringExtra("android.intent.extra.shortcut.ID") // Intent.EXTRA_SHORTCUT_ID if (shortcutId != null) { Log.i("$TAG Found shortcut ID [$shortcutId]") - return LinphoneUtils.getLocalAndPeerSipUrisFromChatRoomId(shortcutId) + return shortcutId } else { Log.i("$TAG No shortcut ID was found") } diff --git a/app/src/main/java/org/linphone/ui/main/adapter/ConversationsContactsAndSuggestionsListAdapter.kt b/app/src/main/java/org/linphone/ui/main/adapter/ConversationsContactsAndSuggestionsListAdapter.kt index f35e8ab30..356fb10bb 100644 --- a/app/src/main/java/org/linphone/ui/main/adapter/ConversationsContactsAndSuggestionsListAdapter.kt +++ b/app/src/main/java/org/linphone/ui/main/adapter/ConversationsContactsAndSuggestionsListAdapter.kt @@ -87,7 +87,7 @@ class ConversationsContactsAndSuggestionsListAdapter : override fun getItemViewType(position: Int): Int { val model = getItem(position) - return if (model.localAddress != null) { + return if (model.conversationId.isNotEmpty()) { CONVERSATION_TYPE } else if (model.friend != null) { if (model.starred) { diff --git a/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationDocumentsListFragment.kt b/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationDocumentsListFragment.kt index d01a7e959..9b410ca77 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationDocumentsListFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationDocumentsListFragment.kt @@ -91,13 +91,10 @@ class ConversationDocumentsListFragment : SlidingPaneChildFragment() { binding.viewModel = viewModel observeToastEvents(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]" - ) + val conversationId = args.conversationId + Log.i("$TAG Looking up for conversation with conversation ID [$conversationId]") val chatRoom = sharedViewModel.displayedChatRoom - viewModel.findChatRoom(chatRoom, localSipUri, remoteSipUri) + viewModel.findChatRoom(chatRoom, conversationId) val headerItemDecoration = RecyclerViewHeaderDecoration(requireContext(), adapter) binding.documentsList.addItemDecoration(headerItemDecoration) @@ -143,8 +140,7 @@ class ConversationDocumentsListFragment : SlidingPaneChildFragment() { val bundle = Bundle() bundle.apply { - putString("localSipUri", viewModel.localSipUri) - putString("remoteSipUri", viewModel.remoteSipUri) + putString("conversationId", viewModel.conversationId) putString("path", path) putBoolean("isEncrypted", fileModel.isEncrypted) putLong("timestamp", fileModel.fileCreationTimestamp) diff --git a/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationForwardMessageFragment.kt b/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationForwardMessageFragment.kt index a451df537..09014f800 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationForwardMessageFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationForwardMessageFragment.kt @@ -120,17 +120,11 @@ class ConversationForwardMessageFragment : SlidingPaneChildFragment() { } viewModel.chatRoomCreatedEvent.observe(viewLifecycleOwner) { - it.consume { pair -> - Log.i( - "$TAG Navigating to conversation [${pair.second}] with local address [${pair.first}]" - ) - + it.consume { conversationId -> + Log.i("$TAG Navigating to conversation [$conversationId]") if (findNavController().currentDestination?.id == R.id.conversationForwardMessageFragment) { - val localSipUri = pair.first - val remoteSipUri = pair.second val action = ConversationForwardMessageFragmentDirections.actionConversationForwardMessageFragmentToConversationFragment( - localSipUri, - remoteSipUri + conversationId ) disableConsumingEventOnPause = true findNavController().navigate(action) 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 index 9f3628984..f8dfae277 100644 --- 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 @@ -484,14 +484,11 @@ open class ConversationFragment : SlidingPaneChildFragment() { } RecyclerViewSwipeUtils(callbacks).attachToRecyclerView(binding.eventsList) - 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]" - ) + val conversationId = args.conversationId + Log.i("$TAG Looking up for conversation with conversation ID [$conversationId]") val chatRoom = sharedViewModel.displayedChatRoom - viewModel.findChatRoom(chatRoom, localSipUri, remoteSipUri) - Compatibility.setLocusIdInContentCaptureSession(binding.root, localSipUri, remoteSipUri) + viewModel.findChatRoom(chatRoom, conversationId) + Compatibility.setLocusIdInContentCaptureSession(binding.root, conversationId) viewModel.chatRoomFoundEvent.observe(viewLifecycleOwner) { it.consume { found -> @@ -1092,8 +1089,7 @@ open class ConversationFragment : SlidingPaneChildFragment() { if (findNavController().currentDestination?.id == R.id.conversationFragment) { val action = ConversationFragmentDirections.actionConversationFragmentToConversationInfoFragment( - viewModel.localSipUri, - viewModel.remoteSipUri + viewModel.conversationId, ) findNavController().navigate(action) } @@ -1113,8 +1109,7 @@ open class ConversationFragment : SlidingPaneChildFragment() { val bundle = Bundle() bundle.apply { - putString("localSipUri", viewModel.localSipUri) - putString("remoteSipUri", viewModel.remoteSipUri) + putString("conversationId", viewModel.conversationId) putString("path", path) putBoolean("isEncrypted", fileModel.isEncrypted) putLong("timestamp", fileModel.fileCreationTimestamp) @@ -1199,8 +1194,7 @@ open class ConversationFragment : SlidingPaneChildFragment() { if (findNavController().currentDestination?.id == R.id.conversationFragment) { val action = ConversationFragmentDirections.actionConversationFragmentToConversationMediaListFragment( - localSipUri = viewModel.localSipUri, - remoteSipUri = viewModel.remoteSipUri + viewModel.conversationId ) findNavController().navigate(action) } @@ -1211,8 +1205,7 @@ open class ConversationFragment : SlidingPaneChildFragment() { if (findNavController().currentDestination?.id == R.id.conversationFragment) { val action = ConversationFragmentDirections.actionConversationFragmentToConversationDocumentsListFragment( - localSipUri = viewModel.localSipUri, - remoteSipUri = viewModel.remoteSipUri + viewModel.conversationId ) findNavController().navigate(action) } diff --git a/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationInfoFragment.kt b/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationInfoFragment.kt index 9038df390..1f3a37892 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationInfoFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationInfoFragment.kt @@ -96,13 +96,10 @@ class ConversationInfoFragment : SlidingPaneChildFragment() { binding.viewModel = viewModel observeToastEvents(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]" - ) + val conversationId = args.conversationId + Log.i("$TAG Looking up for conversation with conversation ID [$conversationId]") val chatRoom = sharedViewModel.displayedChatRoom - viewModel.findChatRoom(chatRoom, localSipUri, remoteSipUri) + viewModel.findChatRoom(chatRoom, conversationId) binding.participants.isNestedScrollingEnabled = false binding.participants.setHasFixedSize(false) @@ -116,7 +113,7 @@ class ConversationInfoFragment : SlidingPaneChildFragment() { it.consume { found -> if (found) { Log.i( - "$TAG Found matching conversation for local SIP URI [$localSipUri] and remote SIP URI [$remoteSipUri]" + "$TAG Found matching conversation with conversation ID [$conversationId]" ) startPostponedEnterTransition() } else { @@ -333,7 +330,7 @@ class ConversationInfoFragment : SlidingPaneChildFragment() { if (findNavController().currentDestination?.id == R.id.conversationInfoFragment) { Log.i("$TAG Going to shared media fragment") val action = - ConversationInfoFragmentDirections.actionConversationInfoFragmentToConversationMediaListFragment(localSipUri, remoteSipUri) + ConversationInfoFragmentDirections.actionConversationInfoFragmentToConversationMediaListFragment(conversationId) findNavController().navigate(action) } } @@ -342,7 +339,7 @@ class ConversationInfoFragment : SlidingPaneChildFragment() { if (findNavController().currentDestination?.id == R.id.conversationInfoFragment) { Log.i("$TAG Going to shared documents fragment") val action = - ConversationInfoFragmentDirections.actionConversationInfoFragmentToConversationDocumentsListFragment(localSipUri, remoteSipUri) + ConversationInfoFragmentDirections.actionConversationInfoFragmentToConversationDocumentsListFragment(conversationId) findNavController().navigate(action) } } diff --git a/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationMediaListFragment.kt b/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationMediaListFragment.kt index e8d0d1f68..abece7630 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationMediaListFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationMediaListFragment.kt @@ -94,13 +94,10 @@ class ConversationMediaListFragment : SlidingPaneChildFragment() { binding.viewModel = viewModel observeToastEvents(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]" - ) + val conversationId = args.conversationId + Log.i("$TAG Looking up for conversation with conversation ID [$conversationId]") val chatRoom = sharedViewModel.displayedChatRoom - viewModel.findChatRoom(chatRoom, localSipUri, remoteSipUri) + viewModel.findChatRoom(chatRoom, conversationId) val headerItemDecoration = RecyclerViewHeaderDecoration(requireContext(), adapter) binding.mediaList.addItemDecoration(headerItemDecoration) @@ -172,8 +169,7 @@ class ConversationMediaListFragment : SlidingPaneChildFragment() { val bundle = Bundle() bundle.apply { - putString("localSipUri", viewModel.localSipUri) - putString("remoteSipUri", viewModel.remoteSipUri) + putString("conversationId", viewModel.conversationId) putString("path", path) putBoolean("isEncrypted", fileModel.isEncrypted) putLong("timestamp", fileModel.fileCreationTimestamp) 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 ce968857d..1ef9a7c0e 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 @@ -39,6 +39,7 @@ import org.linphone.databinding.ChatListFragmentBinding import org.linphone.ui.GenericActivity import org.linphone.ui.fileviewer.FileViewerActivity import org.linphone.ui.fileviewer.MediaViewerActivity +import org.linphone.ui.main.MainActivity.Companion.ARGUMENTS_CONVERSATION_ID import org.linphone.ui.main.chat.adapter.ConversationsListAdapter import org.linphone.ui.main.chat.viewmodel.ConversationsListViewModel import org.linphone.ui.main.fragment.AbstractMainFragment @@ -162,9 +163,7 @@ class ConversationsListFragment : AbstractMainFragment() { it.consume { model -> Log.i("$TAG Show conversation with ID [${model.id}]") sharedViewModel.displayedChatRoom = model.chatRoom - sharedViewModel.showConversationEvent.value = Event( - Pair(model.localSipUri, model.remoteSipUri) - ) + sharedViewModel.showConversationEvent.value = Event(model.id) } } @@ -191,16 +190,9 @@ class ConversationsListFragment : AbstractMainFragment() { } 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 - ) + it.consume { conversationId -> + Log.i("$TAG Navigating to conversation fragment with ID [$conversationId]") + val action = ConversationFragmentDirections.actionGlobalConversationFragment(conversationId) binding.chatNavContainer.findNavController().navigate(action) } } @@ -330,12 +322,10 @@ class ConversationsListFragment : AbstractMainFragment() { val args = arguments if (args != null) { - val localSipUri = args.getString("LocalSipUri") - val remoteSipUri = args.getString("RemoteSipUri") - if (localSipUri != null && remoteSipUri != null) { - Log.i("$TAG Found local [$localSipUri] & remote [$remoteSipUri] URIs in arguments") - val pair = Pair(localSipUri, remoteSipUri) - sharedViewModel.showConversationEvent.value = Event(pair) + val conversationId = args.getString(ARGUMENTS_CONVERSATION_ID) + if (!conversationId.isNullOrEmpty()) { + Log.i("$TAG Found conversation ID [$conversationId] in arguments") + sharedViewModel.showConversationEvent.value = Event(conversationId) args.clear() } } diff --git a/app/src/main/java/org/linphone/ui/main/chat/fragment/StartConversationFragment.kt b/app/src/main/java/org/linphone/ui/main/chat/fragment/StartConversationFragment.kt index 3c942984a..a0039ebe7 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/fragment/StartConversationFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/fragment/StartConversationFragment.kt @@ -93,11 +93,11 @@ class StartConversationFragment : GenericAddressPickerFragment() { } viewModel.chatRoomCreatedEvent.observe(viewLifecycleOwner) { - it.consume { pair -> + it.consume { conversationId -> Log.i( - "$TAG Conversation [${pair.second}] for local address [${pair.first}] has been created, navigating to it" + "$TAG Conversation [$conversationId] has been created, navigating to it" ) - sharedViewModel.showConversationEvent.value = Event(pair) + sharedViewModel.showConversationEvent.value = Event(conversationId) goBack() } } 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 8da2b98d2..45363fd9a 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 @@ -48,7 +48,7 @@ class ConversationModel private const val TAG = "[Conversation Model]" } - val id = LinphoneUtils.getChatRoomId(chatRoom) + val id = LinphoneUtils.getConversationId(chatRoom) val localSipUri = chatRoom.localAddress.asStringUriOnly() diff --git a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/AbstractConversationViewModel.kt b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/AbstractConversationViewModel.kt index a5a9f1098..37759a39f 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/AbstractConversationViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/AbstractConversationViewModel.kt @@ -28,8 +28,6 @@ import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.R import org.linphone.core.Address import org.linphone.core.ChatRoom -import org.linphone.core.ConferenceParams -import org.linphone.core.Factory import org.linphone.core.MediaDirection import org.linphone.core.tools.Log import org.linphone.ui.GenericViewModel @@ -51,9 +49,7 @@ abstract class AbstractConversationViewModel : GenericViewModel() { lateinit var chatRoom: ChatRoom - lateinit var localSipUri: String - - lateinit var remoteSipUri: String + lateinit var conversationId: String fun isChatRoomInitialized(): Boolean { return ::chatRoom.isInitialized @@ -68,17 +64,13 @@ abstract class AbstractConversationViewModel : GenericViewModel() { } @UiThread - fun findChatRoom(room: ChatRoom?, localSipUri: String, remoteSipUri: String) { - this.localSipUri = localSipUri - this.remoteSipUri = remoteSipUri - + fun findChatRoom(room: ChatRoom?, conversationId: String) { coreContext.postOnCoreThread { core -> - Log.i( - "$TAG Looking for conversation with local SIP URI [$localSipUri] and remote SIP URI [$remoteSipUri]" - ) + Log.i("$TAG Looking for conversation with conversation ID [$conversationId]") if (room != null && ::chatRoom.isInitialized && chatRoom == room) { Log.i("$TAG Conversation object already in memory, skipping") + this@AbstractConversationViewModel.conversationId = conversationId beforeNotifyingChatRoomFound(sameOne = true) chatRoomFoundEvent.postValue(Event(true)) afterNotifyingChatRoomFound(sameOne = true) @@ -86,17 +78,12 @@ abstract class AbstractConversationViewModel : GenericViewModel() { return@postOnCoreThread } - val localAddress = Factory.instance().createAddress(localSipUri) - val remoteAddress = Factory.instance().createAddress(remoteSipUri) - if (room != null && (!::chatRoom.isInitialized || chatRoom != room)) { - if (localAddress?.weakEqual(room.localAddress) == true && remoteAddress?.weakEqual( - room.peerAddress - ) == true - ) { + if (conversationId == LinphoneUtils.getConversationId(room)) { Log.i("$TAG Conversation object available in sharedViewModel, using it") chatRoom = room + this@AbstractConversationViewModel.conversationId = conversationId beforeNotifyingChatRoomFound(sameOne = false) chatRoomFoundEvent.postValue(Event(true)) afterNotifyingChatRoomFound(sameOne = false) @@ -105,18 +92,11 @@ abstract class AbstractConversationViewModel : GenericViewModel() { } } - if (localAddress != null && remoteAddress != null) { + if (conversationId.isNotEmpty()) { Log.i("$TAG Searching for conversation in Core using local & peer SIP addresses") - val params: ConferenceParams? = null - val found = core.searchChatRoom( - params, - localAddress, - remoteAddress, - arrayOfNulls
( - 0 - ) - ) + val found = core.searchChatRoomByIdentifier(conversationId) if (found != null) { + this@AbstractConversationViewModel.conversationId = conversationId if (::chatRoom.isInitialized && chatRoom == found) { Log.i("$TAG Conversation object already in memory, keeping it") beforeNotifyingChatRoomFound(sameOne = true) diff --git a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationDocumentsListViewModel.kt b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationDocumentsListViewModel.kt index 5c3710bf0..ecf157930 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationDocumentsListViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationDocumentsListViewModel.kt @@ -58,7 +58,7 @@ class ConversationDocumentsListViewModel val list = arrayListOf() Log.i( - "$TAG Loading document contents for conversation [${LinphoneUtils.getChatRoomId( + "$TAG Loading document contents for conversation [${LinphoneUtils.getConversationId( chatRoom )}]" ) diff --git a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationForwardMessageViewModel.kt b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationForwardMessageViewModel.kt index 1e65b9081..278b12c6c 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationForwardMessageViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationForwardMessageViewModel.kt @@ -48,8 +48,8 @@ class ConversationForwardMessageViewModel val operationInProgress = MutableLiveData() - val chatRoomCreatedEvent: MutableLiveData>> by lazy { - MutableLiveData>>() + val chatRoomCreatedEvent: MutableLiveData> by lazy { + MutableLiveData>() } val showNumberOrAddressPickerDialogEvent: MutableLiveData>> by lazy { @@ -83,21 +83,14 @@ class ConversationForwardMessageViewModel val state = chatRoom.state if (state == ChatRoom.State.Instantiated) return - val id = LinphoneUtils.getChatRoomId(chatRoom) + val id = LinphoneUtils.getConversationId(chatRoom) Log.i("$TAG Conversation [$id] (${chatRoom.subject}) state changed: [$state]") if (state == ChatRoom.State.Created) { Log.i("$TAG Conversation [$id] successfully created") chatRoom.removeListener(this) operationInProgress.postValue(false) - chatRoomCreatedEvent.postValue( - Event( - Pair( - chatRoom.localAddress.asStringUriOnly(), - chatRoom.peerAddress.asStringUriOnly() - ) - ) - ) + chatRoomCreatedEvent.postValue(Event(LinphoneUtils.getConversationId(chatRoom))) } else if (state == ChatRoom.State.CreationFailed) { Log.e("$TAG Conversation [$id] creation has failed!") chatRoom.removeListener(this) @@ -128,16 +121,9 @@ class ConversationForwardMessageViewModel @UiThread fun handleClickOnModel(model: ConversationContactOrSuggestionModel) { coreContext.postOnCoreThread { core -> - if (model.localAddress != null) { + if (model.conversationId.isNotEmpty()) { Log.i("$TAG User clicked on an existing conversation") - chatRoomCreatedEvent.postValue( - Event( - Pair( - model.localAddress.asStringUriOnly(), - model.address.asStringUriOnly() - ) - ) - ) + chatRoomCreatedEvent.postValue(Event(model.conversationId)) if (searchFilter.value.orEmpty().isNotEmpty()) { // Clear filter after it was used coreContext.postOnMainThread { @@ -230,33 +216,19 @@ class ConversationForwardMessageViewModel if (chatRoom != null) { if (chatParams.backend == ChatRoom.Backend.FlexisipChat) { if (chatRoom.state == ChatRoom.State.Created) { - val id = LinphoneUtils.getChatRoomId(chatRoom) + val id = LinphoneUtils.getConversationId(chatRoom) Log.i("$TAG 1-1 conversation [$id] has been created") operationInProgress.postValue(false) - chatRoomCreatedEvent.postValue( - Event( - Pair( - chatRoom.localAddress.asStringUriOnly(), - chatRoom.peerAddress.asStringUriOnly() - ) - ) - ) + chatRoomCreatedEvent.postValue(Event(LinphoneUtils.getConversationId(chatRoom))) } else { Log.i("$TAG Conversation isn't in Created state yet, wait for it") chatRoom.addListener(chatRoomListener) } } else { - val id = LinphoneUtils.getChatRoomId(chatRoom) + val id = LinphoneUtils.getConversationId(chatRoom) Log.i("$TAG Conversation successfully created [$id]") operationInProgress.postValue(false) - chatRoomCreatedEvent.postValue( - Event( - Pair( - chatRoom.localAddress.asStringUriOnly(), - chatRoom.peerAddress.asStringUriOnly() - ) - ) - ) + chatRoomCreatedEvent.postValue(Event(LinphoneUtils.getConversationId(chatRoom))) } } else { Log.e("$TAG Failed to create 1-1 conversation with [${remote.asStringUriOnly()}]!") @@ -268,14 +240,7 @@ class ConversationForwardMessageViewModel "$TAG A 1-1 conversation between local account [${localAddress?.asStringUriOnly()}] and remote [${remote.asStringUriOnly()}] for given parameters already exists!" ) operationInProgress.postValue(false) - chatRoomCreatedEvent.postValue( - Event( - Pair( - existingChatRoom.localAddress.asStringUriOnly(), - existingChatRoom.peerAddress.asStringUriOnly() - ) - ) - ) + chatRoomCreatedEvent.postValue(Event(LinphoneUtils.getConversationId(existingChatRoom))) } } } diff --git a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationInfoViewModel.kt b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationInfoViewModel.kt index 173c0d8eb..080120051 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationInfoViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationInfoViewModel.kt @@ -151,7 +151,7 @@ class ConversationInfoViewModel @WorkerThread override fun onSubjectChanged(chatRoom: ChatRoom, eventLog: EventLog) { Log.i( - "$TAG Conversation [${LinphoneUtils.getChatRoomId(chatRoom)}] has a new subject [${chatRoom.subject}]" + "$TAG Conversation [${LinphoneUtils.getConversationId(chatRoom)}] has a new subject [${chatRoom.subject}]" ) showGreenToast(R.string.conversation_subject_changed_toast, R.drawable.check) @@ -218,7 +218,7 @@ class ConversationInfoViewModel fun leaveGroup() { coreContext.postOnCoreThread { if (isChatRoomInitialized()) { - Log.i("$TAG Leaving conversation [${LinphoneUtils.getChatRoomId(chatRoom)}]") + Log.i("$TAG Leaving conversation [${LinphoneUtils.getConversationId(chatRoom)}]") chatRoom.leave() } groupLeftEvent.postValue(Event(true)) @@ -230,7 +230,7 @@ class ConversationInfoViewModel coreContext.postOnCoreThread { if (isChatRoomInitialized()) { Log.i( - "$TAG Cleaning conversation [${LinphoneUtils.getChatRoomId(chatRoom)}] history" + "$TAG Cleaning conversation [${LinphoneUtils.getConversationId(chatRoom)}] history" ) chatRoom.deleteHistory() } @@ -280,7 +280,7 @@ class ConversationInfoViewModel coreContext.postOnCoreThread { val address = participantModel.address Log.i( - "$TAG Removing participant [$address] from the conversation [${LinphoneUtils.getChatRoomId( + "$TAG Removing participant [$address] from the conversation [${LinphoneUtils.getConversationId( chatRoom )}]" ) @@ -301,7 +301,7 @@ class ConversationInfoViewModel coreContext.postOnCoreThread { val address = participantModel.address Log.i( - "$TAG Granting admin rights to participant [$address] from the conversation [${LinphoneUtils.getChatRoomId( + "$TAG Granting admin rights to participant [$address] from the conversation [${LinphoneUtils.getConversationId( chatRoom )}]" ) @@ -322,7 +322,7 @@ class ConversationInfoViewModel coreContext.postOnCoreThread { val address = participantModel.address Log.i( - "$TAG Removing admin rights from participant [$address] from the conversation [${LinphoneUtils.getChatRoomId( + "$TAG Removing admin rights from participant [$address] from the conversation [${LinphoneUtils.getConversationId( chatRoom )}]" ) diff --git a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationMediaListViewModel.kt b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationMediaListViewModel.kt index 1b8dea0d5..edf9d4884 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationMediaListViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationMediaListViewModel.kt @@ -54,7 +54,7 @@ class ConversationMediaListViewModel private fun loadMediaList() { val list = arrayListOf() Log.i( - "$TAG Loading media contents for conversation [${LinphoneUtils.getChatRoomId( + "$TAG Loading media contents for conversation [${LinphoneUtils.getConversationId( chatRoom )}]" ) 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 index ad5227034..d592a9495 100644 --- 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 @@ -470,7 +470,7 @@ class ConversationViewModel fun updateCurrentlyDisplayedConversation() { coreContext.postOnCoreThread { if (isChatRoomInitialized()) { - val id = LinphoneUtils.getChatRoomId(chatRoom) + val id = LinphoneUtils.getConversationId(chatRoom) Log.i( "$TAG Asking notifications manager not to notify messages for conversation [$id]" ) diff --git a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationsListViewModel.kt b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationsListViewModel.kt index e2ffd01ee..9da02a47c 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationsListViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationsListViewModel.kt @@ -54,7 +54,7 @@ class ConversationsListViewModel state: ChatRoom.State? ) { Log.i( - "$TAG Conversation [${LinphoneUtils.getChatRoomId(chatRoom)}] state changed [$state]" + "$TAG Conversation [${LinphoneUtils.getConversationId(chatRoom)}] state changed [$state]" ) when (state) { @@ -66,7 +66,7 @@ class ConversationsListViewModel @WorkerThread override fun onMessageSent(core: Core, chatRoom: ChatRoom, message: ChatMessage) { - val id = LinphoneUtils.getChatRoomId(chatRoom) + val id = LinphoneUtils.getConversationId(chatRoom) val found = conversations.value.orEmpty().find { it.id == id } @@ -85,7 +85,7 @@ class ConversationsListViewModel chatRoom: ChatRoom, messages: Array ) { - val id = LinphoneUtils.getChatRoomId(chatRoom) + val id = LinphoneUtils.getConversationId(chatRoom) val found = conversations.value.orEmpty().find { it.id == id } diff --git a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/StartConversationViewModel.kt b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/StartConversationViewModel.kt index d80d28cba..fc87abd9e 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/StartConversationViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/StartConversationViewModel.kt @@ -55,8 +55,8 @@ class StartConversationViewModel MutableLiveData>() } - val chatRoomCreatedEvent: MutableLiveData>> by lazy { - MutableLiveData>>() + val chatRoomCreatedEvent: MutableLiveData> by lazy { + MutableLiveData>() } private val chatRoomListener = object : ChatRoomListenerStub() { @@ -65,21 +65,14 @@ class StartConversationViewModel val state = chatRoom.state if (state == ChatRoom.State.Instantiated) return - val id = LinphoneUtils.getChatRoomId(chatRoom) + val id = LinphoneUtils.getConversationId(chatRoom) Log.i("$TAG Conversation [$id] (${chatRoom.subject}) state changed: [$state]") if (state == ChatRoom.State.Created) { Log.i("$TAG Conversation [$id] successfully created") chatRoom.removeListener(this) operationInProgress.postValue(false) - chatRoomCreatedEvent.postValue( - Event( - Pair( - chatRoom.localAddress.asStringUriOnly(), - chatRoom.peerAddress.asStringUriOnly() - ) - ) - ) + chatRoomCreatedEvent.postValue(Event(LinphoneUtils.getConversationId(chatRoom))) } else if (state == ChatRoom.State.CreationFailed) { Log.e("$TAG Conversation [$id] creation has failed!") chatRoom.removeListener(this) @@ -138,19 +131,12 @@ class StartConversationViewModel if (chatRoom != null) { if (chatParams.backend == ChatRoom.Backend.FlexisipChat) { if (chatRoom.state == ChatRoom.State.Created) { - val id = LinphoneUtils.getChatRoomId(chatRoom) + val id = LinphoneUtils.getConversationId(chatRoom) Log.i( "$TAG Group conversation [$id] ($groupChatRoomSubject) has been created" ) operationInProgress.postValue(false) - chatRoomCreatedEvent.postValue( - Event( - Pair( - chatRoom.localAddress.asStringUriOnly(), - chatRoom.peerAddress.asStringUriOnly() - ) - ) - ) + chatRoomCreatedEvent.postValue(Event(LinphoneUtils.getConversationId(chatRoom))) } else { Log.i( "$TAG Conversation [$groupChatRoomSubject] isn't in Created state yet, wait for it" @@ -158,17 +144,10 @@ class StartConversationViewModel chatRoom.addListener(chatRoomListener) } } else { - val id = LinphoneUtils.getChatRoomId(chatRoom) + val id = LinphoneUtils.getConversationId(chatRoom) Log.i("$TAG Conversation successfully created [$id] ($groupChatRoomSubject)") operationInProgress.postValue(false) - chatRoomCreatedEvent.postValue( - Event( - Pair( - chatRoom.localAddress.asStringUriOnly(), - chatRoom.peerAddress.asStringUriOnly() - ) - ) - ) + chatRoomCreatedEvent.postValue(Event(LinphoneUtils.getConversationId(chatRoom))) } } else { Log.e("$TAG Failed to create group conversation [$groupChatRoomSubject]!") @@ -242,33 +221,19 @@ class StartConversationViewModel if (chatParams.backend == ChatRoom.Backend.FlexisipChat) { val state = chatRoom.state if (state == ChatRoom.State.Created) { - val id = LinphoneUtils.getChatRoomId(chatRoom) + val id = LinphoneUtils.getConversationId(chatRoom) Log.i("$TAG 1-1 conversation [$id] has been created") operationInProgress.postValue(false) - chatRoomCreatedEvent.postValue( - Event( - Pair( - chatRoom.localAddress.asStringUriOnly(), - chatRoom.peerAddress.asStringUriOnly() - ) - ) - ) + chatRoomCreatedEvent.postValue(Event(LinphoneUtils.getConversationId(chatRoom))) } else { Log.i("$TAG Conversation isn't in Created state yet (state is [$state]), wait for it") chatRoom.addListener(chatRoomListener) } } else { - val id = LinphoneUtils.getChatRoomId(chatRoom) + val id = LinphoneUtils.getConversationId(chatRoom) Log.i("$TAG Conversation successfully created [$id]") operationInProgress.postValue(false) - chatRoomCreatedEvent.postValue( - Event( - Pair( - chatRoom.localAddress.asStringUriOnly(), - chatRoom.peerAddress.asStringUriOnly() - ) - ) - ) + chatRoomCreatedEvent.postValue(Event(LinphoneUtils.getConversationId(chatRoom))) } } else { Log.e("$TAG Failed to create 1-1 conversation with [${remote.asStringUriOnly()}]!") @@ -282,14 +247,7 @@ class StartConversationViewModel "$TAG A 1-1 conversation between local account [${localAddress?.asStringUriOnly()}] and remote [${remote.asStringUriOnly()}] for given parameters already exists!" ) operationInProgress.postValue(false) - chatRoomCreatedEvent.postValue( - Event( - Pair( - existingChatRoom.localAddress.asStringUriOnly(), - existingChatRoom.peerAddress.asStringUriOnly() - ) - ) - ) + chatRoomCreatedEvent.postValue(Event(LinphoneUtils.getConversationId(existingChatRoom))) } } diff --git a/app/src/main/java/org/linphone/ui/main/contacts/fragment/ContactFragment.kt b/app/src/main/java/org/linphone/ui/main/contacts/fragment/ContactFragment.kt index 0ee9a3b82..0cbdf5503 100644 --- a/app/src/main/java/org/linphone/ui/main/contacts/fragment/ContactFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/contacts/fragment/ContactFragment.kt @@ -188,9 +188,9 @@ class ContactFragment : SlidingPaneChildFragment() { } viewModel.goToConversationEvent.observe(viewLifecycleOwner) { - it.consume { pair -> - Log.i("$TAG Going to conversation [${pair.first}][${pair.second}]") - sharedViewModel.showConversationEvent.value = Event(pair) + it.consume { conversationId -> + Log.i("$TAG Going to conversation [$conversationId]") + sharedViewModel.showConversationEvent.value = Event(conversationId) sharedViewModel.navigateToConversationsEvent.value = Event(true) } } diff --git a/app/src/main/java/org/linphone/ui/main/contacts/viewmodel/ContactViewModel.kt b/app/src/main/java/org/linphone/ui/main/contacts/viewmodel/ContactViewModel.kt index 71796e3fb..f6c15cea5 100644 --- a/app/src/main/java/org/linphone/ui/main/contacts/viewmodel/ContactViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/contacts/viewmodel/ContactViewModel.kt @@ -117,8 +117,8 @@ class ContactViewModel MutableLiveData>() } - val goToConversationEvent: MutableLiveData>> by lazy { - MutableLiveData>>() + val goToConversationEvent: MutableLiveData> by lazy { + MutableLiveData>() } val vCardTerminatedEvent: MutableLiveData>> by lazy { @@ -198,21 +198,14 @@ class ContactViewModel val state = chatRoom.state if (state == ChatRoom.State.Instantiated) return - val id = LinphoneUtils.getChatRoomId(chatRoom) + val id = LinphoneUtils.getConversationId(chatRoom) Log.i("$TAG Conversation [$id] (${chatRoom.subject}) state changed: [$state]") if (state == ChatRoom.State.Created) { Log.i("$TAG Conversation [$id] successfully created") chatRoom.removeListener(this) operationInProgress.postValue(false) - goToConversationEvent.postValue( - Event( - Pair( - chatRoom.localAddress.asStringUriOnly(), - chatRoom.peerAddress.asStringUriOnly() - ) - ) - ) + goToConversationEvent.postValue(Event(LinphoneUtils.getConversationId(chatRoom))) } else if (state == ChatRoom.State.CreationFailed) { Log.e("$TAG Conversation [$id] creation has failed!") chatRoom.removeListener(this) @@ -543,13 +536,11 @@ class ContactViewModel val existingChatRoom = core.searchChatRoom(params, localAddress, null, participants) if (existingChatRoom != null) { Log.i( - "$TAG Found existing conversation [${LinphoneUtils.getChatRoomId( + "$TAG Found existing conversation [${LinphoneUtils.getConversationId( existingChatRoom )}], going to it" ) - goToConversationEvent.postValue( - Event(Pair(localSipUri, existingChatRoom.peerAddress.asStringUriOnly())) - ) + goToConversationEvent.postValue(Event(LinphoneUtils.getConversationId(existingChatRoom))) } else { Log.i( "$TAG No existing conversation between [$localSipUri] and [$remoteSipUri] was found, let's create it" @@ -559,33 +550,19 @@ class ContactViewModel if (chatRoom != null) { if (chatParams.backend == ChatRoom.Backend.FlexisipChat) { if (chatRoom.state == ChatRoom.State.Created) { - val id = LinphoneUtils.getChatRoomId(chatRoom) + val id = LinphoneUtils.getConversationId(chatRoom) Log.i("$TAG 1-1 conversation [$id] has been created") operationInProgress.postValue(false) - goToConversationEvent.postValue( - Event( - Pair( - chatRoom.localAddress.asStringUriOnly(), - chatRoom.peerAddress.asStringUriOnly() - ) - ) - ) + goToConversationEvent.postValue(Event(LinphoneUtils.getConversationId(chatRoom))) } else { Log.i("$TAG Conversation isn't in Created state yet, wait for it") chatRoom.addListener(chatRoomListener) } } else { - val id = LinphoneUtils.getChatRoomId(chatRoom) + val id = LinphoneUtils.getConversationId(chatRoom) Log.i("$TAG Conversation successfully created [$id]") operationInProgress.postValue(false) - goToConversationEvent.postValue( - Event( - Pair( - chatRoom.localAddress.asStringUriOnly(), - chatRoom.peerAddress.asStringUriOnly() - ) - ) - ) + goToConversationEvent.postValue(Event(LinphoneUtils.getConversationId(chatRoom))) } } else { Log.e( 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 2d93de4e9..f1f243516 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 @@ -148,23 +148,20 @@ class HistoryFragment : SlidingPaneChildFragment() { } viewModel.goToConversationEvent.observe(viewLifecycleOwner) { - it.consume { pair -> - Log.i("$TAG Going to conversation [${pair.first}][${pair.second}]") - sharedViewModel.showConversationEvent.value = Event(pair) + it.consume { conversationId -> + Log.i("$TAG Going to conversation [$conversationId]") + sharedViewModel.showConversationEvent.value = Event(conversationId) sharedViewModel.navigateToConversationsEvent.value = Event(true) } } viewModel.goToMeetingConversationEvent.observe(viewLifecycleOwner) { - it.consume { pair -> - val localAddress = pair.first - val remoteAddress = pair.second + it.consume { conversationId -> if (findNavController().currentDestination?.id == R.id.historyFragment) { - Log.i("$TAG Going to meeting conversation [$localAddress][$remoteAddress]") + Log.i("$TAG Going to meeting conversation [$conversationId]") val action = HistoryFragmentDirections.actionHistoryFragmentToConferenceConversationFragment( - localAddress, - remoteAddress + conversationId ) findNavController().navigate(action) } diff --git a/app/src/main/java/org/linphone/ui/main/history/viewmodel/HistoryViewModel.kt b/app/src/main/java/org/linphone/ui/main/history/viewmodel/HistoryViewModel.kt index 2e8804040..cd66a73f9 100644 --- a/app/src/main/java/org/linphone/ui/main/history/viewmodel/HistoryViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/history/viewmodel/HistoryViewModel.kt @@ -69,12 +69,12 @@ class HistoryViewModel MutableLiveData>() } - val goToMeetingConversationEvent: MutableLiveData>> by lazy { - MutableLiveData>>() + val goToMeetingConversationEvent: MutableLiveData> by lazy { + MutableLiveData>() } - val goToConversationEvent: MutableLiveData>> by lazy { - MutableLiveData>>() + val goToConversationEvent: MutableLiveData> by lazy { + MutableLiveData>() } val conferenceToJoinEvent: MutableLiveData> by lazy { @@ -111,21 +111,14 @@ class HistoryViewModel val state = chatRoom.state if (state == ChatRoom.State.Instantiated) return - val id = LinphoneUtils.getChatRoomId(chatRoom) + val id = LinphoneUtils.getConversationId(chatRoom) Log.i("$TAG Conversation [$id] (${chatRoom.subject}) state changed: [$state]") if (state == ChatRoom.State.Created) { Log.i("$TAG Conversation [$id] successfully created") chatRoom.removeListener(this) operationInProgress.postValue(false) - goToConversationEvent.postValue( - Event( - Pair( - chatRoom.localAddress.asStringUriOnly(), - chatRoom.peerAddress.asStringUriOnly() - ) - ) - ) + goToConversationEvent.postValue(Event(LinphoneUtils.getConversationId(chatRoom))) } else if (state == ChatRoom.State.CreationFailed) { Log.e("$TAG Conversation [$id] creation has failed!") chatRoom.removeListener(this) @@ -212,14 +205,7 @@ class HistoryViewModel coreContext.postOnCoreThread { val chatRoom = meetingChatRoom if (chatRoom != null) { - goToMeetingConversationEvent.postValue( - Event( - Pair( - chatRoom.localAddress.asStringUriOnly(), - chatRoom.peerAddress.asStringUriOnly() - ) - ) - ) + goToMeetingConversationEvent.postValue(Event(LinphoneUtils.getConversationId(chatRoom))) } else { Log.e("$TAG Failed to find chat room for current call log!") } @@ -279,13 +265,11 @@ class HistoryViewModel val existingChatRoom = core.searchChatRoom(params, localAddress, null, participants) if (existingChatRoom != null) { Log.i( - "$TAG Found existing conversation [${LinphoneUtils.getChatRoomId( + "$TAG Found existing conversation [${LinphoneUtils.getConversationId( existingChatRoom )}], going to it" ) - goToConversationEvent.postValue( - Event(Pair(localSipUri, existingChatRoom.peerAddress.asStringUriOnly())) - ) + goToConversationEvent.postValue(Event(LinphoneUtils.getConversationId(existingChatRoom))) } else { Log.i( "$TAG No existing conversation between [$localSipUri] and [$remoteSipUri] was found, let's create it" @@ -295,33 +279,19 @@ class HistoryViewModel if (chatRoom != null) { if (chatParams.backend == ChatRoom.Backend.FlexisipChat) { if (chatRoom.state == ChatRoom.State.Created) { - val id = LinphoneUtils.getChatRoomId(chatRoom) + val id = LinphoneUtils.getConversationId(chatRoom) Log.i("$TAG 1-1 conversation [$id] has been created") operationInProgress.postValue(false) - goToConversationEvent.postValue( - Event( - Pair( - chatRoom.localAddress.asStringUriOnly(), - chatRoom.peerAddress.asStringUriOnly() - ) - ) - ) + goToConversationEvent.postValue(Event(LinphoneUtils.getConversationId(chatRoom))) } else { Log.i("$TAG Conversation isn't in Created state yet, wait for it") chatRoom.addListener(chatRoomListener) } } else { - val id = LinphoneUtils.getChatRoomId(chatRoom) + val id = LinphoneUtils.getConversationId(chatRoom) Log.i("$TAG Conversation successfully created [$id]") operationInProgress.postValue(false) - goToConversationEvent.postValue( - Event( - Pair( - chatRoom.localAddress.asStringUriOnly(), - chatRoom.peerAddress.asStringUriOnly() - ) - ) - ) + goToConversationEvent.postValue(Event(LinphoneUtils.getConversationId(chatRoom))) } } else { Log.e( diff --git a/app/src/main/java/org/linphone/ui/main/model/ConversationContactOrSuggestionModel.kt b/app/src/main/java/org/linphone/ui/main/model/ConversationContactOrSuggestionModel.kt index 8953b7c24..1c53b546e 100644 --- a/app/src/main/java/org/linphone/ui/main/model/ConversationContactOrSuggestionModel.kt +++ b/app/src/main/java/org/linphone/ui/main/model/ConversationContactOrSuggestionModel.kt @@ -32,7 +32,7 @@ class ConversationContactOrSuggestionModel @WorkerThread constructor( val address: Address, - val localAddress: Address? = null, + val conversationId: String = "", conversationSubject: String? = null, val friend: Friend? = null, private val onClicked: ((Address) -> Unit)? = null diff --git a/app/src/main/java/org/linphone/ui/main/viewmodel/AddressSelectionViewModel.kt b/app/src/main/java/org/linphone/ui/main/viewmodel/AddressSelectionViewModel.kt index 00eae160b..c495b5976 100644 --- a/app/src/main/java/org/linphone/ui/main/viewmodel/AddressSelectionViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/viewmodel/AddressSelectionViewModel.kt @@ -343,6 +343,7 @@ abstract class AddressSelectionViewModel if (chatRoom.isReadOnly || (!isBasic && chatRoom.participants.isEmpty())) continue val isOneToOne = chatRoom.hasCapability(ChatRoom.Capabilities.OneToOne.toInt()) + val conversationId = LinphoneUtils.getConversationId(chatRoom) val remoteAddress = chatRoom.peerAddress val matchesFilter: Any? = if (filter.isEmpty()) { null @@ -383,7 +384,6 @@ abstract class AddressSelectionViewModel } } if (filter.isEmpty() || matchesFilter != null) { - val localAddress = chatRoom.localAddress val friend = if (isBasic) { coreContext.contactsManager.findContactByAddress(remoteAddress) } else { @@ -410,7 +410,7 @@ abstract class AddressSelectionViewModel } val model = ConversationContactOrSuggestionModel( remoteAddress, - localAddress, + conversationId, subject, friend ) 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 55ef4469b..10643b305 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 @@ -118,8 +118,9 @@ class SharedMainViewModel } var displayedChatRoom: ChatRoom? = null // Prevents the need to go look for the chat room - val showConversationEvent: MutableLiveData>> by lazy { - MutableLiveData>>() + + val showConversationEvent: MutableLiveData> by lazy { + MutableLiveData>() } val hideConversationEvent: MutableLiveData> by lazy { diff --git a/app/src/main/java/org/linphone/utils/LinphoneUtils.kt b/app/src/main/java/org/linphone/utils/LinphoneUtils.kt index 7ab86b83d..692e7fa89 100644 --- a/app/src/main/java/org/linphone/utils/LinphoneUtils.kt +++ b/app/src/main/java/org/linphone/utils/LinphoneUtils.kt @@ -439,40 +439,8 @@ class LinphoneUtils { } @WorkerThread - fun getChatRoomId(room: ChatRoom): String { - return getChatRoomId(room.localAddress, room.peerAddress) - } - - @WorkerThread - fun getChatRoomId(localAddress: Address, remoteAddress: Address): String { - val localSipUri = localAddress.clone() - localSipUri.clean() - val remoteSipUri = remoteAddress.clone() - remoteSipUri.clean() - return getChatRoomId(localSipUri.asStringUriOnly(), remoteSipUri.asStringUriOnly()) - } - - @AnyThread - fun getChatRoomId(localSipUri: String, remoteSipUri: String): String { - return "$localSipUri$CHAT_ROOM_ID_SEPARATOR$remoteSipUri" - } - - @AnyThread - fun getLocalAndPeerSipUrisFromChatRoomId(id: String): Pair? { - val split = id.split(CHAT_ROOM_ID_SEPARATOR) - if (split.size == 2) { - val localAddress = split[0] - val peerAddress = split[1] - Log.i( - "$TAG Got local [$localAddress] and peer [$peerAddress] SIP URIs from conversation id [$id]" - ) - return Pair(localAddress, peerAddress) - } else { - Log.e( - "$TAG Failed to parse conversation id [$id] with separator [$CHAT_ROOM_ID_SEPARATOR]" - ) - } - return null + fun getConversationId(chatRoom: ChatRoom): String { + return chatRoom.identifier ?: "" } @WorkerThread diff --git a/app/src/main/java/org/linphone/utils/ShortcutUtils.kt b/app/src/main/java/org/linphone/utils/ShortcutUtils.kt index a70a1c2bb..05709d181 100644 --- a/app/src/main/java/org/linphone/utils/ShortcutUtils.kt +++ b/app/src/main/java/org/linphone/utils/ShortcutUtils.kt @@ -37,6 +37,8 @@ import org.linphone.core.ChatRoom import org.linphone.core.tools.Log import org.linphone.mediastream.Version import org.linphone.ui.main.MainActivity +import org.linphone.ui.main.MainActivity.Companion.ARGUMENTS_CHAT +import org.linphone.ui.main.MainActivity.Companion.ARGUMENTS_CONVERSATION_ID class ShortcutUtils { companion object { @@ -44,7 +46,7 @@ class ShortcutUtils { @WorkerThread fun removeShortcutToChatRoom(chatRoom: ChatRoom) { - val id = LinphoneUtils.getChatRoomId(chatRoom) + val id = LinphoneUtils.getConversationId(chatRoom) Log.i("$TAG Removing shortcut to conversation [$id]") ShortcutManagerCompat.removeLongLivedShortcuts(coreContext.context, arrayListOf(id)) } @@ -67,7 +69,7 @@ class ShortcutUtils { for (chatRoom in defaultAccount.chatRooms) { if (defaultAccount.params.instantMessagingEncryptionMandatory && !chatRoom.currentParams.isEncryptionEnabled) { Log.w( - "$TAG Account is in secure mode, skipping not encrypted conversation [${LinphoneUtils.getChatRoomId( + "$TAG Account is in secure mode, skipping not encrypted conversation [${LinphoneUtils.getConversationId( chatRoom )}]" ) @@ -99,9 +101,8 @@ class ShortcutUtils { @WorkerThread private fun createChatRoomShortcut(context: Context, chatRoom: ChatRoom): ShortcutInfoCompat? { - val localAddress = chatRoom.localAddress val peerAddress = chatRoom.peerAddress - val id = LinphoneUtils.getChatRoomId(localAddress, peerAddress) + val id = LinphoneUtils.getConversationId(chatRoom) try { val categories: ArraySet = ArraySet() @@ -145,19 +146,14 @@ class ShortcutUtils { val persons = arrayOfNulls(personsList.size) personsList.toArray(persons) - val localSipUri = localAddress.asStringUriOnly() - val peerSipUri = peerAddress.asStringUriOnly() - val args = Bundle() - args.putString("RemoteSipUri", peerSipUri) - args.putString("LocalSipUri", localSipUri) + args.putString(ARGUMENTS_CONVERSATION_ID, id) val intent = Intent(Intent.ACTION_MAIN) intent.setClass(context, MainActivity::class.java) intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK) - intent.putExtra("Chat", true) - intent.putExtra("RemoteSipUri", peerSipUri) - intent.putExtra("LocalSipUri", localSipUri) + intent.putExtra(ARGUMENTS_CHAT, true) + intent.putExtra(ARGUMENTS_CONVERSATION_ID, id) return ShortcutInfoCompat.Builder(context, id) .setShortLabel(subject) @@ -177,7 +173,7 @@ class ShortcutUtils { @WorkerThread fun isShortcutToChatRoomAlreadyCreated(context: Context, chatRoom: ChatRoom): Boolean { - val id = LinphoneUtils.getChatRoomId(chatRoom) + val id = LinphoneUtils.getConversationId(chatRoom) val found = ShortcutManagerCompat.getDynamicShortcuts(context).find { it.id == id } diff --git a/app/src/main/res/navigation/call_nav_graph.xml b/app/src/main/res/navigation/call_nav_graph.xml index f72db9565..732c7472b 100644 --- a/app/src/main/res/navigation/call_nav_graph.xml +++ b/app/src/main/res/navigation/call_nav_graph.xml @@ -191,10 +191,7 @@ android:label="ConversationFragment" tools:layout="@layout/chat_conversation_fragment"> - diff --git a/app/src/main/res/navigation/chat_nav_graph.xml b/app/src/main/res/navigation/chat_nav_graph.xml index 0d5b0acb8..0f72c0711 100644 --- a/app/src/main/res/navigation/chat_nav_graph.xml +++ b/app/src/main/res/navigation/chat_nav_graph.xml @@ -17,10 +17,7 @@ android:label="ConversationFragment" tools:layout="@layout/chat_conversation_fragment"> - - - @@ -160,10 +151,7 @@ android:label="ConversationDocumentsListFragment" tools:layout="@layout/chat_documents_fragment"> - diff --git a/app/src/main/res/navigation/history_nav_graph.xml b/app/src/main/res/navigation/history_nav_graph.xml index 2f819c532..4264cef1f 100644 --- a/app/src/main/res/navigation/history_nav_graph.xml +++ b/app/src/main/res/navigation/history_nav_graph.xml @@ -48,10 +48,7 @@ android:label="ConferenceConversationFragment" tools:layout="@layout/chat_conversation_fragment"> - From 875198164d116f4016cebf5cbe0cc3fb109c2b91 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Tue, 18 Feb 2025 11:31:03 +0100 Subject: [PATCH 49/89] Added back auto export media to native gallery feature --- CHANGELOG.md | 1 + .../java/org/linphone/core/CoreContext.kt | 47 +++++++++++++++++++ .../java/org/linphone/core/CorePreferences.kt | 7 +++ app/src/main/java/org/linphone/core/VFS.kt | 7 +++ .../ui/fileviewer/MediaViewerActivity.kt | 9 +++- .../viewmodel/MediaListViewModel.kt | 9 ++-- .../java/org/linphone/ui/main/MainActivity.kt | 29 ++++++++++++ .../ConversationDocumentsListFragment.kt | 1 + .../chat/fragment/ConversationFragment.kt | 1 + .../fragment/ConversationMediaListFragment.kt | 1 + .../ui/main/chat/model/EventLogModel.kt | 6 ++- .../linphone/ui/main/chat/model/FileModel.kt | 1 + .../ui/main/chat/model/MessageModel.kt | 30 +++++++++++- .../ConversationDocumentsListViewModel.kt | 6 ++- .../ConversationMediaListViewModel.kt | 3 +- .../chat/viewmodel/ConversationViewModel.kt | 13 +++++ .../SendMessageInConversationViewModel.kt | 2 +- .../settings/viewmodel/SettingsViewModel.kt | 15 +++++- app/src/main/res/layout/settings_chat.xml | 31 +++++++++++- app/src/main/res/values-fr/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 21 files changed, 208 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 55455ece5..286c7d889 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ Group changes to describe their impact on the project, as follows: - If next message is also a voice recording, playback will automatically start after the currently playing one ends. - Chat while in call: a shortcut to a conversation screen with the remote. - Chat while in a conference: if the conference has a text stream enabled, you can chat with the other participants of the conference while it lasts. At the end, you'll find the messages history in the call history (and not in the list of conversations). +- Auto export of media to native gallery even when auto download is enabled (but still not if VFS is enabled nor for ephemeral messages). - Notification showing upload/download of files shared through chat will let user know the progress and keep the app alive during that process. - Screen sharing in conference: only desktop app starting with 6.0 version is able to start it, but on mobiles you'll be able to see it. - You can choose whatever ringtone you'd like for incoming calls (in Android notification channel settings). diff --git a/app/src/main/java/org/linphone/core/CoreContext.kt b/app/src/main/java/org/linphone/core/CoreContext.kt index 3395fdf04..9ff99de9e 100644 --- a/app/src/main/java/org/linphone/core/CoreContext.kt +++ b/app/src/main/java/org/linphone/core/CoreContext.kt @@ -46,6 +46,7 @@ import org.linphone.ui.call.CallActivity import org.linphone.utils.ActivityMonitor import org.linphone.utils.AppUtils import org.linphone.utils.Event +import org.linphone.utils.FileUtils import org.linphone.utils.LinphoneUtils class CoreContext @@ -112,6 +113,11 @@ class CoreContext MutableLiveData>() } + private var filesToExportToNativeMediaGallery = arrayListOf() + val filesToExportToNativeMediaGalleryEvent: MutableLiveData>> by lazy { + MutableLiveData>>() + } + @SuppressLint("HandlerLeak") private lateinit var coreThread: Handler @@ -155,6 +161,42 @@ class CoreContext private var previousCallState = Call.State.Idle private val coreListener = object : CoreListenerStub() { + @WorkerThread + override fun onMessagesReceived( + core: Core, + chatRoom: ChatRoom, + messages: Array + ) { + if (corePreferences.makePublicMediaFilesDownloaded && core.maxSizeForAutoDownloadIncomingFiles >= 0) { + for (message in messages) { + // Never do auto media export for ephemeral messages! + if (message?.isEphemeral == true) continue + + for (content in message?.contents.orEmpty()) { + if (content.isFile) { + val path = content.filePath + if (path.isNullOrEmpty()) continue + + val mime = "${content.type}/${content.subtype}" + val mimeType = FileUtils.getMimeType(mime) + when (mimeType) { + FileUtils.MimeType.Image, FileUtils.MimeType.Video, FileUtils.MimeType.Audio -> { + Log.i("$TAG Added file path [$path] to the list of media to export to native media gallery") + filesToExportToNativeMediaGallery.add(path) + } + else -> {} + } + } + } + } + } + + if (filesToExportToNativeMediaGallery.isNotEmpty()) { + Log.i("$TAG Creating event with [${filesToExportToNativeMediaGallery.size}] files to export to native media gallery") + filesToExportToNativeMediaGalleryEvent.postValue(Event(filesToExportToNativeMediaGallery)) + } + } + @WorkerThread override fun onGlobalStateChanged(core: Core, state: GlobalState, message: String) { Log.i("$TAG Global state changed [$state]") @@ -628,6 +670,11 @@ class CoreContext return found != null } + @WorkerThread + fun clearFilesToExportToNativeGallery() { + filesToExportToNativeMediaGallery.clear() + } + @WorkerThread fun startAudioCall( address: Address, diff --git a/app/src/main/java/org/linphone/core/CorePreferences.kt b/app/src/main/java/org/linphone/core/CorePreferences.kt index 7a9fac7c2..72bd1e4ca 100644 --- a/app/src/main/java/org/linphone/core/CorePreferences.kt +++ b/app/src/main/java/org/linphone/core/CorePreferences.kt @@ -149,6 +149,13 @@ class CorePreferences config.setBool("app", "mark_as_read_notif_dismissal", value) } + var makePublicMediaFilesDownloaded: Boolean + // Keep old name for backward compatibility + get() = config.getBool("app", "make_downloaded_images_public_in_gallery", false) + set(value) { + config.setBool("app", "make_downloaded_images_public_in_gallery", value) + } + // Conference related @get:WorkerThread @set:WorkerThread diff --git a/app/src/main/java/org/linphone/core/VFS.kt b/app/src/main/java/org/linphone/core/VFS.kt index 135aed367..837cf30d0 100644 --- a/app/src/main/java/org/linphone/core/VFS.kt +++ b/app/src/main/java/org/linphone/core/VFS.kt @@ -27,6 +27,7 @@ import android.util.Base64 import android.util.Pair import androidx.security.crypto.EncryptedSharedPreferences import androidx.security.crypto.MasterKey +import org.linphone.LinphoneApplication.Companion.corePreferences import java.math.BigInteger import java.nio.charset.StandardCharsets import java.security.KeyStore @@ -73,6 +74,12 @@ class VFS { } preferences.edit().putBoolean("vfs_enabled", true).apply() + + if (corePreferences.makePublicMediaFilesDownloaded) { + Log.w("$TAG VFS is now enabled, disabling auto export of media files to native gallery") + corePreferences.makePublicMediaFilesDownloaded = false + } + return true } diff --git a/app/src/main/java/org/linphone/ui/fileviewer/MediaViewerActivity.kt b/app/src/main/java/org/linphone/ui/fileviewer/MediaViewerActivity.kt index 95b831db0..e110a956e 100644 --- a/app/src/main/java/org/linphone/ui/fileviewer/MediaViewerActivity.kt +++ b/app/src/main/java/org/linphone/ui/fileviewer/MediaViewerActivity.kt @@ -99,7 +99,8 @@ class MediaViewerActivity : GenericActivity() { val timestamp = args.getLong("timestamp", -1) val isEncrypted = args.getBoolean("isEncrypted", false) val originalPath = args.getString("originalPath", "") - viewModel.initTempModel(path, timestamp, isEncrypted, originalPath) + val isFromEphemeralMessage = args.getBoolean("isFromEphemeralMessage", false) + viewModel.initTempModel(path, timestamp, isEncrypted, originalPath, isFromEphemeralMessage) val conversationId = args.getString("conversationId").orEmpty() Log.i( @@ -183,6 +184,12 @@ class MediaViewerActivity : GenericActivity() { val currentItem = binding.mediaViewPager.currentItem val model = if (currentItem >= 0 && currentItem < list.size) list[currentItem] else null if (model != null) { + // Never do auto media export for ephemeral messages! + if (model.isFromEphemeralMessage) { + Log.e("$TAG Do not export media from ephemeral message!") + return + } + val filePath = model.path lifecycleScope.launch { withContext(Dispatchers.IO) { diff --git a/app/src/main/java/org/linphone/ui/fileviewer/viewmodel/MediaListViewModel.kt b/app/src/main/java/org/linphone/ui/fileviewer/viewmodel/MediaListViewModel.kt index 364c498a9..9562cdd3a 100644 --- a/app/src/main/java/org/linphone/ui/fileviewer/viewmodel/MediaListViewModel.kt +++ b/app/src/main/java/org/linphone/ui/fileviewer/viewmodel/MediaListViewModel.kt @@ -57,9 +57,9 @@ class MediaListViewModel } @UiThread - fun initTempModel(path: String, timestamp: Long, isEncrypted: Boolean, originalPath: String) { + fun initTempModel(path: String, timestamp: Long, isEncrypted: Boolean, originalPath: String, isFromEphemeralMessage: Boolean) { val name = FileUtils.getNameFromFilePath(path) - val model = FileModel(path, name, 0, timestamp, isEncrypted, originalPath) + val model = FileModel(path, name, 0, timestamp, isEncrypted, originalPath, isFromEphemeralMessage) temporaryModel = model Log.i("$TAG Temporary model for file [$name] created, use it while other media for conversation are being loaded") mediaList.postValue(arrayListOf(model)) @@ -98,7 +98,10 @@ class MediaListViewModel val size = mediaContent.size.toLong() val timestamp = mediaContent.creationTimestamp if (path.isNotEmpty() && name.isNotEmpty()) { - val model = FileModel(path, name, size, timestamp, isEncrypted, originalPath) + // TODO FIXME: we don't have the ephemeral info at Content level, using the chatRoom info even if content ephemeral status may or may not be different... + val ephemeral = chatRoom.isEphemeralEnabled + + val model = FileModel(path, name, size, timestamp, isEncrypted, originalPath, ephemeral) list.add(model) } diff --git a/app/src/main/java/org/linphone/ui/main/MainActivity.kt b/app/src/main/java/org/linphone/ui/main/MainActivity.kt index 891de4c80..21e33b655 100644 --- a/app/src/main/java/org/linphone/ui/main/MainActivity.kt +++ b/app/src/main/java/org/linphone/ui/main/MainActivity.kt @@ -52,9 +52,11 @@ import androidx.navigation.NavOptions import androidx.navigation.findNavController import kotlin.math.max import kotlinx.coroutines.Deferred +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.LinphoneApplication.Companion.corePreferences import org.linphone.R @@ -313,6 +315,19 @@ class MainActivity : GenericActivity() { } } + coreContext.filesToExportToNativeMediaGalleryEvent.observe(this) { + it.consume { files -> + Log.i("$TAG Found [${files.size}] files to export to native media gallery") + for (file in files) { + exportFileToNativeMediaGallery(file) + } + + coreContext.postOnCoreThread { + coreContext.clearFilesToExportToNativeGallery() + } + } + } + CarConnection(this).type.observe(this) { val asString = when (it) { CarConnection.CONNECTION_TYPE_NOT_CONNECTED -> "NOT CONNECTED" @@ -777,4 +792,18 @@ class MainActivity : GenericActivity() { dialog.show() currentlyDisplayedAuthDialog = dialog } + + private fun exportFileToNativeMediaGallery(filePath: String) { + lifecycleScope.launch { + withContext(Dispatchers.IO) { + Log.i("$TAG Export file [$filePath] to Android's MediaStore") + val mediaStorePath = FileUtils.addContentToMediaStore(filePath) + if (mediaStorePath.isNotEmpty()) { + Log.i("$TAG File [$filePath] has been successfully exported to MediaStore") + } else { + Log.e("$TAG Failed to export file [$filePath] to MediaStore!") + } + } + } + } } diff --git a/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationDocumentsListFragment.kt b/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationDocumentsListFragment.kt index 9b410ca77..80b334fdd 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationDocumentsListFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationDocumentsListFragment.kt @@ -145,6 +145,7 @@ class ConversationDocumentsListFragment : SlidingPaneChildFragment() { putBoolean("isEncrypted", fileModel.isEncrypted) putLong("timestamp", fileModel.fileCreationTimestamp) putString("originalPath", fileModel.originalPath) + putBoolean("isFromEphemeralMessage", fileModel.isFromEphemeralMessage) putBoolean("isMedia", false) } when (FileUtils.getMimeType(mime)) { 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 index f8dfae277..dd44bcd17 100644 --- 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 @@ -1114,6 +1114,7 @@ open class ConversationFragment : SlidingPaneChildFragment() { putBoolean("isEncrypted", fileModel.isEncrypted) putLong("timestamp", fileModel.fileCreationTimestamp) putString("originalPath", fileModel.originalPath) + putBoolean("isFromEphemeralMessage", fileModel.isFromEphemeralMessage) } when (mimeType) { FileUtils.MimeType.Image, FileUtils.MimeType.Video, FileUtils.MimeType.Audio -> { diff --git a/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationMediaListFragment.kt b/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationMediaListFragment.kt index abece7630..76a9c5e06 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationMediaListFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationMediaListFragment.kt @@ -174,6 +174,7 @@ class ConversationMediaListFragment : SlidingPaneChildFragment() { putBoolean("isEncrypted", fileModel.isEncrypted) putLong("timestamp", fileModel.fileCreationTimestamp) putString("originalPath", fileModel.originalPath) + putBoolean("isFromEphemeralMessage", fileModel.isFromEphemeralMessage) putBoolean("isMedia", true) } when (FileUtils.getMimeType(mime)) { diff --git a/app/src/main/java/org/linphone/ui/main/chat/model/EventLogModel.kt b/app/src/main/java/org/linphone/ui/main/chat/model/EventLogModel.kt index 948ae8de8..257c50357 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/model/EventLogModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/model/EventLogModel.kt @@ -38,7 +38,8 @@ class EventLogModel onWebUrlClicked: ((url: String) -> Unit)? = null, onContactClicked: ((friendRefKey: String) -> Unit)? = null, onRedToastToShow: ((pair: Pair) -> Unit)? = null, - onVoiceRecordingPlaybackEnded: ((id: String) -> Unit)? = null + onVoiceRecordingPlaybackEnded: ((id: String) -> Unit)? = null, + onFileToExportToNativeGallery: ((path: String) -> Unit)? = null ) { companion object { private const val TAG = "[Event Log Model]" @@ -89,7 +90,8 @@ class EventLogModel onWebUrlClicked, onContactClicked, onRedToastToShow, - onVoiceRecordingPlaybackEnded + onVoiceRecordingPlaybackEnded, + onFileToExportToNativeGallery ) } diff --git a/app/src/main/java/org/linphone/ui/main/chat/model/FileModel.kt b/app/src/main/java/org/linphone/ui/main/chat/model/FileModel.kt index c8cc3c868..f02346c08 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/model/FileModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/model/FileModel.kt @@ -45,6 +45,7 @@ class FileModel val fileCreationTimestamp: Long, val isEncrypted: Boolean, val originalPath: String, + val isFromEphemeralMessage: Boolean, val isWaitingToBeDownloaded: Boolean = false, val flexboxLayoutWrapBefore: Boolean = false, private val onClicked: ((model: FileModel) -> Unit)? = null diff --git a/app/src/main/java/org/linphone/ui/main/chat/model/MessageModel.kt b/app/src/main/java/org/linphone/ui/main/chat/model/MessageModel.kt index 7c0a9b5de..de4c8cc1a 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/model/MessageModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/model/MessageModel.kt @@ -42,6 +42,7 @@ import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import org.linphone.LinphoneApplication.Companion.coreContext +import org.linphone.LinphoneApplication.Companion.corePreferences import org.linphone.R import org.linphone.core.Address import org.linphone.core.ChatMessage @@ -82,7 +83,8 @@ class MessageModel private val onWebUrlClicked: ((url: String) -> Unit)? = null, private val onContactClicked: ((friendRefKey: String) -> Unit)? = null, private val onRedToastToShow: ((pair: Pair) -> Unit)? = null, - private val onVoiceRecordingPlaybackEnded: ((id: String) -> Unit)? = null + private val onVoiceRecordingPlaybackEnded: ((id: String) -> Unit)? = null, + private val onFileToExportToNativeGallery: ((path: String) -> Unit)? = null ) { companion object { private const val TAG = "[Message Model]" @@ -224,6 +226,27 @@ class MessageModel } } + @WorkerThread + override fun onFileTransferTerminated(message: ChatMessage, content: Content) { + Log.i("$TAG File [${content.name}] from message [${message.messageId}] transfer terminated") + + // Never do auto media export for ephemeral messages! + if (corePreferences.makePublicMediaFilesDownloaded && !message.isEphemeral) { + val path = content.filePath + if (path.isNullOrEmpty()) return + + val mime = "${content.type}/${content.subtype}" + val mimeType = FileUtils.getMimeType(mime) + when (mimeType) { + FileUtils.MimeType.Image, FileUtils.MimeType.Video, FileUtils.MimeType.Audio -> { + Log.i("$TAG Exporting file path [$path] to the native media gallery") + onFileToExportToNativeGallery?.invoke(path) + } + else -> {} + } + } + } + @WorkerThread override fun onNewMessageReaction(message: ChatMessage, reaction: ChatMessageReaction) { Log.i( @@ -443,6 +466,7 @@ class MessageModel timestamp, isFileEncrypted, originalPath, + chatMessage.isEphemeral, flexboxLayoutWrapBefore = wrapBefore ) { model -> onContentClicked?.invoke(model) @@ -470,7 +494,8 @@ class MessageModel content.fileSize.toLong(), timestamp, isFileEncrypted, - path + path, + chatMessage.isEphemeral ) { model -> onContentClicked?.invoke(model) } @@ -482,6 +507,7 @@ class MessageModel timestamp, isFileEncrypted, name, + chatMessage.isEphemeral, isWaitingToBeDownloaded = true ) { model -> downloadContent(model, content) diff --git a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationDocumentsListViewModel.kt b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationDocumentsListViewModel.kt index ecf157930..502f145f0 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationDocumentsListViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationDocumentsListViewModel.kt @@ -79,7 +79,11 @@ class ConversationDocumentsListViewModel val size = documentContent.size.toLong() val timestamp = documentContent.creationTimestamp if (path.isNotEmpty() && name.isNotEmpty()) { - val model = FileModel(path, name, size, timestamp, isEncrypted, originalPath) { + // TODO FIXME: we don't have the ephemeral info at Content level, using the chatRoom info even if content ephemeral status may or may not be different... + val ephemeral = chatRoom.isEphemeralEnabled + + val model = + FileModel(path, name, size, timestamp, isEncrypted, originalPath, ephemeral) { openDocumentEvent.postValue(Event(it)) } list.add(model) diff --git a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationMediaListViewModel.kt b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationMediaListViewModel.kt index edf9d4884..f05ff724f 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationMediaListViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationMediaListViewModel.kt @@ -78,7 +78,8 @@ class ConversationMediaListViewModel val size = mediaContent.size.toLong() val timestamp = mediaContent.creationTimestamp if (path.isNotEmpty() && name.isNotEmpty()) { - val model = FileModel(path, name, size, timestamp, isEncrypted, originalPath) { + val model = + FileModel(path, name, size, timestamp, isEncrypted, originalPath, chatRoom.isEphemeralEnabled) { openMediaEvent.postValue(Event(it)) } list.add(model) 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 index d592a9495..6ddd58e44 100644 --- 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 @@ -761,6 +761,19 @@ class ConversationViewModel }, { id -> voiceRecordPlaybackEndedEvent.postValue(Event(id)) + }, + { filePath -> + viewModelScope.launch { + withContext(Dispatchers.IO) { + Log.i("$TAG Export file [$filePath] to Android's MediaStore") + val mediaStorePath = FileUtils.addContentToMediaStore(filePath) + if (mediaStorePath.isNotEmpty()) { + Log.i("$TAG File [$filePath] has been successfully exported to MediaStore") + } else { + Log.e("$TAG Failed to export file [$filePath] to MediaStore!") + } + } + } } ) eventsList.add(model) diff --git a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/SendMessageInConversationViewModel.kt b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/SendMessageInConversationViewModel.kt index 230a78b56..0c711eba4 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/SendMessageInConversationViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/SendMessageInConversationViewModel.kt @@ -397,7 +397,7 @@ class SendMessageInConversationViewModel val fileName = FileUtils.getNameFromFilePath(file) val timestamp = System.currentTimeMillis() / 1000 - val model = FileModel(file, fileName, 0, timestamp, false, file) { model -> + val model = FileModel(file, fileName, 0, timestamp, false, file, chatRoom.isEphemeralEnabled) { model -> removeAttachment(model.path) } diff --git a/app/src/main/java/org/linphone/ui/main/settings/viewmodel/SettingsViewModel.kt b/app/src/main/java/org/linphone/ui/main/settings/viewmodel/SettingsViewModel.kt index 9c90b3894..eaef19b0c 100644 --- a/app/src/main/java/org/linphone/ui/main/settings/viewmodel/SettingsViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/settings/viewmodel/SettingsViewModel.kt @@ -90,6 +90,8 @@ class SettingsViewModel val autoDownloadEnabled = MutableLiveData() + val autoExportMediaToNativeGallery = MutableLiveData() + val markAsReadWhenDismissingNotification = MutableLiveData() // Contacts settings @@ -246,7 +248,8 @@ class SettingsViewModel expandAudioCodecs.value = false expandVideoCodecs.value = false - isVfsEnabled.value = VFS.isEnabled(coreContext.context) + val vfsEnabled = VFS.isEnabled(coreContext.context) + isVfsEnabled.value = vfsEnabled val vibrator = coreContext.context.getSystemService(Vibrator::class.java) isVibrationAvailable.value = vibrator.hasVibrator() @@ -285,6 +288,7 @@ class SettingsViewModel allowIpv6.postValue(core.isIpv6Enabled) autoDownloadEnabled.postValue(core.maxSizeForAutoDownloadIncomingFiles == 0) + autoExportMediaToNativeGallery.postValue(corePreferences.makePublicMediaFilesDownloaded && !vfsEnabled) markAsReadWhenDismissingNotification.postValue( corePreferences.markConversationAsReadWhenDismissingMessageNotification ) @@ -439,6 +443,15 @@ class SettingsViewModel } } + @UiThread + fun toggleAutoExportMediaFilesToNativeGallery() { + val newValue = autoExportMediaToNativeGallery.value == false + coreContext.postOnCoreThread { core -> + corePreferences.makePublicMediaFilesDownloaded = newValue + autoExportMediaToNativeGallery.postValue(newValue) + } + } + @UiThread fun toggleMarkConversationAsReadWhenDismissingNotification() { val newValue = markAsReadWhenDismissingNotification.value == false diff --git a/app/src/main/res/layout/settings_chat.xml b/app/src/main/res/layout/settings_chat.xml index d118eada6..0e9522744 100644 --- a/app/src/main/res/layout/settings_chat.xml +++ b/app/src/main/res/layout/settings_chat.xml @@ -40,10 +40,39 @@ android:layout_height="wrap_content" android:layout_marginTop="20dp" android:layout_marginEnd="16dp" + android:enabled="@{!viewModel.isVfsEnabled}" android:checked="@{viewModel.autoDownloadEnabled}" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" /> + + + + + app:layout_constraintTop_toBottomOf="@id/auto_export_media_to_native_gallery_switch" /> diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 8c533f513..d7557d089 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -201,6 +201,7 @@ Changer de sonnerie Conversations Télécharger automatiquement les fichiers + Rendre visible dans la galerie les médias téléchargés Marquer la conversation comme lue lorsqu\'une notification de message est supprimée Contacts Ajouter un serveur LDAP diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index aa82e83a6..f66b4bbed 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -240,6 +240,7 @@ Change ringtone Conversations Auto-download files + Make downloaded media public Mark conversation as read when dismissing message notification Contacts Add LDAP server From ec9c7bd070908145894a62d3a9fc7355bbec332b Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Tue, 18 Feb 2025 12:02:57 +0100 Subject: [PATCH 50/89] Bumped AGP to 8.8.1 --- .../chat/viewmodel/ConversationForwardMessageViewModel.kt | 4 ++-- .../ui/main/chat/viewmodel/StartConversationViewModel.kt | 4 ++-- .../linphone/ui/main/contacts/viewmodel/ContactViewModel.kt | 4 ++-- .../linphone/ui/main/history/viewmodel/HistoryViewModel.kt | 4 ++-- gradle/libs.versions.toml | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationForwardMessageViewModel.kt b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationForwardMessageViewModel.kt index 278b12c6c..db0f32397 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationForwardMessageViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationForwardMessageViewModel.kt @@ -179,13 +179,13 @@ class ConversationForwardMessageViewModel val sameDomain = remote.domain == corePreferences.defaultDomain && remote.domain == account.params.domain if (account.params.instantMessagingEncryptionMandatory && sameDomain) { - Log.i("$TAG Account is in secure mode & domain matches, creating a E2E conversation") + Log.i("$TAG Account is in secure mode & domain matches, creating an E2E encrypted conversation") chatParams.backend = ChatRoom.Backend.FlexisipChat params.securityLevel = Conference.SecurityLevel.EndToEnd } else if (!account.params.instantMessagingEncryptionMandatory) { if (LinphoneUtils.isEndToEndEncryptedChatAvailable(core)) { Log.i( - "$TAG Account is in interop mode but LIME is available, creating a E2E conversation" + "$TAG Account is in interop mode but LIME is available, creating an E2E encrypted conversation" ) chatParams.backend = ChatRoom.Backend.FlexisipChat params.securityLevel = Conference.SecurityLevel.EndToEnd diff --git a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/StartConversationViewModel.kt b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/StartConversationViewModel.kt index fc87abd9e..057f6566d 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/StartConversationViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/StartConversationViewModel.kt @@ -181,13 +181,13 @@ class StartConversationViewModel val sameDomain = remote.domain == corePreferences.defaultDomain && remote.domain == account.params.domain if (account.params.instantMessagingEncryptionMandatory && sameDomain) { - Log.i("$TAG Account is in secure mode & domain matches, creating a E2E conversation") + Log.i("$TAG Account is in secure mode & domain matches, creating an E2E encrypted conversation") chatParams.backend = ChatRoom.Backend.FlexisipChat params.securityLevel = Conference.SecurityLevel.EndToEnd } else if (!account.params.instantMessagingEncryptionMandatory) { if (LinphoneUtils.isEndToEndEncryptedChatAvailable(core)) { Log.i( - "$TAG Account is in interop mode but LIME is available, creating a E2E conversation" + "$TAG Account is in interop mode but LIME is available, creating an E2E encrypted conversation" ) chatParams.backend = ChatRoom.Backend.FlexisipChat params.securityLevel = Conference.SecurityLevel.EndToEnd diff --git a/app/src/main/java/org/linphone/ui/main/contacts/viewmodel/ContactViewModel.kt b/app/src/main/java/org/linphone/ui/main/contacts/viewmodel/ContactViewModel.kt index f6c15cea5..e78fa9e43 100644 --- a/app/src/main/java/org/linphone/ui/main/contacts/viewmodel/ContactViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/contacts/viewmodel/ContactViewModel.kt @@ -505,14 +505,14 @@ class ContactViewModel val sameDomain = remote.domain == corePreferences.defaultDomain && remote.domain == account.params.domain if (account.params.instantMessagingEncryptionMandatory && sameDomain) { Log.i( - "$TAG Account is in secure mode & domain matches, creating a E2E conversation" + "$TAG Account is in secure mode & domain matches, creating an E2E encrypted conversation" ) chatParams.backend = ChatRoom.Backend.FlexisipChat params.securityLevel = Conference.SecurityLevel.EndToEnd } else if (!account.params.instantMessagingEncryptionMandatory) { if (LinphoneUtils.isEndToEndEncryptedChatAvailable(core)) { Log.i( - "$TAG Account is in interop mode but LIME is available, creating a E2E conversation" + "$TAG Account is in interop mode but LIME is available, creating an E2E encrypted conversation" ) chatParams.backend = ChatRoom.Backend.FlexisipChat params.securityLevel = Conference.SecurityLevel.EndToEnd diff --git a/app/src/main/java/org/linphone/ui/main/history/viewmodel/HistoryViewModel.kt b/app/src/main/java/org/linphone/ui/main/history/viewmodel/HistoryViewModel.kt index cd66a73f9..5d12485df 100644 --- a/app/src/main/java/org/linphone/ui/main/history/viewmodel/HistoryViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/history/viewmodel/HistoryViewModel.kt @@ -234,14 +234,14 @@ class HistoryViewModel val sameDomain = remote.domain == corePreferences.defaultDomain && remote.domain == account.params.domain if (account.params.instantMessagingEncryptionMandatory && sameDomain) { Log.i( - "$TAG Account is in secure mode & domain matches, creating a E2E conversation" + "$TAG Account is in secure mode & domain matches, creating an E2E encrypted conversation" ) chatParams.backend = ChatRoom.Backend.FlexisipChat params.securityLevel = Conference.SecurityLevel.EndToEnd } else if (!account.params.instantMessagingEncryptionMandatory) { if (LinphoneUtils.isEndToEndEncryptedChatAvailable(core)) { Log.i( - "$TAG Account is in interop mode but LIME is available, creating a E2E conversation" + "$TAG Account is in interop mode but LIME is available, creating an E2E encrypted conversation" ) chatParams.backend = ChatRoom.Backend.FlexisipChat params.securityLevel = Conference.SecurityLevel.EndToEnd diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index aca3e0e0e..1b2900970 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -agp = "8.8.0" +agp = "8.8.1" kotlin = "2.0.21" gmsGoogleServices = "4.4.2" firebaseCrashlytics = "3.0.3" From ef794755252ab912885d9f03c5fb23e1b36ab03c Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Tue, 18 Feb 2025 16:33:50 +0100 Subject: [PATCH 51/89] Fixed resend menu not visible when message goes into error state unless conversation is left & opened again --- .../java/org/linphone/ui/main/chat/model/MessageModel.kt | 5 ++++- .../ui/main/chat/viewmodel/ChatMessageLongPressViewModel.kt | 3 +-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/linphone/ui/main/chat/model/MessageModel.kt b/app/src/main/java/org/linphone/ui/main/chat/model/MessageModel.kt index de4c8cc1a..d7027e537 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/model/MessageModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/model/MessageModel.kt @@ -100,7 +100,7 @@ class MessageModel val isOutgoing = chatMessage.isOutgoing - val isInError = chatMessage.state == ChatMessage.State.NotDelivered + val isInError = MutableLiveData() val timestamp = chatMessage.time @@ -224,6 +224,7 @@ class MessageModel } } } + isInError.postValue(messageState == ChatMessage.State.NotDelivered) } @WorkerThread @@ -297,6 +298,8 @@ class MessageModel init { updateAvatarModel() + isInError.postValue(chatMessage.state == ChatMessage.State.NotDelivered) + groupedWithNextMessage.postValue(isGroupedWithNextOne) groupedWithPreviousMessage.postValue(isGroupedWithPreviousOne) isPlayingVoiceRecord.postValue(false) diff --git a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ChatMessageLongPressViewModel.kt b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ChatMessageLongPressViewModel.kt index d56aa551f..32e65c0aa 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ChatMessageLongPressViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ChatMessageLongPressViewModel.kt @@ -72,7 +72,6 @@ class ChatMessageLongPressViewModel : GenericViewModel() { val onDismissedEvent = MutableLiveData>() - private lateinit var emojiBottomSheet: ChatBubbleEmojiPickerBottomSheetBinding private lateinit var emojiBottomSheetBehavior: BottomSheetBehavior init { @@ -91,7 +90,7 @@ class ChatMessageLongPressViewModel : GenericViewModel() { hideCopyTextToClipboard.value = model.text.value.isNullOrEmpty() isChatRoomReadOnly.value = model.chatRoomIsReadOnly isMessageOutgoing.value = model.isOutgoing - isMessageInError.value = model.isInError + isMessageInError.value = model.isInError.value == true horizontalBias.value = if (model.isOutgoing) 1f else 0f messageModel.value = model From 0bf6d60570dc844d65daf6bed4c286b2d31a57cb Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Tue, 18 Feb 2025 16:58:28 +0100 Subject: [PATCH 52/89] Prevent crash if MediaPlayer can't be instancianted --- .../ui/fileviewer/viewmodel/MediaViewModel.kt | 58 +++++++++++-------- app/src/main/res/values-fr/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 3 files changed, 35 insertions(+), 25 deletions(-) diff --git a/app/src/main/java/org/linphone/ui/fileviewer/viewmodel/MediaViewModel.kt b/app/src/main/java/org/linphone/ui/fileviewer/viewmodel/MediaViewModel.kt index d7a0a8ce5..eecc3ffe0 100644 --- a/app/src/main/java/org/linphone/ui/fileviewer/viewmodel/MediaViewModel.kt +++ b/app/src/main/java/org/linphone/ui/fileviewer/viewmodel/MediaViewModel.kt @@ -34,6 +34,7 @@ import org.linphone.ui.GenericViewModel import org.linphone.utils.Event import org.linphone.utils.FileUtils import org.linphone.utils.TimestampUtils +import org.linphone.R class MediaViewModel @UiThread @@ -163,33 +164,40 @@ class MediaViewModel private fun initMediaPlayer() { isMediaPlaying.value = false - mediaPlayer = MediaPlayer().apply { - setAudioAttributes( - AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_MUSIC).setUsage( - AudioAttributes.USAGE_MEDIA - ).build() - ) - setDataSource(filePath) - setOnCompletionListener { - Log.i("$TAG Media player reached the end of file") - isMediaPlaying.postValue(false) - position.postValue(0) - stopUpdatePlaybackPosition() + try { + mediaPlayer = MediaPlayer().apply { + setAudioAttributes( + AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) + .setUsage( + AudioAttributes.USAGE_MEDIA + ).build() + ) + setDataSource(filePath) + setOnCompletionListener { + Log.i("$TAG Media player reached the end of file") + isMediaPlaying.postValue(false) + position.postValue(0) + stopUpdatePlaybackPosition() - // Leave full screen when playback is done - fullScreenMode.postValue(false) - changeFullScreenModeEvent.postValue(Event(false)) - } - setOnVideoSizeChangedListener { mediaPlayer, width, height -> - videoSizeChangedEvent.postValue(Event(Pair(width, height))) - } - try { - prepare() - } catch (e: Exception) { - fullScreenMode.postValue(false) - changeFullScreenModeEvent.postValue(Event(false)) - Log.e("$TAG Failed to prepare video file: $e") + // Leave full screen when playback is done + fullScreenMode.postValue(false) + changeFullScreenModeEvent.postValue(Event(false)) + } + setOnVideoSizeChangedListener { mediaPlayer, width, height -> + videoSizeChangedEvent.postValue(Event(Pair(width, height))) + } + try { + prepare() + } catch (e: Exception) { + fullScreenMode.postValue(false) + changeFullScreenModeEvent.postValue(Event(false)) + Log.e("$TAG Failed to prepare video file: $e") + } } + } catch (e: Exception) { + Log.e("$TAG Failed to initialize media player for file [$filePath]: $e") + showRedToast(R.string.media_player_generic_error_toast, R.drawable.warning_circle) + return } val durationInMillis = mediaPlayer.duration diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index d7557d089..22f3c3670 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -90,6 +90,7 @@ Volume faible : vous risquez de ne pas entendre Configuration appliquée Erreur lors du chargement ou de l\'application de la configuration + Erreur lors de la création du lecteur média Conditions de service & politique de confidentialité diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f66b4bbed..567b02e0a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -129,6 +129,7 @@ Media volume is low, you may not hear anything! Configuration successfully applied Error while trying to download and apply remote configuration + Error trying to create media player General terms & privacy policy From f3991fb29d29c0c6bc05e22a1de02befa8f27a36 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Wed, 19 Feb 2025 13:51:27 +0100 Subject: [PATCH 53/89] Small improvements for 7 inches screen in portrait --- .../assistant_landing_fragment.xml | 26 +++++++++---------- .../assistant_permissions_fragment.xml | 16 +++++++++--- ...ant_register_confirm_sms_code_fragment.xml | 26 +++++++++---------- .../assistant_register_fragment.xml | 26 +++++++++---------- ...third_party_sip_account_login_fragment.xml | 26 +++++++++---------- ...ird_party_sip_account_warning_fragment.xml | 26 +++++++++---------- .../res/layout-sw600dp/welcome_page_1.xml | 2 +- .../res/layout-sw600dp/welcome_page_2.xml | 2 +- .../res/layout-sw600dp/welcome_page_3.xml | 2 +- 9 files changed, 80 insertions(+), 72 deletions(-) diff --git a/app/src/main/res/layout-sw600dp/assistant_landing_fragment.xml b/app/src/main/res/layout-sw600dp/assistant_landing_fragment.xml index 78147ea66..58afd27b3 100644 --- a/app/src/main/res/layout-sw600dp/assistant_landing_fragment.xml +++ b/app/src/main/res/layout-sw600dp/assistant_landing_fragment.xml @@ -35,6 +35,19 @@ android:layout_width="match_parent" android:layout_height="wrap_content"> + + - - + + - - + + - - + + - - + + - - Date: Wed, 19 Feb 2025 14:14:51 +0100 Subject: [PATCH 54/89] Fixed password text fields not using the right font when in visible mode & for hint text --- app/src/main/java/org/linphone/utils/DataBindingUtils.kt | 7 +++++++ app/src/main/java/org/linphone/utils/LinphoneUtils.kt | 2 -- .../main/res/layout-sw600dp/assistant_landing_fragment.xml | 2 +- .../res/layout-sw600dp/assistant_register_fragment.xml | 2 +- .../assistant_third_party_sip_account_login_fragment.xml | 2 +- app/src/main/res/layout/assistant_landing_fragment.xml | 2 +- app/src/main/res/layout/assistant_register_fragment.xml | 2 +- .../assistant_third_party_sip_account_login_fragment.xml | 2 +- app/src/main/res/layout/dialog_update_account_password.xml | 2 +- ...alog_update_account_password_after_register_failure.xml | 2 +- app/src/main/res/layout/settings_contacts_carddav.xml | 2 +- app/src/main/res/layout/settings_contacts_ldap.xml | 2 +- 12 files changed, 17 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/org/linphone/utils/DataBindingUtils.kt b/app/src/main/java/org/linphone/utils/DataBindingUtils.kt index 18904df0d..f9cff847b 100644 --- a/app/src/main/java/org/linphone/utils/DataBindingUtils.kt +++ b/app/src/main/java/org/linphone/utils/DataBindingUtils.kt @@ -575,6 +575,13 @@ fun RoundCornersTextureView.setRoundCornersRadius(radius: Float) { setRadius(radius) } +@BindingAdapter("passwordInputType") +fun setInputTypeAndFont(editText: EditText, type: Int) { + editText.inputType = type + // Typeface must be set again... + editText.typeface = ResourcesCompat.getFont(editText.context, R.font.noto_sans) +} + @BindingAdapter("focusNextOnInput") fun focusNextOnInput(editText: EditText, enabled: Boolean) { if (!enabled) return diff --git a/app/src/main/java/org/linphone/utils/LinphoneUtils.kt b/app/src/main/java/org/linphone/utils/LinphoneUtils.kt index 692e7fa89..4a3e15724 100644 --- a/app/src/main/java/org/linphone/utils/LinphoneUtils.kt +++ b/app/src/main/java/org/linphone/utils/LinphoneUtils.kt @@ -63,8 +63,6 @@ class LinphoneUtils { const val RECORDING_FILE_NAME_URI_TIMESTAMP_SEPARATOR = "_on_" const val RECORDING_FILE_EXTENSION = ".smff" - private const val CHAT_ROOM_ID_SEPARATOR = "#~#" - @WorkerThread fun getDefaultAccount(): Account? { return coreContext.core.defaultAccount ?: coreContext.core.accountList.firstOrNull() diff --git a/app/src/main/res/layout-sw600dp/assistant_landing_fragment.xml b/app/src/main/res/layout-sw600dp/assistant_landing_fragment.xml index 58afd27b3..ea889e0ee 100644 --- a/app/src/main/res/layout-sw600dp/assistant_landing_fragment.xml +++ b/app/src/main/res/layout-sw600dp/assistant_landing_fragment.xml @@ -172,7 +172,7 @@ android:textColor="?attr/color_main2_600" android:background="@drawable/edit_text_background" android:hint="@string/password" - android:inputType="@{viewModel.showPassword ? InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD : InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD, default=textPassword}" + passwordInputType="@{viewModel.showPassword ? InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD : InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD, default=textPassword}" app:layout_constraintWidth_max="@dimen/text_input_max_width" app:layout_constraintStart_toStartOf="@id/title" app:layout_constraintEnd_toEndOf="@id/title" diff --git a/app/src/main/res/layout-sw600dp/assistant_register_fragment.xml b/app/src/main/res/layout-sw600dp/assistant_register_fragment.xml index 16b5dae49..6afa574ed 100644 --- a/app/src/main/res/layout-sw600dp/assistant_register_fragment.xml +++ b/app/src/main/res/layout-sw600dp/assistant_register_fragment.xml @@ -247,7 +247,7 @@ android:textColor="?attr/color_main2_600" android:background="@drawable/edit_text_background" android:hint="@string/password" - android:inputType="@{viewModel.showPassword ? InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD : InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD, default=textPassword}" + passwordInputType="@{viewModel.showPassword ? InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD : InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD, default=textPassword}" app:layout_constraintWidth_max="@dimen/text_input_max_width" app:layout_constraintTop_toBottomOf="@id/password_label" app:layout_constraintBottom_toTopOf="@id/password_error" diff --git a/app/src/main/res/layout-sw600dp/assistant_third_party_sip_account_login_fragment.xml b/app/src/main/res/layout-sw600dp/assistant_third_party_sip_account_login_fragment.xml index d2b765e20..f33491050 100644 --- a/app/src/main/res/layout-sw600dp/assistant_third_party_sip_account_login_fragment.xml +++ b/app/src/main/res/layout-sw600dp/assistant_third_party_sip_account_login_fragment.xml @@ -128,7 +128,7 @@ android:textColor="?attr/color_main2_600" android:background="@drawable/edit_text_background" android:hint="@string/password" - android:inputType="@{viewModel.showPassword ? InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD : InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD, default=textPassword}" + passwordInputType="@{viewModel.showPassword ? InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD : InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD, default=textPassword}" app:layout_constraintWidth_max="@dimen/text_input_max_width" app:layout_constraintTop_toBottomOf="@id/password_label" app:layout_constraintBottom_toTopOf="@id/domain_label" diff --git a/app/src/main/res/layout/assistant_landing_fragment.xml b/app/src/main/res/layout/assistant_landing_fragment.xml index cd726cf98..0bf744549 100644 --- a/app/src/main/res/layout/assistant_landing_fragment.xml +++ b/app/src/main/res/layout/assistant_landing_fragment.xml @@ -132,7 +132,7 @@ android:textColor="?attr/color_main2_600" android:background="@drawable/edit_text_background" android:hint="@string/password" - android:inputType="@{viewModel.showPassword ? InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD : InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD, default=textPassword}" + passwordInputType="@{viewModel.showPassword ? InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD : InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD, default=textPassword}" app:layout_constraintWidth_max="@dimen/text_input_max_width" app:layout_constraintTop_toBottomOf="@id/password_label" app:layout_constraintStart_toStartOf="parent" diff --git a/app/src/main/res/layout/assistant_register_fragment.xml b/app/src/main/res/layout/assistant_register_fragment.xml index 4d1a4a193..ec8ae5fc8 100644 --- a/app/src/main/res/layout/assistant_register_fragment.xml +++ b/app/src/main/res/layout/assistant_register_fragment.xml @@ -235,7 +235,7 @@ android:textColor="?attr/color_main2_600" android:background="@drawable/edit_text_background" android:hint="@string/password" - android:inputType="@{viewModel.showPassword ? InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD : InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD, default=textPassword}" + passwordInputType="@{viewModel.showPassword ? InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD : InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD, default=textPassword}" app:layout_constraintWidth_max="@dimen/text_input_max_width" app:layout_constraintTop_toBottomOf="@id/password_label" app:layout_constraintStart_toStartOf="parent" diff --git a/app/src/main/res/layout/assistant_third_party_sip_account_login_fragment.xml b/app/src/main/res/layout/assistant_third_party_sip_account_login_fragment.xml index db7afa09f..80b8e9665 100644 --- a/app/src/main/res/layout/assistant_third_party_sip_account_login_fragment.xml +++ b/app/src/main/res/layout/assistant_third_party_sip_account_login_fragment.xml @@ -120,7 +120,7 @@ android:textColor="?attr/color_main2_600" android:background="@drawable/edit_text_background" android:hint="@string/password" - android:inputType="@{viewModel.showPassword ? InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD : InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD, default=textPassword}" + passwordInputType="@{viewModel.showPassword ? InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD : InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD, default=textPassword}" app:layout_constraintWidth_max="@dimen/text_input_max_width" app:layout_constraintTop_toBottomOf="@id/password_label" app:layout_constraintStart_toStartOf="parent" diff --git a/app/src/main/res/layout/dialog_update_account_password.xml b/app/src/main/res/layout/dialog_update_account_password.xml index 381a05e83..6f9fa12e8 100644 --- a/app/src/main/res/layout/dialog_update_account_password.xml +++ b/app/src/main/res/layout/dialog_update_account_password.xml @@ -61,7 +61,7 @@ android:textColor="?attr/color_main2_600" android:maxLines="1" android:background="@drawable/edit_text_background" - android:inputType="@{viewModel.showPassword ? InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD : InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD, default=textPassword}" + passwordInputType="@{viewModel.showPassword ? InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD : InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD, default=textPassword}" app:layout_constraintHorizontal_bias="0" app:layout_constraintWidth_max="@dimen/text_input_max_width" app:layout_constraintBottom_toTopOf="@id/cancel" diff --git a/app/src/main/res/layout/dialog_update_account_password_after_register_failure.xml b/app/src/main/res/layout/dialog_update_account_password_after_register_failure.xml index 1a175f0f7..553d7aa8f 100644 --- a/app/src/main/res/layout/dialog_update_account_password_after_register_failure.xml +++ b/app/src/main/res/layout/dialog_update_account_password_after_register_failure.xml @@ -77,7 +77,7 @@ android:textColor="?attr/color_main2_600" android:maxLines="1" android:background="@drawable/edit_text_background" - android:inputType="@{viewModel.showPassword ? InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD : InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD, default=textPassword}" + passwordInputType="@{viewModel.showPassword ? InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD : InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD, default=textPassword}" app:layout_constraintHorizontal_bias="0" app:layout_constraintWidth_max="@dimen/text_input_max_width" app:layout_constraintBottom_toTopOf="@id/cancel" diff --git a/app/src/main/res/layout/settings_contacts_carddav.xml b/app/src/main/res/layout/settings_contacts_carddav.xml index ce80ad71d..42aebb291 100644 --- a/app/src/main/res/layout/settings_contacts_carddav.xml +++ b/app/src/main/res/layout/settings_contacts_carddav.xml @@ -197,7 +197,7 @@ android:paddingEnd="20dp" android:text="@={viewModel.password}" android:hint="@string/password" - android:inputType="@{viewModel.showPassword ? InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD : InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD, default=textPassword}" + passwordInputType="@{viewModel.showPassword ? InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD : InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD, default=textPassword}" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@id/password_title" /> diff --git a/app/src/main/res/layout/settings_contacts_ldap.xml b/app/src/main/res/layout/settings_contacts_ldap.xml index b0825d33a..c50098ed8 100644 --- a/app/src/main/res/layout/settings_contacts_ldap.xml +++ b/app/src/main/res/layout/settings_contacts_ldap.xml @@ -164,7 +164,7 @@ android:paddingEnd="20dp" android:text="@={viewModel.password}" android:hint="@string/password" - android:inputType="@{viewModel.showPassword ? InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD : InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD, default=textPassword}" + passwordInputType="@{viewModel.showPassword ? InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD : InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD, default=textPassword}" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@id/password_title" /> From 7e2a4c124de3cba3fc23d43a8b2b3b22b787a29d Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Wed, 19 Feb 2025 15:23:57 +0100 Subject: [PATCH 55/89] Improved UI of assitant & welcome pages, moving mountains at the bottom like on desktop --- app/src/main/AndroidManifest.xml | 24 +++++------ .../main/res/layout-land/welcome_activity.xml | 25 +++-------- .../assistant_landing_fragment.xml | 4 +- .../assistant_permissions_fragment.xml | 4 +- ...ant_register_confirm_sms_code_fragment.xml | 4 +- .../assistant_register_fragment.xml | 6 +-- ...third_party_sip_account_login_fragment.xml | 6 +-- ...ird_party_sip_account_warning_fragment.xml | 6 +-- .../res/layout-sw600dp/welcome_activity.xml | 4 +- .../res/layout/assistant_landing_fragment.xml | 36 ++++++++-------- .../layout/assistant_permissions_fragment.xml | 41 +++++++++---------- ...ant_register_confirm_sms_code_fragment.xml | 33 ++++++++------- .../layout/assistant_register_fragment.xml | 36 ++++++++-------- ...third_party_sip_account_login_fragment.xml | 36 ++++++++-------- ...ird_party_sip_account_warning_fragment.xml | 24 ++++++++--- app/src/main/res/layout/welcome_activity.xml | 37 ++++++++--------- 16 files changed, 158 insertions(+), 168 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b8543bc29..0d7d79c2e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -122,18 +122,6 @@ - - - - + + + + - - @@ -83,7 +68,7 @@ android:layout_width="0dp" android:layout_height="0dp" android:layout_marginBottom="20dp" - app:layout_constraintTop_toBottomOf="@id/header" + app:layout_constraintTop_toBottomOf="@id/title_second_line" app:layout_constraintBottom_toTopOf="@id/dots_indicator" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"/> diff --git a/app/src/main/res/layout-sw600dp/assistant_landing_fragment.xml b/app/src/main/res/layout-sw600dp/assistant_landing_fragment.xml index ea889e0ee..9021d47a6 100644 --- a/app/src/main/res/layout-sw600dp/assistant_landing_fragment.xml +++ b/app/src/main/res/layout-sw600dp/assistant_landing_fragment.xml @@ -266,10 +266,10 @@ app:layout_constraintStart_toStartOf="@id/title" app:layout_constraintEnd_toEndOf="@id/title" app:layout_constraintTop_toBottomOf="@id/scan_qr_code" - app:layout_constraintBottom_toTopOf="@id/header"/> + app:layout_constraintBottom_toTopOf="@id/mountains"/> + app:layout_constraintBottom_toTopOf="@id/mountains"/> + app:layout_constraintBottom_toTopOf="@id/mountains" /> - - @@ -296,7 +281,20 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@id/no_account_yet" app:layout_constraintTop_toBottomOf="@id/third_party_sip_account" - app:layout_constraintBottom_toBottomOf="parent"/> + app:layout_constraintBottom_toTopOf="@id/mountains"/> + + diff --git a/app/src/main/res/layout/assistant_permissions_fragment.xml b/app/src/main/res/layout/assistant_permissions_fragment.xml index d8af6a7d8..af5d54c6d 100644 --- a/app/src/main/res/layout/assistant_permissions_fragment.xml +++ b/app/src/main/res/layout/assistant_permissions_fragment.xml @@ -49,31 +49,16 @@ app:tint="?attr/color_main2_500" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent"/> - - @@ -89,7 +74,7 @@ android:gravity="center" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintTop_toBottomOf="@id/title" /> + app:layout_constraintTop_toBottomOf="@id/back" /> + + diff --git a/app/src/main/res/layout/assistant_register_confirm_sms_code_fragment.xml b/app/src/main/res/layout/assistant_register_confirm_sms_code_fragment.xml index 629651331..1f50cc150 100644 --- a/app/src/main/res/layout/assistant_register_confirm_sms_code_fragment.xml +++ b/app/src/main/res/layout/assistant_register_confirm_sms_code_fragment.xml @@ -34,29 +34,15 @@ app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent"/> - - @@ -195,6 +181,19 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@id/code_first_digit" /> + + - - @@ -350,7 +335,20 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@id/already_an_account" app:layout_constraintTop_toBottomOf="@id/create_email_account" - app:layout_constraintBottom_toBottomOf="parent"/> + app:layout_constraintBottom_toTopOf="@id/mountains"/> + + diff --git a/app/src/main/res/layout/assistant_third_party_sip_account_login_fragment.xml b/app/src/main/res/layout/assistant_third_party_sip_account_login_fragment.xml index 80b8e9665..0a21b8b29 100644 --- a/app/src/main/res/layout/assistant_third_party_sip_account_login_fragment.xml +++ b/app/src/main/res/layout/assistant_third_party_sip_account_login_fragment.xml @@ -35,30 +35,15 @@ app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent"/> - - @@ -350,10 +335,23 @@ app:layout_constraintWidth_max="@dimen/button_max_width" app:layout_constraintVertical_bias="1" app:layout_constraintTop_toBottomOf="@id/outbound_proxy" - app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintBottom_toTopOf="@id/mountains" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" /> + + diff --git a/app/src/main/res/layout/assistant_third_party_sip_account_warning_fragment.xml b/app/src/main/res/layout/assistant_third_party_sip_account_warning_fragment.xml index e78d95e31..1ea97fe99 100644 --- a/app/src/main/res/layout/assistant_third_party_sip_account_warning_fragment.xml +++ b/app/src/main/res/layout/assistant_third_party_sip_account_warning_fragment.xml @@ -46,10 +46,10 @@ android:id="@+id/title" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginTop="100dp" android:text="@string/assistant_login_third_party_sip_account_title" - android:textColor="?attr/color_main2_600" - app:layout_constraintTop_toTopOf="parent" + android:textColor="?attr/color_text" + app:layout_constraintTop_toTopOf="@id/back" + app:layout_constraintBottom_toBottomOf="@id/back" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"/> @@ -134,6 +134,7 @@ android:text="@string/assistant_third_party_sip_account_create_linphone_account" app:layout_constraintWidth_max="@dimen/button_max_width" app:layout_constraintVertical_bias="1" + app:layout_constraintVertical_chainStyle="packed" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@id/contact" @@ -145,7 +146,6 @@ android:id="@+id/continue_third_party_account_login" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginTop="32dp" android:layout_marginStart="16dp" android:layout_marginEnd="16dp" android:layout_marginBottom="@dimen/screen_bottom_margin" @@ -153,10 +153,24 @@ android:paddingEnd="20dp" android:text="@string/assistant_third_party_sip_account_warning_ok" app:layout_constraintWidth_max="@dimen/button_max_width" - app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintTop_toBottomOf="@id/create_linphone_account" + app:layout_constraintBottom_toTopOf="@id/mountains" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" /> + + diff --git a/app/src/main/res/layout/welcome_activity.xml b/app/src/main/res/layout/welcome_activity.xml index 49a28d17c..1505e3c29 100644 --- a/app/src/main/res/layout/welcome_activity.xml +++ b/app/src/main/res/layout/welcome_activity.xml @@ -40,20 +40,6 @@ app:layout_constraintTop_toTopOf="parent" app:layout_constraintEnd_toEndOf="parent" /> - - @@ -85,7 +71,7 @@ android:layout_width="0dp" android:layout_height="0dp" android:layout_marginBottom="20dp" - app:layout_constraintTop_toBottomOf="@id/header" + app:layout_constraintTop_toBottomOf="@id/title_second_line" app:layout_constraintBottom_toTopOf="@id/dots_indicator" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"/> @@ -121,10 +107,23 @@ android:text="@string/next" app:layout_constraintWidth_max="@dimen/button_max_width" app:layout_constraintTop_toBottomOf="@id/dots_indicator" - app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintBottom_toTopOf="@id/mountains" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" /> + + From 296a324ba3cf9ffb75941e8485ff292e7ff7629c Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Mon, 24 Feb 2025 12:16:05 +0100 Subject: [PATCH 56/89] Prevent call in state IDLE to be added to TelecomManager, wait for IncomingReceived or OutgoingProgress --- .../org/linphone/telecom/TelecomManager.kt | 54 +++++++++++-------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/app/src/main/java/org/linphone/telecom/TelecomManager.kt b/app/src/main/java/org/linphone/telecom/TelecomManager.kt index 994eca22e..4b7923459 100644 --- a/app/src/main/java/org/linphone/telecom/TelecomManager.kt +++ b/app/src/main/java/org/linphone/telecom/TelecomManager.kt @@ -23,6 +23,7 @@ import android.content.Context import android.net.Uri import androidx.annotation.WorkerThread import androidx.core.telecom.CallAttributesCompat +import androidx.core.telecom.CallException import androidx.core.telecom.CallsManager import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -51,8 +52,15 @@ class TelecomManager private val coreListener = object : CoreListenerStub() { @WorkerThread - override fun onCallCreated(core: Core, call: Call) { - onCallCreated(call) + override fun onCallStateChanged( + core: Core, + call: Call, + state: Call.State?, + message: String + ) { + if (state == Call.State.IncomingReceived || state == Call.State.OutgoingProgress) { + onCallCreated(call) + } } @WorkerThread @@ -88,7 +96,7 @@ class TelecomManager @WorkerThread fun onCallCreated(call: Call) { - Log.i("$TAG Call to [${call.remoteAddress.asStringUriOnly()}] created") + Log.i("$TAG Call to [${call.remoteAddress.asStringUriOnly()}] created in state [${call.state}]") val address = call.callLog.remoteAddress val uri = Uri.parse(address.asStringUriOnly()) @@ -99,9 +107,9 @@ class TelecomManager CallAttributesCompat.DIRECTION_INCOMING } - val conferenceInfo = LinphoneUtils.getConferenceInfoIfAny(call) val capabilities = CallAttributesCompat.SUPPORTS_SET_INACTIVE or CallAttributesCompat.SUPPORTS_TRANSFER + val conferenceInfo = LinphoneUtils.getConferenceInfoIfAny(call) val displayName = if (call.conference != null || conferenceInfo != null) { conferenceInfo?.subject ?: call.conference?.subject ?: LinphoneUtils.getDisplayName(address) } else { @@ -109,20 +117,24 @@ class TelecomManager friend?.name ?: LinphoneUtils.getDisplayName(address) } - // When call is created, it is ringing (incoming or outgoing, do not set video) - val type = CallAttributesCompat.Companion.CALL_TYPE_AUDIO_CALL - - val callAttributes = CallAttributesCompat( - displayName, - uri, - direction, - type, - capabilities - ) - Log.i("$TAG Adding call to Telecom's CallsManager with attributes [$callAttributes]") + val isVideo = LinphoneUtils.isVideoEnabled(call) + val type = if (isVideo) { + CallAttributesCompat.Companion.CALL_TYPE_VIDEO_CALL + } else { + CallAttributesCompat.Companion.CALL_TYPE_AUDIO_CALL + } scope.launch { try { + val callAttributes = CallAttributesCompat( + displayName, + uri, + direction, + type, + capabilities + ) + Log.i("$TAG Adding call to Telecom's CallsManager with attributes [$callAttributes]") + callsManager.addCall( callAttributes, { callType -> // onAnswer @@ -160,6 +172,11 @@ class TelecomManager ) { val callbacks = TelecomCallControlCallback(call, this, scope) + // We must first call setCallback on callControlScope before using it + callbacks.onCallControlCallbackSet() + currentlyFollowedCalls += 1 + Log.i("$TAG Call added to Telecom's CallsManager") + coreContext.postOnCoreThread { val callId = call.callLog.callId.orEmpty() if (callId.isNotEmpty()) { @@ -167,13 +184,8 @@ class TelecomManager map[callId] = callbacks } } - - // We must first call setCallback on callControlScope before using it - callbacks.onCallControlCallbackSet() - currentlyFollowedCalls += 1 - Log.i("$TAG Call added to Telecom's CallsManager") } - } catch (e: Exception) { + } catch (e: CallException) { Log.e("$TAG Failed to add call to Telecom's CallsManager: $e") } } From f1b23337e0381042ebedc9b767d62bf046766d98 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Tue, 25 Feb 2025 09:27:17 +0100 Subject: [PATCH 57/89] Prevent connexion in progress when going back to waiting room --- .../ui/main/meetings/viewmodel/MeetingWaitingRoomViewModel.kt | 2 ++ app/src/main/res/layout/meeting_waiting_room_fragment.xml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/linphone/ui/main/meetings/viewmodel/MeetingWaitingRoomViewModel.kt b/app/src/main/java/org/linphone/ui/main/meetings/viewmodel/MeetingWaitingRoomViewModel.kt index 155cbaa04..3a76ef481 100644 --- a/app/src/main/java/org/linphone/ui/main/meetings/viewmodel/MeetingWaitingRoomViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/meetings/viewmodel/MeetingWaitingRoomViewModel.kt @@ -111,10 +111,12 @@ class MeetingWaitingRoomViewModel when (state) { Call.State.End -> { Log.i("$TAG Call has ended, leaving waiting room fragment") + joining.postValue(false) leaveWaitingRoomEvent.postValue(Event(true)) } Call.State.Error -> { Log.w("$TAG Call has failed, leaving waiting room fragment") + joining.postValue(false) leaveWaitingRoomEvent.postValue(Event(true)) } else -> {} diff --git a/app/src/main/res/layout/meeting_waiting_room_fragment.xml b/app/src/main/res/layout/meeting_waiting_room_fragment.xml index a4149de81..f2a3c474a 100644 --- a/app/src/main/res/layout/meeting_waiting_room_fragment.xml +++ b/app/src/main/res/layout/meeting_waiting_room_fragment.xml @@ -196,7 +196,7 @@ android:layout_width="0dp" android:layout_height="0dp" android:background="@color/gray_900" - android:visibility="@{viewModel.joining ? View.VISIBLE : View.GONE}" + android:visibility="@{viewModel.joining ? View.VISIBLE : View.GONE, default=gone}" app:layout_constraintTop_toBottomOf="@id/back" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" From fcd365ad815a571c2a8eb5f1525911af30e81935 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Tue, 25 Feb 2025 11:22:26 +0100 Subject: [PATCH 58/89] Play incoming chat sound file when a message is being received in currently opened conversation --- .../java/org/linphone/core/CorePreferences.kt | 4 +++ .../notifications/NotificationsManager.kt | 30 ++++++++++++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/linphone/core/CorePreferences.kt b/app/src/main/java/org/linphone/core/CorePreferences.kt index 72bd1e4ca..36eccce22 100644 --- a/app/src/main/java/org/linphone/core/CorePreferences.kt +++ b/app/src/main/java/org/linphone/core/CorePreferences.kt @@ -345,6 +345,10 @@ class CorePreferences val ssoCacheFile: String get() = context.filesDir.absolutePath + "/auth_state.json" + @get:AnyThread + val messageReceivedInVisibleConversationNotificationSound: String + get() = context.filesDir.absolutePath + "/share/sounds/linphone/incoming_chat.wav" + @UiThread fun copyAssetsFromPackage() { copy("linphonerc_default", configPath) diff --git a/app/src/main/java/org/linphone/notifications/NotificationsManager.kt b/app/src/main/java/org/linphone/notifications/NotificationsManager.kt index 0cac92344..2d16bacd2 100644 --- a/app/src/main/java/org/linphone/notifications/NotificationsManager.kt +++ b/app/src/main/java/org/linphone/notifications/NotificationsManager.kt @@ -31,6 +31,7 @@ import android.content.pm.PackageManager import android.graphics.Bitmap import android.media.AudioAttributes import android.media.AudioManager +import android.media.MediaPlayer import android.media.RingtoneManager import android.net.Uri import android.os.Bundle @@ -124,6 +125,8 @@ class NotificationsManager private var currentlyDisplayedChatRoomId: String = "" private var currentlyDisplayedIncomingCallFragment: Boolean = false + private val mediaPlayer: MediaPlayer + private val contactsListener = object : ContactsListener { @WorkerThread override fun onContactsLoaded() { } @@ -267,8 +270,9 @@ class NotificationsManager val id = LinphoneUtils.getConversationId(chatRoom) if (currentlyDisplayedChatRoomId.isNotEmpty() && id == currentlyDisplayedChatRoomId) { Log.i( - "$TAG Do not notify received messages for currently displayed conversation [$id]" + "$TAG Do not notify received messages for currently displayed conversation [$id] but play sound" ) + playMessageReceivedSound() return } @@ -442,6 +446,21 @@ class NotificationsManager previousChatNotifications.add(notification.id) } } + + val soundPath = corePreferences.messageReceivedInVisibleConversationNotificationSound + mediaPlayer = MediaPlayer().apply { + setAudioAttributes( + AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) + .setUsage(AudioAttributes.USAGE_NOTIFICATION) + .build() + ) + setDataSource(soundPath) + try { + prepare() + } catch (e: Exception) { + Log.e("$TAG Failed to prepare message received sound file [$soundPath]: $e") + } + } } @AnyThread @@ -1606,6 +1625,15 @@ class NotificationsManager } } + @WorkerThread + private fun playMessageReceivedSound() { + try { + mediaPlayer.start() + } catch (e: Exception) { + Log.e("$TAG Failed to play message received sound file: $e") + } + } + class Notifiable(val notificationId: Int) { var myself: String? = null From 977fb6369350d5fe454d049eca7d4d31223ec0b3 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Tue, 25 Feb 2025 14:06:53 +0100 Subject: [PATCH 59/89] Add domain to third party SIP account auth info --- .../assistant/viewmodel/ThirdPartySipAccountLoginViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/linphone/ui/assistant/viewmodel/ThirdPartySipAccountLoginViewModel.kt b/app/src/main/java/org/linphone/ui/assistant/viewmodel/ThirdPartySipAccountLoginViewModel.kt index 0e193edd8..de69daf2f 100644 --- a/app/src/main/java/org/linphone/ui/assistant/viewmodel/ThirdPartySipAccountLoginViewModel.kt +++ b/app/src/main/java/org/linphone/ui/assistant/viewmodel/ThirdPartySipAccountLoginViewModel.kt @@ -209,7 +209,7 @@ class ThirdPartySipAccountLoginViewModel password.value.orEmpty().trim(), null, null, - null + domainValue ) core.addAuthInfo(newlyCreatedAuthInfo) From a392cf3a6b6b572b3c6237d3f02b72efe54e6c76 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Tue, 25 Feb 2025 19:27:16 +0100 Subject: [PATCH 60/89] Prevent crash first time app is installed due to chat message notification sound not installed yet --- .../notifications/NotificationsManager.kt | 42 ++++++++++--------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/org/linphone/notifications/NotificationsManager.kt b/app/src/main/java/org/linphone/notifications/NotificationsManager.kt index 2d16bacd2..93a0b3299 100644 --- a/app/src/main/java/org/linphone/notifications/NotificationsManager.kt +++ b/app/src/main/java/org/linphone/notifications/NotificationsManager.kt @@ -125,7 +125,7 @@ class NotificationsManager private var currentlyDisplayedChatRoomId: String = "" private var currentlyDisplayedIncomingCallFragment: Boolean = false - private val mediaPlayer: MediaPlayer + private lateinit var mediaPlayer: MediaPlayer private val contactsListener = object : ContactsListener { @WorkerThread @@ -446,21 +446,6 @@ class NotificationsManager previousChatNotifications.add(notification.id) } } - - val soundPath = corePreferences.messageReceivedInVisibleConversationNotificationSound - mediaPlayer = MediaPlayer().apply { - setAudioAttributes( - AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) - .setUsage(AudioAttributes.USAGE_NOTIFICATION) - .build() - ) - setDataSource(soundPath) - try { - prepare() - } catch (e: Exception) { - Log.e("$TAG Failed to prepare message received sound file [$soundPath]: $e") - } - } } @AnyThread @@ -554,6 +539,21 @@ class NotificationsManager core.addListener(coreListener) coreContext.contactsManager.addListener(contactsListener) + + val soundPath = corePreferences.messageReceivedInVisibleConversationNotificationSound + mediaPlayer = MediaPlayer().apply { + try { + setAudioAttributes( + AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) + .setUsage(AudioAttributes.USAGE_NOTIFICATION) + .build() + ) + setDataSource(soundPath) + prepare() + } catch (e: Exception) { + Log.e("$TAG Failed to prepare message received sound file [$soundPath]: $e") + } + } } @WorkerThread @@ -1627,10 +1627,12 @@ class NotificationsManager @WorkerThread private fun playMessageReceivedSound() { - try { - mediaPlayer.start() - } catch (e: Exception) { - Log.e("$TAG Failed to play message received sound file: $e") + if (::mediaPlayer.isInitialized) { + try { + mediaPlayer.start() + } catch (e: Exception) { + Log.e("$TAG Failed to play message received sound file: $e") + } } } From 8eda500daec75c4bb0aff1f1af4d53f1fd05f6ff Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Tue, 25 Feb 2025 20:17:18 +0100 Subject: [PATCH 61/89] Prevent login issue if SIP identity is given instead of username in third party account login --- .../viewmodel/AccountCreationViewModel.kt | 6 ++-- .../viewmodel/AccountLoginViewModel.kt | 2 ++ .../ThirdPartySipAccountLoginViewModel.kt | 28 +++++++++---------- ...third_party_sip_account_login_fragment.xml | 6 ++-- ...third_party_sip_account_login_fragment.xml | 4 +-- app/src/main/res/values-fr/strings.xml | 1 - app/src/main/res/values/strings.xml | 1 - 7 files changed, 24 insertions(+), 24 deletions(-) diff --git a/app/src/main/java/org/linphone/ui/assistant/viewmodel/AccountCreationViewModel.kt b/app/src/main/java/org/linphone/ui/assistant/viewmodel/AccountCreationViewModel.kt index 493470f9f..db9ba778e 100644 --- a/app/src/main/java/org/linphone/ui/assistant/viewmodel/AccountCreationViewModel.kt +++ b/app/src/main/java/org/linphone/ui/assistant/viewmodel/AccountCreationViewModel.kt @@ -442,9 +442,9 @@ class AccountCreationViewModel operationInProgress.postValue(true) createEnabled.postValue(false) - val usernameValue = username.value - val passwordValue = password.value - if (usernameValue.isNullOrEmpty() || passwordValue.isNullOrEmpty()) { + val usernameValue = username.value.orEmpty().trim() + val passwordValue = password.value.orEmpty().trim() + if (usernameValue.isEmpty() || passwordValue.isEmpty()) { Log.e("$TAG Either username [$usernameValue] or password is null or empty!") return } diff --git a/app/src/main/java/org/linphone/ui/assistant/viewmodel/AccountLoginViewModel.kt b/app/src/main/java/org/linphone/ui/assistant/viewmodel/AccountLoginViewModel.kt index 285b0c42f..67b93e15b 100644 --- a/app/src/main/java/org/linphone/ui/assistant/viewmodel/AccountLoginViewModel.kt +++ b/app/src/main/java/org/linphone/ui/assistant/viewmodel/AccountLoginViewModel.kt @@ -172,6 +172,8 @@ open class AccountLoginViewModel "sip:$userInput@$defaultDomain" } } + Log.i("$TAG Computed identity is [$identity] from user input [$userInput]") + val identityAddress = Factory.instance().createAddress(identity) if (identityAddress == null) { Log.e("$TAG Can't parse [$identity] as Address!") diff --git a/app/src/main/java/org/linphone/ui/assistant/viewmodel/ThirdPartySipAccountLoginViewModel.kt b/app/src/main/java/org/linphone/ui/assistant/viewmodel/ThirdPartySipAccountLoginViewModel.kt index de69daf2f..e1676881f 100644 --- a/app/src/main/java/org/linphone/ui/assistant/viewmodel/ThirdPartySipAccountLoginViewModel.kt +++ b/app/src/main/java/org/linphone/ui/assistant/viewmodel/ThirdPartySipAccountLoginViewModel.kt @@ -181,21 +181,21 @@ class ThirdPartySipAccountLoginViewModel // Allow to enter SIP identity instead of simply username // in case identity domain doesn't match proxy domain - val user = username.value.orEmpty().trim() - val userId = authId.value.orEmpty().trim() - val identity = if (user.startsWith("sip:")) { - if (user.contains("@")) { - user - } else { - "$user@$domain" - } - } else { - if (user.contains("@")) { - "sip:$user" - } else { - "sip:$user@$domain" - } + var user = username.value.orEmpty().trim() + if (user.startsWith("sip:")) { + user = user.substring("sip:".length) + } else if (user.startsWith("sips:")) { + user = user.substring("sips:".length) } + if (user.contains("@")) { + user = user.split("@")[0] + } + + val userId = authId.value.orEmpty().trim() + + Log.i("$TAG Parsed username is [$user], user ID [$userId] and domain [$domain]") + + val identity = "sip:$user@$domain" val identityAddress = Factory.instance().createAddress(identity) if (identityAddress == null) { Log.e("$TAG Can't parse [$identity] as Address!") diff --git a/app/src/main/res/layout-sw600dp/assistant_third_party_sip_account_login_fragment.xml b/app/src/main/res/layout-sw600dp/assistant_third_party_sip_account_login_fragment.xml index 43490d289..19598da8d 100644 --- a/app/src/main/res/layout-sw600dp/assistant_third_party_sip_account_login_fragment.xml +++ b/app/src/main/res/layout-sw600dp/assistant_third_party_sip_account_login_fragment.xml @@ -74,11 +74,11 @@ android:layout_marginStart="16dp" android:layout_marginEnd="16dp" android:layout_marginTop="16dp" - android:text="@string/assistant_third_party_sip_account_username_or_identity" + android:text="@{@string/username + `*`}" app:layout_constraintVertical_chainStyle="packed" app:layout_constraintTop_toBottomOf="@id/title" app:layout_constraintBottom_toTopOf="@id/username" - app:layout_constraintStart_toStartOf="@id/title"/> + app:layout_constraintStart_toStartOf="@id/username"/> diff --git a/app/src/main/res/layout/assistant_third_party_sip_account_login_fragment.xml b/app/src/main/res/layout/assistant_third_party_sip_account_login_fragment.xml index 0a21b8b29..224bb3f38 100644 --- a/app/src/main/res/layout/assistant_third_party_sip_account_login_fragment.xml +++ b/app/src/main/res/layout/assistant_third_party_sip_account_login_fragment.xml @@ -55,7 +55,7 @@ android:layout_marginTop="38dp" android:layout_marginStart="16dp" android:layout_marginEnd="16dp" - android:text="@string/assistant_third_party_sip_account_username_or_identity" + android:text="@{@string/username + `*`}" app:layout_constraintTop_toBottomOf="@id/title" app:layout_constraintStart_toStartOf="@id/username"/> @@ -87,7 +87,7 @@ android:layout_marginStart="16dp" android:layout_marginEnd="16dp" android:layout_marginTop="16dp" - android:text="@string/password" + android:text="@{@string/password + `*`}" app:layout_constraintTop_toBottomOf="@id/username" app:layout_constraintStart_toStartOf="@id/password"/> diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 22f3c3670..aee2eb2ef 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -119,7 +119,6 @@ Certaines fonctionnalités telles que les conversations de groupe, les vidéo-conférences, etc… nécessitent un compte &appName;.\n\nCes fonctionnalités seront masquées si vous utilisez un compte SIP tiers.\n\nPour les activer dans un projet commercial, merci de nous contacter. Je préfère créer un compte J\'ai compris - Nom d\'utilisateur ou identité SIP* Notifications push indisponible, la création de compte est donc désactivée. Notification push non reçue, merci de réessayer plus tard Un erreur inattendue est survenue, merci de réessayer plus tard diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 567b02e0a..8fbc07dc6 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -158,7 +158,6 @@ Some features require a &appName; account, such as group messaging, video conferences…\n\nThese features are hidden when you register with a third party SIP account.\n\nTo enable it in a commercial project, please contact us. I prefer to create an account I understand - Username or SIP identity* Push notifications not available, account creation disabled Push notification with auth token not received in 5 seconds, please try again later Unexpected error occurred, please try again later From 01c079440d928471054200427988ca3a21b57622 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Wed, 26 Feb 2025 10:53:28 +0100 Subject: [PATCH 62/89] Do not follow telecom manager audio routing requests if not connected to Android Auto and concerns Speaker or Earpiece --- .../notifications/NotificationsManager.kt | 2 +- .../telecom/TelecomCallControlCallback.kt | 28 ++++++++++++++++--- .../java/org/linphone/utils/LinphoneUtils.kt | 2 +- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/org/linphone/notifications/NotificationsManager.kt b/app/src/main/java/org/linphone/notifications/NotificationsManager.kt index 93a0b3299..8b9fe3126 100644 --- a/app/src/main/java/org/linphone/notifications/NotificationsManager.kt +++ b/app/src/main/java/org/linphone/notifications/NotificationsManager.kt @@ -1124,7 +1124,7 @@ class NotificationsManager } Log.i( - "Creating notification for [${if (isIncoming) "incoming" else "outgoing"}] [${if (isConference) "conference" else "call"}] with video [${if (isVideo) "enabled" else "disabled"}] on channel [$channel]" + "Creating notification for ${if (isIncoming) "[incoming] " else ""}[${if (isConference) "conference" else "call"}] with video [${if (isVideo) "enabled" else "disabled"}] on channel [$channel]" ) val builder = NotificationCompat.Builder( diff --git a/app/src/main/java/org/linphone/telecom/TelecomCallControlCallback.kt b/app/src/main/java/org/linphone/telecom/TelecomCallControlCallback.kt index 1addc4c6b..6d551839d 100644 --- a/app/src/main/java/org/linphone/telecom/TelecomCallControlCallback.kt +++ b/app/src/main/java/org/linphone/telecom/TelecomCallControlCallback.kt @@ -31,6 +31,7 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import org.linphone.LinphoneApplication.Companion.coreContext +import org.linphone.LinphoneApplication.Companion.corePreferences import org.linphone.core.AudioDevice import org.linphone.core.Call import org.linphone.core.CallListenerStub @@ -51,6 +52,7 @@ class TelecomCallControlCallback( private var availableEndpoints: List = arrayListOf() private var currentEndpoint = CallEndpointCompat.TYPE_UNKNOWN + private var endpointUpdateRequestFromLinphone: Boolean = false private val callListener = object : CallListenerStub() { @WorkerThread @@ -65,9 +67,14 @@ class TelecomCallControlCallback( CallAttributesCompat.Companion.CALL_TYPE_AUDIO_CALL } scope.launch { - Log.i("$TAG Answering ${if (isVideo) "video" else "audio"} call") + Log.i("$TAG Answering [${if (isVideo) "video" else "audio"}] call") callControl.answer(type) } + + if (isVideo && corePreferences.routeAudioToSpeakerWhenVideoIsEnabled) { + Log.i("$TAG Answering video call, routing audio to speaker") + AudioUtils.routeAudioToSpeaker(call) + } } else { scope.launch { Log.i("$TAG Setting call active") @@ -156,11 +163,23 @@ class TelecomCallControlCallback( }.launchIn(scope) callControl.currentCallEndpoint.onEach { endpoint -> - Log.i("$TAG We're asked to use [${endpoint.name}] audio endpoint") + val type = endpoint.type + currentEndpoint = type + if (endpointUpdateRequestFromLinphone) { + Log.i("$TAG Linphone requests to use [${endpoint.name}] audio endpoint with type [$type]") + } else { + Log.i("$TAG Android requests us to use [${endpoint.name}] audio endpoint with type [$type]") + } + + if (!endpointUpdateRequestFromLinphone && !coreContext.isConnectedToAndroidAuto && (type == CallEndpointCompat.Companion.TYPE_EARPIECE || type == CallEndpointCompat.Companion.TYPE_SPEAKER)) { + Log.w("$TAG Device isn't connected to Android Auto, do not follow system request to change audio endpoint to either earpiece or speaker") + return@onEach + } + // Change audio route in SDK, this way the usual listener will trigger // and we'll be able to update the UI accordingly val route = arrayListOf() - when (endpoint.type) { + when (type) { CallEndpointCompat.Companion.TYPE_EARPIECE -> { route.add(AudioDevice.Type.Earpiece) } @@ -176,7 +195,6 @@ class TelecomCallControlCallback( } } if (route.isNotEmpty()) { - currentEndpoint = endpoint.type coreContext.postOnCoreThread { if (!AudioUtils.applyAudioRouteChangeInLinphone(call, route)) { Log.w("$TAG Failed to apply audio route change, trying again in 200ms") @@ -186,6 +204,7 @@ class TelecomCallControlCallback( } } } + endpointUpdateRequestFromLinphone = false }.launchIn(scope) callControl.isMuted.onEach { muted -> @@ -215,6 +234,7 @@ class TelecomCallControlCallback( } fun applyAudioRouteToCallWithId(routes: List): Boolean { + endpointUpdateRequestFromLinphone = true Log.i("$TAG Looking for audio endpoint with type [${routes.first()}]") var wiredHeadsetFound = false diff --git a/app/src/main/java/org/linphone/utils/LinphoneUtils.kt b/app/src/main/java/org/linphone/utils/LinphoneUtils.kt index 4a3e15724..fe594e7fd 100644 --- a/app/src/main/java/org/linphone/utils/LinphoneUtils.kt +++ b/app/src/main/java/org/linphone/utils/LinphoneUtils.kt @@ -231,7 +231,7 @@ class LinphoneUtils { val isIncoming = isCallIncoming(call.state) return if (isConference || getConferenceInfoIfAny(call) != null) { true - } else if (isIncoming) { + } else if (isIncoming || call.state == Call.State.Connected) { // In connected state call.currentParams.isVideoEnabled will return false... call.remoteParams?.isVideoEnabled == true && call.remoteParams?.videoDirection != MediaDirection.Inactive } else { call.currentParams.isVideoEnabled && call.currentParams.videoDirection != MediaDirection.Inactive From e05d4cf94a5b9f4954047f4008b71a6e9593bc1e Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Thu, 27 Feb 2025 09:58:54 +0100 Subject: [PATCH 63/89] Prevent crash when sharing file from native gallery due to chatRoom not being initialized yet (but it is not needed) --- .../main/chat/viewmodel/SendMessageInConversationViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/SendMessageInConversationViewModel.kt b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/SendMessageInConversationViewModel.kt index 0c711eba4..ae35f1517 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/SendMessageInConversationViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/SendMessageInConversationViewModel.kt @@ -397,7 +397,7 @@ class SendMessageInConversationViewModel val fileName = FileUtils.getNameFromFilePath(file) val timestamp = System.currentTimeMillis() / 1000 - val model = FileModel(file, fileName, 0, timestamp, false, file, chatRoom.isEphemeralEnabled) { model -> + val model = FileModel(file, fileName, 0, timestamp, false, file, false) { model -> removeAttachment(model.path) } From 4afa2ebc93b0a863a709a3c130ed4f0ac380e1b6 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Thu, 27 Feb 2025 10:21:30 +0100 Subject: [PATCH 64/89] Updated dependencies --- gradle/libs.versions.toml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1b2900970..cb97499b1 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -agp = "8.8.1" +agp = "8.8.2" kotlin = "2.0.21" gmsGoogleServices = "4.4.2" firebaseCrashlytics = "3.0.3" @@ -7,11 +7,11 @@ firebaseBomVersion = "33.9.0" ktlint = "12.1.2" annotations = "1.9.1" -activity = "1.10.0" +activity = "1.10.1" appcompat = "1.7.0" -constraintLayout = "2.2.0" +constraintLayout = "2.2.1" coreKtx = "1.15.0" -splashscreen = "1.2.0-alpha02" +splashscreen = "1.2.0-beta01" telecom = "1.0.0-beta01" media = "1.7.0" recyclerview = "1.4.0" @@ -19,7 +19,7 @@ slidingpanelayout = "1.2.0" window = "1.3.0" gridlayout = "1.0.0" securityCryptoKtx = "1.1.0-alpha06" -navigation = "2.8.7" +navigation = "2.8.8" emoji2 = "1.5.0" car = "1.7.0-rc01" flexbox = "3.0.0" From 990549eb24d5669b003487a18f3bba2cb4fe45eb Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Thu, 27 Feb 2025 14:48:28 +0100 Subject: [PATCH 65/89] Prevent going back to operation in progress dialog after hanging up started group call, also go back to calls history --- .../ui/main/history/fragment/StartCallFragment.kt | 6 +++++- .../ui/main/history/viewmodel/StartCallViewModel.kt | 10 ++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/linphone/ui/main/history/fragment/StartCallFragment.kt b/app/src/main/java/org/linphone/ui/main/history/fragment/StartCallFragment.kt index e5dffd716..c4b7b163c 100644 --- a/app/src/main/java/org/linphone/ui/main/history/fragment/StartCallFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/history/fragment/StartCallFragment.kt @@ -120,7 +120,11 @@ class StartCallFragment : GenericAddressPickerFragment() { viewModel.leaveFragmentEvent.observe(viewLifecycleOwner) { it.consume { - goBack() + // Post on main thread to allow for main activity to be resumed + coreContext.postOnMainThread { + Log.i("$TAG Going back") + goBack() + } } } diff --git a/app/src/main/java/org/linphone/ui/main/history/viewmodel/StartCallViewModel.kt b/app/src/main/java/org/linphone/ui/main/history/viewmodel/StartCallViewModel.kt index 4cce533a7..b19f385ea 100644 --- a/app/src/main/java/org/linphone/ui/main/history/viewmodel/StartCallViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/history/viewmodel/StartCallViewModel.kt @@ -84,8 +84,14 @@ class StartCallViewModel @WorkerThread override fun onStateChanged(conference: Conference, newState: Conference.State?) { Log.i("$TAG Conference state changed [$newState]") - if (newState == Conference.State.Created || newState == Conference.State.CreationFailed) { - operationInProgress.postValue(false) + when (newState) { + Conference.State.Created, Conference.State.CreationFailed, Conference.State.TerminationPending -> { + operationInProgress.postValue(false) + } + Conference.State.Terminated -> { + leaveFragmentEvent.postValue(Event(true)) + } + else -> {} } } } From 12e6de52a8f951e0abf98aef47e633980993b254 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Mon, 3 Mar 2025 10:22:25 +0100 Subject: [PATCH 66/89] Fixed inserting emoji/digit at the current cursor position instead of at the end --- app/src/main/java/org/linphone/utils/DataBindingUtils.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/linphone/utils/DataBindingUtils.kt b/app/src/main/java/org/linphone/utils/DataBindingUtils.kt index f9cff847b..3e79408bd 100644 --- a/app/src/main/java/org/linphone/utils/DataBindingUtils.kt +++ b/app/src/main/java/org/linphone/utils/DataBindingUtils.kt @@ -179,9 +179,9 @@ fun AppCompatEditText.removeCharacterAtPosition() { @UiThread fun AppCompatEditText.addCharacterAtPosition(character: String) { - val newValue = "${text}$character" - setText(newValue) - setSelection(newValue.length) + val start = selectionStart + text?.insert(start, character) + setSelection(start + character.length) } @UiThread From 8b9ceef6da97dfcd16c466492491dd676e5975c7 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Mon, 3 Mar 2025 11:46:12 +0100 Subject: [PATCH 67/89] Updated firebase BoM --- app/src/main/java/org/linphone/LinphoneApplication.kt | 1 + gradle/libs.versions.toml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/linphone/LinphoneApplication.kt b/app/src/main/java/org/linphone/LinphoneApplication.kt index a81a4247a..524600b2c 100644 --- a/app/src/main/java/org/linphone/LinphoneApplication.kt +++ b/app/src/main/java/org/linphone/LinphoneApplication.kt @@ -127,6 +127,7 @@ class LinphoneApplication : Application(), SingletonImageLoader.Factory { .crossfade(false) .components { add(VideoFrameDecoder.Factory()) + // add(GifDecoder.Factory) // Do not add it, GIFs are properly rendered without it and adding it breaks resizing... add(SvgDecoder.Factory()) } .memoryCache { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index cb97499b1..f269ec546 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,7 +3,7 @@ agp = "8.8.2" kotlin = "2.0.21" gmsGoogleServices = "4.4.2" firebaseCrashlytics = "3.0.3" -firebaseBomVersion = "33.9.0" +firebaseBomVersion = "33.10.0" ktlint = "12.1.2" annotations = "1.9.1" From 7d6c50cf296431831a724bab6249378fb8f46ef9 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Tue, 18 Feb 2025 16:11:45 +0100 Subject: [PATCH 68/89] Renamed some layouts --- .../chat/adapter/ConversationEventAdapter.kt | 10 ++++++++-- .../fragment/ConversationDialogFragment.kt | 4 ++-- .../chat/fragment/ConversationFragment.kt | 19 ++++++++++++------- ...EndToEndEncryptionDetailsDialogFragment.kt | 4 ++-- ...versationDisabledDetailsDialogFragment.kt} | 8 ++++---- .../ui/main/chat/model/ConversationModel.kt | 2 ++ .../chat/viewmodel/ConversationViewModel.kt | 5 ++++- ...on_e2e_encrypted_details_bottom_sheet.xml} | 0 ...onversation_e2e_encrypted_first_event.xml} | 0 .../res/layout/chat_conversation_fragment.xml | 2 +- ... => chat_conversation_long_press_menu.xml} | 0 ..._unsafe_disabled_details_bottom_sheet.xml} | 0 app/src/main/res/layout/chat_list_cell.xml | 2 +- 13 files changed, 36 insertions(+), 20 deletions(-) rename app/src/main/java/org/linphone/ui/main/chat/fragment/{UnsafeConversationDetailsDialogFragment.kt => UnsafeConversationDisabledDetailsDialogFragment.kt} (87%) rename app/src/main/res/layout/{chat_conversation_e2e_details_bottom_sheet.xml => chat_conversation_e2e_encrypted_details_bottom_sheet.xml} (100%) rename app/src/main/res/layout/{chat_conversation_secured_first_event.xml => chat_conversation_e2e_encrypted_first_event.xml} (100%) rename app/src/main/res/layout/{chat_long_press_menu.xml => chat_conversation_long_press_menu.xml} (100%) rename app/src/main/res/layout/{chat_conversation_unsafe_details_bottom_sheet.xml => chat_conversation_unsafe_disabled_details_bottom_sheet.xml} (100%) diff --git a/app/src/main/java/org/linphone/ui/main/chat/adapter/ConversationEventAdapter.kt b/app/src/main/java/org/linphone/ui/main/chat/adapter/ConversationEventAdapter.kt index bdf282863..f53551ec7 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/adapter/ConversationEventAdapter.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/adapter/ConversationEventAdapter.kt @@ -35,7 +35,7 @@ import org.linphone.core.tools.Log import org.linphone.databinding.ChatBubbleIncomingBinding import org.linphone.databinding.ChatBubbleOutgoingBinding import org.linphone.databinding.ChatConversationEventBinding -import org.linphone.databinding.ChatConversationSecuredFirstEventBinding +import org.linphone.databinding.ChatConversationE2eEncryptedFirstEventBinding import org.linphone.ui.main.chat.model.EventLogModel import org.linphone.ui.main.chat.model.EventModel import org.linphone.ui.main.chat.model.MessageModel @@ -70,13 +70,19 @@ class ConversationEventAdapter : MutableLiveData>() } + private var isConversationSecured: Boolean = false + + fun setIsConversationSecured(secured: Boolean) { + isConversationSecured = secured + } + override fun displayHeaderForPosition(position: Int): Boolean { // We only want to display it at top return position == 0 } override fun getHeaderViewForPosition(context: Context, position: Int): View { - val binding = ChatConversationSecuredFirstEventBinding.inflate(LayoutInflater.from(context)) + val binding = ChatConversationE2eEncryptedFirstEventBinding.inflate(LayoutInflater.from(context)) return binding.root } diff --git a/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationDialogFragment.kt b/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationDialogFragment.kt index 1daf8b3fd..b56cb83cf 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationDialogFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationDialogFragment.kt @@ -29,7 +29,7 @@ import androidx.annotation.UiThread import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialogFragment -import org.linphone.databinding.ChatLongPressMenuBinding +import org.linphone.databinding.ChatConversationLongPressMenuBinding @UiThread class ConversationDialogFragment( @@ -71,7 +71,7 @@ class ConversationDialogFragment( container: ViewGroup?, savedInstanceState: Bundle? ): View { - val view = ChatLongPressMenuBinding.inflate(layoutInflater) + val view = ChatConversationLongPressMenuBinding.inflate(layoutInflater) view.isMuted = isMuted view.isGroup = isGroup view.isReadOnly = isReadOnly 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 index dd44bcd17..1a3eaf118 100644 --- 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 @@ -314,7 +314,9 @@ open class ConversationFragment : SlidingPaneChildFragment() { if (e.action == MotionEvent.ACTION_UP) { if ((rv.layoutManager as LinearLayoutManager).findFirstCompletelyVisibleItemPosition() == 0) { if (e.y >= 0 && e.y <= headerItemDecoration.getDecorationHeight(0)) { - showEndToEndEncryptionDetailsBottomSheet() + if (viewModel.isEndToEndEncrypted.value == true) { + showEndToEndEncryptionDetailsBottomSheet() + } return true } } @@ -504,6 +506,7 @@ open class ConversationFragment : SlidingPaneChildFragment() { } } else { sendMessageViewModel.configureChatRoom(viewModel.chatRoom) + adapter.setIsConversationSecured(viewModel.isEndToEndEncrypted.value == true) // Wait for chat room to be ready before trying to forward a message in it sharedViewModel.messageToForwardEvent.observe(viewLifecycleOwner) { event -> @@ -585,6 +588,8 @@ open class ConversationFragment : SlidingPaneChildFragment() { } viewModel.isEndToEndEncrypted.observe(viewLifecycleOwner) { encrypted -> + adapter.setIsConversationSecured(encrypted) + if (encrypted) { binding.eventsList.addItemDecoration(headerItemDecoration) binding.eventsList.addOnItemTouchListener(listItemTouchListener) @@ -700,7 +705,7 @@ open class ConversationFragment : SlidingPaneChildFragment() { } binding.setWarningConversationDisabledClickListener { - showUnsafeConversationDetailsBottomSheet() + showUnsafeConversationDisabledDetailsBottomSheet() } binding.searchField.setOnEditorActionListener { view, actionId, _ -> @@ -1415,13 +1420,13 @@ open class ConversationFragment : SlidingPaneChildFragment() { bottomSheetDialog = e2eEncryptionDetailsBottomSheet } - private fun showUnsafeConversationDetailsBottomSheet() { - val unsafeConversationDetailsBottomSheet = UnsafeConversationDetailsDialogFragment() - unsafeConversationDetailsBottomSheet.show( + private fun showUnsafeConversationDisabledDetailsBottomSheet() { + val unsafeConversationDisabledDetailsBottomSheet = UnsafeConversationDisabledDetailsDialogFragment() + unsafeConversationDisabledDetailsBottomSheet.show( requireActivity().supportFragmentManager, - UnsafeConversationDetailsDialogFragment.TAG + UnsafeConversationDisabledDetailsDialogFragment.TAG ) - bottomSheetDialog = unsafeConversationDetailsBottomSheet + bottomSheetDialog = unsafeConversationDisabledDetailsBottomSheet } private fun showOpenOrExportFileDialog(path: String, mime: String, bundle: Bundle) { diff --git a/app/src/main/java/org/linphone/ui/main/chat/fragment/EndToEndEncryptionDetailsDialogFragment.kt b/app/src/main/java/org/linphone/ui/main/chat/fragment/EndToEndEncryptionDetailsDialogFragment.kt index 482491b0d..cc40b959f 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/fragment/EndToEndEncryptionDetailsDialogFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/fragment/EndToEndEncryptionDetailsDialogFragment.kt @@ -29,7 +29,7 @@ import androidx.annotation.UiThread import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialogFragment -import org.linphone.databinding.ChatConversationE2eDetailsBottomSheetBinding +import org.linphone.databinding.ChatConversationE2eEncryptedDetailsBottomSheetBinding @UiThread class EndToEndEncryptionDetailsDialogFragment( @@ -62,7 +62,7 @@ class EndToEndEncryptionDetailsDialogFragment( container: ViewGroup?, savedInstanceState: Bundle? ): View { - val view = ChatConversationE2eDetailsBottomSheetBinding.inflate(layoutInflater) + val view = ChatConversationE2eEncryptedDetailsBottomSheetBinding.inflate(layoutInflater) return view.root } } diff --git a/app/src/main/java/org/linphone/ui/main/chat/fragment/UnsafeConversationDetailsDialogFragment.kt b/app/src/main/java/org/linphone/ui/main/chat/fragment/UnsafeConversationDisabledDetailsDialogFragment.kt similarity index 87% rename from app/src/main/java/org/linphone/ui/main/chat/fragment/UnsafeConversationDetailsDialogFragment.kt rename to app/src/main/java/org/linphone/ui/main/chat/fragment/UnsafeConversationDisabledDetailsDialogFragment.kt index 597120f99..681cea5af 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/fragment/UnsafeConversationDetailsDialogFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/fragment/UnsafeConversationDisabledDetailsDialogFragment.kt @@ -29,14 +29,14 @@ import androidx.annotation.UiThread import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialogFragment -import org.linphone.databinding.ChatConversationUnsafeDetailsBottomSheetBinding +import org.linphone.databinding.ChatConversationUnsafeDisabledDetailsBottomSheetBinding @UiThread -class UnsafeConversationDetailsDialogFragment( +class UnsafeConversationDisabledDetailsDialogFragment( private val onDismiss: (() -> Unit)? = null ) : BottomSheetDialogFragment() { companion object { - const val TAG = "UnsafeConversationDetailsDialogFragment" + const val TAG = "UnsafeConversationDisabledDetailsDialogFragment" } override fun onCancel(dialog: DialogInterface) { @@ -62,7 +62,7 @@ class UnsafeConversationDetailsDialogFragment( container: ViewGroup?, savedInstanceState: Bundle? ): View { - val view = ChatConversationUnsafeDetailsBottomSheetBinding.inflate(layoutInflater) + val view = ChatConversationUnsafeDisabledDetailsBottomSheetBinding.inflate(layoutInflater) return view.root } } 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 45363fd9a..514ec947c 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 @@ -60,6 +60,8 @@ class ConversationModel val isEncrypted = chatRoom.hasCapability(Capabilities.Encrypted.toInt()) + val isEncryptionAvailable = LinphoneUtils.isEndToEndEncryptedChatAvailable(chatRoom.core) + val isReadOnly = MutableLiveData() val subject = 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 index 6ddd58e44..789f93f21 100644 --- 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 @@ -72,6 +72,8 @@ class ConversationViewModel val isEndToEndEncrypted = MutableLiveData() + val isEndToEndEncryptionAvailable = MutableLiveData() + val isGroup = MutableLiveData() val subject = MutableLiveData() @@ -313,7 +315,8 @@ class ConversationViewModel } init { - coreContext.postOnCoreThread { + coreContext.postOnCoreThread { core -> + isEndToEndEncryptionAvailable.postValue(LinphoneUtils.isEndToEndEncryptedChatAvailable(core)) coreContext.contactsManager.addListener(contactsListener) } diff --git a/app/src/main/res/layout/chat_conversation_e2e_details_bottom_sheet.xml b/app/src/main/res/layout/chat_conversation_e2e_encrypted_details_bottom_sheet.xml similarity index 100% rename from app/src/main/res/layout/chat_conversation_e2e_details_bottom_sheet.xml rename to app/src/main/res/layout/chat_conversation_e2e_encrypted_details_bottom_sheet.xml diff --git a/app/src/main/res/layout/chat_conversation_secured_first_event.xml b/app/src/main/res/layout/chat_conversation_e2e_encrypted_first_event.xml similarity index 100% rename from app/src/main/res/layout/chat_conversation_secured_first_event.xml rename to app/src/main/res/layout/chat_conversation_e2e_encrypted_first_event.xml diff --git a/app/src/main/res/layout/chat_conversation_fragment.xml b/app/src/main/res/layout/chat_conversation_fragment.xml index e55c41f38..48a70bf0a 100644 --- a/app/src/main/res/layout/chat_conversation_fragment.xml +++ b/app/src/main/res/layout/chat_conversation_fragment.xml @@ -289,7 +289,7 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:visibility="@{viewModel.isEmpty && viewModel.isEndToEndEncrypted ? View.VISIBLE : View.GONE}" - layout="@layout/chat_conversation_secured_first_event" + layout="@layout/chat_conversation_e2e_encrypted_first_event" app:layout_constraintTop_toTopOf="@id/events_list" app:layout_constraintStart_toStartOf="@id/events_list" app:layout_constraintEnd_toEndOf="@id/events_list" /> diff --git a/app/src/main/res/layout/chat_long_press_menu.xml b/app/src/main/res/layout/chat_conversation_long_press_menu.xml similarity index 100% rename from app/src/main/res/layout/chat_long_press_menu.xml rename to app/src/main/res/layout/chat_conversation_long_press_menu.xml diff --git a/app/src/main/res/layout/chat_conversation_unsafe_details_bottom_sheet.xml b/app/src/main/res/layout/chat_conversation_unsafe_disabled_details_bottom_sheet.xml similarity index 100% rename from app/src/main/res/layout/chat_conversation_unsafe_details_bottom_sheet.xml rename to app/src/main/res/layout/chat_conversation_unsafe_disabled_details_bottom_sheet.xml diff --git a/app/src/main/res/layout/chat_list_cell.xml b/app/src/main/res/layout/chat_list_cell.xml index 74dbd8502..77d4e4c90 100644 --- a/app/src/main/res/layout/chat_list_cell.xml +++ b/app/src/main/res/layout/chat_list_cell.xml @@ -206,7 +206,7 @@ android:layout_marginEnd="10dp" android:src="@drawable/lock_simple_open_bold" android:contentDescription="@string/content_description_chat_unsecured" - android:visibility="@{model.isEncrypted ? View.GONE : View.VISIBLE}" + android:visibility="@{model.isEncrypted || !model.isEncryptionAvailable ? View.GONE : View.VISIBLE}" app:layout_constraintTop_toBottomOf="@id/date_time" app:layout_constraintBottom_toTopOf="@id/separator" app:layout_constraintEnd_toStartOf="@id/ephemeral" From fb323a4606da3316dd218c6fad71b9ae15f185cb Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Mon, 3 Mar 2025 14:39:40 +0100 Subject: [PATCH 69/89] Do not play sound for currently displayed conversation if message is outgoing or read --- .../linphone/notifications/NotificationsManager.kt | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/linphone/notifications/NotificationsManager.kt b/app/src/main/java/org/linphone/notifications/NotificationsManager.kt index 8b9fe3126..3432bf952 100644 --- a/app/src/main/java/org/linphone/notifications/NotificationsManager.kt +++ b/app/src/main/java/org/linphone/notifications/NotificationsManager.kt @@ -270,9 +270,19 @@ class NotificationsManager val id = LinphoneUtils.getConversationId(chatRoom) if (currentlyDisplayedChatRoomId.isNotEmpty() && id == currentlyDisplayedChatRoomId) { Log.i( - "$TAG Do not notify received messages for currently displayed conversation [$id] but play sound" + "$TAG Do not notify received messages for currently displayed conversation [$id], but play sound if at least one message is incoming and not read" ) - playMessageReceivedSound() + + var playSound = false + for (message in messages) { + if (!message.isOutgoing && !message.isRead) { + playSound = true + break + } + } + if (playSound) { + playMessageReceivedSound() + } return } From 9ffe3b4d7fe9e8ab910c034abb72c69181da52b9 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Mon, 3 Mar 2025 14:53:40 +0100 Subject: [PATCH 70/89] Hide incoming messages delivery status in groups if IMDN threshold is set to 1 --- .../main/java/org/linphone/ui/main/chat/model/MessageModel.kt | 2 ++ app/src/main/res/layout/chat_bubble_incoming.xml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/linphone/ui/main/chat/model/MessageModel.kt b/app/src/main/java/org/linphone/ui/main/chat/model/MessageModel.kt index d7027e537..6f7021f0f 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/model/MessageModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/model/MessageModel.kt @@ -106,6 +106,8 @@ class MessageModel val time = TimestampUtils.toString(timestamp) + val hideDeliveryStatus = !isOutgoing && coreContext.core.imdnToEverybodyThreshold == 1 + val chatRoomIsReadOnly = chatMessage.chatRoom.isReadOnly || ( !chatMessage.chatRoom.hasCapability(ChatRoom.Capabilities.Encrypted.toInt()) && LinphoneUtils.getAccountForAddress( diff --git a/app/src/main/res/layout/chat_bubble_incoming.xml b/app/src/main/res/layout/chat_bubble_incoming.xml index c3ecabbd7..dfb663728 100644 --- a/app/src/main/res/layout/chat_bubble_incoming.xml +++ b/app/src/main/res/layout/chat_bubble_incoming.xml @@ -252,7 +252,7 @@ android:paddingTop="2dp" android:contentDescription="@string/content_description_chat_bubble_delivery_status" android:src="@{model.statusIcon, default=@drawable/checks}" - android:visibility="@{model.isFromGroup ? View.VISIBLE : View.GONE}" + android:visibility="@{model.isFromGroup && !model.hideDeliveryStatus ? View.VISIBLE : View.GONE}" app:tint="?attr/color_main1_500" /> Date: Tue, 4 Mar 2025 09:01:27 +0100 Subject: [PATCH 71/89] Show green toast when VFS is successfully enabled --- .../linphone/ui/main/settings/fragment/SettingsFragment.kt | 1 - .../linphone/ui/main/settings/viewmodel/SettingsViewModel.kt | 5 +++-- app/src/main/res/values-fr/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/linphone/ui/main/settings/fragment/SettingsFragment.kt b/app/src/main/java/org/linphone/ui/main/settings/fragment/SettingsFragment.kt index 8dd7e95df..1f58890f7 100644 --- a/app/src/main/java/org/linphone/ui/main/settings/fragment/SettingsFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/settings/fragment/SettingsFragment.kt @@ -307,7 +307,6 @@ class SettingsFragment : GenericMainFragment() { viewModel.enableVfs() dialog.dismiss() - findNavController().popBackStack() } } diff --git a/app/src/main/java/org/linphone/ui/main/settings/viewmodel/SettingsViewModel.kt b/app/src/main/java/org/linphone/ui/main/settings/viewmodel/SettingsViewModel.kt index eaef19b0c..62a36d08b 100644 --- a/app/src/main/java/org/linphone/ui/main/settings/viewmodel/SettingsViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/settings/viewmodel/SettingsViewModel.kt @@ -335,11 +335,12 @@ class SettingsViewModel isVfsEnabled.postValue(enabled) if (enabled) { Log.i("$TAG VFS has been enabled") + showGreenToast(R.string.settings_security_enable_vfs_success_toast, R.drawable.lock_key) } } else { - showRedToast(R.string.settings_security_enable_vfs_failure_toast, R.drawable.warning_circle) - isVfsEnabled.postValue(false) Log.e("$TAG Failed to enable VFS!") + isVfsEnabled.postValue(false) + showRedToast(R.string.settings_security_enable_vfs_failure_toast, R.drawable.warning_circle) } } diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index aee2eb2ef..3b94ac6e0 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -182,6 +182,7 @@ Chiffrer tous les fichiers Attention, vous ne pourrez pas revenir en arrière ! Échec à l\'activation du module d\'encryption + Module d\'encryption activé Confirmer l\'activation du chiffrement Une fois la fonctionnalité activée, toutes les données de l\'application seront chiffrées et accessibles uniquement via celle-ci.\n\nCe changement est irréversible. Empêcher l\'interface d\'être enregistrée diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8fbc07dc6..f5a9d8c22 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -221,6 +221,7 @@ Encrypt everything Warning: once enabled it can\'t be disabled! Failed to enable encryption module! + Encryption module enabled Do you really want to encrypt everything? Be careful, it can\'t be undone! Prevent interface from being recorded From 354f39d76d4e5efa7d11891eca8ebc44a8416afb Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Tue, 4 Mar 2025 09:45:43 +0100 Subject: [PATCH 72/89] Fixed media list fragment when VFS is enabled, the first time it was opened it wasn't possible to scroll to other files --- .../ui/fileviewer/viewmodel/MediaListViewModel.kt | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/linphone/ui/fileviewer/viewmodel/MediaListViewModel.kt b/app/src/main/java/org/linphone/ui/fileviewer/viewmodel/MediaListViewModel.kt index 9562cdd3a..49a56714e 100644 --- a/app/src/main/java/org/linphone/ui/fileviewer/viewmodel/MediaListViewModel.kt +++ b/app/src/main/java/org/linphone/ui/fileviewer/viewmodel/MediaListViewModel.kt @@ -90,10 +90,13 @@ class MediaListViewModel Log.d( "$TAG [VFS] Content is encrypted, requesting plain file path for file [${mediaContent.filePath}]" ) - mediaContent.exportPlainFile() + val exportedPath = mediaContent.exportPlainFile() + Log.i("$TAG Media original path is [$originalPath], newly exported plain file path is [$exportedPath]") + exportedPath } else { originalPath } + val name = mediaContent.name.orEmpty() val size = mediaContent.size.toLong() val timestamp = mediaContent.creationTimestamp @@ -103,10 +106,12 @@ class MediaListViewModel val model = FileModel(path, name, size, timestamp, isEncrypted, originalPath, ephemeral) list.add(model) + } else { + Log.w("$TAG Skipping content because either name [$name] or path [$path] is empty") } if (tempFilePath.isNotEmpty() && !tempFileModelFound) { - if (path == tempFilePath) { + if (path == tempFilePath || (isEncrypted && originalPath == temporaryModel.originalPath)) { tempFileModelFound = true } } From 30364c48b0e3c57275f27b971d02dd3c27b03dd3 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Wed, 26 Feb 2025 11:27:57 +0100 Subject: [PATCH 73/89] Clear auth info password requested dialog when account is removed --- app/src/main/java/org/linphone/core/CoreContext.kt | 14 ++++++++++++++ .../main/java/org/linphone/ui/main/MainActivity.kt | 6 ++++++ 2 files changed, 20 insertions(+) diff --git a/app/src/main/java/org/linphone/core/CoreContext.kt b/app/src/main/java/org/linphone/core/CoreContext.kt index 9ff99de9e..d84a1fc38 100644 --- a/app/src/main/java/org/linphone/core/CoreContext.kt +++ b/app/src/main/java/org/linphone/core/CoreContext.kt @@ -93,6 +93,10 @@ class CoreContext MutableLiveData>() } + val clearAuthenticationRequestDialogEvent: MutableLiveData> by lazy { + MutableLiveData>() + } + val refreshMicrophoneMuteStateEvent: MutableLiveData> by lazy { MutableLiveData>() } @@ -391,6 +395,7 @@ class CoreContext } } + @WorkerThread override fun onAccountAdded(core: Core, account: Account) { // Prevent this trigger when core is stopped/start in remote prov if (core.globalState == GlobalState.Off) return @@ -412,6 +417,15 @@ class CoreContext } } } + + @WorkerThread + override fun onAccountRemoved(core: Core, account: Account) { + Log.i("$TAG Account [${account.params.identityAddress?.asStringUriOnly()}] removed, clearing auth request dialog if needed") + if (account.findAuthInfo() == digestAuthInfoPendingPasswordUpdate) { + Log.i("$TAG Removed account matches auth info pending password update, removing dialog") + clearAuthenticationRequestDialogEvent.postValue(Event(true)) + } + } } private var logcatEnabled: Boolean = corePreferences.printLogsInLogcat diff --git a/app/src/main/java/org/linphone/ui/main/MainActivity.kt b/app/src/main/java/org/linphone/ui/main/MainActivity.kt index 21e33b655..99f96af86 100644 --- a/app/src/main/java/org/linphone/ui/main/MainActivity.kt +++ b/app/src/main/java/org/linphone/ui/main/MainActivity.kt @@ -284,6 +284,12 @@ class MainActivity : GenericActivity() { } } + coreContext.clearAuthenticationRequestDialogEvent.observe(this) { + it.consume { + currentlyDisplayedAuthDialog?.dismiss() + } + } + coreContext.showGreenToastEvent.observe(this) { it.consume { pair -> val message = getString(pair.first) From 4e852601fca21494afdad16738603ef780b68262 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Tue, 4 Mar 2025 16:48:25 +0100 Subject: [PATCH 74/89] Use new core.createChatRoom() that replaces the older version (API change was introduced in SDK release/5.4 branch commit ID dd4b83553fc72ca5449adb6ce72d9882f90ae320) --- .../ui/call/viewmodel/CurrentCallViewModel.kt | 5 +++-- .../ConversationForwardMessageViewModel.kt | 4 +++- .../chat/viewmodel/StartConversationViewModel.kt | 13 ++++++------- .../ui/main/contacts/viewmodel/ContactViewModel.kt | 4 +++- .../ui/main/history/viewmodel/HistoryViewModel.kt | 4 +++- 5 files changed, 18 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/org/linphone/ui/call/viewmodel/CurrentCallViewModel.kt b/app/src/main/java/org/linphone/ui/call/viewmodel/CurrentCallViewModel.kt index 483bf1303..9428233ad 100644 --- a/app/src/main/java/org/linphone/ui/call/viewmodel/CurrentCallViewModel.kt +++ b/app/src/main/java/org/linphone/ui/call/viewmodel/CurrentCallViewModel.kt @@ -1351,14 +1351,13 @@ class CurrentCallViewModel @WorkerThread private fun createCurrentCallConversation(call: Call) { - val localAddress = call.callLog.localAddress val remoteAddress = call.remoteAddress val participants = arrayOf(remoteAddress) val core = call.core operationInProgress.postValue(true) val params = getChatRoomParams(call) ?: return // TODO: show error to user - val chatRoom = core.createChatRoom(params, localAddress, participants) + val chatRoom = core.createChatRoom(params, participants) if (chatRoom != null) { if (params.chatParams?.backend == ChatRoom.Backend.FlexisipChat) { if (chatRoom.state == ChatRoom.State.Created) { @@ -1398,6 +1397,8 @@ class CurrentCallViewModel params.isChatEnabled = true params.isGroupEnabled = false params.subject = AppUtils.getString(R.string.conversation_one_to_one_hidden_subject) + params.account = account + val chatParams = params.chatParams ?: return null chatParams.ephemeralLifetime = 0 // Make sure ephemeral is disabled by default diff --git a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationForwardMessageViewModel.kt b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationForwardMessageViewModel.kt index db0f32397..569359e30 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationForwardMessageViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationForwardMessageViewModel.kt @@ -174,6 +174,8 @@ class ConversationForwardMessageViewModel params.isChatEnabled = true params.isGroupEnabled = false params.subject = AppUtils.getString(R.string.conversation_one_to_one_hidden_subject) + params.account = account + val chatParams = params.chatParams ?: return chatParams.ephemeralLifetime = 0 // Make sure ephemeral is disabled by default @@ -212,7 +214,7 @@ class ConversationForwardMessageViewModel Log.i( "$TAG No existing 1-1 conversation between local account [${localAddress?.asStringUriOnly()}] and remote [${remote.asStringUriOnly()}] was found for given parameters, let's create it" ) - val chatRoom = core.createChatRoom(params, localAddress, participants) + val chatRoom = core.createChatRoom(params, participants) if (chatRoom != null) { if (chatParams.backend == ChatRoom.Backend.FlexisipChat) { if (chatRoom.state == ChatRoom.State.Created) { diff --git a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/StartConversationViewModel.kt b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/StartConversationViewModel.kt index 057f6566d..3e7c26156 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/StartConversationViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/StartConversationViewModel.kt @@ -112,6 +112,8 @@ class StartConversationViewModel params.isGroupEnabled = true params.subject = groupChatRoomSubject params.securityLevel = Conference.SecurityLevel.EndToEnd + params.account = account + val chatParams = params.chatParams ?: return@postOnCoreThread chatParams.ephemeralLifetime = 0 // Make sure ephemeral is disabled by default chatParams.backend = ChatRoom.Backend.FlexisipChat @@ -120,14 +122,9 @@ class StartConversationViewModel for (participant in selection.value.orEmpty()) { participants.add(participant.address) } - val localAddress = account.params.identityAddress val participantsArray = arrayOf
() - val chatRoom = core.createChatRoom( - params, - localAddress, - participants.toArray(participantsArray) - ) + val chatRoom = core.createChatRoom(params, participants.toArray(participantsArray)) if (chatRoom != null) { if (chatParams.backend == ChatRoom.Backend.FlexisipChat) { if (chatRoom.state == ChatRoom.State.Created) { @@ -176,6 +173,8 @@ class StartConversationViewModel params.isChatEnabled = true params.isGroupEnabled = false params.subject = AppUtils.getString(R.string.conversation_one_to_one_hidden_subject) + params.account = account + val chatParams = params.chatParams ?: return chatParams.ephemeralLifetime = 0 // Make sure ephemeral is disabled by default @@ -216,7 +215,7 @@ class StartConversationViewModel Log.i( "$TAG No existing 1-1 conversation between local account [${localAddress?.asStringUriOnly()}] and remote [${remote.asStringUriOnly()}] was found for given parameters, let's create it" ) - val chatRoom = core.createChatRoom(params, localAddress, participants) + val chatRoom = core.createChatRoom(params, participants) if (chatRoom != null) { if (chatParams.backend == ChatRoom.Backend.FlexisipChat) { val state = chatRoom.state diff --git a/app/src/main/java/org/linphone/ui/main/contacts/viewmodel/ContactViewModel.kt b/app/src/main/java/org/linphone/ui/main/contacts/viewmodel/ContactViewModel.kt index e78fa9e43..7cbb7090e 100644 --- a/app/src/main/java/org/linphone/ui/main/contacts/viewmodel/ContactViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/contacts/viewmodel/ContactViewModel.kt @@ -499,6 +499,8 @@ class ContactViewModel params.isChatEnabled = true params.isGroupEnabled = false params.subject = AppUtils.getString(R.string.conversation_one_to_one_hidden_subject) + params.account = account + val chatParams = params.chatParams ?: return chatParams.ephemeralLifetime = 0 // Make sure ephemeral is disabled by default @@ -546,7 +548,7 @@ class ContactViewModel "$TAG No existing conversation between [$localSipUri] and [$remoteSipUri] was found, let's create it" ) operationInProgress.postValue(true) - val chatRoom = core.createChatRoom(params, localAddress, participants) + val chatRoom = core.createChatRoom(params, participants) if (chatRoom != null) { if (chatParams.backend == ChatRoom.Backend.FlexisipChat) { if (chatRoom.state == ChatRoom.State.Created) { diff --git a/app/src/main/java/org/linphone/ui/main/history/viewmodel/HistoryViewModel.kt b/app/src/main/java/org/linphone/ui/main/history/viewmodel/HistoryViewModel.kt index 5d12485df..9f51fd231 100644 --- a/app/src/main/java/org/linphone/ui/main/history/viewmodel/HistoryViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/history/viewmodel/HistoryViewModel.kt @@ -228,6 +228,8 @@ class HistoryViewModel params.isChatEnabled = true params.isGroupEnabled = false params.subject = AppUtils.getString(R.string.conversation_one_to_one_hidden_subject) + params.account = account + val chatParams = params.chatParams ?: return@postOnCoreThread chatParams.ephemeralLifetime = 0 // Make sure ephemeral is disabled by default @@ -275,7 +277,7 @@ class HistoryViewModel "$TAG No existing conversation between [$localSipUri] and [$remoteSipUri] was found, let's create it" ) operationInProgress.postValue(true) - val chatRoom = core.createChatRoom(params, localAddress, participants) + val chatRoom = core.createChatRoom(params, participants) if (chatRoom != null) { if (chatParams.backend == ChatRoom.Backend.FlexisipChat) { if (chatRoom.state == ChatRoom.State.Created) { From 4cca59a39fdd0daf1352e5f89b8eb86602e4bf6b Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Tue, 4 Mar 2025 12:27:48 +0100 Subject: [PATCH 75/89] Use newly added APIs to get real information about a content being related to an ephemeral chat message or not, and use that information to hide save/export buttons & prevent screenshots --- CHANGELOG.md | 4 +++- README.md | 2 ++ .../java/org/linphone/ui/GenericActivity.kt | 2 +- .../ui/call/fragment/TransferCallFragment.kt | 18 --------------- .../ui/fileviewer/FileViewerActivity.kt | 8 +++++++ .../ui/fileviewer/MediaViewerActivity.kt | 23 +++++++++++++------ .../ui/fileviewer/viewmodel/FileViewModel.kt | 2 ++ .../viewmodel/MediaListViewModel.kt | 15 ++++++++++-- .../ConversationDocumentsListViewModel.kt | 13 +++++++++-- .../res/layout/file_media_viewer_activity.xml | 4 +++- .../main/res/layout/file_viewer_activity.xml | 4 +++- 11 files changed, 62 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 286c7d889..7b51c146a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ Group changes to describe their impact on the project, as follows: Security to invite users to upgrade in case of vulnerabilities. -## [6.0.0] - 2025-02-?? +## [6.0.0] - 2025-03-?? 6.0.0 release is a complete rework of Linphone Android, with a fully redesigned UI, so it is impossible to list everything here. @@ -21,6 +21,7 @@ Group changes to describe their impact on the project, as follows: - Improved multi account: you'll only see history, conversations, meetings etc... related to currently selected account, and you can switch the default account in two clicks. - Call transfer: Blind & Attended call transfer have been merged into one: during a call, if you initiate a transfer action, either pick another call to do the attended transfer or select a contact from the list (you can input a SIP URI not already in the suggestions list) to start a blind transfer. - User can only send up to 12 files in a single chat message. +- IMDNs are now only sent to the message sender, preventing huge traffic in large groups, and thus the delivery status icon for received messages is now hidden in groups (as it was in 1-1 conversations). - Settings: a lot of them are gone, the one that are still there have been reworked to increase user friendliness. - Default screen (between contacts, call history, conversations & meetings list) will change depending on where you were when the app was paused or killed, and you will return to that last visited screen on the next startup. - Gradle files have been migrated from Groovy to Kotlin DSL, and dependencies are now in a separated file (libs.versions.toml). @@ -38,6 +39,7 @@ Group changes to describe their impact on the project, as follows: - Chat while in call: a shortcut to a conversation screen with the remote. - Chat while in a conference: if the conference has a text stream enabled, you can chat with the other participants of the conference while it lasts. At the end, you'll find the messages history in the call history (and not in the list of conversations). - Auto export of media to native gallery even when auto download is enabled (but still not if VFS is enabled nor for ephemeral messages). +- Save / export document & media from ephemeral messages will be disabled, and secure policy that prevents screenshots will be enforced in file viewer even if the setting is disabled. - Notification showing upload/download of files shared through chat will let user know the progress and keep the app alive during that process. - Screen sharing in conference: only desktop app starting with 6.0 version is able to start it, but on mobiles you'll be able to see it. - You can choose whatever ringtone you'd like for incoming calls (in Android notification channel settings). diff --git a/README.md b/README.md index 99fc7f372..748fd7d8c 100644 --- a/README.md +++ b/README.md @@ -134,6 +134,8 @@ adb logcat -d | ndk-stack -sym ./libs-debug/`adb shell getprop ro.product.cpu.ab ``` Warning: This command won't print anything until you reproduce the crash! +Starting [NDK r29](https://github.com/android/ndk/wiki/Changelog-r29) you will be able to directly use the ```libs-debug.zip``` file in ```ndk-stack -sym``` argument. + ## Create an APK with a different package name Simply edit the ```app/build.gradle.kts``` file and change the value of the ```packageName``` variable. diff --git a/app/src/main/java/org/linphone/ui/GenericActivity.kt b/app/src/main/java/org/linphone/ui/GenericActivity.kt index 6f82a5992..8d5930966 100644 --- a/app/src/main/java/org/linphone/ui/GenericActivity.kt +++ b/app/src/main/java/org/linphone/ui/GenericActivity.kt @@ -235,7 +235,7 @@ open class GenericActivity : AppCompatActivity() { startActivity(intent) } - private fun enableWindowSecureMode(enable: Boolean) { + protected fun enableWindowSecureMode(enable: Boolean) { val flags: Int = window.attributes.flags if ((enable && flags and WindowManager.LayoutParams.FLAG_SECURE != 0) || (!enable && flags and WindowManager.LayoutParams.FLAG_SECURE == 0) diff --git a/app/src/main/java/org/linphone/ui/call/fragment/TransferCallFragment.kt b/app/src/main/java/org/linphone/ui/call/fragment/TransferCallFragment.kt index dc1209182..e27cb2df1 100644 --- a/app/src/main/java/org/linphone/ui/call/fragment/TransferCallFragment.kt +++ b/app/src/main/java/org/linphone/ui/call/fragment/TransferCallFragment.kt @@ -41,8 +41,6 @@ import org.linphone.ui.call.model.CallModel import org.linphone.ui.call.viewmodel.CallsViewModel import org.linphone.ui.call.viewmodel.CurrentCallViewModel import org.linphone.ui.main.adapter.ConversationsContactsAndSuggestionsListAdapter -import org.linphone.ui.main.contacts.model.ContactNumberOrAddressClickListener -import org.linphone.ui.main.contacts.model.ContactNumberOrAddressModel import org.linphone.ui.main.history.viewmodel.StartCallViewModel import org.linphone.utils.ConfirmationDialogModel import org.linphone.ui.main.model.ConversationContactOrSuggestionModel @@ -75,22 +73,6 @@ class TransferCallFragment : GenericCallFragment() { private var numberOrAddressPickerDialog: Dialog? = null - private val listener = object : ContactNumberOrAddressClickListener { - @UiThread - override fun onClicked(model: ContactNumberOrAddressModel) { - val address = model.address - if (address != null) { - coreContext.postOnCoreThread { - // TODO FIXME: transfer call (blind) - } - } - } - - @UiThread - override fun onLongPress(model: ContactNumberOrAddressModel) { - } - } - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) diff --git a/app/src/main/java/org/linphone/ui/fileviewer/FileViewerActivity.kt b/app/src/main/java/org/linphone/ui/fileviewer/FileViewerActivity.kt index 0e95f8a0b..f42a51b82 100644 --- a/app/src/main/java/org/linphone/ui/fileviewer/FileViewerActivity.kt +++ b/app/src/main/java/org/linphone/ui/fileviewer/FileViewerActivity.kt @@ -73,11 +73,19 @@ class FileViewerActivity : GenericActivity() { return } + val isFromEphemeralMessage = args.getBoolean("isFromEphemeralMessage", false) + if (isFromEphemeralMessage) { + Log.i("$TAG Displayed content is from an ephemeral chat message, force secure mode to prevent screenshots") + // Force preventing screenshots for ephemeral messages contents + enableWindowSecureMode(true) + } + val timestamp = args.getLong("timestamp", -1) val preLoadedContent = args.getString("content") Log.i( "$TAG Path argument is [$path], pre loaded text content is ${if (preLoadedContent.isNullOrEmpty()) "not available" else "available, using it"}" ) + viewModel.isFromEphemeralMessage.value = isFromEphemeralMessage viewModel.loadFile(path, timestamp, preLoadedContent) binding.setBackClickListener { diff --git a/app/src/main/java/org/linphone/ui/fileviewer/MediaViewerActivity.kt b/app/src/main/java/org/linphone/ui/fileviewer/MediaViewerActivity.kt index e110a956e..9072c8b28 100644 --- a/app/src/main/java/org/linphone/ui/fileviewer/MediaViewerActivity.kt +++ b/app/src/main/java/org/linphone/ui/fileviewer/MediaViewerActivity.kt @@ -17,6 +17,7 @@ import java.io.File import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import org.linphone.LinphoneApplication.Companion.corePreferences import org.linphone.R import org.linphone.core.tools.Log import org.linphone.databinding.FileMediaViewerActivityBinding @@ -51,6 +52,13 @@ class MediaViewerActivity : GenericActivity() { val model = list[position] viewModel.currentlyDisplayedFileName.value = model.fileName viewModel.currentlyDisplayedFileDateTime.value = model.dateTime + + val isFromEphemeral = model.isFromEphemeralMessage + viewModel.isCurrentlyDisplayedFileFromEphemeralMessage.value = isFromEphemeral + if (!corePreferences.enableSecureMode) { + // Force preventing screenshots for ephemeral messages contents, but allow it for others + enableWindowSecureMode(isFromEphemeral) + } } } } @@ -96,10 +104,17 @@ class MediaViewerActivity : GenericActivity() { return } + val isFromEphemeralMessage = args.getBoolean("isFromEphemeralMessage", false) + if (isFromEphemeralMessage) { + Log.i("$TAG Displayed content is from an ephemeral chat message, force secure mode to prevent screenshots") + // Force preventing screenshots for ephemeral messages contents + enableWindowSecureMode(true) + } + val timestamp = args.getLong("timestamp", -1) val isEncrypted = args.getBoolean("isEncrypted", false) val originalPath = args.getString("originalPath", "") - val isFromEphemeralMessage = args.getBoolean("isFromEphemeralMessage", false) + Log.i("$TAG Path argument is [$path], timestamp [$timestamp], encrypted [$isEncrypted] and original path [$originalPath]") viewModel.initTempModel(path, timestamp, isEncrypted, originalPath, isFromEphemeralMessage) val conversationId = args.getString("conversationId").orEmpty() @@ -184,12 +199,6 @@ class MediaViewerActivity : GenericActivity() { val currentItem = binding.mediaViewPager.currentItem val model = if (currentItem >= 0 && currentItem < list.size) list[currentItem] else null if (model != null) { - // Never do auto media export for ephemeral messages! - if (model.isFromEphemeralMessage) { - Log.e("$TAG Do not export media from ephemeral message!") - return - } - val filePath = model.path lifecycleScope.launch { withContext(Dispatchers.IO) { diff --git a/app/src/main/java/org/linphone/ui/fileviewer/viewmodel/FileViewModel.kt b/app/src/main/java/org/linphone/ui/fileviewer/viewmodel/FileViewModel.kt index a2fb1bb25..22b599dab 100644 --- a/app/src/main/java/org/linphone/ui/fileviewer/viewmodel/FileViewModel.kt +++ b/app/src/main/java/org/linphone/ui/fileviewer/viewmodel/FileViewModel.kt @@ -69,6 +69,8 @@ class FileViewModel val dateTime = MutableLiveData() + val isFromEphemeralMessage = MutableLiveData() + val exportPlainTextFileEvent: MutableLiveData> by lazy { MutableLiveData>() } diff --git a/app/src/main/java/org/linphone/ui/fileviewer/viewmodel/MediaListViewModel.kt b/app/src/main/java/org/linphone/ui/fileviewer/viewmodel/MediaListViewModel.kt index 49a56714e..505e396da 100644 --- a/app/src/main/java/org/linphone/ui/fileviewer/viewmodel/MediaListViewModel.kt +++ b/app/src/main/java/org/linphone/ui/fileviewer/viewmodel/MediaListViewModel.kt @@ -41,6 +41,8 @@ class MediaListViewModel val currentlyDisplayedFileDateTime = MutableLiveData() + val isCurrentlyDisplayedFileFromEphemeralMessage = MutableLiveData() + private lateinit var temporaryModel: FileModel override fun beforeNotifyingChatRoomFound(sameOne: Boolean) { @@ -101,8 +103,17 @@ class MediaListViewModel val size = mediaContent.size.toLong() val timestamp = mediaContent.creationTimestamp if (path.isNotEmpty() && name.isNotEmpty()) { - // TODO FIXME: we don't have the ephemeral info at Content level, using the chatRoom info even if content ephemeral status may or may not be different... - val ephemeral = chatRoom.isEphemeralEnabled + val messageId = mediaContent.relatedChatMessageId + val ephemeral = if (messageId != null) { + val chatMessage = chatRoom.findMessage(messageId) + if (chatMessage == null) { + Log.w("$TAG Failed to find message using ID [$messageId] related to this content, can't get real info about being related to ephemeral message") + } + chatMessage?.isEphemeral ?: chatRoom.isEphemeralEnabled + } else { + Log.e("$TAG No chat message ID related to this content, can't get real info about being related to ephemeral message") + chatRoom.isEphemeralEnabled + } val model = FileModel(path, name, size, timestamp, isEncrypted, originalPath, ephemeral) list.add(model) diff --git a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationDocumentsListViewModel.kt b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationDocumentsListViewModel.kt index 502f145f0..e703677b6 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationDocumentsListViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationDocumentsListViewModel.kt @@ -79,8 +79,17 @@ class ConversationDocumentsListViewModel val size = documentContent.size.toLong() val timestamp = documentContent.creationTimestamp if (path.isNotEmpty() && name.isNotEmpty()) { - // TODO FIXME: we don't have the ephemeral info at Content level, using the chatRoom info even if content ephemeral status may or may not be different... - val ephemeral = chatRoom.isEphemeralEnabled + val messageId = documentContent.relatedChatMessageId + val ephemeral = if (messageId != null) { + val chatMessage = chatRoom.findMessage(messageId) + if (chatMessage == null) { + Log.w("$TAG Failed to find message using ID [$messageId] related to this content, can't get real info about being related to ephemeral message") + } + chatMessage?.isEphemeral ?: chatRoom.isEphemeralEnabled + } else { + Log.e("$TAG No chat message ID related to this content, can't get real info about being related to ephemeral message") + chatRoom.isEphemeralEnabled + } val model = FileModel(path, name, size, timestamp, isEncrypted, originalPath, ephemeral) { diff --git a/app/src/main/res/layout/file_media_viewer_activity.xml b/app/src/main/res/layout/file_media_viewer_activity.xml index e02561b40..c0fde0e67 100644 --- a/app/src/main/res/layout/file_media_viewer_activity.xml +++ b/app/src/main/res/layout/file_media_viewer_activity.xml @@ -30,7 +30,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:visibility="@{sharedViewModel.mediaViewerFullScreenMode ? View.GONE : View.VISIBLE}" - app:constraint_referenced_ids="top_bar_background, back, file_name, share, save, date_time"/> + app:constraint_referenced_ids="top_bar_background, back, file_name, date_time"/> @@ -121,6 +122,7 @@ android:padding="15dp" android:src="@drawable/download_simple" android:contentDescription="@string/content_description_save_file" + android:visibility="@{sharedViewModel.mediaViewerFullScreenMode || viewModel.isCurrentlyDisplayedFileFromEphemeralMessage ? View.GONE : View.VISIBLE}" app:tint="@color/gray_main2_500" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" /> diff --git a/app/src/main/res/layout/file_viewer_activity.xml b/app/src/main/res/layout/file_viewer_activity.xml index f06de0fe2..cc24247b4 100644 --- a/app/src/main/res/layout/file_viewer_activity.xml +++ b/app/src/main/res/layout/file_viewer_activity.xml @@ -25,7 +25,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:visibility="@{viewModel.fullScreenMode ? View.GONE : View.VISIBLE}" - app:constraint_referenced_ids="top_bar_background, back, file_name, share, save, date_time"/> + app:constraint_referenced_ids="top_bar_background, back, file_name, date_time"/> @@ -159,6 +160,7 @@ android:padding="15dp" android:src="@drawable/download_simple" android:contentDescription="@string/content_description_save_file" + android:visibility="@{viewModel.fullScreenMode || viewModel.isFromEphemeralMessage ? View.GONE : View.VISIBLE}" app:tint="@color/gray_main2_500" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" /> From e0e70328275f3c4337a989db6a592e09544b3a8a Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Wed, 5 Mar 2025 10:46:29 +0100 Subject: [PATCH 76/89] Updated AGP to 8.9.0 and gradle to 8.11.1 --- gradle/libs.versions.toml | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f269ec546..d5530bb77 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -agp = "8.8.2" +agp = "8.9.0" kotlin = "2.0.21" gmsGoogleServices = "4.4.2" firebaseCrashlytics = "3.0.3" diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 04c30cb2b..f0fad5900 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Thu Jun 22 12:11:25 CEST 2023 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From 3c21044cf037da311d92fa8a9733c9a1678f2080 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Wed, 5 Mar 2025 13:53:10 +0100 Subject: [PATCH 77/89] Renamed Android Auto related classes --- app/src/main/AndroidManifest.xml | 3 ++- .../telecom/auto/{AAScreen.kt => AndroidAutoScreen.kt} | 2 +- .../telecom/auto/{AAService.kt => AndroidAutoService.kt} | 4 ++-- .../telecom/auto/{AASession.kt => AndroidAutoSession.kt} | 4 ++-- 4 files changed, 7 insertions(+), 6 deletions(-) rename app/src/main/java/org/linphone/telecom/auto/{AAScreen.kt => AndroidAutoScreen.kt} (98%) rename app/src/main/java/org/linphone/telecom/auto/{AAService.kt => AndroidAutoService.kt} (96%) rename app/src/main/java/org/linphone/telecom/auto/{AASession.kt => AndroidAutoSession.kt} (93%) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 0d7d79c2e..04a51f47f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -60,6 +60,7 @@ + @@ -199,7 +200,7 @@ SIP address Device ID - username@domain Display name Domain Username @@ -922,6 +921,7 @@ Go to conversation Copy text to clipboard Voice message are available + Long press to dial voicemail diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index adab3cc5b..5b54e32e7 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -149,15 +149,7 @@ ?attr/color_danger_500 8dp - +