Skip to content

Commit 91245a1

Browse files
authored
fix(android): keep date between min and max (#699)
1 parent aefbac3 commit 91245a1

File tree

6 files changed

+114
-20
lines changed

6 files changed

+114
-20
lines changed
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package com.reactcommunity.rndatetimepicker;
2+
3+
import android.os.Bundle;
4+
import android.widget.DatePicker;
5+
6+
import androidx.annotation.NonNull;
7+
8+
import java.util.Calendar;
9+
10+
// fix for https://issuetracker.google.com/issues/169602180
11+
// TODO revisit day, month, year with timezoneoffset fixes
12+
public class KeepDateInRangeListener implements DatePicker.OnDateChangedListener {
13+
14+
private final Bundle args;
15+
16+
public KeepDateInRangeListener(@NonNull Bundle args) {
17+
this.args = args;
18+
}
19+
20+
@Override
21+
public void onDateChanged(DatePicker view, int year, int month, int day) {
22+
fixPotentialMaxDateBug(view, year, month, day);
23+
fixPotentialMinDateBug(view, year, month, day);
24+
}
25+
26+
private void fixPotentialMaxDateBug(DatePicker datePicker, int year, int month, int day) {
27+
if (!isDateAfterMaxDate(args, year, month, day)) {
28+
return;
29+
}
30+
Calendar maxDate = Calendar.getInstance();
31+
maxDate.setTimeInMillis(args.getLong(RNConstants.ARG_MAXDATE));
32+
datePicker.updateDate(maxDate.get(Calendar.YEAR), maxDate.get(Calendar.MONTH), maxDate.get(Calendar.DAY_OF_MONTH));
33+
}
34+
35+
private void fixPotentialMinDateBug(DatePicker datePicker, int year, int month, int day) {
36+
if (!isDateBeforeMinDate(args, year, month, day)) {
37+
return;
38+
}
39+
Calendar c = Calendar.getInstance();
40+
c.setTimeInMillis(args.getLong(RNConstants.ARG_MINDATE));
41+
datePicker.updateDate(c.get(Calendar.YEAR), c.get(Calendar.MONTH), c.get(Calendar.DAY_OF_MONTH));
42+
}
43+
44+
public static boolean isDateAfterMaxDate(Bundle args, int year, int month, int day) {
45+
if (!args.containsKey(RNConstants.ARG_MAXDATE)) {
46+
return false;
47+
}
48+
Calendar maxDate = Calendar.getInstance();
49+
maxDate.setTimeInMillis(args.getLong(RNConstants.ARG_MAXDATE));
50+
return (year > maxDate.get(Calendar.YEAR) ||
51+
(year == maxDate.get(Calendar.YEAR) && month > maxDate.get(Calendar.MONTH)) ||
52+
(year == maxDate.get(Calendar.YEAR) && month == maxDate.get(Calendar.MONTH) && day > maxDate.get(Calendar.DAY_OF_MONTH)));
53+
}
54+
55+
public static boolean isDateBeforeMinDate(Bundle args, int year, int month, int day) {
56+
if (!args.containsKey(RNConstants.ARG_MINDATE)) {
57+
return false;
58+
}
59+
Calendar minDate = Calendar.getInstance();
60+
minDate.setTimeInMillis(args.getLong(RNConstants.ARG_MINDATE));
61+
return (year < minDate.get(Calendar.YEAR) ||
62+
(year == minDate.get(Calendar.YEAR) && month < minDate.get(Calendar.MONTH)) ||
63+
(year == minDate.get(Calendar.YEAR) && month == minDate.get(Calendar.MONTH) && day < minDate.get(Calendar.DAY_OF_MONTH)));
64+
}
65+
}

android/src/main/java/com/reactcommunity/rndatetimepicker/RNDatePickerDialogFragment.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import android.content.DialogInterface;
2020
import android.content.DialogInterface.OnDismissListener;
2121
import android.content.DialogInterface.OnClickListener;
22+
import android.os.Build;
2223
import android.os.Bundle;
2324

2425
import androidx.annotation.NonNull;
@@ -59,7 +60,6 @@ DatePickerDialog getDialog(
5960
Bundle args,
6061
Context activityContext,
6162
@Nullable OnDateSetListener onDateSetListener) {
62-
6363
final RNDate date = new RNDate(args);
6464
final int year = date.year();
6565
final int month = date.month();
@@ -139,6 +139,11 @@ private DatePickerDialog createDialog(Bundle args) {
139139
datePicker.setMaxDate(c.getTimeInMillis() - getOffset(c, timeZoneOffsetInMilliseconds));
140140
}
141141

142+
if (args != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
143+
&& (args.containsKey(RNConstants.ARG_MAXDATE) || args.containsKey(RNConstants.ARG_MINDATE))) {
144+
datePicker.setOnDateChangedListener(new KeepDateInRangeListener(args));
145+
}
146+
142147
return dialog;
143148
}
144149

android/src/main/java/com/reactcommunity/rndatetimepicker/RNDatePickerDialogModule.java

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
import android.os.Bundle;
1515
import android.widget.DatePicker;
1616
import androidx.annotation.NonNull;
17-
import androidx.annotation.Nullable;
1817
import androidx.fragment.app.FragmentActivity;
1918
import androidx.fragment.app.FragmentManager;
2019

@@ -23,6 +22,10 @@
2322
import com.facebook.react.module.annotations.ReactModule;
2423

2524
import static com.reactcommunity.rndatetimepicker.Common.dismissDialog;
25+
import static com.reactcommunity.rndatetimepicker.KeepDateInRangeListener.isDateAfterMaxDate;
26+
import static com.reactcommunity.rndatetimepicker.KeepDateInRangeListener.isDateBeforeMinDate;
27+
28+
import java.util.Calendar;
2629

2730
/**
2831
* {@link NativeModule} that allows JS to show a native date picker dialog and get called back when
@@ -46,10 +49,12 @@ public RNDatePickerDialogModule(ReactApplicationContext reactContext) {
4649
private class DatePickerDialogListener implements OnDateSetListener, OnDismissListener, OnClickListener {
4750

4851
private final Promise mPromise;
52+
private final Bundle mArgs;
4953
private boolean mPromiseResolved = false;
5054

51-
public DatePickerDialogListener(final Promise promise) {
55+
public DatePickerDialogListener(final Promise promise, Bundle arguments) {
5256
mPromise = promise;
57+
mArgs = arguments;
5358
}
5459

5560
@Override
@@ -60,6 +65,27 @@ public void onDateSet(DatePicker view, int year, int month, int day) {
6065
result.putInt("year", year);
6166
result.putInt("month", month);
6267
result.putInt("day", day);
68+
69+
// https://issuetracker.google.com/issues/169602180
70+
// TODO revisit day, month, year with timezoneoffset fixes
71+
if (isDateAfterMaxDate(mArgs, year, month, day)) {
72+
Calendar maxDate = Calendar.getInstance();
73+
maxDate.setTimeInMillis(mArgs.getLong(RNConstants.ARG_MAXDATE));
74+
75+
result.putInt("year", maxDate.get(Calendar.YEAR));
76+
result.putInt("month", maxDate.get(Calendar.MONTH) );
77+
result.putInt("day", maxDate.get(Calendar.DAY_OF_MONTH));
78+
}
79+
80+
if (isDateBeforeMinDate(mArgs, year, month, day)) {
81+
Calendar minDate = Calendar.getInstance();
82+
minDate.setTimeInMillis(mArgs.getLong(RNConstants.ARG_MINDATE));
83+
84+
result.putInt("year", minDate.get(Calendar.YEAR));
85+
result.putInt("month", minDate.get(Calendar.MONTH) );
86+
result.putInt("day", minDate.get(Calendar.DAY_OF_MONTH));
87+
}
88+
6389
mPromise.resolve(result);
6490
mPromiseResolved = true;
6591
}
@@ -117,7 +143,7 @@ public void dismiss(Promise promise) {
117143
* dismiss, year, month and date are undefined.
118144
*/
119145
@ReactMethod
120-
public void open(@Nullable final ReadableMap options, final Promise promise) {
146+
public void open(final ReadableMap options, final Promise promise) {
121147
FragmentActivity activity = (FragmentActivity) getCurrentActivity();
122148
if (activity == null) {
123149
promise.reject(
@@ -134,18 +160,16 @@ public void run() {
134160
RNDatePickerDialogFragment oldFragment =
135161
(RNDatePickerDialogFragment) fragmentManager.findFragmentByTag(FRAGMENT_TAG);
136162

137-
if (oldFragment != null && options != null) {
163+
if (oldFragment != null) {
138164
oldFragment.update(createFragmentArguments(options));
139165
return;
140166
}
141167

142168
RNDatePickerDialogFragment fragment = new RNDatePickerDialogFragment();
143169

144-
if (options != null) {
145-
fragment.setArguments(createFragmentArguments(options));
146-
}
170+
fragment.setArguments(createFragmentArguments(options));
147171

148-
final DatePickerDialogListener listener = new DatePickerDialogListener(promise);
172+
final DatePickerDialogListener listener = new DatePickerDialogListener(promise, createFragmentArguments(options));
149173
fragment.setOnDismissListener(listener);
150174
fragment.setOnDateSetListener(listener);
151175
fragment.setOnNeutralButtonActionListener(listener);

android/src/main/java/com/reactcommunity/rndatetimepicker/RNTimePickerDialogModule.java

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
import android.widget.TimePicker;
2121

2222
import androidx.annotation.NonNull;
23-
import androidx.annotation.Nullable;
2423
import androidx.fragment.app.FragmentActivity;
2524
import androidx.fragment.app.FragmentManager;
2625

@@ -94,8 +93,7 @@ public void dismiss(Promise promise) {
9493
}
9594

9695
@ReactMethod
97-
public void open(@Nullable final ReadableMap options, final Promise promise) {
98-
96+
public void open(final ReadableMap options, final Promise promise) {
9997
FragmentActivity activity = (FragmentActivity) getCurrentActivity();
10098
if (activity == null) {
10199
promise.reject(
@@ -113,16 +111,14 @@ public void run() {
113111
RNTimePickerDialogFragment oldFragment =
114112
(RNTimePickerDialogFragment) fragmentManager.findFragmentByTag(FRAGMENT_TAG);
115113

116-
if (oldFragment != null && options != null) {
114+
if (oldFragment != null) {
117115
oldFragment.update(createFragmentArguments(options));
118116
return;
119117
}
120118

121119
RNTimePickerDialogFragment fragment = new RNTimePickerDialogFragment();
122120

123-
if (options != null) {
124-
fragment.setArguments(createFragmentArguments(options));
125-
}
121+
fragment.setArguments(createFragmentArguments(options));
126122

127123
final TimePickerDialogListener listener = new TimePickerDialogListener(promise);
128124
fragment.setOnDismissListener(listener);

example/app.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
{
2+
"$schema": "https://raw.githubusercontent.com/microsoft/react-native-test-app/trunk/schema.json",
23
"name": "date-time-picker-example",
34
"displayName": "date-time-picker-example",
45
"singleApp": "date-time-picker-example",
6+
"android": {
7+
"package": "com.datetimepickerexample"
8+
},
59
"components": [
610
{
711
"appKey": "date-time-picker-example",

example/ios/Podfile.lock

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -681,11 +681,11 @@ PODS:
681681
- React-jsi (= 0.70.0)
682682
- React-logger (= 0.70.0)
683683
- React-perflogger (= 0.70.0)
684-
- ReactTestApp-DevSupport (1.6.13):
684+
- ReactTestApp-DevSupport (1.6.19):
685685
- React-Core
686686
- React-jsi
687687
- ReactTestApp-Resources (1.0.0-dev)
688-
- RNDateTimePicker (6.4.2):
688+
- RNDateTimePicker (6.7.0):
689689
- RCT-Folly
690690
- RCTRequired
691691
- RCTTypeSafety
@@ -921,9 +921,9 @@ SPEC CHECKSUMS:
921921
React-rncore: 8858fe6b719170c20c197a8fd2dd53507bdae04b
922922
React-runtimeexecutor: 80c195ffcafb190f531fdc849dc2d19cb4bb2b34
923923
ReactCommon: de55f940495d7bf87b5d7bf55b5b15cdd50d7d7b
924-
ReactTestApp-DevSupport: 8a8cff38c37cd8145a12ac7d7d0503dd08f97d65
924+
ReactTestApp-DevSupport: dcff56cd656b2b47ac14e5908c03ce5d00a70004
925925
ReactTestApp-Resources: ff5f151e465e890010b417ce65ca6c5de6aeccbb
926-
RNDateTimePicker: 0913d8322a3b21bb3d2010aca96c5090248223b6
926+
RNDateTimePicker: 515431cd1a50a1fab054de40c7fc6a2cdd4803ab
927927
RNLocalize: cbcb55d0e19c78086ea4eea20e03fe8000bbbced
928928
SocketRocket: fccef3f9c5cedea1353a9ef6ada904fde10d6608
929929
Yoga: 82c9e8f652789f67d98bed5aef9d6653f71b04a9

0 commit comments

Comments
 (0)