Skip to content

Commit d589750

Browse files
add option to show year selection view first.
1 parent 53033d2 commit d589750

File tree

11 files changed

+103
-1
lines changed

11 files changed

+103
-1
lines changed

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

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
package com.reactcommunity.rndatetimepicker;
22

33
import android.app.AlertDialog;
4+
import android.app.DatePickerDialog;
45
import android.content.Context;
56
import android.content.DialogInterface;
67
import android.content.res.Resources;
78
import android.graphics.Color;
89
import android.os.Bundle;
910
import android.util.TypedValue;
11+
import android.view.View;
1012
import android.widget.Button;
13+
import android.widget.DatePicker;
1114

1215
import androidx.annotation.ColorInt;
1316
import androidx.annotation.ColorRes;
@@ -92,6 +95,30 @@ public static DialogInterface.OnShowListener setButtonTextColor(@NonNull final C
9295
};
9396
}
9497

98+
@NonNull
99+
public static DialogInterface.OnShowListener openYearDialog(final AlertDialog dialog, final boolean canOpenYearDialog, final boolean showYearPickerFirst) {
100+
return dialogInterface -> {
101+
if (canOpenYearDialog && showYearPickerFirst && dialog instanceof DatePickerDialog datePickerDialog) {
102+
DatePicker datePicker = datePickerDialog.getDatePicker();
103+
104+
int yearId = Resources.getSystem().getIdentifier("date_picker_header_year", "id", "android");
105+
View yearView = datePicker.findViewById(yearId);
106+
if (yearView != null) {
107+
yearView.performClick();
108+
}
109+
}
110+
};
111+
}
112+
113+
@NonNull
114+
public static DialogInterface.OnShowListener combine(@NonNull DialogInterface.OnShowListener... listeners) {
115+
return dialogInterface -> {
116+
for (DialogInterface.OnShowListener l : listeners) {
117+
if (l != null) l.onShow(dialogInterface);
118+
}
119+
};
120+
}
121+
95122
private static void setTextColor(Button button, String buttonKey, final Bundle args, final boolean needsColorOverride, int textColorPrimary) {
96123
if (button == null) return;
97124

@@ -245,6 +272,9 @@ public static Bundle createDatePickerArguments(ReadableMap options) {
245272
// Android DatePicker uses 1-indexed values, SUNDAY being 1 and SATURDAY being 7, so the +1 is necessary in this case
246273
args.putInt(RNConstants.FIRST_DAY_OF_WEEK, options.getInt(RNConstants.FIRST_DAY_OF_WEEK)+1);
247274
}
275+
if (options.hasKey(RNConstants.ARG_SHOW_YEAR_PICKER_FIRST) && !options.isNull(RNConstants.ARG_SHOW_YEAR_PICKER_FIRST)) {
276+
args.putBoolean(RNConstants.ARG_SHOW_YEAR_PICKER_FIRST, options.getBoolean(RNConstants.ARG_SHOW_YEAR_PICKER_FIRST));
277+
}
248278
return args;
249279
}
250280

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ public final class RNConstants {
2222
public static final String ACTION_DISMISSED = "dismissedAction";
2323
public static final String ACTION_NEUTRAL_BUTTON = "neutralButtonAction";
2424
public static final String FIRST_DAY_OF_WEEK = "firstDayOfWeek";
25+
public static final String ARG_SHOW_YEAR_PICKER_FIRST = "showYearPickerFirst";
2526

2627
/**
2728
* Minimum date supported by {@link TimePickerDialog}, 01 Jan 1900

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77

88
package com.reactcommunity.rndatetimepicker;
99

10+
import static com.reactcommunity.rndatetimepicker.Common.combine;
1011
import static com.reactcommunity.rndatetimepicker.Common.getDisplayDate;
12+
import static com.reactcommunity.rndatetimepicker.Common.openYearDialog;
1113
import static com.reactcommunity.rndatetimepicker.Common.setButtonTextColor;
1214
import static com.reactcommunity.rndatetimepicker.Common.setButtonTitles;
1315

@@ -101,7 +103,14 @@ private DatePickerDialog createDialog(Bundle args) {
101103
if (activityContext != null) {
102104
RNDatePickerDisplay display = getDisplayDate(args);
103105
boolean needsColorOverride = display == RNDatePickerDisplay.SPINNER;
104-
dialog.setOnShowListener(setButtonTextColor(activityContext, dialog, args, needsColorOverride));
106+
boolean canOpenYearDialog = display == RNDatePickerDisplay.DEFAULT;
107+
boolean showYearPickerFirst = args.getBoolean(RNConstants.ARG_SHOW_YEAR_PICKER_FIRST);
108+
dialog.setOnShowListener(
109+
combine(
110+
openYearDialog(dialog, canOpenYearDialog, showYearPickerFirst),
111+
setButtonTextColor(activityContext, dialog, args, needsColorOverride)
112+
)
113+
);
105114
}
106115
}
107116

android/src/main/java/com/reactcommunity/rndatetimepicker/RNMaterialDatePicker.kt

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ package com.reactcommunity.rndatetimepicker
33
import android.content.DialogInterface
44
import android.os.Bundle
55
import android.util.TypedValue
6+
import android.view.View
7+
import android.view.ViewGroup
8+
import android.widget.TextView
9+
import androidx.appcompat.app.AppCompatActivity
610
import androidx.fragment.app.FragmentManager
711
import com.facebook.react.bridge.Promise
812
import com.facebook.react.bridge.ReactApplicationContext
@@ -42,6 +46,8 @@ class RNMaterialDatePicker(
4246
setFullscreen()
4347

4448
datePicker = builder.build()
49+
50+
setYearPickerFirst()
4551
}
4652

4753
private fun setInitialDate() {
@@ -108,6 +114,42 @@ class RNMaterialDatePicker(
108114
}
109115
}
110116

117+
private fun setYearPickerFirst() {
118+
val showYearPickerFirst = args.getBoolean(RNConstants.ARG_SHOW_YEAR_PICKER_FIRST)
119+
if (!showYearPickerFirst) return
120+
val initialDate = RNDate(args)
121+
val activity = reactContext.currentActivity as? AppCompatActivity
122+
activity?.let { lifecycleOwner ->
123+
datePicker!!.viewLifecycleOwnerLiveData.observe(lifecycleOwner) { owner ->
124+
if (owner != null) {
125+
datePicker?.requireDialog()?.window?.decorView?.post {
126+
val root = datePicker!!.dialog?.window?.decorView ?: return@post
127+
128+
val yearText = initialDate.year().toString()
129+
val hit = findViewBy(root) { v ->
130+
v is TextView && v.isShown && v.isClickable && v.text?.toString()?.contains(yearText) == true
131+
}
132+
if (hit != null) {
133+
hit.performClick()
134+
return@post
135+
}
136+
}
137+
}
138+
}
139+
}
140+
}
141+
142+
private fun findViewBy(root: View, pred: (View) -> Boolean): View? {
143+
if (pred(root)) return root
144+
145+
if (root is ViewGroup) {
146+
for (i in 0 until root.childCount) {
147+
findViewBy(root.getChildAt(i), pred)?.let { return it }
148+
}
149+
}
150+
return null
151+
}
152+
111153
private fun obtainMaterialThemeOverlayId(resId: Int): Int {
112154
val theme = reactContext.currentActivity?.theme ?: run {
113155
return resId

src/DateTimePickerAndroid.android.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ function open(props: AndroidNativeProps) {
5151
initialInputMode,
5252
design,
5353
fullscreen,
54+
showYearPickerFirst,
5455
} = props;
5556
validateAndroidProps(props);
5657
invariant(originalValue, 'A date or time must be specified as `value` prop.');
@@ -97,6 +98,7 @@ function open(props: AndroidNativeProps) {
9798
title,
9899
initialInputMode,
99100
fullscreen,
101+
showYearPickerFirst,
100102
});
101103

102104
switch (action) {

src/androidUtils.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ type OpenParams = {
3838
title: AndroidNativeProps['title'],
3939
design: AndroidNativeProps['design'],
4040
fullscreen: AndroidNativeProps['fullscreen'],
41+
showYearPickerFirst: AndroidNativeProps['showYearPickerFirst'],
4142
};
4243

4344
export type PresentPickerCallback =
@@ -88,6 +89,7 @@ function getOpenPicker(
8889
title,
8990
initialInputMode,
9091
fullscreen,
92+
showYearPickerFirst,
9193
}: OpenParams) =>
9294
// $FlowFixMe - `AbstractComponent` [1] is not an instance type.
9395
pickers[ANDROID_MODE.date].open({
@@ -103,6 +105,7 @@ function getOpenPicker(
103105
title,
104106
initialInputMode,
105107
fullscreen,
108+
showYearPickerFirst,
106109
});
107110
}
108111
}

src/datetimepicker.android.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export default function RNDateTimePickerAndroid(
3737
initialInputMode,
3838
design,
3939
fullscreen,
40+
showYearPickerFirst,
4041
} = props;
4142
const valueTimestamp = value.getTime();
4243

@@ -72,6 +73,7 @@ export default function RNDateTimePickerAndroid(
7273
initialInputMode,
7374
design,
7475
fullscreen,
76+
showYearPickerFirst,
7577
};
7678
DateTimePickerAndroid.open(params);
7779
},

src/index.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,10 @@ export type AndroidNativeProps = Readonly<
203203
* Use Material 3 pickers or the default ones
204204
*/
205205
design?: Design;
206+
/**
207+
* Show the year picker first when opening the calendar dialog.
208+
*/
209+
showYearPickerFirst?: boolean;
206210
}
207211
>;
208212

src/specs/NativeModuleDatePicker.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export type DatePickerOpenParams = $ReadOnly<{
1111
testID?: string,
1212
timeZoneName?: number,
1313
timeZoneOffsetInMinutes?: number,
14+
showYearPickerFirst?: boolean,
1415
}>;
1516

1617
type DateSetAction = 'dateSetAction' | 'dismissedAction';

src/specs/NativeModuleMaterialDatePicker.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export type DatePickerOpenParams = $ReadOnly<{
1414
timeZoneName?: number,
1515
timeZoneOffsetInMinutes?: number,
1616
firstDayOfWeek?: number,
17+
showYearPickerFirst?: boolean,
1718
}>;
1819

1920
type DateSetAction = 'dateSetAction' | 'dismissedAction';

0 commit comments

Comments
 (0)