From a129cc5f95ea65cc9999157cc99d5318e0c2c046 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Wed, 25 Oct 2023 11:57:12 +0200 Subject: [PATCH] Started conference waiting room --- .../java/org/linphone/core/CoreContext.kt | 2 +- .../java/org/linphone/core/CorePreferences.kt | 4 - .../fragment/QrCodeScannerFragment.kt | 3 + .../ui/call/fragment/CallsListFragment.kt | 4 +- .../ui/call/viewmodel/CurrentCallViewModel.kt | 1 + .../chat/fragment/ConversationFragment.kt | 4 +- .../chat/fragment/ConversationInfoFragment.kt | 3 +- .../chat/fragment/ConversationsFragment.kt | 3 +- .../fragment/ConversationsListFragment.kt | 4 +- .../contacts/fragment/ContactsFragment.kt | 4 +- .../contacts/fragment/ContactsListFragment.kt | 4 +- .../main/fragment/AddParticipantsFragment.kt | 3 +- .../fragment/HistoryContactFragment.kt | 4 +- .../main/history/fragment/HistoryFragment.kt | 3 +- .../history/fragment/HistoryListFragment.kt | 4 +- .../main/meetings/fragment/MeetingFragment.kt | 13 +- .../fragment/MeetingWaitingRoomFragment.kt | 158 +++++++++++++++ .../meetings/fragment/MeetingsFragment.kt | 13 +- .../meetings/fragment/MeetingsListFragment.kt | 4 +- .../meetings/viewmodel/MeetingViewModel.kt | 10 +- .../viewmodel/MeetingWaitingRoomViewModel.kt | 189 ++++++++++++++++++ .../fragment/AccountSettingsFragment.kt | 3 +- .../ui/main/viewmodel/SharedMainViewModel.kt | 4 + ...=> shape_squircle_gray_100_background.xml} | 0 .../shape_squircle_gray_600_background.xml | 5 + .../account_profile_device_list_cell.xml | 2 +- .../layout/assistant_secure_mode_fragment.xml | 4 +- app/src/main/res/layout/call_main_actions.xml | 2 +- app/src/main/res/layout/meeting_fragment.xml | 29 ++- .../layout/meeting_waiting_room_fragment.xml | 179 +++++++++++++++++ .../main/res/navigation/main_nav_graph.xml | 69 ++++--- app/src/main/res/values/strings.xml | 2 + 32 files changed, 652 insertions(+), 84 deletions(-) create mode 100644 app/src/main/java/org/linphone/ui/main/meetings/fragment/MeetingWaitingRoomFragment.kt create mode 100644 app/src/main/java/org/linphone/ui/main/meetings/viewmodel/MeetingWaitingRoomViewModel.kt rename app/src/main/res/drawable/{shape_squircle_gray_2_background.xml => shape_squircle_gray_100_background.xml} (100%) create mode 100644 app/src/main/res/drawable/shape_squircle_gray_600_background.xml create mode 100644 app/src/main/res/layout/meeting_waiting_room_fragment.xml diff --git a/app/src/main/java/org/linphone/core/CoreContext.kt b/app/src/main/java/org/linphone/core/CoreContext.kt index a3db8c039..7fd5072db 100644 --- a/app/src/main/java/org/linphone/core/CoreContext.kt +++ b/app/src/main/java/org/linphone/core/CoreContext.kt @@ -279,7 +279,7 @@ class CoreContext @UiThread constructor(val context: Context) : HandlerThread("C params.mediaEncryption = MediaEncryption.ZRTP } /*if (LinphoneUtils.checkIfNetworkHasLowBandwidth(context)) { - Log.w("[Context] Enabling low bandwidth mode!") + Log.w("$TAG Enabling low bandwidth mode!") params.isLowBandwidthEnabled = true }*/ diff --git a/app/src/main/java/org/linphone/core/CorePreferences.kt b/app/src/main/java/org/linphone/core/CorePreferences.kt index 7e7c3b1dc..73efdf879 100644 --- a/app/src/main/java/org/linphone/core/CorePreferences.kt +++ b/app/src/main/java/org/linphone/core/CorePreferences.kt @@ -146,10 +146,6 @@ class CorePreferences @UiThread constructor(private val context: Context) { val ringtonesPath: String get() = context.filesDir.absolutePath + "/share/sounds/linphone/rings/" - @get:AnyThread - val defaultRingtonePath: String - get() = ringtonesPath + "notes_of_the_optimistic.mkv" - @UiThread fun copyAssetsFromPackage() { copy("linphonerc_default", configPath) 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 b24142fe7..278517653 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 @@ -104,6 +104,9 @@ class QrCodeScannerFragment : Fragment() { super.onResume() if (isCameraPermissionGranted()) { + Log.i( + "$TAG Record video permission is granted, starting video preview with back cam if possible" + ) viewModel.setBackCamera() enableQrCodeVideoScanner() } 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 2cabff9ea..42741d5b0 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 @@ -64,9 +64,7 @@ class CallsListFragment : GenericCallFragment() { adapter = CallsListAdapter(viewLifecycleOwner) binding.callsList.setHasFixedSize(true) binding.callsList.adapter = adapter - - val layoutManager = LinearLayoutManager(requireContext()) - binding.callsList.layoutManager = layoutManager + binding.callsList.layoutManager = LinearLayoutManager(requireContext()) adapter.callLongClickedEvent.observe(viewLifecycleOwner) { it.consume { model -> 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 9af83ced8..dfd4a8601 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 @@ -541,6 +541,7 @@ class CurrentCallViewModel @UiThread constructor() : ViewModel() { @UiThread fun switchCamera() { coreContext.postOnCoreThread { + Log.i("$TAG Switching camera") coreContext.switchCamera() } } 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 1120430d1..b6e03fb25 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 @@ -162,9 +162,7 @@ class ConversationFragment : GenericFragment() { adapter = ConversationEventAdapter(viewLifecycleOwner) binding.eventsList.setHasFixedSize(true) binding.eventsList.adapter = adapter - - val layoutManager = LinearLayoutManager(requireContext()) - binding.eventsList.layoutManager = layoutManager + binding.eventsList.layoutManager = LinearLayoutManager(requireContext()) bottomSheetAdapter = ChatMessageBottomSheetAdapter(viewLifecycleOwner) binding.messageBottomSheet.bottomSheetList.setHasFixedSize(true) 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 8b98203d7..ceb97c465 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 @@ -63,8 +63,7 @@ class ConversationInfoFragment : GenericFragment() { } override fun goBack(): Boolean { - findNavController().popBackStack() - return true + return findNavController().popBackStack() } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { diff --git a/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationsFragment.kt b/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationsFragment.kt index cfaa975a4..b395db1d9 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationsFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationsFragment.kt @@ -112,7 +112,8 @@ class ConversationsFragment : GenericFragment() { sharedViewModel.showStartConversationEvent.observe(viewLifecycleOwner) { it.consume { Log.i("$TAG Navigating to start conversation fragment") - findNavController().navigate(R.id.action_global_startConversationFragment) + val action = ConversationsFragmentDirections.actionConversationsFragmentToStartConversationFragment() + findNavController().navigate(action) } } 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 e0fa23d80..c4b1f9407 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 @@ -70,9 +70,7 @@ class ConversationsListFragment : AbstractTopBarFragment() { adapter = ConversationsListAdapter(viewLifecycleOwner) binding.conversationsList.setHasFixedSize(true) binding.conversationsList.adapter = adapter - - val layoutManager = LinearLayoutManager(requireContext()) - binding.conversationsList.layoutManager = layoutManager + binding.conversationsList.layoutManager = LinearLayoutManager(requireContext()) adapter.conversationLongClickedEvent.observe(viewLifecycleOwner) { it.consume { model -> diff --git a/app/src/main/java/org/linphone/ui/main/contacts/fragment/ContactsFragment.kt b/app/src/main/java/org/linphone/ui/main/contacts/fragment/ContactsFragment.kt index d0101a86e..21f5cdc27 100644 --- a/app/src/main/java/org/linphone/ui/main/contacts/fragment/ContactsFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/contacts/fragment/ContactsFragment.kt @@ -114,8 +114,8 @@ class ContactsFragment : GenericFragment() { ) { it.consume { Log.i("$TAG Opening contact editor for creating new contact") - val navController = findNavController() - navController.navigate(R.id.action_global_newContactFragment) + val action = ContactsFragmentDirections.actionContactsFragmentToNewContactFragment() + findNavController().navigate(action) } } diff --git a/app/src/main/java/org/linphone/ui/main/contacts/fragment/ContactsListFragment.kt b/app/src/main/java/org/linphone/ui/main/contacts/fragment/ContactsListFragment.kt index a8c75ea18..f86914230 100644 --- a/app/src/main/java/org/linphone/ui/main/contacts/fragment/ContactsListFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/contacts/fragment/ContactsListFragment.kt @@ -81,9 +81,7 @@ class ContactsListFragment : AbstractTopBarFragment() { binding.contactsList.setHasFixedSize(true) binding.contactsList.adapter = adapter configureAdapter(adapter) - - val layoutManager = LinearLayoutManager(requireContext()) - binding.contactsList.layoutManager = layoutManager + binding.contactsList.layoutManager = LinearLayoutManager(requireContext()) favouritesAdapter = ContactsListAdapter(viewLifecycleOwner, favourites = true) binding.favouritesContactsList.setHasFixedSize(true) diff --git a/app/src/main/java/org/linphone/ui/main/fragment/AddParticipantsFragment.kt b/app/src/main/java/org/linphone/ui/main/fragment/AddParticipantsFragment.kt index 31bb8928a..4a97b4aec 100644 --- a/app/src/main/java/org/linphone/ui/main/fragment/AddParticipantsFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/fragment/AddParticipantsFragment.kt @@ -54,8 +54,7 @@ class AddParticipantsFragment : GenericAddressPickerFragment() { } override fun goBack(): Boolean { - findNavController().popBackStack() - return true + return findNavController().popBackStack() } override fun onSingleAddressSelected(address: Address, friend: Friend) { diff --git a/app/src/main/java/org/linphone/ui/main/history/fragment/HistoryContactFragment.kt b/app/src/main/java/org/linphone/ui/main/history/fragment/HistoryContactFragment.kt index 05bf72037..8ec940adf 100644 --- a/app/src/main/java/org/linphone/ui/main/history/fragment/HistoryContactFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/history/fragment/HistoryContactFragment.kt @@ -93,9 +93,7 @@ class HistoryContactFragment : GenericFragment() { adapter = ContactHistoryListAdapter(viewLifecycleOwner) binding.callHistory.setHasFixedSize(true) binding.callHistory.adapter = adapter - - val layoutManager = LinearLayoutManager(requireContext()) - binding.callHistory.layoutManager = layoutManager + binding.callHistory.layoutManager = LinearLayoutManager(requireContext()) binding.setBackClickListener { goBack() 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 22f625e96..3a319d82a 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 @@ -99,7 +99,8 @@ class HistoryFragment : GenericFragment() { sharedViewModel.showStartCallEvent.observe(viewLifecycleOwner) { it.consume { Log.i("$TAG Navigating to start call fragment") - findNavController().navigate(R.id.action_global_startCallFragment) + val action = HistoryFragmentDirections.actionHistoryFragmentToStartCallFragment() + findNavController().navigate(action) } } diff --git a/app/src/main/java/org/linphone/ui/main/history/fragment/HistoryListFragment.kt b/app/src/main/java/org/linphone/ui/main/history/fragment/HistoryListFragment.kt index eef9372a6..615d95d35 100644 --- a/app/src/main/java/org/linphone/ui/main/history/fragment/HistoryListFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/history/fragment/HistoryListFragment.kt @@ -80,9 +80,7 @@ class HistoryListFragment : AbstractTopBarFragment() { adapter = HistoryListAdapter(viewLifecycleOwner) binding.historyList.setHasFixedSize(true) binding.historyList.adapter = adapter - - val layoutManager = LinearLayoutManager(requireContext()) - binding.historyList.layoutManager = layoutManager + binding.historyList.layoutManager = LinearLayoutManager(requireContext()) adapter.callLogLongClickedEvent.observe(viewLifecycleOwner) { it.consume { model -> 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 a825529b9..caffb64d0 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 @@ -79,9 +79,7 @@ class MeetingFragment : GenericFragment() { binding.lifecycleOwner = viewLifecycleOwner - viewModel = requireActivity().run { - ViewModelProvider(this)[MeetingViewModel::class.java] - } + viewModel = ViewModelProvider(this)[MeetingViewModel::class.java] binding.viewModel = viewModel val uri = args.conferenceUri @@ -95,6 +93,7 @@ class MeetingFragment : GenericFragment() { } binding.setShareClickListener { + Log.i("$TAG Sharing conference info as Google Calendar event") shareMeetingInfoAsCalendarEvent() } @@ -102,6 +101,12 @@ class MeetingFragment : GenericFragment() { showPopupMenu() } + binding.setJoinClickListener { + val conferenceUri = args.conferenceUri + Log.i("$TAG Requesting to go to waiting room for conference URI [$conferenceUri]") + sharedViewModel.goToMeetingWaitingRoomEvent.value = Event(conferenceUri) + } + sharedViewModel.isSlidingPaneSlideable.observe(viewLifecycleOwner) { slideable -> viewModel.showBackButton.value = slideable } @@ -181,7 +186,7 @@ class MeetingFragment : GenericFragment() { try { startActivity(intent) } catch (exception: ActivityNotFoundException) { - Log.e("${MeetingFragment.TAG} No activity found to handle intent: $exception") + Log.e("$TAG No activity found to handle intent: $exception") } } } diff --git a/app/src/main/java/org/linphone/ui/main/meetings/fragment/MeetingWaitingRoomFragment.kt b/app/src/main/java/org/linphone/ui/main/meetings/fragment/MeetingWaitingRoomFragment.kt new file mode 100644 index 000000000..bf3911337 --- /dev/null +++ b/app/src/main/java/org/linphone/ui/main/meetings/fragment/MeetingWaitingRoomFragment.kt @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2010-2023 Belledonne Communications SARL. + * + * This file is part of linphone-android + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.linphone.ui.main.meetings.fragment + +import android.Manifest +import android.content.pm.PackageManager +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.activity.result.contract.ActivityResultContracts +import androidx.annotation.UiThread +import androidx.core.content.ContextCompat +import androidx.core.view.doOnPreDraw +import androidx.lifecycle.ViewModelProvider +import androidx.navigation.fragment.findNavController +import androidx.navigation.fragment.navArgs +import org.linphone.LinphoneApplication.Companion.coreContext +import org.linphone.core.tools.Log +import org.linphone.databinding.MeetingWaitingRoomFragmentBinding +import org.linphone.ui.main.fragment.GenericFragment +import org.linphone.ui.main.meetings.viewmodel.MeetingWaitingRoomViewModel + +@UiThread +class MeetingWaitingRoomFragment : GenericFragment() { + companion object { + private const val TAG = "[Meeting Waiting Room Fragment]" + } + + private lateinit var binding: MeetingWaitingRoomFragmentBinding + + private lateinit var viewModel: MeetingWaitingRoomViewModel + + private val args: MeetingWaitingRoomFragmentArgs by navArgs() + + private val requestPermissionLauncher = registerForActivityResult( + ActivityResultContracts.RequestPermission() + ) { isGranted -> + if (isGranted) { + Log.i("$TAG Camera permission has been granted") + enableVideoPreview() + } else { + Log.e("$TAG Camera permission has been denied, leaving this fragment") + goBack() + } + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + binding = MeetingWaitingRoomFragmentBinding.inflate(layoutInflater) + return binding.root + } + + override fun goBack(): Boolean { + return findNavController().popBackStack() + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + postponeEnterTransition() + + binding.lifecycleOwner = viewLifecycleOwner + + viewModel = ViewModelProvider(this)[MeetingWaitingRoomViewModel::class.java] + binding.viewModel = viewModel + + val uri = args.conferenceUri + Log.i( + "$TAG Looking up for conference with SIP URI [$uri]" + ) + viewModel.findConferenceInfo(uri) + + binding.setBackClickListener { + goBack() + } + + viewModel.conferenceInfoFoundEvent.observe(viewLifecycleOwner) { + it.consume { found -> + if (found) { + (view.parent as? ViewGroup)?.doOnPreDraw { + startPostponedEnterTransition() + } + } else { + Log.e("$TAG Failed to find meeting with URI [$uri], going back") + (view.parent as? ViewGroup)?.doOnPreDraw { + goBack() + } + } + } + } + + if (!isCameraPermissionGranted()) { + viewModel.isVideoAvailable.value = false + Log.w("$TAG Camera permission wasn't granted yet, asking for it now") + requestPermissionLauncher.launch(Manifest.permission.CAMERA) + } + } + + override fun onResume() { + super.onResume() + + if (isCameraPermissionGranted()) { + Log.i( + "$TAG Record video permission is granted, starting video preview with front cam if possible" + ) + viewModel.setFrontCamera() + enableVideoPreview() + } + } + + override fun onPause() { + coreContext.postOnCoreThread { core -> + core.nativePreviewWindowId = null + core.isVideoPreviewEnabled = false + } + + super.onPause() + } + + private fun isCameraPermissionGranted(): Boolean { + val granted = ContextCompat.checkSelfPermission( + requireContext(), + Manifest.permission.CAMERA + ) == PackageManager.PERMISSION_GRANTED + Log.i("$TAG Camera permission is ${if (granted) "granted" else "denied"}") + return granted + } + + private fun enableVideoPreview() { + coreContext.postOnCoreThread { core -> + if (core.isVideoEnabled) { + viewModel.isVideoAvailable.postValue(true) + core.nativePreviewWindowId = binding.videoPreview + core.isVideoPreviewEnabled = true + } + } + } +} diff --git a/app/src/main/java/org/linphone/ui/main/meetings/fragment/MeetingsFragment.kt b/app/src/main/java/org/linphone/ui/main/meetings/fragment/MeetingsFragment.kt index c0f89095d..e0e03f452 100644 --- a/app/src/main/java/org/linphone/ui/main/meetings/fragment/MeetingsFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/meetings/fragment/MeetingsFragment.kt @@ -99,7 +99,18 @@ class MeetingsFragment : GenericFragment() { sharedViewModel.showScheduleMeetingEvent.observe(viewLifecycleOwner) { it.consume { Log.i("$TAG Navigating to schedule meeting fragment") - findNavController().navigate(R.id.action_global_scheduleMeetingFragment) + val action = MeetingsFragmentDirections.actionMeetingsFragmentToScheduleMeetingFragment() + findNavController().navigate(action) + } + } + + sharedViewModel.goToMeetingWaitingRoomEvent.observe(viewLifecycleOwner) { + it.consume { uri -> + Log.i("$TAG Navigating to meeting waiting room fragment with URI [$uri]") + val action = MeetingsFragmentDirections.actionMeetingsFragmentToMeetingWaitingRoomFragment( + uri + ) + findNavController().navigate(action) } } 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 ce50cd9a9..b097aebb7 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 @@ -75,9 +75,7 @@ class MeetingsListFragment : AbstractTopBarFragment() { val headerItemDecoration = RecyclerViewHeaderDecoration(requireContext(), adapter, true) binding.meetingsList.addItemDecoration(headerItemDecoration) - - val layoutManager = LinearLayoutManager(requireContext()) - binding.meetingsList.layoutManager = layoutManager + binding.meetingsList.layoutManager = LinearLayoutManager(requireContext()) binding.setNewMeetingClicked { sharedViewModel.showScheduleMeetingEvent.value = Event(true) diff --git a/app/src/main/java/org/linphone/ui/main/meetings/viewmodel/MeetingViewModel.kt b/app/src/main/java/org/linphone/ui/main/meetings/viewmodel/MeetingViewModel.kt index 0bc31adfa..e1acada39 100644 --- a/app/src/main/java/org/linphone/ui/main/meetings/viewmodel/MeetingViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/meetings/viewmodel/MeetingViewModel.kt @@ -91,23 +91,21 @@ class MeetingViewModel @UiThread constructor() : ViewModel() { if (address != null) { val found = core.findConferenceInformationFromUri(address) if (found != null) { + Log.i("$TAG Conference info with SIP URI [$uri] was found") conferenceInfo = found configureConferenceInfo() conferenceInfoFoundEvent.postValue(Event(true)) } else { + Log.e("$TAG Conference info with SIP URI [$uri] couldn't be found!") conferenceInfoFoundEvent.postValue(Event(false)) } } else { + Log.e("$TAG Failed to parse SIP URI [$uri] as Address!") conferenceInfoFoundEvent.postValue(Event(false)) } } } - @UiThread - fun join() { - // TODO - } - @UiThread fun delete() { coreContext.postOnCoreThread { core -> @@ -181,7 +179,7 @@ class MeetingViewModel @UiThread constructor() : ViewModel() { val participant = info.address val isOrganizer = organizer?.weakEqual(participant) ?: false Log.i( - "$TAG Conference [${subject.value}] ${if (isOrganizer) "organizer" else "participant"} [${participant.asStringUriOnly()}] is a [${info.role}]" + "$TAG Conference [${conferenceInfo.subject}] ${if (isOrganizer) "organizer" else "participant"} [${participant.asStringUriOnly()}] is a [${info.role}]" ) if (isOrganizer) { organizerFound = true 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 new file mode 100644 index 000000000..d9b06697b --- /dev/null +++ b/app/src/main/java/org/linphone/ui/main/meetings/viewmodel/MeetingWaitingRoomViewModel.kt @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2010-2023 Belledonne Communications SARL. + * + * This file is part of linphone-android + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.linphone.ui.main.meetings.viewmodel + +import androidx.annotation.UiThread +import androidx.annotation.WorkerThread +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import org.linphone.LinphoneApplication.Companion.coreContext +import org.linphone.core.ConferenceInfo +import org.linphone.core.Factory +import org.linphone.core.tools.Log +import org.linphone.ui.main.contacts.model.ContactAvatarModel +import org.linphone.utils.Event +import org.linphone.utils.LinphoneUtils +import org.linphone.utils.TimestampUtils + +class MeetingWaitingRoomViewModel @UiThread constructor() : ViewModel() { + companion object { + private const val TAG = "[Meeting Waiting Room ViewModel]" + } + + val subject = MutableLiveData() + + val dateTime = MutableLiveData() + + val selfAvatar = MutableLiveData() + + val isMicrophoneMuted = MutableLiveData() + + val isVideoAvailable = MutableLiveData() + + val isVideoEnabled = MutableLiveData() + + val isSwitchCameraAvailable = MutableLiveData() + + val conferenceInfoFoundEvent = MutableLiveData>() + + private lateinit var conferenceInfo: ConferenceInfo + + @UiThread + override fun onCleared() { + super.onCleared() + } + + @UiThread + fun findConferenceInfo(uri: String) { + coreContext.postOnCoreThread { core -> + val address = Factory.instance().createAddress(uri) + if (address != null) { + val found = core.findConferenceInformationFromUri(address) + if (found != null) { + Log.i("$TAG Conference info with SIP URI [$uri] was found") + conferenceInfo = found + configureConferenceInfo() + configureWaitingRoom() + conferenceInfoFoundEvent.postValue(Event(true)) + } else { + Log.e("$TAG Conference info with SIP URI [$uri] couldn't be found!") + conferenceInfoFoundEvent.postValue(Event(false)) + } + } else { + Log.e("$TAG Failed to parse SIP URI [$uri] as Address!") + conferenceInfoFoundEvent.postValue(Event(false)) + } + } + } + + @UiThread + fun setFrontCamera() { + coreContext.postOnCoreThread { core -> + for (camera in core.videoDevicesList) { + if (camera.contains("Front")) { + Log.i("$TAG Found front facing camera [$camera], using it") + coreContext.core.videoDevice = camera + return@postOnCoreThread + } + } + + val first = core.videoDevicesList.firstOrNull() + if (first != null) { + Log.w("$TAG No front facing camera found, using first one available [$first]") + coreContext.core.videoDevice = first + } + } + } + + @UiThread + fun join() { + coreContext.postOnCoreThread { core -> + if (::conferenceInfo.isInitialized) { + val conferenceUri = conferenceInfo.uri + if (conferenceUri == null) { + Log.e("$TAG Conference Info doesn't have a conference SIP URI to call!") + return@postOnCoreThread + } + + val params = core.createCallParams(null) + params ?: return@postOnCoreThread + + params.isVideoEnabled = isVideoEnabled.value == true + params.isMicEnabled = isMicrophoneMuted.value == false + params.account = core.defaultAccount + coreContext.startCall(conferenceUri, params) + } + } + } + + @UiThread + fun switchCamera() { + coreContext.postOnCoreThread { + Log.i("$TAG Switching camera") + coreContext.switchCamera() + } + } + + @UiThread + fun toggleVideo() { + isVideoEnabled.value = isVideoEnabled.value == false + } + + @UiThread + fun toggleMuteMicrophone() { + isMicrophoneMuted.value = isMicrophoneMuted.value == false + } + + @UiThread + fun toggleSpeaker() { + // TODO + } + + @WorkerThread + private fun configureConferenceInfo() { + if (::conferenceInfo.isInitialized) { + subject.postValue(conferenceInfo.subject) + + val timestamp = conferenceInfo.dateTime + val duration = conferenceInfo.duration + val date = TimestampUtils.toString( + timestamp, + onlyDate = true, + shortDate = false, + hideYear = false + ) + val startTime = TimestampUtils.timeToString(timestamp) + val end = timestamp + (duration * 60) + val endTime = TimestampUtils.timeToString(end) + dateTime.postValue("$date | $startTime - $endTime") + + val localAddress = coreContext.core.defaultAccount?.params?.identityAddress + val fakeFriend = coreContext.core.createFriend() + fakeFriend.address = localAddress + fakeFriend.name = LinphoneUtils.getDisplayName(localAddress) + val avatarModel = ContactAvatarModel(fakeFriend) + selfAvatar.postValue(avatarModel) + } + } + + @WorkerThread + private fun configureWaitingRoom() { + val core = coreContext.core + + isVideoEnabled.postValue( + core.isVideoEnabled && core.videoActivationPolicy.automaticallyInitiate + ) + isSwitchCameraAvailable.postValue(coreContext.showSwitchCameraButton()) + + isMicrophoneMuted.postValue(!core.isMicEnabled) + + // TODO: audio routes + } +} diff --git a/app/src/main/java/org/linphone/ui/main/settings/fragment/AccountSettingsFragment.kt b/app/src/main/java/org/linphone/ui/main/settings/fragment/AccountSettingsFragment.kt index 59ab0f426..64d83ae13 100644 --- a/app/src/main/java/org/linphone/ui/main/settings/fragment/AccountSettingsFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/settings/fragment/AccountSettingsFragment.kt @@ -48,8 +48,7 @@ class AccountSettingsFragment : GenericFragment() { } override fun goBack(): Boolean { - findNavController().popBackStack() - return true + return findNavController().popBackStack() } override fun onCreateView( 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 d2cc5a364..dca355c74 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 @@ -123,6 +123,10 @@ class SharedMainViewModel @UiThread constructor() : ViewModel() { MutableLiveData>() } + val goToMeetingWaitingRoomEvent: MutableLiveData> by lazy { + MutableLiveData>() + } + /* Other */ val listOfSelectedSipUrisEvent: MutableLiveData>> by lazy { diff --git a/app/src/main/res/drawable/shape_squircle_gray_2_background.xml b/app/src/main/res/drawable/shape_squircle_gray_100_background.xml similarity index 100% rename from app/src/main/res/drawable/shape_squircle_gray_2_background.xml rename to app/src/main/res/drawable/shape_squircle_gray_100_background.xml diff --git a/app/src/main/res/drawable/shape_squircle_gray_600_background.xml b/app/src/main/res/drawable/shape_squircle_gray_600_background.xml new file mode 100644 index 000000000..deffe1a5e --- /dev/null +++ b/app/src/main/res/drawable/shape_squircle_gray_600_background.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/account_profile_device_list_cell.xml b/app/src/main/res/layout/account_profile_device_list_cell.xml index b76e21414..94ff83b0b 100644 --- a/app/src/main/res/layout/account_profile_device_list_cell.xml +++ b/app/src/main/res/layout/account_profile_device_list_cell.xml @@ -15,7 +15,7 @@ android:layout_height="wrap_content" android:layout_marginTop="15dp" android:paddingBottom="27dp" - android:background="@drawable/shape_squircle_gray_2_background"> + android:background="@drawable/shape_squircle_gray_100_background"> + @@ -198,7 +201,6 @@ android:layout_height="1dp" android:layout_marginTop="16dp" android:background="@color/gray_main2_200" - android:visibility="@{viewModel.description.length() > 0 ? View.VISIBLE : View.GONE}" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@id/timezone" /> @@ -230,10 +232,18 @@ android:layout_height="1dp" android:layout_marginTop="16dp" android:background="@color/gray_main2_200" + android:visibility="@{viewModel.description.length() > 0 ? View.VISIBLE : View.GONE}" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@id/description" /> + + + app:layout_constraintTop_toBottomOf="@id/speakers_bottom_barrier" /> + + + app:layout_constraintTop_toBottomOf="@id/participants_bottom_barrier" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/main_nav_graph.xml b/app/src/main/res/navigation/main_nav_graph.xml index 4154193d0..b5f6862ba 100644 --- a/app/src/main/res/navigation/main_nav_graph.xml +++ b/app/src/main/res/navigation/main_nav_graph.xml @@ -29,6 +29,12 @@ app:launchSingleTop="true" app:popUpTo="@id/contactsFragment" app:popUpToInclusive="true" /> + + - - - - + - - + + - + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 07525e52c..6f4c0e9fc 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -394,6 +394,8 @@ Delete meeting Meeting has been deleted + Join + Operation in progress, please wait Transfer