From 74d7db40e23368283cc899274b41e5138e9845b8 Mon Sep 17 00:00:00 2001 From: Henning Hall Date: Thu, 14 Jun 2018 23:36:59 +0200 Subject: [PATCH] Minute interval support. --- README.md | 2 +- .../date_picker/DatePickerManager.java | 10 +- .../henninghall/date_picker/PickerView.java | 149 ++++++++++++------ .../wheelFunctions/AnimateToDate.java | 21 +++ .../date_picker/wheelFunctions/Refresh.java | 13 ++ .../date_picker/wheelFunctions/SetDate.java | 21 +++ .../wheelFunctions/WheelFunction.java | 10 ++ .../date_picker/wheels/AmPmWheel.java | 22 +-- .../date_picker/wheels/DayWheel.java | 13 +- .../date_picker/wheels/HourWheel.java | 14 +- .../date_picker/wheels/MinutesWheel.java | 26 ++- .../henninghall/date_picker/wheels/Wheel.java | 46 ++---- example/App.js | 4 +- 13 files changed, 221 insertions(+), 130 deletions(-) create mode 100644 android/src/main/java/com/henninghall/date_picker/wheelFunctions/AnimateToDate.java create mode 100644 android/src/main/java/com/henninghall/date_picker/wheelFunctions/Refresh.java create mode 100644 android/src/main/java/com/henninghall/date_picker/wheelFunctions/SetDate.java create mode 100644 android/src/main/java/com/henninghall/date_picker/wheelFunctions/WheelFunction.java diff --git a/README.md b/README.md index b7583e7..fe442d0 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ The goal is to make a cross platform variant of [DatePickerIOS](https://facebook - [x] Animate to date when state change occur. - [x] Switch between AM/PM when scrolling between forenoon and afternoon. - [x] Support maximumDate/minimumDate. -- [ ] Minute interval prop. +- [x] Minute interval prop. - [ ] Colored background support. - [ ] Align text to right. - [ ] Mode: date diff --git a/android/src/main/java/com/henninghall/date_picker/DatePickerManager.java b/android/src/main/java/com/henninghall/date_picker/DatePickerManager.java index 85e3a9e..3d41740 100644 --- a/android/src/main/java/com/henninghall/date_picker/DatePickerManager.java +++ b/android/src/main/java/com/henninghall/date_picker/DatePickerManager.java @@ -2,17 +2,13 @@ package com.henninghall.date_picker; import android.support.annotation.Nullable; import android.view.View; - import com.facebook.react.common.MapBuilder; import com.facebook.react.uimanager.SimpleViewManager; import com.facebook.react.uimanager.ThemedReactContext; import com.facebook.react.uimanager.annotations.ReactProp; - import net.time4j.android.ApplicationStarter; - import org.apache.commons.lang3.LocaleUtils; -import java.util.Date; import java.util.Map; public class DatePickerManager extends SimpleViewManager { @@ -53,6 +49,12 @@ public class DatePickerManager extends SimpleViewManager { view.setMaximumDate(Utils.unixToDate(date)); } + @ReactProp(name = "minuteInterval") + public void setMinuteInterval(PickerView view, @Nullable int interval) throws Exception { + if (interval < 0 || interval > 59) throw new Exception("Minute interval out of bounds"); + if (interval > 1) view.setMinuteInterval(interval); + } + public Map getExportedCustomBubblingEventTypeConstants() { return MapBuilder.builder() .put("dateChange", MapBuilder.of("phasedRegistrationNames", diff --git a/android/src/main/java/com/henninghall/date_picker/PickerView.java b/android/src/main/java/com/henninghall/date_picker/PickerView.java index 9cc8bd3..1a23fdb 100644 --- a/android/src/main/java/com/henninghall/date_picker/PickerView.java +++ b/android/src/main/java/com/henninghall/date_picker/PickerView.java @@ -1,11 +1,16 @@ package com.henninghall.date_picker; +import android.annotation.SuppressLint; import android.view.View; import android.widget.RelativeLayout; import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.WritableMap; import com.facebook.react.uimanager.events.RCTEventEmitter; +import com.henninghall.date_picker.wheelFunctions.AnimateToDate; +import com.henninghall.date_picker.wheelFunctions.Refresh; +import com.henninghall.date_picker.wheelFunctions.SetDate; +import com.henninghall.date_picker.wheelFunctions.WheelFunction; import com.henninghall.date_picker.wheels.AmPmWheel; import com.henninghall.date_picker.wheels.DayWheel; import com.henninghall.date_picker.wheels.HourWheel; @@ -16,8 +21,12 @@ import org.apache.commons.lang3.time.DateUtils; import java.text.ParseException; import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Calendar; +import java.util.Collection; import java.util.Date; +import java.util.List; import java.util.Locale; import cn.carbswang.android.numberpickerview.library.NumberPickerView; @@ -25,41 +34,41 @@ import cn.carbswang.android.numberpickerview.library.NumberPickerView; public class PickerView extends RelativeLayout { - private final RelativeLayout wheelsWrapper; private final NumberPickerView hourPicker; private final NumberPickerView ampmPicker; private SimpleDateFormat dateFormat; private HourWheel hourWheel; private DayWheel dayWheel; - private MinutesWheel minutesWheel; + public MinutesWheel minutesWheel; private AmPmWheel ampmWheel; private Date minDate; private Date maxDate; + public int minuteInterval = 1; + public Locale locale; public PickerView() { super(DatePickerManager.context); View rootView = inflate(getContext(), R.layout.datepicker_view, this); - wheelsWrapper = (RelativeLayout) rootView.findViewById(R.id.wheelsWrapper); + RelativeLayout wheelsWrapper = (RelativeLayout) rootView.findViewById(R.id.wheelsWrapper); wheelsWrapper.setWillNotDraw(false); - Locale locale = android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP ? Locale.forLanguageTag("en") : Locale.getDefault(); + locale = android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP ? Locale.forLanguageTag("en") : Locale.getDefault(); NumberPickerView dayPicker = (NumberPickerView) rootView.findViewById(R.id.day); - dayWheel = new DayWheel(dayPicker, onWheelChangeListener, locale); + dayWheel = new DayWheel(dayPicker, this); NumberPickerView minutePicker = (NumberPickerView) rootView.findViewById(R.id.minutes); - minutesWheel = new MinutesWheel(minutePicker, onWheelChangeListener, locale); + minutesWheel = new MinutesWheel(minutePicker, this); ampmPicker = (NumberPickerView) rootView.findViewById(R.id.ampm); - ampmWheel = new AmPmWheel(ampmPicker, onWheelChangeListener, locale); + ampmWheel = new AmPmWheel(ampmPicker, this); hourPicker = (NumberPickerView) rootView.findViewById(R.id.hour); - hourWheel = new HourWheel(hourPicker, onWheelChangeListener, locale); + hourWheel = new HourWheel(hourPicker,this); dateFormat = new SimpleDateFormat(getDateFormatTemplate(), Locale.US); changeAmPmWhenPassingMidnightOrNoon(); - } WheelChangeListener onWheelChangeListener = new WheelChangeListener(){ @@ -68,8 +77,8 @@ public class PickerView extends RelativeLayout { WritableMap event = Arguments.createMap(); try { Date date = dateFormat.parse(getDateString()); - if (date.before(minDate)) wheel.animateToDate(minDate); - if (date.after(maxDate)) wheel.animateToDate(maxDate); + if (date.before(minDate)) applyOnVisibleWheels(new AnimateToDate(minDate)); + if (date.after(maxDate)) applyOnVisibleWheels(new AnimateToDate(maxDate)); else { event.putDouble("date", date.getTime()); DatePickerManager.context.getJSModule(RCTEventEmitter.class).receiveEvent(getId(), "dateChange", event); @@ -80,12 +89,72 @@ public class PickerView extends RelativeLayout { } }; + private final Runnable measureAndLayout = new Runnable() { + @Override + public void run() { + measure( + MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.EXACTLY)); + layout(getLeft(), getTop(), getRight(), getBottom()); + } + }; + + private void changeAmPmWhenPassingMidnightOrNoon(){ + hourPicker.setOnValueChangeListenerInScrolling(new NumberPickerView.OnValueChangeListenerInScrolling() { + @Override + public void onValueChangeInScrolling(NumberPickerView picker, int oldVal, int newVal) { + if(Utils.usesAmPm(locale)){ + String oldValue = hourWheel.getValueAtIndex(oldVal); + String newValue = hourWheel.getValueAtIndex(newVal); + boolean passingNoonOrMidnight = (oldValue.equals("12") && newValue.equals("11")) || oldValue.equals("11") && newValue.equals("12"); + if (passingNoonOrMidnight) ampmPicker.smoothScrollToValue((ampmPicker.getValue() + 1) % 2,false); + } + } + }); + } + + public void setMinimumDate(Date date) { + minDate = DateUtils.truncate(date, Calendar.MINUTE); + } + + public void setMaximumDate(Date date) { + maxDate = DateUtils.truncate(date, Calendar.MINUTE); + } + + public void setDate(Date date) { + applyOnVisibleWheels(new SetDate(date)); + } + + public void setLocale(Locale locale) { + this.locale = locale; + dateFormat = new SimpleDateFormat(getDateFormatTemplate(), Locale.US); + applyOnAllWheels(new Refresh()); + } + + public void setMinuteInterval(int interval) { + this.minuteInterval = interval; + applyOnVisibleWheels(new Refresh()); + } + + // Rounding cal to closest minute interval + public Calendar getInitialDate() { + Calendar cal = Calendar.getInstance(); + if(minuteInterval <= 1) return cal; + int exactMinute = Integer.valueOf(minutesWheel.format.format(cal.getTime())); + int diffSinceLastInterval = exactMinute % minuteInterval; + int diffAhead = minuteInterval - diffSinceLastInterval; + int diffBehind= -diffSinceLastInterval; + boolean closerToPrevious = minuteInterval / 2 > diffSinceLastInterval; + int diffToExactValue = closerToPrevious ? diffBehind : diffAhead; + cal.add(Calendar.MINUTE, diffToExactValue); + return (Calendar) cal.clone(); + } private String getDateFormatTemplate() { return dayWheel.getFormatTemplate() + " " + hourWheel.getFormatTemplate() + " " + minutesWheel.getFormatTemplate() - + ampmWheel.getFormatTemplate(); + + ampmWheel.getFormatTemplate(); } private String getDateString() { @@ -95,58 +164,36 @@ public class PickerView extends RelativeLayout { + ampmWheel.getValue(); } - public void setMinimumDate(Date date) { - minDate = DateUtils.truncate(date, Calendar.MINUTE); + public Collection getVisibleWheels() { + Collection visibleWheels = new ArrayList<>(); + for (Wheel wheel: getAllWheels()) { + if (wheel.visible()) { + visibleWheels.add(wheel); + } + } + return visibleWheels; } - public void setMaximumDate(Date date) { - maxDate = DateUtils.truncate(date, Calendar.MINUTE); + public List getAllWheels(){ + return new ArrayList<>(Arrays.asList(dayWheel, hourWheel, minutesWheel, ampmWheel)); } - public void setDate(Date date) { - dayWheel.setValue(date); - hourWheel.setValue(date); - minutesWheel.setValue(date); - ampmWheel.setValue(date); + public void applyOnAllWheels(WheelFunction function) { + for (Wheel wheel: getAllWheels()) function.apply(wheel); } - public void setLocale(Locale locale) { - dayWheel.setLocale(locale); - hourWheel.setLocale(locale); - minutesWheel.setLocale(locale); - ampmWheel.setLocale(locale); - - dateFormat = new SimpleDateFormat(getDateFormatTemplate(), Locale.US); + public void applyOnVisibleWheels(WheelFunction function) { + for (Wheel wheel: getVisibleWheels()) function.apply(wheel); } - private final Runnable measureAndLayout = new Runnable() { - @Override - public void run() { - measure( - MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.EXACTLY)); - layout(getLeft(), getTop(), getRight(), getBottom()); - } - }; - @Override public void requestLayout() { super.requestLayout(); post(measureAndLayout); } - private void changeAmPmWhenPassingMidnightOrNoon(){ - hourPicker.setOnValueChangeListenerInScrolling(new NumberPickerView.OnValueChangeListenerInScrolling() { - @Override - public void onValueChangeInScrolling(NumberPickerView picker, int oldVal, int newVal) { - if(Utils.usesAmPm(hourWheel.getLocale())){ - String oldValue = hourWheel.getValueAtIndex(oldVal); - String newValue = hourWheel.getValueAtIndex(newVal); - boolean passingNoonOrMidnight = (oldValue.equals("12") && newValue.equals("11")) || oldValue.equals("11") && newValue.equals("12"); - if (passingNoonOrMidnight) ampmPicker.smoothScrollToValue((ampmPicker.getValue() + 1) % 2); - } - } - }); - + public WheelChangeListener getListener() { + return onWheelChangeListener; } + } diff --git a/android/src/main/java/com/henninghall/date_picker/wheelFunctions/AnimateToDate.java b/android/src/main/java/com/henninghall/date_picker/wheelFunctions/AnimateToDate.java new file mode 100644 index 0000000..e3741df --- /dev/null +++ b/android/src/main/java/com/henninghall/date_picker/wheelFunctions/AnimateToDate.java @@ -0,0 +1,21 @@ +package com.henninghall.date_picker.wheelFunctions; + +import com.henninghall.date_picker.wheels.Wheel; + +import java.util.Date; + +public class AnimateToDate implements WheelFunction { + + private Date date; + + public AnimateToDate(Date date) { + this.date = date; + } + + @Override + public void apply(Wheel wheel) { + wheel.animateToDate(date); + } +} + + diff --git a/android/src/main/java/com/henninghall/date_picker/wheelFunctions/Refresh.java b/android/src/main/java/com/henninghall/date_picker/wheelFunctions/Refresh.java new file mode 100644 index 0000000..69eae35 --- /dev/null +++ b/android/src/main/java/com/henninghall/date_picker/wheelFunctions/Refresh.java @@ -0,0 +1,13 @@ +package com.henninghall.date_picker.wheelFunctions; + +import com.henninghall.date_picker.wheels.Wheel; + +public class Refresh implements WheelFunction { + + @Override + public void apply(Wheel wheel) { + wheel.refresh(true); + } +} + + diff --git a/android/src/main/java/com/henninghall/date_picker/wheelFunctions/SetDate.java b/android/src/main/java/com/henninghall/date_picker/wheelFunctions/SetDate.java new file mode 100644 index 0000000..f588acc --- /dev/null +++ b/android/src/main/java/com/henninghall/date_picker/wheelFunctions/SetDate.java @@ -0,0 +1,21 @@ +package com.henninghall.date_picker.wheelFunctions; + +import com.henninghall.date_picker.wheels.Wheel; + +import java.util.Date; + +public class SetDate implements WheelFunction { + + private Date date; + + public SetDate(Date date) { + this.date = date; + } + + @Override + public void apply(Wheel wheel) { + wheel.setValue(date); + } +} + + diff --git a/android/src/main/java/com/henninghall/date_picker/wheelFunctions/WheelFunction.java b/android/src/main/java/com/henninghall/date_picker/wheelFunctions/WheelFunction.java new file mode 100644 index 0000000..560c9e3 --- /dev/null +++ b/android/src/main/java/com/henninghall/date_picker/wheelFunctions/WheelFunction.java @@ -0,0 +1,10 @@ +package com.henninghall.date_picker.wheelFunctions; + +import com.henninghall.date_picker.wheels.Wheel; + +public interface WheelFunction { + + void apply(Wheel wheel); +} + + diff --git a/android/src/main/java/com/henninghall/date_picker/wheels/AmPmWheel.java b/android/src/main/java/com/henninghall/date_picker/wheels/AmPmWheel.java index d9e9b49..65ab41a 100644 --- a/android/src/main/java/com/henninghall/date_picker/wheels/AmPmWheel.java +++ b/android/src/main/java/com/henninghall/date_picker/wheels/AmPmWheel.java @@ -1,28 +1,21 @@ package com.henninghall.date_picker.wheels; +import com.henninghall.date_picker.PickerView; import com.henninghall.date_picker.Utils; -import com.henninghall.date_picker.WheelChangeListener; - -import java.text.Format; -import java.text.SimpleDateFormat; -import java.util.ArrayList; import java.util.Calendar; -import java.util.Date; -import java.util.Locale; - import cn.carbswang.android.numberpickerview.library.NumberPickerView; public class AmPmWheel extends Wheel { - public AmPmWheel(NumberPickerView p, WheelChangeListener listener, Locale locale){ - super(p, listener, locale); + public AmPmWheel(NumberPickerView ampmPicker, PickerView pickerView) { + super(ampmPicker, pickerView); } @Override void init() { - Calendar cal = Calendar.getInstance(); + Calendar cal = pickerView.getInitialDate(); cal.set(Calendar.HOUR_OF_DAY, 0); displayValues.add(displayFormat.format(cal.getTime())); @@ -39,14 +32,13 @@ public class AmPmWheel extends Wheel { } @Override - boolean visible() { - return Utils.usesAmPm(locale); + public boolean visible() { + return Utils.usesAmPm(pickerView.locale); } @Override public String getFormatTemplate() { - return Utils.usesAmPm(locale) ? " a " : ""; + return Utils.usesAmPm(pickerView.locale) ? " a " : ""; } - } diff --git a/android/src/main/java/com/henninghall/date_picker/wheels/DayWheel.java b/android/src/main/java/com/henninghall/date_picker/wheels/DayWheel.java index 13e272d..bd26149 100644 --- a/android/src/main/java/com/henninghall/date_picker/wheels/DayWheel.java +++ b/android/src/main/java/com/henninghall/date_picker/wheels/DayWheel.java @@ -1,5 +1,6 @@ package com.henninghall.date_picker.wheels; +import com.henninghall.date_picker.PickerView; import com.henninghall.date_picker.Utils; import com.henninghall.date_picker.WheelChangeListener; @@ -11,23 +12,25 @@ import cn.carbswang.android.numberpickerview.library.NumberPickerView; public class DayWheel extends Wheel { - public DayWheel(NumberPickerView p, WheelChangeListener listener, Locale locale){ - super(p, listener, locale); + public DayWheel(NumberPickerView dayPicker, PickerView pickerView) { + super(dayPicker, pickerView); } @Override void init() { + int min = 0; int max = 10000; // bug - Calendar cal = Calendar.getInstance(); + Calendar cal = pickerView.getInitialDate(); cal.add(Calendar.DAY_OF_MONTH, -max/2); + for(int i=0; i<=(max-min); i++){ values.add(format.format(cal.getTime())); // Print "today" if date is today if(i == max/2){ - String todayString = Utils.printToday(locale); + String todayString = Utils.printToday(pickerView.locale); String todayStringCapitalized = todayString .substring(0, 1).toUpperCase() + todayString.substring(1); displayValues.add(todayStringCapitalized); } @@ -43,7 +46,7 @@ public class DayWheel extends Wheel { } @Override - boolean visible() { + public boolean visible() { return true; } diff --git a/android/src/main/java/com/henninghall/date_picker/wheels/HourWheel.java b/android/src/main/java/com/henninghall/date_picker/wheels/HourWheel.java index 6522048..08abe51 100644 --- a/android/src/main/java/com/henninghall/date_picker/wheels/HourWheel.java +++ b/android/src/main/java/com/henninghall/date_picker/wheels/HourWheel.java @@ -1,23 +1,23 @@ package com.henninghall.date_picker.wheels; +import com.henninghall.date_picker.PickerView; import com.henninghall.date_picker.Utils; -import com.henninghall.date_picker.WheelChangeListener; import java.util.Calendar; -import java.util.Locale; import cn.carbswang.android.numberpickerview.library.NumberPickerView; public class HourWheel extends Wheel { - public HourWheel(NumberPickerView p, WheelChangeListener listener, Locale locale){ - super(p, listener, locale); + public HourWheel(NumberPickerView hourPicker, PickerView pickerView) { + super(hourPicker, pickerView); } @Override void init() { - int numberOfHours = Utils.usesAmPm(locale) ? 12 : 24; - Calendar cal = Calendar.getInstance(); + int numberOfHours = Utils.usesAmPm(pickerView.locale) ? 12 : 24; + Calendar cal = pickerView.getInitialDate(); + for(int i=0; i values; ArrayList displayValues; NumberPickerView picker; - Locale locale; - SimpleDateFormat format; + public SimpleDateFormat format; SimpleDateFormat displayFormat; - Wheel(NumberPickerView picker, final WheelChangeListener listener, Locale locale){ + public Wheel(NumberPickerView picker, final PickerView pickerView) { this.self = this; - this.locale = locale; + this.pickerView = pickerView; this.picker = picker; refresh(false); picker.setOnValueChangedListener(new NumberPickerView.OnValueChangeListener() { @Override public void onValueChange(NumberPickerView picker, int oldVal, int newVal) { - prevValue = oldVal; - listener.onChange(self); + pickerView.getListener().onChange(self); } }); } + public int getIndexOfDate(Date date){ + return values.indexOf(format.format(date)); + } + public void animateToDate(Date date) { - int index = values.indexOf(format.format(date)); - picker.smoothScrollToValue(index ); + picker.smoothScrollToValue(getIndexOfDate(date)); } public String getValue() { @@ -65,7 +58,7 @@ public abstract class Wheel { } public void setValue(Date date) { - int index = values.indexOf(format.format(date)); + int index = getIndexOfDate(date); if(index > -1) { // Set value directly during initializing @@ -75,14 +68,8 @@ public abstract class Wheel { } } - public void setLocale(Locale locale) { - this.locale = locale; - refresh(true); - } - - - private void refresh(boolean keepOldValue) { - this.displayFormat = new SimpleDateFormat(getFormatTemplate(), locale); + public void refresh(boolean keepOldValue) { + this.displayFormat = new SimpleDateFormat(getFormatTemplate(), pickerView.locale); this.format = new SimpleDateFormat(getFormatTemplate(), LocaleUtils.toLocale("en_US")); this.values = new ArrayList<>(); this.displayValues= new ArrayList<>(); @@ -105,7 +92,4 @@ public abstract class Wheel { picker.setVisibility(View.VISIBLE); } - public Locale getLocale() { - return locale; - } } diff --git a/example/App.js b/example/App.js index 43f38de..892e5bf 100644 --- a/example/App.js +++ b/example/App.js @@ -25,8 +25,7 @@ export default class App extends Component { setDate = (newDate) => this.setState({ chosenDate: newDate }) render() { - const result = locales.filter(createFilter(this.state.searchTerm)).slice(0, 5); - console.log({ result }); + const result = locales.filter(createFilter(this.state.searchTerm)).slice(0, 5); return ( @@ -34,6 +33,7 @@ export default class App extends Component { date={this.state.chosenDate} onDateChange={this.setDate} locale={this.state.locale} + minuteInterval={1} minimumDate={new Date()} maximumDate={(new Date()).addHours(24*5)} />