[NUI] Fix timePicker ampm internal logic
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI.Components / Controls / TimePicker.cs
1 /* Copyright (c) 2021 Samsung Electronics Co., Ltd.
2  *
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  *
15  */
16 using System;
17 using Tizen.NUI.BaseComponents;
18 using System.Collections.Generic;
19 using System.Collections.ObjectModel;
20 using System.ComponentModel;
21 using System.Diagnostics.CodeAnalysis;
22 using System.Globalization;
23 using Tizen.NUI.Binding;
24
25 namespace Tizen.NUI.Components
26 {
27     /// <summary>
28     /// TimeChangedEventArgs is a class to notify changed TimePicker value argument which will sent to user.
29     /// </summary>
30     /// <since_tizen> 9 </since_tizen>
31     public class TimeChangedEventArgs : EventArgs
32     {
33         /// <summary>
34         /// TimeChangedEventArgs default constructor.
35         /// <param name="time">time value of TimePicker.</param>
36         /// </summary>
37         [EditorBrowsable(EditorBrowsableState.Never)]   
38         public TimeChangedEventArgs(DateTime time)
39         {
40             Time = time;
41         }
42
43         /// <summary>
44         /// TimeChangedEventArgs default constructor.
45         /// <returns>The current time value of TimePicker.</returns>
46         /// </summary>
47         /// <since_tizen> 9 </since_tizen>
48         public DateTime Time { get; }
49     }
50
51     /// <summary>
52     /// TimePicker is a class which provides a function that allows the user to select 
53     /// a time through a scrolling motion by expressing the specified value as a list.
54     /// TimePicker expresses the current time using the locale information of the system.
55     /// </summary>
56     /// <since_tizen> 9 </since_tizen>
57     public class TimePicker : Control
58     {
59         /// <summary>
60         /// TimeProperty
61         /// </summary>
62         [EditorBrowsable(EditorBrowsableState.Never)]
63         public static readonly BindableProperty TimeProperty = BindableProperty.Create(nameof(Time), typeof(DateTime), typeof(TimePicker), default(DateTime), propertyChanged: (bindable, oldValue, newValue) =>
64         {
65             var instance = (TimePicker)bindable;
66             if (newValue != null)
67             {
68                 instance.InternalTime = (DateTime)newValue;
69             }
70         },
71         defaultValueCreator: (bindable) =>
72         {
73             var instance = (TimePicker)bindable;
74             return instance.InternalTime;
75         });
76
77         /// <summary>
78         /// Is24HourViewProperty
79         /// </summary>
80         [EditorBrowsable(EditorBrowsableState.Never)]
81         public static readonly BindableProperty Is24HourViewProperty = BindableProperty.Create(nameof(Is24HourView), typeof(bool), typeof(TimePicker), default(bool), propertyChanged: (bindable, oldValue, newValue) =>
82         {
83             var instance = (TimePicker)bindable;
84             if (newValue != null)
85             {
86                 instance.InternalIs24HourView = (bool)newValue;
87             }
88         },
89         defaultValueCreator: (bindable) =>
90         {
91             var instance = (TimePicker)bindable;
92             return instance.InternalIs24HourView;
93         });
94
95         private bool isAm;
96         private bool is24HourView;
97         private DateTime currentTime;
98         private String[] ampmText;
99         private Picker hourPicker;
100         private Picker minutePicker;
101         private Picker ampmPicker;
102
103         /// <summary>
104         /// Creates a new instance of TimePicker.
105         /// </summary>
106         /// <since_tizen> 9 </since_tizen>
107         public TimePicker()
108         {
109         }
110
111         /// <summary>
112         /// Creates a new instance of TimePicker.
113         /// </summary>
114         /// <param name="style">Creates TimePicker by special style defined in UX.</param>
115         /// <since_tizen> 9 </since_tizen>
116         public TimePicker(string style) : base(style)
117         {
118         }
119
120         /// <summary>
121         /// Creates a new instance of TimePicker.
122         /// </summary>
123         /// <param name="timePickerStyle">Creates TimePicker by style customized by user.</param>
124         /// <since_tizen> 9 </since_tizen>
125         public TimePicker(TimePickerStyle timePickerStyle) : base(timePickerStyle)
126         {
127         }
128
129         /// <summary>
130         /// Dispose TimePicker and all children on it.
131         /// </summary>
132         /// <param name="type">Dispose type.</param>
133         [EditorBrowsable(EditorBrowsableState.Never)]
134         protected override void Dispose(DisposeTypes type)
135         {
136             if (disposed)
137             {
138                 return;
139             }
140
141             if (type == DisposeTypes.Explicit)
142             {
143                 Remove(hourPicker);
144                 Utility.Dispose(hourPicker);
145                 hourPicker = null;
146                 Remove(minutePicker);
147                 Utility.Dispose(minutePicker);
148                 minutePicker = null;
149                 Remove(ampmPicker);
150                 Utility.Dispose(ampmPicker);
151                 ampmPicker = null;
152             }
153
154             base.Dispose(type);
155         }
156
157         /// <summary>
158         /// An event emitted when TimePicker value changed, user can subscribe or unsubscribe to this event handler.
159         /// </summary>
160         /// <since_tizen> 9 </since_tizen>
161         public event EventHandler<TimeChangedEventArgs> TimeChanged;
162
163         /// <summary>
164         /// The hour value of TimePicker.
165         /// </summary>
166         /// <since_tizen> 9 </since_tizen>
167         public DateTime Time
168         {
169             get
170             {
171                 return (DateTime)GetValue(TimeProperty);
172             }
173             set
174             {
175                 SetValue(TimeProperty, value);
176                 NotifyPropertyChanged();
177             }
178         }
179         private DateTime InternalTime
180         {
181             get
182             {
183                 return currentTime;
184             }
185             set
186             {
187                 currentTime = value;
188                 if (!is24HourView)
189                 {
190                     if (currentTime.Hour >= 12 && currentTime.Hour <= 23)
191                     {
192                         isAm = false;
193                         if (currentTime.Hour == 12) hourPicker.CurrentValue = currentTime.Hour;
194                         else hourPicker.CurrentValue = currentTime.Hour - 12;
195                         ampmPicker.CurrentValue = 2;
196                     }
197                     else 
198                     {
199                         isAm = true;
200                         if (currentTime.Hour == 0) hourPicker.CurrentValue = 12;
201                         else hourPicker.CurrentValue = currentTime.Hour;
202                         ampmPicker.CurrentValue = 1;
203                     }
204                 }
205                 else hourPicker.CurrentValue = currentTime.Hour;
206
207                 minutePicker.CurrentValue = currentTime.Minute;
208             }
209         }
210
211         /// <summary>
212         /// The is24hourview value of TimePicker.
213         /// </summary>
214         /// <since_tizen> 9 </since_tizen>
215         public bool Is24HourView
216         {
217             get
218             {
219                 return (bool)GetValue(Is24HourViewProperty);
220             }
221             set
222             {
223                 SetValue(Is24HourViewProperty, value);
224                 NotifyPropertyChanged();
225             }
226         }
227         private bool InternalIs24HourView
228         {
229             get
230             {
231                 return is24HourView;
232             }
233             set
234             {
235                 if (is24HourView == value) return;
236
237                 Console.WriteLine(" Is 24 Hour View");
238                 is24HourView = value;
239                 if (value == true)
240                 {
241                     Remove(ampmPicker);
242                     hourPicker.MinValue = 0;
243                     hourPicker.MaxValue = 23;
244                     hourPicker.CurrentValue = currentTime.Hour;
245                 }
246                 else 
247                 {
248                     hourPicker.MinValue = 1;
249                     hourPicker.MaxValue = 12;
250                     PickersOrderSet(true);
251                     SetAmpmText();
252                     if (currentTime.Hour > 12)
253                     {
254                         ampmPicker.CurrentValue = 2;
255                         hourPicker.CurrentValue = currentTime.Hour - 12;
256                     }
257                 }
258             }
259         }
260
261         /// <summary>
262         /// Initialize TimePicker object.
263         /// </summary>
264         [EditorBrowsable(EditorBrowsableState.Never)]
265         public override void OnInitialize()
266         {
267             base.OnInitialize();
268             SetAccessibilityConstructor(Role.DateEditor);
269
270             hourPicker = new Picker()
271             {
272                 MinValue = 1,
273                 MaxValue = 12,
274             };
275             hourPicker.ValueChanged += OnHourValueChanged;
276
277             minutePicker = new Picker()
278             {
279                 MinValue = 0,
280                 MaxValue = 59,
281             };
282             minutePicker.ValueChanged += OnMinuteValueChanged;
283
284             ampmPicker = new Picker()
285             {
286                 MinValue = 1,
287                 MaxValue = 2,
288             };
289             ampmPicker.ValueChanged += OnAmpmValueChanged;
290
291             currentTime = DateTime.Now;
292             if (currentTime.Hour > 12)
293             {
294                 ampmPicker.CurrentValue = 2;
295                 hourPicker.CurrentValue = currentTime.Hour - 12;
296             }
297             else
298             {
299                 ampmPicker.CurrentValue = 1;
300                 hourPicker.CurrentValue = currentTime.Hour;
301             }
302
303             minutePicker.CurrentValue = currentTime.Minute;
304
305             Initialize();
306         }
307
308         /// <summary>
309         /// Applies style to TimePicker.
310         /// </summary>
311         /// <param name="viewStyle">The style to apply.</param>
312         [EditorBrowsable(EditorBrowsableState.Never)]
313         public override void ApplyStyle(ViewStyle viewStyle)
314         {
315             base.ApplyStyle(viewStyle);
316
317             var timePickerStyle = viewStyle as TimePickerStyle;
318
319             if (timePickerStyle == null) return;
320
321             //Apply CellPadding.
322             if (timePickerStyle?.CellPadding != null && Layout != null)
323                 ((LinearLayout)Layout).CellPadding = new Size2D(timePickerStyle.CellPadding.Width, timePickerStyle.CellPadding.Height);
324             
325             //Apply Internal Pickers style.
326             if (timePickerStyle?.Pickers != null && hourPicker != null && minutePicker != null && ampmPicker != null)
327             {
328                 hourPicker.ApplyStyle(timePickerStyle.Pickers);
329                 minutePicker.ApplyStyle(timePickerStyle.Pickers);
330                 ampmPicker.ApplyStyle(timePickerStyle.Pickers);
331             }
332         }
333                 
334         [SuppressMessage("Microsoft.Reliability",
335                          "CA2000:DisposeObjectsBeforeLosingScope",
336                          Justification = "The CellPadding will be dispose when the time picker disposed")]
337         private void Initialize()
338         {
339             HeightSpecification = LayoutParamPolicies.MatchParent;
340
341             Layout = new LinearLayout() { 
342                 LinearOrientation = LinearLayout.Orientation.Horizontal,
343             };
344             Console.WriteLine("initialize");
345
346             is24HourView = false;
347
348             PickersOrderSet(false);
349             SetAmpmText();
350         }
351
352         private void ChangeTime(int hour, int minute, bool hourUpdate)
353         {
354             if (hourUpdate)
355                 currentTime = new DateTime(currentTime.Year, currentTime.Month, currentTime.Day, hour, currentTime.Minute, 0);
356             else
357                 currentTime = new DateTime(currentTime.Year, currentTime.Month, currentTime.Day, currentTime.Hour, minute, 0);
358         }
359
360         private void OnHourValueChanged(object sender, ValueChangedEventArgs e)
361         {
362             if (currentTime.Hour == e.Value) return;
363
364             if (!is24HourView)
365             {
366                 if (isAm) 
367                 {
368                     if (e.Value == 12) ChangeTime(0, 0, true);
369                     else ChangeTime(e.Value, 0, true);
370                 }
371                 else 
372                 {
373                     if (e.Value == 12) ChangeTime(12, 0, true);
374                     else ChangeTime(e.Value + 12, 0, true);
375                 }
376             }
377             else
378                 ChangeTime(e.Value, 0, true);
379             
380             OnTimeChanged();
381         }
382
383         private void OnMinuteValueChanged(object sender, ValueChangedEventArgs e)
384         { 
385             if (currentTime.Minute == e.Value) return;
386
387             ChangeTime(0, e.Value, false);
388
389             OnTimeChanged();
390         }
391
392         private void OnAmpmValueChanged(object sender, ValueChangedEventArgs e)
393         { 
394             if ((isAm && e.Value == 1) || (!isAm && e.Value == 2)) return;
395
396             if (e.Value == 1)
397             { //AM
398                 if (currentTime.Hour == 12) ChangeTime(0, 0, true);
399                 else ChangeTime(currentTime.Hour - 12, 0, true);
400
401                 isAm = true;
402             }
403             else 
404             { //PM
405                 if (currentTime.Hour == 0) ChangeTime(12, 0, true);
406                 else ChangeTime(currentTime.Hour + 12, 0, true);
407
408                 isAm = false;
409             }
410
411             OnTimeChanged();
412         }
413
414         private void OnTimeChanged()
415         { 
416             TimeChangedEventArgs eventArgs = new TimeChangedEventArgs(currentTime);
417             TimeChanged?.Invoke(this, eventArgs);
418         }
419
420         private void PickersOrderSet(bool ampmForceSet)
421         {
422             //FIXME: Check the pickers located in already proper position or not.
423             Remove(hourPicker);
424             Remove(minutePicker);
425             Remove(ampmPicker);
426
427             //Get current system locale's time pattern
428             DateTimeFormatInfo timeFormatInfo = CultureInfo.CurrentCulture.DateTimeFormat;
429             String timePattern = timeFormatInfo.ShortTimePattern;
430             String[] timePatternArray = timePattern.Split(' ', ':');
431
432             foreach (String format in timePatternArray) {
433                 if (format.IndexOf("H") != -1|| format.IndexOf("h") != -1)  Add(hourPicker);
434                 else if (format.IndexOf("M") != -1 || format.IndexOf("m") != -1) Add(minutePicker);
435                 else if (format.IndexOf("t") != -1) 
436                 {
437                     is24HourView = false;
438                     ampmForceSet = false;
439                     Add(ampmPicker);
440                 }
441             }
442
443             if (ampmForceSet) Add(ampmPicker);
444         }
445
446         private void SetAmpmText()
447         {
448             //FIXME: There is no localeChanged Event for Component now
449             //       AMPM text has to update when system locale changed.
450             CultureInfo info = CultureInfo.CurrentCulture;
451             ampmText = new string[] {info.DateTimeFormat.AMDesignator, info.DateTimeFormat.PMDesignator};
452             ampmPicker.DisplayedValues = new ReadOnlyCollection<string>(ampmText);
453         }
454     }
455 }