Browse Source

Bugfix: Scroll away from invalid dates (#280)

* initial working version

* fix native variant issue

* cleanup

* cleanup
master
Henning Hall 4 years ago
committed by GitHub
parent
commit
26f7316d60
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 172 additions and 29 deletions
  1. +80
    -18
      android/src/main/java/com/henninghall/date_picker/ui/WheelChangeListenerImpl.java
  2. +31
    -10
      android/src/main/java/com/henninghall/date_picker/ui/Wheels.java
  3. +1
    -1
      android/src/main/java/com/henninghall/date_picker/wheels/DateWheel.java
  4. +8
    -0
      android/src/main/java/com/henninghall/date_picker/wheels/Wheel.java
  5. +52
    -0
      examples/detox/e2e/tests/invalidDates.spec.js

+ 80
- 18
android/src/main/java/com/henninghall/date_picker/ui/WheelChangeListenerImpl.java View File

@ -13,7 +13,6 @@ import com.henninghall.date_picker.wheels.Wheel;
import java.text.ParseException; import java.text.ParseException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Calendar; import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone; import java.util.TimeZone;
public class WheelChangeListenerImpl implements WheelChangeListener { public class WheelChangeListenerImpl implements WheelChangeListener {
@ -30,33 +29,96 @@ public class WheelChangeListenerImpl implements WheelChangeListener {
this.rootView = rootView; this.rootView = rootView;
} }
private SimpleDateFormat getDateFormat(){
TimeZone timeZone = state.getTimeZone();
SimpleDateFormat dateFormat = uiManager.getDateFormat();
dateFormat.setTimeZone(timeZone);
return dateFormat;
}
@Override @Override
public void onChange(Wheel picker) { public void onChange(Wheel picker) {
if(wheels.hasSpinningWheel()) return; if(wheels.hasSpinningWheel()) return;
WritableMap event = Arguments.createMap();
TimeZone timeZone = state.getTimeZone();
SimpleDateFormat dateFormat = uiManager.getDateFormat();
if(!exists()){
Calendar closestExistingDate = getClosestExistingDateInPast();
if(closestExistingDate != null) {
uiManager.animateToDate(closestExistingDate);
}
return;
}
Calendar selectedDate = getSelectedDate();
if(selectedDate == null) return;
Calendar minDate = state.getMinimumDate(); Calendar minDate = state.getMinimumDate();
if (minDate != null && selectedDate.before(minDate)) {
uiManager.animateToDate(minDate);
return;
}
Calendar maxDate = state.getMaximumDate(); Calendar maxDate = state.getMaximumDate();
if (maxDate != null && selectedDate.after(maxDate)) {
uiManager.animateToDate(maxDate);
return;
}
emitDateChangeEvent(selectedDate);
}
// Example: Jan 1 returns true, April 31 returns false.
private boolean exists(){
SimpleDateFormat dateFormat = getDateFormat();
String toParse = wheels.getDateTimeString();
try { try {
dateFormat.setTimeZone(timeZone);
Calendar date = Calendar.getInstance(timeZone);
String toParse = wheels.getDateString();
Date newDate = dateFormat.parse(toParse);
date.setTime(newDate);
String dateString = Utils.dateToIso(date);
if (minDate != null && date.before(minDate)) uiManager.animateToDate(minDate);
else if (maxDate != null && date.after(maxDate)) uiManager.animateToDate(maxDate);
else {
event.putString("date", dateString);
event.putString("dateString", uiManager.getDisplayValueString());
DatePickerManager.context.getJSModule(RCTEventEmitter.class)
.receiveEvent(rootView.getId(), "dateChange", event);
}
dateFormat.setLenient(false); // disallow parsing invalid dates
dateFormat.parse(toParse);
return true;
} catch (ParseException e) {
return false;
}
}
private Calendar getSelectedDate(){
SimpleDateFormat dateFormat = getDateFormat();
String toParse = wheels.getDateTimeString();
TimeZone timeZone = state.getTimeZone();
Calendar date = Calendar.getInstance(timeZone);
try {
dateFormat.setLenient(true); // allow parsing invalid dates
date.setTime(dateFormat.parse(toParse));
return date;
} catch (ParseException e) { } catch (ParseException e) {
e.printStackTrace(); e.printStackTrace();
} }
return null;
}
private Calendar getClosestExistingDateInPast(){
SimpleDateFormat dateFormat = getDateFormat();
dateFormat.setLenient(false); // disallow parsing invalid dates
int maxDaysInPastToCheck = 10;
for (int i = 0; i < maxDaysInPastToCheck; i++){
try {
String toParse = wheels.getDateTimeString(i);
Calendar calendar = Calendar.getInstance(state.getTimeZone());
calendar.setTime(dateFormat.parse(toParse));
return calendar;
} catch (ParseException ignored) {
// continue checking if exception (which means invalid date)
}
}
return null;
}
private void emitDateChangeEvent(Calendar date) {
WritableMap event = Arguments.createMap();
String dateString = Utils.dateToIso(date);
event.putString("date", dateString);
event.putString("dateString", uiManager.getDisplayValueString());
DatePickerManager.context.getJSModule(RCTEventEmitter.class)
.receiveEvent(rootView.getId(), "dateChange", event);
} }
} }

+ 31
- 10
android/src/main/java/com/henninghall/date_picker/ui/Wheels.java View File

@ -6,7 +6,6 @@ import com.henninghall.date_picker.models.Variant;
import com.henninghall.date_picker.pickers.Picker; import com.henninghall.date_picker.pickers.Picker;
import com.henninghall.date_picker.R; import com.henninghall.date_picker.R;
import com.henninghall.date_picker.State; import com.henninghall.date_picker.State;
import com.henninghall.date_picker.Utils;
import com.henninghall.date_picker.models.WheelType; import com.henninghall.date_picker.models.WheelType;
import com.henninghall.date_picker.models.Mode; import com.henninghall.date_picker.models.Mode;
import com.henninghall.date_picker.wheelFunctions.SetDividerHeight; import com.henninghall.date_picker.wheelFunctions.SetDividerHeight;
@ -112,19 +111,41 @@ public class Wheels {
return wheelPerWheelType.get(type); return wheelPerWheelType.get(type);
} }
String getDateString() {
String getDateTimeString(int daysToSubtract) {
return getDateString(daysToSubtract) + " " + getTimeString();
}
private String getDateModeString(int daysToSubtract) {
ArrayList<Wheel> wheels = getOrderedVisibleWheels(); ArrayList<Wheel> wheels = getOrderedVisibleWheels();
String dateString = (state.getMode() == Mode.date)
? wheels.get(0).getValue() + " "
+ wheels.get(1).getValue() + " "
+ wheels.get(2).getValue()
: dayWheel.getValue();
return dateString
+ " " + hourWheel.getValue()
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 3; i++) {
if (i != 0) sb.append(" ");
Wheel w = wheels.get(i);
if (w instanceof DateWheel) {
sb.append(w.getPastValue(daysToSubtract));
}
else sb.append(w.getValue());
}
return sb.toString();
}
private String getDateString(int daysToSubtract){
if(state.getMode() == Mode.date ){
return getDateModeString(daysToSubtract);
}
return dayWheel.getValue();
}
private String getTimeString(){
return hourWheel.getValue()
+ " " + minutesWheel.getValue() + " " + minutesWheel.getValue()
+ ampmWheel.getValue(); + ampmWheel.getValue();
} }
String getDateTimeString() {
return getDateTimeString(0);
}
String getDisplayValue() { String getDisplayValue() {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
for (Wheel wheel: getOrderedVisibleWheels()) { for (Wheel wheel: getOrderedVisibleWheels()) {
@ -137,7 +158,7 @@ public class Wheels {
ArrayList<WheelType> wheels = state.derived.getOrderedVisibleWheels(); ArrayList<WheelType> wheels = state.derived.getOrderedVisibleWheels();
for (WheelType wheelType : wheels) { for (WheelType wheelType : wheels) {
Wheel wheel = getWheel(wheelType); Wheel wheel = getWheel(wheelType);
pickerWrapper.addPicker(wheel.picker.getView());
pickerWrapper.addPicker(wheel.picker.getView());
} }
} }

+ 1
- 1
android/src/main/java/com/henninghall/date_picker/wheels/DateWheel.java View File

@ -19,7 +19,7 @@ public class DateWheel extends Wheel
Calendar cal = Calendar.getInstance(); Calendar cal = Calendar.getInstance();
ArrayList<String> values = new ArrayList<>(); ArrayList<String> values = new ArrayList<>();
cal.set(Calendar.MONTH, 0); cal.set(Calendar.MONTH, 0);
cal.set(Calendar.DATE, 0);
cal.set(Calendar.DATE, 1);
final int maxDate = 31; final int maxDate = 31;
final int minDate = 1; final int minDate = 1;
for (int i = minDate; i <= maxDate; ++i) { for (int i = minDate; i <= maxDate; ++i) {

+ 8
- 0
android/src/main/java/com/henninghall/date_picker/wheels/Wheel.java View File

@ -53,6 +53,14 @@ public abstract class Wheel {
return getValueAtIndex(getIndex()); return getValueAtIndex(getIndex());
} }
public String getPastValue(int subtractIndex) {
if(!visible()) return format.format(userSetValue.getTime());
int size = values.size();
int pastValueIndex = (getIndex() + size - subtractIndex) % size;
return getValueAtIndex(pastValueIndex);
}
private int getIndex() { private int getIndex() {
return picker.getValue(); return picker.getValue();
} }

+ 52
- 0
examples/detox/e2e/tests/invalidDates.spec.js View File

@ -0,0 +1,52 @@
const { scrollWheel, expectDate, setDate, setMode, setMaximumDate, setMinimumDate } = require('../utils')
const scrollDays = (days) => scrollWheel(1, days)
describe('Invalid dates', () => {
before(async () => {
await device.reloadReactNative()
await element(by.text('Advanced')).tap()
await setMinimumDate(undefined)
await setMaximumDate(undefined)
await setMode('date')
})
it('scrolls back to last valid date', async () => {
await setDate(new Date("2001-02-28 00:00"))
await scrollDays(1)
await expectDate('2001-02-28 00:00:00')
})
it('scrolls back after scrolling multiple dates', async () => {
await setDate(new Date("2001-02-27 00:00"))
await scrollDays(2)
await expectDate('2001-02-28 00:00:00')
})
it('not scrolling back on unusual valid dates', async () => {
await setDate(new Date("2000-02-28 00:00"))
await scrollDays(1)
await expectDate('2000-02-29 00:00:00')
})
it('not scrolling back after scrolling past invalid dates', async () => {
await setDate(new Date("2001-02-28 00:00"))
await scrollDays(4)
await expectDate('2001-02-01 00:00:00')
})
it('works on months with 30 days', async () => {
await setDate(new Date("2001-04-30 00:00"))
await scrollDays(1)
await expectDate('2001-04-30 00:00:00')
})
it('works on months with 31 days', async () => {
await setDate(new Date("2001-05-30 00:00"))
await scrollDays(1)
await expectDate('2001-05-31 00:00:00')
})
})

Loading…
Cancel
Save