Skip to content
This repository has been archived by the owner on Nov 1, 2022. It is now read-only.

Commit

Permalink
For #12151 - Support step attribute in TimePicker
Browse files Browse the repository at this point in the history
  • Loading branch information
Alexandru2909 committed Aug 17, 2022
1 parent a980963 commit 686fcea
Show file tree
Hide file tree
Showing 12 changed files with 576 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import mozilla.components.concept.storage.LoginEntry
import mozilla.components.support.base.log.logger.Logger
import mozilla.components.support.ktx.android.net.getFileName
import mozilla.components.support.ktx.kotlin.toDate
import mozilla.components.support.utils.TimePicker.shouldShowMillisecondsPicker
import mozilla.components.support.utils.TimePicker.shouldShowSecondsPicker
import org.mozilla.geckoview.AllowOrDeny
import org.mozilla.geckoview.Autocomplete
import org.mozilla.geckoview.GeckoResult
Expand Down Expand Up @@ -400,7 +402,15 @@ internal class GeckoPromptDelegate(private val geckoEngineSession: GeckoEngineSe
DATE -> "yyyy-MM-dd"
MONTH -> "yyyy-MM"
WEEK -> "yyyy-'W'ww"
TIME -> "HH:mm"
TIME -> {
if (shouldShowMillisecondsPicker(prompt.stepValue?.toFloat())) {
"HH:mm:ss.SSS"
} else if (shouldShowSecondsPicker(prompt.stepValue?.toFloat())) {
"HH:mm:ss"
} else {
"HH:mm"
}
}
DATETIME_LOCAL -> "yyyy-MM-dd'T'HH:mm"
else -> {
throw InvalidParameterException("${prompt.type} is not a valid DatetimeType")
Expand All @@ -412,6 +422,7 @@ internal class GeckoPromptDelegate(private val geckoEngineSession: GeckoEngineSe
initialDateString,
prompt.minValue,
prompt.maxValue,
prompt.stepValue,
onClear,
format,
onConfirm,
Expand Down Expand Up @@ -685,6 +696,7 @@ internal class GeckoPromptDelegate(private val geckoEngineSession: GeckoEngineSe
initialDateString: String,
minDateString: String?,
maxDateString: String?,
stepValue: String?,
onClear: () -> Unit,
format: String,
onConfirm: (String) -> Unit,
Expand All @@ -699,7 +711,7 @@ internal class GeckoPromptDelegate(private val geckoEngineSession: GeckoEngineSe
}

val selectionType = when (format) {
"HH:mm" -> PromptRequest.TimeSelection.Type.TIME
"HH:mm", "HH:mm:ss", "HH:mm:ss.SSS" -> PromptRequest.TimeSelection.Type.TIME
"yyyy-MM" -> PromptRequest.TimeSelection.Type.MONTH
"yyyy-MM-dd'T'HH:mm" -> PromptRequest.TimeSelection.Type.DATE_AND_TIME
else -> PromptRequest.TimeSelection.Type.DATE
Expand All @@ -712,6 +724,7 @@ internal class GeckoPromptDelegate(private val geckoEngineSession: GeckoEngineSe
initialDate,
minDate,
maxDate,
stepValue,
selectionType,
onSelect,
onClear,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ sealed class PromptRequest(
val initialDate: java.util.Date,
val minimumDate: java.util.Date?,
val maximumDate: java.util.Date?,
val stepValue: String? = null,
val type: Type = Type.DATE,
val onConfirm: (java.util.Date) -> Unit,
val onClear: () -> Unit,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ class PromptRequestTest {
Date(),
Date(),
Date(),
"1",
Type.DATE,
{ _ -> },
{},
Expand All @@ -110,6 +111,7 @@ class PromptRequestTest {

assertEquals(dateRequest.title, "title")
assertEquals(dateRequest.type, Type.DATE)
assertEquals("1", dateRequest.stepValue)
assertNotNull(dateRequest.initialDate)
assertNotNull(dateRequest.minimumDate)
assertNotNull(dateRequest.maximumDate)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -712,7 +712,8 @@ class PromptFeature private constructor(
initialDate,
minimumDate,
maximumDate,
selectionType
selectionType,
stepValue
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import android.app.AlertDialog
import android.app.DatePickerDialog
import android.app.Dialog
import android.app.TimePickerDialog
import android.content.Context
import android.content.DialogInterface
import android.content.DialogInterface.BUTTON_NEGATIVE
import android.content.DialogInterface.BUTTON_NEUTRAL
Expand All @@ -25,11 +26,15 @@ import androidx.annotation.VisibleForTesting.PRIVATE
import mozilla.components.feature.prompts.R
import mozilla.components.feature.prompts.ext.day
import mozilla.components.feature.prompts.ext.hour
import mozilla.components.feature.prompts.ext.millisecond
import mozilla.components.feature.prompts.ext.minute
import mozilla.components.feature.prompts.ext.month
import mozilla.components.feature.prompts.ext.second
import mozilla.components.feature.prompts.ext.toCalendar
import mozilla.components.feature.prompts.ext.year
import mozilla.components.feature.prompts.widget.MonthAndYearPicker
import mozilla.components.feature.prompts.widget.TimePrecisionPicker
import mozilla.components.support.utils.TimePicker.shouldShowSecondsPicker
import java.util.Calendar
import java.util.Date

Expand All @@ -38,6 +43,7 @@ private const val KEY_MIN_DATE = "KEY_MIN_DATE"
private const val KEY_MAX_DATE = "KEY_MAX_DATE"
private const val KEY_SELECTED_DATE = "KEY_SELECTED_DATE"
private const val KEY_SELECTION_TYPE = "KEY_SELECTION_TYPE"
private const val KEY_STEP_VALUE = "KEY_STEP_VALUE"

/**
* [DialogFragment][androidx.fragment.app.DialogFragment] implementation to display date picker with a native dialog.
Expand All @@ -49,11 +55,13 @@ internal class TimePickerDialogFragment :
TimePickerDialog.OnTimeSetListener,
DatePickerDialog.OnDateSetListener,
DialogInterface.OnClickListener,
MonthAndYearPicker.OnDateSetListener {
MonthAndYearPicker.OnDateSetListener,
TimePrecisionPicker.OnTimeSetListener {
private val initialDate: Date by lazy { safeArguments.getSerializable(KEY_INITIAL_DATE) as Date }
private val minimumDate: Date? by lazy { safeArguments.getSerializable((KEY_MIN_DATE)) as? Date }
private val maximumDate: Date? by lazy { safeArguments.getSerializable(KEY_MAX_DATE) as? Date }
private val selectionType: Int by lazy { safeArguments.getInt(KEY_SELECTION_TYPE) }
private val stepSize: String? by lazy { safeArguments.getString(KEY_STEP_VALUE) }

@VisibleForTesting(otherwise = PRIVATE)
internal var selectedDate: Date
Expand All @@ -66,15 +74,7 @@ internal class TimePickerDialogFragment :
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val context = requireContext()
val dialog = when (selectionType) {
SELECTION_TYPE_TIME -> initialDate.toCalendar().let { cal ->
TimePickerDialog(
context,
this,
cal.hour,
cal.minute,
DateFormat.is24HourFormat(context)
)
}
SELECTION_TYPE_TIME -> createTimePickerDialog(context)
SELECTION_TYPE_DATE -> initialDate.toCalendar().let { cal ->
DatePickerDialog(
context,
Expand Down Expand Up @@ -118,6 +118,62 @@ internal class TimePickerDialogFragment :
onClick(dialog, BUTTON_NEGATIVE)
}

// Create the appropriate time picker dialog for the given step value.
private fun createTimePickerDialog(context: Context): AlertDialog {

// Create the Android time picker dialog
fun createTimePickerDialog(): AlertDialog {
return initialDate.toCalendar().let { cal ->
TimePickerDialog(
context,
this,
cal.hour,
cal.minute,
DateFormat.is24HourFormat(context)
)
}
}

// Create the custom time picker dialog
fun createTimeStepPickerDialog(stepValue: Float): AlertDialog {
return AlertDialog.Builder(context)
.setTitle(R.string.mozac_feature_prompts_set_time)
.setView(
TimePrecisionPicker(
context = requireContext(),
selectedTime = initialDate.toCalendar(),
maxTime = maximumDate?.toCalendar()
?: MonthAndYearPicker.getDefaultMaxDate(),
minTime = minimumDate?.toCalendar()
?: MonthAndYearPicker.getDefaultMinDate(),
stepValue = stepValue,
timeSetListener = this
)
)
.create()
.also {
it.setButton(
BUTTON_POSITIVE,
context.getString(R.string.mozac_feature_prompts_set_date),
this
)
it.setButton(
BUTTON_NEGATIVE,
context.getString(R.string.mozac_feature_prompts_cancel),
this
)
}
}

return if (!shouldShowSecondsPicker(stepSize?.toFloat())) {
createTimePickerDialog()
} else {
stepSize?.let {
createTimeStepPickerDialog(it.toFloat())
} ?: createTimePickerDialog()
}
}

@SuppressLint("InflateParams")
private fun inflateDateTimePicker(inflater: LayoutInflater): View {
val view = inflater.inflate(R.layout.mozac_feature_prompts_date_time_picker, null)
Expand Down Expand Up @@ -165,6 +221,21 @@ internal class TimePickerDialogFragment :
}
}

override fun onTimeSet(
picker: TimePrecisionPicker,
hour: Int,
minute: Int,
second: Int,
millisecond: Int
) {
val calendar = selectedDate.toCalendar()
calendar.hour = hour
calendar.minute = minute
calendar.second = second
calendar.millisecond = millisecond
selectedDate = calendar.time
}

override fun onDateChanged(view: DatePicker?, year: Int, monthOfYear: Int, dayOfMonth: Int) {
val calendar = Calendar.getInstance()
calendar.set(year, monthOfYear, dayOfMonth)
Expand Down Expand Up @@ -214,6 +285,7 @@ internal class TimePickerDialogFragment :
* @param selectionType indicate which type of time should be selected, valid values are
* ([TimePickerDialogFragment.SELECTION_TYPE_DATE], [TimePickerDialogFragment.SELECTION_TYPE_DATE_AND_TIME],
* and [TimePickerDialogFragment.SELECTION_TYPE_TIME])
* @param stepValue value of time jumped whenever the time is incremented/decremented.
*
* @return a new instance of [TimePickerDialogFragment]
*/
Expand All @@ -225,7 +297,8 @@ internal class TimePickerDialogFragment :
initialDate: Date,
minDate: Date?,
maxDate: Date?,
selectionType: Int = SELECTION_TYPE_DATE
selectionType: Int = SELECTION_TYPE_DATE,
stepValue: String? = null,
): TimePickerDialogFragment {
val fragment = TimePickerDialogFragment()
val arguments = fragment.arguments ?: Bundle()
Expand All @@ -237,6 +310,7 @@ internal class TimePickerDialogFragment :
putSerializable(KEY_INITIAL_DATE, initialDate)
putSerializable(KEY_MIN_DATE, minDate)
putSerializable(KEY_MAX_DATE, maxDate)
putString(KEY_STEP_VALUE, stepValue)
putInt(KEY_SELECTION_TYPE, selectionType)
}
fragment.selectedDate = initialDate
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,35 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@file:Suppress("TooManyFunctions")

package mozilla.components.feature.prompts.ext

import java.util.Calendar
import java.util.Date

internal fun Date.toCalendar() = Calendar.getInstance().also { it.time = this }

internal val Calendar.minute: Int
internal var Calendar.millisecond: Int
get() = get(Calendar.MILLISECOND)
set(value) {
set(Calendar.MILLISECOND, value)
}
internal var Calendar.second: Int
get() = get(Calendar.SECOND)
set(value) {
set(Calendar.SECOND, value)
}
internal var Calendar.minute: Int
get() = get(Calendar.MINUTE)
internal val Calendar.hour: Int
set(value) {
set(Calendar.MINUTE, value)
}
internal var Calendar.hour: Int
get() = get(Calendar.HOUR_OF_DAY)
set(value) {
set(Calendar.HOUR_OF_DAY, value)
}
internal var Calendar.day: Int
get() = get(Calendar.DAY_OF_MONTH)
set(value) {
Expand All @@ -30,6 +48,14 @@ internal var Calendar.month: Int
set(Calendar.MONTH, value)
}

internal fun Calendar.minMillisecond(): Int = getActualMinimum(Calendar.MILLISECOND)
internal fun Calendar.maxMillisecond(): Int = getActualMaximum(Calendar.MILLISECOND)
internal fun Calendar.minSecond(): Int = getActualMinimum(Calendar.SECOND)
internal fun Calendar.maxSecond(): Int = getActualMaximum(Calendar.SECOND)
internal fun Calendar.minMinute(): Int = getActualMinimum(Calendar.MINUTE)
internal fun Calendar.maxMinute(): Int = getActualMaximum(Calendar.MINUTE)
internal fun Calendar.minHour(): Int = getActualMinimum(Calendar.HOUR_OF_DAY)
internal fun Calendar.maxHour(): Int = getActualMaximum(Calendar.HOUR_OF_DAY)
internal fun Calendar.minMonth(): Int = getMinimum(Calendar.MONTH)
internal fun Calendar.maxMonth(): Int = getActualMaximum(Calendar.MONTH)
internal fun Calendar.minDay(): Int = getMinimum(Calendar.DAY_OF_MONTH)
Expand Down
Loading

0 comments on commit 686fcea

Please sign in to comment.