/** * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ package com.facebook.react.modules.datepicker; import javax.annotation.Nullable; import java.util.Map; import android.app.Activity; import android.app.DatePickerDialog.OnDateSetListener; import android.app.DialogFragment; import android.app.FragmentManager; import android.content.DialogInterface; import android.content.DialogInterface.OnDismissListener; import android.os.Bundle; import android.widget.DatePicker; import com.facebook.react.bridge.NativeModule; 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.ReadableMap; import com.facebook.react.bridge.WritableMap; import com.facebook.react.bridge.WritableNativeMap; import com.facebook.react.common.annotations.VisibleForTesting; import com.facebook.react.module.annotations.ReactModule; /** * {@link NativeModule} that allows JS to show a native date picker dialog and get called back when * the user selects a date. */ @ReactModule(name = "DatePickerAndroid") public class DatePickerDialogModule extends ReactContextBaseJavaModule { @VisibleForTesting public static final String FRAGMENT_TAG = "DatePickerAndroid"; private static final String ERROR_NO_ACTIVITY = "E_NO_ACTIVITY"; /* package */ static final String ARG_DATE = "date"; /* package */ static final String ARG_MINDATE = "minDate"; /* package */ static final String ARG_MAXDATE = "maxDate"; /* package */ static final String ARG_MODE = "mode"; /* package */ static final String ACTION_DATE_SET = "dateSetAction"; /* package */ static final String ACTION_DISMISSED = "dismissedAction"; public DatePickerDialogModule(ReactApplicationContext reactContext) { super(reactContext); } @Override public String getName() { return "DatePickerAndroid"; } private class DatePickerDialogListener implements OnDateSetListener, OnDismissListener { private final Promise mPromise; private boolean mPromiseResolved = false; public DatePickerDialogListener(final Promise promise) { mPromise = promise; } @Override public void onDateSet(DatePicker view, int year, int month, int day) { if (!mPromiseResolved && getReactApplicationContext().hasActiveCatalystInstance()) { WritableMap result = new WritableNativeMap(); result.putString("action", ACTION_DATE_SET); result.putInt("year", year); result.putInt("month", month); result.putInt("day", day); mPromise.resolve(result); mPromiseResolved = true; } } @Override public void onDismiss(DialogInterface dialog) { if (!mPromiseResolved && getReactApplicationContext().hasActiveCatalystInstance()) { WritableMap result = new WritableNativeMap(); result.putString("action", ACTION_DISMISSED); mPromise.resolve(result); mPromiseResolved = true; } } } /** * 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 minDate} (timestamp in milliseconds) the minimum date the user should be allowed * to select * </li> * <li> * {@code maxDate} (timestamp in milliseconds) the maximum date the user should be allowed * to select * </li> * <li> * {@code mode} To set the date picker mode 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(@Nullable final ReadableMap options, Promise promise) { Activity activity = getCurrentActivity(); if (activity == null) { promise.reject( ERROR_NO_ACTIVITY, "Tried to open a DatePicker 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. if (activity instanceof android.support.v4.app.FragmentActivity) { android.support.v4.app.FragmentManager fragmentManager = ((android.support.v4.app.FragmentActivity) activity).getSupportFragmentManager(); android.support.v4.app.DialogFragment oldFragment = (android.support.v4.app.DialogFragment) fragmentManager.findFragmentByTag(FRAGMENT_TAG); if (oldFragment != null) { oldFragment.dismiss(); } SupportDatePickerDialogFragment fragment = new SupportDatePickerDialogFragment(); if (options != null) { final Bundle args = createFragmentArguments(options); fragment.setArguments(args); } final DatePickerDialogListener listener = new DatePickerDialogListener(promise); fragment.setOnDismissListener(listener); fragment.setOnDateSetListener(listener); fragment.show(fragmentManager, FRAGMENT_TAG); } else { FragmentManager fragmentManager = activity.getFragmentManager(); DialogFragment oldFragment = (DialogFragment) fragmentManager.findFragmentByTag(FRAGMENT_TAG); if (oldFragment != null) { oldFragment.dismiss(); } DatePickerDialogFragment fragment = new DatePickerDialogFragment(); if (options != null) { final Bundle args = createFragmentArguments(options); fragment.setArguments(args); } final DatePickerDialogListener listener = new DatePickerDialogListener(promise); fragment.setOnDismissListener(listener); fragment.setOnDateSetListener(listener); fragment.show(fragmentManager, FRAGMENT_TAG); } } private Bundle createFragmentArguments(ReadableMap options) { final Bundle args = new Bundle(); if (options.hasKey(ARG_DATE) && !options.isNull(ARG_DATE)) { args.putLong(ARG_DATE, (long) options.getDouble(ARG_DATE)); } if (options.hasKey(ARG_MINDATE) && !options.isNull(ARG_MINDATE)) { args.putLong(ARG_MINDATE, (long) options.getDouble(ARG_MINDATE)); } if (options.hasKey(ARG_MAXDATE) && !options.isNull(ARG_MAXDATE)) { args.putLong(ARG_MAXDATE, (long) options.getDouble(ARG_MAXDATE)); } if (options.hasKey(ARG_MODE) && !options.isNull(ARG_MODE)) { args.putString(ARG_MODE, options.getString(ARG_MODE)); } return args; } }