@react-native-community/datetimepicker
Advanced tools
| /** | ||
| * Copyright (c) Facebook, Inc. and its affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| */ | ||
| package com.reactcommunity.rndatetimepicker; | ||
| import android.app.DatePickerDialog.OnDateSetListener; | ||
| import android.content.DialogInterface; | ||
| import android.content.DialogInterface.OnDismissListener; | ||
| import android.content.DialogInterface.OnClickListener; | ||
| import android.os.Bundle; | ||
| import android.widget.DatePicker; | ||
| import androidx.annotation.NonNull; | ||
| import androidx.fragment.app.FragmentActivity; | ||
| import androidx.fragment.app.FragmentManager; | ||
| import com.facebook.react.bridge.*; | ||
| import com.facebook.react.common.annotations.VisibleForTesting; | ||
| import com.facebook.react.module.annotations.ReactModule; | ||
| import static com.reactcommunity.rndatetimepicker.Common.dismissDialog; | ||
| import java.util.Calendar; | ||
| /** | ||
| * {@link NativeModule} that allows JS to show a native date picker dialog and get called back when | ||
| * the user selects a date. | ||
| */ | ||
| @ReactModule(name = DatePickerModule.NAME) | ||
| public class DatePickerModule extends NativeModuleDatePickerSpec { | ||
| @VisibleForTesting | ||
| public static final String NAME = "RNCDatePicker"; | ||
| public DatePickerModule(ReactApplicationContext reactContext) { | ||
| super(reactContext); | ||
| } | ||
| @NonNull | ||
| @Override | ||
| public String getName() { | ||
| return NAME; | ||
| } | ||
| private class DatePickerDialogListener implements OnDateSetListener, OnDismissListener, OnClickListener { | ||
| private final Promise mPromise; | ||
| private final Bundle mArgs; | ||
| private boolean mPromiseResolved = false; | ||
| public DatePickerDialogListener(final Promise promise, Bundle arguments) { | ||
| mPromise = promise; | ||
| mArgs = arguments; | ||
| } | ||
| @Override | ||
| public void onDateSet(DatePicker view, int year, int month, int day) { | ||
| if (!mPromiseResolved && getReactApplicationContext().hasActiveReactInstance()) { | ||
| final RNDate date = new RNDate(mArgs); | ||
| Calendar calendar = Calendar.getInstance(Common.getTimeZone(mArgs)); | ||
| calendar.set(year, month, day, date.hour(), date.minute(), 0); | ||
| calendar.set(Calendar.MILLISECOND, 0); | ||
| WritableMap result = new WritableNativeMap(); | ||
| result.putString("action", RNConstants.ACTION_DATE_SET); | ||
| result.putDouble("timestamp", calendar.getTimeInMillis()); | ||
| result.putDouble("utcOffset", calendar.getTimeZone().getOffset(calendar.getTimeInMillis()) / 1000 / 60); | ||
| mPromise.resolve(result); | ||
| mPromiseResolved = true; | ||
| } | ||
| } | ||
| @Override | ||
| public void onDismiss(DialogInterface dialog) { | ||
| if (!mPromiseResolved && getReactApplicationContext().hasActiveReactInstance()) { | ||
| WritableMap result = new WritableNativeMap(); | ||
| result.putString("action", RNConstants.ACTION_DISMISSED); | ||
| mPromise.resolve(result); | ||
| mPromiseResolved = true; | ||
| } | ||
| } | ||
| @Override | ||
| public void onClick(DialogInterface dialog, int which) { | ||
| if (!mPromiseResolved && getReactApplicationContext().hasActiveReactInstance()) { | ||
| WritableMap result = new WritableNativeMap(); | ||
| result.putString("action", RNConstants.ACTION_NEUTRAL_BUTTON); | ||
| mPromise.resolve(result); | ||
| mPromiseResolved = true; | ||
| } | ||
| } | ||
| } | ||
| @ReactMethod | ||
| public void dismiss(Promise promise) { | ||
| FragmentActivity activity = (FragmentActivity) getCurrentActivity(); | ||
| dismissDialog(activity, NAME, promise); | ||
| } | ||
| /** | ||
| * Show a date picker dialog. | ||
| * | ||
| * @param options a map containing options. Available keys are: | ||
| * | ||
| * <ul> | ||
| * <li>{@code date} (timestamp in milliseconds) the date to show by default</li> | ||
| * <li> | ||
| * {@code minimumDate} (timestamp in milliseconds) the minimum date the user should be allowed | ||
| * to select | ||
| * </li> | ||
| * <li> | ||
| * {@code maximumDate} (timestamp in milliseconds) the maximum date the user should be allowed | ||
| * to select | ||
| * </li> | ||
| * <li> | ||
| * {@code display} To set the date picker display to 'calendar/spinner/default' | ||
| * </li> | ||
| * <li> | ||
| * {@code testID} testID for testing with e.g. detox. | ||
| * </li> | ||
| * </ul> | ||
| * | ||
| * @param promise This will be invoked with parameters action, year, | ||
| * month (0-11), day, where action is {@code dateSetAction} or | ||
| * {@code dismissedAction}, depending on what the user did. If the action is | ||
| * dismiss, year, month and date are undefined. | ||
| */ | ||
| @ReactMethod | ||
| public void open(final ReadableMap options, final Promise promise) { | ||
| FragmentActivity activity = (FragmentActivity) getCurrentActivity(); | ||
| if (activity == null) { | ||
| promise.reject( | ||
| RNConstants.ERROR_NO_ACTIVITY, | ||
| "Tried to open a DatePicker dialog while not attached to an Activity"); | ||
| return; | ||
| } | ||
| final FragmentManager fragmentManager = activity.getSupportFragmentManager(); | ||
| UiThreadUtil.runOnUiThread(() -> { | ||
| RNDatePickerDialogFragment oldFragment = | ||
| (RNDatePickerDialogFragment) fragmentManager.findFragmentByTag(NAME); | ||
| Bundle arguments = createFragmentArguments(options); | ||
| if (oldFragment != null) { | ||
| oldFragment.update(arguments); | ||
| return; | ||
| } | ||
| RNDatePickerDialogFragment fragment = new RNDatePickerDialogFragment(); | ||
| fragment.setArguments(arguments); | ||
| final DatePickerDialogListener listener = new DatePickerDialogListener(promise, arguments); | ||
| fragment.setOnDismissListener(listener); | ||
| fragment.setOnDateSetListener(listener); | ||
| fragment.setOnNeutralButtonActionListener(listener); | ||
| 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)); | ||
| } | ||
| return args; | ||
| } | ||
| } |
| /** | ||
| * Copyright (c) Facebook, Inc. and its affiliates. | ||
| * <p> | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * </p> | ||
| */ | ||
| package com.reactcommunity.rndatetimepicker; | ||
| import com.facebook.react.bridge.*; | ||
| import com.facebook.react.common.annotations.VisibleForTesting; | ||
| import com.facebook.react.module.annotations.ReactModule; | ||
| import android.app.TimePickerDialog.OnTimeSetListener; | ||
| import android.content.DialogInterface; | ||
| import android.content.DialogInterface.OnDismissListener; | ||
| import android.content.DialogInterface.OnClickListener; | ||
| import android.os.Bundle; | ||
| import android.widget.TimePicker; | ||
| import androidx.annotation.NonNull; | ||
| import androidx.fragment.app.FragmentActivity; | ||
| import androidx.fragment.app.FragmentManager; | ||
| import static com.reactcommunity.rndatetimepicker.Common.dismissDialog; | ||
| import java.util.Calendar; | ||
| /** | ||
| * {@link NativeModule} that allows JS to show a native time picker dialog and get called back when | ||
| * the user selects a time. | ||
| */ | ||
| @ReactModule(name = TimePickerModule.NAME) | ||
| public class TimePickerModule extends NativeModuleTimePickerSpec { | ||
| @VisibleForTesting | ||
| public static final String NAME = "RNCTimePicker"; | ||
| public TimePickerModule(ReactApplicationContext reactContext) { | ||
| super(reactContext); | ||
| } | ||
| @NonNull | ||
| @Override | ||
| public String getName() { | ||
| return NAME; | ||
| } | ||
| private class TimePickerDialogListener implements OnTimeSetListener, OnDismissListener, OnClickListener { | ||
| private final Promise mPromise; | ||
| private final Bundle mArgs; | ||
| private boolean mPromiseResolved = false; | ||
| public TimePickerDialogListener(Promise promise, Bundle arguments) { | ||
| mPromise = promise; | ||
| mArgs = arguments; | ||
| } | ||
| @Override | ||
| public void onTimeSet(TimePicker view, int hour, int minute) { | ||
| if (!mPromiseResolved && getReactApplicationContext().hasActiveReactInstance()) { | ||
| final RNDate date = new RNDate(mArgs); | ||
| Calendar calendar = Calendar.getInstance(Common.getTimeZone(mArgs)); | ||
| calendar.set(date.year(), date.month(), date.day(), hour, minute, 0); | ||
| calendar.set(Calendar.MILLISECOND, 0); | ||
| WritableMap result = new WritableNativeMap(); | ||
| result.putString("action", RNConstants.ACTION_TIME_SET); | ||
| result.putDouble("timestamp", calendar.getTimeInMillis()); | ||
| result.putDouble("utcOffset", calendar.getTimeZone().getOffset(calendar.getTimeInMillis()) / 1000 / 60); | ||
| mPromise.resolve(result); | ||
| mPromiseResolved = true; | ||
| } | ||
| } | ||
| @Override | ||
| public void onDismiss(DialogInterface dialog) { | ||
| if (!mPromiseResolved && getReactApplicationContext().hasActiveReactInstance()) { | ||
| WritableMap result = new WritableNativeMap(); | ||
| result.putString("action", RNConstants.ACTION_DISMISSED); | ||
| mPromise.resolve(result); | ||
| mPromiseResolved = true; | ||
| } | ||
| } | ||
| @Override | ||
| public void onClick(DialogInterface dialog, int which) { | ||
| if (!mPromiseResolved && getReactApplicationContext().hasActiveReactInstance()) { | ||
| WritableMap result = new WritableNativeMap(); | ||
| result.putString("action", RNConstants.ACTION_NEUTRAL_BUTTON); | ||
| mPromise.resolve(result); | ||
| mPromiseResolved = true; | ||
| } | ||
| } | ||
| } | ||
| @ReactMethod | ||
| public void dismiss(Promise promise) { | ||
| FragmentActivity activity = (FragmentActivity) getCurrentActivity(); | ||
| dismissDialog(activity, NAME, promise); | ||
| } | ||
| @ReactMethod | ||
| public void open(final ReadableMap options, final Promise promise) { | ||
| FragmentActivity activity = (FragmentActivity) getCurrentActivity(); | ||
| if (activity == null) { | ||
| promise.reject( | ||
| RNConstants.ERROR_NO_ACTIVITY, | ||
| "Tried to open a TimePicker dialog while not attached to an Activity"); | ||
| return; | ||
| } | ||
| // We want to support both android.app.Activity and the pre-Honeycomb FragmentActivity | ||
| // (for apps that use it for legacy reasons). This unfortunately leads to some code duplication. | ||
| final FragmentManager fragmentManager = activity.getSupportFragmentManager(); | ||
| UiThreadUtil.runOnUiThread(() -> { | ||
| RNTimePickerDialogFragment oldFragment = | ||
| (RNTimePickerDialogFragment) fragmentManager.findFragmentByTag(NAME); | ||
| Bundle arguments = createFragmentArguments(options); | ||
| if (oldFragment != null) { | ||
| oldFragment.update(arguments); | ||
| return; | ||
| } | ||
| RNTimePickerDialogFragment fragment = new RNTimePickerDialogFragment(); | ||
| fragment.setArguments(arguments); | ||
| final TimePickerDialogListener listener = new TimePickerDialogListener(promise, arguments); | ||
| fragment.setOnDismissListener(listener); | ||
| fragment.setOnTimeSetListener(listener); | ||
| fragment.setOnNeutralButtonActionListener(listener); | ||
| 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; | ||
| } | ||
| } |
| /** | ||
| * 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 NativeModuleDatePickerSpec extends ReactContextBaseJavaModule implements ReactModuleWithSpec, TurboModule { | ||
| public NativeModuleDatePickerSpec(ReactApplicationContext reactContext) { | ||
| super(reactContext); | ||
| } | ||
| @ReactMethod | ||
| @DoNotStrip | ||
| public abstract void dismiss(Promise promise); | ||
| @ReactMethod | ||
| @DoNotStrip | ||
| public abstract void open(ReadableMap params, Promise promise); | ||
| } |
| /** | ||
| * 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 NativeModuleTimePickerSpec extends ReactContextBaseJavaModule implements ReactModuleWithSpec, TurboModule { | ||
| public NativeModuleTimePickerSpec(ReactApplicationContext reactContext) { | ||
| super(reactContext); | ||
| } | ||
| @ReactMethod | ||
| @DoNotStrip | ||
| public abstract void dismiss(Promise promise); | ||
| @ReactMethod | ||
| @DoNotStrip | ||
| public abstract void open(ReadableMap params, Promise promise); | ||
| } |
| // @flow strict-local | ||
| import type {ViewProps} from 'react-native/Libraries/Components/View/ViewPropTypes'; | ||
| import type {ColorValue} from 'react-native/Libraries/StyleSheet/StyleSheet'; | ||
| import type {HostComponent} from 'react-native'; | ||
| import type { | ||
| BubblingEventHandler, | ||
| Double, | ||
| Int32, | ||
| WithDefault, | ||
| } from 'react-native/Libraries/Types/CodegenTypes'; | ||
| import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent'; | ||
| type DateTimePickerEvent = $ReadOnly<{| | ||
| timestamp: Double, | ||
| utcOffset: Int32, | ||
| |}>; | ||
| type NativeProps = $ReadOnly<{| | ||
| ...ViewProps, | ||
| onChange?: ?BubblingEventHandler<DateTimePickerEvent>, | ||
| onPickerDismiss?: ?BubblingEventHandler<null>, | ||
| maximumDate?: ?Double, | ||
| minimumDate?: ?Double, | ||
| date?: ?Double, | ||
| locale?: ?string, | ||
| minuteInterval?: ?Int32, | ||
| mode?: WithDefault<'date' | 'time' | 'datetime' | 'countdown', 'date'>, | ||
| timeZoneOffsetInMinutes?: ?Double, | ||
| timeZoneName?: ?string, | ||
| textColor?: ?ColorValue, | ||
| accentColor?: ?ColorValue, | ||
| themeVariant?: WithDefault<'dark' | 'light' | 'unspecified', 'unspecified'>, | ||
| displayIOS?: WithDefault< | ||
| 'default' | 'spinner' | 'compact' | 'inline', | ||
| 'default', | ||
| >, | ||
| enabled?: WithDefault<boolean, true>, | ||
| |}>; | ||
| export default (codegenNativeComponent<NativeProps>('RNDateTimePicker', { | ||
| excludedPlatforms: ['android'], | ||
| }): HostComponent<NativeProps>); |
| // @flow strict-local | ||
| import type {TurboModule} from 'react-native/Libraries/TurboModule/RCTExport'; | ||
| import {TurboModuleRegistry} from 'react-native'; | ||
| import type {DateTimePickerResult} from '../types'; | ||
| type OpenParams = $ReadOnly<{| | ||
| // TODO does codegen handle object type? | ||
| |}>; | ||
| export interface Spec extends TurboModule { | ||
| dismiss(): Promise<boolean>; | ||
| open(params: OpenParams): Promise<DateTimePickerResult>; | ||
| } | ||
| export default (TurboModuleRegistry.getEnforcing<Spec>('RNCDatePicker'): ?Spec); |
| // @flow strict-local | ||
| import type {TurboModule} from 'react-native/Libraries/TurboModule/RCTExport'; | ||
| import {TurboModuleRegistry} from 'react-native'; | ||
| import type {DateTimePickerResult} from '../types'; | ||
| type OpenParams = $ReadOnly<{| | ||
| // TODO does codegen handle object type? | ||
| |}>; | ||
| export interface Spec extends TurboModule { | ||
| dismiss(): Promise<boolean>; | ||
| open(params: OpenParams): Promise<DateTimePickerResult>; | ||
| } | ||
| export default (TurboModuleRegistry.getEnforcing<Spec>('RNCTimePicker'): ?Spec); |
Sorry, the diff of this file is not supported yet
| //{{NO_DEPENDENCIES}} | ||
| // Microsoft Visual C++ generated include file. | ||
| // Used by DateTimePicker.rc | ||
| // Next default values for new objects | ||
| // | ||
| #ifdef APSTUDIO_INVOKED | ||
| #ifndef APSTUDIO_READONLY_SYMBOLS | ||
| #define _APS_NEXT_RESOURCE_VALUE 101 | ||
| #define _APS_NEXT_COMMAND_VALUE 40001 | ||
| #define _APS_NEXT_CONTROL_VALUE 1001 | ||
| #define _APS_NEXT_SYMED_VALUE 101 | ||
| #endif | ||
| #endif |
+55
-13
@@ -1,25 +0,67 @@ | ||
| apply plugin: 'com.android.library' | ||
| buildscript { | ||
| repositories { | ||
| google() | ||
| mavenCentral() | ||
| } | ||
| } | ||
| def safeExtGet(prop, fallback) { | ||
| def retValue = rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback | ||
| return retValue | ||
| def getExtOrIntegerDefault(name) { | ||
| return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties['ReactNativeDateTimePicker_' + name]).toInteger() | ||
| } | ||
| def isNewArchitectureEnabled() { | ||
| // To opt-in for the New Architecture, you can either: | ||
| // - Set `newArchEnabled` to true inside the `gradle.properties` file | ||
| // - Invoke gradle with `-newArchEnabled=true` | ||
| // - Set an environment variable `ORG_GRADLE_PROJECT_newArchEnabled=true` | ||
| return project.hasProperty("newArchEnabled") && project.newArchEnabled == "true" | ||
| } | ||
| apply plugin: 'com.android.library' | ||
| if (isNewArchitectureEnabled()) { | ||
| apply plugin: "com.facebook.react" | ||
| } | ||
| android { | ||
| compileSdkVersion safeExtGet('compileSdkVersion', 28) | ||
| buildToolsVersion safeExtGet('buildToolsVersion', '28.0.3') | ||
| def agpVersion = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION | ||
| if (agpVersion.tokenize('.')[0].toInteger() >= 7) { | ||
| namespace "com.reactcommunity.rndatetimepicker" | ||
| } | ||
| compileSdkVersion getExtOrIntegerDefault('compileSdkVersion') | ||
| // Used to override the NDK path/version on internal CI or by allowing | ||
| // users to customize the NDK path/version from their root project (e.g. for M1 support) | ||
| if (rootProject.hasProperty("ndkPath")) { | ||
| ndkPath rootProject.ext.ndkPath | ||
| } | ||
| if (rootProject.hasProperty("ndkVersion")) { | ||
| ndkVersion rootProject.ext.ndkVersion | ||
| } | ||
| defaultConfig { | ||
| minSdkVersion safeExtGet('minSdkVersion', 21) | ||
| targetSdkVersion safeExtGet('targetSdkVersion', 28) | ||
| versionCode 1 | ||
| versionName "1.0" | ||
| minSdkVersion getExtOrIntegerDefault('minSdkVersion') | ||
| targetSdkVersion getExtOrIntegerDefault('targetSdkVersion') | ||
| buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString() | ||
| } | ||
| lintOptions { | ||
| abortOnError false | ||
| sourceSets.main { | ||
| java { | ||
| if (!isNewArchitectureEnabled()) { | ||
| srcDirs += 'src/paper/java' | ||
| } | ||
| } | ||
| } | ||
| } | ||
| repositories { | ||
| google() | ||
| mavenLocal() | ||
| mavenCentral() | ||
| } | ||
| dependencies { | ||
| implementation "com.facebook.react:react-native:${safeExtGet('reactNativeVersion', '+')}" // From node_modules | ||
| //noinspection GradleDynamicVersion | ||
| implementation 'com.facebook.react:react-native:+' | ||
| } |
@@ -21,4 +21,11 @@ package com.reactcommunity.rndatetimepicker; | ||
| import com.facebook.react.bridge.Promise; | ||
| import com.facebook.react.bridge.ReadableMap; | ||
| import com.facebook.react.util.RNLog; | ||
| import java.util.Arrays; | ||
| import java.util.Calendar; | ||
| import java.util.HashSet; | ||
| import java.util.Locale; | ||
| import java.util.SimpleTimeZone; | ||
| import java.util.TimeZone; | ||
@@ -66,18 +73,15 @@ public class Common { | ||
| @NonNull | ||
| public static DialogInterface.OnShowListener setButtonTextColor(@NonNull Context activityContext, final AlertDialog dialog, final Bundle args, final boolean needsColorOverride) { | ||
| return new DialogInterface.OnShowListener() { | ||
| @Override | ||
| public void onShow(DialogInterface dialogInterface) { | ||
| // change text color only if custom color is set or if spinner mode is set | ||
| // because spinner suffers from https://github.com/react-native-datetimepicker/datetimepicker/issues/543 | ||
| public static DialogInterface.OnShowListener setButtonTextColor(@NonNull final Context activityContext, final AlertDialog dialog, final Bundle args, final boolean needsColorOverride) { | ||
| return dialogInterface -> { | ||
| // change text color only if custom color is set or if spinner mode is set | ||
| // because spinner suffers from https://github.com/react-native-datetimepicker/datetimepicker/issues/543 | ||
| Button positiveButton = dialog.getButton(AlertDialog.BUTTON_POSITIVE); | ||
| Button negativeButton = dialog.getButton(AlertDialog.BUTTON_NEGATIVE); | ||
| Button neutralButton = dialog.getButton(AlertDialog.BUTTON_NEUTRAL); | ||
| Button positiveButton = dialog.getButton(AlertDialog.BUTTON_POSITIVE); | ||
| Button negativeButton = dialog.getButton(AlertDialog.BUTTON_NEGATIVE); | ||
| Button neutralButton = dialog.getButton(AlertDialog.BUTTON_NEUTRAL); | ||
| int textColorPrimary = getDefaultDialogButtonTextColor(activityContext); | ||
| setTextColor(positiveButton, POSITIVE, args, needsColorOverride, textColorPrimary); | ||
| setTextColor(negativeButton, NEGATIVE, args, needsColorOverride, textColorPrimary); | ||
| setTextColor(neutralButton, NEUTRAL, args, needsColorOverride, textColorPrimary); | ||
| } | ||
| int textColorPrimary = getDefaultDialogButtonTextColor(activityContext); | ||
| setTextColor(positiveButton, POSITIVE, args, needsColorOverride, textColorPrimary); | ||
| setTextColor(negativeButton, NEGATIVE, args, needsColorOverride, textColorPrimary); | ||
| setTextColor(neutralButton, NEUTRAL, args, needsColorOverride, textColorPrimary); | ||
| }; | ||
@@ -144,2 +148,61 @@ } | ||
| } | ||
| public static TimeZone getTimeZone(Bundle args) { | ||
| if (args != null && args.containsKey(RNConstants.ARG_TZOFFSET_MINS)) { | ||
| return new SimpleTimeZone((int)args.getLong(RNConstants.ARG_TZOFFSET_MINS) * 60 * 1000, "GMT"); | ||
| } | ||
| if (args != null && args.containsKey(RNConstants.ARG_TZ_NAME)) { | ||
| String timeZoneName = args.getString(RNConstants.ARG_TZ_NAME); | ||
| if ("GMT".equals(timeZoneName)) { | ||
| return TimeZone.getTimeZone("GMT"); | ||
| } else if (!"GMT".equals(TimeZone.getTimeZone(timeZoneName).getID())) { | ||
| return TimeZone.getTimeZone(timeZoneName); | ||
| } | ||
| RNLog.w(null, "'" + timeZoneName + "' does not exist in TimeZone.getAvailableIDs(). Falling back to TimeZone.getDefault()=" + TimeZone.getDefault().getID()); | ||
| } | ||
| return TimeZone.getDefault(); | ||
| } | ||
| public static long maxDateWithTimeZone(Bundle args) { | ||
| if (!args.containsKey(RNConstants.ARG_MAXDATE)) { | ||
| return Long.MAX_VALUE; | ||
| } | ||
| Calendar maxDate = Calendar.getInstance(getTimeZone(args)); | ||
| maxDate.setTimeInMillis(args.getLong(RNConstants.ARG_MAXDATE)); | ||
| maxDate.set(Calendar.HOUR_OF_DAY, 23); | ||
| maxDate.set(Calendar.MINUTE, 59); | ||
| maxDate.set(Calendar.SECOND, 59); | ||
| maxDate.set(Calendar.MILLISECOND, 999); | ||
| return maxDate.getTimeInMillis(); | ||
| } | ||
| public static long minDateWithTimeZone(Bundle args) { | ||
| if (!args.containsKey(RNConstants.ARG_MINDATE)) { | ||
| return 0; | ||
| } | ||
| Calendar minDate = Calendar.getInstance(getTimeZone(args)); | ||
| minDate.setTimeInMillis(args.getLong(RNConstants.ARG_MINDATE)); | ||
| minDate.set(Calendar.HOUR_OF_DAY, 0); | ||
| minDate.set(Calendar.MINUTE, 0); | ||
| minDate.set(Calendar.SECOND, 0); | ||
| minDate.set(Calendar.MILLISECOND, 0); | ||
| return minDate.getTimeInMillis(); | ||
| } | ||
| public static Bundle createFragmentArguments(ReadableMap options) { | ||
| final Bundle args = new Bundle(); | ||
| if (options.hasKey(RNConstants.ARG_VALUE) && !options.isNull(RNConstants.ARG_VALUE)) { | ||
| args.putLong(RNConstants.ARG_VALUE, (long) options.getDouble(RNConstants.ARG_VALUE)); | ||
| } | ||
| 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)); | ||
| } | ||
| return args; | ||
| } | ||
| } |
@@ -15,2 +15,4 @@ package com.reactcommunity.rndatetimepicker; | ||
| 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 ACTION_DATE_SET = "dateSetAction"; | ||
@@ -17,0 +19,0 @@ public static final String ACTION_TIME_SET = "timeSetAction"; |
@@ -14,15 +14,7 @@ package com.reactcommunity.rndatetimepicker; | ||
| if (args != null && args.containsKey(RNConstants.ARG_VALUE)) { | ||
| set(args.getLong(RNConstants.ARG_VALUE)); | ||
| now.setTimeInMillis((args.getLong(RNConstants.ARG_VALUE))); | ||
| } | ||
| if (args != null && args.containsKey(RNConstants.ARG_TZOFFSET_MINS)) { | ||
| now.setTimeZone(TimeZone.getTimeZone("GMT")); | ||
| Long timeZoneOffsetInMinutesFallback = args.getLong(RNConstants.ARG_TZOFFSET_MINS); | ||
| Integer timeZoneOffsetInMinutes = args.getInt(RNConstants.ARG_TZOFFSET_MINS, timeZoneOffsetInMinutesFallback.intValue()); | ||
| now.add(Calendar.MILLISECOND, timeZoneOffsetInMinutes * 60000); | ||
| } | ||
| } | ||
| public void set(long value) { | ||
| now.setTimeInMillis(value); | ||
| now.setTimeZone(Common.getTimeZone(args)); | ||
| } | ||
@@ -29,0 +21,0 @@ |
@@ -28,2 +28,3 @@ /** | ||
| import androidx.fragment.app.DialogFragment; | ||
| import android.widget.DatePicker; | ||
@@ -33,3 +34,2 @@ | ||
| import java.util.Locale; | ||
| import java.util.TimeZone; | ||
@@ -99,4 +99,2 @@ @SuppressLint("ValidFragment") | ||
| Context activityContext = getActivity(); | ||
| final Calendar c = Calendar.getInstance(); | ||
| DatePickerDialog dialog = getDialog(args, activityContext, mOnDateSetListener); | ||
@@ -114,18 +112,7 @@ | ||
| final DatePicker datePicker = dialog.getDatePicker(); | ||
| final long minDate = Common.minDateWithTimeZone(args); | ||
| final long maxDate = Common.maxDateWithTimeZone(args); | ||
| Integer timeZoneOffsetInMilliseconds = getTimeZoneOffset(args); | ||
| if (timeZoneOffsetInMilliseconds != null) { | ||
| c.setTimeZone(TimeZone.getTimeZone("GMT")); | ||
| } | ||
| if (args != null && args.containsKey(RNConstants.ARG_MINDATE)) { | ||
| // Set minDate to the beginning of the day. We need this because of clowniness in datepicker | ||
| // that causes it to throw an exception if minDate is greater than the internal timestamp | ||
| // that it generates from the y/m/d passed in the constructor. | ||
| c.setTimeInMillis(args.getLong(RNConstants.ARG_MINDATE)); | ||
| c.set(Calendar.HOUR_OF_DAY, 0); | ||
| c.set(Calendar.MINUTE, 0); | ||
| c.set(Calendar.SECOND, 0); | ||
| c.set(Calendar.MILLISECOND, 0); | ||
| datePicker.setMinDate(c.getTimeInMillis() - getOffset(c, timeZoneOffsetInMilliseconds)); | ||
| if (args.containsKey(RNConstants.ARG_MINDATE)) { | ||
| datePicker.setMinDate(minDate); | ||
| } else { | ||
@@ -136,37 +123,25 @@ // This is to work around a bug in DatePickerDialog where it doesn't display a title showing | ||
| } | ||
| if (args != null && args.containsKey(RNConstants.ARG_MAXDATE)) { | ||
| // Set maxDate to the end of the day, same reason as for minDate. | ||
| c.setTimeInMillis(args.getLong(RNConstants.ARG_MAXDATE)); | ||
| c.set(Calendar.HOUR_OF_DAY, 23); | ||
| c.set(Calendar.MINUTE, 59); | ||
| c.set(Calendar.SECOND, 59); | ||
| c.set(Calendar.MILLISECOND, 999); | ||
| datePicker.setMaxDate(c.getTimeInMillis() - getOffset(c, timeZoneOffsetInMilliseconds)); | ||
| if (args.containsKey(RNConstants.ARG_MAXDATE)) { | ||
| datePicker.setMaxDate(maxDate); | ||
| } | ||
| if (args != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O | ||
| && (args.containsKey(RNConstants.ARG_MAXDATE) || args.containsKey(RNConstants.ARG_MINDATE))) { | ||
| datePicker.setOnDateChangedListener(new KeepDateInRangeListener(args)); | ||
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && (args.containsKey(RNConstants.ARG_MAXDATE) || args.containsKey(RNConstants.ARG_MINDATE))) { | ||
| datePicker.setOnDateChangedListener((view, year, monthOfYear, dayOfMonth) -> { | ||
| Calendar calendar = Calendar.getInstance(Common.getTimeZone(args)); | ||
| calendar.set(year, monthOfYear, dayOfMonth, 0, 0, 0); | ||
| long timestamp = Math.min(Math.max(calendar.getTimeInMillis(), minDate), maxDate); | ||
| calendar.setTimeInMillis(timestamp); | ||
| if (datePicker.getYear() != calendar.get(Calendar.YEAR) || datePicker.getMonth() != calendar.get(Calendar.MONTH) || datePicker.getDayOfMonth() != calendar.get(Calendar.DAY_OF_MONTH)) { | ||
| datePicker.updateDate(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), calendar.get(Calendar.DAY_OF_MONTH)); | ||
| } | ||
| }); | ||
| } | ||
| return dialog; | ||
| } | ||
| private static Integer getTimeZoneOffset(Bundle args) { | ||
| if (args != null && args.containsKey(RNConstants.ARG_TZOFFSET_MINS)) { | ||
| long timeZoneOffsetInMinutesFallback = args.getLong(RNConstants.ARG_TZOFFSET_MINS); | ||
| int timeZoneOffsetInMinutes = args.getInt(RNConstants.ARG_TZOFFSET_MINS, (int) timeZoneOffsetInMinutesFallback); | ||
| return timeZoneOffsetInMinutes * 60000; | ||
| if (args.containsKey(RNConstants.ARG_TESTID)) { | ||
| datePicker.setTag(args.getString(RNConstants.ARG_TESTID)); | ||
| } | ||
| return null; | ||
| return dialog; | ||
| } | ||
| private static int getOffset(Calendar c, Integer timeZoneOffsetInMilliseconds) { | ||
| if (timeZoneOffsetInMilliseconds != null) { | ||
| return TimeZone.getDefault().getOffset(c.getTimeInMillis()) - timeZoneOffsetInMilliseconds; | ||
| } | ||
| return 0; | ||
| } | ||
| @Override | ||
@@ -173,0 +148,0 @@ public void onDismiss(@NonNull DialogInterface dialog) { |
| package com.reactcommunity.rndatetimepicker; | ||
| import java.util.Arrays; | ||
| import java.util.Collections; | ||
| import java.util.List; | ||
| import com.facebook.react.ReactPackage; | ||
| import androidx.annotation.Nullable; | ||
| import com.facebook.react.TurboReactPackage; | ||
| import com.facebook.react.bridge.NativeModule; | ||
| import com.facebook.react.bridge.ReactApplicationContext; | ||
| import com.facebook.react.uimanager.ViewManager; | ||
| import com.facebook.react.module.model.ReactModuleInfo; | ||
| import com.facebook.react.module.model.ReactModuleInfoProvider; | ||
| public class RNDateTimePickerPackage implements ReactPackage { | ||
| @Override | ||
| public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) { | ||
| return Arrays.<NativeModule>asList( | ||
| new RNDatePickerDialogModule(reactContext), | ||
| new RNTimePickerDialogModule(reactContext) | ||
| ); | ||
| import java.util.HashMap; | ||
| import java.util.Map; | ||
| public class RNDateTimePickerPackage extends TurboReactPackage { | ||
| @Nullable | ||
| @Override | ||
| public NativeModule getModule(String name, ReactApplicationContext reactContext) { | ||
| if (name.equals(DatePickerModule.NAME)) { | ||
| return new DatePickerModule(reactContext); | ||
| } else if (name.equals(TimePickerModule.NAME)) { | ||
| return new TimePickerModule(reactContext); | ||
| } else { | ||
| return null; | ||
| } | ||
| } | ||
| @Override | ||
| public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) { | ||
| return Collections.emptyList(); | ||
| } | ||
| @Override | ||
| public ReactModuleInfoProvider getReactModuleInfoProvider() { | ||
| return () -> { | ||
| boolean isTurboModule = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED; | ||
| final Map<String, ReactModuleInfo> moduleInfos = new HashMap<>(); | ||
| moduleInfos.put( | ||
| DatePickerModule.NAME, | ||
| new ReactModuleInfo( | ||
| DatePickerModule.NAME, | ||
| DatePickerModule.NAME, | ||
| false, // canOverrideExistingModule | ||
| false, // needsEagerInit | ||
| false, // hasConstants | ||
| false, // isCxxModule | ||
| isTurboModule // isTurboModule | ||
| )); | ||
| moduleInfos.put( | ||
| TimePickerModule.NAME, | ||
| new ReactModuleInfo( | ||
| TimePickerModule.NAME, | ||
| TimePickerModule.NAME, | ||
| false, // canOverrideExistingModule | ||
| false, // needsEagerInit | ||
| false, // hasConstants | ||
| false, // isCxxModule | ||
| isTurboModule // isTurboModule | ||
| )); | ||
| return moduleInfos; | ||
| }; | ||
| } | ||
| } |
@@ -12,4 +12,4 @@ | ||
| #include "RNDateTimePickerState.h" | ||
| #include <react/renderer/components/RNDateTimePicker/EventEmitters.h> | ||
| #include <react/renderer/components/RNDateTimePicker/Props.h> | ||
| #include <react/renderer/components/RNDateTimePickerCGen/EventEmitters.h> | ||
| #include <react/renderer/components/RNDateTimePickerCGen/Props.h> | ||
| #include <react/renderer/components/view/ConcreteViewShadowNode.h> | ||
@@ -16,0 +16,0 @@ #include <jsi/jsi.h> |
@@ -9,5 +9,5 @@ /** | ||
| #import "cpp/react/renderer/components/RNDateTimePicker/ComponentDescriptors.h" | ||
| #import <react/renderer/components/RNDateTimePicker/EventEmitters.h> | ||
| #import <react/renderer/components/RNDateTimePicker/Props.h> | ||
| #import <react/renderer/components/RNDateTimePicker/RCTComponentViewHelpers.h> | ||
| #import <react/renderer/components/RNDateTimePickerCGen/EventEmitters.h> | ||
| #import <react/renderer/components/RNDateTimePickerCGen/Props.h> | ||
| #import <react/renderer/components/RNDateTimePickerCGen/RCTComponentViewHelpers.h> | ||
@@ -40,16 +40,16 @@ #import "RCTFabricComponentsPlugins.h" | ||
| _props = defaultProps; | ||
| _picker = [RNDateTimePicker new]; | ||
| _dummyPicker = [RNDateTimePicker new]; | ||
| [_picker addTarget:self action:@selector(onChange:) forControlEvents:UIControlEventValueChanged]; | ||
| [_picker addTarget:self action:@selector(onDismiss:) forControlEvents:UIControlEventEditingDidEnd]; | ||
| // Default Picker mode | ||
| _picker.datePickerMode = UIDatePickerModeDate; | ||
| _dummyPicker.datePickerMode = UIDatePickerModeDate; | ||
| self.contentView = _picker; | ||
| } | ||
| return self; | ||
@@ -73,3 +73,3 @@ } | ||
| } | ||
| NSTimeInterval timestamp = [sender.date timeIntervalSince1970]; | ||
@@ -80,3 +80,3 @@ RNDateTimePickerEventEmitter::OnChange event = { | ||
| }; | ||
| std::dynamic_pointer_cast<const RNDateTimePickerEventEmitter>(_eventEmitter) | ||
@@ -142,3 +142,3 @@ ->onChange(event); | ||
| _state = std::static_pointer_cast<const RNDateTimePickerShadowNode::ConcreteState>(state); | ||
| if (oldState == nullptr) { | ||
@@ -157,7 +157,7 @@ // Calculate the initial picker measurements | ||
| - (Boolean)updatePropsForPicker:(UIDatePicker *)picker props:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps { | ||
| const auto &oldPickerProps = *std::static_pointer_cast<const RNDateTimePickerProps>(_props); | ||
| const auto &newPickerProps = *std::static_pointer_cast<const RNDateTimePickerProps>(props); | ||
| Boolean needsToUpdateMeasurements = false; | ||
| if (oldPickerProps.date != newPickerProps.date) { | ||
@@ -167,11 +167,11 @@ picker.date = convertJSTimeToDate(newPickerProps.date); | ||
| } | ||
| if (oldPickerProps.minimumDate != newPickerProps.minimumDate) { | ||
| picker.minimumDate = convertJSTimeToDate(newPickerProps.minimumDate); | ||
| } | ||
| if (oldPickerProps.maximumDate != newPickerProps.maximumDate) { | ||
| picker.maximumDate = convertJSTimeToDate(newPickerProps.maximumDate); | ||
| } | ||
| if (oldPickerProps.locale != newPickerProps.locale) { | ||
@@ -184,3 +184,3 @@ NSString *convertedLocale = RCTNSStringFromString(newPickerProps.locale); | ||
| } | ||
| if (oldPickerProps.mode != newPickerProps.mode) { | ||
@@ -202,3 +202,3 @@ switch(newPickerProps.mode) { | ||
| } | ||
| if (@available(iOS 14.0, *)) { | ||
@@ -222,15 +222,32 @@ if (oldPickerProps.displayIOS != newPickerProps.displayIOS) { | ||
| } | ||
| if (oldPickerProps.minuteInterval != newPickerProps.minuteInterval) { | ||
| picker.minuteInterval = newPickerProps.minuteInterval; | ||
| } | ||
| if (oldPickerProps.timeZoneOffsetInMinutes != newPickerProps.timeZoneOffsetInMinutes) { | ||
| // JS standard for time zones is minutes. | ||
| picker.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:newPickerProps.timeZoneOffsetInMinutes * 60.0]; | ||
| needsToUpdateMeasurements = true; | ||
| } | ||
| if (oldPickerProps.timeZoneName != newPickerProps.timeZoneName) { | ||
| NSString *timeZoneName = [NSString stringWithUTF8String:newPickerProps.timeZoneName.c_str()]; | ||
| if ([@"" isEqualToString:timeZoneName]) { | ||
| picker.timeZone = NSTimeZone.localTimeZone; | ||
| } else { | ||
| NSTimeZone *timeZone = [NSTimeZone timeZoneWithName:timeZoneName]; | ||
| if (timeZone != nil) { | ||
| picker.timeZone = timeZone; | ||
| } else { | ||
| RCTLogWarn(@"'%@' does not exist in NSTimeZone.knownTimeZoneNames. Falling back to localTimeZone=%@", timeZoneName, NSTimeZone.localTimeZone.name); | ||
| picker.timeZone = NSTimeZone.localTimeZone; | ||
| } | ||
| } | ||
| needsToUpdateMeasurements = true; | ||
| } | ||
| if (oldPickerProps.accentColor != newPickerProps.accentColor) { | ||
| UIColor *color = RCTUIColorFromSharedColor(newPickerProps.accentColor); | ||
| if (color != nil) { | ||
@@ -246,7 +263,7 @@ [picker setTintColor:color]; | ||
| } | ||
| if (oldPickerProps.textColor != newPickerProps.textColor) { | ||
| [self updateTextColorForPicker:picker color:RCTUIColorFromSharedColor(newPickerProps.textColor)]; | ||
| } | ||
| if (@available(iOS 13.0, *)) { | ||
@@ -266,7 +283,7 @@ if (oldPickerProps.themeVariant != newPickerProps.themeVariant) { | ||
| } | ||
| if (oldPickerProps.enabled != newPickerProps.enabled) { | ||
| picker.enabled = newPickerProps.enabled; | ||
| } | ||
| return needsToUpdateMeasurements; | ||
@@ -279,9 +296,9 @@ } | ||
| Boolean needsToUpdateMeasurements = [self updatePropsForPicker:_dummyPicker props:props oldProps:oldProps]; | ||
| if (needsToUpdateMeasurements) { | ||
| [self updateMeasurements]; | ||
| } | ||
| [self updatePropsForPicker:_picker props:props oldProps:oldProps]; | ||
| [super updateProps:props oldProps:oldProps]; | ||
@@ -288,0 +305,0 @@ } |
@@ -44,3 +44,3 @@ /** | ||
| if (_onChange) { | ||
| _onChange(@{ @"timestamp": @(self.date.timeIntervalSince1970 * 1000.0) }); | ||
| _onChange(@{ @"timestamp": @(self.date.timeIntervalSince1970 * 1000.0), @"utcOffset": @([self.timeZone secondsFromGMTForDate:self.date] / 60 )}); | ||
| } | ||
@@ -47,0 +47,0 @@ } |
@@ -28,2 +28,3 @@ /** | ||
| @"datetime": @(UIDatePickerModeDateAndTime), | ||
| @"countdown": @(UIDatePickerModeCountDownTimer), | ||
| }), UIDatePickerModeTime, integerValue) | ||
@@ -82,3 +83,3 @@ | ||
| { | ||
| RNDateTimePickerShadowView* shadowView = [RNDateTimePickerShadowView new]; | ||
| RNDateTimePickerShadowView* shadowView = [RNDateTimePickerShadowView new]; | ||
| shadowView.picker = _picker; | ||
@@ -109,2 +110,3 @@ return shadowView; | ||
| RCT_EXPORT_SHADOW_PROPERTY(displayIOS, RNCUIDatePickerStyle) | ||
| RCT_EXPORT_SHADOW_PROPERTY(timeZoneName, NSString) | ||
@@ -184,2 +186,17 @@ RCT_EXPORT_VIEW_PROPERTY(date, NSDate) | ||
| RCT_CUSTOM_VIEW_PROPERTY(timeZoneName, NSString, RNDateTimePicker) | ||
| { | ||
| if (json) { | ||
| NSTimeZone *timeZone = [NSTimeZone timeZoneWithName:json]; | ||
| if (timeZone == nil) { | ||
| RCTLogWarn(@"'%@' does not exist in NSTimeZone.knownTimeZoneNames fallback to localTimeZone=%@", json, NSTimeZone.localTimeZone.name); | ||
| view.timeZone = NSTimeZone.localTimeZone; | ||
| } else { | ||
| view.timeZone = timeZone; | ||
| } | ||
| } else { | ||
| view.timeZone = NSTimeZone.localTimeZone; | ||
| } | ||
| } | ||
| @end |
| #import <React/RCTShadowView.h> | ||
| #import <React/RCTLog.h> | ||
| #import "RNDateTimePicker.h" | ||
@@ -10,4 +11,6 @@ | ||
| @property (nullable, nonatomic, strong) NSLocale *locale; | ||
| @property (nonatomic, assign) NSInteger timeZoneOffsetInMinutes; | ||
| @property (nullable, nonatomic, strong) NSString *timeZoneName; | ||
| @property (nonatomic, assign) UIDatePickerStyle displayIOS API_AVAILABLE(ios(13.4)); | ||
| @end |
@@ -34,2 +34,12 @@ #import "RNDateTimePickerShadowView.h" | ||
| - (void)setTimeZoneOffsetInMinutes:(NSInteger)timeZoneOffsetInMinutes { | ||
| _timeZoneOffsetInMinutes = timeZoneOffsetInMinutes; | ||
| YGNodeMarkDirty(self.yogaNode); | ||
| } | ||
| - (void)setTimeZoneName:(NSString *)timeZoneName { | ||
| _timeZoneName = timeZoneName; | ||
| YGNodeMarkDirty(self.yogaNode); | ||
| } | ||
| static YGSize RNDateTimePickerShadowViewMeasure(YGNodeRef node, float width, YGMeasureMode widthMode, float height, YGMeasureMode heightMode) | ||
@@ -44,9 +54,24 @@ { | ||
| [shadowPickerView.picker setLocale:shadowPickerView.locale]; | ||
| [shadowPickerView.picker setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:shadowPickerView.timeZoneOffsetInMinutes * 60]]; | ||
| if (shadowPickerView.timeZoneName) { | ||
| NSTimeZone *timeZone = [NSTimeZone timeZoneWithName:shadowPickerView.timeZoneName]; | ||
| if (timeZone != nil) { | ||
| [shadowPickerView.picker setTimeZone:timeZone]; | ||
| } else { | ||
| RCTLogWarn(@"'%@' does not exist in NSTimeZone.knownTimeZoneNames. Falling back to localTimeZone=%@", shadowPickerView.timeZoneName, NSTimeZone.localTimeZone.name); | ||
| [shadowPickerView.picker setTimeZone:NSTimeZone.localTimeZone]; | ||
| } | ||
| } else { | ||
| [shadowPickerView.picker setTimeZone:NSTimeZone.localTimeZone]; | ||
| } | ||
| if (@available(iOS 14.0, *)) { | ||
| [shadowPickerView.picker setPreferredDatePickerStyle:shadowPickerView.displayIOS]; | ||
| } | ||
| size = [shadowPickerView.picker sizeThatFits:UILayoutFittingCompressedSize]; | ||
| size.width += 10; | ||
| size = [shadowPickerView.picker sizeThatFits:UILayoutFittingCompressedSize]; | ||
| size.width += 10; | ||
| }); | ||
| return (YGSize){ | ||
@@ -53,0 +78,0 @@ RCTYogaFloatFromCoreGraphicsFloat(size.width), |
+3
-10
@@ -15,15 +15,8 @@ /** | ||
| const pickedDate = new Date(timestampFromPickerValueProp); | ||
| pickedDate.setFullYear( | ||
| datePickedByUser.getFullYear(), | ||
| datePickedByUser.getMonth(), | ||
| datePickedByUser.getDate(), | ||
| ); | ||
| pickedDate.setTime(datePickedByUser.getTime()); | ||
| return { | ||
| action: DATE_SET_ACTION, | ||
| year: pickedDate.getFullYear(), | ||
| month: pickedDate.getMonth(), | ||
| day: pickedDate.getDate(), | ||
| hour: pickedDate.getHours(), | ||
| minute: pickedDate.getMinutes(), | ||
| timestamp: pickedDate.getTime(), | ||
| utcOffset: 0, | ||
| }; | ||
@@ -30,0 +23,0 @@ } |
+23
-69
| { | ||
| "name": "@react-native-community/datetimepicker", | ||
| "version": "6.7.1", | ||
| "version": "7.6.2", | ||
| "description": "DateTimePicker component for React Native", | ||
@@ -32,3 +32,3 @@ "main": "./src/index.js", | ||
| "test": "jest", | ||
| "lint": "eslint {example,src,test}/**/*.js src/index.d.ts", | ||
| "lint": "NODE_ENV=lint eslint {example,src,test}/**/*.js src/index.d.ts", | ||
| "flow": "flow check", | ||
@@ -71,11 +71,10 @@ "detox:ios:build:debug": "detox build -c ios.sim.debug", | ||
| "devDependencies": { | ||
| "@babel/core": "^7.19.0", | ||
| "@babel/runtime": "^7.19.0", | ||
| "@react-native-community/eslint-config": "^3.1.0", | ||
| "@react-native-segmented-control/segmented-control": "^2.4.0", | ||
| "@react-native/eslint-config": "^0.72.2", | ||
| "@react-native/metro-config": "^0.73.2", | ||
| "@semantic-release/git": "^10.0.1", | ||
| "@testing-library/react-native": "^9.0.0", | ||
| "babel-jest": "^29.0.2", | ||
| "detox": "19.12.0", | ||
| "eslint": "^8.23.0", | ||
| "@testing-library/react-native": "9.1.0", | ||
| "babel-jest": "^29.5.0", | ||
| "detox": "^20.9.1", | ||
| "eslint": "^8.42.0", | ||
| "eslint-plugin-ft-flow": "^2.0.1", | ||
@@ -85,16 +84,14 @@ "eslint-plugin-prettier": "^4.2.1", | ||
| "flow-typed": "^3.8.0", | ||
| "jest": "^29.0.2", | ||
| "jest-circus": "^29.0.2", | ||
| "metro-babel-register": "^0.72.2", | ||
| "metro-react-native-babel-preset": "^0.72.2", | ||
| "jest": "^29.5.0", | ||
| "moment": "^2.24.0", | ||
| "moment-timezone": "^0.5.41", | ||
| "patch-package": "^6.4.7", | ||
| "postinstall-postinstall": "^2.1.0", | ||
| "prettier": "^2.6.0", | ||
| "react": "18.1.0", | ||
| "react-native": "^0.70.0", | ||
| "react-native-localize": "^2.2.0", | ||
| "react-native-test-app": "^1.6.19", | ||
| "react-native-windows": "^0.70.0-preview.2", | ||
| "react-test-renderer": "18.1.0", | ||
| "prettier": "^2.8.8", | ||
| "react": "18.2.0", | ||
| "react-native": "^0.72.7", | ||
| "react-native-localize": "^3.0.3", | ||
| "react-native-test-app": "^2.5.32", | ||
| "react-native-windows": "^0.71.3", | ||
| "react-test-renderer": "18.2.0", | ||
| "semantic-release": "^19.0.3" | ||
@@ -105,53 +102,10 @@ }, | ||
| }, | ||
| "detox": { | ||
| "test-runner": "jest", | ||
| "runner-config": "example/e2e/config.json", | ||
| "configurations": { | ||
| "ios.sim.debug": { | ||
| "binaryPath": "example/ios/build/Build/Products/Debug-iphonesimulator/ReactTestApp.app", | ||
| "build": "export RCT_NO_LAUNCH_PACKAGER=true && xcodebuild -workspace example/ios/date-time-picker-example.xcworkspace -destination 'platform=iOS Simulator,name=iPhone 13' -scheme date-time-picker-example -configuration Debug -derivedDataPath example/ios/build -UseModernBuildSystem=YES", | ||
| "type": "ios.simulator", | ||
| "device": { | ||
| "type": "iPhone 13" | ||
| } | ||
| }, | ||
| "ios.sim.release": { | ||
| "binaryPath": "example/ios/build/Build/Products/Release-iphonesimulator/ReactTestApp.app", | ||
| "build": "export RCT_NO_LAUNCH_PACKAGER=true && xcodebuild -workspace example/ios/date-time-picker-example.xcworkspace -sdk iphonesimulator -scheme date-time-picker-example -configuration Release -derivedDataPath example/ios/build -UseModernBuildSystem=YES", | ||
| "type": "ios.simulator", | ||
| "device": { | ||
| "type": "iPhone 13" | ||
| } | ||
| }, | ||
| "android.emu.debug": { | ||
| "binaryPath": "example/android/app/build/outputs/apk/debug/app-debug.apk", | ||
| "build": "export RCT_NO_LAUNCH_PACKAGER=true && (cd example/android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug)", | ||
| "type": "android.emulator", | ||
| "device": { | ||
| "avdName": "Pixel_5_Android_12_api_31" | ||
| } | ||
| }, | ||
| "android.device.debug": { | ||
| "binaryPath": "example/android/app/build/outputs/apk/debug/app-debug.apk", | ||
| "build": "export RCT_NO_LAUNCH_PACKAGER=true && (cd example/android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug)", | ||
| "type": "android.attached", | ||
| "device": { | ||
| "adbName": "34HDU19716000753" | ||
| } | ||
| }, | ||
| "android.emu.release": { | ||
| "binaryPath": "example/android/app/build/outputs/apk/release/app-release.apk", | ||
| "build": "export RCT_NO_LAUNCH_PACKAGER=true && (cd example/android && ./gradlew assembleRelease assembleAndroidTest -DtestBuildType=release)", | ||
| "type": "android.emulator", | ||
| "device": { | ||
| "avdName": "TestingAVD" | ||
| } | ||
| } | ||
| "codegenConfig": { | ||
| "name": "RNDateTimePickerCGen", | ||
| "type": "all", | ||
| "jsSrcsDir": "src/specs", | ||
| "android": { | ||
| "javaPackageName": "com.reactcommunity.rndatetimepicker" | ||
| } | ||
| }, | ||
| "codegenConfig": { | ||
| "name": "RNDateTimePicker", | ||
| "type": "components", | ||
| "jsSrcsDir": "src/specs" | ||
| } | ||
| } |
+60
-19
@@ -1,8 +0,8 @@ | ||
| ### 🚧🚧 Looking for collaborators and backers 🚧🚧 | ||
| # 🚧🚧 Looking for collaborators and financial backers 🚧🚧 | ||
| See this [issue](https://github.com/react-native-datetimepicker/datetimepicker/issues/313). | ||
| Please support maintenance of the module with a monthly donation or help us with issues and pull requests. | ||
| ### Backers | ||
| [Become a backer on OpenCollective](https://opencollective.com/react-native-datetimepicker) or [sponsor us on GitHub Sponsors](https://github.com/sponsors/react-native-datetimepicker). | ||
| Support us with a monthly donation and help us continue our activities. [Become a backer on OpenCollective](https://opencollective.com/react-native-datetimepicker) or [sponsor us on GitHub Sponsors](https://github.com/sponsors/react-native-datetimepicker). | ||
| See this [issue](https://github.com/react-native-datetimepicker/datetimepicker/issues/313) for context. Thank you! | ||
@@ -13,4 +13,21 @@ <a href="https://opencollective.com/react-native-datetimepicker/donate" target="_blank"> | ||
| # React Native DateTimePicker | ||
| <br> | ||
| <br> | ||
| <br> | ||
| <br> | ||
| <br> | ||
| <br> | ||
| <br> | ||
| <br> | ||
| <br> | ||
| <br> | ||
| <br> | ||
| <br> | ||
| <br> | ||
| <br> | ||
| --- | ||
| ## React Native DateTimePicker | ||
| This repository was moved out of the react native community GH organization, in accordance to [this proposal](https://github.com/react-native-community/discussions-and-proposals/issues/176). | ||
@@ -24,3 +41,3 @@ The module is still published on `npm` under the old namespace (as documented) but will be published under a new namespace at some point, with a major version bump. | ||
| React Native date & time picker component for iOS, Android and Windows. | ||
| React Native date & time picker component for iOS, Android and Windows (please note Windows is not actively maintained). | ||
@@ -71,2 +88,3 @@ ## Screenshots | ||
| - [`minimumDate` (`optional`)](#minimumdate-optional) | ||
| - [`timeZoneName` (`optional`, `iOS or Android only`)](#timeZoneName-optional-ios-and-android-only) | ||
| - [`timeZoneOffsetInMinutes` (`optional`, `iOS or Android only`)](#timezoneoffsetinminutes-optional-ios-and-android-only) | ||
@@ -99,4 +117,6 @@ - [`timeZoneOffsetInSeconds` (`optional`, `Windows only`)](#timezoneoffsetinsecond-optional-windows-only) | ||
| - Only Android API level >=21 (Android 5), iOS >= 11 are supported. | ||
| - Tested with Xcode 14.0 and RN 0.70. Other configurations are very likely to work as well but have not been tested. | ||
| - Tested with Xcode 14.0 and RN 0.72.7. Other configurations are very likely to work as well but have not been tested. | ||
| The module supports the [new React Native architecture](https://reactnative.dev/docs/next/the-new-architecture/why) (Fabric rendering of iOS components, and turbomodules on Android). If you are using the new architecture, you will need to use React Native 0.71.4 or higher. | ||
| ## Expo users notice | ||
@@ -107,2 +127,4 @@ | ||
| If you're using a [Dev Client](https://docs.expo.dev/development/create-development-builds/), rebuild the Dev Client after installing the dependencies. | ||
| If you're using the [`expo prebuild`](https://docs.expo.dev/workflow/prebuild/) command and building your native app projects (e.g. with EAS Build or locally), you can use the latest version of the module. | ||
@@ -172,7 +194,7 @@ | ||
| return ( | ||
| <View> | ||
| <SafeAreaView> | ||
| <Button onPress={showDatepicker} title="Show date picker!" /> | ||
| <Button onPress={showTimepicker} title="Show time picker!" /> | ||
| <Text>selected: {date.toLocaleString()}</Text> | ||
| </View> | ||
| </SafeAreaView> | ||
| ); | ||
@@ -197,6 +219,3 @@ }; | ||
| const showMode = (currentMode) => { | ||
| if (Platform.OS === 'android') { | ||
| setShow(false); | ||
| // for iOS, add a button that closes the picker | ||
| } | ||
| setShow(true); | ||
| setMode(currentMode); | ||
@@ -214,3 +233,3 @@ }; | ||
| return ( | ||
| <View> | ||
| <SafeAreaView> | ||
| <Button onPress={showDatepicker} title="Show date picker!" /> | ||
@@ -228,3 +247,3 @@ <Button onPress={showTimepicker} title="Show time picker!" /> | ||
| )} | ||
| </View> | ||
| </SafeAreaView> | ||
| ); | ||
@@ -320,2 +339,4 @@ }; | ||
| The `utcOffset` field is only available on Android and iOS. It is the offset in minutes between the selected date and UTC time. | ||
| ```js | ||
@@ -325,3 +346,3 @@ const setDate = (event: DateTimePickerEvent, date: Date) => { | ||
| type, | ||
| nativeEvent: {timestamp}, | ||
| nativeEvent: {timestamp, utcOffset}, | ||
| } = event; | ||
@@ -357,7 +378,18 @@ }; | ||
| #### `timeZoneName` (`optional`, `iOS and Android only`) | ||
| Allows changing of the time zone of the date picker. By default, it uses the device's time zone. | ||
| Use the time zone name from the IANA (TZDB) database name in https://en.wikipedia.org/wiki/List_of_tz_database_time_zones. | ||
| ```js | ||
| <RNDateTimePicker timeZoneName={'Europe/Prague'} /> | ||
| ``` | ||
| #### `timeZoneOffsetInMinutes` (`optional`, `iOS and Android only`) | ||
| Allows changing of the timeZone of the date picker. By default, it uses the device's time zone. | ||
| We strongly recommend avoiding this prop on android because of known issues in the implementation (eg. [#528](https://github.com/react-native-datetimepicker/datetimepicker/issues/528)). | ||
| Allows changing of the time zone of the date picker. By default, it uses the device's time zone. | ||
| We **strongly** recommend using `timeZoneName` prop instead; this prop has known issues in the android implementation (eg. [#528](https://github.com/react-native-datetimepicker/datetimepicker/issues/528)). | ||
| This prop will be removed in a future release. | ||
| ```js | ||
@@ -488,3 +520,3 @@ // GMT+1 | ||
| ```js | ||
| <RNDateTimePicker positiveButtonLabel="Negative" /> | ||
| <RNDateTimePicker negativeButtonLabel="Negative" /> | ||
| ``` | ||
@@ -532,2 +564,11 @@ | ||
| #### `testID` (`optional`) | ||
| Usually used by app automation frameworks. | ||
| Fully supported on iOS. On Android, only supported for `mode="date"`. | ||
| ```js | ||
| <RNDateTimePicker testID="datePicker" /> | ||
| ``` | ||
| #### `View Props` (`optional`, `iOS only`) | ||
@@ -534,0 +575,0 @@ |
+15
-20
@@ -5,3 +5,3 @@ /** | ||
| */ | ||
| import {ANDROID_DISPLAY, ANDROID_MODE, MIN_MS} from './constants'; | ||
| import {ANDROID_DISPLAY, ANDROID_MODE} from './constants'; | ||
| import pickers from './picker'; | ||
@@ -20,3 +20,3 @@ import type {AndroidNativeProps, DateTimePickerResult} from './types'; | ||
| type Params = { | ||
| type OpenParams = { | ||
| value: Timestamp, | ||
@@ -29,2 +29,4 @@ display: AndroidNativeProps['display'], | ||
| timeZoneOffsetInMinutes: AndroidNativeProps['timeZoneOffsetInMinutes'], | ||
| timeZoneName: AndroidNativeProps['timeZoneName'], | ||
| testID: AndroidNativeProps['testID'], | ||
| dialogButtons: { | ||
@@ -37,3 +39,4 @@ positive: ProcessedButton, | ||
| export type PresentPickerCallback = (Params) => Promise<DateTimePickerResult>; | ||
| export type PresentPickerCallback = | ||
| (OpenParams) => Promise<DateTimePickerResult>; | ||
@@ -51,4 +54,5 @@ function getOpenPicker( | ||
| timeZoneOffsetInMinutes, | ||
| timeZoneName, | ||
| dialogButtons, | ||
| }: Params) => | ||
| }: OpenParams) => | ||
| // $FlowFixMe - `AbstractComponent` [1] is not an instance type. | ||
@@ -61,2 +65,3 @@ pickers[mode].open({ | ||
| timeZoneOffsetInMinutes, | ||
| timeZoneName, | ||
| dialogButtons, | ||
@@ -71,4 +76,6 @@ }); | ||
| timeZoneOffsetInMinutes, | ||
| timeZoneName, | ||
| dialogButtons, | ||
| }: Params) => | ||
| testID, | ||
| }: OpenParams) => | ||
| // $FlowFixMe - `AbstractComponent` [1] is not an instance type. | ||
@@ -81,3 +88,5 @@ pickers[ANDROID_MODE.date].open({ | ||
| timeZoneOffsetInMinutes, | ||
| timeZoneName, | ||
| dialogButtons, | ||
| testID, | ||
| }); | ||
@@ -87,16 +96,2 @@ } | ||
| function timeZoneOffsetDateSetter( | ||
| date: Date, | ||
| timeZoneOffsetInMinutes: ?number, | ||
| ): Date { | ||
| if (typeof timeZoneOffsetInMinutes === 'number') { | ||
| // FIXME this causes a bug. repro: set tz offset to zero, and then keep opening and closing the calendar picker | ||
| // https://github.com/react-native-datetimepicker/datetimepicker/issues/528 | ||
| const offset = date.getTimezoneOffset() + timeZoneOffsetInMinutes; | ||
| const shiftedDate = new Date(date.getTime() - offset * MIN_MS); | ||
| return shiftedDate; | ||
| } | ||
| return date; | ||
| } | ||
| function validateAndroidProps(props: AndroidNativeProps) { | ||
@@ -121,2 +116,2 @@ sharedPropsValidation({value: props?.value}); | ||
| } | ||
| export {getOpenPicker, timeZoneOffsetDateSetter, validateAndroidProps}; | ||
| export {getOpenPicker, validateAndroidProps}; |
@@ -11,5 +11,4 @@ /** | ||
| import {DATE_SET_ACTION, DISMISS_ACTION, ANDROID_DISPLAY} from './constants'; | ||
| import {NativeModules} from 'react-native'; | ||
| import {toMilliseconds} from './utils'; | ||
| import RNDatePickerAndroid from './specs/NativeModuleDatePicker'; | ||
| import type {DatePickerOptions, DateTimePickerResult} from './types'; | ||
@@ -26,2 +25,3 @@ | ||
| * - `maximumDate` (`Date` object) - maximum date that can be selected | ||
| * - `testID` (`string`) - Sets view tag for use with automation frameworks | ||
| * - `display` (`enum('calendar', 'spinner', 'default')`) - To set the date-picker display to calendar/spinner/default | ||
@@ -44,7 +44,7 @@ * - 'calendar': Show a date picker in calendar mode. | ||
| return NativeModules.RNDatePickerAndroid.open(options); | ||
| return RNDatePickerAndroid.open(options); | ||
| } | ||
| static async dismiss(): Promise<boolean> { | ||
| return NativeModules.RNDatePickerAndroid.dismiss(); | ||
| return RNDatePickerAndroid.dismiss(); | ||
| } | ||
@@ -51,0 +51,0 @@ |
@@ -27,2 +27,3 @@ /** | ||
| timeZoneOffsetInMinutes, | ||
| timeZoneName, | ||
| positiveButton, | ||
@@ -34,2 +35,3 @@ negativeButton, | ||
| neutralButtonLabel, | ||
| testID, | ||
| } = props; | ||
@@ -55,2 +57,3 @@ const valueTimestamp = value.getTime(); | ||
| timeZoneOffsetInMinutes, | ||
| timeZoneName, | ||
| onError, | ||
@@ -64,2 +67,3 @@ onChange, | ||
| neutralButtonLabel, | ||
| testID, | ||
| }; | ||
@@ -66,0 +70,0 @@ DateTimePickerAndroid.open(params); |
@@ -53,2 +53,3 @@ /** | ||
| timeZoneOffsetInMinutes, | ||
| timeZoneName, | ||
| textColor, | ||
@@ -63,3 +64,3 @@ accentColor, | ||
| }: IOSNativeProps): React.Node { | ||
| sharedPropsValidation({value}); | ||
| sharedPropsValidation({value, timeZoneOffsetInMinutes, timeZoneName}); | ||
@@ -88,2 +89,3 @@ const display = getDisplaySafe(providedDisplay); | ||
| timestamp: value.getTime(), | ||
| utcOffset: 0, // TODO vonovak - the dismiss event should not carry any date information | ||
| }, | ||
@@ -105,2 +107,3 @@ }, | ||
| timeZoneOffsetInMinutes={timeZoneOffsetInMinutes} | ||
| timeZoneName={timeZoneName} | ||
| onChange={_onChange} | ||
@@ -107,0 +110,0 @@ onPickerDismiss={onDismiss} |
@@ -42,2 +42,3 @@ /** | ||
| const localProps = { | ||
| accessibilityLabel: props.accessibilityLabel, | ||
| dayOfWeekFormat: props.dayOfWeekFormat, | ||
@@ -58,3 +59,7 @@ dateFormat: props.dateFormat, | ||
| ...event, | ||
| nativeEvent: {...event.nativeEvent, timestamp: event.nativeEvent.newDate}, | ||
| nativeEvent: { | ||
| ...event.nativeEvent, | ||
| timestamp: event.nativeEvent.newDate, | ||
| utcOffset: 0, | ||
| }, | ||
| type: EVENT_TYPE_SET, | ||
@@ -61,0 +66,0 @@ }; |
@@ -16,7 +16,3 @@ /** | ||
| import type {AndroidNativeProps} from './types'; | ||
| import { | ||
| getOpenPicker, | ||
| timeZoneOffsetDateSetter, | ||
| validateAndroidProps, | ||
| } from './androidUtils'; | ||
| import {getOpenPicker, validateAndroidProps} from './androidUtils'; | ||
| import pickers from './picker'; | ||
@@ -40,2 +36,3 @@ import { | ||
| timeZoneOffsetInMinutes, | ||
| timeZoneName, | ||
| onChange, | ||
@@ -49,2 +46,3 @@ onError, | ||
| negativeButtonLabel, | ||
| testID, | ||
| } = props; | ||
@@ -81,3 +79,3 @@ validateAndroidProps(props); | ||
| : ANDROID_DISPLAY.default; | ||
| const {action, day, month, year, minute, hour} = await openPicker({ | ||
| const {action, timestamp, utcOffset} = await openPicker({ | ||
| value: valueTimestamp, | ||
@@ -90,20 +88,12 @@ display: displayOverride, | ||
| timeZoneOffsetInMinutes, | ||
| timeZoneName, | ||
| dialogButtons, | ||
| testID, | ||
| }); | ||
| switch (action) { | ||
| case DATE_SET_ACTION: { | ||
| let date = new Date(valueTimestamp); | ||
| date.setFullYear(year, month, day); | ||
| date = timeZoneOffsetDateSetter(date, timeZoneOffsetInMinutes); | ||
| const [event] = createDateTimeSetEvtParams(date); | ||
| onChange?.(event, date); | ||
| break; | ||
| } | ||
| case DATE_SET_ACTION: | ||
| case TIME_SET_ACTION: { | ||
| let date = new Date(valueTimestamp); | ||
| date.setHours(hour, minute); | ||
| date = timeZoneOffsetDateSetter(date, timeZoneOffsetInMinutes); | ||
| const [event] = createDateTimeSetEvtParams(date); | ||
| const date = new Date(timestamp); | ||
| const [event] = createDateTimeSetEvtParams(date, utcOffset); | ||
| onChange?.(event, date); | ||
@@ -114,3 +104,3 @@ break; | ||
| case NEUTRAL_BUTTON_ACTION: { | ||
| const [event] = createNeutralEvtParams(originalValue); | ||
| const [event] = createNeutralEvtParams(originalValue, utcOffset); | ||
| onChange?.(event, originalValue); | ||
@@ -121,3 +111,3 @@ break; | ||
| default: { | ||
| const [event] = createDismissEvtParams(originalValue); | ||
| const [event] = createDismissEvtParams(originalValue, utcOffset); | ||
| onChange?.(event, originalValue); | ||
@@ -124,0 +114,0 @@ break; |
@@ -9,2 +9,3 @@ /** | ||
| date: Date, | ||
| utcOffset: number, | ||
| ): [DateTimePickerEvent, Date] => { | ||
@@ -16,2 +17,3 @@ return [ | ||
| timestamp: date.getTime(), | ||
| utcOffset, | ||
| }, | ||
@@ -25,2 +27,3 @@ }, | ||
| date: Date, | ||
| utcOffset: number, | ||
| ): [DateTimePickerEvent, Date] => { | ||
@@ -32,2 +35,3 @@ return [ | ||
| timestamp: date.getTime(), | ||
| utcOffset, | ||
| }, | ||
@@ -41,2 +45,3 @@ }, | ||
| date: Date, | ||
| utcOffset: number, | ||
| ): [DateTimePickerEvent, Date] => { | ||
@@ -48,2 +53,3 @@ return [ | ||
| timestamp: date.getTime(), | ||
| utcOffset, | ||
| }, | ||
@@ -50,0 +56,0 @@ }, |
+12
-2
@@ -22,3 +22,4 @@ import {FC, Ref, SyntheticEvent} from 'react'; | ||
| nativeEvent: { | ||
| timestamp?: number; | ||
| timestamp: number; | ||
| utcOffset: number; | ||
| }; | ||
@@ -67,3 +68,11 @@ }; | ||
| export type BaseProps = Readonly<Omit<ViewProps, 'children'> & DateOptions>; | ||
| export type BaseProps = Readonly< | ||
| Omit<ViewProps, 'children'> & | ||
| DateOptions & { | ||
| /** | ||
| * The tz database name in https://en.wikipedia.org/wiki/List_of_tz_database_time_zones | ||
| */ | ||
| timeZoneName?: string; | ||
| } | ||
| >; | ||
@@ -207,2 +216,3 @@ export type IOSNativeProps = Readonly< | ||
| minuteInterval?: number; | ||
| accessibilityLabel?: string; | ||
| } | ||
@@ -209,0 +219,0 @@ >; |
@@ -10,4 +10,4 @@ /** | ||
| */ | ||
| import RNDateTimePicker from './specs/DateTimePickerNativeComponent'; | ||
| import RNDateTimePicker from './specs/NativeComponentDateTimePicker'; | ||
| export default RNDateTimePicker; |
@@ -11,5 +11,4 @@ /** | ||
| import {TIME_SET_ACTION, DISMISS_ACTION, ANDROID_DISPLAY} from './constants'; | ||
| import {NativeModules} from 'react-native'; | ||
| import {toMilliseconds} from './utils'; | ||
| import RNTimePickerAndroid from './specs/NativeModuleTimePicker'; | ||
| import type {TimePickerOptions, DateTimePickerResult} from './types'; | ||
@@ -40,8 +39,7 @@ | ||
| options.display = options.display || ANDROID_DISPLAY.default; | ||
| return NativeModules.RNTimePickerAndroid.open(options); | ||
| return RNTimePickerAndroid.open(options); | ||
| } | ||
| static async dismiss(): Promise<boolean> { | ||
| return NativeModules.RNTimePickerAndroid.dismiss(); | ||
| return RNTimePickerAndroid.dismiss(); | ||
| } | ||
@@ -48,0 +46,0 @@ |
+14
-6
@@ -32,2 +32,3 @@ /** | ||
| timestamp: number, | ||
| utcOffset: number, | ||
| |}>, | ||
@@ -39,3 +40,4 @@ >; | ||
| nativeEvent: $ReadOnly<{ | ||
| timestamp?: number, | ||
| timestamp: number, | ||
| utcOffset: number, | ||
| ... | ||
@@ -97,2 +99,9 @@ }>, | ||
| ...DateOptions, | ||
| /** | ||
| * Timezone in database name. | ||
| * | ||
| * By default, the date picker will use the device's timezone. With this | ||
| * parameter, it is possible to force a certain timezone based on IANA | ||
| */ | ||
| timeZoneName?: ?string, | ||
| |}>; | ||
@@ -181,2 +190,3 @@ | ||
| timeZoneOffsetInMinutes?: ?number, | ||
| /** | ||
@@ -218,7 +228,4 @@ * The interval at which minutes can be selected. | ||
| action: 'timeSetAction' | 'dateSetAction' | 'dismissedAction', | ||
| year: number, | ||
| month: number, | ||
| day: number, | ||
| hour: number, | ||
| minute: number, | ||
| timestamp: number, | ||
| utcOffset: number, | ||
| |}>; | ||
@@ -255,2 +262,3 @@ | ||
| minuteInterval?: number, | ||
| accessibilityLabel?: string, | ||
| |}>; |
+18
-1
@@ -33,3 +33,11 @@ /** | ||
| export function sharedPropsValidation({value}: {value: ?Date}) { | ||
| export function sharedPropsValidation({ | ||
| value, | ||
| timeZoneName, | ||
| timeZoneOffsetInMinutes, | ||
| }: { | ||
| value: Date, | ||
| timeZoneName?: ?string, | ||
| timeZoneOffsetInMinutes?: ?number, | ||
| }) { | ||
| invariant(value, 'A date or time must be specified as `value` prop'); | ||
@@ -40,2 +48,11 @@ invariant( | ||
| ); | ||
| invariant( | ||
| timeZoneName == null || timeZoneOffsetInMinutes == null, | ||
| '`timeZoneName` and `timeZoneOffsetInMinutes` cannot be specified at the same time', | ||
| ); | ||
| if (timeZoneOffsetInMinutes !== undefined) { | ||
| console.warn( | ||
| '`timeZoneOffsetInMinutes` is deprecated and will be removed in a future release. Use `timeZoneName` instead.', | ||
| ); | ||
| } | ||
| } |
@@ -113,2 +113,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. | ||
| } | ||
| else if (propertyName == "accessibilityLabel") { | ||
| if (!propertyValue.IsNull()) { | ||
| this->Name(to_hstring(propertyValue.AsString())); | ||
| } | ||
| } | ||
| } | ||
@@ -115,0 +120,0 @@ |
@@ -42,2 +42,3 @@ // Copyright (c) Microsoft Corporation. All rights reserved. | ||
| nativeProps.Insert(L"accessibilityLabel", ViewManagerPropertyType::String); | ||
| nativeProps.Insert(L"dayOfWeekFormat", ViewManagerPropertyType::String); | ||
@@ -44,0 +45,0 @@ nativeProps.Insert(L"dateFormat", ViewManagerPropertyType::String); |
| package com.reactcommunity.rndatetimepicker; | ||
| import android.os.Bundle; | ||
| import android.widget.DatePicker; | ||
| import androidx.annotation.NonNull; | ||
| import java.util.Calendar; | ||
| // fix for https://issuetracker.google.com/issues/169602180 | ||
| // TODO revisit day, month, year with timezoneoffset fixes | ||
| public class KeepDateInRangeListener implements DatePicker.OnDateChangedListener { | ||
| private final Bundle args; | ||
| public KeepDateInRangeListener(@NonNull Bundle args) { | ||
| this.args = args; | ||
| } | ||
| @Override | ||
| public void onDateChanged(DatePicker view, int year, int month, int day) { | ||
| fixPotentialMaxDateBug(view, year, month, day); | ||
| fixPotentialMinDateBug(view, year, month, day); | ||
| } | ||
| private void fixPotentialMaxDateBug(DatePicker datePicker, int year, int month, int day) { | ||
| if (!isDateAfterMaxDate(args, year, month, day)) { | ||
| return; | ||
| } | ||
| Calendar maxDate = Calendar.getInstance(); | ||
| maxDate.setTimeInMillis(args.getLong(RNConstants.ARG_MAXDATE)); | ||
| datePicker.updateDate(maxDate.get(Calendar.YEAR), maxDate.get(Calendar.MONTH), maxDate.get(Calendar.DAY_OF_MONTH)); | ||
| } | ||
| private void fixPotentialMinDateBug(DatePicker datePicker, int year, int month, int day) { | ||
| if (!isDateBeforeMinDate(args, year, month, day)) { | ||
| return; | ||
| } | ||
| Calendar c = Calendar.getInstance(); | ||
| c.setTimeInMillis(args.getLong(RNConstants.ARG_MINDATE)); | ||
| datePicker.updateDate(c.get(Calendar.YEAR), c.get(Calendar.MONTH), c.get(Calendar.DAY_OF_MONTH)); | ||
| } | ||
| public static boolean isDateAfterMaxDate(Bundle args, int year, int month, int day) { | ||
| if (!args.containsKey(RNConstants.ARG_MAXDATE)) { | ||
| return false; | ||
| } | ||
| Calendar maxDate = Calendar.getInstance(); | ||
| maxDate.setTimeInMillis(args.getLong(RNConstants.ARG_MAXDATE)); | ||
| return (year > maxDate.get(Calendar.YEAR) || | ||
| (year == maxDate.get(Calendar.YEAR) && month > maxDate.get(Calendar.MONTH)) || | ||
| (year == maxDate.get(Calendar.YEAR) && month == maxDate.get(Calendar.MONTH) && day > maxDate.get(Calendar.DAY_OF_MONTH))); | ||
| } | ||
| public static boolean isDateBeforeMinDate(Bundle args, int year, int month, int day) { | ||
| if (!args.containsKey(RNConstants.ARG_MINDATE)) { | ||
| return false; | ||
| } | ||
| Calendar minDate = Calendar.getInstance(); | ||
| minDate.setTimeInMillis(args.getLong(RNConstants.ARG_MINDATE)); | ||
| return (year < minDate.get(Calendar.YEAR) || | ||
| (year == minDate.get(Calendar.YEAR) && month < minDate.get(Calendar.MONTH)) || | ||
| (year == minDate.get(Calendar.YEAR) && month == minDate.get(Calendar.MONTH) && day < minDate.get(Calendar.DAY_OF_MONTH))); | ||
| } | ||
| } |
| /** | ||
| * Copyright (c) Facebook, Inc. and its affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| */ | ||
| package com.reactcommunity.rndatetimepicker; | ||
| import android.app.DatePickerDialog.OnDateSetListener; | ||
| import android.content.DialogInterface; | ||
| import android.content.DialogInterface.OnDismissListener; | ||
| import android.content.DialogInterface.OnClickListener; | ||
| import android.os.Bundle; | ||
| import android.widget.DatePicker; | ||
| import androidx.annotation.NonNull; | ||
| import androidx.fragment.app.FragmentActivity; | ||
| import androidx.fragment.app.FragmentManager; | ||
| import com.facebook.react.bridge.*; | ||
| import com.facebook.react.common.annotations.VisibleForTesting; | ||
| import com.facebook.react.module.annotations.ReactModule; | ||
| import static com.reactcommunity.rndatetimepicker.Common.dismissDialog; | ||
| import static com.reactcommunity.rndatetimepicker.KeepDateInRangeListener.isDateAfterMaxDate; | ||
| import static com.reactcommunity.rndatetimepicker.KeepDateInRangeListener.isDateBeforeMinDate; | ||
| import java.util.Calendar; | ||
| /** | ||
| * {@link NativeModule} that allows JS to show a native date picker dialog and get called back when | ||
| * the user selects a date. | ||
| */ | ||
| @ReactModule(name = RNDatePickerDialogModule.FRAGMENT_TAG) | ||
| public class RNDatePickerDialogModule extends ReactContextBaseJavaModule { | ||
| @VisibleForTesting | ||
| public static final String FRAGMENT_TAG = "RNDatePickerAndroid"; | ||
| public RNDatePickerDialogModule(ReactApplicationContext reactContext) { | ||
| super(reactContext); | ||
| } | ||
| @Override | ||
| public @NonNull String getName() { | ||
| return RNDatePickerDialogModule.FRAGMENT_TAG; | ||
| } | ||
| private class DatePickerDialogListener implements OnDateSetListener, OnDismissListener, OnClickListener { | ||
| private final Promise mPromise; | ||
| private final Bundle mArgs; | ||
| private boolean mPromiseResolved = false; | ||
| public DatePickerDialogListener(final Promise promise, Bundle arguments) { | ||
| mPromise = promise; | ||
| mArgs = arguments; | ||
| } | ||
| @Override | ||
| public void onDateSet(DatePicker view, int year, int month, int day) { | ||
| if (!mPromiseResolved && getReactApplicationContext().hasActiveReactInstance()) { | ||
| WritableMap result = new WritableNativeMap(); | ||
| result.putString("action", RNConstants.ACTION_DATE_SET); | ||
| result.putInt("year", year); | ||
| result.putInt("month", month); | ||
| result.putInt("day", day); | ||
| // https://issuetracker.google.com/issues/169602180 | ||
| // TODO revisit day, month, year with timezoneoffset fixes | ||
| if (isDateAfterMaxDate(mArgs, year, month, day)) { | ||
| Calendar maxDate = Calendar.getInstance(); | ||
| maxDate.setTimeInMillis(mArgs.getLong(RNConstants.ARG_MAXDATE)); | ||
| result.putInt("year", maxDate.get(Calendar.YEAR)); | ||
| result.putInt("month", maxDate.get(Calendar.MONTH) ); | ||
| result.putInt("day", maxDate.get(Calendar.DAY_OF_MONTH)); | ||
| } | ||
| if (isDateBeforeMinDate(mArgs, year, month, day)) { | ||
| Calendar minDate = Calendar.getInstance(); | ||
| minDate.setTimeInMillis(mArgs.getLong(RNConstants.ARG_MINDATE)); | ||
| result.putInt("year", minDate.get(Calendar.YEAR)); | ||
| result.putInt("month", minDate.get(Calendar.MONTH) ); | ||
| result.putInt("day", minDate.get(Calendar.DAY_OF_MONTH)); | ||
| } | ||
| mPromise.resolve(result); | ||
| mPromiseResolved = true; | ||
| } | ||
| } | ||
| @Override | ||
| public void onDismiss(DialogInterface dialog) { | ||
| if (!mPromiseResolved && getReactApplicationContext().hasActiveReactInstance()) { | ||
| WritableMap result = new WritableNativeMap(); | ||
| result.putString("action", RNConstants.ACTION_DISMISSED); | ||
| mPromise.resolve(result); | ||
| mPromiseResolved = true; | ||
| } | ||
| } | ||
| @Override | ||
| public void onClick(DialogInterface dialog, int which) { | ||
| if (!mPromiseResolved && getReactApplicationContext().hasActiveReactInstance()) { | ||
| WritableMap result = new WritableNativeMap(); | ||
| result.putString("action", RNConstants.ACTION_NEUTRAL_BUTTON); | ||
| mPromise.resolve(result); | ||
| mPromiseResolved = true; | ||
| } | ||
| } | ||
| } | ||
| @ReactMethod | ||
| public void dismiss(Promise promise) { | ||
| FragmentActivity activity = (FragmentActivity) getCurrentActivity(); | ||
| dismissDialog(activity, FRAGMENT_TAG, promise); | ||
| } | ||
| /** | ||
| * Show a date picker dialog. | ||
| * | ||
| * @param options a map containing options. Available keys are: | ||
| * | ||
| * <ul> | ||
| * <li>{@code date} (timestamp in milliseconds) the date to show by default</li> | ||
| * <li> | ||
| * {@code minimumDate} (timestamp in milliseconds) the minimum date the user should be allowed | ||
| * to select | ||
| * </li> | ||
| * <li> | ||
| * {@code maximumDate} (timestamp in milliseconds) the maximum date the user should be allowed | ||
| * to select | ||
| * </li> | ||
| * <li> | ||
| * {@code display} To set the date picker display to 'calendar/spinner/default' | ||
| * </li> | ||
| * </ul> | ||
| * | ||
| * @param promise This will be invoked with parameters action, year, | ||
| * month (0-11), day, where action is {@code dateSetAction} or | ||
| * {@code dismissedAction}, depending on what the user did. If the action is | ||
| * dismiss, year, month and date are undefined. | ||
| */ | ||
| @ReactMethod | ||
| public void open(final ReadableMap options, final Promise promise) { | ||
| FragmentActivity activity = (FragmentActivity) getCurrentActivity(); | ||
| if (activity == null) { | ||
| promise.reject( | ||
| RNConstants.ERROR_NO_ACTIVITY, | ||
| "Tried to open a DatePicker dialog while not attached to an Activity"); | ||
| return; | ||
| } | ||
| final FragmentManager fragmentManager = activity.getSupportFragmentManager(); | ||
| UiThreadUtil.runOnUiThread(new Runnable() { | ||
| @Override | ||
| public void run() { | ||
| RNDatePickerDialogFragment oldFragment = | ||
| (RNDatePickerDialogFragment) fragmentManager.findFragmentByTag(FRAGMENT_TAG); | ||
| if (oldFragment != null) { | ||
| oldFragment.update(createFragmentArguments(options)); | ||
| return; | ||
| } | ||
| RNDatePickerDialogFragment fragment = new RNDatePickerDialogFragment(); | ||
| fragment.setArguments(createFragmentArguments(options)); | ||
| final DatePickerDialogListener listener = new DatePickerDialogListener(promise, createFragmentArguments(options)); | ||
| fragment.setOnDismissListener(listener); | ||
| fragment.setOnDateSetListener(listener); | ||
| fragment.setOnNeutralButtonActionListener(listener); | ||
| fragment.show(fragmentManager, FRAGMENT_TAG); | ||
| } | ||
| }); | ||
| } | ||
| private Bundle createFragmentArguments(ReadableMap options) { | ||
| final Bundle args = new Bundle(); | ||
| if (options.hasKey(RNConstants.ARG_VALUE) && !options.isNull(RNConstants.ARG_VALUE)) { | ||
| args.putLong(RNConstants.ARG_VALUE, (long) options.getDouble(RNConstants.ARG_VALUE)); | ||
| } | ||
| 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)); | ||
| } | ||
| return args; | ||
| } | ||
| } |
| /** | ||
| * Copyright (c) Facebook, Inc. and its affiliates. | ||
| * <p> | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * </p> | ||
| */ | ||
| package com.reactcommunity.rndatetimepicker; | ||
| import com.facebook.react.bridge.*; | ||
| import com.facebook.react.common.annotations.VisibleForTesting; | ||
| import com.facebook.react.module.annotations.ReactModule; | ||
| import android.app.TimePickerDialog.OnTimeSetListener; | ||
| import android.content.DialogInterface; | ||
| import android.content.DialogInterface.OnDismissListener; | ||
| import android.content.DialogInterface.OnClickListener; | ||
| import android.os.Bundle; | ||
| import android.widget.TimePicker; | ||
| import androidx.annotation.NonNull; | ||
| import androidx.fragment.app.FragmentActivity; | ||
| import androidx.fragment.app.FragmentManager; | ||
| import static com.reactcommunity.rndatetimepicker.Common.dismissDialog; | ||
| /** | ||
| * {@link NativeModule} that allows JS to show a native time picker dialog and get called back when | ||
| * the user selects a time. | ||
| */ | ||
| @ReactModule(name = RNTimePickerDialogModule.FRAGMENT_TAG) | ||
| public class RNTimePickerDialogModule extends ReactContextBaseJavaModule { | ||
| @VisibleForTesting | ||
| public static final String FRAGMENT_TAG = "RNTimePickerAndroid"; | ||
| public RNTimePickerDialogModule(ReactApplicationContext reactContext) { | ||
| super(reactContext); | ||
| } | ||
| @NonNull | ||
| @Override | ||
| public String getName() { | ||
| return FRAGMENT_TAG; | ||
| } | ||
| private class TimePickerDialogListener implements OnTimeSetListener, OnDismissListener, OnClickListener { | ||
| private final Promise mPromise; | ||
| private boolean mPromiseResolved = false; | ||
| public TimePickerDialogListener(Promise promise) { | ||
| mPromise = promise; | ||
| } | ||
| @Override | ||
| public void onTimeSet(TimePicker view, int hour, int minute) { | ||
| if (!mPromiseResolved && getReactApplicationContext().hasActiveReactInstance()) { | ||
| WritableMap result = new WritableNativeMap(); | ||
| result.putString("action", RNConstants.ACTION_TIME_SET); | ||
| result.putInt("hour", hour); | ||
| result.putInt("minute", minute); | ||
| mPromise.resolve(result); | ||
| mPromiseResolved = true; | ||
| } | ||
| } | ||
| @Override | ||
| public void onDismiss(DialogInterface dialog) { | ||
| if (!mPromiseResolved && getReactApplicationContext().hasActiveReactInstance()) { | ||
| WritableMap result = new WritableNativeMap(); | ||
| result.putString("action", RNConstants.ACTION_DISMISSED); | ||
| mPromise.resolve(result); | ||
| mPromiseResolved = true; | ||
| } | ||
| } | ||
| @Override | ||
| public void onClick(DialogInterface dialog, int which) { | ||
| if (!mPromiseResolved && getReactApplicationContext().hasActiveReactInstance()) { | ||
| WritableMap result = new WritableNativeMap(); | ||
| result.putString("action", RNConstants.ACTION_NEUTRAL_BUTTON); | ||
| mPromise.resolve(result); | ||
| mPromiseResolved = true; | ||
| } | ||
| } | ||
| } | ||
| @ReactMethod | ||
| public void dismiss(Promise promise) { | ||
| FragmentActivity activity = (FragmentActivity) getCurrentActivity(); | ||
| dismissDialog(activity, FRAGMENT_TAG, promise); | ||
| } | ||
| @ReactMethod | ||
| public void open(final ReadableMap options, final Promise promise) { | ||
| FragmentActivity activity = (FragmentActivity) getCurrentActivity(); | ||
| if (activity == null) { | ||
| promise.reject( | ||
| RNConstants.ERROR_NO_ACTIVITY, | ||
| "Tried to open a TimePicker dialog while not attached to an Activity"); | ||
| return; | ||
| } | ||
| // We want to support both android.app.Activity and the pre-Honeycomb FragmentActivity | ||
| // (for apps that use it for legacy reasons). This unfortunately leads to some code duplication. | ||
| final FragmentManager fragmentManager = activity.getSupportFragmentManager(); | ||
| UiThreadUtil.runOnUiThread(new Runnable() { | ||
| @Override | ||
| public void run() { | ||
| RNTimePickerDialogFragment oldFragment = | ||
| (RNTimePickerDialogFragment) fragmentManager.findFragmentByTag(FRAGMENT_TAG); | ||
| if (oldFragment != null) { | ||
| oldFragment.update(createFragmentArguments(options)); | ||
| return; | ||
| } | ||
| RNTimePickerDialogFragment fragment = new RNTimePickerDialogFragment(); | ||
| fragment.setArguments(createFragmentArguments(options)); | ||
| final TimePickerDialogListener listener = new TimePickerDialogListener(promise); | ||
| fragment.setOnDismissListener(listener); | ||
| fragment.setOnTimeSetListener(listener); | ||
| fragment.setOnNeutralButtonActionListener(listener); | ||
| fragment.show(fragmentManager, FRAGMENT_TAG); | ||
| } | ||
| }); | ||
| } | ||
| private Bundle createFragmentArguments(ReadableMap options) { | ||
| final Bundle args = new Bundle(); | ||
| if (options.hasKey(RNConstants.ARG_VALUE) && !options.isNull(RNConstants.ARG_VALUE)) { | ||
| args.putLong(RNConstants.ARG_VALUE, (long) options.getDouble(RNConstants.ARG_VALUE)); | ||
| } | ||
| 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.putInt(RNConstants.ARG_TZOFFSET_MINS, options.getInt(RNConstants.ARG_TZOFFSET_MINS)); | ||
| } | ||
| return args; | ||
| } | ||
| } |
| // @flow strict-local | ||
| import type {ViewProps} from 'react-native/Libraries/Components/View/ViewPropTypes'; | ||
| import type {ColorValue} from 'react-native/Libraries/StyleSheet/StyleSheet'; | ||
| import type {HostComponent} from 'react-native'; | ||
| import type { | ||
| BubblingEventHandler, | ||
| Double, | ||
| Int32, | ||
| WithDefault, | ||
| } from 'react-native/Libraries/Types/CodegenTypes'; | ||
| import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent'; | ||
| type DateTimePickerEvent = $ReadOnly<{| | ||
| timestamp: Double, | ||
| |}>; | ||
| type NativeProps = $ReadOnly<{| | ||
| ...ViewProps, | ||
| onChange?: ?BubblingEventHandler<DateTimePickerEvent>, | ||
| onPickerDismiss?: ?BubblingEventHandler<null>, | ||
| maximumDate?: ?Double, | ||
| minimumDate?: ?Double, | ||
| date?: ?Double, | ||
| locale?: ?string, | ||
| minuteInterval?: ?Int32, | ||
| mode?: WithDefault<'date' | 'time' | 'datetime' | 'countdown', 'date'>, | ||
| timeZoneOffsetInMinutes?: ?Double, | ||
| textColor?: ?ColorValue, | ||
| accentColor?: ?ColorValue, | ||
| themeVariant?: WithDefault<'dark' | 'light' | 'unspecified', 'unspecified'>, | ||
| displayIOS?: WithDefault< | ||
| 'default' | 'spinner' | 'compact' | 'inline', | ||
| 'default', | ||
| >, | ||
| enabled?: WithDefault<boolean, true>, | ||
| |}>; | ||
| export default (codegenNativeComponent<NativeProps>( | ||
| 'RNDateTimePicker', | ||
| ): HostComponent<NativeProps>); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
218390
5.37%25
-10.71%84
6.33%2708
4.23%599
7.35%