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 aa73a368e..416bd7754 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 @@ -20,12 +20,19 @@ 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 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.databinding.MeetingScheduleFragmentBinding import org.linphone.ui.main.fragment.GenericFragment import org.linphone.ui.main.meetings.viewmodel.ScheduleMeetingViewModel @@ -73,5 +80,79 @@ class ScheduleMeetingFragment : GenericFragment() { 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") + } } } 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 4d4e4f7d7..f5bf09978 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 @@ -22,6 +22,13 @@ package org.linphone.ui.main.meetings.viewmodel import androidx.annotation.UiThread import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import java.util.Calendar +import java.util.Locale +import java.util.TimeZone +import org.linphone.R +import org.linphone.core.tools.Log +import org.linphone.utils.AppUtils +import org.linphone.utils.TimestampUtils class ScheduleMeetingViewModel @UiThread constructor() : ViewModel() { companion object { @@ -34,9 +41,119 @@ class ScheduleMeetingViewModel @UiThread constructor() : ViewModel() { val showBroadcastHelp = MutableLiveData() + val subject = MutableLiveData() + + val description = MutableLiveData() + + val allDayMeeting = MutableLiveData() + + val fromDate = MutableLiveData() + + val toDate = MutableLiveData() + + val fromTime = MutableLiveData() + + val toTime = MutableLiveData() + + val timezone = MutableLiveData() + + val sendInvitations = MutableLiveData() + + private var startTimestamp = 0L + private var endTimestamp = 0L + + internal var startHour = 0 + internal var startMinutes = 0 + + internal var endHour = 0 + internal var endMinutes = 0 + init { isBroadcastSelected.value = false showBroadcastHelp.value = false + allDayMeeting.value = false + + val now = System.currentTimeMillis() + val cal = Calendar.getInstance() + cal.timeInMillis = now + cal.add(Calendar.HOUR, 1) + cal.set(Calendar.MINUTE, 0) + cal.set(Calendar.SECOND, 0) + val nextFullHour = cal.timeInMillis + startHour = cal.get(Calendar.HOUR_OF_DAY) + startMinutes = 0 + + cal.add(Calendar.HOUR, 1) + val twoHoursLater = cal.timeInMillis + endHour = cal.get(Calendar.HOUR_OF_DAY) + endMinutes = 0 + + startTimestamp = nextFullHour + endTimestamp = twoHoursLater + + Log.i( + "$TAG Default start time is [$startHour:$startMinutes], default end time is [$startHour:$startMinutes]" + ) + Log.i("$TAG Default start date is [$startTimestamp], default end date is [$endTimestamp]") + + 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() + } + } + ) + sendInvitations.value = true + } + + @UiThread + fun getCurrentlySelectedStartDate(): Long { + return startTimestamp + } + + @UiThread + fun setStartDate(timestamp: Long) { + startTimestamp = timestamp + endTimestamp = timestamp + computeDateLabels() + } + + @UiThread + fun getCurrentlySelectedEndDate(): Long { + return endTimestamp + } + + @UiThread + fun setEndDate(timestamp: Long) { + endTimestamp = timestamp + computeDateLabels() + } + + @UiThread + fun setStartTime(hours: Int, minutes: Int) { + startHour = hours + startMinutes = minutes + + endHour = hours + 1 + endMinutes = minutes + + computeTimeLabels() + } + + @UiThread + fun setEndTime(hours: Int, minutes: Int) { + endHour = hours + endMinutes = minutes + + computeTimeLabels() } @UiThread @@ -55,4 +172,45 @@ class ScheduleMeetingViewModel @UiThread constructor() : ViewModel() { fun closeBroadcastHelp() { showBroadcastHelp.value = false } + + @UiThread + private fun computeDateLabels() { + val start = TimestampUtils.toString( + startTimestamp, + onlyDate = true, + timestampInSecs = false, + shortDate = false, + hideYear = false + ) + fromDate.value = start + Log.i("$TAG Computed start date for timestamp [$startTimestamp] is [$start]") + + val end = TimestampUtils.toString( + endTimestamp, + onlyDate = true, + timestampInSecs = false, + shortDate = false, + hideYear = false + ) + toDate.value = end + Log.i("$TAG Computed end date for timestamp [$endTimestamp] is [$end]") + } + + @UiThread + private fun computeTimeLabels() { + val cal = Calendar.getInstance() + cal.timeInMillis = startTimestamp + 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 + + cal.timeInMillis = endTimestamp + 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 + } } diff --git a/app/src/main/java/org/linphone/utils/TimestampUtils.kt b/app/src/main/java/org/linphone/utils/TimestampUtils.kt index 3c2db2e5f..e2be2138d 100644 --- a/app/src/main/java/org/linphone/utils/TimestampUtils.kt +++ b/app/src/main/java/org/linphone/utils/TimestampUtils.kt @@ -24,7 +24,10 @@ import java.text.DateFormat import java.text.Format import java.text.SimpleDateFormat import java.time.format.TextStyle -import java.util.* +import java.util.Calendar +import java.util.Date +import java.util.Locale +import java.util.TimeZone import org.linphone.LinphoneApplication.Companion.coreContext class TimestampUtils { @@ -146,7 +149,7 @@ class TimestampUtils { shortDate: Boolean = true, hideYear: Boolean = true ): String { - val dateFormat = if (isToday(timestamp, timestampInSecs)) { + val dateFormat = if (!onlyDate && isToday(timestamp, timestampInSecs)) { DateFormat.getTimeInstance(DateFormat.SHORT) } else { if (onlyDate) { @@ -159,7 +162,7 @@ class TimestampUtils { } } as SimpleDateFormat - if (hideYear || isSameYear(timestamp, timestampInSecs)) { + if (hideYear) { // Remove the year part of the format dateFormat.applyPattern( dateFormat.toPattern().replace( diff --git a/app/src/main/res/drawable/arrow_clockwise.xml b/app/src/main/res/drawable/arrow_clockwise.xml new file mode 100644 index 000000000..7c0fc7d70 --- /dev/null +++ b/app/src/main/res/drawable/arrow_clockwise.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/file_text.xml b/app/src/main/res/drawable/file_text.xml new file mode 100644 index 000000000..da26bb18b --- /dev/null +++ b/app/src/main/res/drawable/file_text.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/user_square.xml b/app/src/main/res/drawable/user_square.xml new file mode 100644 index 000000000..78d35f844 --- /dev/null +++ b/app/src/main/res/drawable/user_square.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/users.xml b/app/src/main/res/drawable/users.xml new file mode 100644 index 000000000..dd5df32df --- /dev/null +++ b/app/src/main/res/drawable/users.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/meeting_schedule_fragment.xml b/app/src/main/res/layout/meeting_schedule_fragment.xml index c80c32ec6..d5c9e1d31 100644 --- a/app/src/main/res/layout/meeting_schedule_fragment.xml +++ b/app/src/main/res/layout/meeting_schedule_fragment.xml @@ -7,6 +7,18 @@ + + + + @@ -61,25 +73,25 @@ @@ -94,18 +106,70 @@ android:layout_marginStart="16dp" android:layout_marginEnd="16dp" android:text="@string/meeting_schedule_broadcast_label" - android:textColor="@{viewModel.isBroadcastSelected ? @color/primary_button_label_color : @color/secondary_button_label_color, default=@color/secondary_button_label_color}" + android:textColor="@color/secondary_button_label_color" android:textSize="16sp" android:gravity="center" android:drawableStart="@drawable/slideshow" - android:drawableTint="@{viewModel.isBroadcastSelected ? @color/primary_button_label_color : @color/secondary_button_label_color, default=@color/secondary_button_label_color}" + android:drawableTint="@color/secondary_button_label_color" android:drawablePadding="5dp" - android:background="@{viewModel.isBroadcastSelected ? @drawable/primary_button_background : @drawable/secondary_button_background, default=@drawable/secondary_button_background}" + android:background="@drawable/secondary_button_background" android:paddingTop="@dimen/primary_secondary_buttons_label_padding" android:paddingBottom="@dimen/primary_secondary_buttons_label_padding" android:paddingStart="16dp" android:paddingEnd="16dp" + android:visibility="@{viewModel.isBroadcastSelected ? View.GONE : View.VISIBLE}" app:layout_constraintTop_toBottomOf="@id/title" + app:layout_constraintStart_toEndOf="@id/meeting_selected" + app:layout_constraintEnd_toEndOf="parent"/> + + + + @@ -154,11 +218,14 @@ android:layout_marginStart="16dp" android:layout_marginEnd="16dp" android:hint="@string/meeting_schedule_subject_hint" + android:textColorHint="@color/gray_main2_600" + android:text="@={viewModel.subject}" android:textSize="20sp" android:textColor="@color/gray_main2_600" android:inputType="text|textCapSentences" android:drawableStart="@{viewModel.isBroadcastSelected ? @drawable/slideshow : @drawable/users_three, default=@drawable/users_three}" android:drawablePadding="8dp" + android:drawableTint="@color/gray_main2_600" android:background="@color/transparent_color" app:layout_constraintTop_toBottomOf="@id/broadcast_help" app:layout_constraintStart_toStartOf="parent" @@ -174,6 +241,244 @@ app:layout_constraintTop_toBottomOf="@id/subject" android:background="@color/gray_main2_200" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bd6ad23cb..276bf6b12 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -337,6 +337,17 @@ Broadcast Info about broadcast. Learn more Add title… + All day + Choose the start date + Choose the end date + Choose the start time + Choose the end time + Timezone: %s + One time + Add description + Add participants + Add speaker + Send invitation to participants Operation in progress, please wait