Open source Android date and time pickers

Reviewed By: bestander

Differential Revision: D2856486

fb-gh-sync-id: 0bb81136289e2f121387649765ba682103e4701b
This commit is contained in:
Martin Konicek
2016-01-26 10:29:47 -08:00
committed by facebook-github-bot-8
parent 5f0ef12cb5
commit 9a0539d2c4
29 changed files with 1275 additions and 41 deletions

View File

@@ -0,0 +1,20 @@
include_defs('//ReactAndroid/DEFS')
android_library(
name = 'timepicker',
srcs = glob(['**/*.java']),
deps = [
react_native_target('java/com/facebook/react/bridge:bridge'),
react_native_target('java/com/facebook/react/common:common'),
react_native_dep('third-party/android/support/v4:lib-support-v4'),
react_native_dep('third-party/java/infer-annotations:infer-annotations'),
react_native_dep('third-party/java/jsr-305:jsr-305'),
],
visibility = [
'PUBLIC',
],
)
project_config(
src_target = ':timepicker',
)

View File

@@ -0,0 +1,58 @@
/**
* 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.timepicker;
import javax.annotation.Nullable;
import android.app.TimePickerDialog;
import android.content.Context;
import android.os.Build;
/**
* <p>
* Certain versions of Android (Jellybean-KitKat) have a bug where when dismissed, the
* {@link TimePickerDialog} still calls the OnTimeSetListener. This class works around that issue
* by *not* calling super.onStop on KitKat on lower, as that would erroneously call the
* OnTimeSetListener when the dialog is dismissed, or call it twice when "OK" is pressed.
* </p>
*
* <p>
* See: <a href="https://code.google.com/p/android/issues/detail?id=34833">Issue 34833</a>
* </p>
*/
public class DismissableTimePickerDialog extends TimePickerDialog {
public DismissableTimePickerDialog(
Context context,
@Nullable OnTimeSetListener callback,
int hourOfDay,
int minute,
boolean is24HourView) {
super(context, callback, hourOfDay, minute, is24HourView);
}
public DismissableTimePickerDialog(
Context context,
int theme,
@Nullable OnTimeSetListener callback,
int hourOfDay,
int minute,
boolean is24HourView) {
super(context, theme, callback, hourOfDay, minute, is24HourView);
}
@Override
protected void onStop() {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {
super.onStop();
}
}
}

View File

@@ -0,0 +1,50 @@
/**
* 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.timepicker;
import javax.annotation.Nullable;
import android.app.Dialog;
import android.app.TimePickerDialog.OnTimeSetListener;
import android.content.DialogInterface;
import android.content.DialogInterface.OnDismissListener;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
@SuppressWarnings("ValidFragment")
public class SupportTimePickerDialogFragment extends DialogFragment {
@Nullable
private OnTimeSetListener mOnTimeSetListener;
@Nullable
private OnDismissListener mOnDismissListener;
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Bundle args = getArguments();
return TimePickerDialogFragment.createDialog(args, getActivity(), mOnTimeSetListener);
}
@Override
public void onDismiss(DialogInterface dialog) {
super.onDismiss(dialog);
if (mOnDismissListener != null) {
mOnDismissListener.onDismiss(dialog);
}
}
public void setOnDismissListener(@Nullable OnDismissListener onDismissListener) {
mOnDismissListener = onDismissListener;
}
public void setOnTimeSetListener(@Nullable OnTimeSetListener onTimeSetListener) {
mOnTimeSetListener = onTimeSetListener;
}
}

View File

@@ -0,0 +1,78 @@
/**
* 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.timepicker;
import javax.annotation.Nullable;
import java.util.Calendar;
import android.app.Dialog;
import android.app.DialogFragment;
import android.app.TimePickerDialog.OnTimeSetListener;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnDismissListener;
import android.os.Bundle;
import android.text.format.DateFormat;
@SuppressWarnings("ValidFragment")
public class TimePickerDialogFragment extends DialogFragment {
@Nullable
private OnTimeSetListener mOnTimeSetListener;
@Nullable
private OnDismissListener mOnDismissListener;
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Bundle args = getArguments();
return createDialog(args, getActivity(), mOnTimeSetListener);
}
/*package*/ static Dialog createDialog(
Bundle args, Context activityContext, @Nullable OnTimeSetListener onTimeSetListener
) {
final Calendar now = Calendar.getInstance();
int hour = now.get(Calendar.HOUR_OF_DAY);
int minute = now.get(Calendar.MINUTE);
boolean is24hour = DateFormat.is24HourFormat(activityContext);
if (args != null) {
hour = args.getInt(TimePickerDialogModule.ARG_HOUR, now.get(Calendar.HOUR_OF_DAY));
minute = args.getInt(TimePickerDialogModule.ARG_MINUTE, now.get(Calendar.MINUTE));
is24hour = args.getBoolean(
TimePickerDialogModule.ARG_IS24HOUR,
DateFormat.is24HourFormat(activityContext));
}
return new DismissableTimePickerDialog(
activityContext,
onTimeSetListener,
hour,
minute,
is24hour);
}
@Override
public void onDismiss(DialogInterface dialog) {
super.onDismiss(dialog);
if (mOnDismissListener != null) {
mOnDismissListener.onDismiss(dialog);
}
}
public void setOnDismissListener(@Nullable OnDismissListener onDismissListener) {
mOnDismissListener = onDismissListener;
}
public void setOnTimeSetListener(@Nullable OnTimeSetListener onTimeSetListener) {
mOnTimeSetListener = onTimeSetListener;
}
}

View File

@@ -0,0 +1,153 @@
/**
* 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.timepicker;
import javax.annotation.Nullable;
import java.util.Map;
import android.app.Activity;
import android.app.DialogFragment;
import android.app.FragmentManager;
import android.app.TimePickerDialog.OnTimeSetListener;
import android.content.DialogInterface;
import android.content.DialogInterface.OnDismissListener;
import android.os.Bundle;
import android.widget.TimePicker;
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;
/**
* {@link NativeModule} that allows JS to show a native time picker dialog and get called back when
* the user selects a time.
*/
public class TimePickerDialogModule extends ReactContextBaseJavaModule {
@VisibleForTesting
public static final String FRAGMENT_TAG = "TimePickerAndroid";
private static final String ERROR_NO_ACTIVITY = "E_NO_ACTIVITY";
/* package */ static final String ARG_HOUR = "hour";
/* package */ static final String ARG_MINUTE = "minute";
/* package */ static final String ARG_IS24HOUR = "is24Hour";
/* package */ static final String ACTION_TIME_SET = "timeSetAction";
/* package */ static final String ACTION_DISMISSED = "dismissedAction";
public TimePickerDialogModule(ReactApplicationContext reactContext) {
super(reactContext);
}
@Override
public String getName() {
return "TimePickerAndroid";
}
private class TimePickerDialogListener implements OnTimeSetListener, OnDismissListener {
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().hasActiveCatalystInstance()) {
WritableMap result = new WritableNativeMap();
result.putString("action", 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().hasActiveCatalystInstance()) {
WritableMap result = new WritableNativeMap();
result.putString("action", ACTION_DISMISSED);
mPromise.resolve(result);
mPromiseResolved = true;
}
}
}
@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 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.
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();
}
SupportTimePickerDialogFragment fragment = new SupportTimePickerDialogFragment();
if (options != null) {
Bundle args = createFragmentArguments(options);
fragment.setArguments(args);
}
TimePickerDialogListener listener = new TimePickerDialogListener(promise);
fragment.setOnDismissListener(listener);
fragment.setOnTimeSetListener(listener);
fragment.show(fragmentManager, FRAGMENT_TAG);
} else {
FragmentManager fragmentManager = activity.getFragmentManager();
DialogFragment oldFragment = (DialogFragment) fragmentManager.findFragmentByTag(FRAGMENT_TAG);
if (oldFragment != null) {
oldFragment.dismiss();
}
TimePickerDialogFragment fragment = new TimePickerDialogFragment();
if (options != null) {
final Bundle args = createFragmentArguments(options);
fragment.setArguments(args);
}
TimePickerDialogListener listener = new TimePickerDialogListener(promise);
fragment.setOnDismissListener(listener);
fragment.setOnTimeSetListener(listener);
fragment.show(fragmentManager, FRAGMENT_TAG);
}
}
private Bundle createFragmentArguments(ReadableMap options) {
final Bundle args = new Bundle();
if (options.hasKey(ARG_HOUR) && !options.isNull(ARG_HOUR)) {
args.putInt(ARG_HOUR, options.getInt(ARG_HOUR));
}
if (options.hasKey(ARG_MINUTE) && !options.isNull(ARG_MINUTE)) {
args.putInt(ARG_MINUTE, options.getInt(ARG_MINUTE));
}
if (options.hasKey(ARG_IS24HOUR) && !options.isNull(ARG_IS24HOUR)) {
args.putBoolean(ARG_IS24HOUR, options.getBoolean(ARG_IS24HOUR));
}
return args;
}
}