Fixed time picker in conference scheduler + added time zone picker in schedule meeting UI

This commit is contained in:
Sylvain Berfini 2024-07-08 14:43:40 +02:00
parent 5289dc4824
commit 5ae345e794
9 changed files with 230 additions and 41 deletions

View file

@ -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]")
}
}
}

View file

@ -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)
}
}

View file

@ -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)
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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<TimeZoneModel> {
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)
}
}

View file

@ -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<String>()
val timezone = MutableLiveData<String>()
val availableTimeZones: List<TimeZoneModel> = TimeZone.getAvailableIDs().map { id ->
TimeZoneModel(TimeZone.getTimeZone(id))
}.toList().sorted()
var selectedTimeZone = MutableLiveData<TimeZoneModel>()
val sendInvitations = MutableLiveData<Boolean>()
@ -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<SelectedAddressModel>()
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()
}
}
)
)
}
}

View file

@ -195,13 +195,13 @@
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style_700"
android:id="@+id/timezone"
android:id="@+id/timezone_label"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="26dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:text="@{viewModel.timezone, default=@string/meeting_schedule_timezone_title}"
android:text="@string/meeting_schedule_timezone_title"
android:textColor="?attr/color_main2_600"
android:textSize="14sp"
android:maxLines="1"
@ -213,6 +213,38 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/to_date" />
<androidx.appcompat.widget.AppCompatSpinner
style="@style/default_text_style"
android:id="@+id/timezone_picker"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_marginEnd="16dp"
android:layout_marginTop="5dp"
android:paddingStart="20dp"
android:paddingEnd="20dp"
android:textSize="14sp"
android:textColor="@color/gray_main2_600"
android:gravity="center_vertical"
android:overlapAnchor="false"
android:dropDownVerticalOffset="25dp"
android:spinnerMode="dropdown"
android:popupBackground="@drawable/shape_squircle_white_background"
android:background="@drawable/edit_text_background"
app:layout_constraintTop_toBottomOf="@id/timezone_label"
app:layout_constraintStart_toStartOf="@id/timezone_label"
app:layout_constraintEnd_toEndOf="parent" />
<ImageView
android:id="@+id/timezone_picker_caret"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:src="@drawable/caret_down"
android:contentDescription="@null"
app:layout_constraintTop_toTopOf="@id/timezone_picker"
app:layout_constraintBottom_toBottomOf="@id/timezone_picker"
app:layout_constraintEnd_toEndOf="@id/timezone_picker"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style_700"
android:id="@+id/repeat"
@ -229,7 +261,7 @@
android:drawableTint="?attr/color_main2_600"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/timezone" />
app:layout_constraintTop_toBottomOf="@id/timezone_picker" />
<View
android:id="@+id/separator_2"

View file

@ -339,13 +339,13 @@
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style_700"
android:id="@+id/timezone"
android:id="@+id/timezone_label"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:text="@{viewModel.timezone, default=@string/meeting_schedule_timezone_title}"
android:text="@string/meeting_schedule_timezone_title"
android:textColor="?attr/color_main2_600"
android:textSize="14sp"
android:maxLines="1"
@ -357,6 +357,38 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/all_day_switch" />
<androidx.appcompat.widget.AppCompatSpinner
style="@style/default_text_style"
android:id="@+id/timezone_picker"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_marginEnd="16dp"
android:layout_marginTop="5dp"
android:paddingStart="20dp"
android:paddingEnd="20dp"
android:textSize="14sp"
android:textColor="@color/gray_main2_600"
android:gravity="center_vertical"
android:overlapAnchor="false"
android:dropDownVerticalOffset="25dp"
android:spinnerMode="dropdown"
android:popupBackground="@drawable/shape_squircle_white_background"
android:background="@drawable/edit_text_background"
app:layout_constraintTop_toBottomOf="@id/timezone_label"
app:layout_constraintStart_toStartOf="@id/timezone_label"
app:layout_constraintEnd_toEndOf="parent" />
<ImageView
android:id="@+id/timezone_picker_caret"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:src="@drawable/caret_down"
android:contentDescription="@null"
app:layout_constraintTop_toTopOf="@id/timezone_picker"
app:layout_constraintBottom_toBottomOf="@id/timezone_picker"
app:layout_constraintEnd_toEndOf="@id/timezone_picker"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style_700"
android:id="@+id/repeat"
@ -373,7 +405,7 @@
android:drawableTint="?attr/color_main2_600"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/timezone" />
app:layout_constraintTop_toBottomOf="@id/timezone_picker" />
<View
android:id="@+id/separator_2"

View file

@ -508,14 +508,14 @@
<string name="meeting_schedule_title">Nouvelle réunion</string>
<string name="meeting_schedule_meeting_label">Réunion</string>
<string name="meeting_schedule_broadcast_label">Webinar</string>
<string name="meeting_schedule_broadcast_help">Informations sur le mode Webinar. <u>En savoir plus</u></string>
<string name="meeting_schedule_broadcast_help">Informations sur le mode Webinar.\n<u>En savoir plus</u></string>
<string name="meeting_schedule_subject_hint">Ajouter un titre…</string>
<string name="meeting_schedule_date_all_day_title">Toute la journée</string>
<string name="meeting_schedule_pick_start_date_title">Date de début</string>
<string name="meeting_schedule_pick_end_date_title">Date de fin</string>
<string name="meeting_schedule_pick_start_time_title">Heure de début</string>
<string name="meeting_schedule_pick_end_time_title">Heure de fin</string>
<string name="meeting_schedule_timezone_title">Fuseau horaire : %s</string>
<string name="meeting_schedule_timezone_title">Fuseau horaire</string>
<string name="meeting_schedule_one_time_label">Une seule fois</string>
<string name="meeting_schedule_description_hint">Ajouter une description</string>
<string name="meeting_schedule_add_participants_title">Ajouter des participants</string>

View file

@ -545,14 +545,14 @@
<string name="meeting_schedule_title">New meeting</string>
<string name="meeting_schedule_meeting_label">Meeting</string>
<string name="meeting_schedule_broadcast_label">Broadcast</string>
<string name="meeting_schedule_broadcast_help">Info about broadcast. <u>Learn more</u></string>
<string name="meeting_schedule_broadcast_help">Info about broadcast.\n<u>Learn more</u></string>
<string name="meeting_schedule_subject_hint">Add title…</string>
<string name="meeting_schedule_date_all_day_title">All day</string>
<string name="meeting_schedule_pick_start_date_title">Choose the start date</string>
<string name="meeting_schedule_pick_end_date_title">Choose the end date</string>
<string name="meeting_schedule_pick_start_time_title">Choose the start time</string>
<string name="meeting_schedule_pick_end_time_title">Choose the end time</string>
<string name="meeting_schedule_timezone_title">Timezone: %s</string>
<string name="meeting_schedule_timezone_title">Timezone</string>
<string name="meeting_schedule_one_time_label">One time</string>
<string name="meeting_schedule_description_hint">Add description</string>
<string name="meeting_schedule_add_participants_title">Add participants</string>