diff --git a/gradle.properties b/gradle.properties index ec9ff61b..d3081ef8 100644 --- a/gradle.properties +++ b/gradle.properties @@ -16,8 +16,8 @@ org.gradle.jvmargs=-Xmx1536m # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true -VERSION_NAME=4.2.3 -VERSION_CODE=54 +VERSION_NAME=5.0.0 +VERSION_CODE=55 GROUP=com.wdullaer ANDROID_BUILD_MIN_SDK_VERSION=16 diff --git a/library/build.gradle b/library/build.gradle index 1418273d..8d6c2347 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -34,12 +34,12 @@ dependencies { implementation 'androidx.recyclerview:recyclerview:1.0.0' testImplementation 'junit:junit:4.12' - testImplementation 'com.pholser:junit-quickcheck-core:0.7' - testImplementation 'com.pholser:junit-quickcheck-generators:0.7' + testImplementation 'com.pholser:junit-quickcheck-core:0.9' + testImplementation 'com.pholser:junit-quickcheck-generators:0.9' androidTestImplementation 'androidx.test.ext:junit:1.1.1' androidTestImplementation 'androidx.test:runner:1.2.0' - androidTestImplementation 'androidx.test:rules:1.2.0' + androidTestImplementation 'androidx.test.ext:junit:1.1.1' } apply from: 'gradle-mvn-push.gradle' diff --git a/library/src/androidTest/java/com/wdullaer/materialdatetimepicker/date/DefaultDateRangeLimiterTest.java b/library/src/androidTest/java/com/wdullaer/materialdatetimepicker/date/DefaultDateRangeLimiterTest.java index 63464d83..1ffa965e 100644 --- a/library/src/androidTest/java/com/wdullaer/materialdatetimepicker/date/DefaultDateRangeLimiterTest.java +++ b/library/src/androidTest/java/com/wdullaer/materialdatetimepicker/date/DefaultDateRangeLimiterTest.java @@ -1,6 +1,7 @@ package com.wdullaer.materialdatetimepicker.date; import android.os.Parcel; + import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; @@ -8,7 +9,8 @@ import java.util.Calendar; -import static org.junit.Assert.*; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; /** * Unit tests for DefaultDateRangeLimiter which need to run on an android device diff --git a/library/src/androidTest/java/com/wdullaer/materialdatetimepicker/time/DefaultTimepointLimiterTest.java b/library/src/androidTest/java/com/wdullaer/materialdatetimepicker/time/DefaultTimepointLimiterTest.java index 7daed2a4..1df51f15 100644 --- a/library/src/androidTest/java/com/wdullaer/materialdatetimepicker/time/DefaultTimepointLimiterTest.java +++ b/library/src/androidTest/java/com/wdullaer/materialdatetimepicker/time/DefaultTimepointLimiterTest.java @@ -1,12 +1,14 @@ package com.wdullaer.materialdatetimepicker.time; import android.os.Parcel; + import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; -import static org.junit.Assert.*; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; /** * Unit tests for DefaultTimepointLimiter which need to run on an android device diff --git a/library/src/androidTest/java/com/wdullaer/materialdatetimepicker/time/TimepointTest.java b/library/src/androidTest/java/com/wdullaer/materialdatetimepicker/time/TimepointTest.java index 43cc7d8d..181423d9 100644 --- a/library/src/androidTest/java/com/wdullaer/materialdatetimepicker/time/TimepointTest.java +++ b/library/src/androidTest/java/com/wdullaer/materialdatetimepicker/time/TimepointTest.java @@ -1,12 +1,14 @@ package com.wdullaer.materialdatetimepicker.time; import android.os.Parcel; + import androidx.test.ext.junit.runners.AndroidJUnit4; -import static org.junit.Assert.*; import org.junit.Test; import org.junit.runner.RunWith; +import static org.junit.Assert.assertEquals; + /** * Test for Timepoint which need to run on an actual device * Created by wdullaer on 1/11/17. diff --git a/library/src/main/assets/fonts/Roboto-Medium.ttf b/library/src/main/assets/fonts/Roboto-Medium.ttf new file mode 100644 index 00000000..39c63d74 Binary files /dev/null and b/library/src/main/assets/fonts/Roboto-Medium.ttf differ diff --git a/library/src/main/java/com/wdullaer/materialdatetimepicker/GravitySnapHelper.java b/library/src/main/java/com/wdullaer/materialdatetimepicker/GravitySnapHelper.java index 8da8ef78..9507ec1c 100644 --- a/library/src/main/java/com/wdullaer/materialdatetimepicker/GravitySnapHelper.java +++ b/library/src/main/java/com/wdullaer/materialdatetimepicker/GravitySnapHelper.java @@ -18,15 +18,15 @@ package com.wdullaer.materialdatetimepicker; import android.os.Build; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.LinearSnapHelper; +import android.view.Gravity; +import android.view.View; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.LinearSnapHelper; import androidx.recyclerview.widget.OrientationHelper; import androidx.recyclerview.widget.RecyclerView; -import android.view.Gravity; -import android.view.View; /** * Enables snapping better snapping in a RecyclerView @@ -101,8 +101,6 @@ public int[] calculateDistanceToFinalSnap(@NonNull RecyclerView.LayoutManager la } else { // END out[0] = distanceToEnd(targetView, getHorizontalHelper(layoutManager), false); } - } else { - out[0] = 0; } if (layoutManager.canScrollVertically()) { @@ -111,8 +109,6 @@ public int[] calculateDistanceToFinalSnap(@NonNull RecyclerView.LayoutManager la } else { // BOTTOM out[1] = distanceToEnd(targetView, getVerticalHelper(layoutManager), false); } - } else { - out[1] = 0; } return out; diff --git a/library/src/main/java/com/wdullaer/materialdatetimepicker/HapticFeedbackController.java b/library/src/main/java/com/wdullaer/materialdatetimepicker/HapticFeedbackController.java index 01ef461e..5b41b4e8 100644 --- a/library/src/main/java/com/wdullaer/materialdatetimepicker/HapticFeedbackController.java +++ b/library/src/main/java/com/wdullaer/materialdatetimepicker/HapticFeedbackController.java @@ -54,8 +54,9 @@ public void start() { /** * Method to verify that vibrate permission has been granted. - * + *

* Allows users of the library to disabled vibrate support if desired. + * * @return true if Vibrate permission has been granted */ private boolean hasVibratePermission(Context context) { diff --git a/library/src/main/java/com/wdullaer/materialdatetimepicker/JalaliCalendar.java b/library/src/main/java/com/wdullaer/materialdatetimepicker/JalaliCalendar.java new file mode 100644 index 00000000..9501483b --- /dev/null +++ b/library/src/main/java/com/wdullaer/materialdatetimepicker/JalaliCalendar.java @@ -0,0 +1,854 @@ +package com.wdullaer.materialdatetimepicker; + +import android.annotation.SuppressLint; + +import java.util.Calendar; +import java.util.GregorianCalendar; +import java.util.Locale; +import java.util.TimeZone; + +public class JalaliCalendar extends Calendar { + private static int[] gregorianDaysInMonth = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + private static int[] jalaliDaysInMonth = {31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29}; + + private final static int FARVARDIN = 0; + private final static int ORDIBEHESHT = 1; + private final static int KHORDAD = 2; + private final static int TIR = 3; + private final static int MORDAD = 4; + private final static int SHAHRIVAR = 5; + private final static int MEHR = 6; + private final static int ABAN = 7; + private final static int AZAR = 8; + private final static int DEY = 9; + private final static int BAHMAN = 10; + private final static int ESFAND = 11; + + private final static String[] monthNames = {"فروردین", "اردیبهشت", "خرداد", "تیر", "مرداد", "شهریور", "مهر", "آبان", "آذر", "دی", "بهمن", "اسفند"}; + private final static String[] monthENLocaleNames = {"Farvardin", "Ordibehesht", "Khordad", "Tir", "Mordad", "Shahrivar", "Mehr", "Aban", "Azar", "Dey", "Bahman", "Esfand"}; + private final static String[] weekDayNames = {"یکشنبه", "دوشنبه", "سه شنبه", "چهارشنبه", "پنجشنبه", "جمعه", "شنبه"}; + private final static String[] weekDayENLocaleNames = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}; + + private static TimeZone timeZone = TimeZone.getDefault(); + private static Locale locale = Locale.getDefault(); + private static boolean isTimeSeted = false; + + private static final int ONE_SECOND = 1000; + private static final int ONE_MINUTE = 60 * ONE_SECOND; + private static final int ONE_HOUR = 60 * ONE_MINUTE; + private static final long ONE_DAY = 24 * ONE_HOUR; + private static final int BCE = 0; + private static final int CE = 1; + public static final int AD = 1; + private GregorianCalendar cal; + + private static final int[] MIN_VALUES = { + BCE, // ERA + 1, // YEAR + FARVARDIN, // MONTH + 1, // WEEK_OF_YEAR + 0, // WEEK_OF_MONTH + 1, // DAY_OF_MONTH + 1, // DAY_OF_YEAR + SATURDAY, // DAY_OF_WEEK + 1, // DAY_OF_WEEK_IN_MONTH + AM, // AM_PM + 0, // HOUR + 0, // HOUR_OF_DAY + 0, // MINUTE + 0, // SECOND + 0, // MILLISECOND + -13 * ONE_HOUR, // ZONE_OFFSET (UNIX compatibility) + 0 // DST_OFFSET + }; + + private static final int[] LEAST_MAX_VALUES = { + CE, // ERA + 292269054, // YEAR + ESFAND, // MONTH + 52, // WEEK_OF_YEAR + 4, // WEEK_OF_MONTH + 28, // DAY_OF_MONTH + 365, // DAY_OF_YEAR + FRIDAY, // DAY_OF_WEEK + 4, // DAY_OF_WEEK_IN + PM, // AM_PM + 11, // HOUR + 23, // HOUR_OF_DAY + 59, // MINUTE + 59, // SECOND + 999, // MILLISECOND + 14 * ONE_HOUR, // ZONE_OFFSET + 20 * ONE_MINUTE // DST_OFFSET (historical least maximum) + }; + + private static final int[] MAX_VALUES = { + CE, // ERA + 292278994, // YEAR + ESFAND, // MONTH + 53, // WEEK_OF_YEAR + 6, // WEEK_OF_MONTH + 31, // DAY_OF_MONTH + 366, // DAY_OF_YEAR + FRIDAY, // DAY_OF_WEEK + 6, // DAY_OF_WEEK_IN + PM, // AM_PM + 11, // HOUR + 23, // HOUR_OF_DAY + 59, // MINUTE + 59, // SECOND + 999, // MILLISECOND + 14 * ONE_HOUR, // ZONE_OFFSET + 2 * ONE_HOUR // DST_OFFSET (double summer time) + }; + + public JalaliCalendar() { + this(TimeZone.getDefault(), Locale.getDefault()); + } + + public JalaliCalendar(TimeZone zone) { + this(zone, Locale.getDefault()); + } + + public JalaliCalendar(Locale aLocale) { + this(TimeZone.getDefault(), aLocale); + } + + public JalaliCalendar(TimeZone zone, Locale aLocale) { + + super(zone, aLocale); + timeZone = zone; + locale = aLocale; + Calendar calendar = Calendar.getInstance(zone, aLocale); + + YearMonthDate yearMonthDate = new YearMonthDate(calendar.get(YEAR), calendar.get(MONTH), calendar.get(DATE)); + yearMonthDate = gregorianToJalali(yearMonthDate); + set(yearMonthDate.getYear(), yearMonthDate.getMonth(), yearMonthDate.getDate()); + complete(); + + } + + public JalaliCalendar(int year, int month, int dayOfMonth) { + this(year, month, dayOfMonth, 0, 0, 0, 0); + } + + public JalaliCalendar(int year, int month, int dayOfMonth, int hourOfDay, + int minute) { + this(year, month, dayOfMonth, hourOfDay, minute, 0, 0); + } + + public JalaliCalendar(int year, int month, int dayOfMonth, int hourOfDay, + int minute, int second) { + this(year, month, dayOfMonth, hourOfDay, minute, second, 0); + } + + public JalaliCalendar(int year, int month, int dayOfMonth, + int hourOfDay, int minute, int second, int millis) { + super(); + + this.set(YEAR, year); + this.set(MONTH, month); + this.set(DAY_OF_MONTH, dayOfMonth); + + if (hourOfDay >= 12 && hourOfDay <= 23) { + + this.set(AM_PM, PM); + this.set(HOUR, hourOfDay - 12); + } else { + this.set(HOUR, hourOfDay); + this.set(AM_PM, AM); + } + + this.set(HOUR_OF_DAY, hourOfDay); + this.set(MINUTE, minute); + this.set(SECOND, second); + + this.set(MILLISECOND, millis); + + YearMonthDate yearMonthDate = jalaliToGregorian(new YearMonthDate(fields[1], fields[2], fields[5])); + cal = new GregorianCalendar(yearMonthDate.getYear(), yearMonthDate.getMonth(), yearMonthDate.getDate(), hourOfDay, + minute, second); + time = cal.getTimeInMillis(); + + isTimeSeted = true; + } + + public static JalaliCalendar getInstance() { + return new JalaliCalendar(); + } + + public static JalaliCalendar getInstance(TimeZone zone) { + return new JalaliCalendar(zone); + } + + public static JalaliCalendar getInstance(TimeZone zone, Locale locale) { + return new JalaliCalendar(zone, locale); + } + + public String getMonthName() { + return locale.getLanguage().equals("fa") ? monthNames[get(MONTH)] : monthENLocaleNames[get(MONTH)]; + } + + public String getWeekDayName() { + return locale.getLanguage().equals("fa") ? weekDayNames[get(DAY_OF_WEEK) - 1] : weekDayENLocaleNames[get(DAY_OF_WEEK) - 1]; + } + + public static String getWeekDayName(int dayNum) { + return locale.getLanguage().equals("fa") ? weekDayNames[dayNum - 1] : weekDayENLocaleNames[dayNum - 1]; + } + + public static YearMonthDate gregorianToJalali(YearMonthDate gregorian) { + + if (gregorian.getMonth() > 11 || gregorian.getMonth() < -11) { + throw new IllegalArgumentException(); + } + int jalaliYear; + int jalaliMonth; + int jalaliDay; + + int gregorianDayNo, jalaliDayNo; + int jalaliNP; + int i; + + gregorian.setYear(gregorian.getYear() - 1600); + gregorian.setDate(gregorian.getDate() - 1); + + gregorianDayNo = 365 * gregorian.getYear() + (int) Math.floor((gregorian.getYear() + 3) / 4) + - (int) Math.floor((gregorian.getYear() + 99) / 100) + + (int) Math.floor((gregorian.getYear() + 399) / 400); + for (i = 0; i < gregorian.getMonth(); ++i) { + gregorianDayNo += gregorianDaysInMonth[i]; + } + + if (gregorian.getMonth() > 1 && ((gregorian.getYear() % 4 == 0 && gregorian.getYear() % 100 != 0) + || (gregorian.getYear() % 400 == 0))) { + ++gregorianDayNo; + } + + gregorianDayNo += gregorian.getDate(); + + jalaliDayNo = gregorianDayNo - 79; + + jalaliNP = (int) Math.floor(jalaliDayNo / 12053); + jalaliDayNo = jalaliDayNo % 12053; + + jalaliYear = 979 + 33 * jalaliNP + 4 * (int) (jalaliDayNo / 1461); + jalaliDayNo = jalaliDayNo % 1461; + + if (jalaliDayNo >= 366) { + jalaliYear += (int) Math.floor((jalaliDayNo - 1) / 365); + jalaliDayNo = (jalaliDayNo - 1) % 365; + } + + for (i = 0; i < 11 && jalaliDayNo >= jalaliDaysInMonth[i]; ++i) { + jalaliDayNo -= jalaliDaysInMonth[i]; + } + jalaliMonth = i; + jalaliDay = jalaliDayNo + 1; + + return new YearMonthDate(jalaliYear, jalaliMonth, jalaliDay); + } + + public static YearMonthDate jalaliToGregorian(YearMonthDate jalali) { + if (jalali.getMonth() > 11 || jalali.getMonth() < -11) { + throw new IllegalArgumentException(); + } + + int gregorianYear; + int gregorianMonth; + int gregorianDay; + + int gregorianDayNo, jalaliDayNo; + int leap; + + int i; + jalali.setYear(jalali.getYear() - 979); + jalali.setDate(jalali.getDate() - 1); + + jalaliDayNo = 365 * jalali.getYear() + (int) (jalali.getYear() / 33) * 8 + + (int) Math.floor(((jalali.getYear() % 33) + 3) / 4); + for (i = 0; i < jalali.getMonth(); ++i) { + jalaliDayNo += jalaliDaysInMonth[i]; + } + + jalaliDayNo += jalali.getDate(); + + gregorianDayNo = jalaliDayNo + 79; + + gregorianYear = 1600 + 400 * (int) Math.floor(gregorianDayNo / 146097); /* 146097 = 365*400 + 400/4 - 400/100 + 400/400 */ + gregorianDayNo = gregorianDayNo % 146097; + + leap = 1; + if (gregorianDayNo >= 36525) /* 36525 = 365*100 + 100/4 */ { + gregorianDayNo--; + gregorianYear += 100 * (int) Math.floor(gregorianDayNo / 36524); /* 36524 = 365*100 + 100/4 - 100/100 */ + gregorianDayNo = gregorianDayNo % 36524; + + if (gregorianDayNo >= 365) { + gregorianDayNo++; + } else { + leap = 0; + } + } + + gregorianYear += 4 * (int) Math.floor(gregorianDayNo / 1461); /* 1461 = 365*4 + 4/4 */ + gregorianDayNo = gregorianDayNo % 1461; + + if (gregorianDayNo >= 366) { + leap = 0; + + gregorianDayNo--; + gregorianYear += (int) Math.floor(gregorianDayNo / 365); + gregorianDayNo = gregorianDayNo % 365; + } + + for (i = 0; gregorianDayNo >= gregorianDaysInMonth[i] + ((i == 1 && leap == 1) ? i : 0); i++) { + gregorianDayNo -= gregorianDaysInMonth[i] + ((i == 1 && leap == 1) ? i : 0); + } + gregorianMonth = i; + gregorianDay = gregorianDayNo + 1; + + return new YearMonthDate(gregorianYear, gregorianMonth, gregorianDay); + + } + + public static int weekOfYear(int dayOfYear, int year) { + switch (dayOfWeek(JalaliCalendar.jalaliToGregorian(new YearMonthDate(year, 0, 1)))) { + case 2: + dayOfYear++; + break; + case 3: + dayOfYear += 2; + break; + case 4: + dayOfYear += 3; + break; + case 5: + dayOfYear += 4; + break; + case 6: + dayOfYear += 5; + break; + case 7: + dayOfYear--; + break; + } + ; + dayOfYear = (int) Math.floor(dayOfYear / 7); + return dayOfYear + 1; + } + + public static int dayOfWeek(YearMonthDate yearMonthDate) { + + Calendar cal = new GregorianCalendar(yearMonthDate.getYear(), yearMonthDate.getMonth(), yearMonthDate.getDate()); + return cal.get(DAY_OF_WEEK); + + } + + public static boolean isLeepYear(int year) { + //Algorithm from www.wikipedia.com + return (year % 33 == 1 || year % 33 == 5 || year % 33 == 9 || year % 33 == 13 || + year % 33 == 17 || year % 33 == 22 || year % 33 == 26 || year % 33 == 30); + } + + @Override + protected void computeTime() { + if (!isTimeSet && !isTimeSeted) { + Calendar cal = GregorianCalendar.getInstance(timeZone); + if (!isSet(HOUR_OF_DAY)) { + super.set(HOUR_OF_DAY, cal.get(HOUR_OF_DAY)); + } + if (!isSet(HOUR)) { + super.set(HOUR, cal.get(HOUR)); + } + if (!isSet(MINUTE)) { + super.set(MINUTE, cal.get(MINUTE)); + } + if (!isSet(SECOND)) { + super.set(SECOND, cal.get(SECOND)); + } + if (!isSet(MILLISECOND)) { + super.set(MILLISECOND, cal.get(MILLISECOND)); + } + if (!isSet(ZONE_OFFSET)) { + super.set(ZONE_OFFSET, cal.get(ZONE_OFFSET)); + } + if (!isSet(DST_OFFSET)) { + super.set(DST_OFFSET, cal.get(DST_OFFSET)); + } + if (!isSet(AM_PM)) { + super.set(AM_PM, cal.get(AM_PM)); + } + + if (internalGet(HOUR_OF_DAY) >= 12 && internalGet(HOUR_OF_DAY) <= 23) { + super.set(AM_PM, PM); + super.set(HOUR, internalGet(HOUR_OF_DAY) - 12); + } else { + super.set(HOUR, internalGet(HOUR_OF_DAY)); + super.set(AM_PM, AM); + } + + YearMonthDate yearMonthDate = jalaliToGregorian(new YearMonthDate(internalGet(YEAR), internalGet(MONTH), internalGet(DAY_OF_MONTH))); + cal.set(yearMonthDate.getYear(), yearMonthDate.getMonth(), yearMonthDate.getDate() + , internalGet(HOUR_OF_DAY), internalGet(MINUTE), internalGet(SECOND)); + time = cal.getTimeInMillis(); + + } else if (!isTimeSet && isTimeSeted) { + if (internalGet(HOUR_OF_DAY) >= 12 && internalGet(HOUR_OF_DAY) <= 23) { + super.set(AM_PM, PM); + super.set(HOUR, internalGet(HOUR_OF_DAY) - 12); + } else { + super.set(HOUR, internalGet(HOUR_OF_DAY)); + super.set(AM_PM, AM); + } + cal = new GregorianCalendar(); + super.set(ZONE_OFFSET, timeZone.getRawOffset()); + super.set(DST_OFFSET, timeZone.getDSTSavings()); + YearMonthDate yearMonthDate = jalaliToGregorian(new YearMonthDate(internalGet(YEAR), internalGet(MONTH), internalGet(DAY_OF_MONTH))); + cal.set(yearMonthDate.getYear(), yearMonthDate.getMonth(), yearMonthDate.getDate(), internalGet(HOUR_OF_DAY), + internalGet(MINUTE), internalGet(SECOND)); + time = cal.getTimeInMillis(); + } + } + + public void set(int field, int value) { + switch (field) { + case DATE: { + super.set(field, 0); + add(field, value); + break; + } + case MONTH: { + if (value > 11) { + super.set(field, 11); + add(field, value - 11); + } else if (value < 0) { + super.set(field, 0); + add(field, value); + } else { + super.set(field, value); + } + break; + } + case DAY_OF_YEAR: { + if (isSet(YEAR) && isSet(MONTH) && isSet(DAY_OF_MONTH)) { + super.set(YEAR, internalGet(YEAR)); + super.set(MONTH, 0); + super.set(DATE, 0); + add(field, value); + } else { + super.set(field, value); + } + break; + } + case WEEK_OF_YEAR: { + if (isSet(YEAR) && isSet(MONTH) && isSet(DAY_OF_MONTH)) { + add(field, value - get(WEEK_OF_YEAR)); + } else { + super.set(field, value); + } + break; + } + case WEEK_OF_MONTH: { + if (isSet(YEAR) && isSet(MONTH) && isSet(DAY_OF_MONTH)) { + add(field, value - get(WEEK_OF_MONTH)); + } else { + super.set(field, value); + } + break; + } + case DAY_OF_WEEK: { + if (isSet(YEAR) && isSet(MONTH) && isSet(DAY_OF_MONTH)) { + add(DAY_OF_WEEK, value % 7 - get(DAY_OF_WEEK)); + } else { + super.set(field, value); + } + break; + } + case HOUR_OF_DAY: + case HOUR: + case MINUTE: + case SECOND: + case MILLISECOND: + case ZONE_OFFSET: + case DST_OFFSET: { + if (isSet(YEAR) && isSet(MONTH) && isSet(DATE) && isSet(HOUR) && isSet(HOUR_OF_DAY) && + isSet(MINUTE) && isSet(SECOND) && isSet(MILLISECOND)) { + cal = new GregorianCalendar(); + YearMonthDate yearMonthDate = jalaliToGregorian(new YearMonthDate(internalGet(YEAR), internalGet(MONTH), internalGet(DATE))); + cal.set(yearMonthDate.getYear(), yearMonthDate.getMonth(), yearMonthDate.getDate(), internalGet(HOUR_OF_DAY), internalGet(MINUTE), + internalGet(SECOND)); + cal.set(field, value); + yearMonthDate = gregorianToJalali(new YearMonthDate(cal.get(YEAR), cal.get(MONTH), cal.get(DATE))); + super.set(YEAR, yearMonthDate.getYear()); + super.set(MONTH, yearMonthDate.getMonth()); + super.set(DATE, yearMonthDate.getDate()); + super.set(HOUR_OF_DAY, cal.get(HOUR_OF_DAY)); + super.set(MINUTE, cal.get(MINUTE)); + super.set(SECOND, cal.get(SECOND)); + + } else { + super.set(field, value); + } + break; + } + + + default: { + super.set(field, value); + } + } + } + + @SuppressLint("WrongConstant") + @Override + protected void computeFields() { + boolean temp = isTimeSet; + if (!areFieldsSet) { + setMinimalDaysInFirstWeek(1); + setFirstDayOfWeek(7); + + //Day_Of_Year + int dayOfYear = 0; + int index = 0; + + while (index < fields[2]) { + dayOfYear += jalaliDaysInMonth[index++]; + } + dayOfYear += fields[5]; + super.set(DAY_OF_YEAR, dayOfYear); + //*** + + //Day_of_Week + super.set(DAY_OF_WEEK, dayOfWeek(jalaliToGregorian(new YearMonthDate(fields[1], fields[2], fields[5])))); + //*** + + //Day_Of_Week_In_Month + if (0 < fields[5] && fields[5] < 8) { + super.set(DAY_OF_WEEK_IN_MONTH, 1); + } + + if (7 < fields[5] && fields[5] < 15) { + super.set(DAY_OF_WEEK_IN_MONTH, 2); + } + + if (14 < fields[5] && fields[5] < 22) { + super.set(DAY_OF_WEEK_IN_MONTH, 3); + } + + if (21 < fields[5] && fields[5] < 29) { + super.set(DAY_OF_WEEK_IN_MONTH, 4); + } + + if (28 < fields[5] && fields[5] < 32) { + super.set(DAY_OF_WEEK_IN_MONTH, 5); + } + //*** + + //Week_Of_Year + super.set(WEEK_OF_YEAR, weekOfYear(fields[6], fields[1])); + //*** + + //Week_Of_Month + super.set(WEEK_OF_MONTH, weekOfYear(fields[6], fields[1]) - weekOfYear(fields[6] - fields[5], fields[1]) + 1); + // + + isTimeSet = temp; + } + } + + @Override + public void add(int field, int amount) { + + if (field == MONTH) { + amount += get(MONTH); + add(YEAR, amount / 12); + super.set(MONTH, amount % 12); + if (get(DAY_OF_MONTH) > jalaliDaysInMonth[amount % 12]) { + super.set(DAY_OF_MONTH, jalaliDaysInMonth[amount % 12]); + if (get(MONTH) == 11 && isLeepYear(get(YEAR))) { + super.set(DAY_OF_MONTH, 30); + } + } + complete(); + + } else if (field == YEAR) { + + super.set(YEAR, get(YEAR) + amount); + if (get(DAY_OF_MONTH) == 30 && get(MONTH) == 11 && !isLeepYear(get(YEAR))) { + super.set(DAY_OF_MONTH, 29); + } + + complete(); + } else { + YearMonthDate yearMonthDate = jalaliToGregorian(new YearMonthDate(get(YEAR), get(MONTH), get(DATE))); + Calendar gc = new GregorianCalendar(yearMonthDate.getYear(), yearMonthDate.getMonth(), yearMonthDate.getDate(), + get(HOUR_OF_DAY), get(MINUTE), get(SECOND)); + gc.add(field, amount); + yearMonthDate = gregorianToJalali(new YearMonthDate(gc.get(YEAR), gc.get(MONTH), gc.get(DATE))); + super.set(YEAR, yearMonthDate.getYear()); + super.set(MONTH, yearMonthDate.getMonth()); + super.set(DATE, yearMonthDate.getDate()); + super.set(HOUR_OF_DAY, gc.get(HOUR_OF_DAY)); + super.set(MINUTE, gc.get(MINUTE)); + super.set(SECOND, gc.get(SECOND)); + complete(); + } + + } + + @Override + public void roll(int field, boolean up) { + roll(field, up ? +1 : -1); + } + + @Override + public void roll(int field, int amount) { + if (amount == 0) { + return; + } + + if (field < 0 || field >= ZONE_OFFSET) { + throw new IllegalArgumentException(); + } + + complete(); + + switch (field) { + case AM_PM: { + if (amount % 2 != 0) { + if (internalGet(AM_PM) == AM) { + fields[AM_PM] = PM; + } else { + fields[AM_PM] = AM; + } + if (get(AM_PM) == AM) { + super.set(HOUR_OF_DAY, get(HOUR)); + } else { + super.set(HOUR_OF_DAY, get(HOUR) + 12); + } + } + break; + } + case YEAR: { + super.set(YEAR, internalGet(YEAR) + amount); + if (internalGet(MONTH) == 11 && internalGet(DAY_OF_MONTH) == 30 && !isLeepYear(internalGet(YEAR))) { + super.set(DAY_OF_MONTH, 29); + } + break; + } + case MINUTE: { + int unit = 60; + int m = (internalGet(MINUTE) + amount) % unit; + if (m < 0) { + m += unit; + } + super.set(MINUTE, m); + break; + } + case SECOND: { + int unit = 60; + int s = (internalGet(SECOND) + amount) % unit; + if (s < 0) { + s += unit; + } + super.set(SECOND, s); + break; + } + case MILLISECOND: { + int unit = 1000; + int ms = (internalGet(MILLISECOND) + amount) % unit; + if (ms < 0) { + ms += unit; + } + super.set(MILLISECOND, ms); + break; + } + + case HOUR: { + super.set(HOUR, (internalGet(HOUR) + amount) % 12); + if (internalGet(HOUR) < 0) { + fields[HOUR] += 12; + } + if (internalGet(AM_PM) == AM) { + super.set(HOUR_OF_DAY, internalGet(HOUR)); + } else { + super.set(HOUR_OF_DAY, internalGet(HOUR) + 12); + } + + break; + } + case HOUR_OF_DAY: { + fields[HOUR_OF_DAY] = (internalGet(HOUR_OF_DAY) + amount) % 24; + if (internalGet(HOUR_OF_DAY) < 0) { + fields[HOUR_OF_DAY] += 24; + } + if (internalGet(HOUR_OF_DAY) < 12) { + fields[AM_PM] = AM; + fields[HOUR] = internalGet(HOUR_OF_DAY); + } else { + fields[AM_PM] = PM; + fields[HOUR] = internalGet(HOUR_OF_DAY) - 12; + } + + } + case MONTH: { + int mon = (internalGet(MONTH) + amount) % 12; + if (mon < 0) { + mon += 12; + } + super.set(MONTH, mon); + + int monthLen = jalaliDaysInMonth[mon]; + if (internalGet(MONTH) == 11 && isLeepYear(internalGet(YEAR))) { + monthLen = 30; + } + if (internalGet(DAY_OF_MONTH) > monthLen) { + super.set(DAY_OF_MONTH, monthLen); + } + break; + } + case DAY_OF_MONTH: { + int unit = 0; + if (0 <= get(MONTH) && get(MONTH) <= 5) { + unit = 31; + } + if (6 <= get(MONTH) && get(MONTH) <= 10) { + unit = 30; + } + if (get(MONTH) == 11) { + if (isLeepYear(get(YEAR))) { + unit = 30; + } else { + unit = 29; + } + } + int d = (get(DAY_OF_MONTH) + amount) % unit; + if (d < 0) { + d += unit; + } + super.set(DAY_OF_MONTH, d); + break; + + } + case WEEK_OF_YEAR: { + break; + } + case DAY_OF_YEAR: { + int unit = (isLeepYear(internalGet(YEAR)) ? 366 : 365); + int dayOfYear = (internalGet(DAY_OF_YEAR) + amount) % unit; + dayOfYear = (dayOfYear > 0) ? dayOfYear : dayOfYear + unit; + int month = 0, temp = 0; + while (dayOfYear > temp) { + temp += jalaliDaysInMonth[month++]; + } + super.set(MONTH, --month); + super.set(DAY_OF_MONTH, jalaliDaysInMonth[internalGet(MONTH)] - (temp - dayOfYear)); + break; + } + case DAY_OF_WEEK: { + int index = amount % 7; + if (index < 0) { + index += 7; + } + int i = 0; + while (i != index) { + if (internalGet(DAY_OF_WEEK) == FRIDAY) { + add(DAY_OF_MONTH, -6); + } else { + add(DAY_OF_MONTH, +1); + } + i++; + } + break; + } + + default: + throw new IllegalArgumentException(); + } + + } + + @Override + public int getMinimum(int field) { + return MIN_VALUES[field]; + } + + @Override + public int getMaximum(int field) { + return MAX_VALUES[field]; + } + + @Override + public int getGreatestMinimum(int field) { + return MIN_VALUES[field]; + } + + @Override + public int getLeastMaximum(int field) { + return LEAST_MAX_VALUES[field]; + } + + public static class YearMonthDate { + + YearMonthDate(int year, int month, int date) { + this.year = year; + this.month = month; + this.date = date; + } + + private int year; + private int month; + private int date; + + public int getYear() { + return year; + } + + public void setYear(int year) { + this.year = year; + } + + public int getMonth() { + return month; + } + + public void setMonth(int month) { + this.month = month; + } + + public int getDate() { + return date; + } + + public void setDate(int date) { + this.date = date; + } + + public String toString() { + return getYear() + "/" + getMonth() + "/" + getDate(); + } + } + + @Override + public int getFirstDayOfWeek() { + return SATURDAY; + } + + @Override + public int getActualMaximum(int field) { + if (field == Calendar.DAY_OF_MONTH) { + return jalaliDaysInMonth[get(MONTH)]; + } + return super.getActualMaximum(field); + } + + public static void setLocale(Locale locale) { + JalaliCalendar.locale = locale; + } + + public static Locale getLocale() { + return locale; + } +} + diff --git a/library/src/main/java/com/wdullaer/materialdatetimepicker/Utils.java b/library/src/main/java/com/wdullaer/materialdatetimepicker/Utils.java index 6fc4d1cc..2ad3ce87 100644 --- a/library/src/main/java/com/wdullaer/materialdatetimepicker/Utils.java +++ b/library/src/main/java/com/wdullaer/materialdatetimepicker/Utils.java @@ -23,12 +23,14 @@ import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Color; +import android.graphics.Typeface; import android.os.Build; -import androidx.annotation.AttrRes; -import androidx.core.content.ContextCompat; import android.util.TypedValue; import android.view.View; +import androidx.annotation.AttrRes; +import androidx.core.content.ContextCompat; + import java.util.Calendar; /** @@ -46,8 +48,14 @@ public class Utils { // Alpha level for fully opaque. public static final int FULL_ALPHA = 255; + /** + * user custom font used shared among all classes + */ + private static Typeface customFont; + /** * Try to speak the specified text, for accessibility. Only available on JB or later. + * * @param text Text to announce. */ public static void tryAccessibilityAnnounce(View view, CharSequence text) { @@ -58,11 +66,12 @@ public static void tryAccessibilityAnnounce(View view, CharSequence text) { /** * Render an animator to pulsate a view in place. + * * @param labelToAnimate the view to pulsate. * @return The animator object. Use .start() to begin. */ public static ObjectAnimator getPulseAnimator(View labelToAnimate, float decreaseRatio, - float increaseRatio) { + float increaseRatio) { Keyframe k0 = Keyframe.ofFloat(0f, 1f); Keyframe k1 = Keyframe.ofFloat(0.275f, decreaseRatio); Keyframe k2 = Keyframe.ofFloat(0.69f, increaseRatio); @@ -81,7 +90,7 @@ public static ObjectAnimator getPulseAnimator(View labelToAnimate, float decreas * Convert Dp to Pixel */ @SuppressWarnings("unused") - public static int dpToPx(float dp, Resources resources){ + public static int dpToPx(float dp, Resources resources) { float px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, resources.getDisplayMetrics()); return (int) px; } @@ -95,6 +104,7 @@ public static int darkenColor(int color) { /** * Gets the colorAccent from the current context, if possible/available + * * @param context The context to use as reference for the color * @return the accent color of the current context */ @@ -116,6 +126,7 @@ public static int getAccentColorFromThemeIfAvailable(Context context) { /** * Gets dialog type (Light/Dark) from current theme + * * @param context The context to use as reference for the boolean * @param current Default value to return if cannot resolve the attribute * @return true if dark mode, false if light. @@ -126,8 +137,9 @@ public static boolean isDarkTheme(Context context, boolean current) { /** * Gets the required boolean value from the current context, if possible/available - * @param context The context to use as reference for the boolean - * @param attr Attribute id to resolve + * + * @param context The context to use as reference for the boolean + * @param attr Attribute id to resolve * @param fallback Default value to return if no value is specified in theme * @return the boolean value from current theme */ @@ -154,4 +166,12 @@ public static Calendar trimToMidnight(Calendar calendar) { calendar.set(Calendar.MILLISECOND, 0); return calendar; } + + synchronized public static void setCustomFont(Typeface customFont) { + Utils.customFont = customFont; + } + + synchronized public static Typeface getCustomFont() { + return customFont; + } } diff --git a/library/src/main/java/com/wdullaer/materialdatetimepicker/VerticalTextView.java b/library/src/main/java/com/wdullaer/materialdatetimepicker/VerticalTextView.java index 6a62b33a..6b40da6d 100644 --- a/library/src/main/java/com/wdullaer/materialdatetimepicker/VerticalTextView.java +++ b/library/src/main/java/com/wdullaer/materialdatetimepicker/VerticalTextView.java @@ -14,11 +14,11 @@ public class VerticalTextView extends androidx.appcompat.widget.AppCompatTextView { final boolean topDown; - public VerticalTextView(Context context, AttributeSet attrs){ + public VerticalTextView(Context context, AttributeSet attrs) { super(context, attrs); final int gravity = getGravity(); - if (Gravity.isVertical(gravity) && (gravity&Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { - setGravity((gravity&Gravity.HORIZONTAL_GRAVITY_MASK) | Gravity.TOP); + if (Gravity.isVertical(gravity) && (gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { + setGravity((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) | Gravity.TOP); topDown = false; } else { topDown = true; @@ -26,21 +26,21 @@ public VerticalTextView(Context context, AttributeSet attrs){ } @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){ + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //noinspection SuspiciousNameCombination super.onMeasure(heightMeasureSpec, widthMeasureSpec); setMeasuredDimension(getMeasuredHeight(), getMeasuredWidth()); } @Override - protected void onDraw(Canvas canvas){ + protected void onDraw(Canvas canvas) { TextPaint textPaint = getPaint(); textPaint.setColor(getCurrentTextColor()); textPaint.drawableState = getDrawableState(); canvas.save(); - if (topDown){ + if (topDown) { canvas.translate(getWidth(), 0); canvas.rotate(90); } else { diff --git a/library/src/main/java/com/wdullaer/materialdatetimepicker/date/DatePickerController.java b/library/src/main/java/com/wdullaer/materialdatetimepicker/date/DatePickerController.java index 161f7ba1..b8448e80 100644 --- a/library/src/main/java/com/wdullaer/materialdatetimepicker/date/DatePickerController.java +++ b/library/src/main/java/com/wdullaer/materialdatetimepicker/date/DatePickerController.java @@ -16,6 +16,12 @@ package com.wdullaer.materialdatetimepicker.date; +import android.graphics.Typeface; + +import com.wdullaer.materialdatetimepicker.enums.CalendarType; +import com.wdullaer.materialdatetimepicker.enums.ScrollOrientation; +import com.wdullaer.materialdatetimepicker.enums.Version; + import java.util.Calendar; import java.util.Locale; import java.util.TimeZone; @@ -39,7 +45,7 @@ public interface DatePickerController { boolean isThemeDark(); int getAccentColor(); - + boolean isHighlighted(int year, int month, int day); int getFirstDayOfWeek(); @@ -60,7 +66,11 @@ public interface DatePickerController { Locale getLocale(); - DatePickerDialog.Version getVersion(); + Version getVersion(); + + ScrollOrientation getScrollOrientation(); + + CalendarType getCalendarType(); - DatePickerDialog.ScrollOrientation getScrollOrientation(); + void setFont(Typeface customFont); } diff --git a/library/src/main/java/com/wdullaer/materialdatetimepicker/date/DatePickerDialog.java b/library/src/main/java/com/wdullaer/materialdatetimepicker/date/DatePickerDialog.java index 040822aa..8a0cef49 100644 --- a/library/src/main/java/com/wdullaer/materialdatetimepicker/date/DatePickerDialog.java +++ b/library/src/main/java/com/wdullaer/materialdatetimepicker/date/DatePickerDialog.java @@ -22,10 +22,12 @@ import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Color; +import android.graphics.Typeface; import android.os.Build; import android.os.Bundle; import android.text.format.DateFormat; import android.text.format.DateUtils; +import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; @@ -36,15 +38,21 @@ import android.widget.Button; import android.widget.LinearLayout; import android.widget.TextView; + import androidx.annotation.ColorInt; import androidx.annotation.NonNull; import androidx.annotation.StringRes; import androidx.appcompat.app.AppCompatDialogFragment; import androidx.core.content.ContextCompat; import androidx.core.content.res.ResourcesCompat; + import com.wdullaer.materialdatetimepicker.HapticFeedbackController; +import com.wdullaer.materialdatetimepicker.JalaliCalendar; import com.wdullaer.materialdatetimepicker.R; import com.wdullaer.materialdatetimepicker.Utils; +import com.wdullaer.materialdatetimepicker.enums.CalendarType; +import com.wdullaer.materialdatetimepicker.enums.ScrollOrientation; +import com.wdullaer.materialdatetimepicker.enums.Version; import java.text.SimpleDateFormat; import java.util.Arrays; @@ -59,16 +67,6 @@ public class DatePickerDialog extends AppCompatDialogFragment implements OnClickListener, DatePickerController { - public enum Version { - VERSION_1, - VERSION_2 - } - - public enum ScrollOrientation { - HORIZONTAL, - VERTICAL - } - private static final int UNINITIALIZED = -1; private static final int MONTH_AND_DAY_VIEW = 0; private static final int YEAR_VIEW = 1; @@ -100,6 +98,7 @@ public enum ScrollOrientation { private static final String KEY_DATERANGELIMITER = "daterangelimiter"; private static final String KEY_SCROLL_ORIENTATION = "scrollorientation"; private static final String KEY_LOCALE = "locale"; + private static final String KEY_CALENDAR_TYPE = "calendarType"; private static final int ANIMATION_DURATION = 300; private static final int ANIMATION_DELAY = 500; @@ -109,7 +108,8 @@ public enum ScrollOrientation { private static SimpleDateFormat DAY_FORMAT = new SimpleDateFormat("dd", Locale.getDefault()); private static SimpleDateFormat VERSION_2_FORMAT; - private Calendar mCalendar = Utils.trimToMidnight(Calendar.getInstance(getTimeZone())); + private Calendar mCalendar; + private CalendarType mCalendarType; private OnDateSetListener mCallBack; private HashSet mListeners = new HashSet<>(); private DialogInterface.OnCancelListener mOnCancelListener; @@ -117,6 +117,8 @@ public enum ScrollOrientation { private AccessibleDateAnimator mAnimator; + private Button mOkButton; + private Button mCancelButton; private TextView mDatePickerHeaderView; private LinearLayout mMonthAndDayView; private TextView mSelectedMonthTextView; @@ -127,7 +129,7 @@ public enum ScrollOrientation { private int mCurrentView = UNINITIALIZED; - private int mWeekStart = mCalendar.getFirstDayOfWeek(); + private int mWeekStart; private String mTitle; private HashSet highlightedDays = new HashSet<>(); private boolean mThemeDark = false; @@ -182,54 +184,98 @@ protected interface OnDateChangedListener { void onDateChanged(); } - public DatePickerDialog() { // Empty constructor required for dialog fragment. } /** * Create a new DatePickerDialog instance with a specific initial selection. - * @param callBack How the parent is notified that the date is set. - * @param year The initial year of the dialog. - * @param monthOfYear The initial month of the dialog. - * @param dayOfMonth The initial day of the dialog. + * + * @param callBack How the parent is notified that the date is set. + * @param calendarType calendarType of calendar + * @param year The initial year of the dialog. + * @param monthOfYear The initial month of the dialog. + * @param dayOfMonth The initial day of the dialog. * @return a new DatePickerDialog instance. */ - public static DatePickerDialog newInstance(OnDateSetListener callBack, int year, int monthOfYear, int dayOfMonth) { + public static DatePickerDialog newInstance( + OnDateSetListener callBack, + CalendarType calendarType, + int year, + int monthOfYear, + int dayOfMonth + ) { DatePickerDialog ret = new DatePickerDialog(); - ret.initialize(callBack, year, monthOfYear, dayOfMonth); + ret.initialize(callBack, calendarType, year, monthOfYear, dayOfMonth); return ret; } /** * Create a new DatePickerDialog instance initialised to the current system date. - * @param callback How the parent is notified that the date is set. + * + * @param callback How the parent is notified that the date is set. + * @param calendarType calendarType of calendar * @return a new DatePickerDialog instance */ @SuppressWarnings({"unused", "WeakerAccess"}) - public static DatePickerDialog newInstance(OnDateSetListener callback) { - Calendar now = Calendar.getInstance(); - return DatePickerDialog.newInstance(callback, now); + public static DatePickerDialog newInstance( + OnDateSetListener callback, + CalendarType calendarType + ) { + Calendar now; + switch (calendarType) { + case JALALI: + now = JalaliCalendar.getInstance(); + break; + case GREGORIAN: + default: + now = Calendar.getInstance(); + break; + } + + return DatePickerDialog.newInstance(callback, calendarType, now); } /** * Create a new DatePickerDialog instance with a specific initial selection. + * * @param callback How the parent is notified that the date is set. + * @param calendarType calendarType of calendar * @param initialSelection A Calendar object containing the original selection of the picker. * (Time is ignored by trimming the Calendar to midnight in the current * TimeZone of the Calendar object) * @return a new DatePickerDialog instance */ @SuppressWarnings({"unused", "WeakerAccess"}) - public static DatePickerDialog newInstance(OnDateSetListener callback, Calendar initialSelection) { + public static DatePickerDialog newInstance( + OnDateSetListener callback, + CalendarType calendarType, + Calendar initialSelection + ) { DatePickerDialog ret = new DatePickerDialog(); - ret.initialize(callback, initialSelection); + ret.initialize(callback, calendarType, initialSelection); return ret; } - public void initialize(OnDateSetListener callBack, Calendar initialSelection) { + private void initialize( + OnDateSetListener callBack, + CalendarType calendarType, + Calendar initialSelection + ) { mCallBack = callBack; - mCalendar = Utils.trimToMidnight((Calendar) initialSelection.clone()); + this.setCalendarType(calendarType); + switch (calendarType) { + case JALALI: + mCalendar = Utils.trimToMidnight((JalaliCalendar) initialSelection.clone()); + mDefaultLimiter.setYearRange(1300, 1500); + break; + case GREGORIAN: + default: + mCalendar = Utils.trimToMidnight((Calendar) initialSelection.clone()); + mDefaultLimiter.setYearRange(1900, 2100); + break; + } + mWeekStart = mCalendar.getFirstDayOfWeek(); mScrollOrientation = null; //noinspection deprecation setTimeZone(mCalendar.getTimeZone()); @@ -237,12 +283,28 @@ public void initialize(OnDateSetListener callBack, Calendar initialSelection) { mVersion = Build.VERSION.SDK_INT < Build.VERSION_CODES.M ? Version.VERSION_1 : Version.VERSION_2; } - public void initialize(OnDateSetListener callBack, int year, int monthOfYear, int dayOfMonth) { - Calendar cal = Calendar.getInstance(getTimeZone()); + public void initialize( + OnDateSetListener callBack, + CalendarType calendarType, + int year, + int monthOfYear, + int dayOfMonth + ) { + Calendar cal; + switch (calendarType) { + case JALALI: + cal = JalaliCalendar.getInstance(getTimeZone(), getLocale()); + break; + case GREGORIAN: + default: + cal = Calendar.getInstance(getTimeZone()); + break; + } + cal.set(Calendar.YEAR, year); cal.set(Calendar.MONTH, monthOfYear); cal.set(Calendar.DAY_OF_MONTH, dayOfMonth); - this.initialize(callBack, cal); + this.initialize(callBack, calendarType, cal); } @Override @@ -253,13 +315,7 @@ public void onCreate(Bundle savedInstanceState) { WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN); setStyle(AppCompatDialogFragment.STYLE_NO_TITLE, 0); mCurrentView = UNINITIALIZED; - if (savedInstanceState != null) { - mCalendar.set(Calendar.YEAR, savedInstanceState.getInt(KEY_SELECTED_YEAR)); - mCalendar.set(Calendar.MONTH, savedInstanceState.getInt(KEY_SELECTED_MONTH)); - mCalendar.set(Calendar.DAY_OF_MONTH, savedInstanceState.getInt(KEY_SELECTED_DAY)); - mDefaultView = savedInstanceState.getInt(KEY_DEFAULT_VIEW); - } - if (Build.VERSION.SDK_INT < 18) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) { VERSION_2_FORMAT = new SimpleDateFormat(activity.getResources().getString(R.string.mdtp_date_v2_daymonthyear), mLocale); } else { VERSION_2_FORMAT = new SimpleDateFormat(DateFormat.getBestDateTimePattern(mLocale, "EEEMMMdd"), mLocale); @@ -270,6 +326,7 @@ public void onCreate(Bundle savedInstanceState) { @Override public void onSaveInstanceState(@NonNull Bundle outState) { super.onSaveInstanceState(outState); + outState.putSerializable(KEY_CALENDAR_TYPE, mCalendarType); outState.putInt(KEY_SELECTED_YEAR, mCalendar.get(Calendar.YEAR)); outState.putInt(KEY_SELECTED_MONTH, mCalendar.get(Calendar.MONTH)); outState.putInt(KEY_SELECTED_DAY, mCalendar.get(Calendar.DAY_OF_MONTH)); @@ -317,6 +374,20 @@ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, : ScrollOrientation.HORIZONTAL; } if (savedInstanceState != null) { + this.setCalendarType((CalendarType) savedInstanceState.get(KEY_CALENDAR_TYPE)); + switch (mCalendarType) { + case JALALI: + mCalendar = JalaliCalendar.getInstance(getTimeZone(), getLocale()); + break; + case GREGORIAN: + default: + mCalendar = Calendar.getInstance(getTimeZone()); + break; + } + mCalendar.set(Calendar.YEAR, savedInstanceState.getInt(KEY_SELECTED_YEAR)); + mCalendar.set(Calendar.MONTH, savedInstanceState.getInt(KEY_SELECTED_MONTH)); + mCalendar.set(Calendar.DAY_OF_MONTH, savedInstanceState.getInt(KEY_SELECTED_DAY)); + mDefaultView = savedInstanceState.getInt(KEY_DEFAULT_VIEW); mWeekStart = savedInstanceState.getInt(KEY_WEEK_START); currentView = savedInstanceState.getInt(KEY_CURRENT_VIEW); listPosition = savedInstanceState.getInt(KEY_LIST_POSITION); @@ -325,17 +396,23 @@ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, highlightedDays = (HashSet) savedInstanceState.getSerializable(KEY_HIGHLIGHTED_DAYS); mThemeDark = savedInstanceState.getBoolean(KEY_THEME_DARK); mThemeDarkChanged = savedInstanceState.getBoolean(KEY_THEME_DARK_CHANGED); - if (savedInstanceState.containsKey(KEY_ACCENT)) mAccentColor = savedInstanceState.getInt(KEY_ACCENT); + if (savedInstanceState.containsKey(KEY_ACCENT)) { + mAccentColor = savedInstanceState.getInt(KEY_ACCENT); + } mVibrate = savedInstanceState.getBoolean(KEY_VIBRATE); mDismissOnPause = savedInstanceState.getBoolean(KEY_DISMISS); mAutoDismiss = savedInstanceState.getBoolean(KEY_AUTO_DISMISS); mTitle = savedInstanceState.getString(KEY_TITLE); mOkResid = savedInstanceState.getInt(KEY_OK_RESID); mOkString = savedInstanceState.getString(KEY_OK_STRING); - if (savedInstanceState.containsKey(KEY_OK_COLOR)) mOkColor = savedInstanceState.getInt(KEY_OK_COLOR); + if (savedInstanceState.containsKey(KEY_OK_COLOR)) { + mOkColor = savedInstanceState.getInt(KEY_OK_COLOR); + } mCancelResid = savedInstanceState.getInt(KEY_CANCEL_RESID); mCancelString = savedInstanceState.getString(KEY_CANCEL_STRING); - if (savedInstanceState.containsKey(KEY_CANCEL_COLOR)) mCancelColor = savedInstanceState.getInt(KEY_CANCEL_COLOR); + if (savedInstanceState.containsKey(KEY_CANCEL_COLOR)) { + mCancelColor = savedInstanceState.getInt(KEY_CANCEL_COLOR); + } mVersion = (Version) savedInstanceState.getSerializable(KEY_VERSION); mScrollOrientation = (ScrollOrientation) savedInstanceState.getSerializable(KEY_SCROLL_ORIENTATION); mTimezone = (TimeZone) savedInstanceState.getSerializable(KEY_TIMEZONE); @@ -377,6 +454,20 @@ affect the behaviour of the picker (in the unlikely event the user reconfigures mYearView = view.findViewById(R.id.mdtp_date_picker_year); mYearView.setOnClickListener(this); + if (mVersion == Version.VERSION_2) { + switch (mCalendarType) { + case JALALI: + mYearView.setGravity(Gravity.END); + mMonthAndDayView.setGravity(Gravity.END); + break; + case GREGORIAN: + default: + mYearView.setGravity(Gravity.START); + mMonthAndDayView.setGravity(Gravity.START); + break; + } + } + final Activity activity = requireActivity(); mDayPickerView = new DayPickerGroup(activity, this); mYearPickerView = new YearPickerView(activity, this); @@ -409,43 +500,43 @@ affect the behaviour of the picker (in the unlikely event the user reconfigures animation2.setDuration(ANIMATION_DURATION); mAnimator.setOutAnimation(animation2); - Button okButton = view.findViewById(R.id.mdtp_ok); - okButton.setOnClickListener(v -> { + mOkButton = view.findViewById(R.id.mdtp_ok); + mOkButton.setOnClickListener(v -> { tryVibrate(); notifyOnDateListener(); dismiss(); }); - okButton.setTypeface(ResourcesCompat.getFont(activity, R.font.robotomedium)); - if (mOkString != null) okButton.setText(mOkString); - else okButton.setText(mOkResid); + if (mOkString != null) mOkButton.setText(mOkString); + else mOkButton.setText(mOkResid); - Button cancelButton = view.findViewById(R.id.mdtp_cancel); - cancelButton.setOnClickListener(v -> { + mCancelButton = view.findViewById(R.id.mdtp_cancel); + mCancelButton.setOnClickListener(v -> { tryVibrate(); if (getDialog() != null) getDialog().cancel(); }); - cancelButton.setTypeface(ResourcesCompat.getFont(activity, R.font.robotomedium)); - if (mCancelString != null) cancelButton.setText(mCancelString); - else cancelButton.setText(mCancelResid); - cancelButton.setVisibility(isCancelable() ? View.VISIBLE : View.GONE); + if (mCancelString != null) mCancelButton.setText(mCancelString); + else mCancelButton.setText(mCancelResid); + mCancelButton.setVisibility(isCancelable() ? View.VISIBLE : View.GONE); // If an accent color has not been set manually, get it from the context if (mAccentColor == null) { mAccentColor = Utils.getAccentColorFromThemeIfAvailable(getActivity()); } - if (mDatePickerHeaderView != null) mDatePickerHeaderView.setBackgroundColor(Utils.darkenColor(mAccentColor)); + if (mDatePickerHeaderView != null) { + mDatePickerHeaderView.setBackgroundColor(Utils.darkenColor(mAccentColor)); + } view.findViewById(R.id.mdtp_day_picker_selected_date_layout).setBackgroundColor(mAccentColor); // Buttons can have a different color if (mOkColor == null) { mOkColor = mAccentColor; } - okButton.setTextColor(mOkColor); + mOkButton.setTextColor(mOkColor); if (mCancelColor == null) { mCancelColor = mAccentColor; } - cancelButton.setTextColor(mCancelColor); + mCancelButton.setTextColor(mCancelColor); if (getDialog() == null) { view.findViewById(R.id.mdtp_done_background).setVisibility(View.GONE); @@ -463,11 +554,31 @@ affect the behaviour of the picker (in the unlikely event the user reconfigures } mHapticFeedbackController = new HapticFeedbackController(activity); + + setUiFont(); + return view; } + private void setUiFont() { + final Typeface font = Utils.getCustomFont(); + + if (font == null) { + final Activity activity = requireActivity(); + mOkButton.setTypeface(ResourcesCompat.getFont(activity, R.font.robotomedium)); + mCancelButton.setTypeface(ResourcesCompat.getFont(activity, R.font.robotomedium)); + } else { + if (mOkButton != null) mOkButton.setTypeface(font); + if (mCancelButton != null) mCancelButton.setTypeface(font); + if (mDatePickerHeaderView != null) mDatePickerHeaderView.setTypeface(font); + if (mSelectedMonthTextView != null) mSelectedMonthTextView.setTypeface(font); + if (mSelectedDayTextView != null) mSelectedDayTextView.setTypeface(font); + if (mYearView != null) mYearView.setTypeface(font); + } + } + @Override - public void onConfigurationChanged(final Configuration newConfig) { + public void onConfigurationChanged(@NonNull final Configuration newConfig) { super.onConfigurationChanged(newConfig); ViewGroup viewGroup = (ViewGroup) getView(); if (viewGroup != null) { @@ -477,6 +588,10 @@ public void onConfigurationChanged(final Configuration newConfig) { } } + private void setCalendarType(CalendarType calendarType) { + this.mCalendarType = calendarType; + } + @Override public void onResume() { super.onResume(); @@ -491,13 +606,13 @@ public void onPause() { } @Override - public void onCancel(DialogInterface dialog) { + public void onCancel(@NonNull DialogInterface dialog) { super.onCancel(dialog); if (mOnCancelListener != null) mOnCancelListener.onCancel(dialog); } @Override - public void onDismiss(DialogInterface dialog) { + public void onDismiss(@NonNull DialogInterface dialog) { super.onDismiss(dialog); if (mOnDismissListener != null) mOnDismissListener.onDismiss(dialog); } @@ -570,27 +685,69 @@ private void setCurrentView(final int viewIndex) { } private void updateDisplay(boolean announce) { - mYearView.setText(YEAR_FORMAT.format(mCalendar.getTime())); + switch (mCalendarType) { + case JALALI: + mYearView.setText(String.format(getLocale(), "%d", mCalendar.get(Calendar.YEAR))); + break; + case GREGORIAN: + default: + mYearView.setText(YEAR_FORMAT.format(mCalendar.getTime())); + break; + } if (mVersion == Version.VERSION_1) { if (mDatePickerHeaderView != null) { if (mTitle != null) mDatePickerHeaderView.setText(mTitle); else { - mDatePickerHeaderView.setText(mCalendar.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.LONG, - mLocale)); + switch (mCalendarType) { + case JALALI: + mDatePickerHeaderView.setText(JalaliCalendar.getWeekDayName((mCalendar).get(Calendar.DAY_OF_WEEK))); + break; + case GREGORIAN: + default: + mDatePickerHeaderView.setText(mCalendar.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.LONG, + mLocale)); + break; + } } } - mSelectedMonthTextView.setText(MONTH_FORMAT.format(mCalendar.getTime())); - mSelectedDayTextView.setText(DAY_FORMAT.format(mCalendar.getTime())); + switch (mCalendarType) { + case JALALI: + mYearView.setText(String.format(getLocale(), "%d", mCalendar.get(Calendar.YEAR))); + mSelectedMonthTextView.setText(((JalaliCalendar) mCalendar).getMonthName()); + mSelectedDayTextView.setText(String.format(getLocale(), "%02d", mCalendar.get(Calendar.DAY_OF_MONTH))); + break; + case GREGORIAN: + default: + mYearView.setText(YEAR_FORMAT.format(mCalendar.getTime())); + mSelectedMonthTextView.setText(MONTH_FORMAT.format(mCalendar.getTime())); + mSelectedDayTextView.setText(DAY_FORMAT.format(mCalendar.getTime())); + break; + } } if (mVersion == Version.VERSION_2) { - mSelectedDayTextView.setText(VERSION_2_FORMAT.format(mCalendar.getTime())); - if (mTitle != null) - mDatePickerHeaderView.setText(mTitle.toUpperCase(mLocale)); - else - mDatePickerHeaderView.setVisibility(View.GONE); + switch (mCalendarType) { + case JALALI: + JalaliCalendar cal = ((JalaliCalendar) mCalendar); + String dayMonthText = String.format(getLocale(), "%s %d %s", + cal.getWeekDayName(), cal.get(Calendar.DAY_OF_MONTH), cal.getMonthName()); + mSelectedDayTextView.setText(dayMonthText); + if (mTitle != null) + mDatePickerHeaderView.setText(mTitle); + else + mDatePickerHeaderView.setVisibility(View.GONE); + break; + case GREGORIAN: + default: + mSelectedDayTextView.setText(VERSION_2_FORMAT.format(mCalendar.getTime())); + if (mTitle != null) + mDatePickerHeaderView.setText(mTitle.toUpperCase(mLocale)); + else + mDatePickerHeaderView.setVisibility(View.GONE); + break; + } } // Accessibility. @@ -876,6 +1033,7 @@ public Calendar[] getDisabledDays() { /** * Provide a DateRangeLimiter for full control over which dates are enabled and disabled in the picker + * * @param dateRangeLimiter An implementation of the DateRangeLimiter interface */ @SuppressWarnings("unused") @@ -954,6 +1112,7 @@ public Version getVersion() { /** * Set which way the user needs to swipe to switch months in the MonthView + * * @param orientation The orientation to use */ public void setScrollOrientation(ScrollOrientation orientation) { @@ -962,17 +1121,29 @@ public void setScrollOrientation(ScrollOrientation orientation) { /** * Get which way the user needs to swipe to switch months in the MonthView + * * @return SwipeOrientation */ public ScrollOrientation getScrollOrientation() { return mScrollOrientation; } + @Override + public CalendarType getCalendarType() { + return mCalendarType; + } + + @Override + public void setFont(Typeface customFont) { + Utils.setCustomFont(customFont); + } + /** * Set which timezone the picker should use - * + *

* This has been deprecated in favor of setting the TimeZone using the constructor that * takes a Calendar object + * * @param timeZone The timezone to use */ @SuppressWarnings("DeprecatedIsStillUsed") @@ -987,12 +1158,22 @@ public void setTimeZone(TimeZone timeZone) { /** * Set a custom locale to be used when generating various strings in the picker + * * @param locale Locale */ @SuppressWarnings("WeakerAccess") public void setLocale(Locale locale) { mLocale = locale; - mWeekStart = Calendar.getInstance(mTimezone, mLocale).getFirstDayOfWeek(); + switch (mCalendarType) { + case JALALI: + JalaliCalendar.setLocale(locale); + mWeekStart = JalaliCalendar.getInstance(mTimezone, mLocale).getFirstDayOfWeek(); + break; + case GREGORIAN: + default: + mWeekStart = Calendar.getInstance(mTimezone, mLocale).getFirstDayOfWeek(); + break; + } YEAR_FORMAT = new SimpleDateFormat("yyyy", locale); MONTH_FORMAT = new SimpleDateFormat("MMM", locale); DAY_FORMAT = new SimpleDateFormat("dd", locale); @@ -1000,6 +1181,7 @@ public void setLocale(Locale locale) { /** * Return the current locale (default or other) + * * @return Locale */ @Override @@ -1024,6 +1206,7 @@ public void setOnDismissListener(DialogInterface.OnDismissListener onDismissList /** * Get a reference to the callback + * * @return OnDateSetListener the callback */ @SuppressWarnings("unused") @@ -1132,11 +1315,12 @@ public void tryVibrate() { if (mVibrate) mHapticFeedbackController.tryVibrate(); } - @Override public TimeZone getTimeZone() { + @Override + public TimeZone getTimeZone() { return mTimezone == null ? TimeZone.getDefault() : mTimezone; } - public void notifyOnDateListener() { + private void notifyOnDateListener() { if (mCallBack != null) { mCallBack.onDateSet(DatePickerDialog.this, mCalendar.get(Calendar.YEAR), mCalendar.get(Calendar.MONTH), mCalendar.get(Calendar.DAY_OF_MONTH)); diff --git a/library/src/main/java/com/wdullaer/materialdatetimepicker/date/DateRangeLimiter.java b/library/src/main/java/com/wdullaer/materialdatetimepicker/date/DateRangeLimiter.java index 53763e8a..721f3573 100644 --- a/library/src/main/java/com/wdullaer/materialdatetimepicker/date/DateRangeLimiter.java +++ b/library/src/main/java/com/wdullaer/materialdatetimepicker/date/DateRangeLimiter.java @@ -17,6 +17,7 @@ package com.wdullaer.materialdatetimepicker.date; import android.os.Parcelable; + import androidx.annotation.NonNull; import java.util.Calendar; @@ -28,6 +29,7 @@ public interface DateRangeLimiter extends Parcelable { * This method should match getStartDate() * It is recommended to keep the default implementation * This method will be removed from this interface at the next semver major + * * @return the minimum selectable year of the picker */ default int getMinYear() { @@ -39,6 +41,7 @@ default int getMinYear() { * This method should semantically match getEndDate() * It is recommended to keep the default implementation. * This method will be removed from this interface at the next semver major + * * @return the maximum selectable year of the picker */ default int getMaxYear() { @@ -49,26 +52,31 @@ default int getMaxYear() { * getStartDate returns the minimum selectable date of the picker * It is called in various places, including the hot loop when rendering. * It is highly recommended to keep this method as simple as possible + * * @return the minimum selectable date of the picker */ - @NonNull Calendar getStartDate(); + @NonNull + Calendar getStartDate(); /** * getEndDate returns the maximum selectable date of the picker * It is called in various places, including the hot loop when rendering. * It is highly recommended to keep this method as simple as possible + * * @return the maximum selectable date of the picker */ - @NonNull Calendar getEndDate(); + @NonNull + Calendar getEndDate(); /** * isOutOfRange is called for each date when it is about to be rendered * Returning true from this function will cause that particular day to be non selectable * Since this code is called in the inner loop when rendering, it is highly recommended to * keep the logic as simple as possible - * @param year the year of the date + * + * @param year the year of the date * @param month the month of the date - * @param day the day of the month of the date + * @param day the day of the month of the date * @return true if the date should be disabled, false otherwise */ boolean isOutOfRange(int year, int month, int day); @@ -79,8 +87,10 @@ default int getMaxYear() { * valid according to the constraints set by the limiter. * This method is not called when the user selects a day, since the picker prevents the * selection of values which satisfy `isOutOfRange` + * * @param day a date with the current user selection * @return the date after rounding to a selectable value */ - @NonNull Calendar setToNearestDate(@NonNull Calendar day); + @NonNull + Calendar setToNearestDate(@NonNull Calendar day); } \ No newline at end of file diff --git a/library/src/main/java/com/wdullaer/materialdatetimepicker/date/DayPickerGroup.java b/library/src/main/java/com/wdullaer/materialdatetimepicker/date/DayPickerGroup.java index ab7e7026..e4a07b31 100644 --- a/library/src/main/java/com/wdullaer/materialdatetimepicker/date/DayPickerGroup.java +++ b/library/src/main/java/com/wdullaer/materialdatetimepicker/date/DayPickerGroup.java @@ -1,18 +1,21 @@ package com.wdullaer.materialdatetimepicker.date; import android.content.Context; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.core.content.ContextCompat; -import androidx.core.view.ViewCompat; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageButton; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; +import androidx.core.view.ViewCompat; + import com.wdullaer.materialdatetimepicker.R; import com.wdullaer.materialdatetimepicker.Utils; +import com.wdullaer.materialdatetimepicker.enums.ScrollOrientation; +import com.wdullaer.materialdatetimepicker.enums.Version; public class DayPickerGroup extends ViewGroup implements View.OnClickListener, DayPickerView.OnPageListener { @@ -59,7 +62,7 @@ private void init() { prevButton = findViewById(R.id.mdtp_previous_month_arrow); nextButton = findViewById(R.id.mdtp_next_month_arrow); - if (controller.getVersion() == DatePickerDialog.Version.VERSION_1) { + if (controller.getVersion() == Version.VERSION_1) { int size = Utils.dpToPx(16f, getResources()); prevButton.setMinimumHeight(size); prevButton.setMinimumWidth(size); @@ -80,7 +83,7 @@ private void init() { } private void updateButtonVisibility(int position) { - final boolean isHorizontal = controller.getScrollOrientation() == DatePickerDialog.ScrollOrientation.HORIZONTAL; + final boolean isHorizontal = controller.getScrollOrientation() == ScrollOrientation.HORIZONTAL; final boolean hasPrev = position > 0; final boolean hasNext = position < (dayPickerView.getCount() - 1); prevButton.setVisibility(isHorizontal && hasPrev ? View.VISIBLE : View.INVISIBLE); @@ -131,7 +134,7 @@ protected void onLayout(boolean changed, int left, int top, int right, int botto rightButton = nextButton; } - final int topMargin = controller.getVersion() == DatePickerDialog.Version.VERSION_1 + final int topMargin = controller.getVersion() == Version.VERSION_1 ? 0 : getContext().getResources().getDimensionPixelSize(R.dimen.mdtp_date_picker_view_animator_padding_v2); final int width = right - left; diff --git a/library/src/main/java/com/wdullaer/materialdatetimepicker/date/DayPickerView.java b/library/src/main/java/com/wdullaer/materialdatetimepicker/date/DayPickerView.java index 885b9297..723e38ad 100644 --- a/library/src/main/java/com/wdullaer/materialdatetimepicker/date/DayPickerView.java +++ b/library/src/main/java/com/wdullaer/materialdatetimepicker/date/DayPickerView.java @@ -24,19 +24,20 @@ import android.view.View; import android.view.accessibility.AccessibilityEvent; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + import com.wdullaer.materialdatetimepicker.GravitySnapHelper; import com.wdullaer.materialdatetimepicker.Utils; import com.wdullaer.materialdatetimepicker.date.DatePickerDialog.OnDateChangedListener; +import com.wdullaer.materialdatetimepicker.enums.ScrollOrientation; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Locale; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; - /** * This displays a list of months in a calendar format with selectable days. */ @@ -63,6 +64,7 @@ public abstract class DayPickerView extends RecyclerView implements OnDateChange public interface OnPageListener { /** * Called when the visible page of the DayPickerView has changed + * * @param position the new position visible in the DayPickerView */ void onPageChanged(int position); @@ -70,9 +72,9 @@ public interface OnPageListener { public DayPickerView(Context context, AttributeSet attrs) { super(context, attrs); - DatePickerDialog.ScrollOrientation scrollOrientation = Build.VERSION.SDK_INT < Build.VERSION_CODES.M - ? DatePickerDialog.ScrollOrientation.VERTICAL - : DatePickerDialog.ScrollOrientation.HORIZONTAL; + ScrollOrientation scrollOrientation = Build.VERSION.SDK_INT < Build.VERSION_CODES.M + ? ScrollOrientation.VERTICAL + : ScrollOrientation.HORIZONTAL; init(context, scrollOrientation); } @@ -90,9 +92,9 @@ protected void setController(DatePickerController controller) { refreshAdapter(); } - public void init(Context context, DatePickerDialog.ScrollOrientation scrollOrientation) { + public void init(Context context, ScrollOrientation scrollOrientation) { @RecyclerView.Orientation - int layoutOrientation = scrollOrientation == DatePickerDialog.ScrollOrientation.VERTICAL + int layoutOrientation = scrollOrientation == ScrollOrientation.VERTICAL ? RecyclerView.VERTICAL : RecyclerView.HORIZONTAL; LinearLayoutManager linearLayoutManager = new LinearLayoutManager(context, layoutOrientation, false); @@ -110,10 +112,10 @@ public void init(Context context, DatePickerDialog.ScrollOrientation scrollOrien * Sets all the required fields for the list view. Override this method to * set a different list view behavior. */ - protected void setUpRecyclerView(DatePickerDialog.ScrollOrientation scrollOrientation) { + protected void setUpRecyclerView(ScrollOrientation scrollOrientation) { setVerticalScrollBarEnabled(false); setFadingEdgeLength(0); - int gravity = scrollOrientation == DatePickerDialog.ScrollOrientation.VERTICAL + int gravity = scrollOrientation == ScrollOrientation.VERTICAL ? Gravity.TOP : Gravity.START; GravitySnapHelper helper = new GravitySnapHelper(gravity, position -> { @@ -259,8 +261,9 @@ public int getMostVisiblePosition() { return getChildAdapterPosition(getMostVisibleMonth()); } - public @Nullable MonthView getMostVisibleMonth() { - boolean verticalScroll = mController.getScrollOrientation() == DatePickerDialog.ScrollOrientation.VERTICAL; + public @Nullable + MonthView getMostVisibleMonth() { + boolean verticalScroll = mController.getScrollOrientation() == ScrollOrientation.VERTICAL; final int maxSize = verticalScroll ? getHeight() : getWidth(); int maxDisplayedSize = 0; int i = 0; diff --git a/library/src/main/java/com/wdullaer/materialdatetimepicker/date/DefaultDateRangeLimiter.java b/library/src/main/java/com/wdullaer/materialdatetimepicker/date/DefaultDateRangeLimiter.java index 5f4ef466..8d0fcf36 100644 --- a/library/src/main/java/com/wdullaer/materialdatetimepicker/date/DefaultDateRangeLimiter.java +++ b/library/src/main/java/com/wdullaer/materialdatetimepicker/date/DefaultDateRangeLimiter.java @@ -18,6 +18,7 @@ import android.os.Parcel; import android.os.Parcelable; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -40,7 +41,8 @@ class DefaultDateRangeLimiter implements DateRangeLimiter { private TreeSet selectableDays = new TreeSet<>(); private HashSet disabledDays = new HashSet<>(); - DefaultDateRangeLimiter() {} + DefaultDateRangeLimiter() { + } @SuppressWarnings({"unchecked", "WeakerAccess"}) public DefaultDateRangeLimiter(Parcel in) { @@ -112,19 +114,23 @@ void setYearRange(int startYear, int endYear) { mMaxYear = endYear; } - @Nullable Calendar getMinDate() { + @Nullable + Calendar getMinDate() { return mMinDate; } - @Nullable Calendar getMaxDate() { + @Nullable + Calendar getMaxDate() { return mMaxDate; } - @Nullable Calendar[] getSelectableDays() { - return selectableDays.isEmpty() ? null : selectableDays.toArray(new Calendar[0]); + @Nullable + Calendar[] getSelectableDays() { + return selectableDays.isEmpty() ? null : selectableDays.toArray(new Calendar[0]); } - @Nullable Calendar[] getDisabledDays() { + @Nullable + Calendar[] getDisabledDays() { return disabledDays.isEmpty() ? null : disabledDays.toArray(new Calendar[0]); } @@ -143,7 +149,8 @@ public int getMaxYear() { } @Override - public @NonNull Calendar getStartDate() { + public @NonNull + Calendar getStartDate() { if (!selectableDays.isEmpty()) return (Calendar) selectableDays.first().clone(); if (mMinDate != null) return (Calendar) mMinDate.clone(); TimeZone timeZone = mController == null ? TimeZone.getDefault() : mController.getTimeZone(); @@ -155,7 +162,8 @@ public int getMaxYear() { } @Override - public @NonNull Calendar getEndDate() { + public @NonNull + Calendar getEndDate() { if (!selectableDays.isEmpty()) return (Calendar) selectableDays.last().clone(); if (mMaxDate != null) return (Calendar) mMaxDate.clone(); TimeZone timeZone = mController == null ? TimeZone.getDefault() : mController.getTimeZone(); @@ -203,7 +211,8 @@ private boolean isAfterMax(@NonNull Calendar calendar) { } @Override - public @NonNull Calendar setToNearestDate(@NonNull Calendar calendar) { + public @NonNull + Calendar setToNearestDate(@NonNull Calendar calendar) { if (!selectableDays.isEmpty()) { Calendar newCalendar = null; Calendar higher = selectableDays.ceiling(calendar); diff --git a/library/src/main/java/com/wdullaer/materialdatetimepicker/date/MonthAdapter.java b/library/src/main/java/com/wdullaer/materialdatetimepicker/date/MonthAdapter.java index 6d41fa75..b3d1f070 100644 --- a/library/src/main/java/com/wdullaer/materialdatetimepicker/date/MonthAdapter.java +++ b/library/src/main/java/com/wdullaer/materialdatetimepicker/date/MonthAdapter.java @@ -17,11 +17,12 @@ package com.wdullaer.materialdatetimepicker.date; import android.content.Context; -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.RecyclerView; import android.view.ViewGroup; import android.widget.AbsListView.LayoutParams; +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + import com.wdullaer.materialdatetimepicker.date.MonthAdapter.MonthViewHolder; import com.wdullaer.materialdatetimepicker.date.MonthView.OnDayClickListener; @@ -155,7 +156,8 @@ public MonthViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewTyp return new MonthViewHolder(v); } - @Override public void onBindViewHolder(@NonNull MonthViewHolder holder, int position) { + @Override + public void onBindViewHolder(@NonNull MonthViewHolder holder, int position) { holder.bind(position, mController, mSelectedDay); } @@ -164,7 +166,8 @@ public long getItemId(int position) { return position; } - @Override public int getItemCount() { + @Override + public int getItemCount() { Calendar endDate = mController.getEndDate(); Calendar startDate = mController.getStartDate(); int endMonth = endDate.get(Calendar.YEAR) * MONTHS_IN_YEAR + endDate.get(Calendar.MONTH); diff --git a/library/src/main/java/com/wdullaer/materialdatetimepicker/date/MonthView.java b/library/src/main/java/com/wdullaer/materialdatetimepicker/date/MonthView.java index 70325d9b..10c97a15 100644 --- a/library/src/main/java/com/wdullaer/materialdatetimepicker/date/MonthView.java +++ b/library/src/main/java/com/wdullaer/materialdatetimepicker/date/MonthView.java @@ -26,11 +26,6 @@ import android.graphics.Typeface; import android.os.Build; import android.os.Bundle; -import androidx.annotation.NonNull; -import androidx.core.content.ContextCompat; -import androidx.core.view.ViewCompat; -import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; -import androidx.customview.widget.ExploreByTouchHelper; import android.text.format.DateFormat; import android.util.AttributeSet; import android.view.MotionEvent; @@ -38,8 +33,17 @@ import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; +import androidx.annotation.NonNull; +import androidx.core.content.ContextCompat; +import androidx.core.view.ViewCompat; +import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; +import androidx.customview.widget.ExploreByTouchHelper; + +import com.wdullaer.materialdatetimepicker.JalaliCalendar; import com.wdullaer.materialdatetimepicker.R; import com.wdullaer.materialdatetimepicker.date.MonthAdapter.CalendarDay; +import com.wdullaer.materialdatetimepicker.enums.CalendarType; +import com.wdullaer.materialdatetimepicker.enums.Version; import java.security.InvalidParameterException; import java.text.SimpleDateFormat; @@ -138,8 +142,17 @@ public MonthView(Context context, AttributeSet attr, DatePickerController contro mController = controller; Resources res = context.getResources(); - mDayLabelCalendar = Calendar.getInstance(mController.getTimeZone(), mController.getLocale()); - mCalendar = Calendar.getInstance(mController.getTimeZone(), mController.getLocale()); + switch (controller.getCalendarType()) { + case JALALI: + mDayLabelCalendar = JalaliCalendar.getInstance(mController.getTimeZone(), mController.getLocale()); + mCalendar = JalaliCalendar.getInstance(mController.getTimeZone(), mController.getLocale()); + break; + case GREGORIAN: + default: + mDayLabelCalendar = Calendar.getInstance(mController.getTimeZone(), mController.getLocale()); + mCalendar = Calendar.getInstance(mController.getTimeZone(), mController.getLocale()); + break; + } mDayOfWeekTypeface = res.getString(R.string.mdtp_day_of_week_label_typeface); mMonthTitleTypeface = res.getString(R.string.mdtp_sans_serif); @@ -167,7 +180,7 @@ public MonthView(Context context, AttributeSet attr, DatePickerController contro MONTH_DAY_LABEL_TEXT_SIZE = res.getDimensionPixelSize(R.dimen.mdtp_month_day_label_text_size); MONTH_HEADER_SIZE = res.getDimensionPixelOffset(R.dimen.mdtp_month_list_item_header_height); MONTH_HEADER_SIZE_V2 = res.getDimensionPixelOffset(R.dimen.mdtp_month_list_item_header_height_v2); - DAY_SELECTED_CIRCLE_SIZE = mController.getVersion() == DatePickerDialog.Version.VERSION_1 + DAY_SELECTED_CIRCLE_SIZE = mController.getVersion() == Version.VERSION_1 ? res.getDimensionPixelSize(R.dimen.mdtp_day_number_select_circle_radius) : res.getDimensionPixelSize(R.dimen.mdtp_day_number_select_circle_radius_v2); DAY_HIGHLIGHT_CIRCLE_SIZE = res @@ -175,7 +188,7 @@ public MonthView(Context context, AttributeSet attr, DatePickerController contro DAY_HIGHLIGHT_CIRCLE_MARGIN = res .getDimensionPixelSize(R.dimen.mdtp_day_highlight_circle_margin); - if (mController.getVersion() == DatePickerDialog.Version.VERSION_1) { + if (mController.getVersion() == Version.VERSION_1) { mRowHeight = (res.getDimensionPixelOffset(R.dimen.mdtp_date_picker_view_animator_height) - getMonthHeaderSize()) / MAX_NUM_ROWS; } else { @@ -183,7 +196,7 @@ public MonthView(Context context, AttributeSet attr, DatePickerController contro - getMonthHeaderSize() - MONTH_DAY_LABEL_TEXT_SIZE * 2) / MAX_NUM_ROWS; } - mEdgePadding = mController.getVersion() == DatePickerDialog.Version.VERSION_1 + mEdgePadding = mController.getVersion() == Version.VERSION_1 ? 0 : context.getResources().getDimensionPixelSize(R.dimen.mdtp_date_picker_view_animator_padding_v2); @@ -222,13 +235,11 @@ public boolean dispatchHoverEvent(@NonNull MotionEvent event) { @Override public boolean onTouchEvent(@NonNull MotionEvent event) { - switch (event.getAction()) { - case MotionEvent.ACTION_UP: - final int day = getDayFromLocation(event.getX(), event.getY()); - if (day >= 0) { - onDayClick(day); - } - break; + if (event.getAction() == MotionEvent.ACTION_UP) { + final int day = getDayFromLocation(event.getX(), event.getY()); + if (day >= 0) { + onDayClick(day); + } } return true; } @@ -239,7 +250,7 @@ public boolean onTouchEvent(@NonNull MotionEvent event) { */ protected void initView() { mMonthTitlePaint = new Paint(); - if (mController.getVersion() == DatePickerDialog.Version.VERSION_1) + if (mController.getVersion() == Version.VERSION_1) mMonthTitlePaint.setFakeBoldText(true); mMonthTitlePaint.setAntiAlias(true); mMonthTitlePaint.setTextSize(MONTH_LABEL_TEXT_SIZE); @@ -302,7 +313,16 @@ public void setMonthParams(int selectedDay, int year, int month, int weekStart) // Figure out what day today is //final Time today = new Time(Time.getCurrentTimezone()); //today.setToNow(); - final Calendar today = Calendar.getInstance(mController.getTimeZone(), mController.getLocale()); + Calendar today; + switch (mController.getCalendarType()) { + case JALALI: + today = JalaliCalendar.getInstance(mController.getTimeZone(), mController.getLocale()); + break; + case GREGORIAN: + default: + today = Calendar.getInstance(mController.getTimeZone(), mController.getLocale()); + break; + } mHasToday = false; mToday = -1; @@ -374,7 +394,7 @@ public int getYear() { * @return The height in pixels of a row of day labels */ public int getMonthHeight() { - int scaleFactor = mController.getVersion() == DatePickerDialog.Version.VERSION_1 ? 2 : 3; + int scaleFactor = mController.getVersion() == Version.VERSION_1 ? 2 : 3; return getMonthHeaderSize() - MONTH_DAY_LABEL_TEXT_SIZE * scaleFactor; } @@ -396,18 +416,24 @@ public int getEdgePadding() { * A wrapper to the MonthHeaderSize to allow override it in children */ protected int getMonthHeaderSize() { - return mController.getVersion() == DatePickerDialog.Version.VERSION_1 + return mController.getVersion() == Version.VERSION_1 ? MONTH_HEADER_SIZE : MONTH_HEADER_SIZE_V2; } @NonNull private String getMonthAndYearString() { + if (mController.getCalendarType() == CalendarType.JALALI) { + return ((JalaliCalendar) mCalendar).getMonthName() + " " + ((JalaliCalendar) mCalendar).get(Calendar.YEAR); + } Locale locale = mController.getLocale(); String pattern = "MMMM yyyy"; - if (Build.VERSION.SDK_INT < 18) pattern = getContext().getResources().getString(R.string.mdtp_date_v1_monthyear); - else pattern = DateFormat.getBestDateTimePattern(locale, pattern); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) { + pattern = getContext().getResources().getString(R.string.mdtp_date_v1_monthyear); + } else { + pattern = DateFormat.getBestDateTimePattern(locale, pattern); + } SimpleDateFormat formatter = new SimpleDateFormat(pattern, locale); formatter.setTimeZone(mController.getTimeZone()); @@ -418,7 +444,7 @@ private String getMonthAndYearString() { protected void drawMonthTitle(Canvas canvas) { int x = mWidth / 2; - int y = mController.getVersion() == DatePickerDialog.Version.VERSION_1 + int y = mController.getVersion() == Version.VERSION_1 ? (getMonthHeaderSize() - MONTH_DAY_LABEL_TEXT_SIZE) / 2 : getMonthHeaderSize() / 2 - MONTH_DAY_LABEL_TEXT_SIZE; canvas.drawText(getMonthAndYearString(), x, y, mMonthTitlePaint); @@ -433,7 +459,15 @@ protected void drawMonthDayLabels(Canvas canvas) { int calendarDay = (i + mWeekStart) % mNumDays; mDayLabelCalendar.set(Calendar.DAY_OF_WEEK, calendarDay); - String weekString = getWeekDayLabel(mDayLabelCalendar); + String weekString = ""; + switch (mController.getCalendarType()) { + case GREGORIAN: + weekString = getWeekDayLabel(mDayLabelCalendar); + break; + case JALALI: + weekString = JalaliCalendar.getWeekDayName((mDayLabelCalendar).get(Calendar.DAY_OF_WEEK)).substring(0, 1); + break; + } canvas.drawText(weekString, x, y, mMonthDayLabelPaint); } } @@ -553,9 +587,9 @@ private void onDayClick(int day) { } /** - * @param year as an int + * @param year as an int * @param month as an int - * @param day as an int + * @param day as an int * @return true if the given date should be highlighted */ protected boolean isHighlighted(int year, int month, int day) { diff --git a/library/src/main/java/com/wdullaer/materialdatetimepicker/date/SimpleMonthView.java b/library/src/main/java/com/wdullaer/materialdatetimepicker/date/SimpleMonthView.java index cb886a8e..1a31186a 100644 --- a/library/src/main/java/com/wdullaer/materialdatetimepicker/date/SimpleMonthView.java +++ b/library/src/main/java/com/wdullaer/materialdatetimepicker/date/SimpleMonthView.java @@ -18,9 +18,13 @@ import android.content.Context; import android.graphics.Canvas; +import android.graphics.Paint; import android.graphics.Typeface; import android.util.AttributeSet; +import com.wdullaer.materialdatetimepicker.Utils; +import com.wdullaer.materialdatetimepicker.enums.Version; + public class SimpleMonthView extends MonthView { public SimpleMonthView(Context context, AttributeSet attr, DatePickerController controller) { @@ -30,6 +34,7 @@ public SimpleMonthView(Context context, AttributeSet attr, DatePickerController @Override public void drawMonthDay(Canvas canvas, int year, int month, int day, int x, int y, int startX, int stopX, int startY, int stopY) { + final Typeface font = Utils.getCustomFont(); if (mSelectedDay == day) { canvas.drawCircle(x, y - (MINI_DAY_NUMBER_TEXT_SIZE / 3), DAY_SELECTED_CIRCLE_SIZE, mSelectedCirclePaint); @@ -38,16 +43,28 @@ public void drawMonthDay(Canvas canvas, int year, int month, int day, if (isHighlighted(year, month, day) && mSelectedDay != day) { canvas.drawCircle(x, y + MINI_DAY_NUMBER_TEXT_SIZE - DAY_HIGHLIGHT_CIRCLE_MARGIN, DAY_HIGHLIGHT_CIRCLE_SIZE, mSelectedCirclePaint); - mMonthNumPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD)); + if (font != null) { + mMonthNumPaint.setTypeface(Typeface.create(font, Typeface.BOLD)); + } else { + mMonthNumPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD)); + } } else { - mMonthNumPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.NORMAL)); + if (font != null) { + mMonthNumPaint.setTypeface(Typeface.create(font, Typeface.NORMAL)); + } else { + mMonthNumPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.NORMAL)); + } } // gray out the day number if it's outside the range. if (mController.isOutOfRange(year, month, day)) { mMonthNumPaint.setColor(mDisabledDayTextColor); } else if (mSelectedDay == day) { - mMonthNumPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD)); + if (font != null) { + mMonthNumPaint.setTypeface(Typeface.create(font, Typeface.BOLD)); + } else { + mMonthNumPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD)); + } mMonthNumPaint.setColor(mSelectedDayTextColor); } else if (mHasToday && mToday == day) { mMonthNumPaint.setColor(mTodayNumberColor); @@ -57,4 +74,51 @@ public void drawMonthDay(Canvas canvas, int year, int month, int day, canvas.drawText(String.format(mController.getLocale(), "%d", day), x, y, mMonthNumPaint); } + + @Override + protected void initView() { + + final Typeface font = Utils.getCustomFont(); + + mMonthTitlePaint = new Paint(); + if (mController.getVersion() == Version.VERSION_1) + mMonthTitlePaint.setFakeBoldText(true); + mMonthTitlePaint.setAntiAlias(true); + mMonthTitlePaint.setTextSize(MONTH_LABEL_TEXT_SIZE); + if (font != null) { + mMonthTitlePaint.setTypeface(font); + } + mMonthTitlePaint.setColor(mDayTextColor); + mMonthTitlePaint.setTextAlign(Paint.Align.CENTER); + mMonthTitlePaint.setStyle(Paint.Style.FILL); + + mSelectedCirclePaint = new Paint(); + mSelectedCirclePaint.setFakeBoldText(true); + mSelectedCirclePaint.setAntiAlias(true); + mSelectedCirclePaint.setColor(mTodayNumberColor); + mSelectedCirclePaint.setTextAlign(Paint.Align.CENTER); + mSelectedCirclePaint.setStyle(Paint.Style.FILL); + mSelectedCirclePaint.setAlpha(255); + + mMonthDayLabelPaint = new Paint(); + mMonthDayLabelPaint.setAntiAlias(true); + mMonthDayLabelPaint.setTextSize(MONTH_DAY_LABEL_TEXT_SIZE); + mMonthDayLabelPaint.setColor(mMonthDayTextColor); + if (font != null) { + mMonthDayLabelPaint.setTypeface(font); + } + mMonthDayLabelPaint.setStyle(Paint.Style.FILL); + mMonthDayLabelPaint.setTextAlign(Paint.Align.CENTER); + mMonthDayLabelPaint.setFakeBoldText(true); + + mMonthNumPaint = new Paint(); + mMonthNumPaint.setAntiAlias(true); + mMonthNumPaint.setTextSize(MINI_DAY_NUMBER_TEXT_SIZE); + mMonthNumPaint.setStyle(Paint.Style.FILL); + mMonthNumPaint.setTextAlign(Paint.Align.CENTER); + mMonthNumPaint.setFakeBoldText(false); + if (font != null) { + mMonthNumPaint.setTypeface(font); + } + } } diff --git a/library/src/main/java/com/wdullaer/materialdatetimepicker/date/TextViewWithCircularIndicator.java b/library/src/main/java/com/wdullaer/materialdatetimepicker/date/TextViewWithCircularIndicator.java index 36ca105e..638d6e53 100644 --- a/library/src/main/java/com/wdullaer/materialdatetimepicker/date/TextViewWithCircularIndicator.java +++ b/library/src/main/java/com/wdullaer/materialdatetimepicker/date/TextViewWithCircularIndicator.java @@ -24,9 +24,10 @@ import android.graphics.Paint; import android.graphics.Paint.Align; import android.graphics.Paint.Style; +import android.util.AttributeSet; + import androidx.annotation.NonNull; import androidx.core.content.ContextCompat; -import android.util.AttributeSet; import com.wdullaer.materialdatetimepicker.R; @@ -69,8 +70,9 @@ public void setAccentColor(int color, boolean darkMode) { /** * Programmatically set the color state list (see mdtp_date_picker_year_selector) + * * @param accentColor pressed state text color - * @param darkMode current theme mode + * @param darkMode current theme mode * @return ColorStateList with pressed state */ private ColorStateList createTextColor(int accentColor, boolean darkMode) { diff --git a/library/src/main/java/com/wdullaer/materialdatetimepicker/date/YearPickerView.java b/library/src/main/java/com/wdullaer/materialdatetimepicker/date/YearPickerView.java index 04c7fdca..c6ee9d5a 100644 --- a/library/src/main/java/com/wdullaer/materialdatetimepicker/date/YearPickerView.java +++ b/library/src/main/java/com/wdullaer/materialdatetimepicker/date/YearPickerView.java @@ -18,6 +18,7 @@ import android.content.Context; import android.content.res.Resources; +import android.graphics.Typeface; import android.graphics.drawable.StateListDrawable; import android.view.LayoutInflater; import android.view.View; @@ -30,7 +31,9 @@ import android.widget.TextView; import com.wdullaer.materialdatetimepicker.R; +import com.wdullaer.materialdatetimepicker.Utils; import com.wdullaer.materialdatetimepicker.date.DatePickerDialog.OnDateChangedListener; +import com.wdullaer.materialdatetimepicker.enums.Version; /** * Displays a selectable list of years. @@ -50,9 +53,9 @@ public YearPickerView(Context context, DatePickerController controller) { LayoutParams.WRAP_CONTENT); setLayoutParams(frame); Resources res = context.getResources(); - mViewSize = mController.getVersion() == DatePickerDialog.Version.VERSION_1 - ? res.getDimensionPixelOffset(R.dimen.mdtp_date_picker_view_animator_height) - : res.getDimensionPixelOffset(R.dimen.mdtp_date_picker_view_animator_height_v2); + mViewSize = mController.getVersion() == Version.VERSION_1 + ? res.getDimensionPixelOffset(R.dimen.mdtp_date_picker_view_animator_height) + : res.getDimensionPixelOffset(R.dimen.mdtp_date_picker_view_animator_height_v2); mChildSize = res.getDimensionPixelOffset(R.dimen.mdtp_year_label_height); setVerticalFadingEdgeEnabled(true); setFadingEdgeLength(mChildSize / 3); @@ -125,12 +128,16 @@ public View getView(int position, View convertView, ViewGroup parent) { v = (TextViewWithCircularIndicator) convertView; } else { v = (TextViewWithCircularIndicator) LayoutInflater.from(parent.getContext()) - .inflate(R.layout.mdtp_year_label_text_view, parent, false); + .inflate(R.layout.mdtp_year_label_text_view, parent, false); v.setAccentColor(mController.getAccentColor(), mController.isThemeDark()); } + final Typeface font = Utils.getCustomFont(); + if (font != null) { + v.setTypeface(font); + } int year = mMinYear + position; boolean selected = mController.getSelectedDay().year == year; - v.setText(String.format(mController.getLocale(),"%d", year)); + v.setText(String.format(mController.getLocale(), "%d", year)); v.drawIndicator(selected); v.requestLayout(); if (selected) { diff --git a/library/src/main/java/com/wdullaer/materialdatetimepicker/enums/CalendarType.java b/library/src/main/java/com/wdullaer/materialdatetimepicker/enums/CalendarType.java new file mode 100644 index 00000000..ba725714 --- /dev/null +++ b/library/src/main/java/com/wdullaer/materialdatetimepicker/enums/CalendarType.java @@ -0,0 +1,6 @@ +package com.wdullaer.materialdatetimepicker.enums; + +public enum CalendarType { + GREGORIAN, + JALALI +} diff --git a/library/src/main/java/com/wdullaer/materialdatetimepicker/enums/ScrollOrientation.java b/library/src/main/java/com/wdullaer/materialdatetimepicker/enums/ScrollOrientation.java new file mode 100644 index 00000000..ca543395 --- /dev/null +++ b/library/src/main/java/com/wdullaer/materialdatetimepicker/enums/ScrollOrientation.java @@ -0,0 +1,6 @@ +package com.wdullaer.materialdatetimepicker.enums; + +public enum ScrollOrientation { + HORIZONTAL, + VERTICAL +} diff --git a/library/src/main/java/com/wdullaer/materialdatetimepicker/enums/Version.java b/library/src/main/java/com/wdullaer/materialdatetimepicker/enums/Version.java new file mode 100644 index 00000000..177938f9 --- /dev/null +++ b/library/src/main/java/com/wdullaer/materialdatetimepicker/enums/Version.java @@ -0,0 +1,6 @@ +package com.wdullaer.materialdatetimepicker.enums; + +public enum Version { + VERSION_1, + VERSION_2 +} diff --git a/library/src/main/java/com/wdullaer/materialdatetimepicker/time/AmPmCirclesView.java b/library/src/main/java/com/wdullaer/materialdatetimepicker/time/AmPmCirclesView.java index f87e1111..908ae5d5 100644 --- a/library/src/main/java/com/wdullaer/materialdatetimepicker/time/AmPmCirclesView.java +++ b/library/src/main/java/com/wdullaer/materialdatetimepicker/time/AmPmCirclesView.java @@ -20,14 +20,16 @@ import android.content.res.Resources; import android.graphics.Canvas; import android.graphics.Paint; -import android.graphics.Typeface; import android.graphics.Paint.Align; -import androidx.core.content.ContextCompat; +import android.graphics.Typeface; import android.util.Log; import android.view.View; +import androidx.core.content.ContextCompat; + import com.wdullaer.materialdatetimepicker.R; import com.wdullaer.materialdatetimepicker.Utils; +import com.wdullaer.materialdatetimepicker.enums.CalendarType; import java.text.DateFormatSymbols; import java.util.Locale; @@ -74,7 +76,7 @@ public AmPmCirclesView(Context context) { mIsInitialized = false; } - public void initialize(Context context, Locale locale, TimePickerController controller, int amOrPm) { + public void initialize(CalendarType type, Context context, Locale locale, TimePickerController controller, int amOrPm) { if (mIsInitialized) { Log.e(TAG, "AmPmCirclesView may only be initialized once."); return; @@ -100,7 +102,12 @@ public void initialize(Context context, Locale locale, TimePickerController cont String typefaceFamily = res.getString(R.string.mdtp_sans_serif); Typeface tf = Typeface.create(typefaceFamily, Typeface.NORMAL); - mPaint.setTypeface(tf); + final Typeface font = Utils.getCustomFont(); + if (font != null) { + mPaint.setTypeface(font); + } else { + mPaint.setTypeface(tf); + } mPaint.setAntiAlias(true); mPaint.setTextAlign(Align.CENTER); @@ -109,8 +116,17 @@ public void initialize(Context context, Locale locale, TimePickerController cont mAmPmCircleRadiusMultiplier = Float.parseFloat(res.getString(R.string.mdtp_ampm_circle_radius_multiplier)); String[] amPmTexts = new DateFormatSymbols(locale).getAmPmStrings(); - mAmText = amPmTexts[0]; - mPmText = amPmTexts[1]; + switch (type) { + case JALALI: + mAmText = "ق.ظ"; + mPmText = "ب.ظ"; + break; + case GREGORIAN: + default: + mAmText = amPmTexts[0]; + mPmText = amPmTexts[1]; + break; + } mAmDisabled = controller.isAmDisabled(); mPmDisabled = controller.isPmDisabled(); @@ -137,16 +153,16 @@ public int getIsTouchingAmOrPm(float xCoord, float yCoord) { return -1; } - int squaredYDistance = (int) ((yCoord - mAmPmYCenter)*(yCoord - mAmPmYCenter)); + int squaredYDistance = (int) ((yCoord - mAmPmYCenter) * (yCoord - mAmPmYCenter)); int distanceToAmCenter = - (int) Math.sqrt((xCoord - mAmXCenter)*(xCoord - mAmXCenter) + squaredYDistance); + (int) Math.sqrt((xCoord - mAmXCenter) * (xCoord - mAmXCenter) + squaredYDistance); if (distanceToAmCenter <= mAmPmCircleRadius && !mAmDisabled) { return AM; } int distanceToPmCenter = - (int) Math.sqrt((xCoord - mPmXCenter)*(xCoord - mPmXCenter) + squaredYDistance); + (int) Math.sqrt((xCoord - mPmXCenter) * (xCoord - mPmXCenter) + squaredYDistance); if (distanceToPmCenter <= mAmPmCircleRadius && !mPmDisabled) { return PM; } @@ -168,7 +184,7 @@ public void onDraw(Canvas canvas) { int circleRadius = (int) (Math.min(layoutXCenter, layoutYCenter) * mCircleRadiusMultiplier); mAmPmCircleRadius = (int) (circleRadius * mAmPmCircleRadiusMultiplier); - layoutYCenter += mAmPmCircleRadius*0.75; + layoutYCenter += mAmPmCircleRadius * 0.75; int textSize = mAmPmCircleRadius * 3 / 4; mPaint.setTextSize(textSize); diff --git a/library/src/main/java/com/wdullaer/materialdatetimepicker/time/CircleView.java b/library/src/main/java/com/wdullaer/materialdatetimepicker/time/CircleView.java index 46ff501a..f40682bb 100644 --- a/library/src/main/java/com/wdullaer/materialdatetimepicker/time/CircleView.java +++ b/library/src/main/java/com/wdullaer/materialdatetimepicker/time/CircleView.java @@ -20,11 +20,13 @@ import android.content.res.Resources; import android.graphics.Canvas; import android.graphics.Paint; -import androidx.core.content.ContextCompat; import android.util.Log; import android.view.View; +import androidx.core.content.ContextCompat; + import com.wdullaer.materialdatetimepicker.R; +import com.wdullaer.materialdatetimepicker.enums.Version; /** * Draws a simple white circle on which the numbers will be drawn. @@ -65,7 +67,7 @@ public void initialize(Context context, TimePickerController controller) { mPaint.setAntiAlias(true); mIs24HourMode = controller.is24HourMode(); - if (mIs24HourMode || controller.getVersion() != TimePickerDialog.Version.VERSION_1) { + if (mIs24HourMode || controller.getVersion() != Version.VERSION_1) { mCircleRadiusMultiplier = Float.parseFloat( res.getString(R.string.mdtp_circle_radius_multiplier_24HourMode)); } else { @@ -95,7 +97,7 @@ public void onDraw(Canvas canvas) { // a slightly higher center. To keep the entire view centered vertically, we'll // have to push it up by half the radius of the AM/PM circles. int amPmCircleRadius = (int) (mCircleRadius * mAmPmCircleRadiusMultiplier); - mYCenter -= amPmCircleRadius*0.75; + mYCenter -= amPmCircleRadius * 0.75; } mDrawValuesReady = true; diff --git a/library/src/main/java/com/wdullaer/materialdatetimepicker/time/DefaultTimepointLimiter.java b/library/src/main/java/com/wdullaer/materialdatetimepicker/time/DefaultTimepointLimiter.java index 39d6cbfa..74b36c6c 100644 --- a/library/src/main/java/com/wdullaer/materialdatetimepicker/time/DefaultTimepointLimiter.java +++ b/library/src/main/java/com/wdullaer/materialdatetimepicker/time/DefaultTimepointLimiter.java @@ -2,6 +2,7 @@ import android.os.Parcel; import android.os.Parcelable; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -24,7 +25,8 @@ class DefaultTimepointLimiter implements TimepointLimiter { private Timepoint mMinTime; private Timepoint mMaxTime; - DefaultTimepointLimiter() {} + DefaultTimepointLimiter() { + } @SuppressWarnings("WeakerAccess") public DefaultTimepointLimiter(Parcel in) { @@ -61,13 +63,13 @@ public DefaultTimepointLimiter[] newArray(int size) { }; void setMinTime(@NonNull Timepoint minTime) { - if(mMaxTime != null && minTime.compareTo(mMaxTime) > 0) + if (mMaxTime != null && minTime.compareTo(mMaxTime) > 0) throw new IllegalArgumentException("Minimum time must be smaller than the maximum time"); mMinTime = minTime; } void setMaxTime(@NonNull Timepoint maxTime) { - if(mMinTime != null && maxTime.compareTo(mMinTime) < 0) + if (mMinTime != null && maxTime.compareTo(mMinTime) < 0) throw new IllegalArgumentException("Maximum time must be greater than the minimum time"); mMaxTime = maxTime; } @@ -82,23 +84,28 @@ void setDisabledTimes(@NonNull Timepoint[] disabledTimes) { exclusiveSelectableTimes = getExclusiveSelectableTimes(mSelectableTimes, mDisabledTimes); } - @Nullable Timepoint getMinTime() { + @Nullable + Timepoint getMinTime() { return mMinTime; } - @Nullable Timepoint getMaxTime() { + @Nullable + Timepoint getMaxTime() { return mMaxTime; } - @NonNull Timepoint[] getSelectableTimes() { + @NonNull + Timepoint[] getSelectableTimes() { return mSelectableTimes.toArray(new Timepoint[mSelectableTimes.size()]); } - @NonNull Timepoint[] getDisabledTimes() { + @NonNull + Timepoint[] getDisabledTimes() { return mDisabledTimes.toArray(new Timepoint[mDisabledTimes.size()]); } - @NonNull private TreeSet getExclusiveSelectableTimes(@NonNull TreeSet selectable, @NonNull TreeSet disabled) { + @NonNull + private TreeSet getExclusiveSelectableTimes(@NonNull TreeSet selectable, @NonNull TreeSet disabled) { TreeSet output = new TreeSet<>(selectable); output.removeAll(disabled); return output; @@ -111,7 +118,7 @@ public boolean isOutOfRange(@Nullable Timepoint current, int index, @NonNull Tim if (index == HOUR_INDEX) { if (mMinTime != null && mMinTime.getHour() > current.getHour()) return true; - if (mMaxTime != null && mMaxTime.getHour()+1 <= current.getHour()) return true; + if (mMaxTime != null && mMaxTime.getHour() + 1 <= current.getHour()) return true; if (!exclusiveSelectableTimes.isEmpty()) { Timepoint ceil = exclusiveSelectableTimes.ceiling(current); @@ -126,8 +133,7 @@ public boolean isOutOfRange(@Nullable Timepoint current, int index, @NonNull Tim } return false; - } - else if (index == MINUTE_INDEX) { + } else if (index == MINUTE_INDEX) { if (mMinTime != null) { Timepoint roundedMin = new Timepoint(mMinTime.getHour(), mMinTime.getMinute()); if (roundedMin.compareTo(current) > 0) return true; @@ -153,8 +159,7 @@ else if (index == MINUTE_INDEX) { } return false; - } - else return isOutOfRange(current); + } else return isOutOfRange(current); } public boolean isOutOfRange(@NonNull Timepoint current) { @@ -174,7 +179,8 @@ public boolean isAmDisabled() { if (mMinTime != null && mMinTime.compareTo(midday) >= 0) return true; - if (!exclusiveSelectableTimes.isEmpty()) return exclusiveSelectableTimes.first().compareTo(midday) >= 0; + if (!exclusiveSelectableTimes.isEmpty()) + return exclusiveSelectableTimes.first().compareTo(midday) >= 0; return false; } @@ -186,13 +192,15 @@ public boolean isPmDisabled() { if (mMaxTime != null && mMaxTime.compareTo(midday) < 0) return true; - if (!exclusiveSelectableTimes.isEmpty()) return exclusiveSelectableTimes.last().compareTo(midday) < 0; + if (!exclusiveSelectableTimes.isEmpty()) + return exclusiveSelectableTimes.last().compareTo(midday) < 0; return false; } @Override - public @NonNull Timepoint roundToNearest(@NonNull Timepoint time,@Nullable Timepoint.TYPE type, @NonNull Timepoint.TYPE resolution) { + public @NonNull + Timepoint roundToNearest(@NonNull Timepoint time, @Nullable Timepoint.TYPE type, @NonNull Timepoint.TYPE resolution) { if (mMinTime != null && mMinTime.compareTo(time) > 0) return mMinTime; if (mMaxTime != null && mMaxTime.compareTo(time) < 0) return mMaxTime; @@ -213,22 +221,29 @@ public boolean isPmDisabled() { } if (type == Timepoint.TYPE.HOUR) { - if (floor.getHour() != time.getHour() && ceil.getHour() == time.getHour()) return ceil; - if (floor.getHour() == time.getHour() && ceil.getHour() != time.getHour()) return floor; - if (floor.getHour() != time.getHour() && ceil.getHour() != time.getHour()) return time; + if (floor.getHour() != time.getHour() && ceil.getHour() == time.getHour()) + return ceil; + if (floor.getHour() == time.getHour() && ceil.getHour() != time.getHour()) + return floor; + if (floor.getHour() != time.getHour() && ceil.getHour() != time.getHour()) + return time; } if (type == Timepoint.TYPE.MINUTE) { - if (floor.getHour() != time.getHour() && ceil.getHour() != time.getHour()) return time; + if (floor.getHour() != time.getHour() && ceil.getHour() != time.getHour()) + return time; if (floor.getHour() != time.getHour() && ceil.getHour() == time.getHour()) { return ceil.getMinute() == time.getMinute() ? ceil : time; } if (floor.getHour() == time.getHour() && ceil.getHour() != time.getHour()) { return floor.getMinute() == time.getMinute() ? floor : time; } - if (floor.getMinute() != time.getMinute() && ceil.getMinute() == time.getMinute()) return ceil; - if (floor.getMinute() == time.getMinute() && ceil.getMinute() != time.getMinute()) return floor; - if (floor.getMinute() != time.getMinute() && ceil.getMinute() != time.getMinute()) return time; + if (floor.getMinute() != time.getMinute() && ceil.getMinute() == time.getMinute()) + return ceil; + if (floor.getMinute() == time.getMinute() && ceil.getMinute() != time.getMinute()) + return floor; + if (floor.getMinute() != time.getMinute() && ceil.getMinute() != time.getMinute()) + return time; } int floorDist = Math.abs(time.compareTo(floor)); @@ -252,7 +267,8 @@ public boolean isPmDisabled() { boolean ceilDisabled = time.equals(ceil, Timepoint.TYPE.MINUTE); boolean floorDisabled = time.equals(floor, Timepoint.TYPE.MINUTE); - if (ceilDisabled || floorDisabled) return searchValidTimePoint(time, type, resolution); + if (ceilDisabled || floorDisabled) + return searchValidTimePoint(time, type, resolution); return time; } @@ -262,7 +278,8 @@ public boolean isPmDisabled() { boolean ceilDisabled = time.equals(ceil, Timepoint.TYPE.HOUR); boolean floorDisabled = time.equals(floor, Timepoint.TYPE.HOUR); - if (ceilDisabled || floorDisabled) return searchValidTimePoint(time, type, resolution); + if (ceilDisabled || floorDisabled) + return searchValidTimePoint(time, type, resolution); return time; } } diff --git a/library/src/main/java/com/wdullaer/materialdatetimepicker/time/RadialPickerLayout.java b/library/src/main/java/com/wdullaer/materialdatetimepicker/time/RadialPickerLayout.java index 6c6dc889..384556fb 100644 --- a/library/src/main/java/com/wdullaer/materialdatetimepicker/time/RadialPickerLayout.java +++ b/library/src/main/java/com/wdullaer/materialdatetimepicker/time/RadialPickerLayout.java @@ -22,9 +22,6 @@ import android.os.Build; import android.os.Bundle; import android.os.Handler; -import androidx.annotation.NonNull; -import androidx.core.content.ContextCompat; -import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; import android.text.format.DateUtils; import android.util.AttributeSet; import android.util.Log; @@ -38,7 +35,13 @@ import android.view.accessibility.AccessibilityNodeInfo; import android.widget.FrameLayout; +import androidx.annotation.NonNull; +import androidx.core.content.ContextCompat; +import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; + import com.wdullaer.materialdatetimepicker.R; +import com.wdullaer.materialdatetimepicker.enums.CalendarType; +import com.wdullaer.materialdatetimepicker.enums.Version; import java.util.Calendar; import java.util.Locale; @@ -98,7 +101,9 @@ public class RadialPickerLayout extends FrameLayout implements OnTouchListener { public interface OnValueSelectedListener { void onValueSelected(Timepoint newTime); + void enablePicker(); + void advancePicker(int index); } @@ -156,13 +161,21 @@ public void setOnValueSelectedListener(OnValueSelectedListener listener) { /** * Initialize the Layout with starting values. - * @param context A context needed to inflate resources - * @param locale A Locale to be used when generating strings - * @param initialTime The initial selection of the Timepicker + * + * @param calendarType Type of calendar + * @param context A context needed to inflate resources + * @param locale A Locale to be used when generating strings + * @param initialTime The initial selection of the Timepicker * @param is24HourMode Indicates whether we should render in 24hour mode or with AM/PM selectors */ - public void initialize(Context context, Locale locale, TimePickerController timePickerController, - Timepoint initialTime, boolean is24HourMode) { + public void initialize( + CalendarType calendarType, + Context context, + Locale locale, + TimePickerController timePickerController, + Timepoint initialTime, + boolean is24HourMode + ) { if (mTimeInitialized) { Log.e(TAG, "Time has already been initialized."); return; @@ -174,8 +187,8 @@ public void initialize(Context context, Locale locale, TimePickerController time // Initialize the circle and AM/PM circles if applicable. mCircleView.initialize(context, mController); mCircleView.invalidate(); - if (!mIs24HourMode && mController.getVersion() == TimePickerDialog.Version.VERSION_1) { - mAmPmCirclesView.initialize(context, locale, mController, initialTime.isAM() ? AM : PM); + if (!mIs24HourMode && mController.getVersion() == Version.VERSION_1) { + mAmPmCirclesView.initialize(calendarType, context, locale, mController, initialTime.isAM() ? AM : PM); mAmPmCirclesView.invalidate(); } @@ -190,8 +203,8 @@ public void initialize(Context context, Locale locale, TimePickerController time }; RadialTextsView.SelectionValidator hourValidator = selection -> { Timepoint newTime = new Timepoint(selection, mCurrentTime.getMinute(), mCurrentTime.getSecond()); - if(!mIs24HourMode && getIsCurrentlyAmOrPm() == PM) newTime.setPM(); - if(!mIs24HourMode && getIsCurrentlyAmOrPm() == AM) newTime.setAM(); + if (!mIs24HourMode && getIsCurrentlyAmOrPm() == PM) newTime.setPM(); + if (!mIs24HourMode && getIsCurrentlyAmOrPm() == AM) newTime.setAM(); return !mController.isOutOfRange(newTime, HOUR_INDEX); }; @@ -205,7 +218,7 @@ public void initialize(Context context, Locale locale, TimePickerController time String[] minutesTexts = new String[12]; String[] secondsTexts = new String[12]; for (int i = 0; i < 12; i++) { - hoursTexts[i] = is24HourMode? + hoursTexts[i] = is24HourMode ? String.format(locale, "%02d", hours_24[i]) : String.format(locale, "%d", hours[i]); innerHoursTexts[i] = String.format(locale, "%d", hours[i]); minutesTexts[i] = String.format(locale, "%02d", minutes[i]); @@ -213,7 +226,7 @@ public void initialize(Context context, Locale locale, TimePickerController time } // The version 2 layout has the hours > 12 on the inner circle rather than the outer circle // Inner circle and outer circle should be swapped (see #411) - if (mController.getVersion() == TimePickerDialog.Version.VERSION_2) { + if (mController.getVersion() == Version.VERSION_2) { String[] temp = hoursTexts; hoursTexts = innerHoursTexts; innerHoursTexts = temp; @@ -260,13 +273,14 @@ private void setItem(int index, Timepoint time) { /** * Check if a given hour appears in the outer circle or the inner circle + * * @return true if the hour is in the inner circle, false if it's in the outer circle. */ private boolean isHourInnerCircle(int hourOfDay) { // We'll have the 00 hours on the outside circle. boolean isMorning = hourOfDay <= 12 && hourOfDay != 0; // In the version 2 layout the circles are swapped - if (mController.getVersion() != TimePickerDialog.Version.VERSION_1) isMorning = !isMorning; + if (mController.getVersion() != Version.VERSION_1) isMorning = !isMorning; return mIs24HourMode && isMorning; } @@ -292,7 +306,7 @@ public Timepoint getTime() { */ private int getCurrentlyShowingValue() { int currentIndex = getCurrentItemShowing(); - switch(currentIndex) { + switch (currentIndex) { case HOUR_INDEX: return mCurrentTime.getHour(); case MINUTE_INDEX: @@ -315,14 +329,15 @@ public int getIsCurrentlyAmOrPm() { /** * Set the internal value as either AM or PM, and update the AM/PM circle displays. + * * @param amOrPm Integer representing AM of PM (use the supplied constants) */ public void setAmOrPm(int amOrPm) { mAmPmCirclesView.setAmOrPm(amOrPm); mAmPmCirclesView.invalidate(); Timepoint newSelection = new Timepoint(mCurrentTime); - if(amOrPm == AM) newSelection.setAM(); - else if(amOrPm == PM) newSelection.setPM(); + if (amOrPm == AM) newSelection.setAM(); + else if (amOrPm == PM) newSelection.setPM(); newSelection = roundToValidTime(newSelection, HOUR_INDEX); reselectSelector(newSelection, false, HOUR_INDEX); mCurrentTime = newSelection; @@ -402,10 +417,11 @@ private int snapPrefer30s(int degrees) { /** * Returns mapping of any input degrees (0 to 360) to one of 12 visible output degrees (all * multiples of 30), where the input will be "snapped" to the closest visible degrees. - * @param degrees The input degrees + * + * @param degrees The input degrees * @param forceHigherOrLower The output may be forced to either the higher or lower step, or may - * be allowed to snap to whichever is closer. Use 1 to force strictly higher, -1 to force - * strictly lower, and 0 to snap to the closer one. + * be allowed to snap to whichever is closer. Use 1 to force strictly higher, -1 to force + * strictly lower, and 0 to snap to the closer one. * @return output degrees, will be a multiple of 30 */ private static int snapOnly30s(int degrees, int forceHigherOrLower) { @@ -431,12 +447,13 @@ private static int snapOnly30s(int degrees, int forceHigherOrLower) { /** * Snap the input to a selectable value - * @param newSelection Timepoint - Time which should be rounded + * + * @param newSelection Timepoint - Time which should be rounded * @param currentItemShowing int - The index of the current view * @return Timepoint - the rounded value */ private Timepoint roundToValidTime(Timepoint newSelection, int currentItemShowing) { - switch(currentItemShowing) { + switch (currentItemShowing) { case HOUR_INDEX: return mController.roundToNearest(newSelection, null); case MINUTE_INDEX: @@ -450,31 +467,32 @@ private Timepoint roundToValidTime(Timepoint newSelection, int currentItemShowin * For the currently showing view (either hours, minutes or seconds), re-calculate the position * for the selector, and redraw it at that position. The text representing the currently * selected value will be redrawn if required. + * * @param newSelection Timpoint - Time which should be selected. * @param forceDrawDot The dot in the circle will generally only be shown when the selection - * @param index The picker to use as a reference. Will be getCurrentItemShow() except when AM/PM is changed - * is on non-visible values, but use this to force the dot to be shown. + * @param index The picker to use as a reference. Will be getCurrentItemShow() except when AM/PM is changed + * is on non-visible values, but use this to force the dot to be shown. */ private void reselectSelector(Timepoint newSelection, boolean forceDrawDot, int index) { - switch(index) { + switch (index) { case HOUR_INDEX: // The selection might have changed, recalculate the degrees and innerCircle values int hour = newSelection.getHour(); boolean isInnerCircle = isHourInnerCircle(hour); - int degrees = (hour%12)*360/12; - if(!mIs24HourMode) hour = hour%12; - if(!mIs24HourMode && hour == 0) hour += 12; + int degrees = (hour % 12) * 360 / 12; + if (!mIs24HourMode) hour = hour % 12; + if (!mIs24HourMode && hour == 0) hour += 12; mHourRadialSelectorView.setSelection(degrees, isInnerCircle, forceDrawDot); mHourRadialTextsView.setSelection(hour); // If we rounded the minutes, reposition the minuteSelector too. - if(newSelection.getMinute() != mCurrentTime.getMinute()) { + if (newSelection.getMinute() != mCurrentTime.getMinute()) { int minDegrees = newSelection.getMinute() * (360 / 60); mMinuteRadialSelectorView.setSelection(minDegrees, isInnerCircle, forceDrawDot); mMinuteRadialTextsView.setSelection(newSelection.getMinute()); } // If we rounded the seconds, reposition the secondSelector too. - if(newSelection.getSecond() != mCurrentTime.getSecond()) { + if (newSelection.getSecond() != mCurrentTime.getSecond()) { int secDegrees = newSelection.getSecond() * (360 / 60); mSecondRadialSelectorView.setSelection(secDegrees, isInnerCircle, forceDrawDot); mSecondRadialTextsView.setSelection(newSelection.getSecond()); @@ -487,8 +505,8 @@ private void reselectSelector(Timepoint newSelection, boolean forceDrawDot, int mMinuteRadialSelectorView.setSelection(degrees, false, forceDrawDot); mMinuteRadialTextsView.setSelection(newSelection.getMinute()); // If we rounded the seconds, reposition the secondSelector too. - if(newSelection.getSecond() != mCurrentTime.getSecond()) { - int secDegrees = newSelection.getSecond()* (360 / 60); + if (newSelection.getSecond() != mCurrentTime.getSecond()) { + int secDegrees = newSelection.getSecond() * (360 / 60); mSecondRadialSelectorView.setSelection(secDegrees, false, forceDrawDot); mSecondRadialTextsView.setSelection(newSelection.getSecond()); } @@ -501,7 +519,7 @@ private void reselectSelector(Timepoint newSelection, boolean forceDrawDot, int } // Invalidate the currently showing picker to force a redraw - switch(getCurrentItemShowing()) { + switch (getCurrentItemShowing()) { case HOUR_INDEX: mHourRadialSelectorView.invalidate(); mHourRadialTextsView.invalidate(); @@ -565,17 +583,17 @@ private Timepoint getTimeFromDegrees(int degrees, boolean isInnerCircle, boolean } if (currentShowing == HOUR_INDEX - && mController.getVersion() != TimePickerDialog.Version.VERSION_1 + && mController.getVersion() != Version.VERSION_1 && mIs24HourMode) { value = (value + 12) % 24; } Timepoint newSelection; - switch(currentShowing) { + switch (currentShowing) { case HOUR_INDEX: int hour = value; - if(!mIs24HourMode && getIsCurrentlyAmOrPm() == PM && degrees != 360) hour += 12; - if(!mIs24HourMode && getIsCurrentlyAmOrPm() == AM && degrees == 360) hour = 0; + if (!mIs24HourMode && getIsCurrentlyAmOrPm() == PM && degrees != 360) hour += 12; + if (!mIs24HourMode && getIsCurrentlyAmOrPm() == AM && degrees == 360) hour = 0; newSelection = new Timepoint(hour, mCurrentTime.getMinute(), mCurrentTime.getSecond()); break; case MINUTE_INDEX: @@ -594,18 +612,19 @@ private Timepoint getTimeFromDegrees(int degrees, boolean isInnerCircle, boolean /** * Calculate the degrees within the circle that corresponds to the specified coordinates, if * the coordinates are within the range that will trigger a selection. - * @param pointX The x coordinate. - * @param pointY The y coordinate. - * @param forceLegal Force the selection to be legal, regardless of how far the coordinates are - * from the actual numbers. + * + * @param pointX The x coordinate. + * @param pointY The y coordinate. + * @param forceLegal Force the selection to be legal, regardless of how far the coordinates are + * from the actual numbers. * @param isInnerCircle If the selection may be in the inner circle, pass in a size-1 boolean - * array here, inside which the value will be true if the selection is in the inner circle, - * and false if in the outer circle. + * array here, inside which the value will be true if the selection is in the inner circle, + * and false if in the outer circle. * @return Degrees from 0 to 360, if the selection was within the legal range. -1 if not. */ private int getDegreesFromCoords(float pointX, float pointY, boolean forceLegal, - final Boolean[] isInnerCircle) { - switch(getCurrentItemShowing()) { + final Boolean[] isInnerCircle) { + switch (getCurrentItemShowing()) { case HOUR_INDEX: return mHourRadialSelectorView.getDegreesFromCoords( pointX, pointY, forceLegal, isInnerCircle); @@ -633,11 +652,12 @@ public int getCurrentItemShowing() { /** * Set either seconds, minutes or hours as showing. + * * @param animate True to animate the transition, false to show with no animation. */ public void setCurrentItemShowing(int index, boolean animate) { if (index != HOUR_INDEX && index != MINUTE_INDEX && index != SECOND_INDEX) { - Log.e(TAG, "TimePicker does not support view at index "+index); + Log.e(TAG, "TimePicker does not support view at index " + index); return; } @@ -652,7 +672,7 @@ public void setCurrentItemShowing(int index, boolean animate) { anims[1] = mHourRadialSelectorView.getDisappearAnimator(); anims[2] = mMinuteRadialTextsView.getReappearAnimator(); anims[3] = mMinuteRadialSelectorView.getReappearAnimator(); - } else if (index == HOUR_INDEX && lastIndex == MINUTE_INDEX){ + } else if (index == HOUR_INDEX && lastIndex == MINUTE_INDEX) { anims[0] = mHourRadialTextsView.getReappearAnimator(); anims[1] = mHourRadialSelectorView.getReappearAnimator(); anims[2] = mMinuteRadialTextsView.getDisappearAnimator(); @@ -680,7 +700,7 @@ public void setCurrentItemShowing(int index, boolean animate) { } if (anims[0] != null && anims[1] != null && anims[2] != null && - anims[3] != null) { + anims[3] != null) { if (mTransition != null && mTransition.isRunning()) { mTransition.end(); } @@ -716,7 +736,7 @@ public boolean onTouch(View v, MotionEvent event) { final Boolean[] isInnerCircle = new Boolean[1]; isInnerCircle[0] = false; - switch(event.getAction()) { + switch (event.getAction()) { case MotionEvent.ACTION_DOWN: if (!mInputEnabled) { return true; @@ -729,7 +749,7 @@ public boolean onTouch(View v, MotionEvent event) { mDoingMove = false; mDoingTouch = true; // If we're showing the AM/PM, check to see if the user is touching it. - if (!mIs24HourMode && mController.getVersion() == TimePickerDialog.Version.VERSION_1) { + if (!mIs24HourMode && mController.getVersion() == Version.VERSION_1) { mIsTouchingAmOrPm = mAmPmCirclesView.getIsTouchingAmOrPm(eventX, eventY); } else { mIsTouchingAmOrPm = -1; @@ -750,7 +770,8 @@ public boolean onTouch(View v, MotionEvent event) { // Calculate the degrees that is currently being touched. mDownDegrees = getDegreesFromCoords(eventX, eventY, forceLegal, isInnerCircle); Timepoint selectedTime = getTimeFromDegrees(mDownDegrees, isInnerCircle[0], false); - if(mController.isOutOfRange(selectedTime, getCurrentItemShowing())) mDownDegrees = -1; + if (mController.isOutOfRange(selectedTime, getCurrentItemShowing())) + mDownDegrees = -1; if (mDownDegrees != -1) { // If it's a legal touch, set that number as "selected" after the // TAP_TIMEOUT in case the user moves their finger quickly. @@ -839,8 +860,8 @@ public boolean onTouch(View v, MotionEvent event) { mAmPmCirclesView.setAmOrPm(isTouchingAmOrPm); if (getIsCurrentlyAmOrPm() != isTouchingAmOrPm) { Timepoint newSelection = new Timepoint(mCurrentTime); - if(mIsTouchingAmOrPm == AM) newSelection.setAM(); - else if(mIsTouchingAmOrPm == PM) newSelection.setPM(); + if (mIsTouchingAmOrPm == AM) newSelection.setAM(); + else if (mIsTouchingAmOrPm == PM) newSelection.setPM(); newSelection = roundToValidTime(newSelection, HOUR_INDEX); reselectSelector(newSelection, false, HOUR_INDEX); mCurrentTime = newSelection; @@ -883,7 +904,7 @@ public boolean trySettingInputEnabled(boolean inputEnabled) { } mInputEnabled = inputEnabled; - mGrayBox.setVisibility(inputEnabled? View.INVISIBLE : View.VISIBLE); + mGrayBox.setVisibility(inputEnabled ? View.INVISIBLE : View.VISIBLE); return true; } @@ -897,8 +918,7 @@ public void onInitializeAccessibilityNodeInfo(@NonNull AccessibilityNodeInfo inf if (Build.VERSION.SDK_INT >= 21) { info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD); info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD); - } - else { + } else { info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD); info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD); } @@ -990,7 +1010,7 @@ public boolean performAccessibilityAction(int action, Bundle arguments) { } Timepoint newSelection; - switch(currentItemShowing) { + switch (currentItemShowing) { case HOUR_INDEX: newSelection = new Timepoint( value, diff --git a/library/src/main/java/com/wdullaer/materialdatetimepicker/time/RadialSelectorView.java b/library/src/main/java/com/wdullaer/materialdatetimepicker/time/RadialSelectorView.java index bf8866ed..421afa92 100644 --- a/library/src/main/java/com/wdullaer/materialdatetimepicker/time/RadialSelectorView.java +++ b/library/src/main/java/com/wdullaer/materialdatetimepicker/time/RadialSelectorView.java @@ -30,6 +30,7 @@ import com.wdullaer.materialdatetimepicker.R; import com.wdullaer.materialdatetimepicker.Utils; +import com.wdullaer.materialdatetimepicker.enums.Version; import java.lang.ref.WeakReference; @@ -82,19 +83,20 @@ public RadialSelectorView(Context context) { /** * Initialize this selector with the state of the picker. - * @param context Current context. - * @param controller Structure containing the accentColor and the 24-hour mode, which will tell us - * whether the circle's center is moved up slightly to make room for the AM/PM circles. - * @param hasInnerCircle Whether we have both an inner and an outer circle of numbers - * that may be selected. Should be true for 24-hour mode in the hours circle. - * @param disappearsOut Whether the numbers' animation will have them disappearing out - * or disappearing in. + * + * @param context Current context. + * @param controller Structure containing the accentColor and the 24-hour mode, which will tell us + * whether the circle's center is moved up slightly to make room for the AM/PM circles. + * @param hasInnerCircle Whether we have both an inner and an outer circle of numbers + * that may be selected. Should be true for 24-hour mode in the hours circle. + * @param disappearsOut Whether the numbers' animation will have them disappearing out + * or disappearing in. * @param selectionDegrees The initial degrees to be selected. - * @param isInnerCircle Whether the initial selection is in the inner or outer circle. - * Will be ignored when hasInnerCircle is false. + * @param isInnerCircle Whether the initial selection is in the inner or outer circle. + * Will be ignored when hasInnerCircle is false. */ public void initialize(Context context, TimePickerController controller, boolean hasInnerCircle, - boolean disappearsOut, int selectionDegrees, boolean isInnerCircle) { + boolean disappearsOut, int selectionDegrees, boolean isInnerCircle) { if (mIsInitialized) { Log.e(TAG, "This RadialSelectorView may only be initialized once."); return; @@ -110,7 +112,7 @@ public void initialize(Context context, TimePickerController controller, boolean // Calculate values for the circle radius size. mIs24HourMode = controller.is24HourMode(); - if (mIs24HourMode || controller.getVersion() != TimePickerDialog.Version.VERSION_1) { + if (mIs24HourMode || controller.getVersion() != Version.VERSION_1) { mCircleRadiusMultiplier = Float.parseFloat( res.getString(R.string.mdtp_circle_radius_multiplier_24HourMode)); } else { @@ -136,8 +138,8 @@ public void initialize(Context context, TimePickerController controller, boolean // Calculate values for the transition mid-way states. mAnimationRadiusMultiplier = 1; - mTransitionMidRadiusMultiplier = 1f + (0.05f * (disappearsOut? -1 : 1)); - mTransitionEndRadiusMultiplier = 1f + (0.3f * (disappearsOut? 1 : -1)); + mTransitionMidRadiusMultiplier = 1f + (0.05f * (disappearsOut ? -1 : 1)); + mTransitionEndRadiusMultiplier = 1f + (0.3f * (disappearsOut ? 1 : -1)); mInvalidateUpdateListener = new InvalidateUpdateListener(this); setSelection(selectionDegrees, isInnerCircle, false); @@ -146,12 +148,13 @@ public void initialize(Context context, TimePickerController controller, boolean /** * Set the selection. + * * @param selectionDegrees The degrees to be selected. - * @param isInnerCircle Whether the selection should be in the inner circle or outer. Will be - * ignored if hasInnerCircle was initialized to false. - * @param forceDrawDot Whether to force the dot in the center of the selection circle to be - * drawn. If false, the dot will be drawn only when the degrees is not a multiple of 30, i.e. - * the selection is not on a visible number. + * @param isInnerCircle Whether the selection should be in the inner circle or outer. Will be + * ignored if hasInnerCircle was initialized to false. + * @param forceDrawDot Whether to force the dot in the center of the selection circle to be + * drawn. If false, the dot will be drawn only when the degrees is not a multiple of 30, i.e. + * the selection is not on a visible number. */ public void setSelection(int selectionDegrees, boolean isInnerCircle, boolean forceDrawDot) { mSelectionDegrees = selectionDegrees; @@ -184,14 +187,14 @@ public void setAnimationRadiusMultiplier(float animationRadiusMultiplier) { } public int getDegreesFromCoords(float pointX, float pointY, boolean forceLegal, - final Boolean[] isInnerCircle) { + final Boolean[] isInnerCircle) { if (!mDrawValuesReady) { return -1; } double hypotenuse = Math.sqrt( - (pointY - mYCenter)*(pointY - mYCenter) + - (pointX - mXCenter)*(pointX - mXCenter)); + (pointY - mYCenter) * (pointY - mYCenter) + + (pointX - mXCenter) * (pointX - mXCenter)); // Check if we're outside the range if (mHasInnerCircle) { if (forceLegal) { @@ -276,7 +279,7 @@ public void onDraw(Canvas canvas) { // a slightly higher center. To keep the entire view centered vertically, we'll // have to push it up by half the radius of the AM/PM circles. int amPmCircleRadius = (int) (mCircleRadius * mAmPmCircleRadiusMultiplier); - mYCenter -= amPmCircleRadius *0.75; + mYCenter -= amPmCircleRadius * 0.75; } mSelectionRadius = (int) (mCircleRadius * mSelectionRadiusMultiplier); diff --git a/library/src/main/java/com/wdullaer/materialdatetimepicker/time/RadialTextsView.java b/library/src/main/java/com/wdullaer/materialdatetimepicker/time/RadialTextsView.java index 85d1f541..a92f8450 100644 --- a/library/src/main/java/com/wdullaer/materialdatetimepicker/time/RadialTextsView.java +++ b/library/src/main/java/com/wdullaer/materialdatetimepicker/time/RadialTextsView.java @@ -25,13 +25,16 @@ import android.content.res.Resources; import android.graphics.Canvas; import android.graphics.Paint; -import android.graphics.Typeface; import android.graphics.Paint.Align; -import androidx.core.content.ContextCompat; +import android.graphics.Typeface; import android.util.Log; import android.view.View; +import androidx.core.content.ContextCompat; + import com.wdullaer.materialdatetimepicker.R; +import com.wdullaer.materialdatetimepicker.Utils; +import com.wdullaer.materialdatetimepicker.enums.Version; /** * A view to show a series of numbers in a circular pattern. @@ -87,7 +90,7 @@ public RadialTextsView(Context context) { } public void initialize(Context context, String[] texts, String[] innerTexts, - TimePickerController controller, SelectionValidator validator, boolean disappearsOut) { + TimePickerController controller, SelectionValidator validator, boolean disappearsOut) { if (mIsInitialized) { Log.e(TAG, "This RadialTextsView may only be initialized once."); return; @@ -98,9 +101,16 @@ public void initialize(Context context, String[] texts, String[] innerTexts, int textColorRes = controller.isThemeDark() ? R.color.mdtp_white : R.color.mdtp_numbers_text_color; mPaint.setColor(ContextCompat.getColor(context, textColorRes)); String typefaceFamily = res.getString(R.string.mdtp_radial_numbers_typeface); - mTypefaceLight = Typeface.create(typefaceFamily, Typeface.NORMAL); String typefaceFamilyRegular = res.getString(R.string.mdtp_sans_serif); - mTypefaceRegular = Typeface.create(typefaceFamilyRegular, Typeface.NORMAL); + final Typeface font = Utils.getCustomFont(); + if (font != null) { + mTypefaceLight = Typeface.create(font, Typeface.NORMAL); + mTypefaceRegular = Typeface.create(font, Typeface.NORMAL); + } else { + mTypefaceLight = Typeface.create(typefaceFamily, Typeface.NORMAL); + mTypefaceRegular = Typeface.create(typefaceFamilyRegular, Typeface.NORMAL); + } + mPaint.setAntiAlias(true); mPaint.setTextAlign(Align.CENTER); @@ -109,6 +119,7 @@ public void initialize(Context context, String[] texts, String[] innerTexts, mSelectedPaint.setColor(selectedTextColor); mSelectedPaint.setAntiAlias(true); mSelectedPaint.setTextAlign(Align.CENTER); + mSelectedPaint.setTypeface(mTypefaceRegular); // Set up the inactive paint int inactiveColorRes = controller.isThemeDark() ? R.color.mdtp_date_picker_text_disabled_dark_theme @@ -116,6 +127,7 @@ public void initialize(Context context, String[] texts, String[] innerTexts, mInactivePaint.setColor(ContextCompat.getColor(context, inactiveColorRes)); mInactivePaint.setAntiAlias(true); mInactivePaint.setTextAlign(Align.CENTER); + mInactivePaint.setTypeface(mTypefaceRegular); mTexts = texts; mInnerTexts = innerTexts; @@ -123,7 +135,7 @@ public void initialize(Context context, String[] texts, String[] innerTexts, mHasInnerCircle = (innerTexts != null); // Calculate the radius for the main circle. - if (mIs24HourMode || controller.getVersion() != TimePickerDialog.Version.VERSION_1) { + if (mIs24HourMode || controller.getVersion() != Version.VERSION_1) { mCircleRadiusMultiplier = Float.parseFloat( res.getString(R.string.mdtp_circle_radius_multiplier_24HourMode)); } else { @@ -143,7 +155,7 @@ public void initialize(Context context, String[] texts, String[] innerTexts, res.getString(R.string.mdtp_numbers_radius_multiplier_inner)); // Version 2 layout draws outer circle bigger than inner - if (controller.getVersion() == TimePickerDialog.Version.VERSION_1) { + if (controller.getVersion() == Version.VERSION_1) { mTextSizeMultiplier = Float.parseFloat( res.getString(R.string.mdtp_text_size_multiplier_outer)); mInnerTextSizeMultiplier = Float.parseFloat( @@ -165,8 +177,8 @@ public void initialize(Context context, String[] texts, String[] innerTexts, } mAnimationRadiusMultiplier = 1; - mTransitionMidRadiusMultiplier = 1f + (0.05f * (disappearsOut? -1 : 1)); - mTransitionEndRadiusMultiplier = 1f + (0.3f * (disappearsOut? 1 : -1)); + mTransitionMidRadiusMultiplier = 1f + (0.05f * (disappearsOut ? -1 : 1)); + mTransitionEndRadiusMultiplier = 1f + (0.3f * (disappearsOut ? 1 : -1)); mInvalidateUpdateListener = new InvalidateUpdateListener(); mValidator = validator; @@ -177,6 +189,7 @@ public void initialize(Context context, String[] texts, String[] innerTexts, /** * Set the value of the selected text. Depending on the theme this will be rendered differently + * * @param selection The text which is currently selected */ protected void setSelection(int selection) { @@ -216,7 +229,7 @@ public void onDraw(Canvas canvas) { // a slightly higher center. To keep the entire view centered vertically, we'll // have to push it up by half the radius of the AM/PM circles. float amPmCircleRadius = mCircleRadius * mAmPmCircleRadiusMultiplier; - mYCenter -= amPmCircleRadius *0.75; + mYCenter -= amPmCircleRadius * 0.75; } mTextSize = mCircleRadius * mTextSizeMultiplier; @@ -263,7 +276,7 @@ public void onDraw(Canvas canvas) { * textGridWidths parameters. */ private void calculateGridSizes(float numbersRadius, float xCenter, float yCenter, - float textSize, float[] textGridHeights, float[] textGridWidths) { + float textSize, float[] textGridHeights, float[] textGridWidths) { /* * The numbers need to be drawn in a 7x7 grid, representing the points on the Unit Circle. */ @@ -296,10 +309,10 @@ private void calculateGridSizes(float numbersRadius, float xCenter, float yCente private Paint[] assignTextColors(String[] texts) { Paint[] paints = new Paint[texts.length]; - for(int i=0;i * Created by wdullaer on 6/10/15. */ interface TimePickerController { @@ -24,7 +28,7 @@ interface TimePickerController { /** * @return Version - The current version to render */ - TimePickerDialog.Version getVersion(); + Version getVersion(); /** * Request the device to vibrate @@ -32,7 +36,7 @@ interface TimePickerController { void tryVibrate(); /** - * @param time Timepoint - the selected point in time + * @param time Timepoint - the selected point in time * @param index int - The current view to consider when calculating the range * @return boolean - true if this is not a selectable value */ @@ -50,12 +54,15 @@ interface TimePickerController { /** * Will round the given Timepoint to the nearest valid Timepoint given the following restrictions: - * - TYPE.HOUR, it will just round to the next valid point, possible adjusting minutes and seconds - * - TYPE.MINUTE, it will round to the next valid point, without adjusting the hour, but possibly adjusting the seconds - * - TYPE.SECOND, it will round to the next valid point, only adjusting the seconds + * - TYPE.HOUR, it will just round to the next valid point, possible adjusting minutes and seconds + * - TYPE.MINUTE, it will round to the next valid point, without adjusting the hour, but possibly adjusting the seconds + * - TYPE.SECOND, it will round to the next valid point, only adjusting the seconds + * * @param time Timepoint - the timepoint to validate * @param type Timepoint.TYPE - whether we should round the hours, minutes or seconds * @return timepoint - the nearest valid timepoint */ Timepoint roundToNearest(Timepoint time, Timepoint.TYPE type); + + void setFont(Typeface customFont); } diff --git a/library/src/main/java/com/wdullaer/materialdatetimepicker/time/TimePickerDialog.java b/library/src/main/java/com/wdullaer/materialdatetimepicker/time/TimePickerDialog.java index ba2786b9..58239029 100644 --- a/library/src/main/java/com/wdullaer/materialdatetimepicker/time/TimePickerDialog.java +++ b/library/src/main/java/com/wdullaer/materialdatetimepicker/time/TimePickerDialog.java @@ -18,21 +18,15 @@ import android.animation.ObjectAnimator; import android.app.ActionBar.LayoutParams; +import android.app.Activity; import android.content.Context; import android.content.DialogInterface; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Color; +import android.graphics.Typeface; import android.os.Build; import android.os.Bundle; -import androidx.annotation.ColorInt; -import androidx.annotation.IntRange; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.StringRes; -import androidx.appcompat.app.AppCompatDialogFragment; -import androidx.core.content.ContextCompat; -import androidx.core.content.res.ResourcesCompat; import android.util.Log; import android.view.KeyCharacterMap; import android.view.KeyEvent; @@ -45,9 +39,20 @@ import android.widget.RelativeLayout; import android.widget.TextView; +import androidx.annotation.ColorInt; +import androidx.annotation.IntRange; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.StringRes; +import androidx.appcompat.app.AppCompatDialogFragment; +import androidx.core.content.ContextCompat; +import androidx.core.content.res.ResourcesCompat; + import com.wdullaer.materialdatetimepicker.HapticFeedbackController; import com.wdullaer.materialdatetimepicker.R; import com.wdullaer.materialdatetimepicker.Utils; +import com.wdullaer.materialdatetimepicker.enums.CalendarType; +import com.wdullaer.materialdatetimepicker.enums.Version; import com.wdullaer.materialdatetimepicker.time.RadialPickerLayout.OnValueSelectedListener; import java.text.DateFormatSymbols; @@ -63,11 +68,6 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValueSelectedListener, TimePickerController { private static final String TAG = "TimePickerDialog"; - public enum Version { - VERSION_1, - VERSION_2 - } - private static final String KEY_INITIAL_TIME = "initial_time"; private static final String KEY_IS_24_HOUR_VIEW = "is_24_hour_view"; private static final String KEY_TITLE = "dialog_title"; @@ -90,6 +90,7 @@ public enum Version { private static final String KEY_VERSION = "version"; private static final String KEY_TIMEPOINTLIMITER = "timepoint_limiter"; private static final String KEY_LOCALE = "locale"; + private static final String KEY_CALENDAR_TYPE = "mCalendarType"; public static final int HOUR_INDEX = 0; public static final int MINUTE_INDEX = 1; @@ -164,6 +165,8 @@ public enum Version { private String mSecondPickerDescription; private String mSelectSeconds; + private CalendarType mCalendarType; + /** * The callback interface used to indicate the user is done filling in * the time (they clicked on the 'Set' button). @@ -171,10 +174,10 @@ public enum Version { public interface OnTimeSetListener { /** - * @param view The view associated with this listener. + * @param view The view associated with this listener. * @param hourOfDay The hour that was set. - * @param minute The minute that was set. - * @param second The second that was set + * @param minute The minute that was set. + * @param second The second that was set */ void onTimeSet(TimePickerDialog view, int hourOfDay, int minute, int second); } @@ -185,7 +188,9 @@ public TimePickerDialog() { /** * Create a new TimePickerDialog instance with a given intial selection + * * @param callback How the parent is notified that the time is set. + * @param calendarType mCalendarType of calendar * @param hourOfDay The initial hour of the dialog. * @param minute The initial minute of the dialog. * @param second The initial second of the dialog. @@ -193,40 +198,79 @@ public TimePickerDialog() { * @return a new TimePickerDialog instance. */ @SuppressWarnings({"SameParameterValue", "WeakerAccess"}) - public static TimePickerDialog newInstance(OnTimeSetListener callback, - int hourOfDay, int minute, int second, boolean is24HourMode) { + public static TimePickerDialog newInstance( + OnTimeSetListener callback, + CalendarType calendarType, + int hourOfDay, + int minute, + int second, + boolean is24HourMode + ) { TimePickerDialog ret = new TimePickerDialog(); - ret.initialize(callback, hourOfDay, minute, second, is24HourMode); + ret.initialize(callback, calendarType, hourOfDay, minute, second, is24HourMode); return ret; } /** * Create a new TimePickerDialog instance with a given initial selection + * * @param callback How the parent is notified that the time is set. + * @param calendarType mCalendarType of calendar * @param hourOfDay The initial hour of the dialog. * @param minute The initial minute of the dialog. * @param is24HourMode True to render 24 hour mode, false to render AM / PM selectors. * @return a new TimePickerDialog instance. */ - public static TimePickerDialog newInstance(OnTimeSetListener callback, - int hourOfDay, int minute, boolean is24HourMode) { - return TimePickerDialog.newInstance(callback, hourOfDay, minute, 0, is24HourMode); + public static TimePickerDialog newInstance( + OnTimeSetListener callback, + CalendarType calendarType, + int hourOfDay, + int minute, + boolean is24HourMode + ) { + return TimePickerDialog.newInstance( + callback, + calendarType, + hourOfDay, + minute, + 0, + is24HourMode + ); } /** * Create a new TimePickerDialog instance initialized to the current system time + * * @param callback How the parent is notified that the time is set. + * @param calendarType mCalendarType of calendar * @param is24HourMode True to render 24 hour mode, false to render AM / PM selectors. * @return a new TimePickerDialog instance. */ @SuppressWarnings({"unused", "SameParameterValue", "WeakerAccess"}) - public static TimePickerDialog newInstance(OnTimeSetListener callback, boolean is24HourMode) { + public static TimePickerDialog newInstance( + OnTimeSetListener callback, + CalendarType calendarType, + boolean is24HourMode + ) { Calendar now = Calendar.getInstance(); - return TimePickerDialog.newInstance(callback, now.get(Calendar.HOUR_OF_DAY), now.get(Calendar.MINUTE), is24HourMode); - } - - public void initialize(OnTimeSetListener callback, - int hourOfDay, int minute, int second, boolean is24HourMode) { + return TimePickerDialog.newInstance( + callback, + calendarType, + now.get(Calendar.HOUR_OF_DAY), + now.get(Calendar.MINUTE), + is24HourMode + ); + } + + public void initialize( + OnTimeSetListener callback, + CalendarType calendarType, + int hourOfDay, + int minute, + int second, + boolean is24HourMode + ) { + this.setCalendarType(calendarType); mCallback = callback; mInitialTime = new Timepoint(hourOfDay, minute, second); @@ -246,6 +290,10 @@ public void initialize(OnTimeSetListener callback, mTimePicker = null; } + private void setCalendarType(CalendarType calendarType) { + this.mCalendarType = calendarType; + } + /** * Set a title. NOTE: this will only take effect with the next onCreateView */ @@ -268,6 +316,7 @@ public void setThemeDark(boolean dark) { /** * Set the accent color of this dialog + * * @param color the accent color you want */ @SuppressWarnings("unused") @@ -277,6 +326,7 @@ public void setAccentColor(String color) { /** * Set the accent color of this dialog + * * @param color the accent color you want */ public void setAccentColor(@ColorInt int color) { @@ -285,6 +335,7 @@ public void setAccentColor(@ColorInt int color) { /** * Set the text color of the OK button + * * @param color the color you want */ @SuppressWarnings("unused") @@ -294,6 +345,7 @@ public void setOkColor(String color) { /** * Set the text color of the OK button + * * @param color the color you want */ @SuppressWarnings("unused") @@ -303,6 +355,7 @@ public void setOkColor(@ColorInt int color) { /** * Set the text color of the Cancel button + * * @param color the color you want */ @SuppressWarnings("unused") @@ -312,11 +365,12 @@ public void setCancelColor(String color) { /** * Set the text color of the Cancel button + * * @param color the color you want */ @SuppressWarnings("unused") public void setCancelColor(@ColorInt int color) { - mCancelColor= Color.argb(255, Color.red(color), Color.green(color), Color.blue(color)); + mCancelColor = Color.argb(255, Color.red(color), Color.green(color), Color.blue(color)); } @Override @@ -336,6 +390,7 @@ public int getAccentColor() { /** * Set whether the device should vibrate when touching fields + * * @param vibrate true if the device should vibrate when touching a field */ public void vibrate(boolean vibrate) { @@ -344,6 +399,7 @@ public void vibrate(boolean vibrate) { /** * Set whether the picker should dismiss itself when it's pausing or whether it should try to survive an orientation change + * * @param dismissOnPause true if the picker should dismiss itself */ public void dismissOnPause(boolean dismissOnPause) { @@ -353,6 +409,7 @@ public void dismissOnPause(boolean dismissOnPause) { /** * Set whether an additional picker for seconds should be shown * Will enable minutes picker as well if seconds picker should be shown + * * @param enableSeconds true if the seconds picker should be shown */ public void enableSeconds(boolean enableSeconds) { @@ -363,6 +420,7 @@ public void enableSeconds(boolean enableSeconds) { /** * Set whether the picker for minutes should be shown * Will disable seconds if minutes are disbled + * * @param enableMinutes true if minutes picker should be shown */ @SuppressWarnings({"unused", "WeakerAccess"}) @@ -370,6 +428,7 @@ public void enableMinutes(boolean enableMinutes) { if (!enableMinutes) mEnableSeconds = false; mEnableMinutes = enableMinutes; } + @SuppressWarnings("unused") public void setMinTime(int hour, int minute, int second) { setMinTime(new Timepoint(hour, minute, second)); @@ -394,6 +453,7 @@ public void setMaxTime(Timepoint maxTime) { * Pass in an array of Timepoints which are the only possible selections. * Try to specify Timepoints only up to the resolution of your picker (i.e. do not add seconds * if the resolution of the picker is minutes) + * * @param selectableTimes Array of Timepoints which are the only valid selections in the picker */ @SuppressWarnings("WeakerAccess") @@ -408,6 +468,7 @@ public void setSelectableTimes(Timepoint[] selectableTimes) { * very expensive operation if a lot of consecutive Timepoints are disabled * Try to specify Timepoints only up to the resolution of your picker (i.e. do not add seconds * if the resolution of the picker is minutes) + * * @param disabledTimes Array of Timepoints which are disabled in the resulting picker */ public void setDisabledTimes(Timepoint[] disabledTimes) { @@ -420,13 +481,14 @@ public void setDisabledTimes(Timepoint[] disabledTimes) { * The interval for all three time components can be set independently * If you are not using the seconds / minutes picker, set the respective item to 60 for * better performance. - * @param hourInterval The interval between 2 selectable hours ([1,24]) + * + * @param hourInterval The interval between 2 selectable hours ([1,24]) * @param minuteInterval The interval between 2 selectable minutes ([1,60]) * @param secondInterval The interval between 2 selectable seconds ([1,60]) */ - public void setTimeInterval(@IntRange(from=1, to=24) int hourInterval, - @IntRange(from=1, to=60) int minuteInterval, - @IntRange(from=1, to=60) int secondInterval) { + public void setTimeInterval(@IntRange(from = 1, to = 24) int hourInterval, + @IntRange(from = 1, to = 60) int minuteInterval, + @IntRange(from = 1, to = 60) int secondInterval) { List timepoints = new ArrayList<>(); int hour = 0; @@ -451,12 +513,13 @@ public void setTimeInterval(@IntRange(from=1, to=24) int hourInterval, * The interval for all three time components can be set independently * If you are not using the seconds / minutes picker, set the respective item to 60 for * better performance. - * @param hourInterval The interval between 2 selectable hours ([1,24]) + * + * @param hourInterval The interval between 2 selectable hours ([1,24]) * @param minuteInterval The interval between 2 selectable minutes ([1,60]) */ @SuppressWarnings({"SameParameterValue", "WeakerAccess"}) - public void setTimeInterval(@IntRange(from=1, to=24) int hourInterval, - @IntRange(from=1, to=60) int minuteInterval) { + public void setTimeInterval(@IntRange(from = 1, to = 24) int hourInterval, + @IntRange(from = 1, to = 60) int minuteInterval) { setTimeInterval(hourInterval, minuteInterval, 60); } @@ -466,10 +529,11 @@ public void setTimeInterval(@IntRange(from=1, to=24) int hourInterval, * The interval for all three time components can be set independently * If you are not using the seconds / minutes picker, set the respective item to 60 for * better performance. + * * @param hourInterval The interval between 2 selectable hours ([1,24]) */ @SuppressWarnings("unused") - public void setTimeInterval(@IntRange(from=1, to=24) int hourInterval) { + public void setTimeInterval(@IntRange(from = 1, to = 24) int hourInterval) { setTimeInterval(hourInterval, 60); } @@ -490,10 +554,10 @@ public void setOnDismissListener(DialogInterface.OnDismissListener onDismissList * Set the time that will be shown when the picker opens for the first time * Overrides the value given in newInstance() * - * @deprecated in favor of {@link #setInitialSelection(int, int, int)} * @param hourOfDay the hour of the day - * @param minute the minute of the hour - * @param second the second of the minute + * @param minute the minute of the hour + * @param second the second of the minute + * @deprecated in favor of {@link #setInitialSelection(int, int, int)} */ @Deprecated public void setStartTime(int hourOfDay, int minute, int second) { @@ -505,9 +569,9 @@ public void setStartTime(int hourOfDay, int minute, int second) { * Set the time that will be shown when the picker opens for the first time * Overrides the value given in newInstance * - * @deprecated in favor of {@link #setInitialSelection(int, int)} * @param hourOfDay the hour of the day - * @param minute the minute of the hour + * @param minute the minute of the hour + * @deprecated in favor of {@link #setInitialSelection(int, int)} */ @SuppressWarnings({"unused", "deprecation"}) @Deprecated @@ -518,9 +582,10 @@ public void setStartTime(int hourOfDay, int minute) { /** * Set the time that will be shown when the picker opens for the first time * Overrides the value given in newInstance() + * * @param hourOfDay the hour of the day - * @param minute the minute of the hour - * @param second the second of the minute + * @param minute the minute of the hour + * @param second the second of the minute */ @SuppressWarnings("WeakerAccess") public void setInitialSelection(int hourOfDay, int minute, int second) { @@ -530,8 +595,9 @@ public void setInitialSelection(int hourOfDay, int minute, int second) { /** * Set the time that will be shown when the picker opens for the first time * Overrides the value given in newInstance + * * @param hourOfDay the hour of the day - * @param minute the minute of the hour + * @param minute the minute of the hour */ @SuppressWarnings({"unused", "WeakerAccess"}) public void setInitialSelection(int hourOfDay, int minute) { @@ -541,6 +607,7 @@ public void setInitialSelection(int hourOfDay, int minute) { /** * Set the time that will be shown when the picker opens for the first time * Overrides the value given in newInstance() + * * @param time the Timepoint selected when the Dialog opens */ @SuppressWarnings("WeakerAccess") @@ -551,6 +618,7 @@ public void setInitialSelection(Timepoint time) { /** * Set the label for the Ok button (max 12 characters) + * * @param okString A literal String to be used as the Ok button label */ @SuppressWarnings("unused") @@ -560,6 +628,7 @@ public void setOkText(String okString) { /** * Set the label for the Ok button (max 12 characters) + * * @param okResid A resource ID to be used as the Ok button label */ @SuppressWarnings("unused") @@ -570,6 +639,7 @@ public void setOkText(@StringRes int okResid) { /** * Set the label for the Cancel button (max 12 characters) + * * @param cancelString A literal String to be used as the Cancel button label */ @SuppressWarnings("unused") @@ -579,6 +649,7 @@ public void setCancelText(String cancelString) { /** * Set the label for the Cancel button (max 12 characters) + * * @param cancelResid A resource ID to be used as the Cancel button label */ @SuppressWarnings("unused") @@ -589,6 +660,7 @@ public void setCancelText(@StringRes int cancelResid) { /** * Set which layout version the picker should use + * * @param version The version to use */ public void setVersion(Version version) { @@ -598,6 +670,7 @@ public void setVersion(Version version) { /** * Pass in a custom implementation of TimeLimiter * Disables setSelectableTimes, setDisabledTimes, setTimeInterval, setMinTime and setMaxTime + * * @param limiter A custom implementation of TimeLimiter */ @SuppressWarnings("unused") @@ -612,6 +685,7 @@ public Version getVersion() { /** * Get a reference to the OnTimeSetListener callback + * * @return OnTimeSetListener the callback */ @SuppressWarnings("unused") @@ -621,6 +695,7 @@ public OnTimeSetListener getOnTimeSetListener() { /** * Set the Locale which will be used to generate various strings throughout the picker + * * @param locale Locale */ @SuppressWarnings("unused") @@ -633,25 +708,32 @@ public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setStyle(AppCompatDialogFragment.STYLE_NO_TITLE, 0); if (savedInstanceState != null && savedInstanceState.containsKey(KEY_INITIAL_TIME) - && savedInstanceState.containsKey(KEY_IS_24_HOUR_VIEW)) { + && savedInstanceState.containsKey(KEY_IS_24_HOUR_VIEW)) { + this.setCalendarType((CalendarType) savedInstanceState.get(KEY_CALENDAR_TYPE)); mInitialTime = savedInstanceState.getParcelable(KEY_INITIAL_TIME); mIs24HourMode = savedInstanceState.getBoolean(KEY_IS_24_HOUR_VIEW); mInKbMode = savedInstanceState.getBoolean(KEY_IN_KB_MODE); mTitle = savedInstanceState.getString(KEY_TITLE); mThemeDark = savedInstanceState.getBoolean(KEY_THEME_DARK); mThemeDarkChanged = savedInstanceState.getBoolean(KEY_THEME_DARK_CHANGED); - if (savedInstanceState.containsKey(KEY_ACCENT)) mAccentColor = savedInstanceState.getInt(KEY_ACCENT); + if (savedInstanceState.containsKey(KEY_ACCENT)) { + mAccentColor = savedInstanceState.getInt(KEY_ACCENT); + } mVibrate = savedInstanceState.getBoolean(KEY_VIBRATE); mDismissOnPause = savedInstanceState.getBoolean(KEY_DISMISS); mEnableSeconds = savedInstanceState.getBoolean(KEY_ENABLE_SECONDS); mEnableMinutes = savedInstanceState.getBoolean(KEY_ENABLE_MINUTES); mOkResid = savedInstanceState.getInt(KEY_OK_RESID); mOkString = savedInstanceState.getString(KEY_OK_STRING); - if (savedInstanceState.containsKey(KEY_OK_COLOR)) mOkColor = savedInstanceState.getInt(KEY_OK_COLOR); + if (savedInstanceState.containsKey(KEY_OK_COLOR)) { + mOkColor = savedInstanceState.getInt(KEY_OK_COLOR); + } if (mOkColor == Integer.MAX_VALUE) mOkColor = null; mCancelResid = savedInstanceState.getInt(KEY_CANCEL_RESID); mCancelString = savedInstanceState.getString(KEY_CANCEL_STRING); - if (savedInstanceState.containsKey(KEY_CANCEL_COLOR)) mCancelColor = savedInstanceState.getInt(KEY_CANCEL_COLOR); + if (savedInstanceState.containsKey(KEY_CANCEL_COLOR)) { + mCancelColor = savedInstanceState.getInt(KEY_CANCEL_COLOR); + } mVersion = (Version) savedInstanceState.getSerializable(KEY_VERSION); mLimiter = savedInstanceState.getParcelable(KEY_TIMEPOINTLIMITER); mLocale = (Locale) savedInstanceState.getSerializable(KEY_LOCALE); @@ -672,9 +754,9 @@ affect the behaviour of the picker (in the unlikely event the user reconfigures @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { + Bundle savedInstanceState) { int viewRes = mVersion == Version.VERSION_1 ? R.layout.mdtp_time_picker_dialog : R.layout.mdtp_time_picker_dialog_v2; - View view = inflater.inflate(viewRes, container,false); + View view = inflater.inflate(viewRes, container, false); KeyboardListener keyboardListener = new KeyboardListener(); view.findViewById(R.id.mdtp_time_picker_dialog).setOnKeyListener(keyboardListener); @@ -713,13 +795,23 @@ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, mPmTextView = view.findViewById(R.id.mdtp_pm_label); mPmTextView.setOnKeyListener(keyboardListener); mAmPmLayout = view.findViewById(R.id.mdtp_ampm_layout); - String[] amPmTexts = new DateFormatSymbols(mLocale).getAmPmStrings(); + String[] amPmTexts; + switch (mCalendarType) { + case JALALI: + amPmTexts = new String[]{"ق.ظ", "ب.ظ"}; + break; + case GREGORIAN: + default: + amPmTexts = new DateFormatSymbols(mLocale).getAmPmStrings(); + break; + } + mAmText = amPmTexts[0]; mPmText = amPmTexts[1]; mHapticFeedbackController = new HapticFeedbackController(getActivity()); - if(mTimePicker != null) { + if (mTimePicker != null) { mInitialTime = new Timepoint(mTimePicker.getHours(), mTimePicker.getMinutes(), mTimePicker.getSeconds()); } @@ -728,7 +820,7 @@ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, mTimePicker = view.findViewById(R.id.mdtp_time_picker); mTimePicker.setOnValueSelectedListener(this); mTimePicker.setOnKeyListener(keyboardListener); - mTimePicker.initialize(getActivity(), mLocale, this, mInitialTime, mIs24HourMode); + mTimePicker.initialize(mCalendarType, getActivity(), mLocale, this, mInitialTime, mIs24HourMode); int currentItemShowing = HOUR_INDEX; if (savedInstanceState != null && @@ -762,8 +854,7 @@ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, dismiss(); }); mOkButton.setOnKeyListener(keyboardListener); - mOkButton.setTypeface(ResourcesCompat.getFont(context, R.font.robotomedium)); - if(mOkString != null) mOkButton.setText(mOkString); + if (mOkString != null) mOkButton.setText(mOkString); else mOkButton.setText(mOkResid); mCancelButton = view.findViewById(R.id.mdtp_cancel); @@ -771,8 +862,7 @@ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, tryVibrate(); if (getDialog() != null) getDialog().cancel(); }); - mCancelButton.setTypeface(ResourcesCompat.getFont(context, R.font.robotomedium)); - if(mCancelString != null) mCancelButton.setText(mCancelString); + if (mCancelString != null) mCancelButton.setText(mCancelString); else mCancelButton.setText(mCancelResid); mCancelButton.setVisibility(isCancelable() ? View.VISIBLE : View.GONE); @@ -793,7 +883,7 @@ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, } mTimePicker.setAmOrPm(amOrPm); }; - mAmTextView .setVisibility(View.GONE); + mAmTextView.setVisibility(View.GONE); mPmTextView.setVisibility(View.VISIBLE); mAmPmLayout.setOnClickListener(listener); if (mVersion == Version.VERSION_2) { @@ -906,8 +996,7 @@ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, paramsAmPm.addRule(RelativeLayout.BELOW, R.id.mdtp_seconds_space); mAmPmLayout.setLayoutParams(paramsAmPm); } - } - else if (mIs24HourMode && !mEnableSeconds && mEnableMinutes) { + } else if (mIs24HourMode && !mEnableSeconds && mEnableMinutes) { // center first separator RelativeLayout.LayoutParams paramsSeparator = new RelativeLayout.LayoutParams( LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT @@ -925,7 +1014,7 @@ else if (mIs24HourMode && !mEnableSeconds && mEnableMinutes) { if (!mIs24HourMode) { RelativeLayout.LayoutParams paramsAmPm = new RelativeLayout.LayoutParams( - LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT + LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT ); paramsAmPm.addRule(RelativeLayout.RIGHT_OF, R.id.mdtp_hour_space); paramsAmPm.addRule(RelativeLayout.ALIGN_BASELINE, R.id.mdtp_hour_space); @@ -995,7 +1084,7 @@ else if (mIs24HourMode && !mEnableSeconds && mEnableMinutes) { if (mCancelColor == null) mCancelColor = mAccentColor; mCancelButton.setTextColor(mCancelColor); - if(getDialog() == null) { + if (getDialog() == null) { view.findViewById(R.id.mdtp_done_background).setVisibility(View.GONE); } @@ -1004,13 +1093,36 @@ else if (mIs24HourMode && !mEnableSeconds && mEnableMinutes) { int darkBackgroundColor = ContextCompat.getColor(context, R.color.mdtp_light_gray); int lightGray = ContextCompat.getColor(context, R.color.mdtp_light_gray); - mTimePicker.setBackgroundColor(mThemeDark? lightGray : circleBackground); + mTimePicker.setBackgroundColor(mThemeDark ? lightGray : circleBackground); view.findViewById(R.id.mdtp_time_picker_dialog).setBackgroundColor(mThemeDark ? darkBackgroundColor : backgroundColor); + + setUiFont(view); + return view; } + private void setUiFont(View view) { + final Typeface font = Utils.getCustomFont(); + if (font == null) { + final Activity activity = requireActivity(); + mOkButton.setTypeface(ResourcesCompat.getFont(activity, R.font.robotomedium)); + mCancelButton.setTypeface(ResourcesCompat.getFont(activity, R.font.robotomedium)); + } else { + if (mOkButton != null) mOkButton.setTypeface(font); + if (mCancelButton != null) mCancelButton.setTypeface(font); + if (mAmTextView != null) mAmTextView.setTypeface(font); + if (mPmTextView != null) mPmTextView.setTypeface(font); + if (mHourView != null) mHourView.setTypeface(font); + if (mMinuteView != null) mMinuteView.setTypeface(font); + if (mSecondView != null) mSecondView.setTypeface(font); + ((TextView) view.findViewById(R.id.mdtp_separator)).setTypeface(font); + ((TextView) view.findViewById(R.id.mdtp_separator_seconds)).setTypeface(font); + ((TextView) view.findViewById(R.id.mdtp_time_picker_header)).setTypeface(font); + } + } + @Override - public void onConfigurationChanged(final Configuration newConfig) { + public void onConfigurationChanged(@NonNull final Configuration newConfig) { super.onConfigurationChanged(newConfig); ViewGroup viewGroup = (ViewGroup) getView(); if (viewGroup != null) { @@ -1030,24 +1142,24 @@ public void onResume() { public void onPause() { super.onPause(); mHapticFeedbackController.stop(); - if(mDismissOnPause) dismiss(); + if (mDismissOnPause) dismiss(); } @Override - public void onCancel(DialogInterface dialog) { + public void onCancel(@NonNull DialogInterface dialog) { super.onCancel(dialog); - if(mOnCancelListener != null) mOnCancelListener.onCancel(dialog); + if (mOnCancelListener != null) mOnCancelListener.onCancel(dialog); } @Override - public void onDismiss(DialogInterface dialog) { + public void onDismiss(@NonNull DialogInterface dialog) { super.onDismiss(dialog); - if(mOnDismissListener != null) mOnDismissListener.onDismiss(dialog); + if (mOnDismissListener != null) mOnDismissListener.onDismiss(dialog); } @Override public void tryVibrate() { - if(mVibrate) mHapticFeedbackController.tryVibrate(); + if (mVibrate) mHapticFeedbackController.tryVibrate(); } private void updateAmPmDisplay(int amOrPm) { @@ -1066,7 +1178,7 @@ private void updateAmPmDisplay(int amOrPm) { mPmTextView.setText(mAmText); Utils.tryAccessibilityAnnounce(mTimePicker, mAmText); mPmTextView.setContentDescription(mAmText); - } else if (amOrPm == PM){ + } else if (amOrPm == PM) { mPmTextView.setText(mPmText); Utils.tryAccessibilityAnnounce(mTimePicker, mPmText); mPmTextView.setContentDescription(mPmText); @@ -1079,6 +1191,7 @@ private void updateAmPmDisplay(int amOrPm) { @Override public void onSaveInstanceState(@NonNull Bundle outState) { if (mTimePicker != null) { + outState.putSerializable(KEY_CALENDAR_TYPE, mCalendarType); outState.putParcelable(KEY_INITIAL_TIME, mTimePicker.getTime()); outState.putBoolean(KEY_IS_24_HOUR_VIEW, mIs24HourMode); outState.putInt(KEY_CURRENT_ITEM_SHOWING, mTimePicker.getCurrentItemShowing()); @@ -1117,28 +1230,28 @@ public void onValueSelected(Timepoint newValue) { mTimePicker.setContentDescription(mMinutePickerDescription + ": " + newValue.getMinute()); setSecond(newValue.getSecond()); mTimePicker.setContentDescription(mSecondPickerDescription + ": " + newValue.getSecond()); - if(!mIs24HourMode) updateAmPmDisplay(newValue.isAM() ? AM : PM); + if (!mIs24HourMode) updateAmPmDisplay(newValue.isAM() ? AM : PM); } @Override public void advancePicker(int index) { - if(!mAllowAutoAdvance) return; - if(index == HOUR_INDEX && mEnableMinutes) { + if (!mAllowAutoAdvance) return; + if (index == HOUR_INDEX && mEnableMinutes) { setCurrentItemShowing(MINUTE_INDEX, true, true, false); String announcement = mSelectHours + ". " + mTimePicker.getMinutes(); Utils.tryAccessibilityAnnounce(mTimePicker, announcement); - } else if(index == MINUTE_INDEX && mEnableSeconds) { + } else if (index == MINUTE_INDEX && mEnableSeconds) { setCurrentItemShowing(SECOND_INDEX, true, true, false); - String announcement = mSelectMinutes+". " + mTimePicker.getSeconds(); + String announcement = mSelectMinutes + ". " + mTimePicker.getSeconds(); Utils.tryAccessibilityAnnounce(mTimePicker, announcement); } } @Override public void enablePicker() { - if(!isTypedTimeFullyLegal()) mTypedTimes.clear(); + if (!isTypedTimeFullyLegal()) mTypedTimes.clear(); finishKbMode(true); } @@ -1163,6 +1276,7 @@ public boolean isPmDisabled() { /** * Round a given Timepoint to the nearest valid Timepoint + * * @param time Timepoint - The timepoint to round * @return Timepoint - The nearest valid Timepoint */ @@ -1175,11 +1289,18 @@ public Timepoint roundToNearest(@NonNull Timepoint time, @Nullable Timepoint.TYP return mLimiter.roundToNearest(time, type, getPickerResolution()); } + @Override + public void setFont(Typeface customFont) { + Utils.setCustomFont(customFont); + } + /** * Get the configured resolution of the current picker in terms of Timepoint components + * * @return Timepoint.TYPE (hour, minute or second) */ - @NonNull Timepoint.TYPE getPickerResolution() { + @NonNull + Timepoint.TYPE getPickerResolution() { if (mEnableSeconds) return Timepoint.TYPE.SECOND; if (mEnableMinutes) return Timepoint.TYPE.MINUTE; return Timepoint.TYPE.HOUR; @@ -1216,7 +1337,7 @@ private void setMinute(int value) { } private void setSecond(int value) { - if(value == 60) { + if (value == 60) { value = 0; } CharSequence text = String.format(mLocale, "%02d", value); @@ -1227,11 +1348,11 @@ private void setSecond(int value) { // Show either Hours or Minutes. private void setCurrentItemShowing(int index, boolean animateCircle, boolean delayLabelAnimate, - boolean announce) { + boolean announce) { mTimePicker.setCurrentItemShowing(index, animateCircle); TextView labelToAnimate; - switch(index) { + switch (index) { case HOUR_INDEX: int hours = mTimePicker.getHours(); if (!mIs24HourMode) { @@ -1276,12 +1397,13 @@ private void setCurrentItemShowing(int index, boolean animateCircle, boolean del /** * For keyboard mode, processes key events. + * * @param keyCode the pressed key. * @return true if the key was successfully processed, false otherwise. */ private boolean processKeyUp(int keyCode) { if (keyCode == KeyEvent.KEYCODE_TAB) { - if(mInKbMode) { + if (mInKbMode) { if (isTypedTimeFullyLegal()) { finishKbMode(true); } @@ -1323,7 +1445,7 @@ private boolean processKeyUp(int keyCode) { || keyCode == KeyEvent.KEYCODE_6 || keyCode == KeyEvent.KEYCODE_7 || keyCode == KeyEvent.KEYCODE_8 || keyCode == KeyEvent.KEYCODE_9 || (!mIs24HourMode && - (keyCode == getAmOrPmKeyCode(AM) || keyCode == getAmOrPmKeyCode(PM)))) { + (keyCode == getAmOrPmKeyCode(AM) || keyCode == getAmOrPmKeyCode(PM)))) { if (!mInKbMode) { if (mTimePicker == null) { // Something's wrong, because time picker should definitely not be null. @@ -1346,9 +1468,10 @@ private boolean processKeyUp(int keyCode) { /** * Try to start keyboard mode with the specified key, as long as the timepicker is not in the * middle of a touch-event. + * * @param keyCode The key to use as the first press. Keyboard mode will not be started if the - * key is not legal to start with. Or, pass in -1 to get into keyboard mode without a starting - * key. + * key is not legal to start with. Or, pass in -1 to get into keyboard mode without a starting + * key. */ private void tryStartingKbMode(int keyCode) { if (mTimePicker.trySettingInputEnabled(false) && @@ -1433,6 +1556,7 @@ private int deleteLastTypedKey() { /** * Get out of keyboard mode. If there is nothing in typedTimes, revert to TimePicker's time. + * * @param updateDisplays If true, update the displays with the relevant time. */ private void finishKbMode(boolean updateDisplays) { @@ -1456,8 +1580,9 @@ private void finishKbMode(boolean updateDisplays) { * Update the hours, minutes, seconds and AM/PM displays with the typed times. If the typedTimes * is empty, either show an empty display (filled with the placeholder text), or update from the * timepicker's values. + * * @param allowEmptyDisplay if true, then if the typedTimes is empty, use the placeholder text. - * Otherwise, revert to the timepicker's values. + * Otherwise, revert to the timepicker's values. */ private void updateDisplay(boolean allowEmptyDisplay) { if (!allowEmptyDisplay && mTypedTimes.isEmpty()) { @@ -1468,7 +1593,7 @@ private void updateDisplay(boolean allowEmptyDisplay) { setMinute(minute); setSecond(second); if (!mIs24HourMode) { - updateAmPmDisplay(hour < 12? AM : PM); + updateAmPmDisplay(hour < 12 ? AM : PM); } setCurrentItemShowing(mTimePicker.getCurrentItemShowing(), true, true, true); mOkButton.setEnabled(true); @@ -1479,9 +1604,9 @@ private void updateDisplay(boolean allowEmptyDisplay) { String minuteFormat = (enteredZeros[1]) ? "%02d" : "%2d"; String secondFormat = (enteredZeros[1]) ? "%02d" : "%2d"; String hourStr = (values[0] == -1) ? mDoublePlaceholderText : - String.format(hourFormat, values[0]).replace(' ', mPlaceholderText); + String.format(hourFormat, values[0]).replace(' ', mPlaceholderText); String minuteStr = (values[1] == -1) ? mDoublePlaceholderText : - String.format(minuteFormat, values[1]).replace(' ', mPlaceholderText); + String.format(minuteFormat, values[1]).replace(' ', mPlaceholderText); String secondStr = (values[2] == -1) ? mDoublePlaceholderText : String.format(secondFormat, values[1]).replace(' ', mPlaceholderText); mHourView.setText(hourStr); @@ -1528,9 +1653,10 @@ private static int getValFromKeyCode(int keyCode) { /** * Get the currently-entered time, as integer values of the hours, minutes and seconds typed. + * * @param enteredZeros A size-2 boolean array, which the caller should initialize, and which - * may then be used for the caller to know whether zeros had been explicitly entered as either - * hours of minutes. This is helpful for deciding whether to show the dashes, or actual 0's. + * may then be used for the caller to know whether zeros had been explicitly entered as either + * hours of minutes. This is helpful for deciding whether to show the dashes, or actual 0's. * @return A size-3 int array. The first value will be the hours, the second value will be the * minutes, and the third will be either TimePickerDialog.AM or TimePickerDialog.PM. */ @@ -1542,7 +1668,7 @@ private int[] getEnteredTime(@NonNull Boolean[] enteredZeros) { int keyCode = mTypedTimes.get(mTypedTimes.size() - 1); if (keyCode == getAmOrPmKeyCode(AM)) { amOrPm = AM; - } else if (keyCode == getAmOrPmKeyCode(PM)){ + } else if (keyCode == getAmOrPmKeyCode(PM)) { amOrPm = PM; } startIndex = 2; @@ -1557,7 +1683,7 @@ private int[] getEnteredTime(@NonNull Boolean[] enteredZeros) { if (i == startIndex) { second = val; } else if (i == startIndex + 1) { - second += 10*val; + second += 10 * val; if (val == 0) enteredZeros[2] = true; } } @@ -1583,7 +1709,7 @@ private int[] getEnteredTime(@NonNull Boolean[] enteredZeros) { } } - return new int[] {hour, minute, second, amOrPm}; + return new int[]{hour, minute, second, amOrPm}; } /** diff --git a/library/src/main/java/com/wdullaer/materialdatetimepicker/time/Timepoint.java b/library/src/main/java/com/wdullaer/materialdatetimepicker/time/Timepoint.java index 023da205..6d4019b2 100644 --- a/library/src/main/java/com/wdullaer/materialdatetimepicker/time/Timepoint.java +++ b/library/src/main/java/com/wdullaer/materialdatetimepicker/time/Timepoint.java @@ -2,6 +2,7 @@ import android.os.Parcel; import android.os.Parcelable; + import androidx.annotation.IntRange; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -14,7 +15,7 @@ * The time input is expected to use 24 hour mode. * Fields are modulo'd into their correct ranges. * It does not handle timezones. - * + *

* Created by wdullaer on 13/10/15. */ @SuppressWarnings("WeakerAccess") @@ -33,20 +34,20 @@ public Timepoint(Timepoint time) { this(time.hour, time.minute, time.second); } - public Timepoint(@IntRange(from=0, to=23) int hour, - @IntRange(from=0, to=59) int minute, - @IntRange(from=0, to=59) int second) { + public Timepoint(@IntRange(from = 0, to = 23) int hour, + @IntRange(from = 0, to = 59) int minute, + @IntRange(from = 0, to = 59) int second) { this.hour = hour % 24; this.minute = minute % 60; this.second = second % 60; } - public Timepoint(@IntRange(from=0, to=23) int hour, - @IntRange(from=0, to=59) int minute) { + public Timepoint(@IntRange(from = 0, to = 23) int hour, + @IntRange(from = 0, to = 59) int minute) { this(hour, minute, 0); } - public Timepoint(@IntRange(from=0, to=23) int hour) { + public Timepoint(@IntRange(from = 0, to = 23) int hour) { this(hour, 0); } @@ -56,17 +57,17 @@ public Timepoint(Parcel in) { second = in.readInt(); } - @IntRange(from=0, to=23) + @IntRange(from = 0, to = 23) public int getHour() { return hour; } - @IntRange(from=0, to=59) + @IntRange(from = 0, to = 59) public int getMinute() { return minute; } - @IntRange(from=0, to=59) + @IntRange(from = 0, to = 59) public int getSecond() { return second; } @@ -80,11 +81,11 @@ public boolean isPM() { } public void setAM() { - if(hour >= 12) hour = hour % 12; + if (hour >= 12) hour = hour % 12; } public void setPM() { - if(hour < 12) hour = (hour + 12) % 24; + if (hour < 12) hour = (hour + 12) % 24; } public void add(TYPE type, int value) { diff --git a/library/src/main/java/com/wdullaer/materialdatetimepicker/time/TimepointLimiter.java b/library/src/main/java/com/wdullaer/materialdatetimepicker/time/TimepointLimiter.java index 3709fa01..8f8090ba 100644 --- a/library/src/main/java/com/wdullaer/materialdatetimepicker/time/TimepointLimiter.java +++ b/library/src/main/java/com/wdullaer/materialdatetimepicker/time/TimepointLimiter.java @@ -1,6 +1,7 @@ package com.wdullaer.materialdatetimepicker.time; import android.os.Parcelable; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -9,23 +10,23 @@ public interface TimepointLimiter extends Parcelable { /** * isOutOfRange indicates whether a particular timepoint is selectable or not * It is called multiple times in the rendering path, so it should be fast - * + *

* The index parameter indicates which picker is currently visible. This is necessary because * you typically only want to compare with a resolution up to the visible component. (The * implementation should ensure that 8 is selectable, if any valid timepoint with 8 as the hour * is selectable, when the hour picker is showing) - * + *

* Similarly the overall resolution of the picker is passed in, because it can impact the * comparisons an implementation does (especially when comparing with a disabled list) - * + *

* The scope of this method is most likely too broad, which makes it hard to reason about. It is * one of the main reasons the DefaultTimeLimiter implementation of this contains extensive * example and generate tests. The default implementation should cover 90% of use cases, but if * I ever notice that a substantial amount of people are trying to implement this themselves, it * might need to be redesigned. * - * @param point A timepoint to validate - * @param index The currently showing picker (hour, minute, second) + * @param point A timepoint to validate + * @param index The currently showing picker (hour, minute, second) * @param resolution The overall resolution of the picker * @return whether the Timepoint is out of range or selectable */ @@ -36,6 +37,7 @@ public interface TimepointLimiter extends Parcelable { * This method is called when the picker is initialized or when the user clicks / taps the AM or * PM buttons. * This means that it's result can't be updated when the picker is already being rendered + * * @return true if the AM selector should be disabled */ boolean isAmDisabled(); @@ -45,6 +47,7 @@ public interface TimepointLimiter extends Parcelable { * This method is called when the picker is initialized or when the user clicks / taps the AM or * PM buttons. * This means that it's result can't be updated when the picker is already being rendered + * * @return true if the PM selector should be disabled */ boolean isPmDisabled(); @@ -53,16 +56,17 @@ public interface TimepointLimiter extends Parcelable { * roundToNearest returns the nearest selectable timepoint given a particular input * It is called whenever the user touches the screen, which means it can get called very * frequently if the user performs a drag operation - * + *

* Both the currently showing picker and the overall resolution are passed in, for similar * reasons as in isOutOfRange * - * @param time the proposed selection - * @param type the currently showing picker (hour, minute, second) + * @param time the proposed selection + * @param type the currently showing picker (hour, minute, second) * @param resolution the overall resolution of the picker * @return a selectable timepoint */ - @NonNull Timepoint roundToNearest( + @NonNull + Timepoint roundToNearest( @NonNull Timepoint time, @Nullable Timepoint.TYPE type, @NonNull Timepoint.TYPE resolution diff --git a/library/src/main/res/layout/mdtp_date_picker_selected_date_v2.xml b/library/src/main/res/layout/mdtp_date_picker_selected_date_v2.xml index 38299d30..cbbbc732 100644 --- a/library/src/main/res/layout/mdtp_date_picker_selected_date_v2.xml +++ b/library/src/main/res/layout/mdtp_date_picker_selected_date_v2.xml @@ -44,17 +44,19 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center" + android:gravity="start" android:clickable="true" + android:focusable="true" android:orientation="horizontal" android:background="@android:color/transparent" android:textColor="@color/mdtp_date_picker_selector" > - { - - }); + DatePickerDialog dpd = DatePickerDialog.newInstance( + (view, year, monthOfYear, dayOfMonth) -> { + }, + CalendarType.GREGORIAN + ); Assert.assertFalse(dpd.isHighlighted(1990, 1, 1)); } @Test public void isHighlightedShouldReturnFalseIfHighlightedDoesNotContainSelection() { - DatePickerDialog dpd = DatePickerDialog.newInstance((view, year, monthOfYear, dayOfMonth) -> { - - }); + DatePickerDialog dpd = DatePickerDialog.newInstance( + (view, year, monthOfYear, dayOfMonth) -> { + }, + CalendarType.GREGORIAN + ); Calendar highlighted = Calendar.getInstance(); highlighted.set(Calendar.YEAR, 1990); highlighted.set(Calendar.MONTH, 1); @@ -35,9 +41,11 @@ public void isHighlightedShouldReturnFalseIfHighlightedDoesNotContainSelection() @Test public void isHighlightedShouldReturnTrueIfHighlightedDoesContainSelection() { - DatePickerDialog dpd = DatePickerDialog.newInstance((view, year, monthOfYear, dayOfMonth) -> { - - }); + DatePickerDialog dpd = DatePickerDialog.newInstance( + (view, year, monthOfYear, dayOfMonth) -> { + }, + CalendarType.GREGORIAN + ); int year = 1990; int month = 1; int day = 1; @@ -58,9 +66,12 @@ public void isHighlightedShouldReturnTrueIfHighlightedDoesContainSelection() { public void isHighlightedShouldBehaveCorrectlyInCustomTimezones() { String timeZoneString = "Americas/Los_Angeles"; Calendar initial = Calendar.getInstance(TimeZone.getTimeZone(timeZoneString)); - DatePickerDialog dpd = DatePickerDialog.newInstance((view, year, monthOfYear, dayOfMonth) -> { - - }, initial); + DatePickerDialog dpd = DatePickerDialog.newInstance( + (view, year, monthOfYear, dayOfMonth) -> { + }, + CalendarType.GREGORIAN, + initial + ); int year = 1990; int month = 1; int day = 1; diff --git a/library/src/test/java/com/wdullaer/materialdatetimepicker/date/DefaultDateRangeLimiterPropertyTest.java b/library/src/test/java/com/wdullaer/materialdatetimepicker/date/DefaultDateRangeLimiterPropertyTest.java index 59e76bf3..d2b732ba 100644 --- a/library/src/test/java/com/wdullaer/materialdatetimepicker/date/DefaultDateRangeLimiterPropertyTest.java +++ b/library/src/test/java/com/wdullaer/materialdatetimepicker/date/DefaultDateRangeLimiterPropertyTest.java @@ -1,9 +1,14 @@ package com.wdullaer.materialdatetimepicker.date; +import android.graphics.Typeface; + import com.pholser.junit.quickcheck.Property; import com.pholser.junit.quickcheck.generator.InRange; import com.pholser.junit.quickcheck.runner.JUnitQuickcheck; import com.wdullaer.materialdatetimepicker.Utils; +import com.wdullaer.materialdatetimepicker.enums.CalendarType; +import com.wdullaer.materialdatetimepicker.enums.ScrollOrientation; +import com.wdullaer.materialdatetimepicker.enums.Version; import org.junit.Assert; import org.junit.runner.RunWith; @@ -23,16 +28,20 @@ public class DefaultDateRangeLimiterPropertyTest { final private DatePickerController controller = new DatePickerController() { @Override - public void onYearSelected(int year) {} + public void onYearSelected(int year) { + } @Override - public void onDayOfMonthSelected(int year, int month, int day) {} + public void onDayOfMonthSelected(int year, int month, int day) { + } @Override - public void registerOnDateChangedListener(DatePickerDialog.OnDateChangedListener listener) {} + public void registerOnDateChangedListener(DatePickerDialog.OnDateChangedListener listener) { + } @Override - public void unregisterOnDateChangedListener(DatePickerDialog.OnDateChangedListener listener) {} + public void unregisterOnDateChangedListener(DatePickerDialog.OnDateChangedListener listener) { + } @Override public MonthAdapter.CalendarDay getSelectedDay() { @@ -85,7 +94,8 @@ public boolean isOutOfRange(int year, int month, int day) { } @Override - public void tryVibrate() {} + public void tryVibrate() { + } @Override public TimeZone getTimeZone() { @@ -98,13 +108,22 @@ public Locale getLocale() { } @Override - public DatePickerDialog.Version getVersion() { - return DatePickerDialog.Version.VERSION_2; + public Version getVersion() { + return Version.VERSION_2; + } + + @Override + public ScrollOrientation getScrollOrientation() { + return ScrollOrientation.HORIZONTAL; + } + + @Override + public CalendarType getCalendarType() { + return CalendarType.GREGORIAN; } @Override - public DatePickerDialog.ScrollOrientation getScrollOrientation() { - return DatePickerDialog.ScrollOrientation.HORIZONTAL; + public void setFont(Typeface customFont) { } }; diff --git a/library/src/test/java/com/wdullaer/materialdatetimepicker/date/DefaultDateRangeLimiterTest.java b/library/src/test/java/com/wdullaer/materialdatetimepicker/date/DefaultDateRangeLimiterTest.java index ef7bbd04..b12467b2 100644 --- a/library/src/test/java/com/wdullaer/materialdatetimepicker/date/DefaultDateRangeLimiterTest.java +++ b/library/src/test/java/com/wdullaer/materialdatetimepicker/date/DefaultDateRangeLimiterTest.java @@ -1,9 +1,14 @@ package com.wdullaer.materialdatetimepicker.date; +import android.graphics.Typeface; + import com.wdullaer.materialdatetimepicker.Utils; +import com.wdullaer.materialdatetimepicker.enums.CalendarType; +import com.wdullaer.materialdatetimepicker.enums.ScrollOrientation; +import com.wdullaer.materialdatetimepicker.enums.Version; -import org.junit.Test; import org.junit.Assert; +import org.junit.Test; import java.util.Arrays; import java.util.Calendar; @@ -13,22 +18,26 @@ /** * Unit tests for the default DateRangeLimiter implementation * Primarily used to assert that the rounding logic functions properly - * + *

* Created by wdullaer on 14/04/17. */ public class DefaultDateRangeLimiterTest { final private DatePickerController controller = new DatePickerController() { @Override - public void onYearSelected(int year) {} + public void onYearSelected(int year) { + } @Override - public void onDayOfMonthSelected(int year, int month, int day) {} + public void onDayOfMonthSelected(int year, int month, int day) { + } @Override - public void registerOnDateChangedListener(DatePickerDialog.OnDateChangedListener listener) {} + public void registerOnDateChangedListener(DatePickerDialog.OnDateChangedListener listener) { + } @Override - public void unregisterOnDateChangedListener(DatePickerDialog.OnDateChangedListener listener) {} + public void unregisterOnDateChangedListener(DatePickerDialog.OnDateChangedListener listener) { + } @Override public MonthAdapter.CalendarDay getSelectedDay() { @@ -81,7 +90,8 @@ public boolean isOutOfRange(int year, int month, int day) { } @Override - public void tryVibrate() {} + public void tryVibrate() { + } @Override public TimeZone getTimeZone() { @@ -94,13 +104,22 @@ public Locale getLocale() { } @Override - public DatePickerDialog.Version getVersion() { - return DatePickerDialog.Version.VERSION_2; + public Version getVersion() { + return Version.VERSION_2; } @Override - public DatePickerDialog.ScrollOrientation getScrollOrientation() { - return DatePickerDialog.ScrollOrientation.HORIZONTAL; + public ScrollOrientation getScrollOrientation() { + return ScrollOrientation.HORIZONTAL; + } + + @Override + public CalendarType getCalendarType() { + return CalendarType.GREGORIAN; + } + + @Override + public void setFont(Typeface customFont) { } }; @@ -109,7 +128,7 @@ public DatePickerDialog.ScrollOrientation getScrollOrientation() { public void getSelectableDaysShouldHaveDatesTrimmedToMidnight() { DefaultDateRangeLimiter limiter = new DefaultDateRangeLimiter(); Calendar[] days = new Calendar[3]; - for (int i = 0;i < days.length; i++) { + for (int i = 0; i < days.length; i++) { Calendar day = Calendar.getInstance(); day.set(Calendar.YEAR, 1999 + i); day.set(Calendar.HOUR_OF_DAY, 2); @@ -136,7 +155,7 @@ public void getSelectableDaysShouldHaveDatesTrimmedToMidnight() { public void getDisabledDaysShouldHaveDatesTrimmedToMidnight() { DefaultDateRangeLimiter limiter = new DefaultDateRangeLimiter(); Calendar[] days = new Calendar[3]; - for (int i = 0;i < days.length; i++) { + for (int i = 0; i < days.length; i++) { Calendar day = Calendar.getInstance(); day.set(Calendar.YEAR, 1999 + i); day.set(Calendar.HOUR_OF_DAY, 2); @@ -452,16 +471,20 @@ public void isOutOfRangeShouldWorkWithCustomTimeZones() { days[0] = disabledDay; DatePickerController controller = new DatePickerController() { @Override - public void onYearSelected(int year) {} + public void onYearSelected(int year) { + } @Override - public void onDayOfMonthSelected(int year, int month, int day) {} + public void onDayOfMonthSelected(int year, int month, int day) { + } @Override - public void registerOnDateChangedListener(DatePickerDialog.OnDateChangedListener listener) {} + public void registerOnDateChangedListener(DatePickerDialog.OnDateChangedListener listener) { + } @Override - public void unregisterOnDateChangedListener(DatePickerDialog.OnDateChangedListener listener) {} + public void unregisterOnDateChangedListener(DatePickerDialog.OnDateChangedListener listener) { + } @Override public MonthAdapter.CalendarDay getSelectedDay() { @@ -529,14 +552,23 @@ public Locale getLocale() { } @Override - public DatePickerDialog.Version getVersion() { + public Version getVersion() { return null; } @Override - public DatePickerDialog.ScrollOrientation getScrollOrientation() { + public ScrollOrientation getScrollOrientation() { return null; } + + @Override + public CalendarType getCalendarType() { + return CalendarType.GREGORIAN; + } + + @Override + public void setFont(Typeface customFont) { + } }; limiter.setDisabledDays(days); @@ -559,7 +591,7 @@ public void setToNearestShouldReturnTheInputWhenValid() { public void setToNearestShouldRoundDisabledDates() { DefaultDateRangeLimiter limiter = new DefaultDateRangeLimiter(); Calendar[] days = new Calendar[3]; - for (int i = 0;i < days.length; i++) { + for (int i = 0; i < days.length; i++) { Calendar day = Calendar.getInstance(); day.set(Calendar.YEAR, 1999 + i); day.set(Calendar.HOUR_OF_DAY, 2); @@ -613,7 +645,7 @@ public void setToNearestShouldRoundToMaxDate() { public void setToNearestShouldRoundToASelectableDay() { DefaultDateRangeLimiter limiter = new DefaultDateRangeLimiter(); Calendar[] days = new Calendar[3]; - for (int i = 0;i < days.length; i++) { + for (int i = 0; i < days.length; i++) { Calendar day = Calendar.getInstance(); day.set(Calendar.YEAR, 1999 + i); day.set(Calendar.HOUR_OF_DAY, 2); @@ -638,7 +670,7 @@ public void setToNearestShouldRoundToASelectableDayWhenAControllerIsSet() { DefaultDateRangeLimiter limiter = new DefaultDateRangeLimiter(); limiter.setController(controller); Calendar[] days = new Calendar[3]; - for (int i = 0;i < days.length; i++) { + for (int i = 0; i < days.length; i++) { Calendar day = Calendar.getInstance(); day.set(Calendar.YEAR, 1999 + i); day.set(Calendar.HOUR_OF_DAY, 2); diff --git a/library/src/test/java/com/wdullaer/materialdatetimepicker/time/DefaultTimepointLimiterTest.java b/library/src/test/java/com/wdullaer/materialdatetimepicker/time/DefaultTimepointLimiterTest.java index 6f21b879..6ea9e3d0 100644 --- a/library/src/test/java/com/wdullaer/materialdatetimepicker/time/DefaultTimepointLimiterTest.java +++ b/library/src/test/java/com/wdullaer/materialdatetimepicker/time/DefaultTimepointLimiterTest.java @@ -1,15 +1,15 @@ package com.wdullaer.materialdatetimepicker.time; +import org.junit.Assert; +import org.junit.Test; + import static com.wdullaer.materialdatetimepicker.time.TimePickerDialog.HOUR_INDEX; import static com.wdullaer.materialdatetimepicker.time.TimePickerDialog.MINUTE_INDEX; -import org.junit.Test; -import org.junit.Assert; - /** * Unit tests for the default implementation of TimepointLimiter * Mostly used to assert that the rounding logic works - * + *

* Created by wdullaer on 22/06/17. */ public class DefaultTimepointLimiterTest { @@ -257,7 +257,7 @@ public void isOutOfRangeShouldReturnTrueIfInputNotSelectable() { new Timepoint(13), new Timepoint(14) }; - DefaultTimepointLimiter limiter = new DefaultTimepointLimiter(); + DefaultTimepointLimiter limiter = new DefaultTimepointLimiter(); limiter.setSelectableTimes(selectableDays); diff --git a/library/src/test/java/com/wdullaer/materialdatetimepicker/time/TimePickerDialogTest.java b/library/src/test/java/com/wdullaer/materialdatetimepicker/time/TimePickerDialogTest.java index 3bad88b4..10a38bfa 100644 --- a/library/src/test/java/com/wdullaer/materialdatetimepicker/time/TimePickerDialogTest.java +++ b/library/src/test/java/com/wdullaer/materialdatetimepicker/time/TimePickerDialogTest.java @@ -1,19 +1,21 @@ package com.wdullaer.materialdatetimepicker.time; +import com.wdullaer.materialdatetimepicker.enums.CalendarType; + import org.junit.Assert; import org.junit.Test; public class TimePickerDialogTest { @Test public void getPickerResolutionShouldReturnSecondIfSecondsAreEnabled() { - TimePickerDialog tpd = TimePickerDialog.newInstance(null, false); + TimePickerDialog tpd = TimePickerDialog.newInstance(null, CalendarType.GREGORIAN, false); tpd.enableSeconds(true); Assert.assertEquals(tpd.getPickerResolution(), Timepoint.TYPE.SECOND); } @Test public void getPickerResolutionShouldReturnMinuteIfMinutesAreEnabled() { - TimePickerDialog tpd = TimePickerDialog.newInstance(null, false); + TimePickerDialog tpd = TimePickerDialog.newInstance(null, CalendarType.GREGORIAN, false); tpd.enableSeconds(false); tpd.enableMinutes(true); Assert.assertEquals(tpd.getPickerResolution(), Timepoint.TYPE.MINUTE); @@ -21,7 +23,7 @@ public void getPickerResolutionShouldReturnMinuteIfMinutesAreEnabled() { @Test public void getPickerResolutionShouldReturnHourIfMinutesAndSecondsAreDisabled() { - TimePickerDialog tpd = TimePickerDialog.newInstance(null, false); + TimePickerDialog tpd = TimePickerDialog.newInstance(null, CalendarType.GREGORIAN, false); tpd.enableMinutes(false); Assert.assertEquals(tpd.getPickerResolution(), Timepoint.TYPE.HOUR); } diff --git a/library/src/test/java/com/wdullaer/materialdatetimepicker/time/TimepointTest.java b/library/src/test/java/com/wdullaer/materialdatetimepicker/time/TimepointTest.java index 3c25e83f..dc7b84f6 100644 --- a/library/src/test/java/com/wdullaer/materialdatetimepicker/time/TimepointTest.java +++ b/library/src/test/java/com/wdullaer/materialdatetimepicker/time/TimepointTest.java @@ -1,7 +1,7 @@ package com.wdullaer.materialdatetimepicker.time; -import org.junit.Test; import org.junit.Assert; +import org.junit.Test; import java.util.HashSet; diff --git a/sample/build.gradle b/sample/build.gradle index 7f625112..f510f253 100644 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -10,6 +10,7 @@ android { targetSdkVersion Integer.parseInt(project.ANDROID_BUILD_TARGET_SDK_VERSION) versionName project.VERSION_NAME versionCode Integer.parseInt(project.VERSION_CODE) + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { @@ -35,4 +36,6 @@ dependencies { implementation 'com.google.android.material:material:1.0.0' implementation 'androidx.viewpager:viewpager:1.0.0' debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.0-alpha-1' + androidTestImplementation 'androidx.test:runner:1.2.0' + androidTestImplementation 'androidx.test.ext:junit:1.1.1' } diff --git a/sample/src/androidTest/java/com/wdullaer/datetimepickerexample/ApplicationTest.java b/sample/src/androidTest/java/com/wdullaer/datetimepickerexample/ApplicationTest.java deleted file mode 100644 index 88a7376a..00000000 --- a/sample/src/androidTest/java/com/wdullaer/datetimepickerexample/ApplicationTest.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.wdullaer.datetimepickerexample; - -import android.app.Application; -import android.test.ApplicationTestCase; - -/** - * Testing Fundamentals - */ -public class ApplicationTest extends ApplicationTestCase { - public ApplicationTest() { - super(Application.class); - } -} \ No newline at end of file diff --git a/sample/src/androidTest/java/com/wdullaer/datetimepickerexample/ExampleInstrumentedTest.java b/sample/src/androidTest/java/com/wdullaer/datetimepickerexample/ExampleInstrumentedTest.java new file mode 100644 index 00000000..b4eefdee --- /dev/null +++ b/sample/src/androidTest/java/com/wdullaer/datetimepickerexample/ExampleInstrumentedTest.java @@ -0,0 +1,27 @@ +package com.wdullaer.datetimepickerexample; + +import android.content.Context; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.assertEquals; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + + assertEquals("com.wdullaer.materialdatetimepicker.sample", appContext.getPackageName()); + } +} diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml index a7830ee8..4936c8f5 100644 --- a/sample/src/main/AndroidManifest.xml +++ b/sample/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" + android:supportsRtl="true" android:theme="@style/AppTheme" > adapter = ArrayAdapter.createFromResource(requireActivity(), + R.array.calendar_types_array, android.R.layout.simple_spinner_item); + // Specify the layout to use when the list of choices appears + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + // Apply the adapter to the spinner + spinner.setAdapter(adapter); + + final Spinner localeSpinner = view.findViewById(R.id.calendar_locale); + // Create an ArrayAdapter using the string array and a default spinner layout + ArrayAdapter localeAdapter = ArrayAdapter.createFromResource(requireActivity(), + R.array.calendar_Locales_array, android.R.layout.simple_spinner_item); + // Specify the layout to use when the list of choices appears + localeAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + // Apply the adapter to the spinner + localeSpinner.setAdapter(localeAdapter); + + font = Typeface.createFromAsset(requireActivity().getAssets(), "IRANYekanMobileRegular.ttf"); + view.findViewById(R.id.original_button).setOnClickListener(v -> { Calendar now = Calendar.getInstance(); new android.app.DatePickerDialog( @@ -73,18 +106,29 @@ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, // Show a datepicker when the dateButton is clicked dateButton.setOnClickListener(v -> { - Calendar now = Calendar.getInstance(); - if (defaultSelection.isChecked()) { - now.add(Calendar.DATE, 7); - } + Calendar now; /* It is recommended to always create a new instance whenever you need to show a Dialog. The sample app is reusing them because it is useful when looking for regressions during testing */ + + if (spinner.getSelectedItemPosition() == 0) { + calendarType = CalendarType.JALALI; + now = JalaliCalendar.getInstance(); + } else { + calendarType = CalendarType.GREGORIAN; + now = Calendar.getInstance(); + } + + if (defaultSelection.isChecked()) { + now.add(Calendar.DATE, 7); + } + if (dpd == null) { dpd = DatePickerDialog.newInstance( DatePickerFragment.this, + calendarType, now.get(Calendar.YEAR), now.get(Calendar.MONTH), now.get(Calendar.DAY_OF_MONTH) @@ -92,21 +136,47 @@ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, } else { dpd.initialize( DatePickerFragment.this, + calendarType, now.get(Calendar.YEAR), now.get(Calendar.MONTH), now.get(Calendar.DAY_OF_MONTH) ); } + + switch (calendarType) { + case JALALI: + dpd.setFont(font); + break; + case GREGORIAN: + default: + dpd.setFont(null); + break; + } + + if (localeSpinner.getSelectedItemPosition() == 0) { + dpd.setLocale(new Locale("fa")); + } else { + dpd.setLocale(Locale.ENGLISH); + } + dpd.setThemeDark(modeDarkDate.isChecked()); dpd.vibrate(vibrateDate.isChecked()); dpd.dismissOnPause(dismissDate.isChecked()); dpd.showYearPickerFirst(showYearFirst.isChecked()); - dpd.setVersion(showVersion2.isChecked() ? DatePickerDialog.Version.VERSION_2 : DatePickerDialog.Version.VERSION_1); + dpd.setVersion(showVersion2.isChecked() ? Version.VERSION_2 : Version.VERSION_1); if (modeCustomAccentDate.isChecked()) { dpd.setAccentColor(Color.parseColor("#9C27B0")); } if (titleDate.isChecked()) { - dpd.setTitle("DatePicker Title"); + switch (calendarType) { + case JALALI: + dpd.setTitle("عنوان انتخابگر تاریخ"); + break; + case GREGORIAN: + default: + dpd.setTitle("DatePicker Title"); + break; + } } if (highlightDays.isChecked()) { Calendar date1 = Calendar.getInstance(); @@ -120,17 +190,26 @@ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, if (limitSelectableDays.isChecked()) { Calendar[] days = new Calendar[13]; for (int i = -6; i < 7; i++) { - Calendar day = Calendar.getInstance(); + Calendar day; + switch (calendarType) { + case JALALI: + day = JalaliCalendar.getInstance(); + break; + case GREGORIAN: + default: + day = Calendar.getInstance(); + break; + } day.add(Calendar.DAY_OF_MONTH, i * 2); days[i + 6] = day; } dpd.setSelectableDays(days); } if (switchOrientation.isChecked()) { - if (dpd.getVersion() == DatePickerDialog.Version.VERSION_1) { - dpd.setScrollOrientation(DatePickerDialog.ScrollOrientation.HORIZONTAL); + if (dpd.getVersion() == Version.VERSION_1) { + dpd.setScrollOrientation(ScrollOrientation.HORIZONTAL); } else { - dpd.setScrollOrientation(DatePickerDialog.ScrollOrientation.VERTICAL); + dpd.setScrollOrientation(ScrollOrientation.VERTICAL); } } dpd.setOnCancelListener(dialog -> { @@ -153,12 +232,12 @@ public void onDestroy() { public void onResume() { super.onResume(); DatePickerDialog dpd = (DatePickerDialog) requireFragmentManager().findFragmentByTag("Datepickerdialog"); - if(dpd != null) dpd.setOnDateSetListener(this); + if (dpd != null) dpd.setOnDateSetListener(this); } @Override public void onDateSet(DatePickerDialog view, int year, int monthOfYear, int dayOfMonth) { - String date = "You picked the following date: "+dayOfMonth+"/"+(++monthOfYear)+"/"+year; + String date = "You picked the following date: " + dayOfMonth + "/" + (++monthOfYear) + "/" + year; dateTextView.setText(date); dpd = null; } diff --git a/sample/src/main/java/com/wdullaer/datetimepickerexample/MainActivity.java b/sample/src/main/java/com/wdullaer/datetimepickerexample/MainActivity.java index 556d9df2..d6f05332 100644 --- a/sample/src/main/java/com/wdullaer/datetimepickerexample/MainActivity.java +++ b/sample/src/main/java/com/wdullaer/datetimepickerexample/MainActivity.java @@ -1,15 +1,17 @@ package com.wdullaer.datetimepickerexample; import android.os.Bundle; -import com.google.android.material.tabs.TabLayout; + +import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentPagerAdapter; import androidx.viewpager.widget.ViewPager; -public class MainActivity extends AppCompatActivity -{ +import com.google.android.material.tabs.TabLayout; + +public class MainActivity extends AppCompatActivity { ViewPager viewPager; PickerAdapter adapter; @@ -25,7 +27,7 @@ protected void onCreate(Bundle savedInstanceState) { setSupportActionBar(findViewById(R.id.toolbar)); TabLayout tabLayout = findViewById(R.id.tabs); tabLayout.setupWithViewPager(viewPager); - for(int i=0; i < adapter.getCount(); i++) //noinspection ConstantConditions + for (int i = 0; i < adapter.getCount(); i++) //noinspection ConstantConditions tabLayout.getTabAt(i).setText(adapter.getTitle(i)); } @@ -35,7 +37,7 @@ private class PickerAdapter extends FragmentPagerAdapter { Fragment datePickerFragment; PickerAdapter(FragmentManager fm) { - super(fm); + super(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT); timePickerFragment = new TimePickerFragment(); datePickerFragment = new DatePickerFragment(); } @@ -45,9 +47,10 @@ public int getCount() { return NUM_PAGES; } + @NonNull @Override public Fragment getItem(int position) { - switch(position) { + switch (position) { case 0: return timePickerFragment; case 1: @@ -57,7 +60,7 @@ public Fragment getItem(int position) { } int getTitle(int position) { - switch(position) { + switch (position) { case 0: return R.string.tab_title_time; case 1: diff --git a/sample/src/main/java/com/wdullaer/datetimepickerexample/TimePickerFragment.java b/sample/src/main/java/com/wdullaer/datetimepickerexample/TimePickerFragment.java index 7eab7af2..36d06e2f 100644 --- a/sample/src/main/java/com/wdullaer/datetimepickerexample/TimePickerFragment.java +++ b/sample/src/main/java/com/wdullaer/datetimepickerexample/TimePickerFragment.java @@ -1,17 +1,23 @@ package com.wdullaer.datetimepickerexample; import android.graphics.Color; +import android.graphics.Typeface; import android.os.Bundle; -import androidx.annotation.NonNull; -import androidx.fragment.app.Fragment; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.CheckBox; +import android.widget.Spinner; import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; + +import com.wdullaer.materialdatetimepicker.enums.CalendarType; +import com.wdullaer.materialdatetimepicker.enums.Version; import com.wdullaer.materialdatetimepicker.time.TimePickerDialog; import com.wdullaer.materialdatetimepicker.time.Timepoint; @@ -33,8 +39,12 @@ public class TimePickerFragment extends Fragment implements TimePickerDialog.OnT private CheckBox limitSelectableTimes; private CheckBox disableSpecificTimes; private CheckBox showVersion2; + + private static Typeface font; private TimePickerDialog tpd; + private CalendarType calendarType; + public TimePickerFragment() { // Required empty public constructor } @@ -58,6 +68,17 @@ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, disableSpecificTimes = view.findViewById(R.id.disable_times); showVersion2 = view.findViewById(R.id.show_version_2); + final Spinner spinner = view.findViewById(R.id.calendar_type); + // Create an ArrayAdapter using the string array and a default spinner layout + ArrayAdapter adapter = ArrayAdapter.createFromResource(requireActivity(), + R.array.calendar_types_array, android.R.layout.simple_spinner_item); + // Specify the layout to use when the list of choices appears + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + // Apply the adapter to the spinner + spinner.setAdapter(adapter); + + font = Typeface.createFromAsset(requireActivity().getAssets(), "IRANYekanMobileRegular.ttf"); + view.findViewById(R.id.original_button).setOnClickListener(view1 -> { Calendar now = Calendar.getInstance(); new android.app.TimePickerDialog( @@ -72,6 +93,13 @@ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, // Show a timepicker when the timeButton is clicked timeButton.setOnClickListener(v -> { Calendar now = Calendar.getInstance(); + + if (spinner.getSelectedItemPosition() == 0) { + calendarType = CalendarType.JALALI; + } else { + calendarType = CalendarType.GREGORIAN; + } + /* It is recommended to always create a new instance whenever you need to show a Dialog. The sample app is reusing them because it is useful when looking for regressions @@ -80,6 +108,7 @@ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, if (tpd == null) { tpd = TimePickerDialog.newInstance( TimePickerFragment.this, + calendarType, now.get(Calendar.HOUR_OF_DAY), now.get(Calendar.MINUTE), mode24Hours.isChecked() @@ -87,22 +116,41 @@ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, } else { tpd.initialize( TimePickerFragment.this, + calendarType, now.get(Calendar.HOUR_OF_DAY), now.get(Calendar.MINUTE), now.get(Calendar.SECOND), mode24Hours.isChecked() ); } + + switch (calendarType) { + case JALALI: + tpd.setFont(font); + break; + case GREGORIAN: + default: + tpd.setFont(null); + break; + } tpd.setThemeDark(modeDarkTime.isChecked()); tpd.vibrate(vibrateTime.isChecked()); tpd.dismissOnPause(dismissTime.isChecked()); tpd.enableSeconds(enableSeconds.isChecked()); - tpd.setVersion(showVersion2.isChecked() ? TimePickerDialog.Version.VERSION_2 : TimePickerDialog.Version.VERSION_1); + tpd.setVersion(showVersion2.isChecked() ? Version.VERSION_2 : Version.VERSION_1); if (modeCustomAccentTime.isChecked()) { tpd.setAccentColor(Color.parseColor("#9C27B0")); } if (titleTime.isChecked()) { - tpd.setTitle("TimePicker Title"); + switch (calendarType) { + case JALALI: + tpd.setTitle("عنوان انتخابگر زمان"); + break; + case GREGORIAN: + default: + tpd.setTitle("TimePicker Title"); + break; + } } if (limitSelectableTimes.isChecked()) { if (enableSeconds.isChecked()) { @@ -140,15 +188,15 @@ public void onDestroy() { public void onResume() { super.onResume(); TimePickerDialog tpd = (TimePickerDialog) requireFragmentManager().findFragmentByTag("Timepickerdialog"); - if(tpd != null) tpd.setOnTimeSetListener(this); + if (tpd != null) tpd.setOnTimeSetListener(this); } @Override public void onTimeSet(TimePickerDialog view, int hourOfDay, int minute, int second) { - String hourString = hourOfDay < 10 ? "0"+hourOfDay : ""+hourOfDay; - String minuteString = minute < 10 ? "0"+minute : ""+minute; - String secondString = second < 10 ? "0"+second : ""+second; - String time = "You picked the following time: "+hourString+"h"+minuteString+"m"+secondString+"s"; + String hourString = hourOfDay < 10 ? "0" + hourOfDay : "" + hourOfDay; + String minuteString = minute < 10 ? "0" + minute : "" + minute; + String secondString = second < 10 ? "0" + second : "" + second; + String time = "You picked the following time: " + hourString + "h" + minuteString + "m" + secondString + "s"; timeTextView.setText(time); tpd = null; } diff --git a/sample/src/main/res/layout/datepicker_layout.xml b/sample/src/main/res/layout/datepicker_layout.xml index 4fd9decf..a1d7cd7f 100644 --- a/sample/src/main/res/layout/datepicker_layout.xml +++ b/sample/src/main/res/layout/datepicker_layout.xml @@ -26,6 +26,26 @@ android:layout_height="wrap_content" android:text="@string/original_button"/> + + + + + + Date Date+Time + + Jalali + Gregorian + + + + Persian + English + +