From 5ae345e794a22b4ac1e2507f025265107231077f Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Mon, 8 Jul 2024 14:43:40 +0200 Subject: [PATCH] Fixed time picker in conference scheduler + added time zone picker in schedule meeting UI --- .../java/org/linphone/core/CoreContext.kt | 3 + .../meetings/fragment/EditMeetingFragment.kt | 31 +++++++++ .../fragment/ScheduleMeetingFragment.kt | 31 +++++++++ .../ui/main/meetings/model/TimeZoneModel.kt | 56 ++++++++++++++++ .../viewmodel/ScheduleMeetingViewModel.kt | 66 ++++++++++--------- .../main/res/layout/meeting_edit_fragment.xml | 38 ++++++++++- .../res/layout/meeting_schedule_fragment.xml | 38 ++++++++++- app/src/main/res/values-fr/strings.xml | 4 +- app/src/main/res/values/strings.xml | 4 +- 9 files changed, 230 insertions(+), 41 deletions(-) create mode 100644 app/src/main/java/org/linphone/ui/main/meetings/model/TimeZoneModel.kt diff --git a/app/src/main/java/org/linphone/core/CoreContext.kt b/app/src/main/java/org/linphone/core/CoreContext.kt index 360bdbee9..71b7f9321 100644 --- a/app/src/main/java/org/linphone/core/CoreContext.kt +++ b/app/src/main/java/org/linphone/core/CoreContext.kt @@ -324,6 +324,9 @@ class CoreContext @UiThread constructor(val context: Context) : HandlerThread("C AuthMethod.Tls -> { Log.w("$TAG Authentication requested method is TLS, not doing anything...") } + else -> { + Log.w("$TAG Unexpected authentication request method [$method]") + } } } 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 index 74593d171..720eaf886 100644 --- 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 @@ -24,6 +24,8 @@ import android.text.format.DateFormat import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.AdapterView +import android.widget.ArrayAdapter import androidx.annotation.UiThread import androidx.lifecycle.ViewModelProvider import androidx.navigation.fragment.findNavController @@ -53,6 +55,17 @@ class EditMeetingFragment : SlidingPaneChildFragment() { private val args: EditMeetingFragmentArgs by navArgs() + private val timeZonePickerListener = object : AdapterView.OnItemSelectedListener { + override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { + val timeZone = viewModel.availableTimeZones[position] + Log.i("$TAG Selected time zone is now [$timeZone] at index [$position]") + viewModel.updateTimeZone(timeZone) + } + + override fun onNothingSelected(parent: AdapterView<*>?) { + } + } + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -195,5 +208,23 @@ class EditMeetingFragment : SlidingPaneChildFragment() { viewModel.setParticipants(list) } } + + setupTimeZonePicker() + } + + private fun setupTimeZonePicker() { + val timeZoneIndex = viewModel.availableTimeZones.indexOf(viewModel.selectedTimeZone.value) + Log.i("$TAG Setting default time zone at index [$timeZoneIndex]") + val adapter = ArrayAdapter( + requireContext(), + R.layout.drop_down_item, + viewModel.availableTimeZones + ) + adapter.setDropDownViewResource( + R.layout.generic_dropdown_cell + ) + binding.timezonePicker.adapter = adapter + binding.timezonePicker.onItemSelectedListener = timeZonePickerListener + binding.timezonePicker.setSelection(if (timeZoneIndex == -1) 0 else timeZoneIndex) } } 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 e5df970fe..47092be80 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 @@ -24,6 +24,8 @@ import android.text.format.DateFormat import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.AdapterView +import android.widget.ArrayAdapter import androidx.annotation.UiThread import androidx.lifecycle.ViewModelProvider import androidx.navigation.fragment.findNavController @@ -53,6 +55,17 @@ class ScheduleMeetingFragment : GenericMainFragment() { private val args: ScheduleMeetingFragmentArgs by navArgs() + private val timeZonePickerListener = object : AdapterView.OnItemSelectedListener { + override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { + val timeZone = viewModel.availableTimeZones[position] + Log.i("$TAG Selected time zone is now [$timeZone] at index [$position]") + viewModel.updateTimeZone(timeZone) + } + + override fun onNothingSelected(parent: AdapterView<*>?) { + } + } + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -195,5 +208,23 @@ class ScheduleMeetingFragment : GenericMainFragment() { viewModel.setParticipants(list) } } + + setupTimeZonePicker() + } + + private fun setupTimeZonePicker() { + val timeZoneIndex = viewModel.availableTimeZones.indexOf(viewModel.selectedTimeZone.value) + Log.i("$TAG Setting default time zone at index [$timeZoneIndex]") + val adapter = ArrayAdapter( + requireContext(), + R.layout.drop_down_item, + viewModel.availableTimeZones + ) + adapter.setDropDownViewResource( + R.layout.generic_dropdown_cell + ) + binding.timezonePicker.adapter = adapter + binding.timezonePicker.onItemSelectedListener = timeZonePickerListener + binding.timezonePicker.setSelection(if (timeZoneIndex == -1) 0 else timeZoneIndex) } } diff --git a/app/src/main/java/org/linphone/ui/main/meetings/model/TimeZoneModel.kt b/app/src/main/java/org/linphone/ui/main/meetings/model/TimeZoneModel.kt new file mode 100644 index 000000000..0db61373b --- /dev/null +++ b/app/src/main/java/org/linphone/ui/main/meetings/model/TimeZoneModel.kt @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2010-2024 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.model + +import androidx.annotation.UiThread +import java.util.TimeZone +import java.util.concurrent.TimeUnit +import kotlin.math.abs + +class TimeZoneModel @UiThread constructor(timeZone: TimeZone) : Comparable { + val id: String = timeZone.id + + private val hours: Long = TimeUnit.MILLISECONDS.toHours(timeZone.rawOffset.toLong()) + + private val minutes: Long = abs( + TimeUnit.MILLISECONDS.toMinutes(timeZone.rawOffset.toLong()) - + TimeUnit.HOURS.toMinutes(hours) + ) + + private val gmt: String = if (hours >= 0) { + String.format("GMT+%02d:%02d - %s", hours, minutes, timeZone.id) + } else { + String.format("GMT%02d:%02d - %s", hours, minutes, timeZone.id) + } + + override fun toString(): String { + return gmt + } + + override fun compareTo(other: TimeZoneModel): Int { + if (hours == other.hours) { + if (minutes == other.minutes) { + return id.compareTo(other.id) + } + return minutes.compareTo(other.minutes) + } + return hours.compareTo(other.hours) + } +} 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 7a3ebac7b..909d5b6fe 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 @@ -24,7 +24,6 @@ import androidx.annotation.UiThread import androidx.annotation.WorkerThread import androidx.lifecycle.MutableLiveData import java.util.Calendar -import java.util.Locale import java.util.TimeZone import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.LinphoneApplication.Companion.corePreferences @@ -39,8 +38,8 @@ import org.linphone.core.Participant import org.linphone.core.ParticipantInfo import org.linphone.core.tools.Log import org.linphone.ui.GenericViewModel +import org.linphone.ui.main.meetings.model.TimeZoneModel import org.linphone.ui.main.model.SelectedAddressModel -import org.linphone.utils.AppUtils import org.linphone.utils.Event import org.linphone.utils.TimestampUtils @@ -67,7 +66,10 @@ class ScheduleMeetingViewModel @UiThread constructor() : GenericViewModel() { val toTime = MutableLiveData() - val timezone = MutableLiveData() + val availableTimeZones: List = TimeZone.getAvailableIDs().map { id -> + TimeZoneModel(TimeZone.getTimeZone(id)) + }.toList().sorted() + var selectedTimeZone = MutableLiveData() val sendInvitations = MutableLiveData() @@ -194,8 +196,14 @@ class ScheduleMeetingViewModel @UiThread constructor() : GenericViewModel() { allDayMeeting.value = false sendInvitations.value = true + selectedTimeZone.value = availableTimeZones.find { + it.id == TimeZone.getDefault().id + } + val now = System.currentTimeMillis() - val cal = Calendar.getInstance() + val cal = Calendar.getInstance( + TimeZone.getTimeZone(selectedTimeZone.value?.id ?: TimeZone.getDefault().id) + ) cal.timeInMillis = now cal.add(Calendar.HOUR, 1) cal.set(Calendar.MINUTE, 0) @@ -219,7 +227,6 @@ class ScheduleMeetingViewModel @UiThread constructor() : GenericViewModel() { computeDateLabels() computeTimeLabels() - updateTimezone() } override fun onCleared() { @@ -295,6 +302,12 @@ class ScheduleMeetingViewModel @UiThread constructor() : GenericViewModel() { computeDateLabels() } + @UiThread + fun updateTimeZone(timeZone: TimeZoneModel) { + selectedTimeZone.value = timeZone + computeTimeLabels() + } + @UiThread fun setStartTime(hours: Int, minutes: Int) { startHour = hours @@ -486,19 +499,24 @@ class ScheduleMeetingViewModel @UiThread constructor() : GenericViewModel() { isBroadcastSelected.postValue(false) // TODO FIXME: not implemented yet - startHour = 0 - startMinutes = 0 - endHour = 0 - endMinutes = 0 startTimestamp = conferenceInfo.dateTime * 1000 /* Linphone timestamps are in seconds */ endTimestamp = (conferenceInfo.dateTime + conferenceInfo.duration * 60) * 1000 /* Linphone timestamps are in seconds */ Log.i( "$TAG Loaded start date is [$startTimestamp], loaded end date is [$endTimestamp]" ) + val cal = Calendar.getInstance( + TimeZone.getTimeZone(selectedTimeZone.value?.id ?: TimeZone.getDefault().id) + ) + cal.timeInMillis = startTimestamp + startHour = cal.get(Calendar.HOUR_OF_DAY) + startMinutes = cal.get(Calendar.MINUTE) + cal.timeInMillis = endTimestamp + endHour = cal.get(Calendar.HOUR_OF_DAY) + endMinutes = cal.get(Calendar.MINUTE) + computeDateLabels() computeTimeLabels() - updateTimezone() val list = arrayListOf() for (participant in conferenceInfo.participantInfos) { @@ -554,9 +572,13 @@ class ScheduleMeetingViewModel @UiThread constructor() : GenericViewModel() { @AnyThread private fun computeTimeLabels() { - val cal = Calendar.getInstance() + val timeZoneId = selectedTimeZone.value?.id ?: TimeZone.getDefault().id + Log.i("$TAG Updating timestamps using time zone [${selectedTimeZone.value}]($timeZoneId)") + val cal = Calendar.getInstance( + TimeZone.getTimeZone(timeZoneId) + ) cal.timeInMillis = startTimestamp - if (startHour != 0 && startMinutes != 0) { + if (startHour != -1 && startMinutes != -1) { cal.set(Calendar.HOUR_OF_DAY, startHour) cal.set(Calendar.MINUTE, startMinutes) } @@ -565,7 +587,7 @@ class ScheduleMeetingViewModel @UiThread constructor() : GenericViewModel() { fromTime.postValue(start) cal.timeInMillis = endTimestamp - if (endHour != 0 && endMinutes != 0) { + if (endHour != -1 && endMinutes != -1) { cal.set(Calendar.HOUR_OF_DAY, endHour) cal.set(Calendar.MINUTE, endMinutes) } @@ -573,22 +595,4 @@ class ScheduleMeetingViewModel @UiThread constructor() : GenericViewModel() { Log.i("$TAG Computed end time for timestamp [$endTimestamp] is [$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 index ec81464e5..2814699f0 100644 --- a/app/src/main/res/layout/meeting_edit_fragment.xml +++ b/app/src/main/res/layout/meeting_edit_fragment.xml @@ -195,13 +195,13 @@ + + + + + app:layout_constraintTop_toBottomOf="@id/timezone_picker" /> + + + + + app:layout_constraintTop_toBottomOf="@id/timezone_picker" /> Nouvelle réunion Réunion Webinar - Informations sur le mode Webinar. En savoir plus + Informations sur le mode Webinar.\nEn savoir plus Ajouter un titre… Toute la journée Date de début Date de fin Heure de début Heure de fin - Fuseau horaire : %s + Fuseau horaire Une seule fois Ajouter une description Ajouter des participants diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3dad41f6c..0bf997505 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -545,14 +545,14 @@ New meeting Meeting Broadcast - Info about broadcast. Learn more + Info about broadcast.\nLearn more Add title… All day Choose the start date Choose the end date Choose the start time Choose the end time - Timezone: %s + Timezone One time Add description Add participants