diff --git a/apps/tests/ui/time-picker/time-picker-tests.ts b/apps/tests/ui/time-picker/time-picker-tests.ts index 2acb888bf..487e0b426 100644 --- a/apps/tests/ui/time-picker/time-picker-tests.ts +++ b/apps/tests/ui/time-picker/time-picker-tests.ts @@ -67,6 +67,24 @@ export function test_WhenCreated_HourIsUndefined() { }); } +export function test_WhenCreated_MinHourIs1() { + helper.buildUIAndRunTest(_createTimePicker(), function (views: Array) { + var timePicker = views[0]; + var actualValue = timePicker.minHour; + var expectedValue = 1; + TKUnit.assert(actualValue === expectedValue, "Actual: " + actualValue + "; Expected: " + expectedValue); + }); +} + +export function test_WhenCreated_MaxHourIs23() { + helper.buildUIAndRunTest(_createTimePicker(), function (views: Array) { + var timePicker = views[0]; + var actualValue = timePicker.maxHour; + var expectedValue = 23; + TKUnit.assert(actualValue === expectedValue, "Actual: " + actualValue + "; Expected: " + expectedValue); + }); +} + export function test_WhenCreated_MinuteIsUndefined() { helper.buildUIAndRunTest(_createTimePicker(), function (views: Array) { var timePicker = views[0]; @@ -76,6 +94,108 @@ export function test_WhenCreated_MinuteIsUndefined() { }); } +export function test_WhenCreated_MinMinuteIs0() { + helper.buildUIAndRunTest(_createTimePicker(), function (views: Array) { + var timePicker = views[0]; + var actualValue = timePicker.minMinute; + var expectedValue = 0; + TKUnit.assert(actualValue === expectedValue, "Actual: " + actualValue + "; Expected: " + expectedValue); + }); +} + +export function test_WhenCreated_MaxMinuteIs59() { + helper.buildUIAndRunTest(_createTimePicker(), function (views: Array) { + var timePicker = views[0]; + var actualValue = timePicker.maxMinute; + var expectedValue = 59; + TKUnit.assert(actualValue === expectedValue, "Actual: " + actualValue + "; Expected: " + expectedValue); + }); +} + +export function testHourThrowExceptionWhenLessThanMinHour() { + helper.buildUIAndRunTest(_createTimePicker(), function (views: Array) { + var timePicker = views[0]; + timePicker.minHour = 13; + TKUnit.assertThrows(function () { + timePicker.hour = timePicker.minHour - 1; + }, "Setting hour property to a value less than minHour property value should throw."); + }); +} + +export function testHourThrowExceptionWhenGreaterThanMaxHour() { + helper.buildUIAndRunTest(_createTimePicker(), function (views: Array) { + var timePicker = views[0]; + timePicker.maxHour = 13; + TKUnit.assertThrows(function () { + timePicker.hour = timePicker.maxHour + 1;; + }, "Setting hour property to a value greater than maxHour property value should throw."); + }); +} + +export function testMinuteThrowExceptionWhenLessThanMinMinute() { + helper.buildUIAndRunTest(_createTimePicker(), function (views: Array) { + var timePicker = views[0]; + timePicker.minMinute = 13; + TKUnit.assertThrows(function () { + timePicker.minute = timePicker.minMinute - 1; + }, "Setting hour property to a value less than minHour property value should throw."); + }); +} + +export function testMinuteThrowExceptionWhenGreaterThanMaxMinute() { + helper.buildUIAndRunTest(_createTimePicker(), function (views: Array) { + var timePicker = views[0]; + timePicker.maxMinute = 13; + TKUnit.assertThrows(function () { + timePicker.minute = timePicker.maxMinute + 1;; + }, "Setting hour property to a value greater than maxHour property value should throw."); + }); +} + +export function testHourFromNativeEqualToMinHourWhenLessThanMinHour() { + helper.buildUIAndRunTest(_createTimePicker(), function (views: Array) { + var timePicker = views[0]; + var expectedValue = 13; + timePicker.minHour = expectedValue; + timePickerTestsNative.setNativeHour(timePicker, expectedValue - 1); + var actualValue = timePickerTestsNative.getNativeHour(timePicker); + TKUnit.assert(actualValue === expectedValue, "Actual: " + actualValue + "; Expected: " + expectedValue); + }); +} + +export function testHourFromNativeEqualToMaxHourWhenGreaterThanMaxHour() { + helper.buildUIAndRunTest(_createTimePicker(), function (views: Array) { + var timePicker = views[0]; + var expectedValue = 13; + timePicker.maxHour = expectedValue; + timePickerTestsNative.setNativeHour(timePicker, expectedValue + 1); + var actualValue = timePickerTestsNative.getNativeHour(timePicker); + TKUnit.assert(actualValue === expectedValue, "Actual: " + actualValue + "; Expected: " + expectedValue); + }); +} + +export function testMinuteFromNativeEqualToMinMinuteWhenLessThanMinMinute() { + helper.buildUIAndRunTest(_createTimePicker(), function (views: Array) { + var timePicker = views[0]; + var expectedValue = 13; + timePicker.minMinute = expectedValue; + timePickerTestsNative.setNativeMinute(timePicker, expectedValue - 1); + var actualValue = timePickerTestsNative.getNativeMinute(timePicker); + TKUnit.assert(actualValue === expectedValue, "Actual: " + actualValue + "; Expected: " + expectedValue); + }); +} + +export function testMinuteFromNativeEqualToMaxMinuteWhenGreaterThanMaxMinute() { + helper.buildUIAndRunTest(_createTimePicker(), function (views: Array) { + var timePicker = views[0]; + var expectedValue = 13; + timePicker.maxMinute = expectedValue; + timePickerTestsNative.setNativeMinute(timePicker, expectedValue + 1); + var actualValue = timePickerTestsNative.getNativeMinute(timePicker); + TKUnit.assert(actualValue === expectedValue, "Actual: " + actualValue + "; Expected: " + expectedValue); + }); +} + export function testHourFromLocalToNative() { helper.buildUIAndRunTest(_createTimePicker(), function (views: Array) { var timePicker = views[0]; diff --git a/ui/time-picker/time-picker-common.ts b/ui/time-picker/time-picker-common.ts index fd9960667..51b68f77f 100644 --- a/ui/time-picker/time-picker-common.ts +++ b/ui/time-picker/time-picker-common.ts @@ -2,10 +2,62 @@ import dependencyObservable = require("ui/core/dependency-observable"); import proxy = require("ui/core/proxy"); import view = require("ui/core/view"); +import types = require("utils/types"); + +function isHourValid(value: number): boolean { + return types.isNumber(value) && value >= 1 && value <= 23; +} + +function isMinuteValid(value: number): boolean { + return types.isNumber(value) && value >= 0 && value <= 59; +} + +export function getValidHour(hour: number, minHour: number, maxHour: number): number { + let hourValue = hour; + + if (minHour && hour < minHour) { + hourValue = minHour + } + + if (maxHour && hour > maxHour) { + hourValue = maxHour + } + + return hourValue; +} + +export function getValidMinute(minute: number, minMinute: number, maxMinute: number): number { + let minuteValue = minute; + + if (minMinute && minute < minMinute) { + minuteValue = minMinute + } + + if (maxMinute && minute > maxMinute) { + minuteValue = maxMinute + } + + return minuteValue; +} export class TimePicker extends view.View implements definition.TimePicker { - public static hourProperty = new dependencyObservable.Property("hour", "TimePicker", new proxy.PropertyMetadata(undefined)); - public static minuteProperty = new dependencyObservable.Property("minute", "TimePicker", new proxy.PropertyMetadata(undefined)); + public static hourProperty = new dependencyObservable.Property("hour", "TimePicker", + new proxy.PropertyMetadata(undefined, dependencyObservable.PropertyMetadataSettings.None, undefined, isHourValid)); + + public static minHourProperty = new dependencyObservable.Property("minHour", "TimePicker", + new proxy.PropertyMetadata(1, dependencyObservable.PropertyMetadataSettings.None, undefined, isHourValid)); + + public static maxHourProperty = new dependencyObservable.Property("maxHour", "TimePicker", + new proxy.PropertyMetadata(23, dependencyObservable.PropertyMetadataSettings.None, undefined, isHourValid)); + + public static minuteProperty = new dependencyObservable.Property("minute", "TimePicker", + new proxy.PropertyMetadata(undefined, dependencyObservable.PropertyMetadataSettings.None, undefined, isMinuteValid)); + + public static minMinuteProperty = new dependencyObservable.Property("minMinute", "TimePicker", + new proxy.PropertyMetadata(0, dependencyObservable.PropertyMetadataSettings.None, undefined, isMinuteValid)); + + public static maxMinuteProperty = new dependencyObservable.Property("maxMinute", "TimePicker", + new proxy.PropertyMetadata(59, dependencyObservable.PropertyMetadataSettings.None, undefined, isMinuteValid)); constructor() { super(); @@ -24,4 +76,32 @@ export class TimePicker extends view.View implements definition.TimePicker { set minute(value: number) { this._setValue(TimePicker.minuteProperty, value); } + + get maxHour(): number { + return this._getValue(TimePicker.maxHourProperty); + } + set maxHour(value: number) { + this._setValue(TimePicker.maxHourProperty, value); + } + + get maxMinute(): number { + return this._getValue(TimePicker.maxMinuteProperty); + } + set maxMinute(value: number) { + this._setValue(TimePicker.maxMinuteProperty, value); + } + + get minHour(): number { + return this._getValue(TimePicker.minHourProperty); + } + set minHour(value: number) { + this._setValue(TimePicker.minHourProperty, value); + } + + get minMinute(): number { + return this._getValue(TimePicker.minMinuteProperty); + } + set minMinute(value: number) { + this._setValue(TimePicker.minMinuteProperty, value); + } } \ No newline at end of file diff --git a/ui/time-picker/time-picker.android.ts b/ui/time-picker/time-picker.android.ts index d2505d096..758c443b5 100644 --- a/ui/time-picker/time-picker.android.ts +++ b/ui/time-picker/time-picker.android.ts @@ -2,27 +2,41 @@ import dependencyObservable = require("ui/core/dependency-observable"); import proxy = require("ui/core/proxy"); import utils = require("utils/utils") +import types = require("utils/types") function onHourPropertyChanged(data: dependencyObservable.PropertyChangeData) { var picker = data.object; - picker._setNativeHourSilently(data.newValue); + + var validValue = common.getValidHour(data.newValue, picker.minHour, picker.maxHour); + if (validValue === data.newValue) { + picker._setNativeValueSilently(data.newValue, picker.minute); + } else { + throw new Error(`Hour property value (${data.newValue}) is not valid. Min value: (${picker.minHour} ), max value: (${picker.maxHour} ).`); + } } (common.TimePicker.hourProperty.metadata).onSetNativeValue = onHourPropertyChanged; function onMinutePropertyChanged(data: dependencyObservable.PropertyChangeData) { var picker = data.object; - picker._setNativeMinuteSilently(data.newValue); + + var validValue = common.getValidMinute(data.newValue, picker.minMinute, picker.maxMinute); + if (validValue === data.newValue) { + picker._setNativeValueSilently(picker.hour, data.newValue); + } else { + throw new Error(`Minute property value (${data.newValue}) is not valid. Min value: (${picker.minMinute} ), max value: (${picker.maxMinute} ).`); + } } (common.TimePicker.minuteProperty.metadata).onSetNativeValue = onMinutePropertyChanged; global.moduleMerge(common, exports); +var SDK = android.os.Build.VERSION.SDK_INT; + export class TimePicker extends common.TimePicker { private _android: android.widget.TimePicker; private _listener: android.widget.TimePicker.OnTimeChangedListener; - private _isSettingTime: boolean = false; get android(): android.widget.TimePicker { return this._android; @@ -35,51 +49,51 @@ export class TimePicker extends common.TimePicker { this._listener = new android.widget.TimePicker.OnTimeChangedListener( { - get owner() { - return that.get(); - }, + get owner() { + return that.get(); + }, - onTimeChanged: function (picker: android.widget.TimePicker, hour: number, minute: number) { - if (this.owner && !this.owner._isSettingTime) { + onTimeChanged: function (picker: android.widget.TimePicker, hour: number, minute: number) { + if (this.owner) { - if (hour !== this.owner.hour) { - this.owner._onPropertyChangedFromNative(common.TimePicker.hourProperty, hour); - } + this.owner._setNativeValueSilently(hour, minute); - if (minute !== this.owner.minute) { - this.owner._onPropertyChangedFromNative(common.TimePicker.minuteProperty, minute); + if (hour !== this.owner.hour) { + this.owner._onPropertyChangedFromNative(common.TimePicker.hourProperty, hour); + } + + if (minute !== this.owner.minute) { + this.owner._onPropertyChangedFromNative(common.TimePicker.minuteProperty, minute); + } } } - } - }); + }); this._android.setOnTimeChangedListener(this._listener); } - public _setNativeHourSilently(newValue: number) { - if (!this.android) { - return; - } + public _setNativeValueSilently(hour: number, minute: number) { + if (this.android) { + this.android.setOnTimeChangedListener(null); - this._isSettingTime = true; - try { - this.android.setCurrentHour(new java.lang.Integer(newValue)); - } - finally { - this._isSettingTime = false; - } - } + if (types.isNumber(hour)) { + var h = new java.lang.Integer(common.getValidHour(hour, this.minHour, this.maxHour)); + if (SDK >= 23) { + (this.android).setHour(h); + } else { + this.android.setCurrentHour(h); + } + } - public _setNativeMinuteSilently(newValue: number) { - if (!this.android) { - return; - } + if (types.isNumber(minute)) { + var m = new java.lang.Integer(common.getValidMinute(minute, this.minMinute, this.maxMinute)); + if (SDK >= 23) { + (this.android).setMinute(m); + } else { + this.android.setCurrentMinute(m); + } + } - this._isSettingTime = true; - try { - this.android.setCurrentMinute(new java.lang.Integer(newValue)); - } - finally { - this._isSettingTime = false; + this.android.setOnTimeChangedListener(this._listener); } } } diff --git a/ui/time-picker/time-picker.d.ts b/ui/time-picker/time-picker.d.ts index 722f2855d..d12c55bd2 100644 --- a/ui/time-picker/time-picker.d.ts +++ b/ui/time-picker/time-picker.d.ts @@ -33,5 +33,25 @@ declare module "ui/time-picker" { * Gets or sets the time minute. */ minute: number; + + /** + * Gets or sets the max time hour. + */ + maxHour: number; + + /** + * Gets or sets the max time minute. + */ + maxMinute: number; + + /** + * Gets or sets the min time hour. + */ + minHour: number; + + /** + * Gets or sets the min time minute. + */ + minMinute: number; } } diff --git a/ui/time-picker/time-picker.ios.ts b/ui/time-picker/time-picker.ios.ts index 0c03d33fe..13e6eaf13 100644 --- a/ui/time-picker/time-picker.ios.ts +++ b/ui/time-picker/time-picker.ios.ts @@ -1,29 +1,42 @@ import common = require("./time-picker-common"); import dependencyObservable = require("ui/core/dependency-observable"); import proxy = require("ui/core/proxy"); +import types = require("utils/types"); + +function getDate(date: NSDate, hour?: number, minute?: number): NSDate { + var comps = NSCalendar.currentCalendar().componentsFromDate(NSCalendarUnit.NSCalendarUnitHour | NSCalendarUnit.NSCalendarUnitMinute, date); + + if (hour) { + comps.hour = hour; + } + + if (minute) { + comps.minute = minute; + } + + return NSCalendar.currentCalendar().dateFromComponents(comps); +} function onHourPropertyChanged(data: dependencyObservable.PropertyChangeData) { var picker = data.object; - - if (picker.ios) { - var comps = NSCalendar.currentCalendar().componentsFromDate(NSCalendarUnit.NSCalendarUnitHour | NSCalendarUnit.NSCalendarUnitMinute, picker.ios.date); - comps.hour = data.newValue; - picker.ios.setDateAnimated(NSCalendar.currentCalendar().dateFromComponents(comps), false); + var validValue = common.getValidHour(data.newValue, picker.minHour, picker.maxHour); + if (validValue === data.newValue) { + picker.ios.setDateAnimated(getDate(picker.ios.date, data.newValue, picker.minute), false); + } else { + throw new Error(`Hour property value (${data.newValue}) is not valid. Min value: (${picker.minHour} ), max value: (${picker.maxHour} ).`); } } - (common.TimePicker.hourProperty.metadata).onSetNativeValue = onHourPropertyChanged; function onMinutePropertyChanged(data: dependencyObservable.PropertyChangeData) { var picker = data.object; - - if (picker.ios) { - var comps = NSCalendar.currentCalendar().componentsFromDate(NSCalendarUnit.NSCalendarUnitHour | NSCalendarUnit.NSCalendarUnitMinute, picker.ios.date); - comps.minute = data.newValue; - picker.ios.setDateAnimated(NSCalendar.currentCalendar().dateFromComponents(comps), false); + var validValue = common.getValidMinute(data.newValue, picker.minMinute, picker.maxMinute); + if (validValue === data.newValue) { + picker.ios.setDateAnimated(getDate(picker.ios.date, picker.hour, data.newValue), false); + } else { + throw new Error(`Minute property value (${data.newValue}) is not valid. Min value: (${picker.minMinute} ), max value: (${picker.maxMinute} ).`); } } - (common.TimePicker.minuteProperty.metadata).onSetNativeValue = onMinutePropertyChanged; global.moduleMerge(common, exports); @@ -45,6 +58,20 @@ export class TimePicker extends common.TimePicker { get ios(): UIDatePicker { return this._ios; } + + public _setNativeValueSilently(hour: number, minute: number) { + if (this.ios) { + this.ios.removeTargetActionForControlEvents(this._changeHandler, "valueChanged", UIControlEvents.UIControlEventValueChanged) + + if (types.isNumber(hour) && types.isNumber(minute)) { + this.ios.setDateAnimated(getDate(this.ios.date, + common.getValidHour(hour, this.minHour, this.maxHour), + common.getValidMinute(minute, this.minMinute, this.maxMinute)), false); + } + + this.ios.addTargetActionForControlEvents(this._changeHandler, "valueChanged", UIControlEvents.UIControlEventValueChanged); + } + } } class UITimePickerChangeHandlerImpl extends NSObject { @@ -64,6 +91,8 @@ class UITimePickerChangeHandlerImpl extends NSObject { } var comps = NSCalendar.currentCalendar().componentsFromDate(NSCalendarUnit.NSCalendarUnitHour | NSCalendarUnit.NSCalendarUnitMinute, sender.date); + owner._setNativeValueSilently(comps.hour, comps.minute); + comps = NSCalendar.currentCalendar().componentsFromDate(NSCalendarUnit.NSCalendarUnitHour | NSCalendarUnit.NSCalendarUnitMinute, sender.date); if (comps.hour !== owner.hour) { owner._onPropertyChangedFromNative(common.TimePicker.hourProperty, comps.hour); @@ -76,5 +105,5 @@ class UITimePickerChangeHandlerImpl extends NSObject { public static ObjCExposedMethods = { 'valueChanged': { returns: interop.types.void, params: [UIDatePicker] } - }; + } } \ No newline at end of file