From 25fa6964fc3fb53b6af48dee5a4daf2e50c56e82 Mon Sep 17 00:00:00 2001 From: wood1986 <5212215+wood1986@users.noreply.github.com> Date: Wed, 12 Jul 2023 04:47:14 -0700 Subject: [PATCH 1/7] feat: add timeZoneName props --- .gitignore | 2 + .../rndatetimepicker/Common.java | 93 +++- .../rndatetimepicker/DatePickerModule.java | 74 +-- .../KeepDateInRangeListener.java | 65 --- .../rndatetimepicker/RNConstants.java | 1 + .../rndatetimepicker/RNDate.java | 12 +- .../RNDatePickerDialogFragment.java | 63 +-- .../rndatetimepicker/TimePickerModule.java | 57 ++- example/App.js | 461 ++++++++++-------- example/e2e/detoxTest.spec.js | 310 ++++++++++-- example/e2e/utils/actions.js | 2 +- example/e2e/utils/matchers.js | 4 - example/ios/Podfile.lock | 381 +-------------- ios/RNDateTimePicker.m | 2 +- ios/RNDateTimePickerManager.m | 16 +- ios/RNDateTimePickerShadowView.h | 2 + ios/RNDateTimePickerShadowView.m | 27 +- ios/fabric/RNDateTimePickerComponentView.mm | 63 ++- jest/index.js | 13 +- package.json | 1 + src/DateTimePickerAndroid.android.js | 27 +- src/androidUtils.js | 23 +- src/datetimepicker.android.js | 2 + src/datetimepicker.ios.js | 4 +- src/index.d.ts | 10 +- src/specs/NativeComponentDateTimePicker.js | 2 + src/types.js | 19 +- src/utils.js | 17 +- yarn.lock | 9 +- 29 files changed, 838 insertions(+), 924 deletions(-) delete mode 100644 android/src/main/java/com/reactcommunity/rndatetimepicker/KeepDateInRangeListener.java diff --git a/.gitignore b/.gitignore index 2145c4cf..964f0b81 100644 --- a/.gitignore +++ b/.gitignore @@ -59,3 +59,5 @@ buck-out/ # CocoaPods example/ios/Pods/ + +.xcode.env \ No newline at end of file diff --git a/android/src/main/java/com/reactcommunity/rndatetimepicker/Common.java b/android/src/main/java/com/reactcommunity/rndatetimepicker/Common.java index b8c7acb3..665000cc 100644 --- a/android/src/main/java/com/reactcommunity/rndatetimepicker/Common.java +++ b/android/src/main/java/com/reactcommunity/rndatetimepicker/Common.java @@ -18,8 +18,15 @@ import androidx.fragment.app.FragmentManager; 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; public class Common { @@ -63,21 +70,18 @@ public static int getDefaultDialogButtonTextColor(@NonNull Context activity) { @NonNull public static DialogInterface.OnShowListener setButtonTextColor(@NonNull final 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 - - 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); - } + 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); + + int textColorPrimary = getDefaultDialogButtonTextColor(activityContext); + setTextColor(positiveButton, POSITIVE, args, needsColorOverride, textColorPrimary); + setTextColor(negativeButton, NEGATIVE, args, needsColorOverride, textColorPrimary); + setTextColor(neutralButton, NEUTRAL, args, needsColorOverride, textColorPrimary); }; } @@ -139,4 +143,63 @@ private static void setButtonLabel(Bundle buttonConfig, AlertDialog dialog, int } dialog.setButton(whichButton, buttonConfig.getString(LABEL), listener); } + + 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() fallback 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; + } } diff --git a/android/src/main/java/com/reactcommunity/rndatetimepicker/DatePickerModule.java b/android/src/main/java/com/reactcommunity/rndatetimepicker/DatePickerModule.java index 881ffa4d..441f258c 100644 --- a/android/src/main/java/com/reactcommunity/rndatetimepicker/DatePickerModule.java +++ b/android/src/main/java/com/reactcommunity/rndatetimepicker/DatePickerModule.java @@ -22,8 +22,6 @@ 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; @@ -41,8 +39,9 @@ public DatePickerModule(ReactApplicationContext reactContext) { super(reactContext); } + @NonNull @Override - public @NonNull String getName() { + public String getName() { return NAME; } @@ -60,31 +59,15 @@ public DatePickerDialogListener(final Promise promise, Bundle 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.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)); - } + result.putDouble("timestamp", calendar.getTimeInMillis()); + result.putDouble("utcOffset", calendar.getTimeZone().getOffset(calendar.getTimeInMillis()) / 1000 / 60); mPromise.resolve(result); mPromiseResolved = true; @@ -157,35 +140,32 @@ public void open(final ReadableMap options, final Promise promise) { final FragmentManager fragmentManager = activity.getSupportFragmentManager(); - UiThreadUtil.runOnUiThread(new Runnable() { - @Override - public void run() { - RNDatePickerDialogFragment oldFragment = - (RNDatePickerDialogFragment) fragmentManager.findFragmentByTag(NAME); + UiThreadUtil.runOnUiThread(() -> { + RNDatePickerDialogFragment oldFragment = + (RNDatePickerDialogFragment) fragmentManager.findFragmentByTag(NAME); - if (oldFragment != null) { - oldFragment.update(createFragmentArguments(options)); - return; - } + Bundle arguments = createFragmentArguments(options); - RNDatePickerDialogFragment fragment = new RNDatePickerDialogFragment(); + if (oldFragment != null) { + oldFragment.update(arguments); + return; + } - fragment.setArguments(createFragmentArguments(options)); + RNDatePickerDialogFragment fragment = new RNDatePickerDialogFragment(); - final DatePickerDialogListener listener = new DatePickerDialogListener(promise, createFragmentArguments(options)); - fragment.setOnDismissListener(listener); - fragment.setOnDateSetListener(listener); - fragment.setOnNeutralButtonActionListener(listener); - fragment.show(fragmentManager, NAME); - } + 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 = new Bundle(); - if (options.hasKey(RNConstants.ARG_VALUE) && !options.isNull(RNConstants.ARG_VALUE)) { - args.putLong(RNConstants.ARG_VALUE, (long) options.getDouble(RNConstants.ARG_VALUE)); - } + 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)); } diff --git a/android/src/main/java/com/reactcommunity/rndatetimepicker/KeepDateInRangeListener.java b/android/src/main/java/com/reactcommunity/rndatetimepicker/KeepDateInRangeListener.java deleted file mode 100644 index 0da88bfe..00000000 --- a/android/src/main/java/com/reactcommunity/rndatetimepicker/KeepDateInRangeListener.java +++ /dev/null @@ -1,65 +0,0 @@ -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))); - } -} diff --git a/android/src/main/java/com/reactcommunity/rndatetimepicker/RNConstants.java b/android/src/main/java/com/reactcommunity/rndatetimepicker/RNConstants.java index 291cb1ab..99530d51 100644 --- a/android/src/main/java/com/reactcommunity/rndatetimepicker/RNConstants.java +++ b/android/src/main/java/com/reactcommunity/rndatetimepicker/RNConstants.java @@ -12,6 +12,7 @@ public final class RNConstants { public static final String ARG_DISPLAY = "display"; public static final String ARG_DIALOG_BUTTONS = "dialogButtons"; 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"; public static final String ACTION_TIME_SET = "timeSetAction"; diff --git a/android/src/main/java/com/reactcommunity/rndatetimepicker/RNDate.java b/android/src/main/java/com/reactcommunity/rndatetimepicker/RNDate.java index 7cbdac72..137869a9 100644 --- a/android/src/main/java/com/reactcommunity/rndatetimepicker/RNDate.java +++ b/android/src/main/java/com/reactcommunity/rndatetimepicker/RNDate.java @@ -11,19 +11,11 @@ public RNDate(Bundle args) { now = Calendar.getInstance(); 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)); } public int year() { return now.get(Calendar.YEAR); } diff --git a/android/src/main/java/com/reactcommunity/rndatetimepicker/RNDatePickerDialogFragment.java b/android/src/main/java/com/reactcommunity/rndatetimepicker/RNDatePickerDialogFragment.java index 735e578c..fd55a382 100644 --- a/android/src/main/java/com/reactcommunity/rndatetimepicker/RNDatePickerDialogFragment.java +++ b/android/src/main/java/com/reactcommunity/rndatetimepicker/RNDatePickerDialogFragment.java @@ -30,7 +30,6 @@ import java.util.Calendar; import java.util.Locale; -import java.util.TimeZone; @SuppressLint("ValidFragment") public class RNDatePickerDialogFragment extends DialogFragment { @@ -95,8 +94,6 @@ DatePickerDialog getDialog( private DatePickerDialog createDialog(Bundle args) { Context activityContext = getActivity(); - final Calendar c = Calendar.getInstance(); - DatePickerDialog dialog = getDialog(args, activityContext, mOnDateSetListener); if (args != null) { @@ -109,40 +106,27 @@ private DatePickerDialog createDialog(Bundle args) { } 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)); - } 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(RNConstants.DEFAULT_MIN_DATE); + if (args.containsKey(RNConstants.ARG_MINDATE)) { + datePicker.setMinDate(minDate); } - 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)); + 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)); + } + }); } if (args != null && args.containsKey(RNConstants.ARG_TESTID)) { @@ -152,23 +136,6 @@ private DatePickerDialog createDialog(Bundle args) { 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; - } - - return null; - } - - private static int getOffset(Calendar c, Integer timeZoneOffsetInMilliseconds) { - if (timeZoneOffsetInMilliseconds != null) { - return TimeZone.getDefault().getOffset(c.getTimeInMillis()) - timeZoneOffsetInMilliseconds; - } - return 0; - } - @Override public void onDismiss(@NonNull DialogInterface dialog) { super.onDismiss(dialog); diff --git a/android/src/main/java/com/reactcommunity/rndatetimepicker/TimePickerModule.java b/android/src/main/java/com/reactcommunity/rndatetimepicker/TimePickerModule.java index f948d7ad..034df515 100644 --- a/android/src/main/java/com/reactcommunity/rndatetimepicker/TimePickerModule.java +++ b/android/src/main/java/com/reactcommunity/rndatetimepicker/TimePickerModule.java @@ -25,6 +25,8 @@ 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. @@ -47,19 +49,27 @@ public String getName() { private class TimePickerDialogListener implements OnTimeSetListener, OnDismissListener, OnClickListener { private final Promise mPromise; + private final Bundle mArgs; private boolean mPromiseResolved = false; - public TimePickerDialogListener(Promise promise) { + 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.putInt("hour", hour); - result.putInt("minute", minute); + result.putDouble("timestamp", calendar.getTimeInMillis()); + result.putDouble("utcOffset", calendar.getTimeZone().getOffset(calendar.getTimeInMillis()) / 1000 / 60); + mPromise.resolve(result); mPromiseResolved = true; } @@ -105,35 +115,32 @@ public void open(final ReadableMap options, final Promise promise) { // (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(NAME); + UiThreadUtil.runOnUiThread(() -> { + RNTimePickerDialogFragment oldFragment = + (RNTimePickerDialogFragment) fragmentManager.findFragmentByTag(NAME); + + Bundle arguments = createFragmentArguments(options); - if (oldFragment != null) { - oldFragment.update(createFragmentArguments(options)); - return; - } + if (oldFragment != null) { + oldFragment.update(arguments); + return; + } - RNTimePickerDialogFragment fragment = new RNTimePickerDialogFragment(); + RNTimePickerDialogFragment fragment = new RNTimePickerDialogFragment(); - fragment.setArguments(createFragmentArguments(options)); + fragment.setArguments(arguments); - final TimePickerDialogListener listener = new TimePickerDialogListener(promise); - fragment.setOnDismissListener(listener); - fragment.setOnTimeSetListener(listener); - fragment.setOnNeutralButtonActionListener(listener); - fragment.show(fragmentManager, NAME); - } + 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 = new Bundle(); - if (options.hasKey(RNConstants.ARG_VALUE) && !options.isNull(RNConstants.ARG_VALUE)) { - args.putLong(RNConstants.ARG_VALUE, (long) options.getDouble(RNConstants.ARG_VALUE)); - } + 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)); } @@ -147,7 +154,7 @@ private Bundle createFragmentArguments(ReadableMap options) { 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)); + args.putLong(RNConstants.ARG_TZOFFSET_MINS, (long) options.getDouble(RNConstants.ARG_TZOFFSET_MINS)); } return args; } diff --git a/example/App.js b/example/App.js index 95ce3de6..4cad63de 100644 --- a/example/App.js +++ b/example/App.js @@ -11,13 +11,14 @@ import { useColorScheme, Switch, Alert, + FlatList, } from 'react-native'; import DateTimePicker from '@react-native-community/datetimepicker'; import SegmentedControl from './SegmentedControl'; import {Colors} from 'react-native/Libraries/NewAppScreen'; import React, {useRef, useState} from 'react'; import {Picker} from 'react-native-windows'; -import moment from 'moment'; +import moment from 'moment-timezone'; import { ANDROID_MODE, DAY_OF_WEEK, @@ -25,7 +26,22 @@ import { ANDROID_DISPLAY, IOS_DISPLAY, } from '../src/constants'; -// import * as RNLocalize from 'react-native-localize'; +import * as RNLocalize from 'react-native-localize'; + +const timezone = [ + 120, + 0, + -120, + undefined, + 'America/New_York', + 'America/Vancouver', + 'Europe/London', + 'Europe/Istanbul', + 'Asia/Hong_Kong', + 'Australia/Brisbane', + 'Australia/Sydney', + 'Australia/Adelaide', +]; const ThemedText = (props) => { const isDarkMode = useColorScheme() === 'dark'; @@ -49,6 +65,17 @@ const ThemedTextInput = (props) => { }); }; +const Info = ({testID, title, body}) => { + return ( + + {title} + + {body} + + + ); +}; + const MODE_VALUES = Platform.select({ ios: Object.values(IOS_MODE), android: Object.values(ANDROID_MODE), @@ -63,10 +90,11 @@ const MINUTE_INTERVALS = [1, 2, 3, 4, 5, 6, 10, 12, 15, 20, 30]; export const App = () => { // Sat, 13 Nov 2021 10:00:00 GMT (local: Saturday, November 13, 2021 11:00:00 AM GMT+01:00) - const sourceMoment = moment.unix(1636797600); + const sourceMoment = moment.unix(1636765200); const sourceDate = sourceMoment.local().toDate(); const [date, setDate] = useState(sourceDate); const [tzOffsetInMinutes, setTzOffsetInMinutes] = useState(undefined); + const [tzName, setTzName] = useState(RNLocalize.getTimeZone()); const [mode, setMode] = useState(MODE_VALUES[0]); const [show, setShow] = useState(false); const [textColor, setTextColor] = useState(); @@ -80,8 +108,8 @@ export const App = () => { // Windows-specific const [time, setTime] = useState(undefined); - const [maxDate, setMinDate] = useState(new Date('2021')); - const [minDate, setMaxDate] = useState(new Date('2018')); + const [maxDate] = useState(new Date('2021')); + const [minDate] = useState(new Date('2018')); const [is24Hours, set24Hours] = useState(false); const [firstDayOfWeek, setFirstDayOfWeek] = useState(DAY_OF_WEEK.Monday); const [dateFormat, setDateFormat] = useState('longdate'); @@ -132,8 +160,29 @@ export const App = () => { backgroundColor: isDarkMode ? Colors.dark : Colors.lighter, }; + const renderItem = ({item}) => { + const isNumber = !isNaN(item); + const title = isNumber + ? item > 0 + ? `+${item} mins` + : `${item} mins` + : item; + return ( + +