diff --git a/README.md b/README.md
index d4017ff..b659d61 100644
--- a/README.md
+++ b/README.md
@@ -154,6 +154,7 @@ export default () => {
| `confirmText` | Modal only: Confirm button text. |
| `cancelText` | Modal only: Cancel button text. |
| `theme` | Modal only: The theme of the modal. `"light"`, `"dark"`, `"auto"`. Defaults to `"auto"`. |
+| `onStateChange` | Spinner state change handler. Triggered on changes between "idle" and "spinning" state (Android inline only) |
## Additional android styling
@@ -226,6 +227,17 @@ If you have enabled
+
+```
+
## Two different Android variants
On Android there are two design variants to choose from:
diff --git a/android/src/main/java/com/henninghall/date_picker/Emitter.java b/android/src/main/java/com/henninghall/date_picker/Emitter.java
index 845f200..c78fff5 100644
--- a/android/src/main/java/com/henninghall/date_picker/Emitter.java
+++ b/android/src/main/java/com/henninghall/date_picker/Emitter.java
@@ -6,6 +6,7 @@ import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.react.uimanager.events.RCTEventEmitter;
+import com.henninghall.date_picker.ui.SpinnerState;
import java.util.Calendar;
@@ -19,6 +20,18 @@ public class Emitter {
return DatePickerPackage.context.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class);
}
+ public static void onSpinnerStateChange(SpinnerState spinnerState, String id, View view) {
+ WritableMap event = Arguments.createMap();
+ event.putString("spinnerState", spinnerState.toString());
+ event.putString("id", id);
+ if(BuildConfig.IS_NEW_ARCHITECTURE_ENABLED){
+ deviceEventEmitter().emit("spinnerStateChange", event);
+ }
+ else {
+ eventEmitter().receiveEvent(view.getId(),"spinnerStateChange",event);
+ }
+ }
+
public static void onDateChange(Calendar date, String displayValueString, String id, View view) {
WritableMap event = Arguments.createMap();
String dateString = Utils.dateToIso(date);
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 8af607a..50ea154 100644
--- a/android/src/main/java/com/henninghall/date_picker/PickerView.java
+++ b/android/src/main/java/com/henninghall/date_picker/PickerView.java
@@ -6,6 +6,7 @@ import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import com.facebook.react.bridge.Dynamic;
+import com.henninghall.date_picker.models.Variant;
import com.henninghall.date_picker.props.DividerHeightProp;
import com.henninghall.date_picker.props.Is24hourSourceProp;
import com.henninghall.date_picker.props.MaximumDateProp;
@@ -19,6 +20,7 @@ import com.henninghall.date_picker.props.HeightProp;
import com.henninghall.date_picker.props.LocaleProp;
import com.henninghall.date_picker.props.ModeProp;
import com.henninghall.date_picker.props.TextColorProp;
+import com.henninghall.date_picker.ui.SpinnerStateListener;
import com.henninghall.date_picker.ui.UIManager;
import com.henninghall.date_picker.ui.Accessibility;
@@ -107,6 +109,10 @@ public class PickerView extends RelativeLayout {
uiManager.scroll(wheelIndex, scrollTimes);
}
+ public void addSpinnerStateListener(SpinnerStateListener listener){
+ uiManager.addStateListener(listener);
+ }
+
public String getDate() {
return state.derived.getLastDate();
}
@@ -132,4 +138,7 @@ public class PickerView extends RelativeLayout {
}
+ public Variant getVariant() {
+ return state.getVariant();
+ }
}
diff --git a/android/src/main/java/com/henninghall/date_picker/pickers/AndroidNative.java b/android/src/main/java/com/henninghall/date_picker/pickers/AndroidNative.java
index 91cc70c..a3550a3 100644
--- a/android/src/main/java/com/henninghall/date_picker/pickers/AndroidNative.java
+++ b/android/src/main/java/com/henninghall/date_picker/pickers/AndroidNative.java
@@ -17,16 +17,16 @@ import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
-import static android.widget.NumberPicker.OnScrollListener.SCROLL_STATE_FLING;
import static android.widget.NumberPicker.OnScrollListener.SCROLL_STATE_IDLE;
public class AndroidNative extends NumberPicker implements Picker {
private Picker.OnValueChangeListener onValueChangedListener;
- private int state = SCROLL_STATE_IDLE;
+ private int internalSpinState = SCROLL_STATE_IDLE;
private OnValueChangeListenerInScrolling listenerInScrolling;
private boolean isAnimating;
private final Handler handler = new Handler();
+ private boolean spinning;
public AndroidNative(Context context) {
super(context);
@@ -102,7 +102,7 @@ public class AndroidNative extends NumberPicker implements Picker {
@Override
public boolean isSpinning() {
- return state == SCROLL_STATE_FLING || isAnimating;
+ return spinning || isAnimating;
}
@Override
@@ -116,10 +116,13 @@ public class AndroidNative extends NumberPicker implements Picker {
int timeBetweenScrollsMs = 100;
int willStopScrollingInMs = timeBetweenScrollsMs * moves;
isAnimating = true;
+ onValueChangedListener.onSpinnerStateChange();
+
handler.postDelayed(new Runnable() {
@Override
public void run() {
isAnimating = false;
+ onValueChangedListener.onSpinnerStateChange();
}
}, willStopScrollingInMs);
@@ -184,7 +187,7 @@ public class AndroidNative extends NumberPicker implements Picker {
// onValueChange is triggered also during scrolling. Since we don't want
// to send event during scrolling we make sure wheel is still. This particular
// case happens when wheel is tapped, not scrolled.
- if(state == SCROLL_STATE_IDLE) {
+ if(internalSpinState == SCROLL_STATE_IDLE) {
sendEventIn500ms();
}
}
@@ -194,13 +197,17 @@ public class AndroidNative extends NumberPicker implements Picker {
@Override
public void onScrollStateChange(NumberPicker numberPicker, int nextState) {
sendEventIfStopped(nextState);
- state = nextState;
+ internalSpinState = nextState;
+ if (nextState != SCROLL_STATE_IDLE){
+ spinning = true;
+ onValueChangedListener.onSpinnerStateChange();
+ }
}
});
}
private void sendEventIfStopped(int nextState){
- boolean stoppedScrolling = state != SCROLL_STATE_IDLE && nextState == SCROLL_STATE_IDLE;
+ boolean stoppedScrolling = internalSpinState != SCROLL_STATE_IDLE && nextState == SCROLL_STATE_IDLE;
if (stoppedScrolling) {
sendEventIn500ms();
}
@@ -210,7 +217,9 @@ public class AndroidNative extends NumberPicker implements Picker {
handler.postDelayed(new Runnable() {
@Override
public void run() {
+ spinning = false;
onValueChangedListener.onValueChange();
+ onValueChangedListener.onSpinnerStateChange();
}
// the delay make sure the wheel has stopped before sending the value change event
}, 500);
diff --git a/android/src/main/java/com/henninghall/date_picker/pickers/IosClone.java b/android/src/main/java/com/henninghall/date_picker/pickers/IosClone.java
index 7601e00..aae68ae 100644
--- a/android/src/main/java/com/henninghall/date_picker/pickers/IosClone.java
+++ b/android/src/main/java/com/henninghall/date_picker/pickers/IosClone.java
@@ -16,6 +16,7 @@ import com.henninghall.date_picker.ui.Accessibility;
public class IosClone extends NumberPickerView implements Picker {
private Picker.OnValueChangeListenerInScrolling mOnValueChangeListenerInScrolling;
+ private Picker.OnValueChangeListener onValueChangedListener;
public IosClone(Context context) {
super(context);
@@ -54,6 +55,15 @@ public class IosClone extends NumberPickerView implements Picker {
}
}
});
+
+ super.setOnScrollListener(new OnScrollListener() {
+ @Override
+ public void onScrollStateChange(NumberPickerView view, int scrollState) {
+ if (onValueChangedListener != null) {
+ onValueChangedListener.onSpinnerStateChange();
+ }
+ }
+ });
}
@Override
@@ -71,6 +81,8 @@ public class IosClone extends NumberPickerView implements Picker {
@Override
public void setOnValueChangedListener(final Picker.OnValueChangeListener listener) {
+ this.onValueChangedListener = listener;
+
super.setOnValueChangedListener(new NumberPickerView.OnValueChangeListener() {
@Override
public void onValueChange(NumberPickerView picker, int oldVal, int newVal) {
diff --git a/android/src/main/java/com/henninghall/date_picker/pickers/Picker.java b/android/src/main/java/com/henninghall/date_picker/pickers/Picker.java
index b438dcf..26089d6 100644
--- a/android/src/main/java/com/henninghall/date_picker/pickers/Picker.java
+++ b/android/src/main/java/com/henninghall/date_picker/pickers/Picker.java
@@ -32,5 +32,6 @@ public interface Picker {
interface OnValueChangeListener {
void onValueChange();
+ void onSpinnerStateChange();
}
}
diff --git a/android/src/main/java/com/henninghall/date_picker/ui/SpinnerState.java b/android/src/main/java/com/henninghall/date_picker/ui/SpinnerState.java
new file mode 100644
index 0000000..3e44d79
--- /dev/null
+++ b/android/src/main/java/com/henninghall/date_picker/ui/SpinnerState.java
@@ -0,0 +1,6 @@
+package com.henninghall.date_picker.ui;
+
+public enum SpinnerState {
+ idle,
+ spinning
+}
diff --git a/android/src/main/java/com/henninghall/date_picker/ui/SpinnerStateListener.java b/android/src/main/java/com/henninghall/date_picker/ui/SpinnerStateListener.java
new file mode 100644
index 0000000..46e323d
--- /dev/null
+++ b/android/src/main/java/com/henninghall/date_picker/ui/SpinnerStateListener.java
@@ -0,0 +1,5 @@
+package com.henninghall.date_picker.ui;
+
+public interface SpinnerStateListener {
+ void onChange(SpinnerState event);
+}
diff --git a/android/src/main/java/com/henninghall/date_picker/ui/UIManager.java b/android/src/main/java/com/henninghall/date_picker/ui/UIManager.java
index 01a6ea5..930594f 100644
--- a/android/src/main/java/com/henninghall/date_picker/ui/UIManager.java
+++ b/android/src/main/java/com/henninghall/date_picker/ui/UIManager.java
@@ -21,6 +21,7 @@ public class UIManager {
private Wheels wheels;
private FadingOverlay fadingOverlay;
private WheelScroller wheelScroller = new WheelScroller();
+ private WheelChangeListenerImpl onWheelChangeListener;
public UIManager(State state, View rootView){
this.state = state;
@@ -78,10 +79,14 @@ public class UIManager {
}
private void addOnChangeListener(){
- WheelChangeListener onWheelChangeListener = new WheelChangeListenerImpl(wheels, state, this, rootView);
+ onWheelChangeListener = new WheelChangeListenerImpl(wheels, state, this, rootView);
wheels.applyOnAll(new AddOnChangeListener(onWheelChangeListener));
}
+ public void addStateListener(SpinnerStateListener listener){
+ onWheelChangeListener.addStateListener(listener);
+ }
+
public void updateDividerHeight() {
wheels.updateDividerHeight();
}
diff --git a/android/src/main/java/com/henninghall/date_picker/ui/WheelChangeListener.java b/android/src/main/java/com/henninghall/date_picker/ui/WheelChangeListener.java
index c796347..0a6e914 100644
--- a/android/src/main/java/com/henninghall/date_picker/ui/WheelChangeListener.java
+++ b/android/src/main/java/com/henninghall/date_picker/ui/WheelChangeListener.java
@@ -5,6 +5,8 @@ import com.henninghall.date_picker.wheels.Wheel;
public interface WheelChangeListener {
void onChange(Wheel picker);
+
+ void onStateChange(Wheel picker);
}
diff --git a/android/src/main/java/com/henninghall/date_picker/ui/WheelChangeListenerImpl.java b/android/src/main/java/com/henninghall/date_picker/ui/WheelChangeListenerImpl.java
index 9b9c3ed..f6a7d72 100644
--- a/android/src/main/java/com/henninghall/date_picker/ui/WheelChangeListenerImpl.java
+++ b/android/src/main/java/com/henninghall/date_picker/ui/WheelChangeListenerImpl.java
@@ -9,6 +9,8 @@ import com.henninghall.date_picker.wheels.Wheel;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
+import java.util.HashSet;
+import java.util.Set;
import java.util.TimeZone;
public class WheelChangeListenerImpl implements WheelChangeListener {
@@ -17,6 +19,8 @@ public class WheelChangeListenerImpl implements WheelChangeListener {
private final State state;
private final UIManager uiManager;
private final View rootView;
+ private SpinnerState lastEmittedSpinnerState;
+ private Set listeners = new HashSet<>();
WheelChangeListenerImpl(Wheels wheels, State state, UIManager uiManager, View rootView) {
this.wheels = wheels;
@@ -65,6 +69,15 @@ public class WheelChangeListenerImpl implements WheelChangeListener {
Emitter.onDateChange(selectedDate, displayData, state.getId(), rootView);
}
+ @Override
+ public void onStateChange(Wheel picker) {
+ SpinnerState event = wheels.hasSpinningWheel() ? SpinnerState.spinning : SpinnerState.idle;
+ if(event.equals(lastEmittedSpinnerState)) return;
+ lastEmittedSpinnerState = event;
+ Emitter.onSpinnerStateChange(event, state.getId(), rootView);
+ for (SpinnerStateListener l: listeners) l.onChange(event);
+ }
+
// Example: Jan 1 returns true, April 31 returns false.
private boolean dateExists(){
SimpleDateFormat dateFormat = getDateFormat();
@@ -111,4 +124,7 @@ public class WheelChangeListenerImpl implements WheelChangeListener {
return null;
}
+ public void addStateListener(SpinnerStateListener listener) {
+ listeners.add(listener);
+ }
}
diff --git a/android/src/main/java/com/henninghall/date_picker/wheelFunctions/AddOnChangeListener.java b/android/src/main/java/com/henninghall/date_picker/wheelFunctions/AddOnChangeListener.java
index af9e441..7706062 100644
--- a/android/src/main/java/com/henninghall/date_picker/wheelFunctions/AddOnChangeListener.java
+++ b/android/src/main/java/com/henninghall/date_picker/wheelFunctions/AddOnChangeListener.java
@@ -21,6 +21,11 @@ public class AddOnChangeListener implements WheelFunction {
public void onValueChange() {
onChangeListener.onChange(wheel);
}
+
+ @Override
+ public void onSpinnerStateChange() {
+ onChangeListener.onStateChange(wheel);
+ }
});
}
}
diff --git a/android/src/newarch/java/com/henninghall/date_picker/DatePickerModule.java b/android/src/newarch/java/com/henninghall/date_picker/DatePickerModule.java
index 36ea214..7f243c8 100644
--- a/android/src/newarch/java/com/henninghall/date_picker/DatePickerModule.java
+++ b/android/src/newarch/java/com/henninghall/date_picker/DatePickerModule.java
@@ -1,9 +1,7 @@
package com.henninghall.date_picker;
-
import android.app.AlertDialog;
import android.content.DialogInterface;
-import android.telecom.Call;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
@@ -12,12 +10,13 @@ import androidx.annotation.NonNull;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.Dynamic;
-import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
-import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableMapKeySetIterator;
+import com.henninghall.date_picker.models.Variant;
+import com.henninghall.date_picker.ui.SpinnerState;
+import com.henninghall.date_picker.ui.SpinnerStateListener;
import net.time4j.android.ApplicationStarter;
@@ -129,9 +128,23 @@ public class DatePickerModule extends NativeRNDatePickerSpec {
}
}
picker.update();
+
+ boolean canDisableButtonsWithoutCrash = picker.getVariant() == Variant.nativeAndroid;
+ if(canDisableButtonsWithoutCrash){
+ picker.addSpinnerStateListener(new SpinnerStateListener() {
+ @Override
+ public void onChange(SpinnerState state) {
+ setEnabledConfirmButton(state == SpinnerState.idle);
+ }
+ });
+ }
return picker;
}
+ private void setEnabledConfirmButton(boolean enabled) {
+ dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(enabled);
+ }
+
private View withTopMargin(PickerView view) {
LinearLayout linearLayout = new LinearLayout(DatePickerPackage.context);
linearLayout.setLayoutParams(new LinearLayout.LayoutParams(
diff --git a/index.d.ts b/index.d.ts
index ba1de42..45c7337 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -50,6 +50,17 @@ export interface DatePickerProps extends ViewProps {
*/
onDateChange?: (date: Date) => void
+ /**
+ * Spinner state change handler.
+ *
+ * This is called when the user start to spin the picker wheel and the spinner stops.
+ * It can be used to disable a confirm button until a spinner comes to a total stop
+ * to ensure the expected date is being selected.
+ *
+ * Android only.
+ */
+ onStateChange?: (state: 'spinning' | 'idle') => void
+
/**
* Timezone offset in minutes.
*
diff --git a/src/DatePickerAndroid.js b/src/DatePickerAndroid.js
index 128f36a..8daa643 100644
--- a/src/DatePickerAndroid.js
+++ b/src/DatePickerAndroid.js
@@ -40,11 +40,16 @@ class DatePickerAndroid extends React.PureComponent {
componentDidMount = () => {
this.eventEmitter = new NativeEventEmitter(NativeModule)
this.eventEmitter.addListener('dateChange', this._onChange)
+ this.eventEmitter.addListener(
+ 'spinnerStateChange',
+ this._onSpinnerStateChanged
+ )
this.eventEmitter.addListener('onConfirm', this._onConfirm)
this.eventEmitter.addListener('onCancel', this._onCancel)
}
componentWillUnmount = () => {
+ this.eventEmitter.removeAllListeners('spinnerStateChange')
this.eventEmitter.removeAllListeners('dateChange')
this.eventEmitter.removeAllListeners('onConfirm')
this.eventEmitter.removeAllListeners('onCancel')
@@ -80,6 +85,12 @@ class DatePickerAndroid extends React.PureComponent {
this.props.onDateStringChange(dateString)
}
}
+ _onSpinnerStateChanged = (e) => {
+ const { spinnerState, id } = e.nativeEvent ?? e
+ const newArch = id !== null
+ if (newArch && id !== this.id) return
+ this.props.onStateChange && this.props.onStateChange(spinnerState)
+ }
_maximumDate = () =>
this.props.maximumDate &&