From e81c6a12e11ac2cb943529b71ba3226658ac9cac Mon Sep 17 00:00:00 2001
From: sidorchukandrew <36050911+sidorchukandrew@users.noreply.github.com>
Date: Mon, 3 Feb 2025 13:51:16 -0500
Subject: [PATCH] feat(android): material 3 pickers (#952)
* Support Kotlin files
Android Studio recommended these settings for supporting
Kotlin in the project.
* Add Material dependency
* Extract re-usable picker argument utilities
The new Material pickers will utilize these utilities as well.
* Set up new specs/modules
* Accept new arguments for Material pickers
The pickers will allow the user to set a title for the dialog
and specify if the initial input mode should be a text input or
calendar/clock.
* Implement MaterialDatePicker
This also adds a utility function to RNDate to get the date in
milliseconds.
* Implement MaterialTimePicker
* Create JS connectors that call the native modules
* Choose native module based on `design` prop
If the `design` is "material", then use the Material modules.
Otherwise, use the default modules.
* Validate Material 3 props not used with default pickers
* Update components to pass new props to modules
* Update types
* Update example app
* Fix typo in existing docs
* Add dummy files to prevent tests from failing
The tests were trying to access the native modules. These dummy
files will make sure the tests don't try accessing the real
implementations that end with `.android.js`.
* Update README
* Update E2E specs
Since there are multiple "default" options on the screen now,
I've specified to tap the first one. I also had to make sure to
scroll the screen before tapping the show picker button.
* Make example app inherit from Material3 theme
* Move post-install into `start` script
---
README.md | 52 +++++
android/build.gradle | 6 +
.../rndatetimepicker/Common.java | 61 ++++++
.../rndatetimepicker/DatePickerModule.java | 32 +--
.../MaterialDatePickerModule.kt | 44 +++++
.../MaterialTimePickerModule.kt | 49 +++++
.../rndatetimepicker/RNConstants.java | 3 +
.../rndatetimepicker/RNDate.java | 1 +
.../RNDateTimePickerPackage.java | 26 +++
.../rndatetimepicker/RNMaterialDatePicker.kt | 179 +++++++++++++++++
.../rndatetimepicker/RNMaterialInputMode.java | 6 +
.../rndatetimepicker/RNMaterialTimePicker.kt | 155 +++++++++++++++
.../rndatetimepicker/TimePickerModule.java | 24 +--
.../NativeModuleMaterialDatePickerSpec.java | 36 ++++
.../NativeModuleMaterialTimePickerSpec.java | 36 ++++
docs/images/android_material_date.jpg | Bin 0 -> 92790 bytes
docs/images/android_material_time.jpg | Bin 0 -> 94042 bytes
example/App.js | 46 +++++
example/e2e/detoxTest.spec.js | 3 +
example/e2e/utils/actions.js | 3 +-
package.json | 4 +-
patches/react-native-test-app+4.0.7.patch | 13 ++
src/DateTimePickerAndroid.android.js | 23 ++-
src/androidUtils.js | 47 ++++-
src/datetimepicker.android.js | 12 +-
src/index.d.ts | 28 +++
src/materialdatepicker.android.js | 57 ++++++
src/materialdatepicker.js | 6 +
src/materialtimepicker.android.js | 51 +++++
src/materialtimepicker.js | 6 +
src/specs/NativeModuleMaterialDatePicker.js | 33 ++++
src/specs/NativeModuleMaterialTimePicker.js | 28 +++
src/timepicker.android.js | 2 +-
src/types.js | 39 +++-
yarn.lock | 186 +++++++++++++++++-
35 files changed, 1229 insertions(+), 68 deletions(-)
create mode 100644 android/src/main/java/com/reactcommunity/rndatetimepicker/MaterialDatePickerModule.kt
create mode 100644 android/src/main/java/com/reactcommunity/rndatetimepicker/MaterialTimePickerModule.kt
create mode 100644 android/src/main/java/com/reactcommunity/rndatetimepicker/RNMaterialDatePicker.kt
create mode 100644 android/src/main/java/com/reactcommunity/rndatetimepicker/RNMaterialInputMode.java
create mode 100644 android/src/main/java/com/reactcommunity/rndatetimepicker/RNMaterialTimePicker.kt
create mode 100644 android/src/paper/java/com/reactcommunity/rndatetimepicker/NativeModuleMaterialDatePickerSpec.java
create mode 100644 android/src/paper/java/com/reactcommunity/rndatetimepicker/NativeModuleMaterialTimePickerSpec.java
create mode 100644 docs/images/android_material_date.jpg
create mode 100644 docs/images/android_material_time.jpg
create mode 100644 patches/react-native-test-app+4.0.7.patch
create mode 100644 src/materialdatepicker.android.js
create mode 100644 src/materialdatepicker.js
create mode 100644 src/materialtimepicker.android.js
create mode 100644 src/materialtimepicker.js
create mode 100644 src/specs/NativeModuleMaterialDatePicker.js
create mode 100644 src/specs/NativeModuleMaterialTimePicker.js
diff --git a/README.md b/README.md
index 19f7d7c9..9a6542cc 100644
--- a/README.md
+++ b/README.md
@@ -51,6 +51,10 @@ React Native date & time picker component for iOS, Android and Windows (please n

|

|
+
+ 
|
+ 
|
+
Windows |

|
@@ -77,6 +81,10 @@ React Native date & time picker component for iOS, Android and Windows (please n
- [Props / params](#component-props--params-of-the-android-imperative-api)
- [`mode` (`optional`)](#mode-optional)
- [`display` (`optional`)](#display-optional)
+ - [`design` (`optional`, `Android only`)](#design-optional)
+ - [`initialInputMode` (`optional`, `Android only`)](#initialinputmode-optional-android-only)
+ - [`title` (`optional`, `Android only`)](#title-optional-android-only)
+ - [`fullscreen` (`optional`, `Android only`)](#fullscreen-optional-android-only)
- [`onChange` (`optional`)](#onchange-optional)
- [`value` (`required`)](#value-required)
- [`maximumDate` (`optional`)](#maximumdate-optional)
@@ -287,6 +295,8 @@ The reason we recommend the imperative API is: on Android, the date/time picker
### Android styling
+If you'd like to use the Material pickers, your app theme will need to inherit from `Theme.Material3.DayNight.NoActionBar` in `styles.xml`.
+
Styling of the dialogs on Android can be easily customized by using the provided config plugin, provided that you use a [Expo development build](https://docs.expo.dev/develop/development-builds/introduction/). The plugin allows you to configure color properties that cannot be set at runtime and requires building a new app binary to take effect.
Refer to this documentation for more information: [android-styling.md](/docs/android-styling.md).
@@ -334,6 +344,19 @@ List of possible values for iOS (maps to [preferredDatePickerStyle](https://deve
```
+#### `design` (`optional`, `Android only`)
+
+Defines if the picker should use Material 3 components or the default picker. The default value is `"default"`.
+
+List of possible values
+
+- `"default"`
+- `"material"`
+
+```js
+
+```
+
#### `onChange` (`optional`)
Date change handler.
@@ -482,6 +505,35 @@ Allows changing of the time picker to a 24-hour format. By default, this value i
```
+#### `initialInputMode` (`optional`, `Android only`)
+
+:warning: Has effect only when `design` is "material". Allows setting the initial input mode of the picker.
+
+List of possible values:
+
+- `"default"` - Recommended. Date pickers will show the calendar view by default, and time pickers will show the clock view by default.
+- `"keyboard"` - Both pickers will show an input where the user can type the date or time.
+
+```js
+
+```
+
+#### `title` (`optional`, `Android only`)
+
+:warning: Has effect only when `design` is "material". Allows setting the title of the dialog for the pickers.
+
+```js
+
+```
+
+#### `fullscreen` (`optional`, `Android only`)
+
+:warning: Has effect only when `design` is "material". Allows setting the date picker dialog to be fullscreen.
+
+```js
+
+```
+
#### `positiveButton` (`optional`, `Android only`)
Set the positive button label and text color.
diff --git a/android/build.gradle b/android/build.gradle
index 20cc6954..af810e8e 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -18,6 +18,7 @@ def isNewArchitectureEnabled() {
}
apply plugin: 'com.android.library'
+apply plugin: 'org.jetbrains.kotlin.android'
if (isNewArchitectureEnabled()) {
apply plugin: "com.facebook.react"
}
@@ -53,6 +54,9 @@ android {
}
}
}
+ kotlinOptions {
+ jvmTarget = '17'
+ }
}
repositories {
@@ -64,4 +68,6 @@ repositories {
dependencies {
//noinspection GradleDynamicVersion
implementation 'com.facebook.react:react-native:+'
+ implementation 'com.google.android.material:material:1.12.0'
+ implementation 'androidx.core:core-ktx:1.13.1'
}
diff --git a/android/src/main/java/com/reactcommunity/rndatetimepicker/Common.java b/android/src/main/java/com/reactcommunity/rndatetimepicker/Common.java
index 0bfc4c21..ca6b2e06 100644
--- a/android/src/main/java/com/reactcommunity/rndatetimepicker/Common.java
+++ b/android/src/main/java/com/reactcommunity/rndatetimepicker/Common.java
@@ -17,6 +17,7 @@
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
+import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.util.RNLog;
@@ -205,6 +206,66 @@ public static Bundle createFragmentArguments(ReadableMap options) {
if (options.hasKey(RNConstants.ARG_TZ_NAME) && !options.isNull(RNConstants.ARG_TZ_NAME)) {
args.putString(RNConstants.ARG_TZ_NAME, options.getString(RNConstants.ARG_TZ_NAME));
}
+ if (options.hasKey(RNConstants.ARG_TITLE) && !options.isNull(RNConstants.ARG_TITLE)) {
+ args.putString(RNConstants.ARG_TITLE, options.getString(RNConstants.ARG_TITLE));
+ }
+ if (options.hasKey(RNConstants.ARG_INITIAL_INPUT_MODE) && !options.isNull(RNConstants.ARG_INITIAL_INPUT_MODE)) {
+ args.putString(RNConstants.ARG_INITIAL_INPUT_MODE, options.getString(RNConstants.ARG_INITIAL_INPUT_MODE));
+ }
+
+ return args;
+ }
+
+ public static Bundle createDatePickerArguments(ReadableMap options) {
+ final Bundle args = Common.createFragmentArguments(options);
+
+ if (options.hasKey(RNConstants.ARG_MINDATE) && !options.isNull(RNConstants.ARG_MINDATE)) {
+ args.putLong(RNConstants.ARG_MINDATE, (long) options.getDouble(RNConstants.ARG_MINDATE));
+ }
+ if (options.hasKey(RNConstants.ARG_MAXDATE) && !options.isNull(RNConstants.ARG_MAXDATE)) {
+ args.putLong(RNConstants.ARG_MAXDATE, (long) options.getDouble(RNConstants.ARG_MAXDATE));
+ }
+ if (options.hasKey(RNConstants.ARG_DISPLAY) && !options.isNull(RNConstants.ARG_DISPLAY)) {
+ args.putString(RNConstants.ARG_DISPLAY, options.getString(RNConstants.ARG_DISPLAY));
+ }
+ if (options.hasKey(RNConstants.ARG_DIALOG_BUTTONS) && !options.isNull(RNConstants.ARG_DIALOG_BUTTONS)) {
+ args.putBundle(RNConstants.ARG_DIALOG_BUTTONS, Arguments.toBundle(options.getMap(RNConstants.ARG_DIALOG_BUTTONS)));
+ }
+ if (options.hasKey(RNConstants.ARG_TZOFFSET_MINS) && !options.isNull(RNConstants.ARG_TZOFFSET_MINS)) {
+ args.putLong(RNConstants.ARG_TZOFFSET_MINS, (long) options.getDouble(RNConstants.ARG_TZOFFSET_MINS));
+ }
+ if (options.hasKey(RNConstants.ARG_TESTID) && !options.isNull(RNConstants.ARG_TESTID)) {
+ args.putString(RNConstants.ARG_TESTID, options.getString(RNConstants.ARG_TESTID));
+ }
+ if (options.hasKey(RNConstants.ARG_FULLSCREEN) && !options.isNull(RNConstants.ARG_FULLSCREEN)) {
+ args.putBoolean(RNConstants.ARG_FULLSCREEN, options.getBoolean(RNConstants.ARG_FULLSCREEN));
+ }
+ if (options.hasKey(RNConstants.FIRST_DAY_OF_WEEK) && !options.isNull(RNConstants.FIRST_DAY_OF_WEEK)) {
+ // FIRST_DAY_OF_WEEK is 0-indexed, since it uses the same constants DAY_OF_WEEK used in the Windows implementation
+ // Android DatePicker uses 1-indexed values, SUNDAY being 1 and SATURDAY being 7, so the +1 is necessary in this case
+ args.putInt(RNConstants.FIRST_DAY_OF_WEEK, options.getInt(RNConstants.FIRST_DAY_OF_WEEK)+1);
+ }
+ return args;
+ }
+
+ public static Bundle createTimePickerArguments(ReadableMap options) {
+ final Bundle args = Common.createFragmentArguments(options);
+
+ if (options.hasKey(RNConstants.ARG_IS24HOUR) && !options.isNull(RNConstants.ARG_IS24HOUR)) {
+ args.putBoolean(RNConstants.ARG_IS24HOUR, options.getBoolean(RNConstants.ARG_IS24HOUR));
+ }
+ if (options.hasKey(RNConstants.ARG_DISPLAY) && !options.isNull(RNConstants.ARG_DISPLAY)) {
+ args.putString(RNConstants.ARG_DISPLAY, options.getString(RNConstants.ARG_DISPLAY));
+ }
+ if (options.hasKey(RNConstants.ARG_DIALOG_BUTTONS) && !options.isNull(RNConstants.ARG_DIALOG_BUTTONS)) {
+ args.putBundle(RNConstants.ARG_DIALOG_BUTTONS, Arguments.toBundle(options.getMap(RNConstants.ARG_DIALOG_BUTTONS)));
+ }
+ if (options.hasKey(RNConstants.ARG_INTERVAL) && !options.isNull(RNConstants.ARG_INTERVAL)) {
+ args.putInt(RNConstants.ARG_INTERVAL, options.getInt(RNConstants.ARG_INTERVAL));
+ }
+ if (options.hasKey(RNConstants.ARG_TZOFFSET_MINS) && !options.isNull(RNConstants.ARG_TZOFFSET_MINS)) {
+ args.putLong(RNConstants.ARG_TZOFFSET_MINS, (long) options.getDouble(RNConstants.ARG_TZOFFSET_MINS));
+ }
return args;
}
diff --git a/android/src/main/java/com/reactcommunity/rndatetimepicker/DatePickerModule.java b/android/src/main/java/com/reactcommunity/rndatetimepicker/DatePickerModule.java
index 6eec370b..d043e4f9 100644
--- a/android/src/main/java/com/reactcommunity/rndatetimepicker/DatePickerModule.java
+++ b/android/src/main/java/com/reactcommunity/rndatetimepicker/DatePickerModule.java
@@ -21,6 +21,7 @@
import com.facebook.react.common.annotations.VisibleForTesting;
import com.facebook.react.module.annotations.ReactModule;
+import static com.reactcommunity.rndatetimepicker.Common.createDatePickerArguments;
import static com.reactcommunity.rndatetimepicker.Common.dismissDialog;
import java.util.Calendar;
@@ -144,7 +145,7 @@ public void open(final ReadableMap options, final Promise promise) {
RNDatePickerDialogFragment oldFragment =
(RNDatePickerDialogFragment) fragmentManager.findFragmentByTag(NAME);
- Bundle arguments = createFragmentArguments(options);
+ Bundle arguments = createDatePickerArguments(options);
if (oldFragment != null) {
oldFragment.update(arguments);
@@ -162,33 +163,4 @@ public void open(final ReadableMap options, final Promise promise) {
fragment.show(fragmentManager, NAME);
});
}
-
- private Bundle createFragmentArguments(ReadableMap options) {
- final Bundle args = Common.createFragmentArguments(options);
-
- if (options.hasKey(RNConstants.ARG_MINDATE) && !options.isNull(RNConstants.ARG_MINDATE)) {
- args.putLong(RNConstants.ARG_MINDATE, (long) options.getDouble(RNConstants.ARG_MINDATE));
- }
- if (options.hasKey(RNConstants.ARG_MAXDATE) && !options.isNull(RNConstants.ARG_MAXDATE)) {
- args.putLong(RNConstants.ARG_MAXDATE, (long) options.getDouble(RNConstants.ARG_MAXDATE));
- }
- if (options.hasKey(RNConstants.ARG_DISPLAY) && !options.isNull(RNConstants.ARG_DISPLAY)) {
- args.putString(RNConstants.ARG_DISPLAY, options.getString(RNConstants.ARG_DISPLAY));
- }
- if (options.hasKey(RNConstants.ARG_DIALOG_BUTTONS) && !options.isNull(RNConstants.ARG_DIALOG_BUTTONS)) {
- args.putBundle(RNConstants.ARG_DIALOG_BUTTONS, Arguments.toBundle(options.getMap(RNConstants.ARG_DIALOG_BUTTONS)));
- }
- if (options.hasKey(RNConstants.ARG_TZOFFSET_MINS) && !options.isNull(RNConstants.ARG_TZOFFSET_MINS)) {
- args.putLong(RNConstants.ARG_TZOFFSET_MINS, (long) options.getDouble(RNConstants.ARG_TZOFFSET_MINS));
- }
- if (options.hasKey(RNConstants.ARG_TESTID) && !options.isNull(RNConstants.ARG_TESTID)) {
- args.putString(RNConstants.ARG_TESTID, options.getString(RNConstants.ARG_TESTID));
- }
- if (options.hasKey(RNConstants.FIRST_DAY_OF_WEEK) && !options.isNull(RNConstants.FIRST_DAY_OF_WEEK)) {
- // FIRST_DAY_OF_WEEK is 0-indexed, since it uses the same constants DAY_OF_WEEK used in the Windows implementation
- // Android DatePicker uses 1-indexed values, SUNDAY being 1 and SATURDAY being 7, so the +1 is necessary in this case
- args.putInt(RNConstants.FIRST_DAY_OF_WEEK, options.getInt(RNConstants.FIRST_DAY_OF_WEEK)+1);
- }
- return args;
- }
}
diff --git a/android/src/main/java/com/reactcommunity/rndatetimepicker/MaterialDatePickerModule.kt b/android/src/main/java/com/reactcommunity/rndatetimepicker/MaterialDatePickerModule.kt
new file mode 100644
index 00000000..2cb28ade
--- /dev/null
+++ b/android/src/main/java/com/reactcommunity/rndatetimepicker/MaterialDatePickerModule.kt
@@ -0,0 +1,44 @@
+package com.reactcommunity.rndatetimepicker
+
+import androidx.fragment.app.FragmentActivity
+import com.facebook.react.bridge.Promise
+import com.facebook.react.bridge.ReactApplicationContext
+import com.facebook.react.bridge.ReadableMap
+import com.facebook.react.bridge.UiThreadUtil
+import com.reactcommunity.rndatetimepicker.Common.createDatePickerArguments
+import com.reactcommunity.rndatetimepicker.Common.dismissDialog
+
+class MaterialDatePickerModule(reactContext: ReactApplicationContext): NativeModuleMaterialDatePickerSpec(reactContext) {
+ companion object {
+ const val NAME = "RNCMaterialDatePicker"
+ }
+
+ override fun getName(): String {
+ return NAME
+ }
+
+ override fun dismiss(promise: Promise?) {
+ val activity = currentActivity as FragmentActivity?
+ dismissDialog(activity, NAME, promise)
+ }
+
+ override fun open(params: ReadableMap, promise: Promise) {
+ val activity = currentActivity as FragmentActivity?
+ if (activity == null) {
+ promise.reject(
+ RNConstants.ERROR_NO_ACTIVITY,
+ "Tried to open a MaterialDatePicker dialog while not attached to an Activity"
+ )
+ return
+ }
+
+ val fragmentManager = activity.supportFragmentManager
+
+ UiThreadUtil.runOnUiThread {
+ val arguments = createDatePickerArguments(params)
+ val datePicker =
+ RNMaterialDatePicker(arguments, promise, fragmentManager, reactApplicationContext)
+ datePicker.open()
+ }
+ }
+}
diff --git a/android/src/main/java/com/reactcommunity/rndatetimepicker/MaterialTimePickerModule.kt b/android/src/main/java/com/reactcommunity/rndatetimepicker/MaterialTimePickerModule.kt
new file mode 100644
index 00000000..b3e75669
--- /dev/null
+++ b/android/src/main/java/com/reactcommunity/rndatetimepicker/MaterialTimePickerModule.kt
@@ -0,0 +1,49 @@
+package com.reactcommunity.rndatetimepicker
+
+import androidx.fragment.app.FragmentActivity
+import com.facebook.react.bridge.Promise
+import com.facebook.react.bridge.ReactApplicationContext
+import com.facebook.react.bridge.ReadableMap
+import com.facebook.react.bridge.UiThreadUtil
+import com.reactcommunity.rndatetimepicker.Common.createTimePickerArguments
+import com.reactcommunity.rndatetimepicker.Common.dismissDialog
+
+class MaterialTimePickerModule(reactContext: ReactApplicationContext) :
+ NativeModuleMaterialTimePickerSpec(reactContext) {
+ companion object {
+ const val NAME = "RNCMaterialTimePicker"
+ }
+
+ override fun getName(): String {
+ return NAME
+ }
+
+ override fun dismiss(promise: Promise?) {
+ val activity = currentActivity as FragmentActivity?
+ dismissDialog(activity, NAME, promise)
+ }
+
+ override fun open(params: ReadableMap, promise: Promise) {
+ val activity = currentActivity as FragmentActivity?
+ if (activity == null) {
+ promise.reject(
+ RNConstants.ERROR_NO_ACTIVITY,
+ "Tried to open a MaterialTimePicker dialog while not attached to an Activity"
+ )
+ }
+
+ val fragmentManager = activity!!.supportFragmentManager
+
+ UiThreadUtil.runOnUiThread {
+ val arguments =
+ createTimePickerArguments(params)
+ val materialPicker = RNMaterialTimePicker(
+ arguments,
+ promise,
+ fragmentManager,
+ reactApplicationContext
+ )
+ materialPicker.open()
+ }
+ }
+}
diff --git a/android/src/main/java/com/reactcommunity/rndatetimepicker/RNConstants.java b/android/src/main/java/com/reactcommunity/rndatetimepicker/RNConstants.java
index acb56cb9..07220b79 100644
--- a/android/src/main/java/com/reactcommunity/rndatetimepicker/RNConstants.java
+++ b/android/src/main/java/com/reactcommunity/rndatetimepicker/RNConstants.java
@@ -14,6 +14,9 @@ public final class RNConstants {
public static final String ARG_TZOFFSET_MINS = "timeZoneOffsetInMinutes";
public static final String ARG_TZ_NAME = "timeZoneName";
public static final String ARG_TESTID = "testID";
+ public static final String ARG_TITLE = "title";
+ public static final String ARG_INITIAL_INPUT_MODE = "initialInputMode";
+ public static final String ARG_FULLSCREEN = "fullscreen";
public static final String ACTION_DATE_SET = "dateSetAction";
public static final String ACTION_TIME_SET = "timeSetAction";
public static final String ACTION_DISMISSED = "dismissedAction";
diff --git a/android/src/main/java/com/reactcommunity/rndatetimepicker/RNDate.java b/android/src/main/java/com/reactcommunity/rndatetimepicker/RNDate.java
index 137869a9..f5dc0f64 100644
--- a/android/src/main/java/com/reactcommunity/rndatetimepicker/RNDate.java
+++ b/android/src/main/java/com/reactcommunity/rndatetimepicker/RNDate.java
@@ -23,4 +23,5 @@ public RNDate(Bundle args) {
public int day() { return now.get(Calendar.DAY_OF_MONTH); }
public int hour() { return now.get(Calendar.HOUR_OF_DAY); }
public int minute() { return now.get(Calendar.MINUTE); }
+ public Long timestamp() { return now.getTimeInMillis(); }
}
diff --git a/android/src/main/java/com/reactcommunity/rndatetimepicker/RNDateTimePickerPackage.java b/android/src/main/java/com/reactcommunity/rndatetimepicker/RNDateTimePickerPackage.java
index ceafa6ba..417183e4 100644
--- a/android/src/main/java/com/reactcommunity/rndatetimepicker/RNDateTimePickerPackage.java
+++ b/android/src/main/java/com/reactcommunity/rndatetimepicker/RNDateTimePickerPackage.java
@@ -20,6 +20,10 @@ public NativeModule getModule(String name, ReactApplicationContext reactContext)
return new DatePickerModule(reactContext);
} else if (name.equals(TimePickerModule.NAME)) {
return new TimePickerModule(reactContext);
+ } else if (name.equals(MaterialDatePickerModule.NAME)) {
+ return new MaterialDatePickerModule(reactContext);
+ } else if (name.equals(MaterialTimePickerModule.NAME)) {
+ return new MaterialTimePickerModule(reactContext);
} else {
return null;
}
@@ -52,6 +56,28 @@ public ReactModuleInfoProvider getReactModuleInfoProvider() {
false, // isCxxModule
isTurboModule // isTurboModule
));
+ moduleInfos.put(
+ MaterialDatePickerModule.NAME,
+ new ReactModuleInfo(
+ MaterialDatePickerModule.NAME,
+ MaterialDatePickerModule.NAME,
+ false, // canOverrideExistingModule
+ false, // needsEagerInit
+ false, // hasConstants
+ false, // isCxxModule
+ isTurboModule // isTurboModule
+ ));
+ moduleInfos.put(
+ MaterialTimePickerModule.NAME,
+ new ReactModuleInfo(
+ MaterialTimePickerModule.NAME,
+ MaterialTimePickerModule.NAME,
+ false, // canOverrideExistingModule
+ false, // needsEagerInit
+ false, // hasConstants
+ false, // isCxxModule
+ isTurboModule // isTurboModule
+ ));
return moduleInfos;
};
}
diff --git a/android/src/main/java/com/reactcommunity/rndatetimepicker/RNMaterialDatePicker.kt b/android/src/main/java/com/reactcommunity/rndatetimepicker/RNMaterialDatePicker.kt
new file mode 100644
index 00000000..3e0d88b7
--- /dev/null
+++ b/android/src/main/java/com/reactcommunity/rndatetimepicker/RNMaterialDatePicker.kt
@@ -0,0 +1,179 @@
+package com.reactcommunity.rndatetimepicker
+
+import android.content.DialogInterface
+import android.os.Bundle
+import androidx.fragment.app.FragmentManager
+import com.facebook.react.bridge.Promise
+import com.facebook.react.bridge.ReactApplicationContext
+import com.facebook.react.bridge.WritableNativeMap
+import com.google.android.material.datepicker.CalendarConstraints
+import com.google.android.material.datepicker.CalendarConstraints.DateValidator
+import com.google.android.material.datepicker.CompositeDateValidator
+import com.google.android.material.datepicker.DateValidatorPointBackward
+import com.google.android.material.datepicker.DateValidatorPointForward
+import com.google.android.material.datepicker.MaterialDatePicker
+import com.google.android.material.datepicker.MaterialPickerOnPositiveButtonClickListener
+import java.util.Calendar
+
+class RNMaterialDatePicker(
+ private val args: Bundle,
+ private val promise: Promise,
+ private val fragmentManager: FragmentManager,
+ private val reactContext: ReactApplicationContext
+) {
+ private var promiseResolved = false
+ private var datePicker: MaterialDatePicker? = null
+ private var builder = MaterialDatePicker.Builder.datePicker()
+
+ fun open() {
+ createDatePicker()
+ addListeners()
+ show()
+ }
+
+ private fun createDatePicker() {
+ setInitialDate()
+ setTitle()
+ setInputMode()
+ setButtons()
+ setConstraints()
+ setFullscreen()
+
+ datePicker = builder.build()
+ }
+
+ private fun setInitialDate() {
+ val initialDate = RNDate(args)
+ builder.setSelection(initialDate.timestamp())
+ }
+
+ private fun setTitle() {
+ val title = args.getString(RNConstants.ARG_TITLE)
+ if (!title.isNullOrEmpty()) {
+ builder.setTitleText(args.getString(RNConstants.ARG_TITLE))
+ }
+ }
+
+ private fun setInputMode() {
+ if (args.getString(RNConstants.ARG_INITIAL_INPUT_MODE).isNullOrEmpty()) {
+ builder.setInputMode(MaterialDatePicker.INPUT_MODE_CALENDAR)
+ return
+ }
+
+ val inputMode =
+ RNMaterialInputMode.valueOf(
+ args.getString(RNConstants.ARG_INITIAL_INPUT_MODE)!!.uppercase()
+ )
+
+ if (inputMode == RNMaterialInputMode.KEYBOARD) {
+ builder.setInputMode(MaterialDatePicker.INPUT_MODE_TEXT)
+ } else {
+ builder.setInputMode(MaterialDatePicker.INPUT_MODE_CALENDAR)
+ }
+ }
+
+ private fun setConstraints() {
+ val constraintsBuilder = CalendarConstraints.Builder()
+
+ if (args.containsKey(RNConstants.FIRST_DAY_OF_WEEK)) {
+ constraintsBuilder.setFirstDayOfWeek(args.getInt(RNConstants.FIRST_DAY_OF_WEEK))
+ }
+
+ val validators = mutableListOf()
+
+ if (args.containsKey(RNConstants.ARG_MINDATE)) {
+ val minDate = Common.minDateWithTimeZone(args)
+ validators.add(DateValidatorPointForward.from(minDate))
+ }
+
+ if (args.containsKey(RNConstants.ARG_MAXDATE)) {
+ val maxDate = Common.maxDateWithTimeZone(args)
+ validators.add(DateValidatorPointBackward.before(maxDate))
+ }
+
+ constraintsBuilder.setValidator(CompositeDateValidator.allOf(validators))
+ builder.setCalendarConstraints(constraintsBuilder.build())
+ }
+
+ private fun setFullscreen() {
+ val isFullscreen = args.getBoolean(RNConstants.ARG_FULLSCREEN)
+
+ if (isFullscreen) {
+ builder.setTheme(com.google.android.material.R.style.ThemeOverlay_Material3_MaterialCalendar_Fullscreen)
+ } else {
+ builder.setTheme(com.google.android.material.R.style.ThemeOverlay_Material3_MaterialCalendar)
+ }
+ }
+
+ private fun addListeners() {
+ val listeners = Listeners()
+ datePicker!!.addOnPositiveButtonClickListener(listeners)
+ datePicker!!.addOnDismissListener(listeners)
+ }
+
+ private fun show() {
+ datePicker!!.show(fragmentManager, MaterialDatePickerModule.NAME)
+ }
+
+ private fun setButtons() {
+ val buttons = args.getBundle(RNConstants.ARG_DIALOG_BUTTONS) ?: return
+
+ val negativeButton = buttons.getBundle(Common.NEGATIVE)
+ val positiveButton = buttons.getBundle(Common.POSITIVE)
+
+ if (negativeButton != null) {
+ builder.setNegativeButtonText(negativeButton.getString(Common.LABEL))
+ }
+
+ if (positiveButton != null) {
+ builder.setPositiveButtonText(positiveButton.getString(Common.LABEL))
+ }
+ }
+
+ private inner class Listeners : MaterialPickerOnPositiveButtonClickListener,
+ DialogInterface.OnDismissListener {
+ override fun onDismiss(dialog: DialogInterface) {
+ if (promiseResolved || !reactContext.hasActiveReactInstance()) return
+
+ val result = WritableNativeMap()
+ result.putString("action", RNConstants.ACTION_DISMISSED)
+ promise.resolve(result)
+ promiseResolved = true
+ }
+
+ override fun onPositiveButtonClick(selection: Long) {
+ if (promiseResolved || !reactContext.hasActiveReactInstance()) return
+
+ val newCalendar = createNewCalendar(selection)
+ val result = WritableNativeMap()
+
+ result.putString("action", RNConstants.ACTION_DATE_SET)
+ result.putDouble("timestamp", newCalendar.timeInMillis.toDouble())
+ result.putDouble(
+ "utcOffset",
+ newCalendar.timeZone.getOffset(newCalendar.timeInMillis).toDouble() / 1000 / 60
+ )
+
+ promise.resolve(result)
+ promiseResolved = true
+ }
+
+ private fun createNewCalendar(selection: Long): Calendar {
+ val initialDate = RNDate(args)
+ val newCalendar = Calendar.getInstance(
+ Common.getTimeZone(
+ args
+ )
+ )
+
+ newCalendar.timeInMillis = selection
+
+ newCalendar[Calendar.HOUR_OF_DAY] = initialDate.hour()
+ newCalendar[Calendar.MINUTE] = initialDate.minute()
+ newCalendar[Calendar.SECOND] = 0
+ newCalendar[Calendar.MILLISECOND] = 0
+
+ return newCalendar
+ }
+ }
+}
diff --git a/android/src/main/java/com/reactcommunity/rndatetimepicker/RNMaterialInputMode.java b/android/src/main/java/com/reactcommunity/rndatetimepicker/RNMaterialInputMode.java
new file mode 100644
index 00000000..5952b0a2
--- /dev/null
+++ b/android/src/main/java/com/reactcommunity/rndatetimepicker/RNMaterialInputMode.java
@@ -0,0 +1,6 @@
+package com.reactcommunity.rndatetimepicker;
+
+public enum RNMaterialInputMode {
+ DEFAULT,
+ KEYBOARD
+}
diff --git a/android/src/main/java/com/reactcommunity/rndatetimepicker/RNMaterialTimePicker.kt b/android/src/main/java/com/reactcommunity/rndatetimepicker/RNMaterialTimePicker.kt
new file mode 100644
index 00000000..9ff384bf
--- /dev/null
+++ b/android/src/main/java/com/reactcommunity/rndatetimepicker/RNMaterialTimePicker.kt
@@ -0,0 +1,155 @@
+package com.reactcommunity.rndatetimepicker
+
+import android.content.DialogInterface
+import android.os.Bundle
+import android.text.format.DateFormat
+import android.view.View
+import androidx.fragment.app.FragmentManager
+import com.facebook.react.bridge.Promise
+import com.facebook.react.bridge.ReactApplicationContext
+import com.facebook.react.bridge.WritableMap
+import com.facebook.react.bridge.WritableNativeMap
+import com.google.android.material.timepicker.MaterialTimePicker
+import com.google.android.material.timepicker.TimeFormat
+import java.util.Calendar
+
+class RNMaterialTimePicker(
+ private val args: Bundle,
+ private val promise: Promise,
+ private val fragmentManager: FragmentManager,
+ private val reactContext: ReactApplicationContext
+) {
+ private var promiseResolved = false
+ private var timePicker: MaterialTimePicker? = null
+ private var builder = MaterialTimePicker.Builder()
+
+ fun open() {
+ createTimePicker()
+ addListeners()
+ show()
+ }
+
+ private fun createTimePicker() {
+ setInitialDate()
+ setTitle()
+ setInputMode()
+ setButtons()
+ setTimeFormat()
+
+ timePicker = builder.build()
+ }
+
+ private fun setInitialDate() {
+ val initialDate = RNDate(args)
+
+ builder.setHour(initialDate.hour())
+ .setMinute(initialDate.minute())
+ }
+
+ private fun setTitle() {
+ val title = args.getString(RNConstants.ARG_TITLE)
+ if (!title.isNullOrEmpty()) {
+ builder.setTitleText(args.getString(RNConstants.ARG_TITLE))
+ }
+ }
+
+ private fun setInputMode() {
+ if (args.getString(RNConstants.ARG_INITIAL_INPUT_MODE).isNullOrEmpty()) {
+ builder.setInputMode(MaterialTimePicker.INPUT_MODE_CLOCK)
+ return
+ }
+
+ val inputMode =
+ RNMaterialInputMode.valueOf(
+ args.getString(RNConstants.ARG_INITIAL_INPUT_MODE)!!.uppercase()
+ )
+
+ if (inputMode == RNMaterialInputMode.KEYBOARD) {
+ builder.setInputMode(MaterialTimePicker.INPUT_MODE_KEYBOARD)
+ } else {
+ builder.setInputMode(MaterialTimePicker.INPUT_MODE_CLOCK)
+ }
+ }
+
+ private fun setButtons() {
+ val buttons = args.getBundle(RNConstants.ARG_DIALOG_BUTTONS) ?: return
+
+ val negativeButton = buttons.getBundle(Common.NEGATIVE)
+ val positiveButton = buttons.getBundle(Common.POSITIVE)
+
+ if (negativeButton != null) {
+ builder.setNegativeButtonText(negativeButton.getString(Common.LABEL))
+ }
+
+ if (positiveButton != null) {
+ builder.setPositiveButtonText(positiveButton.getString(Common.LABEL))
+ }
+ }
+
+ private fun setTimeFormat() {
+ if (args.getBoolean(RNConstants.ARG_IS24HOUR)) {
+ builder.setTimeFormat(TimeFormat.CLOCK_24H)
+ return
+ }
+
+ if (DateFormat.is24HourFormat(reactContext)) {
+ builder.setTimeFormat(TimeFormat.CLOCK_24H)
+ } else {
+ builder.setTimeFormat(TimeFormat.CLOCK_12H)
+ }
+ }
+
+ private fun addListeners() {
+ val listeners = Listeners()
+ timePicker!!.addOnPositiveButtonClickListener(listeners)
+ timePicker!!.addOnDismissListener(listeners)
+ }
+
+ private fun show() {
+ timePicker!!.show(fragmentManager, MaterialTimePickerModule.NAME)
+ }
+
+ private inner class Listeners : View.OnClickListener, DialogInterface.OnDismissListener {
+ override fun onDismiss(dialog: DialogInterface) {
+ if (promiseResolved || !reactContext.hasActiveReactInstance()) return
+
+ val result: WritableMap = WritableNativeMap()
+ result.putString("action", RNConstants.ACTION_DISMISSED)
+ promise.resolve(result)
+ promiseResolved = true
+ }
+
+ override fun onClick(v: View) {
+ if (promiseResolved || !reactContext.hasActiveReactInstance()) return
+
+ val newCalendar = createNewCalendar()
+ val result = WritableNativeMap()
+
+ result.putString("action", RNConstants.ACTION_DATE_SET)
+ result.putDouble("timestamp", newCalendar.timeInMillis.toDouble())
+ result.putDouble(
+ "utcOffset",
+ newCalendar.timeZone.getOffset(newCalendar.timeInMillis).toDouble() / 1000 / 60
+ )
+
+ promise.resolve(result)
+ promiseResolved = true
+ }
+
+ private fun createNewCalendar(): Calendar {
+ val initialDate = RNDate(args)
+ val calendar = Calendar.getInstance(
+ Common.getTimeZone(
+ args
+ )
+ )
+
+ calendar[initialDate.year(), initialDate.month(), initialDate.day(), timePicker!!.hour, timePicker!!.minute] =
+ 0
+
+ calendar[Calendar.MILLISECOND] = 0
+
+ return calendar
+ }
+ }
+}
diff --git a/android/src/main/java/com/reactcommunity/rndatetimepicker/TimePickerModule.java b/android/src/main/java/com/reactcommunity/rndatetimepicker/TimePickerModule.java
index 877b37b1..46dd5e41 100644
--- a/android/src/main/java/com/reactcommunity/rndatetimepicker/TimePickerModule.java
+++ b/android/src/main/java/com/reactcommunity/rndatetimepicker/TimePickerModule.java
@@ -23,6 +23,7 @@
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
+import static com.reactcommunity.rndatetimepicker.Common.createTimePickerArguments;
import static com.reactcommunity.rndatetimepicker.Common.dismissDialog;
import java.util.Calendar;
@@ -119,7 +120,7 @@ public void open(final ReadableMap options, final Promise promise) {
RNTimePickerDialogFragment oldFragment =
(RNTimePickerDialogFragment) fragmentManager.findFragmentByTag(NAME);
- Bundle arguments = createFragmentArguments(options);
+ Bundle arguments = createTimePickerArguments(options);
if (oldFragment != null) {
oldFragment.update(arguments);
@@ -137,25 +138,4 @@ public void open(final ReadableMap options, final Promise promise) {
fragment.show(fragmentManager, NAME);
});
}
-
- private Bundle createFragmentArguments(ReadableMap options) {
- final Bundle args = Common.createFragmentArguments(options);
-
- if (options.hasKey(RNConstants.ARG_IS24HOUR) && !options.isNull(RNConstants.ARG_IS24HOUR)) {
- args.putBoolean(RNConstants.ARG_IS24HOUR, options.getBoolean(RNConstants.ARG_IS24HOUR));
- }
- if (options.hasKey(RNConstants.ARG_DISPLAY) && !options.isNull(RNConstants.ARG_DISPLAY)) {
- args.putString(RNConstants.ARG_DISPLAY, options.getString(RNConstants.ARG_DISPLAY));
- }
- if (options.hasKey(RNConstants.ARG_DIALOG_BUTTONS) && !options.isNull(RNConstants.ARG_DIALOG_BUTTONS)) {
- args.putBundle(RNConstants.ARG_DIALOG_BUTTONS, Arguments.toBundle(options.getMap(RNConstants.ARG_DIALOG_BUTTONS)));
- }
- if (options.hasKey(RNConstants.ARG_INTERVAL) && !options.isNull(RNConstants.ARG_INTERVAL)) {
- args.putInt(RNConstants.ARG_INTERVAL, options.getInt(RNConstants.ARG_INTERVAL));
- }
- if (options.hasKey(RNConstants.ARG_TZOFFSET_MINS) && !options.isNull(RNConstants.ARG_TZOFFSET_MINS)) {
- args.putLong(RNConstants.ARG_TZOFFSET_MINS, (long) options.getDouble(RNConstants.ARG_TZOFFSET_MINS));
- }
- return args;
- }
}
diff --git a/android/src/paper/java/com/reactcommunity/rndatetimepicker/NativeModuleMaterialDatePickerSpec.java b/android/src/paper/java/com/reactcommunity/rndatetimepicker/NativeModuleMaterialDatePickerSpec.java
new file mode 100644
index 00000000..4db46bff
--- /dev/null
+++ b/android/src/paper/java/com/reactcommunity/rndatetimepicker/NativeModuleMaterialDatePickerSpec.java
@@ -0,0 +1,36 @@
+
+/**
+ * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
+ *
+ * Then it was commited. It is here to support the old architecture.
+ * If you use the new architecture, this file won't be included and instead will be generated by the codegen.
+ *
+ * @generated by codegen project: GenerateModuleJavaSpec.js
+ *
+ * @nolint
+ */
+
+package com.reactcommunity.rndatetimepicker;
+
+import com.facebook.proguard.annotations.DoNotStrip;
+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.ReactModuleWithSpec;
+import com.facebook.react.bridge.ReadableMap;
+import com.facebook.react.turbomodule.core.interfaces.TurboModule;
+
+public abstract class NativeModuleMaterialDatePickerSpec extends ReactContextBaseJavaModule implements ReactModuleWithSpec, TurboModule {
+ public NativeModuleMaterialDatePickerSpec(ReactApplicationContext reactContext) {
+ super(reactContext);
+ }
+
+ @ReactMethod
+ @DoNotStrip
+ public abstract void dismiss(Promise promise);
+
+ @ReactMethod
+ @DoNotStrip
+ public abstract void open(ReadableMap params, Promise promise);
+}
diff --git a/android/src/paper/java/com/reactcommunity/rndatetimepicker/NativeModuleMaterialTimePickerSpec.java b/android/src/paper/java/com/reactcommunity/rndatetimepicker/NativeModuleMaterialTimePickerSpec.java
new file mode 100644
index 00000000..a59a8cda
--- /dev/null
+++ b/android/src/paper/java/com/reactcommunity/rndatetimepicker/NativeModuleMaterialTimePickerSpec.java
@@ -0,0 +1,36 @@
+
+/**
+ * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
+ *
+ * Then it was commited. It is here to support the old architecture.
+ * If you use the new architecture, this file won't be included and instead will be generated by the codegen.
+ *
+ * @generated by codegen project: GenerateModuleJavaSpec.js
+ *
+ * @nolint
+ */
+
+package com.reactcommunity.rndatetimepicker;
+
+import com.facebook.proguard.annotations.DoNotStrip;
+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.ReactModuleWithSpec;
+import com.facebook.react.bridge.ReadableMap;
+import com.facebook.react.turbomodule.core.interfaces.TurboModule;
+
+public abstract class NativeModuleMaterialTimePickerSpec extends ReactContextBaseJavaModule implements ReactModuleWithSpec, TurboModule {
+ public NativeModuleMaterialTimePickerSpec(ReactApplicationContext reactContext) {
+ super(reactContext);
+ }
+
+ @ReactMethod
+ @DoNotStrip
+ public abstract void dismiss(Promise promise);
+
+ @ReactMethod
+ @DoNotStrip
+ public abstract void open(ReadableMap params, Promise promise);
+}
diff --git a/docs/images/android_material_date.jpg b/docs/images/android_material_date.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..8930cb10fe632ceeb95415b87f960660ba184f6c
GIT binary patch
literal 92790
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