mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-02-12 22:29:41 +08:00
Open source Android date and time pickers
Reviewed By: bestander Differential Revision: D2856486 fb-gh-sync-id: 0bb81136289e2f121387649765ba682103e4701b
This commit is contained in:
committed by
facebook-github-bot-8
parent
5f0ef12cb5
commit
9a0539d2c4
@@ -9,8 +9,6 @@
|
||||
|
||||
package com.facebook.react.bridge;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Interface that represents a JavaScript Promise which can be passed to the native module as a
|
||||
* method parameter.
|
||||
@@ -19,10 +17,38 @@ import javax.annotation.Nullable;
|
||||
* will be marked as "remoteAsync" and will return a promise when invoked from JavaScript.
|
||||
*/
|
||||
public interface Promise {
|
||||
|
||||
/**
|
||||
* Successfully resolve the Promise.
|
||||
*/
|
||||
void resolve(Object value);
|
||||
void reject(Throwable reason);
|
||||
|
||||
/**
|
||||
* Report an error which wasn't caused by an exception.
|
||||
*/
|
||||
void reject(String code, String message);
|
||||
|
||||
/**
|
||||
* Report an exception.
|
||||
*/
|
||||
void reject(String code, Throwable e);
|
||||
|
||||
/**
|
||||
* Report an exception with a custom error message.
|
||||
*/
|
||||
void reject(String code, String message, Throwable e);
|
||||
|
||||
/**
|
||||
* Report an error which wasn't caused by an exception.
|
||||
* @deprecated Prefer passing a module-specific error code to JS.
|
||||
* Using this method will pass the error code "EUNSPECIFIED".
|
||||
*/
|
||||
@Deprecated
|
||||
void reject(String reason);
|
||||
void reject(String code, Throwable extra);
|
||||
void reject(String code, String reason, @Nullable Throwable extra);
|
||||
void reject(String message);
|
||||
|
||||
/**
|
||||
* Report an exception, with default error code.
|
||||
* Useful in catch-all scenarios where it's unclear why the error occurred.
|
||||
*/
|
||||
void reject(Throwable reason);
|
||||
}
|
||||
|
||||
@@ -34,23 +34,28 @@ public class PromiseImpl implements Promise {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reject(Throwable reason) {
|
||||
reject(DEFAULT_ERROR, reason.getMessage(), reason);
|
||||
public void reject(String code, String message) {
|
||||
reject(code, message, /*Throwable*/null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public void reject(String reason) {
|
||||
reject(DEFAULT_ERROR, reason, null);
|
||||
public void reject(String message) {
|
||||
reject(DEFAULT_ERROR, message, /*Throwable*/null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reject(String code, Throwable extra) {
|
||||
reject(code, extra.getMessage(), extra);
|
||||
public void reject(String code, Throwable e) {
|
||||
reject(code, e.getMessage(), e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reject(String code, String reason, @Nullable Throwable extra) {
|
||||
public void reject(Throwable e) {
|
||||
reject(DEFAULT_ERROR, e.getMessage(), e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reject(String code, String message, @Nullable Throwable e) {
|
||||
if (mReject != null) {
|
||||
if (code == null) {
|
||||
code = DEFAULT_ERROR;
|
||||
@@ -60,7 +65,7 @@ public class PromiseImpl implements Promise {
|
||||
// error instance.
|
||||
WritableNativeMap errorInfo = new WritableNativeMap();
|
||||
errorInfo.putString("code", code);
|
||||
errorInfo.putString("message", reason);
|
||||
errorInfo.putString("message", message);
|
||||
// TODO(8850038): add the stack trace info in, need to figure out way to serialize that
|
||||
mReject.invoke(errorInfo);
|
||||
}
|
||||
|
||||
@@ -59,7 +59,6 @@ import com.facebook.react.common.ReactConstants;
|
||||
*/
|
||||
public class CameraRollManager extends ReactContextBaseJavaModule {
|
||||
|
||||
private static final String TAG = "Catalyst/CameraRollManager";
|
||||
private static final String ERROR_UNABLE_TO_LOAD = "E_UNABLE_TO_LOAD";
|
||||
private static final String ERROR_UNABLE_TO_LOAD_PERMISSION = "E_UNABLE_TO_LOAD_PERMISSION";
|
||||
private static final String ERROR_UNABLE_TO_SAVE = "E_UNABLE_TO_SAVE";
|
||||
@@ -145,7 +144,7 @@ public class CameraRollManager extends ReactContextBaseJavaModule {
|
||||
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
|
||||
pictures.mkdirs();
|
||||
if (!pictures.isDirectory()) {
|
||||
mPromise.reject(ERROR_UNABLE_TO_LOAD, "External storage pictures directory not available", null);
|
||||
mPromise.reject(ERROR_UNABLE_TO_LOAD, "External storage pictures directory not available");
|
||||
return;
|
||||
}
|
||||
File dest = new File(pictures, source.getName());
|
||||
@@ -178,7 +177,7 @@ public class CameraRollManager extends ReactContextBaseJavaModule {
|
||||
if (uri != null) {
|
||||
mPromise.resolve(uri.toString());
|
||||
} else {
|
||||
mPromise.reject(ERROR_UNABLE_TO_SAVE, "Could not add image to gallery", null);
|
||||
mPromise.reject(ERROR_UNABLE_TO_SAVE, "Could not add image to gallery");
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -302,7 +301,7 @@ public class CameraRollManager extends ReactContextBaseJavaModule {
|
||||
Images.Media.DATE_TAKEN + " DESC, " + Images.Media.DATE_MODIFIED + " DESC LIMIT " +
|
||||
(mFirst + 1)); // set LIMIT to first + 1 so that we know how to populate page_info
|
||||
if (photos == null) {
|
||||
mPromise.reject(ERROR_UNABLE_TO_LOAD, "Could not get photos", null);
|
||||
mPromise.reject(ERROR_UNABLE_TO_LOAD, "Could not get photos");
|
||||
} else {
|
||||
try {
|
||||
putEdges(resolver, photos, response, mFirst);
|
||||
@@ -412,7 +411,7 @@ public class CameraRollManager extends ReactContextBaseJavaModule {
|
||||
width = options.outWidth;
|
||||
height = options.outHeight;
|
||||
} catch (IOException e) {
|
||||
FLog.e(TAG, "Could not get width/height for " + photoUri.toString(), e);
|
||||
FLog.e(ReactConstants.TAG, "Could not get width/height for " + photoUri.toString(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
include_defs('//ReactAndroid/DEFS')
|
||||
|
||||
android_library(
|
||||
name = 'datepicker',
|
||||
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 = ':datepicker',
|
||||
)
|
||||
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* 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.Calendar;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.DatePickerDialog;
|
||||
import android.app.DatePickerDialog.OnDateSetListener;
|
||||
import android.app.Dialog;
|
||||
import android.app.DialogFragment;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnDismissListener;
|
||||
import android.os.Bundle;
|
||||
import android.widget.DatePicker;
|
||||
|
||||
@SuppressLint("ValidFragment")
|
||||
public class DatePickerDialogFragment extends DialogFragment {
|
||||
|
||||
/**
|
||||
* Minimum date supported by {@link DatePicker}, 01 Jan 1900
|
||||
*/
|
||||
private static final long DEFAULT_MIN_DATE = -2208988800001l;
|
||||
|
||||
@Nullable
|
||||
private OnDateSetListener mOnDateSetListener;
|
||||
@Nullable
|
||||
private OnDismissListener mOnDismissListener;
|
||||
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
Bundle args = getArguments();
|
||||
return createDialog(args, getActivity(), mOnDateSetListener);
|
||||
}
|
||||
|
||||
/*package*/ static Dialog createDialog(
|
||||
Bundle args, Context activityContext, @Nullable OnDateSetListener onDateSetListener) {
|
||||
final Calendar c = Calendar.getInstance();
|
||||
if (args != null && args.containsKey(DatePickerDialogModule.ARG_DATE)) {
|
||||
c.setTimeInMillis(args.getLong(DatePickerDialogModule.ARG_DATE));
|
||||
}
|
||||
final int year = c.get(Calendar.YEAR);
|
||||
final int month = c.get(Calendar.MONTH);
|
||||
final int day = c.get(Calendar.DAY_OF_MONTH);
|
||||
|
||||
final DatePickerDialog dialog =
|
||||
new DismissableDatePickerDialog(activityContext, onDateSetListener, year, month, day);
|
||||
final DatePicker datePicker = dialog.getDatePicker();
|
||||
|
||||
if (args != null && args.containsKey(DatePickerDialogModule.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(DatePickerDialogModule.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());
|
||||
} else {
|
||||
// This is to work around a bug in DatePickerDialog where it doesn't display a title showing
|
||||
// the date under certain conditions.
|
||||
datePicker.setMinDate(DEFAULT_MIN_DATE);
|
||||
}
|
||||
if (args != null && args.containsKey(DatePickerDialogModule.ARG_MAXDATE)) {
|
||||
// Set maxDate to the end of the day, same reason as for minDate.
|
||||
c.setTimeInMillis(args.getLong(DatePickerDialogModule.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());
|
||||
}
|
||||
|
||||
return dialog;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDismiss(DialogInterface dialog) {
|
||||
super.onDismiss(dialog);
|
||||
if (mOnDismissListener != null) {
|
||||
mOnDismissListener.onDismiss(dialog);
|
||||
}
|
||||
}
|
||||
|
||||
/*package*/ void setOnDateSetListener(@Nullable OnDateSetListener onDateSetListener) {
|
||||
mOnDateSetListener = onDateSetListener;
|
||||
}
|
||||
|
||||
/*package*/ void setOnDismissListener(@Nullable OnDismissListener onDismissListener) {
|
||||
mOnDismissListener = onDismissListener;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* {@link NativeModule} that allows JS to show a native date picker dialog and get called back when
|
||||
* the user selects a date.
|
||||
*/
|
||||
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 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>
|
||||
* </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));
|
||||
}
|
||||
return args;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* 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 android.app.DatePickerDialog;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Certain versions of Android (Jellybean-KitKat) have a bug where when dismissed, the
|
||||
* {@link DatePickerDialog} still calls the OnDateSetListener. This class works around that issue.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* See: <a href="https://code.google.com/p/android/issues/detail?id=34833">Issue 34833</a>
|
||||
* </p>
|
||||
*/
|
||||
public class DismissableDatePickerDialog extends DatePickerDialog {
|
||||
|
||||
public DismissableDatePickerDialog(
|
||||
Context context,
|
||||
@Nullable OnDateSetListener callback,
|
||||
int year,
|
||||
int monthOfYear,
|
||||
int dayOfMonth) {
|
||||
super(context, callback, year, monthOfYear, dayOfMonth);
|
||||
}
|
||||
|
||||
public DismissableDatePickerDialog(
|
||||
Context context,
|
||||
int theme,
|
||||
@Nullable OnDateSetListener callback,
|
||||
int year,
|
||||
int monthOfYear,
|
||||
int dayOfMonth) {
|
||||
super(context, theme, callback, year, monthOfYear, dayOfMonth);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
// do *not* call super.onStop() on KitKat on lower, as that would erroneously call the
|
||||
// OnDateSetListener when the dialog is dismissed, or call it twice when "OK" is pressed.
|
||||
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {
|
||||
super.onStop();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* 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 android.annotation.SuppressLint;
|
||||
import android.app.DatePickerDialog.OnDateSetListener;
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnDismissListener;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
|
||||
@SuppressLint("ValidFragment")
|
||||
public class SupportDatePickerDialogFragment extends DialogFragment {
|
||||
|
||||
@Nullable
|
||||
private OnDateSetListener mOnDateSetListener;
|
||||
@Nullable
|
||||
private OnDismissListener mOnDismissListener;
|
||||
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
final Bundle args = getArguments();
|
||||
return DatePickerDialogFragment.createDialog(args, getActivity(), mOnDateSetListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDismiss(DialogInterface dialog) {
|
||||
super.onDismiss(dialog);
|
||||
if (mOnDismissListener != null) {
|
||||
mOnDismissListener.onDismiss(dialog);
|
||||
}
|
||||
}
|
||||
|
||||
/*package*/ void setOnDateSetListener(@Nullable OnDateSetListener onDateSetListener) {
|
||||
mOnDateSetListener = onDateSetListener;
|
||||
}
|
||||
|
||||
/*package*/ void setOnDismissListener(@Nullable OnDismissListener onDismissListener) {
|
||||
mOnDismissListener = onDismissListener;
|
||||
}
|
||||
}
|
||||
@@ -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',
|
||||
)
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@ android_library(
|
||||
react_native_target('java/com/facebook/react/modules/camera:camera'),
|
||||
react_native_target('java/com/facebook/react/modules/clipboard:clipboard'),
|
||||
react_native_target('java/com/facebook/react/modules/core:core'),
|
||||
react_native_target('java/com/facebook/react/modules/datepicker:datepicker'),
|
||||
react_native_target('java/com/facebook/react/modules/debug:debug'),
|
||||
react_native_target('java/com/facebook/react/modules/dialog:dialog'),
|
||||
react_native_target('java/com/facebook/react/modules/fresco:fresco'),
|
||||
@@ -36,6 +37,7 @@ android_library(
|
||||
react_native_target('java/com/facebook/react/modules/netinfo:netinfo'),
|
||||
react_native_target('java/com/facebook/react/modules/network:network'),
|
||||
react_native_target('java/com/facebook/react/modules/storage:storage'),
|
||||
react_native_target('java/com/facebook/react/modules/timepicker:timepicker'),
|
||||
react_native_target('java/com/facebook/react/modules/toast:toast'),
|
||||
react_native_target('java/com/facebook/react/uimanager:uimanager'),
|
||||
react_native_target('java/com/facebook/react/modules/websocket:websocket'),
|
||||
|
||||
@@ -17,16 +17,19 @@ import com.facebook.react.ReactPackage;
|
||||
import com.facebook.react.bridge.JavaScriptModule;
|
||||
import com.facebook.react.bridge.NativeModule;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.modules.appstate.AppStateModule;
|
||||
import com.facebook.react.modules.camera.CameraRollManager;
|
||||
import com.facebook.react.modules.clipboard.ClipboardModule;
|
||||
import com.facebook.react.modules.dialog.DialogModule;
|
||||
import com.facebook.react.modules.datepicker.DatePickerDialogModule;
|
||||
import com.facebook.react.modules.fresco.FrescoModule;
|
||||
import com.facebook.react.modules.intent.IntentModule;
|
||||
import com.facebook.react.modules.location.LocationModule;
|
||||
import com.facebook.react.modules.netinfo.NetInfoModule;
|
||||
import com.facebook.react.modules.network.NetworkingModule;
|
||||
import com.facebook.react.modules.storage.AsyncStorageModule;
|
||||
import com.facebook.react.modules.timepicker.TimePickerDialogModule;
|
||||
import com.facebook.react.modules.toast.ToastModule;
|
||||
import com.facebook.react.modules.appstate.AppStateModule;
|
||||
import com.facebook.react.modules.websocket.WebSocketModule;
|
||||
import com.facebook.react.uimanager.ViewManager;
|
||||
import com.facebook.react.views.art.ARTRenderableViewManager;
|
||||
@@ -50,7 +53,6 @@ import com.facebook.react.views.view.ReactViewManager;
|
||||
import com.facebook.react.views.viewpager.ReactViewPagerManager;
|
||||
import com.facebook.react.views.swiperefresh.SwipeRefreshLayoutManager;
|
||||
import com.facebook.react.views.webview.ReactWebViewManager;
|
||||
import com.facebook.react.modules.clipboard.ClipboardModule;
|
||||
|
||||
/**
|
||||
* Package defining basic modules and view managers.
|
||||
@@ -64,12 +66,14 @@ public class MainReactPackage implements ReactPackage {
|
||||
new AsyncStorageModule(reactContext),
|
||||
new CameraRollManager(reactContext),
|
||||
new ClipboardModule(reactContext),
|
||||
new DatePickerDialogModule(reactContext),
|
||||
new DialogModule(reactContext),
|
||||
new FrescoModule(reactContext),
|
||||
new IntentModule(reactContext),
|
||||
new LocationModule(reactContext),
|
||||
new NetworkingModule(reactContext),
|
||||
new NetInfoModule(reactContext),
|
||||
new TimePickerDialogModule(reactContext),
|
||||
new ToastModule(reactContext),
|
||||
new WebSocketModule(reactContext));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user