Timer Page Implementation 29/180529/6
authorPaweł Kupiecki <p.kupiecki@partner.samsung.com>
Tue, 15 May 2018 15:35:13 +0000 (17:35 +0200)
committerPaweł Kupiecki <p.kupiecki@partner.samsung.com>
Tue, 12 Jun 2018 15:07:48 +0000 (17:07 +0200)
OAPSAN-795
Implement Timer

Change-Id: I271b677462bf330c2cc6949da41cf658a9018a56

21 files changed:
Clock.Tests/TimerManagerTests.cs
Clock/Clock.Tizen.Mobile/Clock.Tizen.Mobile.csproj
Clock/Clock.Tizen.Mobile/Clock.xaml.cs
Clock/Clock.Tizen.Mobile/Controls/ExtendedEntry.cs
Clock/Clock.Tizen.Mobile/Factory/ViewModelFactory.cs
Clock/Clock.Tizen.Mobile/Renderers/ExtendedEntryRenderer.cs
Clock/Clock.Tizen.Mobile/Services/ViewModelLocator.cs
Clock/Clock.Tizen.Mobile/Tools/Converters/StringToTimePartConverter.cs [new file with mode: 0644]
Clock/Clock.Tizen.Mobile/Tools/Converters/TimerFlowToBoolConverter.cs [new file with mode: 0644]
Clock/Clock.Tizen.Mobile/Tools/Converters/TimerMenuStateConverter.cs [deleted file]
Clock/Clock.Tizen.Mobile/Tools/Converters/TotalHourTimeSpanConverter.cs [new file with mode: 0644]
Clock/Clock.Tizen.Mobile/Tools/TriggerActions/ForceFocusAction.cs [new file with mode: 0644]
Clock/Clock.Tizen.Mobile/Views/MainView.xaml
Clock/Clock.Tizen.Mobile/Views/TimerPage.xaml [new file with mode: 0644]
Clock/Clock.Tizen.Mobile/Views/TimerPage.xaml.cs [new file with mode: 0644]
Clock/Clock.Tizen.Mobile/Views/TimerView.xaml [deleted file]
Clock/Clock.Tizen.Mobile/Views/TimerView.xaml.cs [deleted file]
Clock/Clock/Common/Enums/TimeFractionName.cs [new file with mode: 0644]
Clock/Clock/Common/Enums/TimerFlow.cs [new file with mode: 0644]
Clock/Clock/Services/TimerManager.cs
Clock/Clock/ViewModels/TimerViewModel.cs

index 651cd3a..045309f 100644 (file)
@@ -34,7 +34,7 @@ namespace Clock.Tests
         [Test]
         public void TimerManager_CreateManagerWithNullArguments()
         {
-            Assert.Throws(Is.TypeOf<ArgumentNullException>(), () => new TimerManager(null, null, null));
+            Assert.Throws(Is.TypeOf<ArgumentNullException>(), () => new TimerManager(null, null, null, null));
         }
 
         [SetUp]
@@ -50,7 +50,7 @@ namespace Clock.Tests
         {
             //Arrange
             var timerManager =
-                new TimerManager(_platformAlarmsService, _timerRepository, MockFactory.NavigationServiceMock);
+                new TimerManager(_platformAlarmsService, _timerRepository, MockFactory.NavigationServiceMock, null);
 
             //Act
             var time = timerManager.GetTimerTime();
@@ -64,7 +64,7 @@ namespace Clock.Tests
         {
             //Arrange
             var timerManager =
-                new TimerManager(_platformAlarmsService, _timerRepository, MockFactory.NavigationServiceMock);
+                new TimerManager(_platformAlarmsService, _timerRepository, MockFactory.NavigationServiceMock, null);
 
             //Act
             timerManager.SetTimerTime(TimeSpan.FromHours(2));
@@ -78,7 +78,7 @@ namespace Clock.Tests
         {
             //Arrange
             var timerManager =
-                new TimerManager(_platformAlarmsService, _timerRepository, MockFactory.NavigationServiceMock);
+                new TimerManager(_platformAlarmsService, _timerRepository, MockFactory.NavigationServiceMock, null);
 
             //Act
             timerManager.Start();
@@ -97,7 +97,7 @@ namespace Clock.Tests
         {
             //Arrange
             var timerManager =
-                new TimerManager(_platformAlarmsService, _timerRepository, MockFactory.NavigationServiceMock);
+                new TimerManager(_platformAlarmsService, _timerRepository, MockFactory.NavigationServiceMock, null);
 
             _timerRepository.Time = TimeSpan.FromHours(24);
 
@@ -118,7 +118,7 @@ namespace Clock.Tests
         {
             //Arrange
             var timerManager =
-                new TimerManager(_platformAlarmsService, _timerRepository, MockFactory.NavigationServiceMock);
+                new TimerManager(_platformAlarmsService, _timerRepository, MockFactory.NavigationServiceMock, null);
 
             var timerRepositoryTime = new TimeSpan(99, 59, 59);
             _timerRepository.Time = timerRepositoryTime;
@@ -140,7 +140,7 @@ namespace Clock.Tests
         {
             //Arrange
             var timerManager =
-                new TimerManager(_platformAlarmsService, _timerRepository, MockFactory.NavigationServiceMock);
+                new TimerManager(_platformAlarmsService, _timerRepository, MockFactory.NavigationServiceMock, null);
             _timerRepository.State = TimerState.Running;
             _timerRepository.PlatformId = _platformAlarmsService
                 .CreateNewPlatformAlarm(new DateTime(2018, 01, 01, 10, 0, 0), DaysOfWeek.Never).Id;
@@ -160,7 +160,7 @@ namespace Clock.Tests
         {
             //Arrange
             var timerManager =
-                new TimerManager(_platformAlarmsService, _timerRepository, MockFactory.NavigationServiceMock);
+                new TimerManager(_platformAlarmsService, _timerRepository, MockFactory.NavigationServiceMock, null);
             _timerRepository.TimeLeft = TimeSpan.FromMinutes(30);
             _timerRepository.State = TimerState.Paused;
 
@@ -181,7 +181,7 @@ namespace Clock.Tests
         {
             //Arrange
             var timerManager =
-                new TimerManager(_platformAlarmsService, _timerRepository, MockFactory.NavigationServiceMock);
+                new TimerManager(_platformAlarmsService, _timerRepository, MockFactory.NavigationServiceMock, null);
 
             _timerRepository.Time = new TimeSpan(99, 59, 59);
 
@@ -197,7 +197,7 @@ namespace Clock.Tests
         {
             //Arrange
             var timerManager =
-                new TimerManager(_platformAlarmsService, _timerRepository, MockFactory.NavigationServiceMock);
+                new TimerManager(_platformAlarmsService, _timerRepository, MockFactory.NavigationServiceMock, null);
             _timerRepository.PlatformId = 1;
             _timerRepository.Time = TimeSpan.FromHours(24);
             //Act
index 4cde0a3..8596825 100644 (file)
@@ -84,6 +84,9 @@
     <Compile Update="Views\TimerRingPage.xaml.cs">
       <DependentUpon>TimerRingPage.xaml</DependentUpon>
     </Compile>
+    <Compile Update="Views\TimerPage.xaml.cs">
+      <DependentUpon>TimerPage.xaml</DependentUpon>
+    </Compile>
     <Compile Update="Views\WorldClockPage.xaml.cs">
       <DependentUpon>WorldClockPage.xaml</DependentUpon>
     </Compile>
     <EmbeddedResource Update="Views\TimerRingPage.xaml">
       <Generator>MSBuild:UpdateDesignTimeXaml</Generator>
     </EmbeddedResource>
-    <EmbeddedResource Update="Views\TimerView.xaml">
+    <EmbeddedResource Update="Views\TimerPage.xaml">
       <Generator>MSBuild:Compile</Generator>
     </EmbeddedResource>
     <EmbeddedResource Update="Views\WorldClockPage.xaml">
index 0424ff3..764f795 100644 (file)
@@ -126,7 +126,8 @@ namespace Clock
             _ioCContainer.RegisterSingleton<TimerManager>(new InjectionConstructor(
                 typeof(IPlatformAlarmsService),
                 typeof(ITimerRepository),
-                typeof(INavigationService)
+                typeof(INavigationService),
+                typeof(ILogger)
             ));
 
             _ioCContainer.RegisterType<ITranslationService, TranslationService>(
index fefda9b..56a9bac 100644 (file)
-using System;
-using Clock.Controllers;
+/*
+* Copyright 2018  Samsung Electronics Co., Ltd
+*
+* Licensed under the Flora License, Version 1.1 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://floralicense.org/license/
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
 using Xamarin.Forms;
 
 namespace Clock.Tizen.Mobile.Controls
 {
-    public class ExtendedEntry : Entry, IExtendedEntryController
+    public class ExtendedEntry : Entry
     {
-        private int changeNum;
+        public static readonly BindableProperty GuideProperty =
+            BindableProperty.Create(nameof(Guide), typeof(string), typeof(Entry), string.Empty, BindingMode.TwoWay);
 
-        public event EventHandler<TextChangedEventArgs> TextChangedByUser;
-        public const int MaxLenght = 2;
-
-        public static readonly BindableProperty IsSelectedProperty =
-            BindableProperty.Create("IsSelected", typeof(bool), typeof(Entry), default(bool), BindingMode.TwoWay);
-
-        public static readonly BindableProperty MaxValueProperty =
-            BindableProperty.Create("MaxValue", typeof(int), typeof(Entry), default(int), BindingMode.TwoWay);
-
-        public ExtendedEntry NextFocus { get; set; }
-
-        public void SendTextChangedByUser()
-        {
-            TextChangedByUser?.Invoke(this, null);
-        }
-
-        public ExtendedEntry()
-        {
-            TextChanged += OnEntryChanged;
-
-            Unfocused += OnEntryUnfocus;
-            Focused += OnEntryFocus;
-
-            TextChangedByUser += IncreaseChangeCount;
-        }
-
-        private void IncreaseChangeCount(object sender, TextChangedEventArgs e)
-        {
-            changeNum++;
-        }
-
-        public static bool Validate(ExtendedEntry entry)
-        {
-            int n;
-            var isNumeric = int.TryParse(entry.Text, out n);
-
-            if (string.IsNullOrWhiteSpace(entry.Text) || !isNumeric || n < 0)
-            {
-                entry.Text = "00";
-                return false;
-            }
-
-            if (n > entry.MaxValue)
-            {
-                entry.Text = entry.MaxValue.ToString("D2");
-                return false;
-            }
-
-            return true;
-        }
-
-        public int MaxValue
-        {
-            get { return (int)GetValue(MaxValueProperty); }
-            set { SetValue(MaxValueProperty, value); }
-        }
-
-        public bool IsSelected
-        {
-            get { return (bool)GetValue(IsSelectedProperty); }
-            set { SetValue(IsSelectedProperty, value); }
-        }
-
-        public void Increase()
+        public string Guide
         {
-            int n;
-
-            if (!int.TryParse(Text, out n))
-            {
-                return;
-            }
-
-            ++n;
-
-            if (n > MaxValue)
-            {
-                n = 0;
-            }
-
-            Text = n.ToString("D2");
-        }
-
-        public void Decrease()
-        {
-            int n;
-
-            if (!int.TryParse(Text, out n))
-            {
-                return;
-            }
-
-            --n;
-
-            if (n < 0)
-            {
-                n = MaxValue;
-            }
-
-            Text = n.ToString("D2");
+            get { return (string)GetValue(GuideProperty); }
+            set { SetValue(GuideProperty, value); }
         }
 
-        private static void EntryFocusNext(ExtendedEntry entry)
-        {
-            if (entry.NextFocus != null)
-            {
-                entry.NextFocus.Focus();
-            }
-        }
+        public static readonly BindableProperty SelectAllOnFocusedProperty =
+            BindableProperty.Create(nameof(SelectAllOnFocused), typeof(bool), typeof(Entry), default(bool), BindingMode.TwoWay);
 
-        private static void OnEntryFocus(object sender, EventArgs e)
+        public bool SelectAllOnFocused
         {
-            var entry = sender as ExtendedEntry;
-
-            entry.IsSelected = !entry.IsSelected;
-        }
-
-        private static void OnEntryUnfocus(object sender, FocusEventArgs e)
-        {
-            var entry = sender as ExtendedEntry;
-
-            int n;
-            var isNumeric = int.TryParse(entry.Text, out n);
-            if (n < 10)
-            {
-                var output = n.ToString().PadLeft(2, '0');
-
-                if (entry.Text != output)
-                {
-                    entry.Text = output;
-                }
-
-                return;
-            }
-
-            entry.Text = n.ToString("D2");
-        }
-
-        private static void OnEntryChanged(object sender, TextChangedEventArgs e)
-        {
-            var entry = sender as ExtendedEntry;
-
-            if (!Validate(entry))
-            {
-                return;
-            }
-
-            int n;
-            var isNumeric = int.TryParse(entry.Text, out n);
-
-            if (entry.IsFocused && isNumeric && entry.changeNum >= 3)
-            {
-                entry.changeNum = 0;
-                EntryFocusNext(entry);
-            }
+            get { return (bool)GetValue(SelectAllOnFocusedProperty); }
+            set { SetValue(SelectAllOnFocusedProperty, value); }
         }
     }
 }
\ No newline at end of file
index c3211b2..ba0453d 100644 (file)
@@ -139,5 +139,12 @@ namespace Clock.Tizen.Mobile.Factory
                 ServiceLocator.Current.GetInstance<IAppCycleService>(),
                 ServiceLocator.Current.GetInstance<TimerManager>());
         }
+
+        public static TimerViewModel CreateTimerViewModel()
+        {
+            return new TimerViewModel(ServiceLocator.Current.GetInstance<INavigationService>(),
+                ServiceLocator.Current.GetInstance<ILogger>(),
+                ServiceLocator.Current.GetInstance<TimerManager>());
+        }
     }
 }
index 1c1ffe6..98f531b 100644 (file)
@@ -1,6 +1,20 @@
-using System;
+/*
+* Copyright 2018  Samsung Electronics Co., Ltd
+*
+* Licensed under the Flora License, Version 1.1 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://floralicense.org/license/
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+using System;
 using System.ComponentModel;
-using Clock.Controllers;
 using Clock.Tizen.Mobile.Renderers;
 using Xamarin.Forms;
 using TNative = Xamarin.Forms.Platform.Tizen;
@@ -13,38 +27,52 @@ namespace Clock.Tizen.Mobile.Renderers
 {
     public class ExtendedEntryRenderer : TNative.EntryRenderer
     {
+        private bool _isAllSelected;
+
         protected override void OnElementChanged(TNative.ElementChangedEventArgs<Entry> e)
         {
             base.OnElementChanged(e);
 
-            if (e.NewElement != null)
+            if(Control != null && e.NewElement is Clock.Tizen.Mobile.Controls.ExtendedEntry _entry)
             {
-                if (Control != null)
+                if(!String.IsNullOrEmpty(_entry.Guide))
                 {
-                    Control.ChangedByUser += EntryChangedByUser;
+                    Control.SetPartText("elm.guide", _entry.Guide);
                 }
+
+                Control.Focused += Control_Focused;
+                Control.Unfocused += Control_Unfocused;
+                _isAllSelected = false;
             }
         }
 
-        protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+        private void Control_Unfocused(object sender, EventArgs e)
         {
-            if (e.PropertyName == Clock.Tizen.Mobile.Controls.ExtendedEntry.IsSelectedProperty.PropertyName)
+            _isAllSelected = false;
+        }
+
+        private void Control_Focused(object sender, EventArgs e)
+        {
+            if (!_isAllSelected)
             {
-                base.Control.SelectAll();
+                Control.SelectAll();
+                _isAllSelected = true;
             }
+        }
 
+        protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+        {
             base.OnElementPropertyChanged(sender, e);
-
-            if (Control != null)
-            {
-                Control.MoveCursorEnd();
-            }
         }
 
-        private void EntryChangedByUser(object sender, EventArgs e)
+        protected override void Dispose(bool disposing)
         {
-            IExtendedEntryController entry = Element as IExtendedEntryController;
-            entry?.SendTextChangedByUser();
+            if(!IsDisposed)
+            {
+                Control.Focused -= Control_Focused;
+                Control.Unfocused -= Control_Unfocused;
+            }
+            base.Dispose(disposing);
         }
     }
 }
\ No newline at end of file
index a109217..956d2f2 100644 (file)
@@ -29,5 +29,6 @@ namespace Clock.Tizen.Mobile.Services
         public object StopWatchViewModel => ViewModelFactory.CreateStopWatchViewModel();
         public object AlarmRingViewModel => ViewModelFactory.CreateRingViewModel();
         public object TimerRingViewModel => ViewModelFactory.CreateTimerRingViewModel();
+        public object TimerViewModel => ViewModelFactory.CreateTimerViewModel();
     }
 }
diff --git a/Clock/Clock.Tizen.Mobile/Tools/Converters/StringToTimePartConverter.cs b/Clock/Clock.Tizen.Mobile/Tools/Converters/StringToTimePartConverter.cs
new file mode 100644 (file)
index 0000000..492993c
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+* Copyright 2018  Samsung Electronics Co., Ltd
+*
+* Licensed under the Flora License, Version 1.1 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://floralicense.org/license/
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+using System;
+using System.Globalization;
+using Xamarin.Forms;
+
+namespace Clock.Tizen.Mobile.Tools.Converters
+{
+    public class StringToTimePartConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            if (value is int _value)
+            {
+                if (_value < 10)
+                {
+                    return value.ToString().PadLeft(2, '0');
+                }
+
+                return value.ToString();
+            }
+            return "00";
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            if (value is string _value)
+            {
+                if (_value.Length == 2)
+                {
+                    return int.TryParse(_value, out int _result) ? _result : -1;
+                }
+            }
+
+            return -1;
+        }
+    }
+}
diff --git a/Clock/Clock.Tizen.Mobile/Tools/Converters/TimerFlowToBoolConverter.cs b/Clock/Clock.Tizen.Mobile/Tools/Converters/TimerFlowToBoolConverter.cs
new file mode 100644 (file)
index 0000000..d34d760
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+* Copyright 2018  Samsung Electronics Co., Ltd
+*
+* Licensed under the Flora License, Version 1.1 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://floralicense.org/license/
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+using System;
+using System.Globalization;
+using Xamarin.Forms;
+using Clock.Common.Enums;
+
+namespace Clock.Tizen.Mobile.Tools.Converters
+{
+    public class TimerFlowToBoolConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            if (value is TimerFlow _value && parameter is TimerFlow _parameter)
+            {
+                var result = _value.HasFlag(_parameter);
+                return result;
+            }
+
+            return false;
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}
diff --git a/Clock/Clock.Tizen.Mobile/Tools/Converters/TimerMenuStateConverter.cs b/Clock/Clock.Tizen.Mobile/Tools/Converters/TimerMenuStateConverter.cs
deleted file mode 100644 (file)
index 9b9c5b4..0000000
+++ /dev/null
@@ -1,61 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using Xamarin.Forms;
-using Clock.ViewModels;
-
-namespace Clock.Tizen.Mobile.Tools.Converters
-{
-    public class TimerMenuStateConverter : IValueConverter
-    {
-        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
-        {
-            if (value is MenuState && parameter is string)
-            {
-                var menuState = (MenuState)value;
-                var p = (string)parameter;
-
-                if (p == "timeLabel" && menuState != MenuState.STARTUP ||
-                    p == "timeSelector" && menuState == MenuState.STARTUP)
-                {
-                    return true;
-                }
-
-                switch (menuState)
-                {
-                    case MenuState.STARTUP:
-                        if (p == "startButton")
-                        {
-                            return true;
-                        }
-
-                        break;
-                    case MenuState.RUNNING:
-                        if (p == "cancelButton" || p == "pauseButton")
-                        {
-                            return true;
-                        }
-
-                        break;
-                    case MenuState.PAUSED:
-                        if (p == "cancelButton" || p == "resumeButton")
-                        {
-                            return true;
-                        }
-
-                        break;
-                }
-            }
-
-            return false;
-        }
-
-        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
-        {
-            throw new NotImplementedException();
-        }
-    }
-}
\ No newline at end of file
diff --git a/Clock/Clock.Tizen.Mobile/Tools/Converters/TotalHourTimeSpanConverter.cs b/Clock/Clock.Tizen.Mobile/Tools/Converters/TotalHourTimeSpanConverter.cs
new file mode 100644 (file)
index 0000000..cc1dc97
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+* Copyright 2018  Samsung Electronics Co., Ltd
+*
+* Licensed under the Flora License, Version 1.1 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://floralicense.org/license/
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+using System;
+using System.Globalization;
+using Xamarin.Forms;
+
+namespace Clock.Tizen.Mobile.Tools.Converters
+{
+    public class TotalHourTimeSpanConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            if(value is TimeSpan _value)
+            {
+                return $"{System.Convert.ToInt32(Math.Floor(_value.TotalHours)).ToString("D2")}:{_value.Minutes.ToString("D2")}:{_value.Seconds.ToString("D2")}";
+            }
+            return string.Empty;
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}
diff --git a/Clock/Clock.Tizen.Mobile/Tools/TriggerActions/ForceFocusAction.cs b/Clock/Clock.Tizen.Mobile/Tools/TriggerActions/ForceFocusAction.cs
new file mode 100644 (file)
index 0000000..0972193
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+* Copyright 2018  Samsung Electronics Co., Ltd
+*
+* Licensed under the Flora License, Version 1.1 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://floralicense.org/license/
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+using Xamarin.Forms;
+
+namespace Clock.Tizen.Mobile.Tools.TriggerActions
+{
+    public class ForceFocusAction : TriggerAction<VisualElement>
+    {
+        protected override void Invoke(VisualElement sender)
+        {
+            sender?.Focus();
+        }
+    }
+}
index 75403ea..ddb9eb2 100644 (file)
                                Icon="tabs/clock_tabs_ic_worldclock.png" />
         <pages:StopWatchPage Title="{DynamicResource IDS_CLOCK_BODY_STOPWATCH}"
                              Icon="tabs/clock_tabs_ic_stopwatch.png" />
-        <ContentPage Title="{DynamicResource IDS_CLOCK_BODY_TIMER}"
-                     Icon="tabs/clock_tabs_ic_timer.png">
-            <StackLayout>
-                <Label Text="Timer Stub" />
-            </StackLayout>
-        </ContentPage>
+        <pages:TimerPage Title="{DynamicResource IDS_CLOCK_BODY_TIMER}"
+                     Icon="tabs/clock_tabs_ic_timer.png"/>
     </TabbedPage.Children>
-</TabbedPage>
+</TabbedPage>
\ No newline at end of file
diff --git a/Clock/Clock.Tizen.Mobile/Views/TimerPage.xaml b/Clock/Clock.Tizen.Mobile/Views/TimerPage.xaml
new file mode 100644 (file)
index 0000000..c42f494
--- /dev/null
@@ -0,0 +1,204 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
+             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
+             xmlns:controls="clr-namespace:Clock.Tizen.Mobile.Controls;assembly=Clock.Tizen.Mobile"
+             xmlns:viewmodels="clr-namespace:Clock.ViewModels;assembly=Clock"
+             xmlns:converters="clr-namespace:Clock.Tizen.Mobile.Tools.Converters;assembly=Clock.Tizen.Mobile"
+             xmlns:triggerActions="clr-namespace:Clock.Tizen.Mobile.Tools.TriggerActions;assembly=Clock.Tizen.Mobile"
+             xmlns:enums="clr-namespace:Clock.Common.Enums;assembly=Clock"
+             x:Class="Clock.Tizen.Mobile.Views.TimerPage"
+             BindingContext="{Binding Path=TimerViewModel, Source={StaticResource ViewModelLocator}}">
+    <ContentPage.Resources>
+        <ResourceDictionary>
+            <Style TargetType="controls:CustomStyleButton">
+                <Setter Property="WidthRequest" Value="320"/>
+                <Setter Property="VerticalOptions" Value="Center"/>
+                <Setter Property="HorizontalOptions" Value="CenterAndExpand"/>
+                <Setter Property="CustomStyle" Value="bottom"/>
+            </Style>
+            <Style TargetType="controls:ExtendedEntry">
+                <Setter Property="FontSize" Value="80"/>
+                <Setter Property="HorizontalOptions" Value="FillAndExpand"/>
+                <Setter Property="VerticalOptions" Value="FillAndExpand"/>
+                <Setter Property="HorizontalTextAlignment" Value="Center"/>
+                <Setter Property="TextColor" Value="White"/>
+                <Setter Property="SelectAllOnFocused" Value="True"/>
+                <Setter Property="Keyboard" Value="Numeric"/>
+            </Style>
+            <converters:StringToTimePartConverter x:Key="StringToTimePartConverter"/>
+            <converters:TotalHourTimeSpanConverter x:Key="TotalHourTimeSpanConverter"/>
+            <converters:TimerFlowToBoolConverter x:Key="TimerFlowToBoolConverter"/>
+            <enums:TimeFractionName x:Key="TimeFractionName.Hour">Hour</enums:TimeFractionName>
+            <enums:TimeFractionName x:Key="TimeFractionName.Minute">Minute</enums:TimeFractionName>
+            <enums:TimeFractionName x:Key="TimeFractionName.Second">Second</enums:TimeFractionName>
+        </ResourceDictionary>
+    </ContentPage.Resources>
+    <ContentPage.Content>
+        <StackLayout Orientation="Vertical">
+            <StackLayout Orientation="Vertical" VerticalOptions="CenterAndExpand" HorizontalOptions="Fill" Margin="50,0,50,0">
+                <StackLayout x:Name="Headers"
+                             Orientation="Horizontal"
+                             HorizontalOptions="Fill"
+                             VerticalOptions="Center">
+                    <Label Text="Hours"
+                           VerticalOptions="Center"
+                           HorizontalOptions="CenterAndExpand"
+                           VerticalTextAlignment="End"
+                           HorizontalTextAlignment="Center"
+                           TextColor="White"/>
+                    <Label Text="Minutes"
+                           VerticalOptions="Center"
+                           HorizontalOptions="CenterAndExpand"
+                           HorizontalTextAlignment="Center"
+                           TextColor="White" />
+                    <Label Text="Seconds"
+                           VerticalOptions="Center"
+                           HorizontalOptions="CenterAndExpand"
+                           HorizontalTextAlignment="Center"
+                           TextColor="White" />
+                </StackLayout>
+                <controls:CustomStyleLabel x:Name="TimeLabel"
+                                        CustomStyle="Thin"
+                                        Text="{Binding ElapsedTime, Converter={StaticResource TotalHourTimeSpanConverter}}"
+                                        TextColor="White"
+                                        HorizontalTextAlignment="Center"
+                                        FontSize="154"
+                                        HorizontalOptions="CenterAndExpand"
+                                        VerticalOptions="CenterAndExpand"
+                                        IsVisible="{Binding TimerFlow, Converter={StaticResource TimerFlowToBoolConverter}, ConverterParameter={x:Static enums:TimerFlow.Processing}}"/>
+                <StackLayout x:Name="TimeSelector"
+                             Orientation="Vertical"
+                             HorizontalOptions="Fill"
+                             VerticalOptions="Center"
+                             IsVisible="{Binding TimerFlow, Converter={StaticResource TimerFlowToBoolConverter}, ConverterParameter={x:Static enums:TimerFlow.Preparing}}">
+                    <StackLayout Orientation="Horizontal" HorizontalOptions="Fill">
+                        <Button x:Name="ButtonHourIncrease"
+                                VerticalOptions="Center"
+                                HorizontalOptions="CenterAndExpand"
+                                BackgroundColor="Transparent"
+                                Command="{Binding IncreaseTimeFractionCommand}"
+                                CommandParameter="{StaticResource TimeFractionName.Hour}"
+                                Image="images/alarm_picker_arrow_up.png"/>
+                        <Button x:Name="ButtonMinuteIncrease"
+                                VerticalOptions="Center"
+                                HorizontalOptions="CenterAndExpand"
+                                BackgroundColor="Transparent"
+                                Command="{Binding IncreaseTimeFractionCommand}"
+                                CommandParameter="{StaticResource TimeFractionName.Minute}"
+                                Image="images/alarm_picker_arrow_up.png" />
+                        <Button x:Name="ButtonSecondIncrease"
+                                VerticalOptions="Center"
+                                HorizontalOptions="CenterAndExpand"
+                                BackgroundColor="Transparent"
+                                Command="{Binding IncreaseTimeFractionCommand}"
+                                CommandParameter="{StaticResource TimeFractionName.Second}"
+                                Image="images/alarm_picker_arrow_up.png"/>
+                    </StackLayout>
+                    <StackLayout Orientation="Horizontal"
+                                 HorizontalOptions="Fill"
+                                 VerticalOptions="FillAndExpand">
+                        <controls:ExtendedEntry x:Name="EntryHours"
+                                                VerticalOptions="Center"
+                                                HorizontalOptions="CenterAndExpand"
+                                                Text="{Binding Hour, Mode=TwoWay, Converter={StaticResource StringToTimePartConverter}}"
+                                                Guide="Hour"/>
+                        <Label Text=":" FontSize="80"
+                               VerticalOptions="Center"
+                               HorizontalOptions="CenterAndExpand"
+                               VerticalTextAlignment="End"
+                               TextColor="White" />
+                        <controls:ExtendedEntry x:Name="EntryMinutes"
+                                                VerticalOptions="Center"
+                                                HorizontalOptions="CenterAndExpand"
+                                                Text="{Binding Minute, Mode=TwoWay, Converter={StaticResource StringToTimePartConverter}}"
+                                                Guide="Minutes">
+                            <Entry.Triggers>
+                                <DataTrigger TargetType="Entry" Binding="{Binding Source={x:Reference EntryHours}, Path=Text.Length}" Value="2">
+                                    <DataTrigger.EnterActions>
+                                        <triggerActions:ForceFocusAction/>
+                                    </DataTrigger.EnterActions>
+                                </DataTrigger>
+                            </Entry.Triggers>
+                        </controls:ExtendedEntry>
+                        <Label Text=":"
+                               FontSize="80"
+                               VerticalOptions="Center"
+                               HorizontalOptions="CenterAndExpand"
+                               VerticalTextAlignment="End"
+                               TextColor="White"/>
+                        <controls:ExtendedEntry x:Name="EntrySeconds"
+                                                VerticalOptions="Center"
+                                                HorizontalOptions="CenterAndExpand"
+                                                Text="{Binding Second, Mode=TwoWay, Converter={StaticResource StringToTimePartConverter}}"
+                                                Guide="Seconds">
+                            <Entry.Triggers>
+                                <DataTrigger TargetType="Entry" Binding="{Binding Source={x:Reference EntryMinutes}, Path=Text.Length}" Value="2">
+                                    <DataTrigger.EnterActions>
+                                        <triggerActions:ForceFocusAction/>
+                                    </DataTrigger.EnterActions>
+                                </DataTrigger>
+                            </Entry.Triggers>
+                        </controls:ExtendedEntry>
+                    </StackLayout>
+                    <StackLayout Orientation="Horizontal"
+                                 HorizontalOptions="Fill"
+                                 VerticalOptions="Center">
+                        <Button x:Name="ButtonHourDecrease"
+                                VerticalOptions="Center"
+                                HorizontalOptions="CenterAndExpand"
+                                BackgroundColor="Transparent"
+                                Command="{Binding DecreaseTimeFractionCommand}"
+                                CommandParameter="{StaticResource TimeFractionName.Hour}"
+                                Image="images/alarm_picker_arrow_down.png"/>
+                        <Button x:Name="ButtonMinuteDecrease"
+                                VerticalOptions="Center"
+                                HorizontalOptions="CenterAndExpand"
+                                BackgroundColor="Transparent"
+                                Command="{Binding DecreaseTimeFractionCommand}"
+                                CommandParameter="{StaticResource TimeFractionName.Minute}"
+                                Image="images/alarm_picker_arrow_down.png" />
+                        <Button x:Name="ButtonSecondDecrease"
+                                VerticalOptions="Center"
+                                HorizontalOptions="CenterAndExpand"
+                                BackgroundColor="Transparent"
+                                Command="{Binding DecreaseTimeFractionCommand}"
+                                CommandParameter="{StaticResource TimeFractionName.Second}"
+                                Image="images/alarm_picker_arrow_down.png" />
+                    </StackLayout>
+                </StackLayout>
+            </StackLayout>
+            <StackLayout HeightRequest="150"
+                         BackgroundColor="White"
+                         Orientation="Horizontal"
+                         HorizontalOptions="Fill"
+                         VerticalOptions="End">
+                <controls:CustomStyleButton x:Name="StartButton"
+                                        Text="Start"
+                                        Command="{Binding StartTimerCommand}"
+                                        IsVisible="{Binding TimerFlow, Converter={StaticResource TimerFlowToBoolConverter}, ConverterParameter={x:Static enums:TimerFlow.Idle}}"
+                                        IsEnabled="{Binding TimerFlow, Converter={StaticResource TimerFlowToBoolConverter}, ConverterParameter={x:Static enums:TimerFlow.Prepared}}">
+                    <controls:CustomStyleButton.Triggers>
+                        <Trigger TargetType="controls:CustomStyleButton" Property="IsEnabled" Value="False">
+                            <Setter Property="WidthRequest" Value="500"/>
+                        </Trigger>
+                    </controls:CustomStyleButton.Triggers>
+                </controls:CustomStyleButton>
+                <controls:CustomStyleButton x:Name="PauseButton"
+                                        Text="Pause"
+                                        Command="{Binding PauseTimerCommand}"
+                                        IsVisible="{Binding TimerFlow, Converter={StaticResource TimerFlowToBoolConverter}, ConverterParameter={x:Static enums:TimerFlow.Running}}" />
+                <controls:CustomStyleButton x:Name="ResumeButton"
+                                        Text="Resume"
+                                        Command="{Binding ResumeTimerCommand}"
+                                        IsVisible="{Binding TimerFlow, Converter={StaticResource TimerFlowToBoolConverter}, ConverterParameter={x:Static enums:TimerFlow.Paused}}" />
+                <controls:CustomStyleButton x:Name="CancelButton"
+                                        Text="Cancel" Command="{Binding CancelTimerCommand}"
+                                        IsVisible="{Binding TimerFlow, Converter={StaticResource TimerFlowToBoolConverter}, ConverterParameter={x:Static enums:TimerFlow.Canceling}}" />
+                <controls:CustomStyleButton x:Name="ResetButton"
+                                        Text="Reset"
+                                        Command="{Binding ResetTimerCommand}"
+                                        IsVisible="{Binding TimerFlow, Converter={StaticResource TimerFlowToBoolConverter}, ConverterParameter={x:Static enums:TimerFlow.Prepared}}" />
+            </StackLayout>
+        </StackLayout>
+    </ContentPage.Content>
+</ContentPage>
\ No newline at end of file
diff --git a/Clock/Clock.Tizen.Mobile/Views/TimerPage.xaml.cs b/Clock/Clock.Tizen.Mobile/Views/TimerPage.xaml.cs
new file mode 100644 (file)
index 0000000..5eb854c
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+* Copyright 2018  Samsung Electronics Co., Ltd
+*
+* Licensed under the Flora License, Version 1.1 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://floralicense.org/license/
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+using System;
+using Xamarin.Forms;
+using Tizen;
+using Navigation.Tools.Navigation.Interfaces;
+
+namespace Clock.Tizen.Mobile.Views
+{
+    public partial class TimerPage : ContentPage, INavigationServiceAware
+    {
+        public TimerPage()
+        {
+            InitializeComponent();
+        }
+
+        public void DeInitialize()
+        {
+            (BindingContext as INavigationServiceAware)?.DeInitialize();
+        }
+
+        public void Initialize(object parameters)
+        {
+            Initialize();
+        }
+
+        public void Initialize()
+        {
+            (BindingContext as INavigationServiceAware)?.Initialize();
+        }
+
+        public void Initialize<T>()
+        {
+            (BindingContext as INavigationServiceAware)?.Initialize<T>();
+        }
+    }
+}
\ No newline at end of file
diff --git a/Clock/Clock.Tizen.Mobile/Views/TimerView.xaml b/Clock/Clock.Tizen.Mobile/Views/TimerView.xaml
deleted file mode 100644 (file)
index 9e62185..0000000
+++ /dev/null
@@ -1,107 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
-             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
-             xmlns:controls="clr-namespace:Clock.Tizen.Mobile.Controls;assembly=Clock.Tizen.Mobile"
-             xmlns:viewmodels="clr-namespace:Clock.ViewModels;assembly=Clock"
-             xmlns:converters="clr-namespace:Clock.Tizen.Mobile.Tools.Converters;assembly=Clock.Tizen.Mobile"
-             x:Class="Clock.Tizen.Mobile.Views.TimerView">
-
-    <ContentPage.BindingContext>
-        <viewmodels:TimerViewModel />
-    </ContentPage.BindingContext>
-
-    <ContentPage.Resources>
-        <ResourceDictionary>
-            <converters:TimerMenuStateConverter x:Key="MenuStateConverter" />
-            <x:String x:Key="TimeLabel">timeLabel</x:String>
-            <x:String x:Key="TimeSelector">timeSelector</x:String>
-            <x:String x:Key="StartButton">startButton</x:String>
-            <x:String x:Key="CancelButton">cancelButton</x:String>
-            <x:String x:Key="PauseButton">pauseButton</x:String>
-            <x:String x:Key="ResumeButton">resumeButton</x:String>
-            <viewmodels:ButtonType x:Key="HourButton">HOUR</viewmodels:ButtonType>
-            <viewmodels:ButtonType x:Key="MinuteButton">MINUTE</viewmodels:ButtonType>
-            <viewmodels:ButtonType x:Key="SecondButton">SECOND</viewmodels:ButtonType>
-        </ResourceDictionary>
-    </ContentPage.Resources>
-
-    <ContentPage.Content>
-        <AbsoluteLayout>
-            <Label x:Name="TimeLabel" Text="{Binding Time}" TextColor="White" HorizontalTextAlignment="Center"
-                   FontSize="80" AbsoluteLayout.LayoutBounds=".5, .38, 1.0, 0.2" AbsoluteLayout.LayoutFlags="All"
-                   IsVisible="{Binding State, Converter={StaticResource MenuStateConverter}, ConverterParameter={StaticResource TimeLabel}}" />
-            <StackLayout x:Name="TimeSelector" Orientation="Horizontal" AbsoluteLayout.LayoutBounds=".5, .3, 620, 426"
-                         AbsoluteLayout.LayoutFlags="PositionProportional"
-                         IsVisible="{Binding State, Converter={StaticResource MenuStateConverter}, ConverterParameter={StaticResource TimeSelector}}">
-                <StackLayout Orientation="Vertical" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
-                    <Label Text="Hours" AbsoluteLayout.LayoutBounds=".235, .0, .2, .09"
-                           AbsoluteLayout.LayoutFlags="All" VerticalTextAlignment="End"
-                           HorizontalTextAlignment="Center" TextColor="White" />
-                    <Button x:Name="ButtonHourIncrease" BackgroundColor="Transparent" Command="{Binding IncreaseTime}"
-                            CommandParameter="{StaticResource HourButton}" Image="images/alarm_picker_arrow_up.png" />
-                    <controls:ExtendedEntry x:Name="EntryHours"
-                                            Text="{Binding Hour, Mode=TwoWay, StringFormat='{0:D2}'}" FontSize="80"
-                                            HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand"
-                                            HorizontalTextAlignment="Center" TextColor="White" MaxValue="99" />
-                    <Button x:Name="ButtonHourDecrease" BackgroundColor="Transparent" Command="{Binding DecreaseTime}"
-                            CommandParameter="{StaticResource HourButton}" Image="images/alarm_picker_arrow_down.png" />
-                </StackLayout>
-                <Label Text=":" FontSize="80" HorizontalOptions="FillAndExpand" VerticalOptions="Center"
-                       VerticalTextAlignment="Center" TextColor="White" />
-                <StackLayout Orientation="Vertical" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
-                    <Label Text="Minutes" AbsoluteLayout.LayoutBounds=".5, .0, .2, .09"
-                           AbsoluteLayout.LayoutFlags="All" HorizontalTextAlignment="Center" TextColor="White" />
-                    <Button x:Name="ButtonMinuteIncrease" BackgroundColor="Transparent"
-                            Command="{Binding IncreaseTime}" CommandParameter="{StaticResource MinuteButton}"
-                            Image="images/alarm_picker_arrow_up.png" />
-                    <controls:ExtendedEntry x:Name="EntryMinutes"
-                                            Text="{Binding Minute, Mode=TwoWay, StringFormat='{0:D2}'}" FontSize="80"
-                                            HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand"
-                                            HorizontalTextAlignment="Center" TextColor="White" MaxValue="59" />
-                    <Button x:Name="ButtonMinuteDecrease" BackgroundColor="Transparent"
-                            Command="{Binding DecreaseTime}" CommandParameter="{StaticResource MinuteButton}"
-                            Image="images/alarm_picker_arrow_down.png" />
-                </StackLayout>
-                <Label Text=":" FontSize="80" HorizontalOptions="FillAndExpand" VerticalOptions="Center"
-                       VerticalTextAlignment="Center" TextColor="White" />
-                <StackLayout Orientation="Vertical" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
-                    <Label Text="Seconds" AbsoluteLayout.LayoutBounds=".765, .0, .2, .09"
-                           AbsoluteLayout.LayoutFlags="All" HorizontalTextAlignment="Center" TextColor="White" />
-                    <Button x:Name="ButtonSecondIncrease" BackgroundColor="Transparent"
-                            Command="{Binding IncreaseTime}" CommandParameter="{StaticResource SecondButton}"
-                            Image="images/alarm_picker_arrow_up.png" />
-                    <controls:ExtendedEntry x:Name="EntrySeconds"
-                                            Text="{Binding Second, Mode=TwoWay, StringFormat='{0:D2}'}" FontSize="80"
-                                            HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand"
-                                            HorizontalTextAlignment="Center" TextColor="White" MaxValue="59" />
-                    <Button x:Name="ButtonSecondDecrease" BackgroundColor="Transparent"
-                            Command="{Binding DecreaseTime}" CommandParameter="{StaticResource SecondButton}"
-                            Image="images/alarm_picker_arrow_down.png" />
-                </StackLayout>
-            </StackLayout>
-
-            <AbsoluteLayout AbsoluteLayout.LayoutBounds=".5, 1, 720, 120"
-                            AbsoluteLayout.LayoutFlags="PositionProportional">
-                <BoxView Color="White" AbsoluteLayout.LayoutBounds=".5, 1, 1, 1" AbsoluteLayout.LayoutFlags="All" />
-                <Button x:Name="StartButton" Text="Start" Command="{Binding StartTimer}"
-                        AbsoluteLayout.LayoutBounds=".5, .5, 0.7, 0.7" AbsoluteLayout.LayoutFlags="All"
-                        IsVisible="{Binding State, Converter={StaticResource MenuStateConverter}, ConverterParameter={StaticResource StartButton}}"
-                        IsEnabled="{Binding IsTimeValid}" />
-                <Button x:Name="PauseButton" Text="Pause" Command="{Binding PauseTimer}"
-                        AbsoluteLayout.LayoutBounds=".1, .5, 0.4, 0.7" AbsoluteLayout.LayoutFlags="All"
-                        IsVisible="{Binding State, Converter={StaticResource MenuStateConverter}, ConverterParameter={StaticResource PauseButton}}" />
-                <Button x:Name="CancelButton" Text="Cancel" Command="{Binding CancelTimer}"
-                        AbsoluteLayout.LayoutBounds=".9, .5, 0.4, 0.7" AbsoluteLayout.LayoutFlags="All"
-                        IsVisible="{Binding State, Converter={StaticResource MenuStateConverter}, ConverterParameter={StaticResource CancelButton}}" />
-                <Button x:Name="ResumeButton" Text="Resume" Command="{Binding ResumeTimer}"
-                        AbsoluteLayout.LayoutBounds=".1, .5, 0.4, 0.7" AbsoluteLayout.LayoutFlags="All"
-                        IsVisible="{Binding State, Converter={StaticResource MenuStateConverter}, ConverterParameter={StaticResource ResumeButton}}" />
-                <Button x:Name="ResetButton" Text="Reset" Command="{Binding ResetTimer}"
-                        AbsoluteLayout.LayoutBounds=".9, .5, 0.4, 0.7" AbsoluteLayout.LayoutFlags="All"
-                        IsVisible="False" />
-            </AbsoluteLayout>
-
-        </AbsoluteLayout>
-    </ContentPage.Content>
-</ContentPage>
\ No newline at end of file
diff --git a/Clock/Clock.Tizen.Mobile/Views/TimerView.xaml.cs b/Clock/Clock.Tizen.Mobile/Views/TimerView.xaml.cs
deleted file mode 100644 (file)
index 9ee5b1a..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using Xamarin.Forms;
-using Clock.Tizen.Mobile.Controls;
-using System.Windows.Input;
-
-namespace Clock.Tizen.Mobile.Views
-{
-    public partial class TimerView : ContentPage
-    {
-        public TimerView()
-        {
-            InitializeComponent();
-
-            EntryHours.NextFocus = EntryMinutes;
-            EntryMinutes.NextFocus = EntrySeconds;
-            EntrySeconds.NextFocus = EntryHours;
-        }
-    }
-}
\ No newline at end of file
diff --git a/Clock/Clock/Common/Enums/TimeFractionName.cs b/Clock/Clock/Common/Enums/TimeFractionName.cs
new file mode 100644 (file)
index 0000000..f04c913
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+* Copyright 2018  Samsung Electronics Co., Ltd
+*
+* Licensed under the Flora License, Version 1.1 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://floralicense.org/license/
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+namespace Clock.Common.Enums
+{
+    public enum TimeFractionName
+    {
+        Hour,
+        Minute,
+        Second
+    }
+}
\ No newline at end of file
diff --git a/Clock/Clock/Common/Enums/TimerFlow.cs b/Clock/Clock/Common/Enums/TimerFlow.cs
new file mode 100644 (file)
index 0000000..deae8df
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+* Copyright 2018  Samsung Electronics Co., Ltd
+*
+* Licensed under the Flora License, Version 1.1 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://floralicense.org/license/
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+using System;
+
+namespace Clock.Common.Enums
+{
+    [Flags]
+    public enum TimerFlow
+    {
+        /// <summary>
+        /// Timer is not initialized.
+        /// </summary>
+        Idle = 1 << 0,
+        /// <summary>
+        /// Timer has been initialized and is ready to run.
+        /// </summary>
+        Prepared = 1 << 1,
+        /// <summary>
+        /// Timer is running.
+        /// </summary>
+        Running = 1 << 2,
+        /// <summary>
+        /// Timer is paused.
+        /// </summary>
+        Paused = 1 << 3,
+        /// <summary>
+        /// Timer can be calceled.
+        /// </summary>
+        Canceling = 1 << 4,
+        /// <summary>
+        /// Timer is being initialized.
+        /// </summary>
+        Preparing = 1 << 5,
+        /// <summary>
+        /// Timer is running or paused.
+        /// </summary>
+        Processing = 1 << 6
+    }
+}
\ No newline at end of file
index 9c2756b..87fb8a3 100644 (file)
@@ -33,9 +33,23 @@ namespace Clock.Services
         private readonly IPlatformAlarmsService _platformAlarmsService;
         private readonly ITimerRepository _timerRepository;
         private readonly INavigationService _navigationService;
+        private readonly ILogger _logger;
 
+        private DateTime _scheduledDateTime;
         public TimerState State => _timerRepository.State;
 
+        public TimeSpan ElapsedTime
+        {
+            get
+            {
+                if (State != TimerState.Running)
+                {
+                    return _timerRepository.TimeLeft;
+                }
+                return _scheduledDateTime - _platformAlarmsService.GetCurrentPlatformTime();
+            }
+        }
+
         /// <summary>
         /// Invoked when timer was started.
         /// </summary>
@@ -58,7 +72,7 @@ namespace Clock.Services
         /// <param name="timerRepository">Repository with alarms.</param>
         /// <param name="navigationService">Application navigation service.</param>
         public TimerManager(IPlatformAlarmsService platformAlarmsService, ITimerRepository timerRepository,
-            INavigationService navigationService)
+            INavigationService navigationService, ILogger logger)
         {
             if (platformAlarmsService == null || timerRepository == null || navigationService == null)
             {
@@ -68,7 +82,7 @@ namespace Clock.Services
             _platformAlarmsService = platformAlarmsService;
             _timerRepository = timerRepository;
             _navigationService = navigationService;
-
+            _logger = logger;
             _platformAlarmsService.PlatformAlarmTriggered += PlatformAlarmTriggered;
 
             Synchronize();
@@ -109,10 +123,9 @@ namespace Clock.Services
                 time = _timerRepository.TimeLeft;
             }
 
+            _scheduledDateTime = _platformAlarmsService.GetCurrentPlatformTime().AddTicks(time.Ticks);
             _timerRepository.PlatformId = _platformAlarmsService
-                .CreateNewPlatformAlarm(
-                    _platformAlarmsService.GetCurrentPlatformTime().AddTicks(time.Ticks),
-                    DaysOfWeek.Never).Id;
+                .CreateNewPlatformAlarm(_scheduledDateTime, DaysOfWeek.Never).Id;
             _timerRepository.State = TimerState.Running;
             TimerStarted?.Invoke();
         }
@@ -126,12 +139,17 @@ namespace Clock.Services
             {
                 return;
             }
-
             _platformAlarmsService.GetPlatformAlarm(_timerRepository.PlatformId)?.Cancel();
-            _timerRepository.State = TimerState.Idle;
+            Reset();
             TimerStopped?.Invoke();
         }
 
+        private void Reset()
+        {
+            _timerRepository.State = TimerState.Idle;
+            _timerRepository.TimeLeft = TimeSpan.Zero;
+        }
+
         /// <summary>
         /// Pauses timer.
         /// </summary>
@@ -142,10 +160,8 @@ namespace Clock.Services
                 return;
             }
 
-            var platformAlarm = _platformAlarmsService.GetPlatformAlarm(_timerRepository.PlatformId);
-
-            _timerRepository.TimeLeft =
-                platformAlarm.GetScheduledDateTime() - _platformAlarmsService.GetCurrentPlatformTime();
+            IPlatformAlarm platformAlarm = _platformAlarmsService.GetPlatformAlarm(_timerRepository.PlatformId);
+            _timerRepository.TimeLeft = ElapsedTime;
 
             platformAlarm.Cancel();
             _timerRepository.State = TimerState.Paused;
@@ -154,9 +170,14 @@ namespace Clock.Services
 
         private void Synchronize()
         {
-            if (_platformAlarmsService.GetPlatformAlarm(_timerRepository.PlatformId) == null)
+            if (_platformAlarmsService.GetPlatformAlarm(_timerRepository.PlatformId) is IPlatformAlarm _platformAlarm)
+            {
+                _scheduledDateTime = _platformAlarm.GetScheduledDateTime();
+                _timerRepository.State = TimerState.Running;
+            }
+            else
             {
-                _timerRepository.State = TimerState.Idle;
+                Reset();
             }
         }
 
index 465bee3..4512955 100644 (file)
 using Clock.Abstractions;
 using System;
 using System.Windows.Input;
-using Navigation.Tools.Navigation.ViewModels;
 using Xamarin.Forms;
+using Navigation.Tools.Navigation.Interfaces;
 using Clock.Model;
+using Clock.Common.Enums;
+using Clock.Services;
 
 namespace Clock.ViewModels
 {
-    public enum MenuState
+    public class TimerViewModel : NavigationServiceAwareViewModel
     {
-        STARTUP,
-        STARTUP_EXTENDED,
-        RUNNING,
-        PAUSED
-    }
-
-    public enum ButtonType
-    {
-        HOUR,
-        MINUTE,
-        SECOND
-    }
-
-    public class TimerViewModel : ViewModelBase
-    {
-        private string time;
+        private TimeSpan _elapsedTime;
         private int hour, minute, second;
-        private MenuState state = MenuState.STARTUP;
-        private bool isTimeValid;
-        private IAlarmProvider _alarmProvider = null;
+        private TimerFlow _timerFlow = TimerFlow.Idle | TimerFlow.Preparing;
 
-        public ICommand StartTimer { private set; get; }
-        public ICommand PauseTimer { private set; get; }
-        public ICommand CancelTimer { private set; get; }
-        public ICommand ResumeTimer { private set; get; }
-        public ICommand ResetTimer { private set; get; }
-        public ICommand IncreaseTime { private set; get; }
-        public ICommand DecreaseTime { private set; get; }
-
-        private Timer model = new Timer();
-
-        public bool IsTimeValid
-        {
-            get { return isTimeValid; }
-            set
-            {
-                if (value != isTimeValid)
-                {
-                    SetPropertyValue(ref isTimeValid, value);
-                }
-            }
-        }
+        public ICommand StartTimerCommand { private set; get; }
+        public ICommand PauseTimerCommand { private set; get; }
+        public ICommand CancelTimerCommand { private set; get; }
+        public ICommand ResumeTimerCommand { private set; get; }
+        public ICommand ResetTimerCommand { private set; get; }
+        public ICommand IncreaseTimeFractionCommand { private set; get; }
+        public ICommand DecreaseTimeFractionCommand { private set; get; }
+        private readonly ILogger _logger;
+        private readonly TimerManager _timerManager;
 
         public int Hour
         {
             get { return hour; }
             set
             {
-                if (value != hour)
+                if (value != hour && value != -1)
                 {
-                    ValidateTime();
                     SetPropertyValue(ref hour, value);
+                    ElapsedTime = ReplaceTimeFraction(value, TimeFractionName.Hour, ElapsedTime);
                 }
             }
         }
@@ -85,10 +58,10 @@ namespace Clock.ViewModels
             get { return minute; }
             set
             {
-                if (value != minute)
+                if (value != minute && value != -1)
                 {
-                    ValidateTime();
                     SetPropertyValue(ref minute, value);
+                    ElapsedTime = ReplaceTimeFraction(value, TimeFractionName.Minute, ElapsedTime);
                 }
             }
         }
@@ -98,150 +71,215 @@ namespace Clock.ViewModels
             get { return second; }
             set
             {
-                if (value != second)
+                if (value != second && value != -1)
                 {
-                    ValidateTime();
                     SetPropertyValue(ref second, value);
+                    ElapsedTime = ReplaceTimeFraction(value, TimeFractionName.Second, ElapsedTime);
                 }
             }
         }
 
-        private void ValidateTime()
+        private void ValidateTime(TimeSpan timeSpan)
         {
-            if (hour == 0 && minute == 0 && second == 0)
+            if (timeSpan == TimeSpan.Zero)
             {
-                IsTimeValid = false;
+                TimerFlow = TimerFlow.Idle | TimerFlow.Preparing;
             }
             else
             {
-                IsTimeValid = true;
+                if (TimerFlow.HasFlag(TimerFlow.Idle))
+                {
+                    TimerFlow = TimerFlow.Prepared | TimerFlow.Idle | TimerFlow.Preparing;
+                }
             }
         }
 
-        public MenuState State
+        public TimerFlow TimerFlow
         {
-            get { return state; }
-            set
-            {
-                if (value != state)
-                {
-                    SetPropertyValue(ref state, value);
-                }
-            }
+            get => _timerFlow;
+            set => SetPropertyValue(ref _timerFlow, value);
         }
 
-        public string Time
+        public TimeSpan ElapsedTime
         {
-            get { return time; }
+            get => _elapsedTime;
             set
             {
-                if (time != value)
+                if (_elapsedTime != value)
                 {
-                    SetPropertyValue(ref time, value);
+                    SetPropertyValue(ref _elapsedTime, value);
                 }
             }
         }
 
         private bool TimersTick()
         {
-            if (State != MenuState.RUNNING)
+            if (TimerFlow.HasFlag(TimerFlow.Preparing) || TimerFlow.HasFlag(TimerFlow.Paused))
             {
                 return false;
             }
 
-            if ((int)model.Time.TotalHours == 0 && model.Time.Minutes == 0 && model.Time.Seconds == 0)
+            ElapsedTime = TimeSpan.FromSeconds(Math.Floor(_timerManager.ElapsedTime.TotalSeconds) + 1);
+            return true;
+        }
+
+        private TimeSpan ReplaceTimeFraction(int timeFraction, TimeFractionName timeFractionName, TimeSpan source)
+        {
+            TimeSpan result;
+
+            switch (timeFractionName)
             {
-                State = MenuState.STARTUP;
-                CancelTimer.Execute(null);
+                case TimeFractionName.Hour: result = TimeSpan.FromSeconds((source.TotalHours - source.Hours + timeFraction) * 3600); break;
+                case TimeFractionName.Minute: result = TimeSpan.FromSeconds((source.TotalMinutes - source.Minutes + GetValidTimeFraction(timeFraction, 60)) * 60); break;
+                default: result = TimeSpan.FromSeconds(source.TotalSeconds - source.Seconds + GetValidTimeFraction(timeFraction, 60)); break;
             }
+            ValidateTime(result);
 
-            Time = string.Format("{0:00}:{1:00}:{2:00}", (int)model.Time.TotalHours, model.Time.Minutes,
-                model.Time.Seconds);
+            return result;
+        }
 
-            return true;
+        private int GetValidTimeFraction(int timeFraction, int maxValue)
+        {
+            return timeFraction >= maxValue ? maxValue - 1 : timeFraction;
         }
 
-        public TimerViewModel()
+        public TimerViewModel(INavigationService navigationService, ILogger logger, TimerManager timerManager)
+            : base(navigationService)
         {
-            StartTimer = new Command(() =>
-            {
-                model.Timeout = new TimeSpan(hour, minute, second);
-                model.Run();
-                Device.StartTimer(TimeSpan.FromMilliseconds(33), TimersTick);
+            _logger = logger;
 
-                State = MenuState.RUNNING;
+            _timerManager = timerManager;
 
-                _alarmProvider?.CreateAlarm(null);
-            });
+            StartTimerCommand = new Command(StartTimer);
 
-            PauseTimer = new Command(() =>
-            {
-                model.Pause();
-                State = MenuState.PAUSED;
-                _alarmProvider?.DismissAlarm(null);
-            });
+            PauseTimerCommand = new Command(_timerManager.Pause);
 
-            CancelTimer = new Command(() =>
-            {
-                model.Stop();
-                model.Reset();
-                State = MenuState.STARTUP;
+            CancelTimerCommand = new Command(CancelTimer);
+
+            ResumeTimerCommand = new Command(_timerManager.Start);
+
+            ResetTimerCommand = new Command(ResetTimer);
+
+            IncreaseTimeFractionCommand = new Command<TimeFractionName>((type) => ChangeTimeFraction(type, true));
+            DecreaseTimeFractionCommand = new Command<TimeFractionName>((type) => ChangeTimeFraction(type, false));
+        }
+
+        private void ResetTimer()
+        {
+            TimerFlow = TimerFlow.Idle | TimerFlow.Preparing;
+            Reset(TimeSpan.Zero);
+            _timerManager.SetTimerTime(TimeSpan.Zero);
+        }
+
+        private void CancelTimer()
+        {
+            _timerManager.Stop();
+        }
 
-                _alarmProvider?.DismissAlarm(null);
-            });
+        private void StartTimer()
+        {
+            _timerManager.SetTimerTime(ElapsedTime);
+            _timerManager.Start();
+        }
 
-            ResumeTimer = new Command(() =>
+        private void SetTimerFlowForStarted()
+        {
+            if (TimerFlow.HasFlag(TimerFlow.Running))
             {
-                model.Resume();
-                Device.StartTimer(TimeSpan.FromMilliseconds(33), TimersTick);
-                State = MenuState.RUNNING;
+                return;
+            }
+
+            TimerFlow = TimerFlow.Running | TimerFlow.Canceling | TimerFlow.Processing;
+            Device.StartTimer(TimeSpan.FromMilliseconds(33), TimersTick);
+        }
+
+        private void SetTimerFlowForPaused()
+        {
+            TimerFlow = TimerFlow.Paused | TimerFlow.Canceling | TimerFlow.Processing;
+        }
+
+        private void ResetElapsedTime()
+        {
+            ElapsedTime = new TimeSpan(Hour, Minute, Second);
+        }
 
-                _alarmProvider?.CreateAlarm(null);
-            });
+        private void Reset(TimeSpan timeSpan)
+        {
+            Hour = timeSpan.Hours;
+            Minute = timeSpan.Minutes;
+            Second = timeSpan.Seconds;
+        }
 
-            ResetTimer = new Command(() =>
+        private void ChangeTimeFraction(TimeFractionName timeFractionName, bool increment)
+        {
+            switch (timeFractionName)
             {
-                model.Reset();
-                Time = "00:00:00";
-            });
+                case TimeFractionName.Hour: Hour = increment ? (Hour + 1) % 100 : (Hour + 99) % 100; break;
+                case TimeFractionName.Minute: Minute = increment ? (Minute + 1) % 60 : (Minute + 59) % 60; break;
+                default: Second = increment ? (Second + 1) % 60 : (Second + 59) % 60; break;
+            }
+        }
 
-            IncreaseTime = new Command<ButtonType>((type) =>
+        private void SynchronizeStates()
+        {
+            switch (_timerManager.State)
             {
-                switch (type)
-                {
-                    case ButtonType.HOUR:
-                        Hour = (Hour + 1) % 100;
-                        break;
-                    case ButtonType.MINUTE:
-                        Minute = (Minute + 1) % 60;
-                        break;
-                    case ButtonType.SECOND:
-                        Second = (Second + 1) % 60;
-                        break;
-                    default:
-                        throw new ArgumentOutOfRangeException(nameof(type), type, null);
-                }
-            });
+                case TimerState.Idle: SetTimerFlowForIdle(); break;
+                case TimerState.Paused: SetTimerFlowForPaused(); break;
+                case TimerState.Running: SetTimerFlowForStarted(); break;
+                default: break;
+            }
+        }
 
-            DecreaseTime = new Command<ButtonType>((type) =>
+        private void SetTimerFlowForIdle()
+        {
+            if (TimerFlow.HasFlag(TimerFlow.Processing))
+            {
+                TimerFlow = TimerFlow.Idle | TimerFlow.Prepared | TimerFlow.Preparing;
+                ResetElapsedTime();
+            }
+            else
             {
-                switch (type)
+                TimerFlow = TimerFlow.Idle | TimerFlow.Preparing;
+            }
+        }
+
+        public override void DeInitialize()
+        {
+            base.DeInitialize();
+            _timerManager.TimerStarted -= SynchronizeStates;
+            _timerManager.TimerPaused -= SynchronizeStates;
+            _timerManager.TimerStopped -= SynchronizeStates;
+        }
+
+        public override void Initialize()
+        {
+            base.Initialize();
+
+            SynchronizeStates();
+            SynchronizeTime();
+            _timerManager.TimerStarted += SynchronizeStates;
+            _timerManager.TimerPaused += SynchronizeStates;
+            _timerManager.TimerStopped += SynchronizeStates;
+        }
+
+        private void SynchronizeTime()
+        {
+            if (TimerFlow.HasFlag(TimerFlow.Running))
+            {
+                if (_timerManager.GetTimerTime().HasValue)
                 {
-                    case ButtonType.HOUR:
-                        Hour = (Hour + 99) % 100;
-                        break;
-                    case ButtonType.MINUTE:
-                        Minute = (Minute + 59) % 60;
-                        break;
-                    case ButtonType.SECOND:
-                        Second = (Second + 59) % 60;
-                        break;
-                    default:
-                        throw new ArgumentOutOfRangeException(nameof(type), type, null);
+                    Reset(_timerManager.GetTimerTime().Value);
                 }
-            });
+            }
+        }
 
-            Time = "00:00:00";
+        public override void Initialize<T>()
+        {
+            if (typeof(T) == typeof(TimerViewModel))
+            {
+                Initialize();
+            }
         }
     }
 }
\ No newline at end of file