4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | *
7 | */ 8 | 9 | package com.reactcommunity.rndatetimepicker; 10 | 11 | /** 12 | * Date picker display options. 13 | */ 14 | public enum RNTimePickerDisplay { 15 | SPINNER, 16 | DEFAULT 17 | } 18 | -------------------------------------------------------------------------------- /example/visionos/Podfile: -------------------------------------------------------------------------------- 1 | ws_dir = Pathname.new(__dir__) 2 | ws_dir = ws_dir.parent until 3 | File.exist?("#{ws_dir}/node_modules/react-native-test-app/visionos/test_app.rb") || 4 | ws_dir.expand_path.to_s == '/' 5 | require "#{ws_dir}/node_modules/react-native-test-app/visionos/test_app.rb" 6 | 7 | workspace 'date-time-picker-example.xcworkspace' 8 | 9 | use_test_app! do |target| 10 | target.app do 11 | pod 'RNDateTimePicker', :path => '../../RNDateTimePicker.podspec' 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /ios/RNDateTimePickerManager.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | #import4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | *
7 | */ 8 | 9 | package com.reactcommunity.rndatetimepicker; 10 | 11 | import static com.reactcommunity.rndatetimepicker.Common.getDisplayTime; 12 | import static com.reactcommunity.rndatetimepicker.Common.setButtonTextColor; 13 | import static com.reactcommunity.rndatetimepicker.Common.setButtonTitles; 14 | 15 | import android.app.Dialog; 16 | import android.app.TimePickerDialog; 17 | import android.app.TimePickerDialog.OnTimeSetListener; 18 | import android.content.Context; 19 | import android.content.DialogInterface; 20 | import android.content.DialogInterface.OnDismissListener; 21 | import android.content.DialogInterface.OnClickListener; 22 | import android.os.Bundle; 23 | import android.text.format.DateFormat; 24 | 25 | import androidx.annotation.NonNull; 26 | import androidx.annotation.Nullable; 27 | import androidx.fragment.app.DialogFragment; 28 | 29 | @SuppressWarnings("ValidFragment") 30 | public class RNTimePickerDialogFragment extends DialogFragment { 31 | private TimePickerDialog instance; 32 | 33 | @Nullable 34 | private OnTimeSetListener mOnTimeSetListener; 35 | @Nullable 36 | private OnDismissListener mOnDismissListener; 37 | @Nullable 38 | private OnClickListener mOnNeutralButtonActionListener; 39 | 40 | @NonNull 41 | @Override 42 | public Dialog onCreateDialog(Bundle savedInstanceState) { 43 | final Bundle args = getArguments(); 44 | instance = createDialog(args); 45 | return instance; 46 | } 47 | 48 | public void update(Bundle args) { 49 | final RNDate date = new RNDate(args); 50 | instance.updateTime(date.hour(), date.minute()); 51 | } 52 | 53 | static TimePickerDialog getDialog( 54 | Bundle args, 55 | Context activityContext, 56 | @Nullable OnTimeSetListener onTimeSetListener) { 57 | 58 | final RNDate date = new RNDate(args); 59 | final int hour = date.hour(); 60 | final int minute = date.minute(); 61 | boolean is24hour = DateFormat.is24HourFormat(activityContext); 62 | if (args != null) { 63 | is24hour = args.getBoolean(RNConstants.ARG_IS24HOUR, DateFormat.is24HourFormat(activityContext)); 64 | } 65 | 66 | int minuteInterval = RNConstants.DEFAULT_TIME_PICKER_INTERVAL; 67 | if (args != null && MinuteIntervalSnappableTimePickerDialog.isValidMinuteInterval(args.getInt(RNConstants.ARG_INTERVAL))) { 68 | minuteInterval = args.getInt(RNConstants.ARG_INTERVAL); 69 | } 70 | 71 | RNTimePickerDisplay display = getDisplayTime(args); 72 | 73 | if (display == RNTimePickerDisplay.SPINNER) { 74 | return new RNDismissableTimePickerDialog( 75 | activityContext, 76 | R.style.SpinnerTimePickerDialog, 77 | onTimeSetListener, 78 | hour, 79 | minute, 80 | minuteInterval, 81 | is24hour, 82 | display 83 | ); 84 | } 85 | return new RNDismissableTimePickerDialog( 86 | activityContext, 87 | onTimeSetListener, 88 | hour, 89 | minute, 90 | minuteInterval, 91 | is24hour, 92 | display 93 | ); 94 | } 95 | 96 | private TimePickerDialog createDialog(Bundle args) { 97 | Context activityContext = getActivity(); 98 | TimePickerDialog dialog = getDialog(args, activityContext, mOnTimeSetListener); 99 | 100 | if (args != null) { 101 | setButtonTitles(args, dialog, mOnNeutralButtonActionListener); 102 | if (activityContext != null) { 103 | RNTimePickerDisplay display = getDisplayTime(args); 104 | boolean needsColorOverride = display == RNTimePickerDisplay.SPINNER; 105 | dialog.setOnShowListener(setButtonTextColor(activityContext, dialog, args, needsColorOverride)); 106 | } 107 | } 108 | return dialog; 109 | } 110 | 111 | @Override 112 | public void onDismiss(@NonNull DialogInterface dialog) { 113 | super.onDismiss(dialog); 114 | if (mOnDismissListener != null) { 115 | mOnDismissListener.onDismiss(dialog); 116 | } 117 | } 118 | 119 | public void setOnDismissListener(@Nullable OnDismissListener onDismissListener) { 120 | mOnDismissListener = onDismissListener; 121 | } 122 | 123 | public void setOnTimeSetListener(@Nullable OnTimeSetListener onTimeSetListener) { 124 | mOnTimeSetListener = onTimeSetListener; 125 | } 126 | 127 | /*package*/ void setOnNeutralButtonActionListener(@Nullable OnClickListener onNeutralButtonActionListener) { 128 | mOnNeutralButtonActionListener = onNeutralButtonActionListener; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /android/src/main/java/com/reactcommunity/rndatetimepicker/RNDismissableTimePickerDialog.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | *4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | *
7 | */ 8 | package com.reactcommunity.rndatetimepicker; 9 | 10 | import static com.reactcommunity.rndatetimepicker.ReflectionHelper.findField; 11 | 12 | import java.lang.reflect.Constructor; 13 | import java.lang.reflect.Field; 14 | 15 | import android.app.TimePickerDialog; 16 | import android.content.Context; 17 | import android.content.res.TypedArray; 18 | import android.os.Build; 19 | import android.util.AttributeSet; 20 | import android.widget.TimePicker; 21 | 22 | import androidx.annotation.Nullable; 23 | 24 | /** 25 | *26 | * Certain versions of Android (Jellybean-KitKat) have a bug where when dismissed, the 27 | * {@link TimePickerDialog} still calls the OnTimeSetListener. This class works around that issue 28 | * by *not* calling super.onStop on KitKat on lower, as that would erroneously call the 29 | * OnTimeSetListener when the dialog is dismissed, or call it twice when "OK" is pressed. 30 | *
31 | * 32 | *33 | * See: Issue 34833 34 | *
35 | */ 36 | 37 | public class RNDismissableTimePickerDialog extends MinuteIntervalSnappableTimePickerDialog { 38 | 39 | public RNDismissableTimePickerDialog( 40 | Context context, 41 | @Nullable TimePickerDialog.OnTimeSetListener callback, 42 | int hourOfDay, 43 | int minute, 44 | int minuteInterval, 45 | boolean is24HourView, 46 | RNTimePickerDisplay display 47 | ) { 48 | super(context, callback, hourOfDay, minute, minuteInterval, is24HourView, display); 49 | fixSpinner(context, hourOfDay, minute, is24HourView, display); 50 | } 51 | 52 | public RNDismissableTimePickerDialog( 53 | Context context, 54 | int theme, 55 | @Nullable TimePickerDialog.OnTimeSetListener callback, 56 | int hourOfDay, 57 | int minute, 58 | int minuteInterval, 59 | boolean is24HourView, 60 | RNTimePickerDisplay display 61 | ) { 62 | super(context, theme, callback, hourOfDay, minute, minuteInterval, is24HourView, display); 63 | fixSpinner(context, hourOfDay, minute, is24HourView, display); 64 | } 65 | 66 | @Override 67 | protected void onStop() { 68 | if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) { 69 | super.onStop(); 70 | } 71 | } 72 | 73 | private void fixSpinner(Context context, int hourOfDay, int minute, boolean is24HourView, RNTimePickerDisplay display) { 74 | if (Build.VERSION.SDK_INT == Build.VERSION_CODES.N && display == RNTimePickerDisplay.SPINNER) { 75 | try { 76 | Class> styleableClass = Class.forName("com.android.internal.R$styleable"); 77 | Field timePickerStyleableField = styleableClass.getField("TimePicker"); 78 | int[] timePickerStyleable = (int[]) timePickerStyleableField.get(null); 79 | final TypedArray a = context.obtainStyledAttributes(null, timePickerStyleable, android.R.attr.timePickerStyle, 0); 80 | a.recycle(); 81 | 82 | TimePicker timePicker = (TimePicker) findField(TimePickerDialog.class, TimePicker.class, "mTimePicker").get(this); 83 | Class> delegateClass = Class.forName("android.widget.TimePicker$TimePickerDelegate"); 84 | Field delegateField = findField(TimePicker.class, delegateClass, "mDelegate"); 85 | Object delegate = delegateField.get(timePicker); 86 | Class> spinnerDelegateClass; 87 | spinnerDelegateClass = Class.forName("android.widget.TimePickerSpinnerDelegate"); 88 | // In 7.0 Nougat for some reason the timePickerMode is ignored and the delegate is TimePickerClockDelegate 89 | if (delegate.getClass() != spinnerDelegateClass) { 90 | delegateField.set(timePicker, null); // throw out the TimePickerClockDelegate! 91 | timePicker.removeAllViews(); // remove the TimePickerClockDelegate views 92 | Constructor spinnerDelegateConstructor = spinnerDelegateClass.getConstructor(TimePicker.class, Context.class, AttributeSet.class, int.class, int.class); 93 | spinnerDelegateConstructor.setAccessible(true); 94 | // Instantiate a TimePickerSpinnerDelegate 95 | delegate = spinnerDelegateConstructor.newInstance(timePicker, context, null, android.R.attr.timePickerStyle, 0); 96 | delegateField.set(timePicker, delegate); // set the TimePicker.mDelegate to the spinner delegate 97 | // Set up the TimePicker again, with the TimePickerSpinnerDelegate 98 | timePicker.setIs24HourView(is24HourView); 99 | timePicker.setCurrentHour(hourOfDay); 100 | timePicker.setCurrentMinute(minute); 101 | timePicker.setOnTimeChangedListener(this); 102 | } 103 | } catch (Exception e) { 104 | throw new RuntimeException(e); 105 | } 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/androidUtils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @format 3 | * @flow strict-local 4 | */ 5 | import {ANDROID_DISPLAY, ANDROID_MODE} from './constants'; 6 | import defaultPickers from './picker'; 7 | import type {AndroidNativeProps, DateTimePickerResult} from './types'; 8 | import {sharedPropsValidation} from './utils'; 9 | import invariant from 'invariant'; 10 | import {processColor} from 'react-native'; 11 | import MaterialDatePickerAndroid from './materialdatepicker'; 12 | import MaterialTimePickerAndroid from './materialtimepicker'; 13 | 14 | type Timestamp = number; 15 | 16 | type ProcessedButton = { 17 | title: string, 18 | textColor: $Call25 | * Certain versions of Android (Jellybean-KitKat) have a bug where when dismissed, the 26 | * {@link DatePickerDialog} still calls the OnDateSetListener. This class works around that issue. 27 | *
28 | * 29 | *30 | * See: Issue 34833 31 | *
32 | */ 33 | public class RNDismissableDatePickerDialog extends DatePickerDialog { 34 | 35 | public RNDismissableDatePickerDialog( 36 | Context context, 37 | @Nullable DatePickerDialog.OnDateSetListener callback, 38 | int year, 39 | int monthOfYear, 40 | int dayOfMonth, 41 | RNDatePickerDisplay display) { 42 | super(context, callback, year, monthOfYear, dayOfMonth); 43 | fixSpinner(context, year, monthOfYear, dayOfMonth, display); 44 | } 45 | 46 | public RNDismissableDatePickerDialog( 47 | Context context, 48 | int theme, 49 | @Nullable DatePickerDialog.OnDateSetListener callback, 50 | int year, 51 | int monthOfYear, 52 | int dayOfMonth, 53 | RNDatePickerDisplay display) { 54 | super(context, theme, callback, year, monthOfYear, dayOfMonth); 55 | fixSpinner(context, year, monthOfYear, dayOfMonth, display); 56 | } 57 | 58 | @Override 59 | protected void onStop() { 60 | // do *not* call super.onStop() on KitKat on lower, as that would erroneously call the 61 | // OnDateSetListener when the dialog is dismissed, or call it twice when "OK" is pressed. 62 | if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) { 63 | super.onStop(); 64 | } 65 | } 66 | 67 | private void fixSpinner(Context context, int year, int month, int dayOfMonth, RNDatePickerDisplay display) { 68 | if (Build.VERSION.SDK_INT == Build.VERSION_CODES.N && display == RNDatePickerDisplay.SPINNER) { 69 | try { 70 | // Get the theme's android:datePickerMode 71 | Class> styleableClass = Class.forName("com.android.internal.R$styleable"); 72 | Field datePickerStyleableField = styleableClass.getField("DatePicker"); 73 | int[] datePickerStyleable = (int[]) datePickerStyleableField.get(null); 74 | final TypedArray a = context.obtainStyledAttributes(null, datePickerStyleable, android.R.attr.datePickerStyle, 0); 75 | a.recycle(); 76 | 77 | DatePicker datePicker = (DatePicker)findField(DatePickerDialog.class, DatePicker.class, "mDatePicker").get(this); 78 | Class> delegateClass = Class.forName("android.widget.DatePickerSpinnerDelegate"); 79 | Field delegateField = findField(DatePicker.class, delegateClass, "mDelegate"); 80 | Object delegate = delegateField.get(datePicker); 81 | Class> spinnerDelegateClass; 82 | spinnerDelegateClass = Class.forName("android.widget.DatePickerSpinnerDelegate"); 83 | 84 | // In 7.0 Nougat for some reason the datePickerMode is ignored and the delegate is 85 | // DatePickerClockDelegate 86 | if (delegate.getClass() != spinnerDelegateClass) { 87 | delegateField.set(datePicker, null); // throw out the DatePickerClockDelegate! 88 | datePicker.removeAllViews(); // remove the DatePickerClockDelegate views 89 | Method createSpinnerUIDelegate = 90 | DatePicker.class.getDeclaredMethod( 91 | "createSpinnerUIDelegate", 92 | Context.class, 93 | AttributeSet.class, 94 | int.class, 95 | int.class); 96 | createSpinnerUIDelegate.setAccessible(true); 97 | 98 | // Instantiate a DatePickerSpinnerDelegate throughout createSpinnerUIDelegate method 99 | delegate = createSpinnerUIDelegate.invoke(datePicker, context, null, android.R.attr.datePickerStyle, 0); 100 | delegateField.set(datePicker, delegate); // set the DatePicker.mDelegate to the spinner delegate 101 | datePicker.setCalendarViewShown(false); 102 | // Initialize the date for the DatePicker delegate again 103 | datePicker.init(year, month, dayOfMonth, this); 104 | } 105 | } catch (Exception e) { 106 | throw new RuntimeException(e); 107 | } 108 | } 109 | if (display == RNDatePickerDisplay.SPINNER){ 110 | if(this.getDatePicker() != null) 111 | this.getDatePicker().setCalendarViewShown(false); 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /android/src/main/java/com/reactcommunity/rndatetimepicker/RNMaterialTimePicker.kt: -------------------------------------------------------------------------------- 1 | package com.reactcommunity.rndatetimepicker 2 | 3 | import android.content.DialogInterface 4 | import android.os.Bundle 5 | import android.text.format.DateFormat 6 | import android.view.View 7 | import androidx.fragment.app.FragmentManager 8 | import com.facebook.react.bridge.Promise 9 | import com.facebook.react.bridge.ReactApplicationContext 10 | import com.facebook.react.bridge.WritableMap 11 | import com.facebook.react.bridge.WritableNativeMap 12 | import com.google.android.material.timepicker.MaterialTimePicker 13 | import com.google.android.material.timepicker.TimeFormat 14 | import java.util.Calendar 15 | 16 | class RNMaterialTimePicker( 17 | private val args: Bundle, 18 | private val promise: Promise, 19 | private val fragmentManager: FragmentManager, 20 | private val reactContext: ReactApplicationContext 21 | ) { 22 | private var promiseResolved = false 23 | private var timePicker: MaterialTimePicker? = null 24 | private var builder = MaterialTimePicker.Builder() 25 | 26 | fun open() { 27 | createTimePicker() 28 | addListeners() 29 | show() 30 | } 31 | 32 | private fun createTimePicker() { 33 | setInitialDate() 34 | setTitle() 35 | setInputMode() 36 | setButtons() 37 | setTimeFormat() 38 | 39 | timePicker = builder.build() 40 | } 41 | 42 | private fun setInitialDate() { 43 | val initialDate = RNDate(args) 44 | 45 | builder.setHour(initialDate.hour()) 46 | .setMinute(initialDate.minute()) 47 | } 48 | 49 | private fun setTitle() { 50 | val title = args.getString(RNConstants.ARG_TITLE) 51 | if (!title.isNullOrEmpty()) { 52 | builder.setTitleText(args.getString(RNConstants.ARG_TITLE)) 53 | } 54 | } 55 | 56 | private fun setInputMode() { 57 | if (args.getString(RNConstants.ARG_INITIAL_INPUT_MODE).isNullOrEmpty()) { 58 | builder.setInputMode(MaterialTimePicker.INPUT_MODE_CLOCK) 59 | return 60 | } 61 | 62 | val inputMode = 63 | RNMaterialInputMode.valueOf( 64 | args.getString(RNConstants.ARG_INITIAL_INPUT_MODE)!!.uppercase() 65 | ) 66 | 67 | if (inputMode == RNMaterialInputMode.KEYBOARD) { 68 | builder.setInputMode(MaterialTimePicker.INPUT_MODE_KEYBOARD) 69 | } else { 70 | builder.setInputMode(MaterialTimePicker.INPUT_MODE_CLOCK) 71 | } 72 | } 73 | 74 | private fun setButtons() { 75 | val buttons = args.getBundle(RNConstants.ARG_DIALOG_BUTTONS) ?: return 76 | 77 | val negativeButton = buttons.getBundle(Common.NEGATIVE) 78 | val positiveButton = buttons.getBundle(Common.POSITIVE) 79 | 80 | if (negativeButton != null) { 81 | builder.setNegativeButtonText(negativeButton.getString(Common.LABEL)) 82 | } 83 | 84 | if (positiveButton != null) { 85 | builder.setPositiveButtonText(positiveButton.getString(Common.LABEL)) 86 | } 87 | } 88 | 89 | private fun setTimeFormat() { 90 | if (args.getBoolean(RNConstants.ARG_IS24HOUR)) { 91 | builder.setTimeFormat(TimeFormat.CLOCK_24H) 92 | return 93 | } 94 | 95 | if (DateFormat.is24HourFormat(reactContext)) { 96 | builder.setTimeFormat(TimeFormat.CLOCK_24H) 97 | } else { 98 | builder.setTimeFormat(TimeFormat.CLOCK_12H) 99 | } 100 | } 101 | 102 | private fun addListeners() { 103 | val listeners = Listeners() 104 | timePicker!!.addOnPositiveButtonClickListener(listeners) 105 | timePicker!!.addOnDismissListener(listeners) 106 | } 107 | 108 | private fun show() { 109 | timePicker!!.show(fragmentManager, MaterialTimePickerModule.NAME) 110 | } 111 | 112 | private inner class Listeners : View.OnClickListener, DialogInterface.OnDismissListener { 113 | override fun onDismiss(dialog: DialogInterface) { 114 | if (promiseResolved || !reactContext.hasActiveReactInstance()) return 115 | 116 | val result: WritableMap = WritableNativeMap() 117 | result.putString("action", RNConstants.ACTION_DISMISSED) 118 | promise.resolve(result) 119 | promiseResolved = true 120 | } 121 | 122 | override fun onClick(v: View) { 123 | if (promiseResolved || !reactContext.hasActiveReactInstance()) return 124 | 125 | val newCalendar = createNewCalendar() 126 | val result = WritableNativeMap() 127 | 128 | result.putString("action", RNConstants.ACTION_DATE_SET) 129 | result.putDouble("timestamp", newCalendar.timeInMillis.toDouble()) 130 | result.putDouble( 131 | "utcOffset", 132 | newCalendar.timeZone.getOffset(newCalendar.timeInMillis).toDouble() / 1000 / 60 133 | ) 134 | 135 | promise.resolve(result) 136 | promiseResolved = true 137 | } 138 | 139 | private fun createNewCalendar(): Calendar { 140 | val initialDate = RNDate(args) 141 | val calendar = Calendar.getInstance( 142 | Common.getTimeZone( 143 | args 144 | ) 145 | ) 146 | 147 | calendar[initialDate.year(), initialDate.month(), initialDate.day(), timePicker!!.hour, timePicker!!.minute] = 148 | 0 149 | 150 | calendar[Calendar.MILLISECOND] = 0 151 | 152 | return calendar 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /windows/DateTimePickerWindows/TimePickerView.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | #include "pch.h" 5 | #include "JSValueXaml.h" 6 | #include "TimePickerView.h" 7 | #include "TimePickerView.g.cpp" 8 | 9 | #include4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | *
7 | */ 8 | 9 | package com.reactcommunity.rndatetimepicker; 10 | 11 | import com.facebook.react.bridge.*; 12 | import com.facebook.react.common.annotations.VisibleForTesting; 13 | import com.facebook.react.module.annotations.ReactModule; 14 | 15 | import android.app.TimePickerDialog.OnTimeSetListener; 16 | import android.content.DialogInterface; 17 | import android.content.DialogInterface.OnDismissListener; 18 | import android.content.DialogInterface.OnClickListener; 19 | import android.os.Bundle; 20 | import android.widget.TimePicker; 21 | 22 | import androidx.annotation.NonNull; 23 | import androidx.fragment.app.FragmentActivity; 24 | import androidx.fragment.app.FragmentManager; 25 | 26 | import static com.reactcommunity.rndatetimepicker.Common.createTimePickerArguments; 27 | import static com.reactcommunity.rndatetimepicker.Common.dismissDialog; 28 | 29 | import java.util.Calendar; 30 | 31 | /** 32 | * {@link NativeModule} that allows JS to show a native time picker dialog and get called back when 33 | * the user selects a time. 34 | */ 35 | @ReactModule(name = TimePickerModule.NAME) 36 | public class TimePickerModule extends NativeModuleTimePickerSpec { 37 | 38 | @VisibleForTesting 39 | public static final String NAME = "RNCTimePicker"; 40 | 41 | public TimePickerModule(ReactApplicationContext reactContext) { 42 | super(reactContext); 43 | } 44 | 45 | @NonNull 46 | @Override 47 | public String getName() { 48 | return NAME; 49 | } 50 | 51 | private class TimePickerDialogListener implements OnTimeSetListener, OnDismissListener, OnClickListener { 52 | private final Promise mPromise; 53 | private final Bundle mArgs; 54 | private boolean mPromiseResolved = false; 55 | 56 | public TimePickerDialogListener(Promise promise, Bundle arguments) { 57 | mPromise = promise; 58 | mArgs = arguments; 59 | } 60 | 61 | @Override 62 | public void onTimeSet(TimePicker view, int hour, int minute) { 63 | if (!mPromiseResolved && getReactApplicationContext().hasActiveReactInstance()) { 64 | final RNDate date = new RNDate(mArgs); 65 | Calendar calendar = Calendar.getInstance(Common.getTimeZone(mArgs)); 66 | calendar.set(date.year(), date.month(), date.day(), hour, minute, 0); 67 | calendar.set(Calendar.MILLISECOND, 0); 68 | 69 | WritableMap result = new WritableNativeMap(); 70 | result.putString("action", RNConstants.ACTION_TIME_SET); 71 | result.putDouble("timestamp", calendar.getTimeInMillis()); 72 | result.putDouble("utcOffset", calendar.getTimeZone().getOffset(calendar.getTimeInMillis()) / 1000 / 60); 73 | 74 | mPromise.resolve(result); 75 | mPromiseResolved = true; 76 | } 77 | } 78 | 79 | @Override 80 | public void onDismiss(DialogInterface dialog) { 81 | if (!mPromiseResolved && getReactApplicationContext().hasActiveReactInstance()) { 82 | WritableMap result = new WritableNativeMap(); 83 | result.putString("action", RNConstants.ACTION_DISMISSED); 84 | mPromise.resolve(result); 85 | mPromiseResolved = true; 86 | } 87 | } 88 | 89 | @Override 90 | public void onClick(DialogInterface dialog, int which) { 91 | if (!mPromiseResolved && getReactApplicationContext().hasActiveReactInstance()) { 92 | WritableMap result = new WritableNativeMap(); 93 | result.putString("action", RNConstants.ACTION_NEUTRAL_BUTTON); 94 | mPromise.resolve(result); 95 | mPromiseResolved = true; 96 | } 97 | } 98 | } 99 | 100 | @ReactMethod 101 | public void dismiss(Promise promise) { 102 | FragmentActivity activity = (FragmentActivity) getCurrentActivity(); 103 | dismissDialog(activity, NAME, promise); 104 | } 105 | 106 | @ReactMethod 107 | public void open(final ReadableMap options, final Promise promise) { 108 | FragmentActivity activity = (FragmentActivity) getCurrentActivity(); 109 | if (activity == null) { 110 | promise.reject( 111 | RNConstants.ERROR_NO_ACTIVITY, 112 | "Tried to open a TimePicker dialog while not attached to an Activity"); 113 | return; 114 | } 115 | // We want to support both android.app.Activity and the pre-Honeycomb FragmentActivity 116 | // (for apps that use it for legacy reasons). This unfortunately leads to some code duplication. 117 | final FragmentManager fragmentManager = activity.getSupportFragmentManager(); 118 | 119 | UiThreadUtil.runOnUiThread(() -> { 120 | RNTimePickerDialogFragment oldFragment = 121 | (RNTimePickerDialogFragment) fragmentManager.findFragmentByTag(NAME); 122 | 123 | Bundle arguments = createTimePickerArguments(options); 124 | 125 | if (oldFragment != null) { 126 | oldFragment.update(arguments); 127 | return; 128 | } 129 | 130 | RNTimePickerDialogFragment fragment = new RNTimePickerDialogFragment(); 131 | 132 | fragment.setArguments(arguments); 133 | 134 | final TimePickerDialogListener listener = new TimePickerDialogListener(promise, arguments); 135 | fragment.setOnDismissListener(listener); 136 | fragment.setOnTimeSetListener(listener); 137 | fragment.setOnNeutralButtonActionListener(listener); 138 | fragment.show(fragmentManager, NAME); 139 | }); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@react-native-community/datetimepicker", 3 | "version": "8.5.1", 4 | "description": "DateTimePicker component for React Native", 5 | "main": "./src/index.js", 6 | "types": "src/index.d.ts", 7 | "files": [ 8 | "ios", 9 | "android", 10 | "src", 11 | "jest", 12 | "flow-typed", 13 | "windows", 14 | "RNDateTimePicker.podspec", 15 | "!android/build", 16 | "!ios/build", 17 | "!**/__tests__", 18 | "!**/__fixtures__", 19 | "!**/__mocks__", 20 | "plugin/build", 21 | "app.plugin.js" 22 | ], 23 | "publishConfig": { 24 | "access": "public", 25 | "provenance": true 26 | }, 27 | "scripts": { 28 | "start": "patch-package && react-native start", 29 | "start:android": "react-native run-android", 30 | "start:ios": "react-native run-ios", 31 | "start:windows": "react-native run-windows --sln example/windows/date-time-picker-example.sln", 32 | "bundle:android": "mkdir -p example/dist && react-native bundle --platform android --dev false --entry-file index.js --bundle-output example/dist/main.android.jsbundle --assets-dest example/dist/res", 33 | "bundle:ios": "mkdir -p example/dist && react-native bundle --platform ios --dev false --entry-file index.js --bundle-output example/dist/main.ios.jsbundle --assets-dest example/dist/assets", 34 | "test": "jest", 35 | "lint": "NODE_ENV=lint eslint {example,src,test}/**/*.js src/index.d.ts", 36 | "flow": "flow check", 37 | "detox:ios:build:debug": "detox build -c ios.sim.debug", 38 | "detox:ios:test:debug": "SIMCTL_CHILD_TZ=Europe/Prague detox test -c ios.sim.debug -l verbose", 39 | "detox:ios:build:release": "detox build -c ios.sim.release", 40 | "detox:ios:test:release": "SIMCTL_CHILD_TZ=Europe/Prague detox test -c ios.sim.release --record-videos all --record-logs all -l verbose", 41 | "detox:android:build:debug": "ORG_GRADLE_PROJECT_newArchEnabled=false detox build -c android.emu.debug", 42 | "detox:android:test:debug": "adb shell service call alarm 3 s16 Europe/Prague && detox test -c android.emu.debug -l verbose", 43 | "detox:android:build:release": "ORG_GRADLE_PROJECT_newArchEnabled=false detox build -c android.emu.release", 44 | "detox:android:test:release": "adb shell service call alarm 3 s16 Europe/Prague && detox test -c android.emu.release --record-videos all --record-logs all --headless -l verbose", 45 | "detox:clean": "rimraf example/android/build && rimraf example/android/app/build && rimraf example/android/.gradle && rimraf example/ios/build", 46 | "plugin:build": "expo-module build plugin", 47 | "generateManifest": "node node_modules/react-native-test-app/android/android-manifest.js example/app.json example/android/app/build/generated/rnta/src/main/AndroidManifest.xml" 48 | }, 49 | "repository": "https://github.com/react-native-datetimepicker/datetimepicker", 50 | "keywords": [ 51 | "react-native-component", 52 | "react-native", 53 | "ios", 54 | "android", 55 | "windows", 56 | "datepicker", 57 | "timepicker", 58 | "datetime" 59 | ], 60 | "author": "Martijn Swaagman