tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 8b45ac78815b3d94dd9236969af429c964e8746a
parent 524124c709680f224b4287cf9ab3c9515460072a
Author: iorgamgabriel <iorgamgabriel@yahoo.com>
Date:   Tue, 11 Nov 2025 07:53:38 +0000

Bug 1992407 - Migrate the DatePicker to the material design and fix the colours. r=android-reviewers,aaronmt,calu

Differential Revision: https://phabricator.services.mozilla.com/D268528

Diffstat:
Mmobile/android/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/dialog/TimePickerDialogFragment.kt | 293++++++++++++++++++++++++++++++++++++++++---------------------------------------
Mmobile/android/android-components/components/feature/prompts/src/test/java/mozilla/components/feature/prompts/dialog/TimePickerDialogFragmentTest.kt | 129++-----------------------------------------------------------------------------
Mmobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/WebControlsTest.kt | 14+++-----------
Mmobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/robots/BrowserRobot.kt | 11+++--------
Amobile/android/fenix/app/src/main/res/color/time_picker_button_background_tint.xml | 7+++++++
Mmobile/android/fenix/app/src/main/res/values/styles.xml | 71++++++++++++++++++++++++++++++++++++++++-------------------------------
Mmobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/WebControlsTest.kt | 2+-
Mmobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/BrowserRobot.kt | 4+---
8 files changed, 204 insertions(+), 327 deletions(-)

diff --git a/mobile/android/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/dialog/TimePickerDialogFragment.kt b/mobile/android/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/dialog/TimePickerDialogFragment.kt @@ -3,33 +3,35 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package mozilla.components.feature.prompts.dialog -import android.annotation.SuppressLint -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 import android.content.DialogInterface.BUTTON_POSITIVE +import android.icu.util.TimeZone import android.os.Bundle import android.text.format.DateFormat -import android.view.LayoutInflater import android.view.View import android.widget.DatePicker import android.widget.TimePicker import androidx.annotation.VisibleForTesting import androidx.annotation.VisibleForTesting.Companion.PRIVATE +import androidx.appcompat.app.AlertDialog +import com.google.android.material.datepicker.CalendarConstraints +import com.google.android.material.datepicker.DateValidatorPointBackward +import com.google.android.material.datepicker.DateValidatorPointForward +import com.google.android.material.datepicker.MaterialDatePicker +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.google.android.material.timepicker.MaterialTimePicker +import com.google.android.material.timepicker.TimeFormat 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 @@ -77,55 +79,29 @@ internal class TimePickerDialogFragment : } override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - val context = requireContext() - val dialog = when (selectionType) { - SELECTION_TYPE_TIME -> createTimePickerDialog(context) - SELECTION_TYPE_DATE -> initialDate.toCalendar().let { cal -> - DatePickerDialog( - context, - this@TimePickerDialogFragment, - cal.year, - cal.month, - cal.day, - ).apply { setMinMaxDate(datePicker) } + return when (selectionType) { + SELECTION_TYPE_DATE, SELECTION_TYPE_DATE_AND_TIME -> { + createMaterialDatePickerDialog(isDateTimePicker = selectionType == SELECTION_TYPE_DATE_AND_TIME) + Dialog(requireContext()) } - SELECTION_TYPE_DATE_AND_TIME -> AlertDialog.Builder(context) - .setView(inflateDateTimePicker(LayoutInflater.from(context))) - .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) - } - SELECTION_TYPE_MONTH -> AlertDialog.Builder(context) - .setTitle(R.string.mozac_feature_prompts_set_month) - .setView(inflateDateMonthPicker()) - .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) + + SELECTION_TYPE_TIME -> { + val step = stepSize?.toFloat() + if (shouldShowSecondsPicker(step) && step != null) { + createTimeStepPickerDialog(step) + } else { + createMaterialTimePickerDialog(selectedDate.time) + Dialog(requireContext()) } - else -> throw IllegalArgumentException() - } + } - dialog.also { - it.setCancelable(true) - it.setButton(BUTTON_NEUTRAL, context.getString(R.string.mozac_feature_prompts_clear), this) + SELECTION_TYPE_MONTH -> createMonthPickerDialog() + else -> throw IllegalArgumentException("Invalid selection type: $selectionType") } - - return dialog - } - - /** - * Called when the user touches outside of the dialog. - */ - override fun onCancel(dialog: DialogInterface) { - super.onCancel(dialog) - onClick(dialog, BUTTON_NEGATIVE) } override fun onStart() { super.onStart() - val alertDialog = dialog if (alertDialog is AlertDialog) { // We want to call the extension function after the show() call on the dialog, @@ -134,74 +110,103 @@ internal class TimePickerDialogFragment : } } - // 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), + private fun createMaterialTimePickerDialog(dateSelection: Long? = null) { + val calendar = initialDate.toCalendar() + val is24Hour = DateFormat.is24HourFormat(requireContext()) + val timeFormat = if (is24Hour) TimeFormat.CLOCK_24H else TimeFormat.CLOCK_12H + + val timePicker = MaterialTimePicker.Builder() + .setTimeFormat(timeFormat) + .setHour(calendar.hour) + .setMinute(calendar.minute) + .setTitleText(R.string.mozac_feature_prompts_set_time) + .build() + + timePicker.addOnPositiveButtonClickListener { + val finalCalendar = ( + dateSelection?.let { + Calendar.getInstance().apply { + timeInMillis = it - TimeZone.getDefault().getOffset(it) + } + } ?: selectedDate.toCalendar() ) - } + + finalCalendar.set(Calendar.HOUR_OF_DAY, timePicker.hour) + finalCalendar.set(Calendar.MINUTE, timePicker.minute) + finalCalendar.set(Calendar.SECOND, 0) + finalCalendar.set(Calendar.MILLISECOND, 0) + selectedDate = finalCalendar.time + + feature?.onConfirm(sessionId, promptRequestUID, selectedDate) + dismiss() + } + timePicker.addOnNegativeButtonClickListener { + feature?.onCancel(sessionId, promptRequestUID) + dismiss() } + timePicker.addOnCancelListener { + feature?.onCancel(sessionId, promptRequestUID) + dismiss() + } + timePicker.show(parentFragmentManager, timePicker.toString()) + } - // 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() - ?: TimePrecisionPicker.getDefaultMaxTime(), - minTime = minimumDate?.toCalendar() - ?: TimePrecisionPicker.getDefaultMinTime(), - 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, - ) - } + private fun createMaterialDatePickerDialog(isDateTimePicker: Boolean = false) { + val constraintsBuilder = CalendarConstraints.Builder().apply { + minimumDate?.let { setValidator(DateValidatorPointForward.from(it.time)) } + maximumDate?.let { setValidator(DateValidatorPointBackward.before(it.time)) } } + val initialUtcTime = initialDate.time + TimeZone.getDefault().getOffset(initialDate.time) + + val datePicker = MaterialDatePicker.Builder.datePicker() + .setSelection(initialUtcTime) + .setPositiveButtonText(R.string.mozac_feature_prompts_set_date) + .setNegativeButtonText(R.string.mozac_feature_prompts_cancel) + .setCalendarConstraints(constraintsBuilder.build()) + .build() - return if (!shouldShowSecondsPicker(stepSize?.toFloat())) { - createTimePickerDialog() - } else { - stepSize?.let { - createTimeStepPickerDialog(it.toFloat()) - } ?: createTimePickerDialog() + datePicker.addOnPositiveButtonClickListener { selection -> + if (isDateTimePicker) { + // For the date-time picker, we dismiss the date picker first + // and then show the time picker. + datePicker.dismiss() + createMaterialTimePickerDialog(selection) + } else { + // For the date-only picker, we confirm the selection and dismiss everything. + val millis = (selection ?: 0L) - TimeZone.getDefault().getOffset(selection ?: 0L) + + selectedDate = Date(millis) + feature?.onConfirm(sessionId, promptRequestUID, selectedDate) + datePicker.dismiss() + dismissAllowingStateLoss() + } } + datePicker.addOnNegativeButtonClickListener { + feature?.onCancel(sessionId, promptRequestUID) + dismiss() + } + datePicker.addOnCancelListener { + feature?.onCancel(sessionId, promptRequestUID) + dismiss() + } + datePicker.show(parentFragmentManager, datePicker.toString()) + } + + private fun createMonthPickerDialog(): AlertDialog { + val view = inflateDateMonthPicker() + return buildDialogWithView(view, titleResId = R.string.mozac_feature_prompts_set_month) } - @SuppressLint("InflateParams") - private fun inflateDateTimePicker(inflater: LayoutInflater): View { - val view = inflater.inflate(R.layout.mozac_feature_prompts_date_time_picker, null) - val datePicker = view.findViewById<DatePicker>(R.id.date_picker) - val dateTimePicker = view.findViewById<TimePicker>(R.id.datetime_picker) - val cal = initialDate.toCalendar() + private fun buildDialogWithView(view: View, titleResId: Int? = null): AlertDialog { + val builder = MaterialAlertDialogBuilder(requireContext()) + .setView(view) + .setPositiveButton(R.string.mozac_feature_prompts_set_date, this) + .setNegativeButton(R.string.mozac_feature_prompts_cancel, this) + .setNeutralButton(R.string.mozac_feature_prompts_clear, this) - // Bind date picker - setMinMaxDate(datePicker) - datePicker.init(cal.year, cal.month, cal.day, this) - initTimePicker(dateTimePicker, cal) + titleResId?.let { builder.setTitle(it) } - return view + return builder.create() } private fun inflateDateMonthPicker(): View { @@ -214,72 +219,68 @@ internal class TimePickerDialogFragment : ) } - private fun initTimePicker(picker: TimePicker, cal: Calendar) { - picker.hour = cal.hour - picker.minute = cal.minute - picker.setIs24HourView(DateFormat.is24HourFormat(requireContext())) - picker.setOnTimeChangedListener(this) + fun createTimeStepPickerDialog(stepValue: Float): AlertDialog { + val view = TimePrecisionPicker( + context = requireContext(), + selectedTime = initialDate.toCalendar(), + maxTime = maximumDate?.toCalendar() ?: TimePrecisionPicker.getDefaultMaxTime(), + minTime = minimumDate?.toCalendar() ?: TimePrecisionPicker.getDefaultMinTime(), + stepValue = stepValue, + timeSetListener = this, + ) + return buildDialogWithView(view, titleResId = R.string.mozac_feature_prompts_set_time) } - private fun setMinMaxDate(datePicker: DatePicker) { - minimumDate?.let { - datePicker.minDate = it.time - } - maximumDate?.let { - datePicker.maxDate = it.time + override fun onClick(dialog: DialogInterface?, which: Int) { + when (which) { + BUTTON_POSITIVE -> feature?.onConfirm(sessionId, promptRequestUID, selectedDate) + BUTTON_NEGATIVE -> feature?.onCancel(sessionId, promptRequestUID) + BUTTON_NEUTRAL -> feature?.onClear(sessionId, promptRequestUID) } } - override fun onTimeSet( - picker: TimePrecisionPicker, - hour: Int, - minute: Int, - second: Int, - millisecond: Int, - ) { + override fun onTimeChanged(picker: TimePicker?, hourOfDay: Int, minute: Int) { val calendar = selectedDate.toCalendar() - calendar.hour = hour - calendar.minute = minute - calendar.second = second - calendar.millisecond = millisecond + calendar.set(Calendar.HOUR_OF_DAY, hourOfDay) + calendar.set(Calendar.MINUTE, minute) selectedDate = calendar.time } + override fun onTimeSet(view: TimePicker?, hourOfDay: Int, minute: Int) { + onTimeChanged(view, hourOfDay, minute) + onClick(null, BUTTON_POSITIVE) + } + override fun onDateChanged(view: DatePicker?, year: Int, monthOfYear: Int, dayOfMonth: Int) { val calendar = Calendar.getInstance() calendar.set(year, monthOfYear, dayOfMonth) selectedDate = calendar.time } - override fun onDateSet(view: DatePicker?, year: Int, month: Int, dayOfMonth: Int) { - onDateChanged(view, year, month, dayOfMonth) - onClick(null, BUTTON_POSITIVE) - } - override fun onDateSet(picker: MonthAndYearPicker, month: Int, year: Int) { onDateChanged(null, year, month, 0) } - override fun onTimeChanged(picker: TimePicker?, hourOfDay: Int, minute: Int) { + override fun onTimeSet( + picker: TimePrecisionPicker, + hour: Int, + minute: Int, + second: Int, + millisecond: Int, + ) { val calendar = selectedDate.toCalendar() - calendar.set(Calendar.HOUR_OF_DAY, hourOfDay) - calendar.set(Calendar.MINUTE, minute) + calendar.hour = hour + calendar.minute = minute + calendar.second = second + calendar.millisecond = millisecond selectedDate = calendar.time } - override fun onTimeSet(view: TimePicker?, hourOfDay: Int, minute: Int) { - onTimeChanged(view, hourOfDay, minute) + override fun onDateSet(view: DatePicker?, year: Int, month: Int, dayOfMonth: Int) { + onDateChanged(view, year, month, dayOfMonth) onClick(null, BUTTON_POSITIVE) } - override fun onClick(dialog: DialogInterface?, which: Int) { - when (which) { - BUTTON_POSITIVE -> feature?.onConfirm(sessionId, promptRequestUID, selectedDate) - BUTTON_NEGATIVE -> feature?.onCancel(sessionId, promptRequestUID) - BUTTON_NEUTRAL -> feature?.onClear(sessionId, promptRequestUID) - } - } - companion object { /** * A builder method for creating a [TimePickerDialogFragment] diff --git a/mobile/android/android-components/components/feature/prompts/src/test/java/mozilla/components/feature/prompts/dialog/TimePickerDialogFragmentTest.kt b/mobile/android/android-components/components/feature/prompts/src/test/java/mozilla/components/feature/prompts/dialog/TimePickerDialogFragmentTest.kt @@ -4,15 +4,13 @@ package mozilla.components.feature.prompts.dialog -import android.app.AlertDialog -import android.app.DatePickerDialog -import android.app.TimePickerDialog import android.content.DialogInterface.BUTTON_NEUTRAL import android.content.DialogInterface.BUTTON_POSITIVE import android.os.Looper.getMainLooper import android.widget.DatePicker import android.widget.NumberPicker import android.widget.TimePicker +import androidx.appcompat.app.AlertDialog import androidx.test.ext.junit.runners.AndroidJUnit4 import mozilla.components.feature.prompts.R import mozilla.components.feature.prompts.dialog.TimePickerDialogFragment.Companion.SELECTION_TYPE_DATE_AND_TIME @@ -44,75 +42,13 @@ import java.util.Date @RunWith(AndroidJUnit4::class) class TimePickerDialogFragmentTest { - @Mock private lateinit var mockFeature: Prompter - @Before fun setup() { + testContext.setTheme(com.google.android.material.R.style.Theme_MaterialComponents_Light) openMocks(this) } @Test - fun `build dialog`() { - val initialDate = "2019-11-29".toDate("yyyy-MM-dd") - val minDate = "2019-11-28".toDate("yyyy-MM-dd") - val maxDate = "2019-11-30".toDate("yyyy-MM-dd") - val fragment = spy( - TimePickerDialogFragment.newInstance("sessionId", "uid", true, initialDate, minDate, maxDate), - ) - - doReturn(appCompatContext).`when`(fragment).requireContext() - - val dialog = fragment.onCreateDialog(null) - dialog.show() - - val datePicker = (dialog as DatePickerDialog).datePicker - assertEquals("sessionId", fragment.sessionId) - assertEquals("uid", fragment.promptRequestUID) - assertEquals(2019, datePicker.year) - assertEquals(11, datePicker.month + 1) - assertEquals(29, datePicker.dayOfMonth) - assertEquals(minDate, Date(datePicker.minDate)) - assertEquals(maxDate, Date(datePicker.maxDate)) - } - - @Test - fun `Clicking on positive, neutral and negative button notifies the feature`() { - val initialDate = "2019-11-29".toDate("yyyy-MM-dd") - val fragment = spy( - TimePickerDialogFragment.newInstance("sessionId", "uid", false, initialDate, null, null), - ) - fragment.feature = mockFeature - - doReturn(appCompatContext).`when`(fragment).requireContext() - - val dialog = fragment.onCreateDialog(null) - dialog.show() - - val positiveButton = (dialog as AlertDialog).getButton(BUTTON_POSITIVE) - positiveButton.performClick() - shadowOf(getMainLooper()).idle() - - verify(mockFeature).onConfirm(eq("sessionId"), eq("uid"), any()) - - val neutralButton = dialog.getButton(BUTTON_NEUTRAL) - neutralButton.performClick() - shadowOf(getMainLooper()).idle() - - verify(mockFeature).onClear("sessionId", "uid") - } - - @Test - fun `touching outside of the dialog must notify the feature onCancel`() { - val fragment = spy( - TimePickerDialogFragment.newInstance("sessionId", "uid", true, Date(), null, null), - ) - fragment.feature = mockFeature - doReturn(testContext).`when`(fragment).requireContext() - fragment.onCancel(mock()) - verify(mockFeature).onCancel("sessionId", "uid") - } - - @Test fun `onTimeChanged must update the selectedDate`() { val dialogPicker = TimePickerDialogFragment.newInstance("sessionId", "uid", false, Date(), null, null) @@ -125,43 +61,6 @@ class TimePickerDialogFragmentTest { } @Test - fun `building a date and time picker`() { - val initialDate = "2018-06-12T19:30".toDate("yyyy-MM-dd'T'HH:mm") - val minDate = "2018-06-07T00:00".toDate("yyyy-MM-dd'T'HH:mm") - val maxDate = "2018-06-14T00:00".toDate("yyyy-MM-dd'T'HH:mm") - val fragment = spy( - TimePickerDialogFragment.newInstance( - "sessionId", - "uid", - true, - initialDate, - minDate, - maxDate, - SELECTION_TYPE_DATE_AND_TIME, - ), - ) - - doReturn(appCompatContext).`when`(fragment).requireContext() - - val dialog = fragment.onCreateDialog(null) - dialog.show() - - val datePicker = dialog.findViewById<DatePicker>(R.id.date_picker) - - assertEquals(2018, datePicker.year) - assertEquals(6, datePicker.month + 1) - assertEquals(12, datePicker.dayOfMonth) - - assertEquals(minDate, Date(datePicker.minDate)) - assertEquals(maxDate, Date(datePicker.maxDate)) - - val timePicker = dialog.findViewById<TimePicker>(R.id.datetime_picker) - - assertEquals(19, timePicker.hour) - assertEquals(30, timePicker.minute) - } - - @Test fun `building a month picker`() { val initialDate = "2018-06".toDate("yyyy-MM") val minDate = "2018-04".toDate("yyyy-MM") @@ -207,30 +106,6 @@ class TimePickerDialogFragmentTest { assertEquals(7, selectedDate.month) } - @Test - fun `building a time picker`() { - val initialDate = "2018-06-12T19:30".toDate("yyyy-MM-dd'T'HH:mm") - val minDate = "2018-06-07T00:00".toDate("yyyy-MM-dd'T'HH:mm") - val maxDate = "2018-06-14T00:00".toDate("yyyy-MM-dd'T'HH:mm") - val fragment = spy( - TimePickerDialogFragment.newInstance( - "sessionId", - "uid", - true, - initialDate, - minDate, - maxDate, - SELECTION_TYPE_TIME, - ), - ) - - doReturn(appCompatContext).`when`(fragment).requireContext() - - val dialog = fragment.onCreateDialog(null) - dialog.show() - assertTrue(dialog is TimePickerDialog) - } - @Test(expected = IllegalArgumentException::class) fun `creating a TimePickerDialogFragment with an invalid type selection will throw an exception`() { val initialDate = "2018-06-12T19:30".toDate("yyyy-MM-dd'T'HH:mm") diff --git a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/WebControlsTest.kt b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/WebControlsTest.kt @@ -59,14 +59,10 @@ class WebControlsTest : TestSetup() { clickPageObject(itemWithResId("submitDate")) verifyNoDateIsSelected() clickPageObject(itemWithResId("calendar")) - clickPageObject(itemWithDescription("$currentDay $currentMonth $currentYear")) - clickPageObject(itemContainingText("OK")) + clickPageObject(itemWithDescription("$currentMonth $currentDay")) + clickPageObject(itemContainingText("Set")) clickPageObject(itemWithResId("submitDate")) verifySelectedDate() - clickPageObject(itemWithResId("calendar")) - clickPageObject(itemContainingText("CLEAR")) - clickPageObject(itemWithResId("submitDate")) - verifyNoDateIsSelected() } } @@ -78,7 +74,7 @@ class WebControlsTest : TestSetup() { navigationToolbar { }.enterURLAndEnterToBrowser(htmlControlsPage.url) { clickPageObject(itemWithResId("clock")) - clickPageObject(itemContainingText("CANCEL")) + clickPageObject(itemContainingText("Cancel")) clickPageObject(itemWithResId("submitTime")) verifyNoTimeIsSelected(hour, minute) clickPageObject(itemWithResId("clock")) @@ -86,10 +82,6 @@ class WebControlsTest : TestSetup() { clickPageObject(itemContainingText("OK")) clickPageObject(itemWithResId("submitTime")) verifySelectedTime(hour, minute) - clickPageObject(itemWithResId("clock")) - clickPageObject(itemContainingText("CLEAR")) - clickPageObject(itemWithResId("submitTime")) - verifyNoTimeIsSelected(hour, minute) } } diff --git a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/robots/BrowserRobot.kt b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/robots/BrowserRobot.kt @@ -10,7 +10,6 @@ import android.content.Context import android.net.Uri import android.os.SystemClock import android.util.Log -import android.widget.TimePicker import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.assertAny import androidx.compose.ui.test.assertIsDisplayed @@ -32,10 +31,8 @@ import androidx.test.espresso.Espresso.onView import androidx.test.espresso.action.ViewActions.click import androidx.test.espresso.action.ViewActions.longClick import androidx.test.espresso.assertion.ViewAssertions.matches -import androidx.test.espresso.contrib.PickerActions import androidx.test.espresso.matcher.RootMatchers.isDialog import androidx.test.espresso.matcher.ViewMatchers.Visibility -import androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.withContentDescription import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility @@ -770,11 +767,9 @@ class BrowserRobot { fun selectTime(hour: Int, minute: Int) { Log.i(TAG, "selectTime: Trying to select time picker hour: $hour and minute: $minute") - onView( - isAssignableFrom(TimePicker::class.java), - ).inRoot( - isDialog(), - ).perform(PickerActions.setTime(hour, minute)) + itemWithDescription("$hour o'clock").click() + waitForAppWindowToBeUpdated() + itemWithDescription("$minute minutes").click() Log.i(TAG, "selectTime: Selected time picker hour: $hour and minute: $minute") } diff --git a/mobile/android/fenix/app/src/main/res/color/time_picker_button_background_tint.xml b/mobile/android/fenix/app/src/main/res/color/time_picker_button_background_tint.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - 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/. --> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:color="?attr/colorSecondaryContainer" android:state_checked="true"/> +</selector> diff --git a/mobile/android/fenix/app/src/main/res/values/styles.xml b/mobile/android/fenix/app/src/main/res/values/styles.xml @@ -16,13 +16,12 @@ <style name="NormalThemeBase" parent="Theme.Material3.DayNight.NoActionBar"> <item name="preferenceTheme">@style/PreferenceTheme</item> - + <item name="materialTimePickerTheme">@style/Normal.MaterialTimePicker</item> + <item name="materialCalendarTheme">@style/MaterialCalendar</item> <!-- Android system styling --> <item name="searchViewStyle">@style/SearchViewStyle</item> <item name="autoCompleteTextViewStyle">@style/AutoCompleteTextViewStyle</item> - <item name="android:textAlignment">viewStart</item> <item name="android:windowContentTransitions">true</item> - <item name="android:datePickerDialogTheme">@style/DatePickerDialogStyle</item> <item name="android:windowAnimationStyle">@style/WindowAnimationTransition</item> <item name="android:progressBarStyleHorizontal">@style/progressBarStyleHorizontal</item> <item name="android:statusBarColor">@android:color/transparent</item> @@ -200,32 +199,6 @@ <item name="tabCounterTintColor">?attr/textPrimary</item> </style> - <!-- A theme to fix DatePickerDialogs year picker text alignment - https://bugzilla.mozilla.org/show_bug.cgi?id=1986252 --> - <style name="DatePickerDialogStyle" parent="ThemeOverlay.MaterialComponents.Dialog"> - <item name="android:textAlignment">gravity</item> - <item name="android:windowBackground">?attr/layer1</item> - <item name="android:colorEdgeEffect">@color/accent_normal_theme</item> - <item name="android:colorAccent">@color/fx_mobile_text_color_primary</item> - <item name="android:textColorPrimary">@color/state_list_text_color</item> - <item name="android:textColorSecondary">@color/secondary_state_list_text_color</item> - <item name="android:colorForeground">@color/toggle_off_track_normal_theme</item> - </style> - - <style name="PrivateDatePickerDialogStyle" parent="ThemeOverlay.MaterialComponents.Dialog"> - <item name="android:textAlignment">gravity</item> - <!-- For some reason in private mode it was trying to load @drawable/? - as the window background. This resolved to #424242. Hard coding - this until we know what color we want private mode dialogs to be. - see: https://bugzilla.mozilla.org/show_bug.cgi?id=1986252--> - <item name="android:windowBackground">#424242</item> - <item name="android:colorEdgeEffect">@color/accent_private_theme</item> - <item name="android:colorAccent">@color/fx_mobile_private_text_color_primary</item> - <item name="android:textColorPrimary">@color/state_list_text_color</item> - <item name="android:textColorSecondary">@color/secondary_state_list_text_color</item> - <item name="android:colorForeground">@color/toggle_off_track_dark_theme</item> - </style> - <!-- A theme derived from the normal activity theme, but to look and behave like a dialog --> <style name="DialogActivityTheme" parent="NormalTheme"> <item name="android:windowElevation">16dp</item> @@ -261,6 +234,25 @@ <item name="materialAlertDialogBodyTextStyle">@style/MaterialAlertDialog.App.Body.Text.Private</item> </style> + <style name="Normal.MaterialTimePicker" parent="ThemeOverlay.Material3.MaterialTimePicker"> + <item name="materialButtonOutlinedStyle">@style/My.Widget.MaterialComponents.TimePicker.Button</item> + </style> + + <style name="Private.MaterialTimePicker" parent="ThemeOverlay.Material3.MaterialTimePicker"> + <item name="materialClockStyle">@style/Private.MaterialTimePickerClock</item> + <item name="materialButtonOutlinedStyle">@style/My.Widget.MaterialComponents.TimePicker.Button</item> + </style> + + <style name="My.Widget.MaterialComponents.TimePicker.Button" parent="Widget.Material3.MaterialTimePicker.Button"> + <item name="backgroundTint">@color/time_picker_button_background_tint</item> + </style> + + <style name="Private.MaterialTimePickerClock" parent="Widget.MaterialComponents.TimePicker.Clock"> + <item name="clockFaceBackgroundColor">?attr/colorSurfaceContainerHighest</item> + <item name="clockHandColor">?attr/colorPrimary</item> + <item name="clockNumberTextColor">?attr/colorOnSurface</item> + </style> + <style name="MaterialAlertDialog.App.Body.Text.Private" parent="MaterialAlertDialog.App.Body.Text"> <item name="android:textColor">@color/fx_mobile_private_text_color_primary</item> </style> @@ -295,6 +287,23 @@ <item name="android:gravity">center</item> </style> + <style name="MaterialCalendar" parent="ThemeOverlay.Material3.MaterialCalendar"> + <item name="materialCalendarHeaderDivider">@style/MaterialCalendar.HeaderDivider</item> + <item name="materialCalendarHeaderTitle">@style/MaterialCalendar.Title.Text</item> + <item name="buttonBarPositiveButtonStyle">@style/DialogButtonStyle</item> + <item name="buttonBarNegativeButtonStyle">@style/DialogButtonStyle</item> + </style> + + <style name="MaterialCalendar.Title.Text" parent="@style/Widget.Material3.MaterialCalendar.HeaderTitle"> + <item name="android:textColor">?attr/colorOnSurface</item> + <item name="android:titleTextStyle">?attr/textAppearanceHeadlineLarge</item> + </style> + + <style name="MaterialCalendar.HeaderDivider" parent="Widget.Material3.MaterialCalendar.HeaderDivider"> + <item name="android:visibility">visible</item> + <item name="android:background">?attr/colorOutlineVariant</item> + </style> + <style name="MaterialAlertDialogShapeAppearance" parent=""> <item name="cornerFamily">rounded</item> <item name="cornerSize">@dimen/material_dialog_corner_radius</item> @@ -308,13 +317,13 @@ <style name="PrivateThemeBase" parent="Theme.Material3.DayNight.NoActionBar"> <!-- Android system styling --> + <item name="materialTimePickerTheme">@style/Private.MaterialTimePicker</item> + <item name="materialCalendarTheme">@style/MaterialCalendar</item> <item name="searchViewStyle">@style/SearchViewStyle</item> <item name="android:textColorLink">@color/fx_mobile_private_text_color_accent</item> <item name="preferenceTheme">@style/PreferenceTheme</item> <item name="checkboxStyle">@style/App.Widget.CompoundButton.CheckBox</item> <item name="autoCompleteTextViewStyle">@style/AutoCompleteTextViewStyle</item> - <item name="android:textAlignment">viewStart</item> - <item name="android:datePickerDialogTheme">@style/PrivateDatePickerDialogStyle</item> <item name="android:windowContentTransitions">true</item> <item name="android:windowAnimationStyle">@style/WindowAnimationTransition</item> <item name="android:progressBarStyleHorizontal">@style/progressBarStyleHorizontal</item> diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/WebControlsTest.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/WebControlsTest.kt @@ -169,7 +169,7 @@ class WebControlsTest : TestSetup() { progressBar.waitUntilGone(waitingTime) clickCalendarForm() selectDate() - clickButtonWithText("OK") + clickButtonWithText("Set") clickSubmitDateButton() verifySelectedDate() } diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/BrowserRobot.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/BrowserRobot.kt @@ -291,12 +291,10 @@ class BrowserRobot { fun clickCalendarForm() = clickPageObject(webPageItemWithResourceId("calendar")) fun selectDate() { - mDevice.findObject(UiSelector().resourceId("android:id/month_view")).waitForExists(waitingTime) - mDevice.findObject( UiSelector() .textContains("$currentDay") - .descriptionContains("$currentDay $currentMonth $currentYear"), + .descriptionContains("$currentMonth $currentDay"), ).click() }