This repository has been archived by the owner on Nov 1, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 473
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Closes #3843: Ported the Month picker widget from Fennec
- Loading branch information
Showing
13 changed files
with
590 additions
and
24 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
39 changes: 39 additions & 0 deletions
39
components/feature/prompts/src/main/java/mozilla/components/feature/prompts/ext/Calendar.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
/* 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/. */ | ||
|
||
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 | ||
get() = get(Calendar.MINUTE) | ||
internal val Calendar.hour: Int | ||
get() = get(Calendar.HOUR_OF_DAY) | ||
internal var Calendar.day: Int | ||
get() = get(Calendar.DAY_OF_MONTH) | ||
set(value) { | ||
set(Calendar.DAY_OF_MONTH, value) | ||
} | ||
internal var Calendar.year: Int | ||
get() = get(Calendar.YEAR) | ||
set(value) { | ||
set(Calendar.YEAR, value) | ||
} | ||
|
||
internal var Calendar.month: Int | ||
get() = get(Calendar.MONTH) | ||
set(value) { | ||
set(Calendar.MONTH, value) | ||
} | ||
|
||
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) | ||
internal fun Calendar.maxDay(): Int = getActualMaximum(Calendar.DAY_OF_MONTH) | ||
internal fun Calendar.minYear(): Int = getMinimum(Calendar.YEAR) | ||
internal fun Calendar.maxYear(): Int = getActualMaximum(Calendar.YEAR) | ||
internal fun now() = Calendar.getInstance() |
179 changes: 179 additions & 0 deletions
179
...ure/prompts/src/main/java/mozilla/components/feature/prompts/widget/MonthAndYearPicker.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,179 @@ | ||
/* 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/. */ | ||
|
||
package mozilla.components.feature.prompts.widget | ||
|
||
import android.annotation.SuppressLint | ||
import android.content.Context | ||
import android.widget.NumberPicker | ||
import android.widget.ScrollView | ||
import androidx.annotation.VisibleForTesting | ||
import mozilla.components.feature.prompts.R | ||
import mozilla.components.feature.prompts.ext.month | ||
import mozilla.components.feature.prompts.ext.now | ||
import mozilla.components.feature.prompts.ext.year | ||
import java.util.Calendar | ||
|
||
/** | ||
* UI widget that allows to select a month and a year. | ||
*/ | ||
@SuppressLint("ViewConstructor") // This view is only instantiated in code | ||
internal class MonthAndYearPicker @JvmOverloads constructor( | ||
context: Context, | ||
private val selectedDate: Calendar = now(), | ||
private val maxDate: Calendar = getDefaultMaxDate(), | ||
private val minDate: Calendar = getDefaultMinDate(), | ||
internal var dateSetListener: OnDateSetListener? = null | ||
) : ScrollView(context), NumberPicker.OnValueChangeListener { | ||
|
||
@VisibleForTesting | ||
internal val monthView: NumberPicker | ||
@VisibleForTesting | ||
internal val yearView: NumberPicker | ||
private val monthsLabels: Array<out String> | ||
|
||
init { | ||
inflate(context, R.layout.mozac_feature_promps_widget_month_picker, this) | ||
|
||
adjustMinMaxDateIfAreInIllogicalRange() | ||
adjustIfSelectedDateIsInIllogicalRange() | ||
|
||
monthsLabels = context.resources.getStringArray(R.array.mozac_feature_prompts_months) | ||
|
||
monthView = findViewById(R.id.month_chooser) | ||
yearView = findViewById(R.id.year_chooser) | ||
|
||
iniMonthView() | ||
iniYearView() | ||
} | ||
|
||
@Suppress("LongMethod") | ||
override fun onValueChange(view: NumberPicker, oldVal: Int, newVal: Int) { | ||
var month = 0 | ||
var year = 0 | ||
when (view.id) { | ||
R.id.month_chooser -> { | ||
month = newVal | ||
// Wrapping months to update greater fields | ||
if (oldVal == view.maxValue && newVal == view.minValue) { | ||
yearView.value += 1 | ||
if (!yearView.value.isMinYear()) { | ||
month = Calendar.JANUARY | ||
} | ||
} else if (oldVal == view.minValue && newVal == view.maxValue) { | ||
yearView.value -= 1 | ||
if (!yearView.value.isMaxYear()) { | ||
month = Calendar.DECEMBER | ||
} | ||
} | ||
year = yearView.value | ||
} | ||
R.id.year_chooser -> { | ||
month = monthView.value | ||
year = newVal | ||
} | ||
} | ||
|
||
selectedDate.month = month | ||
selectedDate.year = year | ||
updateMonthView(month) | ||
dateSetListener?.onDateSet(this, month + 1, year) // Month is zero based | ||
} | ||
|
||
private fun Int.isMinYear() = minDate.year == this | ||
private fun Int.isMaxYear() = maxDate.year == this | ||
|
||
private fun iniMonthView() { | ||
monthView.setOnValueChangedListener(this) | ||
monthView.setOnLongPressUpdateInterval(SPEED_MONTH_SPINNER) | ||
updateMonthView(selectedDate.month) | ||
} | ||
|
||
private fun iniYearView() { | ||
val year = selectedDate.year | ||
val max = maxDate.year | ||
val min = minDate.year | ||
|
||
yearView.init(year, min, max) | ||
yearView.wrapSelectorWheel = false | ||
yearView.setOnLongPressUpdateInterval(SPEED_YEAR_SPINNER) | ||
} | ||
|
||
private fun updateMonthView(month: Int) { | ||
var min = Calendar.JANUARY | ||
var max = Calendar.DECEMBER | ||
|
||
if (selectedDate.year.isMinYear()) { | ||
min = minDate.month | ||
} | ||
|
||
if (selectedDate.year.isMaxYear()) { | ||
max = maxDate.month | ||
} | ||
|
||
monthView.apply { | ||
displayedValues = null | ||
minValue = min | ||
maxValue = max | ||
displayedValues = monthsLabels.copyOfRange(monthView.minValue, monthView.maxValue + 1) | ||
value = month | ||
wrapSelectorWheel = true | ||
} | ||
} | ||
|
||
private fun adjustMinMaxDateIfAreInIllogicalRange() { | ||
// If the input date range is illogical/garbage, we should not restrict the input range (i.e. allow the | ||
// user to select any date). If we try to make any assumptions based on the illogical min/max date we could | ||
// potentially prevent the user from selecting dates that are in the developers intended range, so it's best | ||
// to allow anything. | ||
if (maxDate.before(minDate)) { | ||
minDate.timeInMillis = getDefaultMinDate().timeInMillis | ||
maxDate.timeInMillis = getDefaultMaxDate().timeInMillis | ||
} | ||
} | ||
|
||
private fun adjustIfSelectedDateIsInIllogicalRange() { | ||
if (selectedDate.before(minDate) || selectedDate.after(maxDate)) { | ||
selectedDate.timeInMillis = minDate.timeInMillis | ||
} | ||
} | ||
|
||
private fun NumberPicker.init(currentValue: Int, min: Int, max: Int) { | ||
minValue = min | ||
maxValue = max | ||
value = currentValue | ||
setOnValueChangedListener(this@MonthAndYearPicker) | ||
} | ||
|
||
interface OnDateSetListener { | ||
fun onDateSet(picker: MonthAndYearPicker, month: Int, year: Int) | ||
} | ||
|
||
companion object { | ||
|
||
private const val SPEED_MONTH_SPINNER = 200L | ||
private const val SPEED_YEAR_SPINNER = 100L | ||
private const val DEFAULT_VALUE = -1 | ||
|
||
@VisibleForTesting | ||
internal const val DEFAULT_MAX_YEAR = 9999 | ||
|
||
@VisibleForTesting | ||
internal const val DEFAULT_MIN_YEAR = 1 | ||
|
||
internal fun getDefaultMinDate(): Calendar { | ||
return now().apply { | ||
month = Calendar.JANUARY | ||
year = DEFAULT_MIN_YEAR | ||
} | ||
} | ||
|
||
internal fun getDefaultMaxDate(): Calendar { | ||
return now().apply { | ||
month = Calendar.DECEMBER | ||
year = DEFAULT_MAX_YEAR | ||
} | ||
} | ||
} | ||
} |
40 changes: 40 additions & 0 deletions
40
components/feature/prompts/src/main/res/layout/mozac_feature_promps_widget_month_picker.xml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
<?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/. --> | ||
|
||
<merge xmlns:android="http://schemas.android.com/apk/res/android" | ||
xmlns:tools="http://schemas.android.com/tools" | ||
android:layout_width="match_parent" | ||
android:layout_height="wrap_content" | ||
tools:parentTag="android.widget.ScrollView"> | ||
|
||
<LinearLayout | ||
android:layout_width="match_parent" | ||
android:layout_height="wrap_content" | ||
android:gravity="center" | ||
android:orientation="horizontal"> | ||
|
||
<android.widget.NumberPicker | ||
android:id="@+id/month_chooser" | ||
android:layout_width="60dp" | ||
android:layout_height="wrap_content" | ||
android:layout_marginStart="1dp" | ||
android:layout_marginEnd="1dp" | ||
android:focusable="true" | ||
android:focusableInTouchMode="true" /> | ||
|
||
<android.widget.NumberPicker | ||
android:id="@+id/year_chooser" | ||
android:layout_width="75dp" | ||
android:layout_height="wrap_content" | ||
android:layout_marginStart="1dp" | ||
android:layout_marginEnd="1dp" | ||
android:focusable="true" | ||
android:focusableInTouchMode="true" /> | ||
|
||
</LinearLayout> | ||
</merge> | ||
|
||
|
21 changes: 21 additions & 0 deletions
21
components/feature/prompts/src/main/res/values/strings-no-translatable.xml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
<?xml version="1.0" encoding="utf-8" standalone="yes"?> | ||
<!-- 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/. --> | ||
<resources> | ||
<!-- Months of the years, used on the month chooser dialog. --> | ||
<string-array name="mozac_feature_prompts_months"> | ||
<item>@string/mozac_feature_prompts_jan</item> | ||
<item>@string/mozac_feature_prompts_feb</item> | ||
<item>@string/mozac_feature_prompts_mar</item> | ||
<item>@string/mozac_feature_prompts_apr</item> | ||
<item>@string/mozac_feature_prompts_may</item> | ||
<item>@string/mozac_feature_prompts_jun</item> | ||
<item>@string/mozac_feature_prompts_jul</item> | ||
<item>@string/mozac_feature_prompts_aug</item> | ||
<item>@string/mozac_feature_prompts_sep</item> | ||
<item>@string/mozac_feature_prompts_oct</item> | ||
<item>@string/mozac_feature_prompts_nov</item> | ||
<item>@string/mozac_feature_prompts_dec</item> | ||
</string-array> | ||
</resources> |
Oops, something went wrong.