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 d4c5e28a7..a942f9d06 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 @@ -38,9 +38,9 @@ import org.linphone.databinding.ChatInfoFragmentBinding import org.linphone.databinding.ChatParticipantAdminPopupMenuBinding import org.linphone.ui.main.MainActivity import org.linphone.ui.main.chat.adapter.ConversationParticipantsAdapter -import org.linphone.ui.main.chat.model.ConversationSetOrEditSubjectDialogModel import org.linphone.ui.main.chat.model.ParticipantModel import org.linphone.ui.main.chat.viewmodel.ConversationInfoViewModel +import org.linphone.ui.main.fragment.GroupSetOrEditSubjectDialogModel import org.linphone.ui.main.fragment.SlidingPaneChildFragment import org.linphone.utils.DialogUtils import org.linphone.utils.Event @@ -217,9 +217,9 @@ class ConversationInfoFragment : SlidingPaneChildFragment() { binding.setEditSubjectClickListener { val currentSubject = viewModel.subject.value.orEmpty() - val model = ConversationSetOrEditSubjectDialogModel(currentSubject) + val model = GroupSetOrEditSubjectDialogModel(currentSubject, isGroupConversation = true) - val dialog = DialogUtils.getSetOrEditConversationSubjectDialog( + val dialog = DialogUtils.getSetOrEditGroupSubjectDialog( requireContext(), model ) 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 9bda3645a..40ed78624 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 @@ -33,9 +33,9 @@ import org.linphone.core.Friend import org.linphone.core.tools.Log import org.linphone.databinding.StartChatFragmentBinding import org.linphone.ui.main.MainActivity -import org.linphone.ui.main.chat.model.ConversationSetOrEditSubjectDialogModel import org.linphone.ui.main.chat.viewmodel.StartConversationViewModel import org.linphone.ui.main.fragment.GenericAddressPickerFragment +import org.linphone.ui.main.fragment.GroupSetOrEditSubjectDialogModel import org.linphone.utils.DialogUtils import org.linphone.utils.Event import org.linphone.utils.hideKeyboard @@ -122,9 +122,9 @@ class StartConversationFragment : GenericAddressPickerFragment() { } private fun showGroupConversationSubjectDialog() { - val model = ConversationSetOrEditSubjectDialogModel("") + val model = GroupSetOrEditSubjectDialogModel("", isGroupConversation = true) - val dialog = DialogUtils.getSetOrEditConversationSubjectDialog( + val dialog = DialogUtils.getSetOrEditGroupSubjectDialog( requireContext(), model ) 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 48bbba60c..1d70350b1 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 @@ -46,7 +46,7 @@ class StartConversationViewModel @UiThread constructor() : AddressSelectionViewM val subject = MutableLiveData() - val groupChatRoomCreateButtonEnabled = MediatorLiveData() + val createGroupConversationButtonEnabled = MediatorLiveData() val operationInProgress = MutableLiveData() @@ -87,9 +87,9 @@ class StartConversationViewModel @UiThread constructor() : AddressSelectionViewM } init { - groupChatRoomCreateButtonEnabled.postValue(false) - groupChatRoomCreateButtonEnabled.addSource(selection) { - groupChatRoomCreateButtonEnabled.postValue(it.isNotEmpty()) + createGroupConversationButtonEnabled.value = false + createGroupConversationButtonEnabled.addSource(selection) { + createGroupConversationButtonEnabled.value = it.isNotEmpty() } updateGroupChatButtonVisibility() diff --git a/app/src/main/java/org/linphone/ui/main/chat/model/ConversationSetOrEditSubjectDialogModel.kt b/app/src/main/java/org/linphone/ui/main/fragment/GroupSetOrEditSubjectDialogModel.kt similarity index 88% rename from app/src/main/java/org/linphone/ui/main/chat/model/ConversationSetOrEditSubjectDialogModel.kt rename to app/src/main/java/org/linphone/ui/main/fragment/GroupSetOrEditSubjectDialogModel.kt index f1324937e..860053294 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/model/ConversationSetOrEditSubjectDialogModel.kt +++ b/app/src/main/java/org/linphone/ui/main/fragment/GroupSetOrEditSubjectDialogModel.kt @@ -17,13 +17,16 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.linphone.ui.main.chat.model +package org.linphone.ui.main.fragment import androidx.annotation.UiThread import androidx.lifecycle.MutableLiveData import org.linphone.utils.Event -class ConversationSetOrEditSubjectDialogModel @UiThread constructor(initialSubject: String) { +class GroupSetOrEditSubjectDialogModel @UiThread constructor( + initialSubject: String, + val isGroupConversation: Boolean +) { val isEdit = initialSubject.isNotEmpty() val subject = MutableLiveData() 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 c4db7a9db..bdbc32893 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 @@ -35,7 +35,9 @@ import org.linphone.core.Friend import org.linphone.core.tools.Log import org.linphone.databinding.StartCallFragmentBinding import org.linphone.ui.main.fragment.GenericAddressPickerFragment +import org.linphone.ui.main.fragment.GroupSetOrEditSubjectDialogModel import org.linphone.ui.main.history.viewmodel.StartCallViewModel +import org.linphone.utils.DialogUtils import org.linphone.utils.addCharacterAtPosition import org.linphone.utils.hideKeyboard import org.linphone.utils.removeCharacterAtPosition @@ -80,6 +82,11 @@ class StartCallFragment : GenericAddressPickerFragment() { viewModel.hideNumpad() } + binding.setAskForGroupCallSubjectClickListener { + viewModel.hideNumpad() + showGroupCallSubjectDialog() + } + setupRecyclerView(binding.contactsAndSuggestionsList) viewModel.contactsAndSuggestionsList.observe( @@ -151,4 +158,40 @@ class StartCallFragment : GenericAddressPickerFragment() { viewModel.isNumpadVisible.value = false } + + private fun showGroupCallSubjectDialog() { + val model = GroupSetOrEditSubjectDialogModel("", isGroupConversation = false) + + val dialog = DialogUtils.getSetOrEditGroupSubjectDialog( + requireContext(), + model + ) + + model.dismissEvent.observe(viewLifecycleOwner) { + it.consume { + Log.i("$TAG Set group call subject cancelled") + dialog.dismiss() + } + } + + model.confirmEvent.observe(viewLifecycleOwner) { + it.consume { newSubject -> + if (newSubject.isNotEmpty()) { + Log.i( + "$TAG Group call subject has been set to [$newSubject]" + ) + viewModel.subject.value = newSubject + viewModel.createGroupCall() + + dialog.currentFocus?.hideKeyboard() + dialog.dismiss() + } else { + // TODO: show error + } + } + } + + Log.i("$TAG Showing dialog to set group call subject") + dialog.show() + } } 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 403377990..e672ec53f 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 @@ -20,12 +20,18 @@ package org.linphone.ui.main.history.viewmodel import androidx.annotation.UiThread +import androidx.lifecycle.MediatorLiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.viewModelScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.LinphoneApplication.Companion.corePreferences +import org.linphone.core.ConferenceScheduler +import org.linphone.core.ConferenceSchedulerListenerStub +import org.linphone.core.Factory +import org.linphone.core.Participant +import org.linphone.core.ParticipantInfo import org.linphone.core.tools.Log import org.linphone.ui.main.history.model.NumpadModel import org.linphone.ui.main.viewmodel.AddressSelectionViewModel @@ -45,7 +51,11 @@ class StartCallViewModel @UiThread constructor() : AddressSelectionViewModel() { val isNumpadVisible = MutableLiveData() - val isGroupCallAvailable = MutableLiveData() + val startGroupCallButtonEnabled = MediatorLiveData() + + val subject = MutableLiveData() + + val operationInProgress = MutableLiveData() val appendDigitToSearchBarEvent: MutableLiveData> by lazy { MutableLiveData>() @@ -59,6 +69,35 @@ class StartCallViewModel @UiThread constructor() : AddressSelectionViewModel() { MutableLiveData>() } + private val conferenceSchedulerListener = object : ConferenceSchedulerListenerStub() { + override fun onStateChanged( + conferenceScheduler: ConferenceScheduler, + state: ConferenceScheduler.State + ) { + Log.i("$TAG Conference scheduler state is $state") + if (state == ConferenceScheduler.State.Ready) { + conferenceScheduler.removeListener(this) + + val conferenceAddress = conferenceScheduler.info?.uri + if (conferenceAddress != null) { + Log.i( + "$TAG Conference info created, address is ${conferenceAddress.asStringUriOnly()}" + ) + coreContext.startCall(conferenceAddress) + } else { + Log.e("$TAG Conference info URI is null!") + // TODO: notify error to user + } + operationInProgress.postValue(false) + } else if (state == ConferenceScheduler.State.Error) { + conferenceScheduler.removeListener(this) + Log.e("$TAG Failed to create group call!") + // TODO: notify error to user + operationInProgress.postValue(false) + } + } + } + init { isNumpadVisible.value = false numpadModel = NumpadModel( @@ -89,6 +128,11 @@ class StartCallViewModel @UiThread constructor() : AddressSelectionViewModel() { } ) + startGroupCallButtonEnabled.value = false + startGroupCallButtonEnabled.addSource(selection) { + startGroupCallButtonEnabled.value = it.isNotEmpty() + } + updateGroupCallButtonVisibility() } @@ -116,4 +160,43 @@ class StartCallViewModel @UiThread constructor() : AddressSelectionViewModel() { fun hideNumpad() { isNumpadVisible.value = false } + + @UiThread + fun createGroupCall() { + coreContext.postOnCoreThread { core -> + val account = core.defaultAccount + if (account == null) { + Log.e( + "$TAG No default account found, can't create group call!" + ) + return@postOnCoreThread + } + + operationInProgress.postValue(true) + + val conferenceInfo = Factory.instance().createConferenceInfo() + conferenceInfo.organizer = account.params.identityAddress + conferenceInfo.subject = subject.value + + val participants = arrayOfNulls(selection.value.orEmpty().size) + var index = 0 + for (participant in selection.value.orEmpty()) { + val info = Factory.instance().createParticipantInfo(participant.address) + // For meetings, all participants must have Speaker role + info?.role = Participant.Role.Speaker + participants[index] = info + index += 1 + } + conferenceInfo.setParticipantInfos(participants) + + Log.i( + "$TAG Creating group call with subject ${subject.value} and ${participants.size} participant(s)" + ) + val conferenceScheduler = core.createConferenceScheduler() + conferenceScheduler.addListener(conferenceSchedulerListener) + conferenceScheduler.account = account + // Will trigger the conference creation/update automatically + conferenceScheduler.info = conferenceInfo + } + } } 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 5eda304f6..381b51ff4 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 @@ -76,8 +76,7 @@ abstract class AddressSelectionViewModel @UiThread constructor() : DefaultAccoun applyFilter( currentFilter, if (limitSearchToLinphoneAccounts) corePreferences.defaultDomain else "", - magicSearchSourceFlags, - MagicSearch.Aggregation.Friend + magicSearchSourceFlags ) } } @@ -109,7 +108,9 @@ abstract class AddressSelectionViewModel @UiThread constructor() : DefaultAccoun @UiThread fun clearFilter() { - searchFilter.value = "" + if (searchFilter.value.orEmpty().isNotEmpty()) { + searchFilter.value = "" + } } @UiThread @@ -185,8 +186,7 @@ abstract class AddressSelectionViewModel @UiThread constructor() : DefaultAccoun applyFilter( filter, if (limitSearchToLinphoneAccounts) corePreferences.defaultDomain else "", - magicSearchSourceFlags, - MagicSearch.Aggregation.Friend + magicSearchSourceFlags ) } } @@ -195,8 +195,7 @@ abstract class AddressSelectionViewModel @UiThread constructor() : DefaultAccoun private fun applyFilter( filter: String, domain: String, - sources: Int, - aggregation: MagicSearch.Aggregation + sources: Int ) { if (previousFilter.isNotEmpty() && ( previousFilter.length > filter.length || @@ -215,7 +214,7 @@ abstract class AddressSelectionViewModel @UiThread constructor() : DefaultAccoun filter, domain, sources, - aggregation + MagicSearch.Aggregation.Friend ) } diff --git a/app/src/main/java/org/linphone/utils/DialogUtils.kt b/app/src/main/java/org/linphone/utils/DialogUtils.kt index d18cdf33b..72608c1a7 100644 --- a/app/src/main/java/org/linphone/utils/DialogUtils.kt +++ b/app/src/main/java/org/linphone/utils/DialogUtils.kt @@ -44,14 +44,14 @@ import org.linphone.databinding.DialogPickNumberOrAddressBinding import org.linphone.databinding.DialogRemoveAccountBinding import org.linphone.databinding.DialogRemoveAllCallLogsBinding import org.linphone.databinding.DialogRemoveCallLogsBinding -import org.linphone.databinding.DialogSetOrEditGroupConversationSubjectBindingImpl +import org.linphone.databinding.DialogSetOrEditGroupSubjectBindingImpl import org.linphone.databinding.DialogUpdateAvailableBinding import org.linphone.ui.assistant.model.AcceptConditionsAndPolicyDialogModel import org.linphone.ui.assistant.model.ConfirmPhoneNumberDialogModel import org.linphone.ui.call.model.ZrtpSasConfirmationDialogModel -import org.linphone.ui.main.chat.model.ConversationSetOrEditSubjectDialogModel import org.linphone.ui.main.contacts.model.NumberOrAddressPickerDialogModel import org.linphone.ui.main.contacts.model.TrustCallDialogModel +import org.linphone.ui.main.fragment.GroupSetOrEditSubjectDialogModel import org.linphone.ui.main.history.model.ConfirmationDialogModel class DialogUtils { @@ -268,13 +268,13 @@ class DialogUtils { } @UiThread - fun getSetOrEditConversationSubjectDialog( + fun getSetOrEditGroupSubjectDialog( context: Context, - viewModel: ConversationSetOrEditSubjectDialogModel + viewModel: GroupSetOrEditSubjectDialogModel ): Dialog { - val binding: DialogSetOrEditGroupConversationSubjectBindingImpl = DataBindingUtil.inflate( + val binding: DialogSetOrEditGroupSubjectBindingImpl = DataBindingUtil.inflate( LayoutInflater.from(context), - R.layout.dialog_set_or_edit_group_conversation_subject, + R.layout.dialog_set_or_edit_group_subject, null, false ) diff --git a/app/src/main/res/layout/dialog_set_or_edit_group_conversation_subject.xml b/app/src/main/res/layout/dialog_set_or_edit_group_subject.xml similarity index 89% rename from app/src/main/res/layout/dialog_set_or_edit_group_conversation_subject.xml rename to app/src/main/res/layout/dialog_set_or_edit_group_subject.xml index 9132cff75..0a2b1dee6 100644 --- a/app/src/main/res/layout/dialog_set_or_edit_group_conversation_subject.xml +++ b/app/src/main/res/layout/dialog_set_or_edit_group_subject.xml @@ -8,7 +8,7 @@ + type="org.linphone.ui.main.fragment.GroupSetOrEditSubjectDialogModel" /> diff --git a/app/src/main/res/layout/start_call_fragment.xml b/app/src/main/res/layout/start_call_fragment.xml index e1c0a36b9..5ba7dd79a 100644 --- a/app/src/main/res/layout/start_call_fragment.xml +++ b/app/src/main/res/layout/start_call_fragment.xml @@ -11,6 +11,9 @@ + @@ -25,27 +28,34 @@ android:layout_height="match_parent" android:background="?attr/color_main2_000"> + + + app:layout_constraintTop_toTopOf="parent" /> @@ -57,15 +67,44 @@ app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/back" /> + + + + + + + + + app:layout_constraintTop_toBottomOf="@id/multiple_selection" /> + android:visibility="@{viewModel.hideGroupCallButton || viewModel.multipleSelectionMode || viewModel.searchFilter.length() > 0 ? View.GONE : View.VISIBLE}" /> + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/start_chat_fragment.xml b/app/src/main/res/layout/start_chat_fragment.xml index eb6956338..261c94f66 100644 --- a/app/src/main/res/layout/start_chat_fragment.xml +++ b/app/src/main/res/layout/start_chat_fragment.xml @@ -229,7 +229,7 @@ android:layout_height="wrap_content" android:layout_gravity="end|bottom" android:layout_margin="16dp" - android:visibility="@{viewModel.multipleSelectionMode && viewModel.groupChatRoomCreateButtonEnabled ? View.VISIBLE : View.GONE, default=gone}" + android:visibility="@{viewModel.multipleSelectionMode && viewModel.createGroupConversationButtonEnabled ? View.VISIBLE : View.GONE, default=gone}" android:src="@drawable/check" app:tint="?attr/color_on_main" app:backgroundTint="?attr/color_main1_500" diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ba5b954c0..f149f0b5b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -123,6 +123,8 @@ Edit conversation subject Conversation subject Confirm + Set group call subject + Group call subject Invalid QR code! SIP address copied into clipboard