diff --git a/README.md b/README.md index e26c432..687d7c6 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ The mode of the date picker. Type: String -Values: `date` | `time` | `datetime` (iOS, Windows only) +Values: `date` | `time` | `datetime` (iOS, Windows only) | `duration` (iOS, Android only) Default: `date` @@ -213,6 +213,13 @@ Type: Integer Default: `1` +### countDownDuration - iOS +Represents the displayed duration in seconds. + +Type: Integer + +Default: `0` + ### popoverArrowDirection - iOS Force the UIPopoverArrowDirection enum. The value `any` will revert to default `UIPopoverArrowDirectionAny` and let the app choose the proper direction itself. diff --git a/plugin.xml b/plugin.xml index 3a23c2c..1db8b11 100644 --- a/plugin.xml +++ b/plugin.xml @@ -28,6 +28,7 @@ + diff --git a/src/android/CustomTimePickerDialog.java b/src/android/CustomTimePickerDialog.java new file mode 100644 index 0000000..d9e4804 --- /dev/null +++ b/src/android/CustomTimePickerDialog.java @@ -0,0 +1,128 @@ +package com.plugin.datepicker; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; + +import android.app.TimePickerDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.widget.NumberPicker; +import android.widget.TimePicker; +import android.os.Bundle; + +public class CustomTimePickerDialog extends TimePickerDialog { + + final OnTimeSetListener mCallback; + TimePicker mTimePicker; + final int increment; + final int theme; + private int minHour = 0; + private int maxHour = 24; + private int minMinute = 0; + private int maxMinute = 60; + + public int getMinHour() { + return minHour; + } + + public void setMinHour(int minHour) { + this.minHour = minHour; + } + + public void setMaxHour(int maxHour) { + this.maxHour = maxHour; + } + + public void setMinMinute(int minMinute) { + this.minMinute = minMinute; + } + + public void setMaxMinute(int maxMinute) { + this.maxMinute = maxMinute; + } + + public CustomTimePickerDialog(Context context, int theme, OnTimeSetListener callBack, int hourOfDay, int minute, boolean is24HourView, int increment) + { + super(context, theme, callBack, hourOfDay, minute/increment, is24HourView); + this.mCallback = callBack; + this.increment = increment; + this.theme = theme; + } + + @Override + public void onClick(DialogInterface dialog, int which) { + if (mCallback != null && mTimePicker!=null) { + mTimePicker.clearFocus(); + int minutes = theme != 2 ? mTimePicker.getCurrentMinute(): mTimePicker.getCurrentMinute()*increment; + mCallback.onTimeSet(mTimePicker, mTimePicker.getCurrentHour(), + minutes); + } + } + + @Override + public void updateTime(int hourOfDay, int minuteOfHour) { + mTimePicker.setCurrentHour(hourOfDay); + mTimePicker.setCurrentMinute((minuteOfHour-minMinute)/increment); + } + + public void updateTimeClock(int hourOfDay, int minuteOfHour) { + if(minuteOfHour == maxMinute){ + minuteOfHour = 0; + } + mTimePicker.setCurrentHour(hourOfDay); + mTimePicker.setCurrentMinute(minuteOfHour); + } + + @Override + protected void onStop() + { + // override and do nothing + } + + private void setHourSpinner(TimePicker timePicker, Field hour) throws IllegalAccessException{ + NumberPicker mHourSpinner = (NumberPicker)timePicker.findViewById(hour.getInt(null)); + mHourSpinner.setMinValue(minHour); + mHourSpinner.setMaxValue(maxHour-1); + List displayedHoursValues = new ArrayList(); + for(int i=minHour;i displayedMinutesValues = new ArrayList(); + for(int i=newMin;i rClass = Class.forName("com.android.internal.R$id"); + Field timePicker = rClass.getField("timePicker"); + this.mTimePicker = (TimePicker)findViewById(timePicker.getInt(null)); + Field m = rClass.getField("minute"); + Field h = rClass.getField("hour"); + + setHourSpinner(this.mTimePicker, h); + setMinuteSpinner(this.mTimePicker, m); + } + catch (Exception e) + { + e.printStackTrace(); + } + } +} diff --git a/src/android/DatePickerPlugin.java b/src/android/DatePickerPlugin.java index b76edcc..e3e0860 100644 --- a/src/android/DatePickerPlugin.java +++ b/src/android/DatePickerPlugin.java @@ -16,7 +16,6 @@ import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.TimeZone; -import java.util.Random; import org.apache.cordova.CallbackContext; import org.apache.cordova.CordovaPlugin; @@ -27,7 +26,6 @@ import android.annotation.SuppressLint; import android.app.DatePickerDialog; import android.app.DatePickerDialog.OnDateSetListener; -import android.app.TimePickerDialog; import android.app.TimePickerDialog.OnTimeSetListener; import android.content.Context; import android.content.DialogInterface; @@ -42,10 +40,11 @@ public class DatePickerPlugin extends CordovaPlugin { private static final String ACTION_DATE = "date"; private static final String ACTION_TIME = "time"; + private static final String ACTION_DURATION = "duration"; private static final String RESULT_ERROR = "error"; private static final String RESULT_CANCEL = "cancel"; private final String pluginName = "DatePickerPlugin"; - + // On some devices, onDateSet or onTimeSet are being called twice private boolean called = false; private boolean canceled = false; @@ -68,12 +67,13 @@ public synchronized void show(final JSONArray data, final CallbackContext callba Context currentCtx = cordova.getActivity(); Runnable runnable; JsonDate jsonDate = new JsonDate().fromJson(data); - - // Retrieve Android theme - JSONObject options = data.optJSONObject(0); - int theme = options.optInt("androidTheme", 1); - if (ACTION_TIME.equalsIgnoreCase(jsonDate.action)) { + // Retrieve Android theme + JSONObject options = data.optJSONObject(0); + int theme = options.optInt("androidTheme", 1); + + if (ACTION_TIME.equalsIgnoreCase(jsonDate.action) || + ACTION_DURATION.equalsIgnoreCase(jsonDate.action)) { runnable = runnableTimeDialog(datePickerPlugin, theme, currentCtx, callbackContext, jsonDate, Calendar.getInstance(TimeZone.getDefault())); @@ -83,30 +83,36 @@ public synchronized void show(final JSONArray data, final CallbackContext callba cordova.getActivity().runOnUiThread(runnable); } - + private TimePicker timePicker; private int timePickerHour = 0; private int timePickerMinute = 0; - + private Runnable runnableTimeDialog(final DatePickerPlugin datePickerPlugin, - final int theme, final Context currentCtx, final CallbackContext callbackContext, - final JsonDate jsonDate, final Calendar calendarDate) { + final int theme, final Context currentCtx, final CallbackContext callbackContext, + final JsonDate jsonDate, final Calendar calendarDate) { return new Runnable() { @Override public void run() { final TimeSetListener timeSetListener = new TimeSetListener(datePickerPlugin, callbackContext, calendarDate); - final TimePickerDialog timeDialog = new TimePickerDialog(currentCtx, theme, timeSetListener, jsonDate.hour, - jsonDate.minutes, jsonDate.is24Hour) { + final CustomTimePickerDialog timeDialog = new CustomTimePickerDialog(currentCtx, theme, timeSetListener, jsonDate.hour, + jsonDate.minutes, jsonDate.is24Hour, jsonDate.minuteInterval) { public void onTimeChanged(TimePicker view, int hourOfDay, int minute) { timePicker = view; timePickerHour = hourOfDay; - timePickerMinute = minute; + if(theme != 2){ + int offset = minute%jsonDate.minuteInterval != 0 ? jsonDate.minuteInterval: 0; + timePickerMinute = (minute/jsonDate.minuteInterval)*jsonDate.minuteInterval + offset; + updateTimeClock(timePickerHour, timePickerMinute); + } else { + timePickerMinute = minute; + } } }; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { timeDialog.setCancelable(true); timeDialog.setCanceledOnTouchOutside(false); - + if (!jsonDate.titleText.isEmpty()){ timeDialog.setTitle(jsonDate.titleText); } @@ -120,8 +126,8 @@ public void onClick(DialogInterface dialog, int which) { } } }); - } - String labelCancel = jsonDate.cancelText.isEmpty() ? currentCtx.getString(android.R.string.cancel) : jsonDate.cancelText; + } + String labelCancel = jsonDate.cancelText.isEmpty() ? currentCtx.getString(android.R.string.cancel) : jsonDate.cancelText; timeDialog.setButton(DialogInterface.BUTTON_NEGATIVE, labelCancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { @@ -132,13 +138,30 @@ public void onClick(DialogInterface dialog, int which) { String labelOk = jsonDate.okText.isEmpty() ? currentCtx.getString(android.R.string.ok) : jsonDate.okText; timeDialog.setButton(DialogInterface.BUTTON_POSITIVE, labelOk, timeDialog); } + + Calendar todayDate = calendarDate.getInstance(); + todayDate.set(Calendar.MINUTE, 0); + todayDate.set(Calendar.SECOND, 0); + todayDate.set(Calendar.MILLISECOND, 0); + + calendarDate.set(Calendar.MINUTE, 0); + calendarDate.set(Calendar.SECOND, 0); + calendarDate.set(Calendar.MILLISECOND, 0); + + if (todayDate.getTime().compareTo(calendarDate.getTime()) == 0 && + !ACTION_DURATION.equalsIgnoreCase(jsonDate.action)) + { + timeDialog.setMinHour(calendarDate.get(Calendar.HOUR_OF_DAY)); + } else { + timeDialog.setMinHour(0); + } + timeDialog.show(); - timeDialog.updateTime(new Random().nextInt(23), new Random().nextInt(59)); timeDialog.updateTime(jsonDate.hour, jsonDate.minutes); } }; } - + private Runnable runnableDatePicker( final DatePickerPlugin datePickerPlugin, final int theme, final Context currentCtx, @@ -155,58 +178,72 @@ public void run() { else { prepareDialogPreHoneycomb(dateDialog, callbackContext, currentCtx, jsonDate); } - + dateDialog.show(); } }; } - - private void prepareDialog(final DatePickerDialog dateDialog, final OnDateSetListener dateListener, - final CallbackContext callbackContext, Context currentCtx, JsonDate jsonDate) { + + private void prepareDialog(final DatePickerDialog dateDialog, final OnDateSetListener dateListener, + final CallbackContext callbackContext, Context currentCtx, JsonDate jsonDate) { dateDialog.setCancelable(true); dateDialog.setCanceledOnTouchOutside(false); if (!jsonDate.titleText.isEmpty()){ dateDialog.setTitle(jsonDate.titleText); } if (!jsonDate.todayText.isEmpty()){ - dateDialog.setButton(DialogInterface.BUTTON_NEUTRAL, jsonDate.todayText, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - Calendar now = Calendar.getInstance(); - DatePicker datePicker = dateDialog.getDatePicker(); + dateDialog.setButton(DialogInterface.BUTTON_NEUTRAL, jsonDate.todayText, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + Calendar now = Calendar.getInstance(); + DatePicker datePicker = dateDialog.getDatePicker(); dateListener.onDateSet(datePicker, now.get(Calendar.YEAR), now.get(Calendar.MONTH), now.get(Calendar.DAY_OF_MONTH)); - } - }); - } - String labelCancel = jsonDate.cancelText.isEmpty() ? currentCtx.getString(android.R.string.cancel) : jsonDate.cancelText; + } + }); + } + String labelCancel = jsonDate.cancelText.isEmpty() ? currentCtx.getString(android.R.string.cancel) : jsonDate.cancelText; dateDialog.setButton(DialogInterface.BUTTON_NEGATIVE, labelCancel, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { + @Override + public void onClick(DialogInterface dialog, int which) { canceled = true; callbackContext.error(RESULT_CANCEL); - } - }); + } + }); String labelOk = jsonDate.okText.isEmpty() ? currentCtx.getString(android.R.string.ok) : jsonDate.okText; dateDialog.setButton(DialogInterface.BUTTON_POSITIVE, labelOk, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { + @Override + public void onClick(DialogInterface dialog, int which) { DatePicker datePicker = dateDialog.getDatePicker(); datePicker.clearFocus(); dateListener.onDateSet(datePicker, datePicker.getYear(), datePicker.getMonth(), datePicker.getDayOfMonth()); - } - }); - - DatePicker dp = dateDialog.getDatePicker(); + } + }); + + DatePicker dp = dateDialog.getDatePicker(); if(jsonDate.minDate > 0) { - dp.setMinDate(jsonDate.minDate); + final Calendar minDate = Calendar.getInstance(); + minDate.setTimeInMillis(jsonDate.minDate); + minDate.set(Calendar.HOUR_OF_DAY, minDate.getMinimum(Calendar.HOUR_OF_DAY)); + minDate.set(Calendar.MINUTE, minDate.getMinimum(Calendar.MINUTE)); + minDate.set(Calendar.SECOND, minDate.getMinimum(Calendar.SECOND)); + minDate.set(Calendar.MILLISECOND, minDate.getMinimum(Calendar.MILLISECOND)); + + dp.setMinDate(minDate.getTimeInMillis()); } if(jsonDate.maxDate > 0 && jsonDate.maxDate > jsonDate.minDate) { - dp.setMaxDate(jsonDate.maxDate); + final Calendar maxDate = Calendar.getInstance(); + maxDate.setTimeInMillis(jsonDate.maxDate); + maxDate.set(Calendar.HOUR_OF_DAY, maxDate.getMaximum(Calendar.HOUR_OF_DAY)); + maxDate.set(Calendar.MINUTE, maxDate.getMaximum(Calendar.MINUTE)); + maxDate.set(Calendar.SECOND, maxDate.getMaximum(Calendar.SECOND)); + maxDate.set(Calendar.MILLISECOND, maxDate.getMaximum(Calendar.MILLISECOND)); + + dp.setMaxDate(maxDate.getTimeInMillis()); } } - + private void prepareDialogPreHoneycomb(DatePickerDialog dateDialog, - final CallbackContext callbackContext, Context currentCtx, final JsonDate jsonDate){ + final CallbackContext callbackContext, Context currentCtx, final JsonDate jsonDate){ java.lang.reflect.Field mDatePickerField = null; try { mDatePickerField = dateDialog.getClass().getDeclaredField("mDatePicker"); @@ -229,28 +266,28 @@ private void prepareDialogPreHoneycomb(DatePickerDialog dateDialog, endDate.setTimeInMillis(jsonDate.maxDate); final int minYear = startDate.get(Calendar.YEAR); - final int minMonth = startDate.get(Calendar.MONTH); - final int minDay = startDate.get(Calendar.DAY_OF_MONTH); - final int maxYear = endDate.get(Calendar.YEAR); - final int maxMonth = endDate.get(Calendar.MONTH); - final int maxDay = endDate.get(Calendar.DAY_OF_MONTH); + final int minMonth = startDate.get(Calendar.MONTH); + final int minDay = startDate.get(Calendar.DAY_OF_MONTH); + final int maxYear = endDate.get(Calendar.YEAR); + final int maxMonth = endDate.get(Calendar.MONTH); + final int maxDay = endDate.get(Calendar.DAY_OF_MONTH); if(startDate !=null || endDate != null) { pickerView.init(jsonDate.year, jsonDate.month, jsonDate.day, new OnDateChangedListener() { - @Override + @Override public void onDateChanged(DatePicker view, int year, int month, int day) { - if(jsonDate.maxDate > 0 && jsonDate.maxDate > jsonDate.minDate) { - if(year > maxYear || month > maxMonth && year == maxYear || day > maxDay && year == maxYear && month == maxMonth){ - view.updateDate(maxYear, maxMonth, maxDay); - } - } - if(jsonDate.minDate > 0) { - if(year < minYear || month < minMonth && year == minYear || day < minDay && year == minYear && month == minMonth) { - view.updateDate(minYear, minMonth, minDay); - } - } - } - }); + if(jsonDate.maxDate > 0 && jsonDate.maxDate > jsonDate.minDate) { + if(year > maxYear || month > maxMonth && year == maxYear || day > maxDay && year == maxYear && month == maxMonth){ + view.updateDate(maxYear, maxMonth, maxDay); + } + } + if(jsonDate.minDate > 0) { + if(year < minYear || month < minMonth && year == minYear || day < minDay && year == minYear && month == minMonth) { + view.updateDate(minYear, minMonth, minDay); + } + } + } + }); } } @@ -264,7 +301,7 @@ private DateSetListener(DatePickerPlugin datePickerPlugin, int theme, CallbackCo this.datePickerPlugin = datePickerPlugin; this.callbackContext = callbackContext; this.jsonDate = jsonDate; - this.theme = theme; + this.theme = theme; } /** @@ -277,24 +314,24 @@ public void onDateSet(final DatePicker view, final int year, final int monthOfYe } called = true; canceled = false; - + Log.d("onDateSet", "called: " + called); Log.d("onDateSet", "canceled: " + canceled); Log.d("onDateSet", "mode: " + jsonDate.action); - + if (ACTION_DATE.equalsIgnoreCase(jsonDate.action)) { String returnDate = year + "/" + (monthOfYear + 1) + "/" + dayOfMonth; Log.d("onDateSet", "returnDate: " + returnDate); - + callbackContext.success(returnDate); - + } else { // Open time dialog Calendar selectedDate = Calendar.getInstance(); selectedDate.set(Calendar.YEAR, year); selectedDate.set(Calendar.MONTH, monthOfYear); selectedDate.set(Calendar.DAY_OF_MONTH, dayOfMonth); - + cordova.getActivity().runOnUiThread(runnableTimeDialog(datePickerPlugin, theme, cordova.getActivity(), callbackContext, jsonDate, selectedDate)); } @@ -319,7 +356,7 @@ public void onTimeSet(final TimePicker view, final int hourOfDay, final int minu if (canceled) { return; } - + calendarDate.set(Calendar.HOUR_OF_DAY, hourOfDay); calendarDate.set(Calendar.MINUTE, minute); calendarDate.set(Calendar.SECOND, 0); @@ -331,9 +368,9 @@ public void onTimeSet(final TimePicker view, final int hourOfDay, final int minu callbackContext.success(toReturn); } } - + private final class JsonDate { - + private String action = ACTION_DATE; private String titleText = ""; private String okText = ""; @@ -348,6 +385,7 @@ private final class JsonDate { private int hour = 0; private int minutes = 0; private boolean is24Hour = false; + private int minuteInterval = 1; public JsonDate() { reset(Calendar.getInstance()); @@ -380,6 +418,8 @@ public JsonDate fromJson(JSONArray data) { : ""; is24Hour = isNotEmpty(obj, "is24Hour") ? obj.getBoolean("is24Hour") : false; + minuteInterval = isNotEmpty(obj, "minuteInterval") ? obj.getInt("minuteInterval") + : 1; String optionDate = obj.getString("date"); @@ -403,7 +443,7 @@ public boolean isNotEmpty(JSONObject object, String key) && !object.isNull(key) && object.get(key).toString().length() > 0 && !JSONObject.NULL.toString().equals( - object.get(key).toString()); + object.get(key).toString()); } } diff --git a/src/ios/DatePicker.h b/src/ios/DatePicker.h index 24077fa..57387de 100644 --- a/src/ios/DatePicker.h +++ b/src/ios/DatePicker.h @@ -18,4 +18,4 @@ - (void)show:(CDVInvokedUrlCommand*)command; -@end \ No newline at end of file +@end diff --git a/src/ios/DatePicker.m b/src/ios/DatePicker.m index caae145..4e10f48 100644 --- a/src/ios/DatePicker.m +++ b/src/ios/DatePicker.m @@ -216,6 +216,8 @@ - (void)updateDatePicker:(NSMutableDictionary *)options { NSString *maxDateString = [options objectForKey:@"maxDate"]; NSString *minuteIntervalString = [options objectForKey:@"minuteInterval"]; NSInteger minuteInterval = [minuteIntervalString integerValue]; + NSString *countDownDurationString = [options objectForKey:@"countDownDuration"]; + NSInteger countDownDuration = [countDownDurationString integerValue]; NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:[options objectForKey:@"locale"]]; @@ -248,6 +250,9 @@ - (void)updateDatePicker:(NSMutableDictionary *)options { } else if ([mode isEqualToString:@"time"]) { self.datePicker.datePickerMode = UIDatePickerModeTime; + } + else if ([mode isEqualToString:@"duration"]) { + self.datePicker.datePickerMode = UIDatePickerModeCountDownTimer; } else { self.datePicker.datePickerMode = UIDatePickerModeDateAndTime; } @@ -256,6 +261,10 @@ - (void)updateDatePicker:(NSMutableDictionary *)options { self.datePicker.minuteInterval = minuteInterval; } + if (countDownDuration) { + self.datePicker.countDownDuration = countDownDuration; + } + if (locale) { [self.datePicker setLocale:locale]; } diff --git a/www/android/DatePicker.js b/www/android/DatePicker.js index 3ba4c22..ed71a95 100644 --- a/www/android/DatePicker.js +++ b/www/android/DatePicker.js @@ -45,6 +45,7 @@ DatePicker.prototype.show = function(options, cb, errCb) { todayText: '', nowText: '', is24Hour: false, + minuteInterval: 1, androidTheme : window.datePicker.ANDROID_THEMES.THEME_TRADITIONAL, // Default theme }; diff --git a/www/ios/DatePicker.js b/www/ios/DatePicker.js index a09a9b2..9a47fb6 100644 --- a/www/ios/DatePicker.js +++ b/www/ios/DatePicker.js @@ -92,6 +92,7 @@ DatePicker.prototype.show = function(options, cb) { x: '0', y: '0', minuteInterval: 1, + countDownDuration: 0, popoverArrowDirection: this._popoverArrowDirectionIntegerFromString("any"), locale: "en_US" };