diff --git a/app/src/main/java/org/linphone/core/CoreContext.kt b/app/src/main/java/org/linphone/core/CoreContext.kt index dfdb54d0c..554066080 100644 --- a/app/src/main/java/org/linphone/core/CoreContext.kt +++ b/app/src/main/java/org/linphone/core/CoreContext.kt @@ -121,16 +121,16 @@ class CoreContext @UiThread constructor(val context: Context) : HandlerThread("C @WorkerThread override fun onConfiguringStatus( core: Core, - status: Config.ConfiguringState?, + status: ConfiguringState?, message: String? ) { Log.i("$TAG Configuring state changed [$status]") - if (status == Config.ConfiguringState.Successful) { + if (status == ConfiguringState.Successful) { val text = context.getString( org.linphone.R.string.assistant_qr_code_provisioning_done ) greenToastToShowEvent.postValue(Event(Pair(text, org.linphone.R.drawable.smiley))) - } else if (status == Config.ConfiguringState.Failed) { + } else if (status == ConfiguringState.Failed) { val text = context.getString( org.linphone.R.string.assistant_qr_code_provisioning_done ) 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 6f48c1182..3f9d8dc4b 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 @@ -211,8 +211,12 @@ class ConversationInfoFragment : SlidingPaneChildFragment() { } binding.setAddParticipantsClickListener { - val action = ConversationInfoFragmentDirections.actionConversationInfoFragmentToAddParticipantsFragment() - findNavController().navigate(action) + if (findNavController().currentDestination?.id == R.id.conversationInfoFragment) { + Log.i("$TAG Going into participant picker fragment") + val action = + ConversationInfoFragmentDirections.actionConversationInfoFragmentToAddParticipantsFragment() + findNavController().navigate(action) + } } binding.setEditSubjectClickListener { 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 d5b6f21a1..f3eb0534d 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 @@ -581,7 +581,7 @@ class MessageModel @WorkerThread constructor( hideYear = false ) val startTime = TimestampUtils.timeToString(timestamp) - val end = timestamp + (duration * 60) + val end = timestamp + duration val endTime = TimestampUtils.timeToString(end) meetingDate.postValue(date) meetingTime.postValue("$startTime - $endTime") diff --git a/app/src/main/java/org/linphone/ui/main/meetings/fragment/EditMeetingFragment.kt b/app/src/main/java/org/linphone/ui/main/meetings/fragment/EditMeetingFragment.kt new file mode 100644 index 000000000..0034270fd --- /dev/null +++ b/app/src/main/java/org/linphone/ui/main/meetings/fragment/EditMeetingFragment.kt @@ -0,0 +1,184 @@ +/* + * 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.os.Bundle +import android.text.format.DateFormat +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.annotation.UiThread +import androidx.lifecycle.ViewModelProvider +import androidx.navigation.fragment.findNavController +import androidx.navigation.fragment.navArgs +import com.google.android.material.datepicker.CalendarConstraints +import com.google.android.material.datepicker.DateValidatorPointForward +import com.google.android.material.datepicker.MaterialDatePicker +import com.google.android.material.timepicker.MaterialTimePicker +import com.google.android.material.timepicker.TimeFormat +import org.linphone.R +import org.linphone.core.tools.Log +import org.linphone.databinding.MeetingEditFragmentBinding +import org.linphone.ui.main.fragment.SlidingPaneChildFragment +import org.linphone.ui.main.meetings.viewmodel.ScheduleMeetingViewModel +import org.linphone.utils.Event + +@UiThread +class EditMeetingFragment : SlidingPaneChildFragment() { + companion object { + private const val TAG = "[Edit Meeting Fragment]" + } + + private lateinit var binding: MeetingEditFragmentBinding + + private lateinit var viewModel: ScheduleMeetingViewModel + + private val args: EditMeetingFragmentArgs by navArgs() + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + binding = MeetingEditFragmentBinding.inflate(layoutInflater) + return binding.root + } + + override fun goBack(): Boolean { + return findNavController().popBackStack() + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.lifecycleOwner = viewLifecycleOwner + + viewModel = ViewModelProvider(this)[ScheduleMeetingViewModel::class.java] + binding.viewModel = viewModel + + val conferenceUri = args.conferenceUri + Log.i("$TAG Found conference URI [$conferenceUri] in arguments") + viewModel.loadExistingConferenceInfoFromUri(conferenceUri) + + binding.setBackClickListener { + goBack() + } + + binding.setPickStartDateClickListener { + val constraintsBuilder = + CalendarConstraints.Builder() + .setValidator(DateValidatorPointForward.now()) + val picker = + MaterialDatePicker.Builder.datePicker() + .setCalendarConstraints(constraintsBuilder.build()) + .setTitleText(R.string.meeting_schedule_pick_start_date_title) + .setSelection(viewModel.getCurrentlySelectedStartDate()) + .build() + picker.addOnPositiveButtonClickListener { + val selection = picker.selection + if (selection != null) { + viewModel.setStartDate(selection) + } + } + picker.show(parentFragmentManager, "Start date picker") + } + + binding.setPickEndDateClickListener { + val constraintsBuilder = + CalendarConstraints.Builder() + .setValidator( + DateValidatorPointForward.from(viewModel.getCurrentlySelectedStartDate()) + ) + val picker = + MaterialDatePicker.Builder.datePicker() + .setCalendarConstraints(constraintsBuilder.build()) + .setTitleText(R.string.meeting_schedule_pick_end_date_title) + .setSelection(viewModel.getCurrentlySelectedEndDate()) + .build() + picker.addOnPositiveButtonClickListener { + val selection = picker.selection + if (selection != null) { + viewModel.setEndDate(selection) + } + } + picker.show(parentFragmentManager, "End date picker") + } + + binding.setPickStartTimeClickListener { + val isSystem24Hour = DateFormat.is24HourFormat(requireContext()) + val clockFormat = if (isSystem24Hour) TimeFormat.CLOCK_24H else TimeFormat.CLOCK_12H + val picker = + MaterialTimePicker.Builder() + .setTimeFormat(clockFormat) + .setTitleText(R.string.meeting_schedule_pick_start_time_title) + .setHour(viewModel.startHour) + .setMinute(viewModel.startMinutes) + .build() + picker.addOnPositiveButtonClickListener { + viewModel.setStartTime(picker.hour, picker.minute) + } + picker.show(parentFragmentManager, "Start time picker") + } + + binding.setPickEndTimeClickListener { + val isSystem24Hour = DateFormat.is24HourFormat( + requireContext() + ) + val clockFormat = if (isSystem24Hour) TimeFormat.CLOCK_24H else TimeFormat.CLOCK_12H + val picker = + MaterialTimePicker.Builder() + .setTimeFormat(clockFormat) + .setTitleText(R.string.meeting_schedule_pick_end_time_title) + .setHour(viewModel.endHour) + .setMinute(viewModel.endMinutes) + .build() + picker.addOnPositiveButtonClickListener { + viewModel.setEndTime(picker.hour, picker.minute) + } + picker.show(parentFragmentManager, "End time picker") + } + + binding.setPickParticipantsClickListener { + if (findNavController().currentDestination?.id == R.id.editMeetingFragment) { + Log.i("$TAG Going into participant picker fragment") + val action = + EditMeetingFragmentDirections.actionEditMeetingFragmentToAddParticipantsFragment() + findNavController().navigate(action) + } + } + + viewModel.conferenceCreatedEvent.observe(viewLifecycleOwner) { + it.consume { + Log.i("$TAG Conference was scheduled, leaving fragment and ask list to refresh") + sharedViewModel.forceRefreshMeetingsListEvent.value = Event(true) + goBack() + } + } + + sharedViewModel.listOfSelectedSipUrisEvent.observe(viewLifecycleOwner) { + it.consume { list -> + Log.i( + "$TAG Found [${list.size}] new participants to add to the meeting, let's do it" + ) + viewModel.addParticipants(list) + } + } + } +} 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 e20224e30..78747ae95 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 @@ -95,6 +95,19 @@ class MeetingFragment : SlidingPaneChildFragment() { goBack() } + binding.setEditClickListener { + val conferenceUri = viewModel.sipUri.value.orEmpty() + if (conferenceUri.isNotEmpty()) { + Log.i( + "$TAG Navigating to meeting edit fragment with conference URI [$conferenceUri]" + ) + val action = MeetingFragmentDirections.actionMeetingFragmentToEditMeetingFragment( + conferenceUri + ) + findNavController().navigate(action) + } + } + binding.setShareClickListener { copyMeetingAddressIntoClipboard(uri) } diff --git a/app/src/main/java/org/linphone/ui/main/meetings/fragment/ScheduleMeetingFragment.kt b/app/src/main/java/org/linphone/ui/main/meetings/fragment/ScheduleMeetingFragment.kt index e81ff3970..2ed9258e2 100644 --- a/app/src/main/java/org/linphone/ui/main/meetings/fragment/ScheduleMeetingFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/meetings/fragment/ScheduleMeetingFragment.kt @@ -158,9 +158,12 @@ class ScheduleMeetingFragment : GenericFragment() { } binding.setPickParticipantsClickListener { - Log.i("$TAG Going into participant picker fragment") - val action = ScheduleMeetingFragmentDirections.actionScheduleMeetingFragmentToAddParticipantsFragment() - findNavController().navigate(action) + if (findNavController().currentDestination?.id == R.id.scheduleMeetingFragment) { + Log.i("$TAG Going into participant picker fragment") + val action = + ScheduleMeetingFragmentDirections.actionScheduleMeetingFragmentToAddParticipantsFragment() + findNavController().navigate(action) + } } viewModel.conferenceCreatedEvent.observe(viewLifecycleOwner) { diff --git a/app/src/main/java/org/linphone/ui/main/meetings/model/MeetingModel.kt b/app/src/main/java/org/linphone/ui/main/meetings/model/MeetingModel.kt index 9e4ee504d..e5f1c8062 100644 --- a/app/src/main/java/org/linphone/ui/main/meetings/model/MeetingModel.kt +++ b/app/src/main/java/org/linphone/ui/main/meetings/model/MeetingModel.kt @@ -49,7 +49,7 @@ class MeetingModel @WorkerThread constructor(private val conferenceInfo: Confere private val startTime = TimestampUtils.timeToString(timestamp) - private val endTime = TimestampUtils.timeToString(timestamp + (conferenceInfo.duration * 60)) + private val endTime = TimestampUtils.timeToString(timestamp + conferenceInfo.duration) val time = "$startTime - $endTime" 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 aede6ba6b..37b2100ae 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 @@ -130,7 +130,7 @@ class MeetingViewModel @UiThread constructor() : ViewModel() { hideYear = false ) val startTime = TimestampUtils.timeToString(timestamp) - val end = timestamp + (duration * 60) + val end = timestamp + duration val endTime = TimestampUtils.timeToString(end) startTimeStamp.postValue(timestamp * 1000) endTimeStamp.postValue(end * 1000) 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 f0b788ebb..51fbdc03d 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 @@ -326,7 +326,7 @@ class MeetingWaitingRoomViewModel @UiThread constructor() : ViewModel() { hideYear = false ) val startTime = TimestampUtils.timeToString(timestamp) - val end = timestamp + (duration * 60) + val end = timestamp + duration val endTime = TimestampUtils.timeToString(end) dateTime.postValue("$date | $startTime - $endTime") 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 28e0d5647..f3e0e6cfd 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 @@ -19,6 +19,7 @@ */ package org.linphone.ui.main.meetings.viewmodel +import androidx.annotation.AnyThread import androidx.annotation.UiThread import androidx.annotation.WorkerThread import androidx.lifecycle.MutableLiveData @@ -30,6 +31,7 @@ import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.R import org.linphone.core.Address import org.linphone.core.ChatRoom +import org.linphone.core.ConferenceInfo import org.linphone.core.ConferenceScheduler import org.linphone.core.ConferenceSchedulerListenerStub import org.linphone.core.Factory @@ -85,6 +87,8 @@ class ScheduleMeetingViewModel @UiThread constructor() : ViewModel() { private lateinit var conferenceScheduler: ConferenceScheduler + private lateinit var conferenceInfoToEdit: ConferenceInfo + private val conferenceSchedulerListener = object : ConferenceSchedulerListenerStub() { @WorkerThread override fun onStateChanged( @@ -99,9 +103,16 @@ class ScheduleMeetingViewModel @UiThread constructor() : ViewModel() { } ConferenceScheduler.State.Ready -> { val conferenceAddress = conferenceScheduler.info?.uri - Log.i( - "$TAG Conference info created, address will be ${conferenceAddress?.asStringUriOnly()}" - ) + if (::conferenceInfoToEdit.isInitialized) { + Log.i( + "$TAG Conference info [${conferenceInfoToEdit.uri?.asStringUriOnly()}] has been updated" + ) + } else { + Log.i( + "$TAG Conference info created, address will be [${conferenceAddress?.asStringUriOnly()}]" + ) + } + if (sendInvitations.value == true) { Log.i("$TAG User asked for invitations to be sent, let's do it") val chatRoomParams = coreContext.core.createDefaultChatRoomParams() @@ -179,19 +190,7 @@ class ScheduleMeetingViewModel @UiThread constructor() : ViewModel() { computeDateLabels() computeTimeLabels() - - timezone.value = AppUtils.getFormattedString( - R.string.meeting_schedule_timezone_title, - TimeZone.getDefault().displayName.replaceFirstChar { - if (it.isLowerCase()) { - it.titlecase( - Locale.getDefault() - ) - } else { - it.toString() - } - } - ) + updateTimezone() } override fun onCleared() { @@ -204,6 +203,64 @@ class ScheduleMeetingViewModel @UiThread constructor() : ViewModel() { } } + @UiThread + fun loadExistingConferenceInfoFromUri(conferenceUri: String) { + coreContext.postOnCoreThread { core -> + val conferenceAddress = core.interpretUrl(conferenceUri, false) + if (conferenceAddress == null) { + Log.e("$TAG Failed to parse conference URI [$conferenceUri], abort") + return@postOnCoreThread + } + + val conferenceInfo = core.findConferenceInformationFromUri(conferenceAddress) + if (conferenceInfo == null) { + Log.e( + "$TAG Failed to find a conference info matching URI [${conferenceAddress.asString()}], abort" + ) + return@postOnCoreThread + } + + conferenceInfoToEdit = conferenceInfo + Log.i( + "$TAG Found conference info matching URI [${conferenceInfo.uri?.asString()}] with subject [${conferenceInfo.subject}]" + ) + subject.postValue(conferenceInfo.subject) + description.postValue(conferenceInfo.description) + + isBroadcastSelected.postValue(false) // TODO FIXME + + startHour = 0 + startMinutes = 0 + endHour = 0 + endMinutes = 0 + startTimestamp = conferenceInfo.dateTime * 1000 /* Linphone timestamps are in seconds */ + endTimestamp = (conferenceInfo.dateTime + conferenceInfo.duration) * 1000 /* Linphone timestamps are in seconds */ + Log.i( + "$TAG Loaded start date is [$startTimestamp], loaded end date is [$endTimestamp]" + ) + computeDateLabels() + computeTimeLabels() + updateTimezone() + + val list = arrayListOf() + for (participant in conferenceInfo.participantInfos) { + val address = participant.address + val avatarModel = coreContext.contactsManager.getContactAvatarModelForAddress( + address + ) + val model = SelectedAddressModel(address, avatarModel) { + // onRemoveFromSelection + } + list.add(model) + Log.i("$TAG Loaded participant [${address.asStringUriOnly()}]") + } + Log.i( + "$TAG [${list.size}] participants loaded from found conference info" + ) + participants.postValue(list) + } + } + @UiThread fun getCurrentlySelectedStartDate(): Long { return startTimestamp @@ -292,6 +349,8 @@ class ScheduleMeetingViewModel @UiThread constructor() : ViewModel() { } } + // TODO FIXME handle speakers when in broadcast mode + @UiThread fun schedule() { coreContext.postOnCoreThread { core -> @@ -339,12 +398,64 @@ class ScheduleMeetingViewModel @UiThread constructor() : ViewModel() { } conferenceScheduler.account = localAccount - // Will trigger the conference creation/update automatically + // Will trigger the conference creation automatically conferenceScheduler.info = conferenceInfo } } @UiThread + fun update() { + coreContext.postOnCoreThread { core -> + Log.i( + "$TAG Updating ${if (isBroadcastSelected.value == true) "broadcast" else "meeting"}" + ) + if (!::conferenceInfoToEdit.isInitialized) { + Log.e("No conference info to edit found!") + return@postOnCoreThread + } + + operationInProgress.postValue(true) + + val conferenceInfo = conferenceInfoToEdit + conferenceInfo.subject = subject.value + conferenceInfo.description = description.value + + val startTime = startTimestamp / 1000 // Linphone expects timestamp in seconds + conferenceInfo.dateTime = startTime + val duration = ((endTimestamp - startTimestamp) / 1000).toInt() // Linphone expects duration in seconds + conferenceInfo.duration = duration + + val participantsList = participants.value.orEmpty() + val participantsInfoList = arrayListOf() + for (participant in participantsList) { + val info = Factory.instance().createParticipantInfo(participant.address) + if (info == null) { + Log.e( + "$TAG Failed to create Participant Info from address [${participant.address.asStringUriOnly()}]" + ) + continue + } + + // For meetings, all participants must have Speaker role + info.role = Participant.Role.Speaker + participantsInfoList.add(info) + } + + val participantsInfoArray = arrayOfNulls(participantsInfoList.size) + participantsInfoList.toArray(participantsInfoArray) + conferenceInfo.setParticipantInfos(participantsInfoArray) + + if (!::conferenceScheduler.isInitialized) { + conferenceScheduler = core.createConferenceScheduler() + conferenceScheduler.addListener(conferenceSchedulerListener) + } + + // Will trigger the conference update automatically + conferenceScheduler.info = conferenceInfo + } + } + + @AnyThread private fun computeDateLabels() { val start = TimestampUtils.toString( startTimestamp, @@ -353,7 +464,7 @@ class ScheduleMeetingViewModel @UiThread constructor() : ViewModel() { shortDate = false, hideYear = false ) - fromDate.value = start + fromDate.postValue(start) Log.i("$TAG Computed start date for timestamp [$startTimestamp] is [$start]") val end = TimestampUtils.toString( @@ -363,25 +474,47 @@ class ScheduleMeetingViewModel @UiThread constructor() : ViewModel() { shortDate = false, hideYear = false ) - toDate.value = end + toDate.postValue(end) Log.i("$TAG Computed end date for timestamp [$endTimestamp] is [$end]") } - @UiThread + @AnyThread private fun computeTimeLabels() { val cal = Calendar.getInstance() cal.timeInMillis = startTimestamp - cal.set(Calendar.HOUR_OF_DAY, startHour) - cal.set(Calendar.MINUTE, startMinutes) + if (startHour != 0 && startMinutes != 0) { + cal.set(Calendar.HOUR_OF_DAY, startHour) + cal.set(Calendar.MINUTE, startMinutes) + } val start = TimestampUtils.timeToString(cal.timeInMillis, timestampInSecs = false) Log.i("$TAG Computed start time for timestamp [$startTimestamp] is [$start]") - fromTime.value = start + fromTime.postValue(start) cal.timeInMillis = endTimestamp - cal.set(Calendar.HOUR_OF_DAY, endHour) - cal.set(Calendar.MINUTE, endMinutes) + if (endHour != 0 && endMinutes != 0) { + cal.set(Calendar.HOUR_OF_DAY, endHour) + cal.set(Calendar.MINUTE, endMinutes) + } val end = TimestampUtils.timeToString(cal.timeInMillis, timestampInSecs = false) Log.i("$TAG Computed end time for timestamp [$endTimestamp] is [$end]") - toTime.value = end + toTime.postValue(end) + } + + @AnyThread + private fun updateTimezone() { + timezone.postValue( + AppUtils.getFormattedString( + R.string.meeting_schedule_timezone_title, + TimeZone.getDefault().displayName.replaceFirstChar { + if (it.isLowerCase()) { + it.titlecase( + Locale.getDefault() + ) + } else { + it.toString() + } + } + ) + ) } } diff --git a/app/src/main/res/layout/meeting_edit_fragment.xml b/app/src/main/res/layout/meeting_edit_fragment.xml new file mode 100644 index 000000000..597b73459 --- /dev/null +++ b/app/src/main/res/layout/meeting_edit_fragment.xml @@ -0,0 +1,415 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/meeting_fragment.xml b/app/src/main/res/layout/meeting_fragment.xml index 588297f22..a023d4508 100644 --- a/app/src/main/res/layout/meeting_fragment.xml +++ b/app/src/main/res/layout/meeting_fragment.xml @@ -7,6 +7,9 @@ + @@ -53,12 +56,13 @@ + app:layout_constraintBottom_toBottomOf="parent"> - - + + diff --git a/app/src/main/res/navigation/meetings_nav_graph.xml b/app/src/main/res/navigation/meetings_nav_graph.xml index d3998b3ee..a0c03d3de 100644 --- a/app/src/main/res/navigation/meetings_nav_graph.xml +++ b/app/src/main/res/navigation/meetings_nav_graph.xml @@ -24,11 +24,42 @@ app:destination="@id/emptyFragment" app:popUpTo="@id/meetingFragment" app:popUpToInclusive="true" /> + + app:launchSingleTop="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 24bbe0cfd..a3905f043 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -489,6 +489,7 @@ Meeting has been deleted Description Create + Edit meeting Join