--- /dev/null
+/*
+ * Copyright (c) 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 Alarm.Controls;
+using Alarm.Implements;
+using Alarm.Models;
+using Alarm.Tizen.Renderers;
+using Alarm.ViewModels;
+using Alarm.Views;
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Threading;
+using Tizen.Applications;
+using Tizen.System;
+using Tizen.Wearable.CircularUI.Forms;
+using Tizen.Wearable.CircularUI.Forms.Renderer;
+using Xamarin.Forms;
+using Xamarin.Forms.Platform.Tizen;
+
+namespace Alarm
+{
+ class Program : global::Xamarin.Forms.Platform.Tizen.ApplicationLifecycle
+ {
+ App app;
+ AlertPageModel _alertPageModel;
+
+ /// <summary>
+ /// Called when this application is launched
+ /// </summary>
+ protected override void OnCreate()
+ {
+ AlarmNativeHandler.ResourceDir = FormsApplication.DirectoryInfo.Resource;
+
+ base.OnCreate();
+
+ app = new App();
+
+ FormsApplication.LoadApplication(app);
+ }
+
+ protected override void OnPause()
+ {
+ Console.WriteLine("OnPause");
+ if (_alertPageModel != null)
+ {
+ if(_alertPageModel.AlertSoundState == SoundState.Start)
+ {
+ Console.WriteLine("Alert sound state is start, pause alert sound!");
+ _alertPageModel.PauseAlert();
+ }
+ }
+
+ base.OnPause();
+
+ }
+
+ protected override void OnResume()
+ {
+ Console.WriteLine("OnResume");
+ base.OnResume();
+ }
+
+ /// <summary>
+ /// Called when this app control event received.
+ /// </summary>
+ /// <param name="e">AppControlReceivedEventArgs</param>
+ async protected override void OnAppControlReceived(AppControlReceivedEventArgs e)
+ {
+ Console.WriteLine("OnAppControlReceived");
+ base.OnAppControlReceived(e);
+ AppControl appControl = e.ReceivedAppControl;
+ var navi = app.MainPage as NavigationPage;
+ var currentPage = navi.CurrentPage;
+ try
+ {
+ if (appControl.ExtraData.Count() != 0)
+ {
+ string AlarmCreatedDate = appControl.ExtraData.Get<string>("AlarmRecord.UniqueIdentifier");
+ Console.WriteLine($"ExtraData.Count() != 0 AlarmCreatedDate:{AlarmCreatedDate}");
+ if (AlarmModel.AlarmRecordDictionary == null)
+ {
+ // Need to retrieve at the page creation time
+ AlarmModel.AlarmRecordDictionary = AlarmPersistentHandler.DeserializeAlarmRecord();
+ }
+
+ AlarmRecord retrievedRecord;
+ if (AlarmModel.AlarmRecordDictionary != null)
+ {
+ if (AlarmModel.AlarmRecordDictionary.TryGetValue(AlarmCreatedDate, out retrievedRecord))
+ {
+ if (retrievedRecord == null)
+ {
+ Console.WriteLine("[OnAppControlReceived] retrievedRecord is null!!");
+ }
+
+ Console.WriteLine("retrievedRecord:" + retrievedRecord);
+ if (retrievedRecord != null && retrievedRecord.AlarmState < AlarmStates.Inactive)
+ {
+ _alertPageModel = new AlertPageModel(retrievedRecord);
+ await currentPage.Navigation.PushAsync(new AlarmAlertPage(_alertPageModel));
+ }
+ }
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("[OnAppControlReceived] Exception - Message: " + ex.Message + ", source: " + ex.Source + ", " + ex.StackTrace);
+ }
+ }
+
+ static void Main(string[] args)
+ {
+ var app = new Program();
+ // define your custom handlers
+ var customRenderers = new Dictionary<Type, Func<IRegisterable>>()
+ {
+ { typeof(CirclePage), ()=> new CirclePageRenderer() },
+ { typeof(global:: Tizen.Wearable.CircularUI.Forms.CircleListView), () => new CircleListViewRenderer() },
+ { typeof(CircleDateTimeSelector), () => new CircleDateTimeSelectorRenderer() },
+ { typeof(CustomImageButton), () => new CustomImageButtonRenderer() }
+ };
+
+ var option = new InitializationOptions(app);
+ option.UseStaticRegistrar(StaticRegistrarStrategy.StaticRegistrarOnly, customRenderers, true);
+ Forms.Init(option);
+
+ // It's mandatory to initialize Circular UI for using Tizen Wearable Circular UI API
+ global::Tizen.Wearable.CircularUI.Forms.Renderer.FormsCircularUI.Init();
+ app.FormsApplication.Run(args);
+ }
+ }
+}
--- /dev/null
+<Project Sdk="Tizen.NET.Sdk/1.0.8">
+
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>tizen40</TargetFramework>
+ </PropertyGroup>
+
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugType>portable</DebugType>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <DebugType>None</DebugType>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <Folder Include="lib\" />
+ <Folder Include="res\" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\..\..\Tizen.CircularUI\Tizen.Wearable.CircularUI.Forms.Renderer\XSF.CircularUI.Forms.Renderer.csproj" />
+ <ProjectReference Include="..\..\..\Tizen.CircularUI\Tizen.Wearable.CircularUI.Forms\XSF.CircularUI.Forms.csproj" />
+ <ProjectReference Include="..\..\..\Xamarin.Forms\Xamarin.Forms.Core\XSF.Core.csproj" />
+ <ProjectReference Include="..\..\..\Xamarin.Forms\Xamarin.Forms.Platform.Tizen\XSF.Platform.Tizen.csproj" />
+ <ProjectReference Include="..\..\..\Xamarin.Forms\Xamarin.Forms.Platform\XSF.Platform.csproj" />
+ <ProjectReference Include="..\..\..\Xamarin.Forms\Xamarin.Forms.Xaml\XSF.Xaml.csproj" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <Compile Update="Resx\AppResource - Copy.Designer.cs">
+ <DependentUpon>AppResource - Copy.resx</DependentUpon>
+ <DesignTime>True</DesignTime>
+ <AutoGen>True</AutoGen>
+ </Compile>
+ <Compile Update="Resx\AppResource - Copy.Designer.cs">
+ <DesignTime>True</DesignTime>
+ <AutoGen>True</AutoGen>
+ <DependentUpon>AppResource.resx</DependentUpon>
+ </Compile>
+ <Compile Update="Resx\AppResources.Designer.cs">
+ <DesignTime>True</DesignTime>
+ <AutoGen>True</AutoGen>
+ <DependentUpon>AppResources.resx</DependentUpon>
+ </Compile>
+ <Compile Update="Views\AlarmAlertPage.xaml.cs">
+ <DependentUpon>AlarmAlertPage.xaml</DependentUpon>
+ </Compile>
+ </ItemGroup>
+
+ <ItemGroup>
+ <EmbeddedResource Update="App.xaml">
+ <Generator>MSBuild:UpdateDesignTimeXaml</Generator>
+ </EmbeddedResource>
+ <EmbeddedResource Update="Resx\AppResources.ko.resx">
+ <LastGenOutput>AppResource.Designer.cs</LastGenOutput>
+ <Generator>PublicResXFileCodeGenerator</Generator>
+ </EmbeddedResource>
+ <EmbeddedResource Update="Resx\AppResources.resx">
+ <Generator>PublicResXFileCodeGenerator</Generator>
+ <LastGenOutput>AppResources.Designer.cs</LastGenOutput>
+ </EmbeddedResource>
+ <EmbeddedResource Update="Views\AlarmEditPage.xaml">
+ <Generator>MSBuild:UpdateDesignTimeXaml</Generator>
+ </EmbeddedResource>
+ <EmbeddedResource Update="Views\AlarmAlertPage.xaml">
+ <Generator>MSBuild:UpdateDesignTimeXaml</Generator>
+ </EmbeddedResource>
+ <EmbeddedResource Update="Views\CirclePageEx.xaml">
+ <Generator>MSBuild:UpdateDesignTimeXaml</Generator>
+ </EmbeddedResource>
+ <EmbeddedResource Update="Views\MainPage.xaml">
+ <Generator>MSBuild:UpdateDesignTimeXaml</Generator>
+ </EmbeddedResource>
+ </ItemGroup>
+
+ <ItemGroup>
+ <None Update="App.xaml">
+ <Generator>MSBuild:Compile</Generator>
+ </None>
+ <None Update="Views\AlarmAlertPage.xaml">
+ <Generator>MSBuild:Compile</Generator>
+ </None>
+ <None Update="Views\AlarmEditPage.xaml">
+ <Generator>MSBuild:Compile</Generator>
+ </None>
+ <None Update="Views\CirclePageEx.xaml">
+ <Generator>MSBuild:Compile</Generator>
+ </None>
+ <None Update="Views\MainPage.xaml">
+ <Generator>MSBuild:Compile</Generator>
+ </None>
+ <None Update="Views\SavePopupPage.xaml">
+ <Generator>MSBuild:Compile</Generator>
+ </None>
+ </ItemGroup>
+
+</Project>
+
--- /dev/null
+/*
+ * Copyright (c) 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;
+using Alarm.Models;
+using Alarm.Views;
+using System;
+using Alarm.ViewModels;
+
+namespace Alarm
+{
+ public enum AlarmPages
+ {
+ /// <summary>
+ /// alarm main page
+ /// </summary>
+ MainPage,
+ /// <summary>
+ /// alarm edit page
+ /// </summary>
+ EditPage,
+ /// <summary>
+ ///
+ /// </summary>
+ SavePoupPage
+ }
+
+ public static class AlarmPageController
+ {
+ /// <summary>
+ /// alarm list page
+ /// </summary>
+ static MainPage alarmMainPage;
+
+ /// <summary>
+ /// alarm edit page
+ /// </summary>
+ static AlarmEditPage alarmEditPage;
+
+ /// <summary>
+ /// no alarm page
+ /// </summary>
+ static SavePopupPage savePopupPage;
+
+ public static Page GetInstance(AlarmPages page, Object o = null)
+ {
+ switch (page)
+ {
+ case AlarmPages.MainPage:
+ return AlarmPageController.alarmMainPage ?? (alarmMainPage = new MainPage(o as MainPageModel));
+ case AlarmPages.EditPage:
+ // parameter 'o' is AlarmRecord object to show in AlarmEditPage.
+ // 1. Copy values to AlarmModel.BindableAlarmRecord
+ AlarmModel.BindableAlarmRecord.DeepCopy((AlarmRecord)o);
+ // 2. Apply values to EditPage (at first, create UI. next time, just show it)
+ alarmEditPage?.Update((AlarmRecord)o);
+ return AlarmPageController.alarmEditPage ?? (alarmEditPage = new AlarmEditPage(o as AlarmRecord));
+ case AlarmPages.SavePoupPage:
+ return AlarmPageController.savePopupPage ?? (savePopupPage = new SavePopupPage(o as AlarmRecord));
+ default:
+ return null;
+ }
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8" ?>
+<Application
+ x:Class="Alarm.App"
+ xmlns="http://xamarin.com/schemas/2014/forms"
+ xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
+ <Application.Resources>
+
+ <!-- Application resource dictionary -->
+
+ </Application.Resources>
+</Application>
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright (c) 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 Alarm.Implements;
+using Alarm.Models;
+using Alarm.ViewModels;
+using Alarm.Views;
+using System;
+using System.Globalization;
+using Tizen.Applications;
+using Xamarin.Forms;
+using Xamarin.Forms.Platform.Tizen;
+using Xamarin.Forms.Xaml;
+
+namespace Alarm
+{
+ /// <summary>
+ /// Alarm Application class
+ /// </summary>
+ [XamlCompilation(XamlCompilationOptions.Compile)]
+ public partial class App : Xamarin.Forms.Application
+ {
+ MainPage firstPage;
+
+ public static CultureInfo Culture { get; set; }
+
+ public App()
+ {
+ UpdateLocale();
+
+ InitializeComponent();
+
+ var alarmModel = new AlarmModel();
+ firstPage = (MainPage)AlarmPageController.GetInstance(AlarmPages.MainPage, new MainPageModel());
+ MainPage = new NavigationPage(firstPage);
+ }
+
+ protected override void OnStart()
+ {
+ // Handle when your app starts
+ }
+
+ protected override void OnSleep()
+ {
+ // Handle when your app sleeps
+ }
+
+ protected override void OnResume()
+ {
+ // Handle when your app resumes
+ }
+
+ /// <summary>
+ /// Get the current locale and apply it.
+ /// </summary>
+ public void UpdateLocale()
+ {
+ // determine the correct, supported .NET culture
+ var handler = new LocaleHandler();
+
+ Culture = handler.CurrentCultureInfo;
+ // set the RESX for resource localization
+ Resx.AppResources.Culture = Culture;
+
+ // Whenever language has been changed, CurrentCulture will be updated.
+ MessagingCenter.Subscribe<LocaleHandler, CultureInfo>(this, MessageKeys.LanguageChanged, (obj, culture) =>
+ {
+ Console.WriteLine($"Received LanguageChanged culture:{culture.ToString()}");
+ });
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright (c) 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.Diagnostics;
+using System.Windows.Input;
+using Xamarin.Forms;
+
+namespace Alarm.Controls
+{
+ /// <summary>
+ /// A element button which is implemented with a custom renderer based on a native image.
+ /// </summary>
+ public class CustomImageButton : Image
+ {
+ public static readonly BindableProperty BlendColorProperty = BindableProperty.Create("BlendColor", typeof(Color), typeof(CustomImageButton), Color.Default);
+
+ public event EventHandler Clicked;
+
+ public event EventHandler Released;
+
+ /// <summary>
+ /// A color when the button is pressed.
+ /// </summary>
+ public Color BlendColor
+ {
+ get { return (Color)GetValue(BlendColorProperty); }
+ set { SetValue(BlendColorProperty, value); }
+ }
+
+ /// <summary>
+ /// To broadcast a click event to subscribers
+ /// </summary>
+ public void SendClicked()
+ {
+ Clicked?.Invoke(this, EventArgs.Empty);
+ }
+
+ /// <summary>
+ /// To broadcast a release event to subscribers
+ /// </summary>
+ public void SendReleased()
+ {
+ Released?.Invoke(this, EventArgs.Empty);
+ }
+
+ /// <summary>
+ /// CustomImageButton constructor
+ /// </summary>
+ public CustomImageButton()
+ {
+ BlendColor = Color.FromRgba(255, 255, 255, 100);
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 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 Alarm.Converters
+{
+ public enum MainPageType
+ {
+ // Empty layout
+ Empty,
+ // ListView
+ ListView,
+ //Action Button
+ Button,
+ }
+
+ /// <summary>
+ /// Converter class
+ /// This class converts bindable source value to proper target value
+ /// Depending on the number of items in ListView, control ListView's visibility
+ /// </summary>
+ class ItemCountToVisibilityConverter : IValueConverter
+ {
+ /// <summary>
+ /// Converting source value to target value
+ /// </summary>
+ /// <param name="value">Source object</param>
+ /// <param name="targetType">The target type to convert</param>
+ /// <param name="parameter">parameter object</param>
+ /// <param name="culture">The culture info</param>
+ /// <returns>Returns converted bool to decide UI widget's visibility</returns>
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ int count = System.Convert.ToInt32(value);
+ MainPageType type = (MainPageType)parameter;
+ bool flag = false;
+ switch (type)
+ {
+ case MainPageType.ListView:
+ case MainPageType.Button:
+ flag = false;
+ break;
+ case MainPageType.Empty:
+ default:
+ flag = true;
+ break;
+ }
+
+ //Console.WriteLine($"[ItemCountToVisibilityConverter] type:{type}, flag:{flag}, count:{count}");
+ if (count == 0)
+ {
+ return (bool)System.Convert.ToBoolean(flag);
+ }
+
+ return (bool)!System.Convert.ToBoolean(flag);
+ }
+
+ /// <summary>
+ /// Converting back source value to target value
+ /// This method is not being used in this app.
+ /// </summary>
+ /// <param name="value">Source object</param>
+ /// <seealso cref="System.object">
+ /// <param name="targetType">The target type to convert</param>
+ /// <seealso cref="Type">
+ /// <param name="CultureInfo">The culture info</param>
+ /// <seealso cref="CultureInfo">
+ /// <returns>Returns null</returns>
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ return null;
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 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 Alarm.Converters
+{
+ /// <summary>
+ /// Converter class
+ /// This class converts bindable source value to proper target value
+ /// Depending on the Alarm time label's text value
+ /// </summary>
+ class ScheduledDateTimeToTextConverter : IValueConverter
+ {
+ /// <summary>
+ /// Converting source value to target value
+ /// </summary>
+ /// <param name="value">Source object</param>
+ /// <seealso cref="System.object">
+ /// <param name="targetType">The target type to convert</param>
+ /// <seealso cref="Type">
+ /// <param name="CultureInfo">The culture info</param>
+ /// <seealso cref="CultureInfo">
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ DateTime time = (DateTime)value;
+ string text = "";
+ text = time.ToString("hh") + ":" + time.ToString("mm") + " " + time.ToString("tt");
+ //Console.WriteLine($"ScheduledDateTimeToTextConverter {text}");
+ return text;
+ }
+
+ /// <summary>
+ /// Converting back source value to target value
+ /// This method is not being used in this app.
+ /// </summary>
+ /// <param name="value">Source object</param>
+ /// <seealso cref="System.object">
+ /// <param name="targetType">The target type to convert</param>
+ /// <seealso cref="Type">
+ /// <param name="CultureInfo">The culture info</param>
+ /// <seealso cref="CultureInfo">
+ /// <returns>Returns null</returns>
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ return null;
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright (c) 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 Alarm.Models;
+using Native = Tizen.Applications;
+using Tizen.Multimedia;
+using Tizen.System;
+using System;
+
+namespace Alarm.Implements
+{
+ /// <summary>
+ /// The Alarm class
+ /// </summary>
+ public static class AlarmNativeHandler
+ {
+ static AudioStreamPolicy audioStreamPolicy;
+ static Feedback feedback;
+ static Player player;
+ static string SoundNotificationUri = "Notification.ogg";
+
+ public static string ResourceDir { get; set; }
+
+ /// <summary>
+ /// Gets system alarm ID from alarm object
+ /// </summary>
+ /// <param name="alarm">AlarmRecord object to get system alarm ID from</param>
+ /// <seealso cref="AlarmRecord">
+ /// <returns> Returns system alarm ID </returns>
+ public static int GetAlarmID(object alarm)
+ {
+ Native.Alarm nativeAlarm = alarm as Native.Alarm;
+ return nativeAlarm.AlarmId;
+ }
+
+ /// <summary>
+ /// Creates a system alarm of AlarmRecord
+ /// </summary>
+ /// <param name="binableAlarmRecord">AlarmRecord object to create a system alarm based on</param>
+ /// <seealso cref="AlarmRecord">
+ /// <returns> Returns native alarm object </returns>
+ public static int CreateAlarm(AlarmRecord record)
+ {
+ Native.AppControl appControl = new Native.AppControl()
+ {
+ ApplicationId = Native.Application.Current.ApplicationInfo.ApplicationId
+ };
+ appControl.ExtraData.Add("AlarmRecord.UniqueIdentifier", record.GetUniqueIdentifier());
+ //temporary set AllDays , After implement select day of week. additional implementation should be add.
+ Native.Alarm nativeAlarm = Native.AlarmManager.CreateAlarm(record.ScheduledDateTime, Native.AlarmWeekFlag.AllDays, appControl);
+ Console.WriteLine("@@ [Alarm.CreateAlarm] UID : " + record.GetUniqueIdentifier()
+ + ", ScheduledDateTime : " + record.ScheduledDateTime + " NativeAlarmID : " + nativeAlarm.AlarmId);
+ return nativeAlarm.AlarmId;
+ }
+
+ /// <summary>
+ /// Cancel a system alarm of AlarmRecord
+ /// </summary>
+ /// <param name="binableAlarmRecord">AlarmRecord object relevant to a system alarm to cancel</param>
+ /// <seealso cref="AlarmRecord">
+ public static void DeleteAlarm(AlarmRecord record)
+ {
+ var allAlarms = Native.AlarmManager.GetAllScheduledAlarms();
+ foreach (Native.Alarm item in allAlarms)
+ {
+ if (item.AlarmId == record.NativeAlarmID)
+ {
+ Console.WriteLine("@@ [Alarm.DeleteAlarm] Cancel NativeAlarmID:" + record.NativeAlarmID + ", UID:" + record.GetUniqueIdentifier());
+ item.Cancel();
+ record.NativeAlarmID = 0;
+ break;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Update the system alarm
+ /// </summary>
+ /// <param name="record">AlarmRecord</param>
+ public static void UpdateAlarm(ref AlarmRecord record)
+ {
+ DeleteAlarm(record);
+ record.NativeAlarmID = CreateAlarm(record);
+ }
+
+ /// <summary>
+ /// Starts to vibrate
+ /// </summary>
+ public static void StartVibration()
+ {
+ feedback = new Feedback();
+ if (feedback.IsSupportedPattern(FeedbackType.Vibration, "Timer"))
+ {
+ feedback.Play(FeedbackType.Vibration, "Timer");
+ }
+ }
+
+ /// <summary>
+ /// Stops to vibrate
+ /// </summary>
+ public static void StopVibration()
+ {
+ if (feedback != null)
+ {
+ feedback.Stop();
+ }
+ }
+
+ /// <summary>
+ /// Asynchronously plays ringing sound
+ /// </summary>
+ async public static void PlaySound()
+ {
+ if (player == null)
+ {
+ audioStreamPolicy = new AudioStreamPolicy(AudioStreamType.Alarm);
+ audioStreamPolicy.AcquireFocus(AudioStreamFocusOptions.Playback, AudioStreamBehaviors.Fading, null);
+
+ player = new Player();
+ string uri = ResourceDir + SoundNotificationUri;
+ MediaUriSource soudSource = new MediaUriSource(uri);
+ player.SetSource(soudSource);
+
+ player.ApplyAudioStreamPolicy(audioStreamPolicy);
+ await player.PrepareAsync();
+ player.IsLooping = true;
+ player.Volume = 1;
+ if (player.State == PlayerState.Ready)
+ {
+ player.Start();
+ }
+ }
+ }
+
+ public static void PauseSound()
+ {
+ if (player.State == PlayerState.Playing)
+ {
+ player.Stop();
+ }
+ }
+
+ public static void ResumeSound()
+ {
+ if (player.State == PlayerState.Paused || player.State == PlayerState.Ready)
+ {
+ player.Start();
+ }
+ }
+
+ /// <summary>
+ /// Asynchronously stops ringing
+ /// </summary>
+ public static void StopSound()
+ {
+ if (player != null)
+ {
+ player.Stop();
+ player.Unprepare();
+ audioStreamPolicy.ReleaseFocus(AudioStreamFocusOptions.Playback, AudioStreamBehaviors.Fading, null);
+ player.Dispose();
+ player = null;
+ }
+
+ if (audioStreamPolicy != null)
+ {
+ audioStreamPolicy.Dispose();
+ }
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 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.Collections.Generic;
+using System.IO;
+using System.Runtime.Serialization;
+using System.Threading.Tasks;
+using System.Xml;
+using Alarm.Models;
+
+namespace Alarm.Implements
+{
+ /// <summary>
+ /// The Deserializer class to deserialize an AlarmRecord object
+ /// </summary>
+ public static class AlarmPersistentHandler
+ {
+ const string AlarmRecordStoreFile = "AlarmRecordFile.xml";
+
+ /// <summary>
+ /// Deserializes an AlarmRecord object from XML document
+ /// </summary>
+ /// <returns> a collection of <string, AlarmRecord></string> </returns>
+ public static IDictionary<string, AlarmRecord> DeserializeAlarmRecord()
+ {
+ Console.WriteLine("DeserializeAlarmRecord");
+ Stream alarmFileStream = null;
+ var fileOpener = new FileOpener();
+ alarmFileStream = fileOpener.OpenFile(AlarmRecordStoreFile, System.IO.FileMode.OpenOrCreate);
+ if (alarmFileStream.Length == 0)
+ {
+ return null;
+ }
+
+ using (XmlDictionaryReader alarmReader = XmlDictionaryReader.CreateBinaryReader(alarmFileStream, XmlDictionaryReaderQuotas.Max))
+ {
+ var dataContractSerializer = new DataContractSerializer(typeof(Dictionary<string, AlarmRecord>));
+ Object ret1 = dataContractSerializer.ReadObject(alarmReader);
+ alarmFileStream = null;
+ return (IDictionary<string, AlarmRecord>)ret1;
+ }
+ }
+
+ /// <summary>
+ /// Serializes an AlarmRecord object to XML document
+ /// </summary>
+ /// <param name="properties">AlarmRecordDictionary object to serialize</param>
+ /// <seealso cref="IDictionary<string, AlarmRecord>">
+ /// <returns> Task return type </returns>
+ public static Task SerializeAlarmRecordAsync(IDictionary<string, AlarmRecord> properties)
+ {
+ Console.WriteLine("SerializeAlarmRecordAsync");
+ //properties = new Dictionary<string, object>(properties);
+ // Serialize property dictionary to local storage
+ // Make sure to use Internal
+ return Task.Run(() =>
+ {
+ Stream alarmFileStream = null;
+ var fileOpener = new FileOpener();
+ try
+ {
+ alarmFileStream = fileOpener.OpenFile(AlarmRecordStoreFile, System.IO.FileMode.Create);
+ using (XmlDictionaryWriter alarmWriter = XmlDictionaryWriter.CreateBinaryWriter(alarmFileStream))
+ {
+ var dataContractSerializer = new DataContractSerializer(typeof(Dictionary<string, AlarmRecord>));
+ dataContractSerializer.WriteObject(alarmWriter, properties);
+ alarmFileStream = null;
+ alarmWriter.Flush();
+ }
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine("[Alarm serialization error] " + e.Message);
+ }
+ finally
+ {
+ if (alarmFileStream != null)
+ {
+ alarmFileStream.Dispose();
+ }
+ }
+
+ return;
+ });
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 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.IO;
+using TApplication = Tizen.Applications.Application;
+
+namespace Alarm.Implements
+{
+ /// <summary>
+ /// The FileAccessor class
+ /// </summary>
+ class FileOpener
+ {
+ string _path;
+
+ internal FileOpener()
+ {
+ _path = TApplication.Current.DirectoryInfo.Data;
+ }
+
+ public Stream OpenFile(string filePath, System.IO.FileMode fileMode)
+ {
+ if (filePath == null || filePath.Trim().Length == 0)
+ {
+ throw new ArgumentNullException("File path arguments are invalid.");
+ }
+
+ return new FileStream(Path.Combine(_path, filePath), fileMode, System.IO.FileAccess.ReadWrite, System.IO.FileShare.Read);
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 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 Tizen.System;
+using System.Threading;
+using Xamarin.Forms;
+using Alarm.Models;
+using Xamarin.Forms.Platform.Tizen;
+
+namespace Alarm.Implements
+{
+ public class LocaleHandler
+ {
+ CultureInfo _currentCultureInfo;
+
+ public LocaleHandler()
+ {
+ _currentCultureInfo = GetCurrentCultureInfo();
+
+ // To get notified when system locale settings has been changed
+ SystemSettings.LocaleLanguageChanged += OnLanguageChanged;
+ }
+
+ /// <summary>
+ /// Get the current CultureInfo
+ /// </summary>
+ public CultureInfo CurrentCultureInfo
+ {
+ get
+ {
+ return _currentCultureInfo;
+ }
+ }
+
+ /// <summary>
+ /// Set the current CultureInfo
+ /// </summary>
+ /// <param name="info">CultureInfo</param>
+ public void SetLocale(CultureInfo info)
+ {
+ Thread.CurrentThread.CurrentCulture = info;
+ Thread.CurrentThread.CurrentUICulture = info;
+ }
+
+ /// <summary>
+ /// Get CultureInfo from the locale information
+ /// </summary>
+ /// <returns>CultureInfo</returns>
+ CultureInfo GetCurrentCultureInfo()
+ {
+ var netLanguage = "en";
+ var TizenLocale = SystemSettings.LocaleLanguage;
+
+ netLanguage = TizenToDotnetLanguage(TizenLocale.ToString().Replace("_", "-"));
+ CultureInfo info = null;
+ try
+ {
+ info = new CultureInfo(netLanguage);
+ }
+ catch (CultureNotFoundException e1)
+ {
+ Console.WriteLine($"cannot find the current cultureInfo. so use 'en'. {e1.Message}");
+ info = new CultureInfo("en");
+ }
+
+ return info;
+ }
+
+ /// <summary>
+ /// Invoked when system locale setting has been changed
+ /// </summary>
+ /// <param name="sender">object</param>
+ /// <param name="e">LocaleLanguageChangedEventArgs</param>
+ private void OnLanguageChanged(object sender, LocaleLanguageChangedEventArgs e)
+ {
+ Console.WriteLine($"OnLanguageChanged");
+ CultureInfo info = GetCurrentCultureInfo();
+ _currentCultureInfo = info;
+ SetLocale(info);
+
+ // set the RESX for resource localization
+ Resx.AppResources.Culture = info;
+
+ // Notify the change of locale information
+ MessagingCenter.Send<LocaleHandler,CultureInfo>(this, MessageKeys.LanguageChanged, info);
+ }
+
+ string TizenToDotnetLanguage(string tizenLanguage)
+ {
+ var netLanguage = tizenLanguage;
+ //certain languages need to be converted to CultureInfo equivalent
+ switch (tizenLanguage)
+ {
+ case "zh-CN": // Chinese Simplified (People's Republic of China)
+ netLanguage = "zh-Hans"; // correct code for .NET
+ break;
+ case "zh-HK": // Chinese Traditional (Hong Kong)
+ case "zh-hk":
+ case "zh-tw": // Chinese Traditional (Taiwan)
+ case "zh-TW":
+ netLanguage = "zh-Hant"; // correct code for .NET
+ break;
+ }
+
+ //Console.WriteLine($".NET Language/Locale:{netLanguage}");
+ return netLanguage;
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 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.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using Alarm.Implements;
+
+namespace Alarm.Models
+{
+ /// <summary>
+ /// AlarmModel class
+ /// </summary>
+ public class AlarmModel : INotifyPropertyChanged
+ {
+ /// <summary>
+ /// Gets/Sets AlarmRecordDictionary
+ /// </summary>
+ public static IDictionary<string, AlarmRecord> AlarmRecordDictionary { get; set; }
+
+ /// <summary>
+ /// ObservableAlarmList property
+ /// </summary>
+ public static ObservableCollection<AlarmRecord> ObservableAlarmList { get; set; }
+
+ /// <summary>
+ /// This static field holds current alarm record which is being displayed for alarm info input.
+ /// Through this record, edit view is automatically updated when users select certain option in sub pages.
+ /// </summary>
+ public static AlarmRecord BindableAlarmRecord { get; set; }
+
+ /// <summary>
+ /// AlarmModel constructor
+ /// </summary>
+ public AlarmModel()
+ {
+ ObservableAlarmList = new ObservableCollection<AlarmRecord>();
+ AlarmRecordDictionary = AlarmPersistentHandler.DeserializeAlarmRecord();
+ BindableAlarmRecord = new AlarmRecord();
+ if (AlarmRecordDictionary == null)
+ {
+ AlarmRecordDictionary = new Dictionary<string, AlarmRecord>();
+ }
+ else
+ {
+ foreach (var alarmItem in AlarmRecordDictionary)
+ {
+ Console.WriteLine("alarmItem key:" + alarmItem.Key);
+ // Key is DateCreated
+ var keyString = alarmItem.Key;
+ // Retrieve alarm record
+ AlarmRecord retrieved = alarmItem.Value;
+ Console.WriteLine("retrieved:" + retrieved.ToString());
+ // Add the retrieved alarm to list for AlarmListUI
+ ObservableAlarmList.Add(retrieved);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Update alarm record
+ /// </summary>
+ /// <param name="record">AlarmRecord</param>
+ public static void UpdateAlarm(AlarmRecord record)
+ {
+ var obj = record;
+ // Update native alarm
+ AlarmNativeHandler.UpdateAlarm(ref obj);
+ record = obj;
+ string alarmUID = record.GetUniqueIdentifier();
+
+ // Update list
+ for (int i = 0; i < ObservableAlarmList.Count; i++)
+ {
+ AlarmRecord item = ObservableAlarmList[i];
+ if (item.GetUniqueIdentifier() == alarmUID)
+ {
+ Console.WriteLine("Found AlarmRecord(UID: " + item.GetUniqueIdentifier() + ") in ObservableAlarmList.");
+ AlarmRecordDictionary.Remove(alarmUID);
+ item.DeepCopy(record);
+ AlarmRecordDictionary.Add(alarmUID, item);
+ SaveDictionary();
+ break;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Create native alarm
+ /// </summary>
+ public static void CreateAlarm()
+ {
+ // create a native alarm using AlarmModel.BindableAlarmRecord
+ // After then, update Native alarm ID.
+ BindableAlarmRecord.NativeAlarmID = AlarmNativeHandler.CreateAlarm(BindableAlarmRecord);
+
+ AlarmRecord record = new AlarmRecord();
+ record.DeepCopy(BindableAlarmRecord);
+ ObservableAlarmList.Add(record);
+ AlarmRecordDictionary.Add(record.GetUniqueIdentifier(), record);
+ SaveDictionary();
+ }
+
+ /// <summary>
+ /// Cancel native alarm
+ /// and then remove AlarmRecord from ObservableAlarmList and AlarmRecordDictionary
+ /// </summary>
+ /// <param name="alarm">AlarmRecord</param>
+ public static void DeleteAlarm(AlarmRecord alarm)
+ {
+ Console.WriteLine("DeleteAlarm:" + alarm.ToString());
+ // Cancel the native alarm
+ AlarmNativeHandler.DeleteAlarm(alarm);
+ string UID = alarm.GetUniqueIdentifier();
+ // Delete alarm from dictionary
+ AlarmModel.AlarmRecordDictionary.Remove(UID);
+ // Delete alarm from List
+ for (int i = AlarmModel.ObservableAlarmList.Count - 1; i >= 0; i--)
+ {
+ if (AlarmModel.ObservableAlarmList[i].GetUniqueIdentifier() == UID)
+ {
+ ObservableAlarmList.RemoveAt(i);
+ break;
+ }
+ }
+
+ AlarmModel.SaveDictionary();
+ }
+
+ /// <summary>
+ /// Cancel native alarm
+ /// and then deactivate AlarmRecord from AlarmRecordDictionary
+ /// </summary>
+ /// <param name="record">AlarmRecord</param>
+ public static void DeactivatelAlarm(AlarmRecord record)
+ {
+ //1. cancel native alarm
+ AlarmNativeHandler.DeleteAlarm(record);
+ //2. Make AlarmRecord's AlarmState
+ string UID = record.GetUniqueIdentifier();
+ UpdateDictionaryAndList(UID, 0, false);
+ }
+
+ /// <summary>
+ /// Reactivate AlarmRecord
+ /// </summary>
+ /// <param name="record">AlarmRecord for reactivating</param>
+ public static void ReactivatelAlarm(AlarmRecord record)
+ {
+ //1. register native alarm
+ int NativeAlarmID = AlarmNativeHandler.CreateAlarm(record);
+ //2. Update NativeAlarmID and AlarmState
+ string UID = record.GetUniqueIdentifier();
+ }
+
+ /// <summary>
+ /// Update AlarmRecord Dictionary and ObservableAlarmList
+ /// </summary>
+ /// <param name="UID">AlarmRecord unique ID</param>
+ /// <param name="NativeAlarmID">Native AlarmID</param>
+ /// <param name="active">active state of AlarmRecord</param>
+ private static void UpdateDictionaryAndList(string UID, int NativeAlarmID, bool active)
+ {
+ for (int i = 0; i < AlarmModel.ObservableAlarmList.Count; i++)
+ {
+ AlarmRecord item = AlarmModel.ObservableAlarmList[i];
+ if (item.GetUniqueIdentifier() == UID)
+ {
+ AlarmModel.AlarmRecordDictionary.Remove(UID);
+ item.NativeAlarmID = NativeAlarmID;
+ if (active)
+ {
+ item.AlarmState = AlarmStates.Active;
+ }
+ else
+ {
+ item.AlarmState = AlarmStates.Inactive;
+ }
+
+ AlarmModel.AlarmRecordDictionary.Add(UID, item);
+ AlarmModel.SaveDictionary();
+ break;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Serializes alarm record dictionary object and save it to file
+ /// </summary>
+ public static void SaveDictionary()
+ {
+ AlarmPersistentHandler.SerializeAlarmRecordAsync(AlarmModel.AlarmRecordDictionary).Wait();
+ }
+
+ ///<summary>
+ ///Event that is raised when the properties of AlarmRecord change
+ ///</summary>
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ /// <summary>
+ /// When property is changed, registered event callback is invoked
+ /// </summary>
+ /// <param name="propertyName">Property name of the change event occured</param>
+ protected virtual void OnPropertyChanged(string propertyName)
+ {
+ if (PropertyChanged != null)
+ {
+ PropertyChanged(this,
+ new PropertyChangedEventArgs(propertyName));
+ }
+ }
+
+ /// <summary>
+ /// PrintAll method for debugging.
+ /// </summary>
+ /// <param name="methodName">method name for printing</param>
+ public static void PrintAll(string methodName)
+ {
+ Console.WriteLine("");
+ Console.WriteLine("+++++++++++++++ PrintAll ++++++++++");
+ Console.WriteLine(" START " + methodName);
+ if (AlarmModel.AlarmRecordDictionary != null)
+ {
+ Console.WriteLine(" AlarmRecordDictionary");
+ foreach (var item in AlarmModel.AlarmRecordDictionary)
+ {
+ Console.WriteLine(" Key: " + item.Key + ", Value:" + item.Value.ToString());
+ }
+ }
+
+ Console.WriteLine("");
+ int count = 1;
+ if (AlarmModel.ObservableAlarmList != null)
+ {
+ Console.WriteLine(" ObservableAlarmList");
+ foreach (AlarmRecord item in AlarmModel.ObservableAlarmList)
+ {
+ Console.WriteLine(count++ + ")" + item.ToString());
+ }
+ }
+
+ Console.WriteLine("");
+ count = 1;
+ Console.WriteLine(" END " + methodName);
+ }
+
+ /// <summary>
+ /// Determines whether ObservableAlarmList contains an alarm which is scheduled at the same time.
+ /// </summary>
+ /// <param name="duplicate">AlarmRecord</param>
+ /// <returns> true if a same alarm is found in the list; otherwise, false.</returns>
+ public static bool CheckAlarmExist(ref AlarmRecord duplicate)
+ {
+ foreach (AlarmRecord item in AlarmModel.ObservableAlarmList)
+ {
+ // Check scheduled time and alarm name
+ if (item.ScheduledDateTime.Equals(AlarmModel.BindableAlarmRecord.ScheduledDateTime))
+ {
+ // a same alarm is found.
+ duplicate.DeepCopy(item);
+ return true;
+ }
+ }
+ // there's no same alarm.
+ duplicate = null;
+ return false;
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright (c) 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;
+
+namespace Alarm.Models
+{
+ [Flags]
+ public enum AlarmWeekFlag
+ {
+ /// <summary>
+ /// Identifier for Never
+ /// </summary>
+ Never = 0x00,
+
+ /// <summary>
+ /// Identifier for Sunday.
+ /// </summary>
+ Sunday = 0x01,
+
+ /// <summary>
+ /// Identifier for Monday.
+ /// </summary>
+ Monday = 0x02,
+
+ /// <summary>
+ /// Identifier for Tuesday.
+ /// </summary>
+ Tuesday = 0x04,
+
+ /// <summary>
+ /// Identifier for Wednesday.
+ /// </summary>
+ Wednesday = 0x08,
+
+ /// <summary>
+ /// Identifier for Thursday.
+ /// </summary>
+ Thursday = 0x10,
+
+ /// <summary>
+ /// Identifier for Friday.
+ /// </summary>
+ Friday = 0x20,
+
+ /// <summary>
+ /// Identifier for Saturday.
+ /// </summary>
+ Saturday = 0x40,
+
+ /// <summary>
+ /// All Days of the Week.
+ /// </summary>
+ AllDays = Sunday | Monday | Tuesday | Wednesday | Thursday | Friday | Saturday,
+
+ /// <summary>
+ /// Only Weekdays
+ /// </summary>
+ WeekDays = Monday | Tuesday | Wednesday | Thursday | Friday
+ }
+
+ /// <summary>
+ /// The enumeration of AlarmStates
+ /// </summary>
+ public enum AlarmStates
+ {
+ /// <summary>
+ /// Identifier for Snooze
+ /// </summary>
+ Snooze,
+ /// <summary>
+ /// Identifier for Active
+ /// </summary>
+ Active,
+ /// <summary>
+ /// Identifier for Inactive
+ /// </summary>
+ Inactive,
+ }
+
+ public class AlarmRecord : INotifyPropertyChanged
+ {
+ private DateTime _scheduledDateTime;
+ /// <summary>
+ /// Gets/Sets ScheduledDateTime
+ /// </summary>
+ public DateTime ScheduledDateTime
+ {
+ get
+ {
+ return _scheduledDateTime;
+ }
+
+ set
+ {
+ if (_scheduledDateTime != value)
+ {
+ _scheduledDateTime = value;
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("ScheduledDateTime"));
+ }
+ }
+ }
+
+ private AlarmWeekFlag _weekFlag;
+ /// <summary>
+ /// Gets/Sets the day of week when an alarm recurs
+ /// </summary>
+ public AlarmWeekFlag WeekFlag
+ {
+ get
+ {
+ return _weekFlag;
+ }
+
+ set
+ {
+ if (_weekFlag != value)
+ {
+ _weekFlag = value;
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("WeekFlag"));
+ }
+ }
+ }
+
+ private AlarmStates _alarmstate;
+ /// <summary>
+ /// Gets/Sets the state of alarm
+ /// </summary>
+ public AlarmStates AlarmState
+ {
+ get { return _alarmstate; }
+ set
+ {
+ if (_alarmstate != value)
+ {
+ _alarmstate = value;
+ OnPropertyChanged("AlarmState");
+ }
+ }
+ }
+
+ /// <summary>
+ /// Gets/Sets alarm creation time
+ /// It is used as a Key in AlarmRecordDictionary.
+ /// </summary>
+ public TimeSpan DateCreated { get; set; }
+
+ /// <summary>
+ /// Gets/Sets system alarm ID
+ /// </summary>
+ public int NativeAlarmID { get; set; }
+
+
+ private bool _isSerialized;
+
+ /// <summary>
+ /// Gets/Sets whether the AlarmRecord object has been serialized
+ /// This indicates whether it is newly created or not.
+ /// </summary>
+ public bool IsSerialized
+ {
+ get
+ {
+ return _isSerialized;
+ }
+
+ set
+ {
+ if (_isSerialized != value)
+ {
+ _isSerialized = value;
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("IsSerialized"));
+ }
+ }
+ }
+
+ ///<summary>
+ ///Event that is raised when the properties of AlarmRecord change
+ ///</summary>
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ /// <summary>
+ /// Initializes a new instance of the AlarmRecord class
+ /// </summary>
+ public AlarmRecord()
+ {
+ }
+
+ /// <summary>
+ /// Converts the string of the creation date time
+ /// </summary>
+ /// <returns> Returns a string representation of value of the alarm creation time</returns>
+ public string GetUniqueIdentifier()
+ {
+ return DateCreated.ToString();
+ }
+
+ /// <summary>
+ /// Return a string that represents the current AlarmRecord object.
+ /// </summary>
+ /// <returns>string</returns>
+ public override string ToString()
+ {
+ return "Alarm " + ScheduledDateTime
+ + ", NativeID[" + NativeAlarmID + "]"
+ + ", (" + WeekFlag + ")"
+ + ", (" + DateCreated + ")"
+ + ", (" + AlarmState + ")"
+ + ", (" + IsSerialized + ")";
+ }
+
+ /// <summary>
+ /// Sets the default value for AlarmRecord object
+ /// </summary>
+ public void SetDefault()
+ {
+ /// Sets current time to scheduled date time (Alarm creation time)
+ ScheduledDateTime = System.DateTime.Now;
+
+ /// Sets Never to Repeat weekday flag
+ WeekFlag = AlarmWeekFlag.AllDays;
+
+ /// This field is used to uniquely identify alarm by created time
+ DateCreated = ScheduledDateTime.TimeOfDay;
+
+ AlarmState = AlarmStates.Active;
+
+ /// This indicates whether it is newly created or not.
+ /// This value should set to false at default creation time
+ IsSerialized = false;
+ }
+
+ /// <summary>
+ /// Deep copy alarm record
+ /// </summary>
+ /// <param name="bindableAlarmRecord">Alarm record to deep copy</param>
+ /// <seealso cref="AlarmRecord">
+ public void DeepCopy(AlarmRecord Record, bool includeUID = true)
+ {
+ // All properties should be copied to this alarm record object
+ ScheduledDateTime = Record.ScheduledDateTime;
+ WeekFlag = Record.WeekFlag;
+ AlarmState = Record.AlarmState;
+ IsSerialized = Record.IsSerialized;
+ if (includeUID)
+ {
+ DateCreated = Record.DateCreated;
+ NativeAlarmID = Record.NativeAlarmID;
+ }
+ }
+
+ /// <summary>
+ /// When property is changed, registered event callback is invoked
+ /// </summary>
+ /// <param name="propertyName">Property name of the change event occured</param>
+ protected virtual void OnPropertyChanged(string propertyName)
+ {
+ if (PropertyChanged != null)
+ {
+ PropertyChanged(this,
+ new PropertyChangedEventArgs(propertyName));
+ }
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright (c) 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 Alarm.Models
+{
+ public class MessageKeys
+ {
+ // Notify that language has been changed
+ public const string LanguageChanged = "LanguageChanged";
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 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 ElmSharp;
+using Xamarin.Forms.Platform.Tizen;
+
+using Alarm.Controls;
+using Alarm.Tizen.Renderers;
+using Xamarin.Forms;
+
+[assembly: ExportRenderer(typeof(CustomImageButton), typeof(Alarm.Tizen.Renderers.CustomImageButtonRenderer))]
+namespace Alarm.Tizen.Renderers
+{
+ /// <summary>
+ /// Calculator element (Operator, numbers) button custom renderer
+ /// Actually to implement command button, A image is used instead a button to display as a calculator button.
+ /// </summary>
+ /// <remarks>
+ /// Please refer to Xamarin Custom Renderer
+ /// https://developer.xamarin.com/guides/xamarin-forms/custom-renderer/
+ /// </remarks>
+ class CustomImageButtonRenderer : ImageRenderer//ViewRenderer<Xamarin.Forms.Image, Image>
+ {
+ /// <summary>
+ /// Tizen's gesture recognizer for Tap gesture, Long Tap gesture, Line gesture and so on.
+ /// </summary>
+ private ElmSharp.GestureLayer GestureRecognizer;
+
+ /// <summary>
+ /// A flags that indicates whether the touch is handled or not.
+ /// </summary>
+ private volatile bool isTouched;
+
+ public CustomImageButtonRenderer()
+ {
+ RegisterPropertyHandler(CustomImageButton.BlendColorProperty, UpdateBlendColor);
+ }
+
+ /// <summary>
+ /// Making a button with a image and set the image's color and blending color by inputted background color.
+ /// Register touch event callback for the Tap, the Long Tap and the Line behavior. </summary>
+ /// <remarks>
+ /// When the button is touched, This class should change the image for each touch down/up situation.
+ /// Even a button touching starts at the Tap touch down, but touch up will be happen in several situations such as the Tap, the Long Tap, the Line.
+ /// </remarks>
+ /// <param name="args"> A Image element changed event's argument </param>
+ protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.Image> args)
+ {
+ base.OnElementChanged(args);
+
+ if (Control == null)
+ {
+ return;
+ }
+
+ if (GestureRecognizer == null)
+ {
+ GestureRecognizer = new ElmSharp.GestureLayer(Control);
+ GestureRecognizer.Attach(Control);
+ }
+
+ if (args.NewElement == null)
+ {
+ GestureRecognizer.ClearCallbacks();
+ return;
+ }
+ else
+ {
+ Control.Clicked += SendClicked;
+ }
+
+ CustomImageButton BtnElement = args.NewElement as CustomImageButton;
+ if (BtnElement == null)
+ {
+ return;
+ }
+
+ GestureRecognizer.SetTapCallback(ElmSharp.GestureLayer.GestureType.Tap, ElmSharp.GestureLayer.GestureState.Start, x =>
+ {
+ KeyDown();
+ });
+ GestureRecognizer.SetTapCallback(ElmSharp.GestureLayer.GestureType.Tap, ElmSharp.GestureLayer.GestureState.End, x =>
+ {
+ KeyUp();
+ });
+ GestureRecognizer.SetTapCallback(ElmSharp.GestureLayer.GestureType.LongTap, ElmSharp.GestureLayer.GestureState.End, x =>
+ {
+ KeyUp();
+ });
+ GestureRecognizer.SetTapCallback(ElmSharp.GestureLayer.GestureType.LongTap, ElmSharp.GestureLayer.GestureState.Abort, x =>
+ {
+ KeyUp();
+ });
+
+ }
+
+ /// <summary>
+ /// A Action delegate which is restore button image as default
+ /// and execute button's Command with CommandParameter. </summary>
+ private void KeyUp()
+ {
+ if (Control == null)
+ {
+ return;
+ }
+
+ if (isTouched)
+ {
+ //invoke click event when a user releases image button.
+ ((CustomImageButton)Element).SendReleased();
+ }
+
+ Control.Color = ElmSharp.Color.Default;
+ isTouched = false;
+ }
+
+ /// <summary>
+ /// A Action delegate which is restore button image as pressed situation. </summary>
+ private void KeyDown()
+ {
+ CustomImageButton BtnElement = Element as CustomImageButton;
+
+ if (BtnElement == null ||
+ Control == null)
+ {
+ return;
+ }
+
+ Control.Color = BtnElement.BlendColor.ToNative();
+ isTouched = true;
+ }
+
+ private void UpdateBlendColor(bool obj)
+ {
+ CustomImageButton BtnElement = Element as CustomImageButton;
+ Control.Color = BtnElement.BlendColor.ToNative();
+ }
+
+ /// <summary>
+ /// To send a click event to the element
+ /// </summary>
+ /// <param name="sender">sender</param>
+ /// <param name="e">event</param>
+ void SendClicked(object sender, EventArgs e)
+ {
+ ((CustomImageButton)Element).SendClicked();
+ }
+
+ }
+}
\ No newline at end of file
--- /dev/null
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace Alarm.Resx {
+ using System;
+
+
+ /// <summary>
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ /// </summary>
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ public class AppResources {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal AppResources() {
+ }
+
+ /// <summary>
+ /// Returns the cached ResourceManager instance used by this class.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ public static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Alarm.Resx.AppResources", typeof(AppResources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ /// <summary>
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ public static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Add.
+ /// </summary>
+ public static string Add {
+ get {
+ return ResourceManager.GetString("Add", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Add alarm.
+ /// </summary>
+ public static string AddAlarm {
+ get {
+ return ResourceManager.GetString("AddAlarm", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to after.
+ /// </summary>
+ public static string After {
+ get {
+ return ResourceManager.GetString("After", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Alarm.
+ /// </summary>
+ public static string Alarm {
+ get {
+ return ResourceManager.GetString("Alarm", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Alarm set.
+ /// </summary>
+ public static string AlarmSet {
+ get {
+ return ResourceManager.GetString("AlarmSet", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Dismiss.
+ /// </summary>
+ public static string Dismiss {
+ get {
+ return ResourceManager.GetString("Dismiss", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to from now.
+ /// </summary>
+ public static string FromNow {
+ get {
+ return ResourceManager.GetString("FromNow", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to one hour.
+ /// </summary>
+ public static string Hour {
+ get {
+ return ResourceManager.GetString("Hour", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to hours.
+ /// </summary>
+ public static string Hours {
+ get {
+ return ResourceManager.GetString("Hours", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to one minute.
+ /// </summary>
+ public static string Minute {
+ get {
+ return ResourceManager.GetString("Minute", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to minutes.
+ /// </summary>
+ public static string Minutes {
+ get {
+ return ResourceManager.GetString("Minutes", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to now.
+ /// </summary>
+ public static string Now {
+ get {
+ return ResourceManager.GetString("Now", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Set alarm.
+ /// </summary>
+ public static string SetAlarm {
+ get {
+ return ResourceManager.GetString("SetAlarm", resourceCulture);
+ }
+ }
+ }
+}
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <data name="Add" xml:space="preserve">
+ <value>추가</value>
+ <comment>Add button text</comment>
+ </data>
+ <data name="AddAlarm" xml:space="preserve">
+ <value>알람 추가</value>
+ <comment>Add alarm text</comment>
+ </data>
+ <data name="After" xml:space="preserve">
+ <value>후</value>
+ </data>
+ <data name="Alarm" xml:space="preserve">
+ <value>알람</value>
+ <comment>Alarm List header label text</comment>
+ </data>
+ <data name="AlarmSet" xml:space="preserve">
+ <value>알람 설정</value>
+ <comment>Alarm set</comment>
+ </data>
+ <data name="Dismiss" xml:space="preserve">
+ <value>해제</value>
+ <comment>Dismiss text</comment>
+ </data>
+ <data name="FromNow" xml:space="preserve">
+ <value>지금부터</value>
+ </data>
+ <data name="Hour" xml:space="preserve">
+ <value>1 시간</value>
+ </data>
+ <data name="Hours" xml:space="preserve">
+ <value>시간</value>
+ </data>
+ <data name="Minute" xml:space="preserve">
+ <value>1 분</value>
+ </data>
+ <data name="Minutes" xml:space="preserve">
+ <value>분</value>
+ </data>
+ <data name="Now" xml:space="preserve">
+ <value>현재시간으로</value>
+ </data>
+ <data name="SetAlarm" xml:space="preserve">
+ <value>알람 설정</value>
+ <comment>EditAlarm header text</comment>
+ </data>
+</root>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <data name="Add" xml:space="preserve">
+ <value>Add</value>
+ <comment>Add button text</comment>
+ </data>
+ <data name="AddAlarm" xml:space="preserve">
+ <value>Add alarm</value>
+ <comment>Add alarm text</comment>
+ </data>
+ <data name="After" xml:space="preserve">
+ <value>after</value>
+ </data>
+ <data name="Alarm" xml:space="preserve">
+ <value>Alarm</value>
+ <comment>Alarm List header label text</comment>
+ </data>
+ <data name="AlarmSet" xml:space="preserve">
+ <value>Alarm set</value>
+ <comment>Alarm set</comment>
+ </data>
+ <data name="Dismiss" xml:space="preserve">
+ <value>Dismiss</value>
+ <comment>Dismiss text</comment>
+ </data>
+ <data name="FromNow" xml:space="preserve">
+ <value>from now</value>
+ </data>
+ <data name="Hour" xml:space="preserve">
+ <value>one hour</value>
+ </data>
+ <data name="Hours" xml:space="preserve">
+ <value>hours</value>
+ </data>
+ <data name="Minute" xml:space="preserve">
+ <value>one minute</value>
+ </data>
+ <data name="Minutes" xml:space="preserve">
+ <value>minutes</value>
+ </data>
+ <data name="Now" xml:space="preserve">
+ <value>now</value>
+ </data>
+ <data name="SetAlarm" xml:space="preserve">
+ <value>Set alarm</value>
+ <comment>EditAlarm header text</comment>
+ </data>
+</root>
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright (c) 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 Alarm.Implements;
+using Alarm.Models;
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+
+namespace Alarm.ViewModels
+{
+ public enum SoundState
+ {
+ Stop = 0,
+ Start = 1,
+ Pause = 2,
+ }
+
+ public class AlertPageModel : BasePageModel
+ {
+ private AlarmRecord _alarmRecord;
+
+ private SoundState _alertSoundState;
+
+ /// <summary>
+ /// Alarm record to display in Alert Page
+ /// </summary>
+ public AlarmRecord Record
+ {
+ get
+ {
+ return _alarmRecord;
+ }
+
+ set
+ {
+ SetProperty(ref _alarmRecord, value, "Record");
+ }
+ }
+
+ public SoundState AlertSoundState
+ {
+ get
+ {
+ return _alertSoundState;
+ }
+
+ set
+ {
+ SetProperty(ref _alertSoundState, value, "AlertState");
+ }
+
+ }
+ /// <summary>
+ /// AlertPageModel constructor with AlarmRecord
+ /// </summary>
+ /// <param name="record">Record to be shown</param>
+ public AlertPageModel(AlarmRecord record = null)
+ {
+ Record = record;
+ }
+
+ public void StartAlert()
+ {
+ AlarmNativeHandler.PlaySound();
+ AlarmNativeHandler.StartVibration();
+ _alertSoundState = SoundState.Start;
+ }
+
+ public void PauseAlert()
+ {
+ AlarmNativeHandler.PauseSound();
+ AlarmNativeHandler.StopVibration();
+ _alertSoundState = SoundState.Pause;
+ }
+
+ public void ResumeAlert()
+ {
+ AlarmNativeHandler.ResumeSound();
+ _alertSoundState = SoundState.Start;
+ }
+
+ /// <summary>
+ /// Delete current AlarmRecord
+ /// </summary>
+ public void Dismiss()
+ {
+ AlarmNativeHandler.StopSound();
+ AlarmNativeHandler.StopVibration();
+ _alertSoundState = SoundState.Stop;
+
+ if (_alarmRecord != null)
+ {
+ AlarmModel.DeleteAlarm(_alarmRecord);
+ }
+ }
+
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 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.Collections.Generic;
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+
+namespace Alarm.ViewModels
+{
+ public class BasePageModel : INotifyPropertyChanged
+ {
+ protected bool SetProperty<T>(ref T backingStore, T value,
+ [CallerMemberName]string propertyName = "",
+ Action onChanged = null)
+ {
+ if (EqualityComparer<T>.Default.Equals(backingStore, value))
+ {
+ return false;
+ }
+
+ backingStore = value;
+ onChanged?.Invoke();
+ OnPropertyChanged(propertyName);
+ return true;
+ }
+
+ #region INotifyPropertyChanged
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ /// <summary>
+ /// Called to notify that a change of property happened
+ /// </summary>
+ /// <param name="propertyName">The name of the property that changed</param>
+ protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
+ {
+ var changed = PropertyChanged;
+ if (changed == null)
+ {
+ return;
+ }
+
+ changed.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+ #endregion
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 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 Alarm.Models;
+using System.Collections.ObjectModel;
+using System.Windows.Input;
+using Xamarin.Forms;
+
+namespace Alarm.ViewModels
+{
+ public class MainPageModel : BasePageModel
+ {
+ /// <summary>
+ /// RecordList to display in MainPage
+ /// </summary>
+ public ObservableCollection<AlarmRecord> RecordList
+ {
+ get
+ {
+ return AlarmModel.ObservableAlarmList;
+ }
+ }
+
+ /// <summary>
+ /// MainPageModel constructor
+ /// </summary>
+ public MainPageModel()
+ {
+ }
+ }
+}
--- /dev/null
+<?xml version="1.0" encoding="utf-8" ?>
+<views:CirclePageEx
+ x:Class="Alarm.Views.AlarmAlertPage"
+ xmlns="http://xamarin.com/schemas/2014/forms"
+ xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
+ xmlns:converters="clr-namespace:Alarm.Converters"
+ xmlns:resx="clr-namespace:Alarm.Resx"
+ xmlns:views="clr-namespace:Alarm.Views"
+ xmlns:w="clr-namespace:Tizen.Wearable.CircularUI.Forms;assembly=XSF.CircularUI.Forms">
+ <ContentPage.Resources>
+ <ResourceDictionary>
+ <converters:ScheduledDateTimeToTextConverter x:Key="TimeConverter" />
+ </ResourceDictionary>
+ </ContentPage.Resources>
+ <w:CirclePage.Content>
+ <AbsoluteLayout BackgroundColor="Black">
+ <Image
+ x:Name="AlarmRingImg"
+ AbsoluteLayout.LayoutBounds="133, 66, 94, 94"
+ AbsoluteLayout.LayoutFlags="None"
+ Source="alarm_ringing_icon.png" />
+ <Label
+ x:Name="TimeLabel"
+ AbsoluteLayout.LayoutBounds="0, 170, 360, 45"
+ AbsoluteLayout.LayoutFlags="None"
+ FontSize="12"
+ HorizontalTextAlignment="Center"
+ Text="{Binding Record.ScheduledDateTime, Converter={StaticResource TimeConverter}}"
+ TextColor="White" />
+ </AbsoluteLayout>
+ </w:CirclePage.Content>
+ <w:CirclePage.ActionButton>
+ <w:ActionButtonItem
+ x:Name="DismissButton"
+ Clicked="OnDismissButtonClicked"
+ Text="{x:Static resx:AppResources.Dismiss}" />
+ </w:CirclePage.ActionButton>
+</views:CirclePageEx>
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright (c) 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 Alarm.Implements;
+using Alarm.Models;
+using Alarm.Resx;
+using Alarm.ViewModels;
+using System;
+using System.Globalization;
+using Xamarin.Forms;
+using Xamarin.Forms.Xaml;
+
+namespace Alarm.Views
+{
+ /// <summary>
+ /// AlarmAlertPage class
+ /// It shows the Alert View of alarm
+ /// User can dismiss alarm alert and remove record using dismiss button in the bottom of view.
+ /// </summary>
+ [XamlCompilation(XamlCompilationOptions.Compile)]
+ public partial class AlarmAlertPage : CirclePageEx
+ {
+ AlertPageModel _viewModel;
+ /// <summary>
+ /// AlarmAlertPage constructor.
+ /// </summary>
+ /// <param name="viewModel">AlertPageModel</param>
+ public AlarmAlertPage(AlertPageModel viewModel)
+ {
+ Console.WriteLine("AlarmAlertPage");
+
+ InitializeComponent();
+ BindingContext = _viewModel = viewModel;
+
+ _viewModel.StartAlert();
+
+ // Subscribe notification of locale changes to update text based on locale
+ MessagingCenter.Subscribe<LocaleHandler, CultureInfo>(this, MessageKeys.LanguageChanged, (obj, culture) =>
+ {
+ Console.WriteLine($"AlarmAlertPage received LanguageChanged");
+ // Update text that has been translated into the current language.
+ TimeLabel.Text = _viewModel.Record.ScheduledDateTime.ToString("hh:mm tt");
+ DismissButton.Text = AppResources.Dismiss;
+ });
+ }
+
+ /// <summary>
+ /// Request to dissmiss alarm alert and remove alarm record.
+ /// </summary>
+ /// <param name="sender">object</param>
+ /// <param name="args">EventArgs</param>
+ void OnDismissButtonClicked(object sender, EventArgs args)
+ {
+ Console.WriteLine("OnDismissButtonClicked()");
+
+ _viewModel.Dismiss();
+ PopAsyncAlertPage();
+ }
+
+ /// <summary>
+ /// Invoked when backbutton is pressed
+ /// Alarm record is dismissed.
+ /// </summary>
+ /// <returns>bool value</returns>
+ protected override bool OnBackButtonPressed()
+ {
+ Console.WriteLine("[AlarmAlertPage] OnBackButtonPressed PopAsync");
+ _viewModel.Dismiss();
+ PopAsyncAlertPage();
+
+ return true;
+ }
+
+ async void PopAsyncAlertPage()
+ {
+ await Navigation.PopAsync();
+ }
+
+ }
+}
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8" ?>
+<views:CirclePageEx
+ x:Class="Alarm.Views.AlarmEditPage"
+ xmlns="http://xamarin.com/schemas/2014/forms"
+ xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
+ xmlns:views="clr-namespace:Alarm.Views"
+ xmlns:w="clr-namespace:Tizen.Wearable.CircularUI.Forms;assembly=XSF.CircularUI.Forms"
+ RotaryFocusObject="{x:Reference TimeSelector}">
+ <w:CirclePage.Content>
+ <StackLayout
+ BackgroundColor="Black"
+ HorizontalOptions="FillAndExpand"
+ Orientation="Vertical"
+ VerticalOptions="FillAndExpand">
+ <w:CircleDateTimeSelector
+ x:Name="TimeSelector"
+ DateTime="{Binding SelectedDateTime, Mode=TwoWay}"
+ IsVisibleOfAmPm="True"
+ ValueType="Time" />
+ </StackLayout>
+ </w:CirclePage.Content>
+ <w:CirclePage.ActionButton>
+ <w:ActionButtonItem
+ x:Name="SaveButton"
+ Clicked="OnSaveButtonClicked"
+ Icon="ic_popup_btn_check.png" />
+ </w:CirclePage.ActionButton>
+</views:CirclePageEx>
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright (c) 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 Alarm.Models;
+using System;
+using Tizen.Wearable.CircularUI.Forms;
+using Xamarin.Forms;
+using Xamarin.Forms.Xaml;
+
+namespace Alarm.Views
+{
+ /// <summary>
+ /// AlarmEditPage class
+ /// It shows the Edit View of alarm
+ /// User can set alarm time using CircleDateTimeSelector. and then save alarm record.
+ /// </summary>
+ [XamlCompilation(XamlCompilationOptions.Compile)]
+ public partial class AlarmEditPage : CirclePageEx
+ {
+ public static readonly BindableProperty SelectedDateTimeProperty = BindableProperty.Create("SelectedDateTime", typeof(DateTime), typeof(AlarmEditPage), DateTime.Now);
+
+ private bool _alarmSaving;
+
+ /// <summary>
+ /// Selected date time for saved record or current time.
+ /// </summary>
+ public DateTime SelectedDateTime
+ {
+ get
+ {
+ return (DateTime)GetValue(SelectedDateTimeProperty);
+ }
+
+ set
+ {
+ SetValue(SelectedDateTimeProperty, value);
+ }
+ }
+
+ /// <summary>
+ /// AlarmEditPage constructor.
+ /// </summary>
+ /// <param name="alarmRecordData">AlarmRecord</param>
+ public AlarmEditPage(AlarmRecord alarmRecordData)
+ {
+ InitializeComponent();
+ TimeSelector.BindingContext = this;
+ _alarmSaving = false;
+ SelectedDateTime = alarmRecordData.ScheduledDateTime;
+ }
+
+ /// <summary>
+ /// Update SelectedDateTime with alarmRecordData time.
+ /// </summary>
+ /// <param name="alarmRecordData">Alarm record data to set SelectedDateTime</param>
+ internal void Update(AlarmRecord alarmRecordData)
+ {
+ Console.WriteLine("Update" + alarmRecordData.ToString());
+ SelectedDateTime = alarmRecordData.ScheduledDateTime;
+ _alarmSaving = false;
+ }
+
+ /// <summary>
+ /// Request to save alarm event.
+ /// </summary>
+ /// <param name="sender">object</param>
+ /// <param name="args">EventArgs</param>
+ async void OnSaveButtonClicked(object sender, EventArgs args)
+ {
+ Console.WriteLine("OnSaveButtonClicked");
+ if (_alarmSaving)
+ {
+ return;
+ }
+
+ _alarmSaving = true;
+ //subtract second from selectedTime.
+ SelectedDateTime = SelectedDateTime.AddSeconds(-SelectedDateTime.Second);
+ Console.WriteLine($"SelectedDateTime :{SelectedDateTime}");
+ AlarmModel.BindableAlarmRecord.ScheduledDateTime = SelectedDateTime;
+
+ AlarmRecord duplicate = new AlarmRecord();
+ bool existSameAlarm = AlarmModel.CheckAlarmExist(ref duplicate);
+ if (existSameAlarm)
+ {
+ // Use alarm created date for unique identifier for an alarm record
+ string alarmUID = AlarmModel.BindableAlarmRecord.GetUniqueIdentifier();
+ if (!AlarmModel.BindableAlarmRecord.IsSerialized)
+ {
+ // in case that AlarmEditPage is shown by clicking at add alarm
+ // when trying to create a system alarm and save it, find the same alarm
+ // expected behavior : update the previous one and do not create a new alarm
+ AlarmModel.BindableAlarmRecord.IsSerialized = true;
+ duplicate.DeepCopy(AlarmModel.BindableAlarmRecord, false);
+ // Update the existing alarm(duplicate)
+ Console.WriteLine("exist same alarm! update previous alarm, not create new alarm:" + AlarmModel.BindableAlarmRecord.ToString());
+ AlarmModel.UpdateAlarm(duplicate);
+ }
+ else if (alarmUID.Equals(duplicate.GetUniqueIdentifier()))
+ {
+ // in case that AlarmEditPage is shown by selecting an item of ListView in AlarmListPage
+ // At saving time, just update itself. (It doesn't affect other alarms)
+ Console.WriteLine("exist same alarm! update current alarm:" + AlarmModel.BindableAlarmRecord.ToString());
+ AlarmModel.UpdateAlarm(AlarmModel.BindableAlarmRecord);
+ }
+ else
+ {
+ // in case that AlarmEditPage is shown by selecting an item of ListView in AlarmListPage
+ // At saving time, the same alarm is found.
+ // In case that this alarm is not new, the existing alarm(duplicate) will be deleted and it will be updated.
+ // 1. delete duplicate alarm
+ Console.WriteLine("exist same alarm! delete duplicate alarm and update current alarm:" + AlarmModel.BindableAlarmRecord.ToString());
+ AlarmModel.DeleteAlarm(duplicate);
+ // 2. update bindableAlarmRecord
+ AlarmModel.UpdateAlarm(AlarmModel.BindableAlarmRecord);
+ }
+ }
+ else
+ {
+ if (!AlarmModel.BindableAlarmRecord.IsSerialized)
+ {
+ // In case that AlarmEditPage is shown by clicking FloatingButton
+ // There's no same alarm. So, just create a new alarm and add to list and dictionary.
+ AlarmModel.BindableAlarmRecord.IsSerialized = true;
+ Console.WriteLine("new Alarm create:" + AlarmModel.BindableAlarmRecord.ToString());
+ AlarmModel.CreateAlarm();
+ }
+ else
+ {
+ // in case that AlarmEditPage is shown by selecting an item of ListView in AlarmListPage
+ // There's no same alarm. So, just update itself.
+ Console.WriteLine("update current alarm:" + AlarmModel.BindableAlarmRecord.ToString());
+ AlarmModel.UpdateAlarm(AlarmModel.BindableAlarmRecord);
+ }
+ }
+
+ //Create SavePopupPage, and then close current EditPage.
+ Navigation.InsertPageBefore(new SavePopupPage(AlarmModel.BindableAlarmRecord), this);
+ await Navigation.PopAsync();
+ _alarmSaving = false;
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright (c) 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 Alarm.Converters;
+using Alarm.Models;
+using System;
+using Tizen.Wearable.CircularUI;
+using Tizen.Wearable.CircularUI.Forms;
+using Xamarin.Forms;
+
+
+namespace Alarm.Views
+{
+ /// <summary>
+ /// This class defines list cell for Alarm.
+ /// This inherits from ViewCell which provides custom UI for a ListView
+ /// It is commonly used for any cell for this ListView
+ /// </summary>
+ public class AlarmListCell : ViewCell
+ {
+
+ public StackLayout alarmItemLayout;
+
+ /// <summary>
+ /// time label for the alarm list
+ /// </summary>
+ public Label timeLabel;
+
+ /// <summary>
+ /// Active/Inactive switch object
+ /// </summary>
+ public Switch switchObj;
+
+ /// <summary>
+ /// Draws alarm list
+ /// </summary>
+ /// <returns>Returns RelativeLayout</returns>
+ protected virtual StackLayout Draw()
+ {
+
+ /// Need to get bindable context to assign list value
+ AlarmRecord alarmData = (AlarmRecord)BindingContext;
+
+ /// If binding context is null, can't proceed further action
+ if (alarmData == null)
+ {
+ return null;
+ }
+
+
+ /// Alarm item layout should be set if null
+ if (alarmItemLayout == null)
+ {
+ // The layout of item cell
+ alarmItemLayout = new StackLayout
+ {
+ HeightRequest = 120,
+ Orientation = StackOrientation.Horizontal,
+ HorizontalOptions = LayoutOptions.FillAndExpand,
+ VerticalOptions = LayoutOptions.FillAndExpand
+ };
+
+ // Time Label
+ timeLabel = new Label()
+ {
+ WidthRequest = 280,
+ FontSize = 12,
+ TextColor = Color.White,
+ HorizontalOptions = LayoutOptions.Center,
+ VerticalOptions = LayoutOptions.Center,
+ HorizontalTextAlignment = TextAlignment.Center,
+ VerticalTextAlignment = TextAlignment.Center,
+ };
+
+ timeLabel.SetBinding(Label.TextProperty, new Binding("ScheduledDateTime", BindingMode.Default, new ScheduledDateTimeToTextConverter()));
+ alarmItemLayout.Children.Add(timeLabel);
+
+ switchObj = new Check
+ {
+ HeightRequest = 80,
+ WidthRequest = 80,
+ DisplayStyle = CheckDisplayStyle.Default,
+ IsToggled = alarmData.AlarmState == AlarmStates.Inactive ? false : true,
+ HorizontalOptions = LayoutOptions.End,
+ VerticalOptions = LayoutOptions.Center,
+ };
+
+ alarmItemLayout.Children.Add(switchObj);
+
+ /// Adds an event
+ switchObj.Toggled += (s, e) =>
+ {
+ AlarmRecord am = (AlarmRecord)BindingContext;
+ /// Modify state and re-draw it. Redraw must be called to redraw
+ if (e.Value)
+ {
+ AlarmModel.ReactivatelAlarm(am);
+ }
+ else
+ {
+ AlarmModel.DeactivatelAlarm(am);
+ }
+ };
+ }
+
+ return alarmItemLayout;
+ }
+
+ /// <summary>
+ /// When binding context is changed, need to redraw
+ /// </summary>
+ protected override void OnBindingContextChanged()
+ {
+ base.OnBindingContextChanged();
+ if (BindingContext == null)
+ {
+ return;
+ }
+ else
+ {
+ View = Draw();
+ }
+ }
+ }
+}
--- /dev/null
+<?xml version="1.0" encoding="utf-8" ?>
+<w:CirclePage
+ x:Class="Alarm.Views.CirclePageEx"
+ xmlns="http://xamarin.com/schemas/2014/forms"
+ xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
+ xmlns:w="clr-namespace:Tizen.Wearable.CircularUI.Forms;assembly=XSF.CircularUI.Forms" />
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright (c) 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 Tizen.Wearable.CircularUI.Forms;
+using Xamarin.Forms;
+using Xamarin.Forms.Xaml;
+
+namespace Alarm.Views
+{
+ /// <summary>
+ /// Base Page class
+ /// </summary>
+ [XamlCompilation(XamlCompilationOptions.Compile)]
+ public partial class CirclePageEx : CirclePage
+ {
+ public CirclePageEx()
+ {
+ // Hide navigation bar
+ NavigationPage.SetHasNavigationBar(this, false);
+
+ InitializeComponent();
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8" ?>
+<views:CirclePageEx
+ x:Class="Alarm.Views.MainPage"
+ xmlns="http://xamarin.com/schemas/2014/forms"
+ xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
+ xmlns:control="clr-namespace:Alarm.Controls"
+ xmlns:converters="clr-namespace:Alarm.Converters"
+ xmlns:resx="clr-namespace:Alarm.Resx"
+ xmlns:views="clr-namespace:Alarm.Views"
+ xmlns:w="clr-namespace:Tizen.Wearable.CircularUI.Forms;assembly=XSF.CircularUI.Forms"
+ Title="MainPage"
+ RotaryFocusObject="{x:Reference AlarmList}">
+ <ContentPage.Resources>
+ <ResourceDictionary>
+ <converters:ItemCountToVisibilityConverter x:Key="VisibilityConverter" />
+ </ResourceDictionary>
+ </ContentPage.Resources>
+ <ContentPage.Content>
+ <StackLayout Orientation="Vertical">
+ <StackLayout
+ x:Name="NoAlarmLayout"
+ HorizontalOptions="Center"
+ IsVisible="{Binding RecordList.Count, Converter={StaticResource VisibilityConverter}, ConverterParameter={x:Static converters:MainPageType.Empty}}"
+ VerticalOptions="FillAndExpand">
+ <Label
+ x:Name="EmptyHeaderLabel"
+ Margin="0,50,0,30"
+ FontAttributes="Bold"
+ FontSize="Medium"
+ HorizontalTextAlignment="Center"
+ Text="{x:Static resx:AppResources.Alarm}"
+ TextColor="#368AFF" />
+ <control:CustomImageButton
+ x:Name="noAlarmImg"
+ Released="OnButtonReleased"
+ Source="alarm_no_alarm_icon.png" />
+ <Label
+ x:Name="EmptyBottomLabel"
+ Margin="0,30,0,50"
+ FontSize="Small"
+ HorizontalTextAlignment="Center"
+ Text="{x:Static resx:AppResources.AddAlarm}" />
+ </StackLayout>
+ <StackLayout
+ x:Name="AlarmListLayout"
+ HorizontalOptions="FillAndExpand"
+ IsVisible="{Binding RecordList.Count, Converter={StaticResource VisibilityConverter}, ConverterParameter={x:Static converters:MainPageType.ListView}}"
+ VerticalOptions="Center">
+ <w:CircleListView
+ x:Name="AlarmList"
+ HasUnevenRows="True"
+ HorizontalOptions="FillAndExpand"
+ ItemTapped="OnItemTapped"
+ ItemsSource="{Binding RecordList}"
+ VerticalOptions="Center">
+ <ListView.Header>
+ <StackLayout HeightRequest="115">
+ <Label
+ x:Name="HeaderLabel"
+ FontAttributes="Bold"
+ FontSize="16"
+ HorizontalOptions="CenterAndExpand"
+ Text="{x:Static resx:AppResources.Alarm}"
+ TextColor="#368AFF"
+ VerticalOptions="FillAndExpand" />
+ </StackLayout>
+ </ListView.Header>
+ <ListView.Footer>
+ <StackLayout HeightRequest="115">
+ <BoxView />
+ </StackLayout>
+ </ListView.Footer>
+ <ListView.ItemTemplate>
+ <DataTemplate>
+ <views:AlarmListCell />
+ </DataTemplate>
+ </ListView.ItemTemplate>
+ </w:CircleListView>
+ </StackLayout>
+ </StackLayout>
+ </ContentPage.Content>
+ <views:CirclePageEx.ActionButton>
+ <w:ActionButtonItem
+ x:Name="AddButton"
+ Clicked="OnAddButtonClicked"
+ IsVisible="{Binding RecordList.Count, Converter={StaticResource VisibilityConverter}, ConverterParameter={x:Static converters:MainPageType.Button}}"
+ Text="{x:Static resx:AppResources.Add}" />
+ </views:CirclePageEx.ActionButton>
+</views:CirclePageEx>
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright (c) 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 Alarm.Converters;
+using Alarm.Implements;
+using Alarm.Models;
+using Alarm.Resx;
+using Alarm.ViewModels;
+using System;
+using System.Globalization;
+using System.Windows.Input;
+using Tizen.Wearable.CircularUI.Forms;
+using Xamarin.Forms;
+using Xamarin.Forms.Xaml;
+
+namespace Alarm.Views
+{
+ /// <summary>
+ /// Main Page class
+ /// </summary>
+ [XamlCompilation(XamlCompilationOptions.Compile)]
+ public partial class MainPage : CirclePageEx
+ {
+ /// <summary>
+ /// Constructor of MainPage class
+ /// </summary>
+ /// <param name="viewModel">MainPageModel</param>
+ public MainPage(MainPageModel viewModel)
+ {
+ BindingContext = viewModel;
+ InitializeComponent();
+
+ // Subscribe notification of locale changes to update text based on locale
+ MessagingCenter.Subscribe<LocaleHandler, CultureInfo>(this, MessageKeys.LanguageChanged, (obj, culture) =>
+ {
+ Console.WriteLine($"MainPage received LanguageChanged");
+ // Update text that has been translated into the current language.
+ HeaderLabel.Text = EmptyHeaderLabel.Text = AppResources.Alarm;
+ EmptyBottomLabel.Text = AppResources.AddAlarm;
+ AddButton.Text = AppResources.Add;
+
+ //This is workaround solution for refresh Listview following to language change.
+ AlarmList.ItemsSource = null;
+ AlarmList.ItemsSource = viewModel.RecordList;
+ });
+ }
+
+ /// <summary>
+ /// ListItem tapped event handler
+ /// </summary>
+ /// <param name="sender">object</param>
+ /// <param name="e">SelectedItemChangedEventArgs</param>
+ void OnItemTapped(object sender, SelectedItemChangedEventArgs e)
+ {
+ /// skips for no selected item case
+ if (e.SelectedItem == null)
+ {
+ return;
+ }
+
+ /// check selected item
+ AlarmRecord alarm = e.SelectedItem as AlarmRecord;
+ /// de-select first item
+ ((ListView)sender).SelectedItem = null; // de-select the row
+ EditAlarm(alarm);
+ }
+
+ /// <summary>
+ /// Image Button event handler
+ /// </summary>
+ /// <param name="sender">object</param>
+ /// <param name="args">EventArgs</param>
+ void OnButtonReleased(object sender, EventArgs args)
+ {
+ CreateNewAlarm();
+ }
+
+ /// <summary>
+ /// Action button event handler
+ /// </summary>
+ /// <param name="sender">object</param>
+ /// <param name="args">EventArgs</param>
+ void OnAddButtonClicked(object sender, EventArgs args)
+ {
+ CreateNewAlarm();
+ }
+
+ /// <summary>
+ /// Launch AlarmEdit page
+ /// </summary>
+ async void CreateNewAlarm()
+ {
+ /// Creates default alarm record
+ AlarmRecord defaultAlarmRecord = new AlarmRecord();
+ defaultAlarmRecord.SetDefault();
+ await Navigation.PushAsync(AlarmPageController.GetInstance(AlarmPages.EditPage, defaultAlarmRecord));
+ }
+
+ /// <summary>
+ /// Launch Edit page
+ /// </summary>
+ /// <param name="record">AlarmRecord</param>
+ async void EditAlarm(AlarmRecord record)
+ {
+ await Navigation.PushAsync(AlarmPageController.GetInstance(AlarmPages.EditPage, record));
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8" ?>
+<ContentPage
+ x:Class="Alarm.Views.SavePopupPage"
+ xmlns="http://xamarin.com/schemas/2014/forms"
+ xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
+ <ContentPage.Content>
+ <StackLayout HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand">
+ <Label x:Name="AlarmSetLabel" Text="Set Alarm now!" />
+ </StackLayout>
+ </ContentPage.Content>
+</ContentPage>
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright (c) 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 Alarm.Models;
+using Alarm.Resx;
+using System;
+using System.Threading.Tasks;
+using Xamarin.Forms;
+using Xamarin.Forms.Xaml;
+
+namespace Alarm.Views
+{
+ /// <summary>
+ /// SavePopupPage class
+ /// It shows the remaining time from the current time to the alarm time.
+ /// </summary>
+ [XamlCompilation(XamlCompilationOptions.Compile)]
+ public partial class SavePopupPage : ContentPage
+ {
+ ///Korean CultureInfo name
+ const string KoreanCultureInfoName = "ko-KR";
+
+ private AlarmRecord _record;
+
+ /// <summary>
+ /// SavePopupPage constructor
+ /// </summary>
+ /// <param name="record">AlarmRecord</param>
+ public SavePopupPage(AlarmRecord record)
+ {
+ InitializeComponent();
+
+ NavigationPage.SetHasNavigationBar(this, false);
+ _record = record;
+ SetPopupLabel();
+ ClosePopup();
+ }
+
+ /// <summary>
+ /// Calculate the remaining time and then set AlarmSetLabel.
+ /// </summary>
+ private void SetPopupLabel()
+ {
+ DateTime savedTime = _record.ScheduledDateTime;
+ DateTime now = System.DateTime.Now;
+ int hour = savedTime.Hour - now.Hour;
+ int min = savedTime.Minute - now.Minute;
+ string text = "";
+
+ if (hour >= 0)
+ {
+ if (min < 0)
+ {
+ min = 60 + min;
+ if (hour == 0)
+ {
+ hour = 24 - 1;
+ }
+ else
+ {
+ hour = hour - 1;
+ }
+ }
+ }
+ else //hour < 0
+ {
+ hour = 24 + hour;
+ if (min < 0)
+ {
+ min = 60 + min;
+ hour = hour - 1;
+ }
+ }
+
+ //Console.WriteLine($"App.Culture.Name:{App.Culture.Name}");
+ if (hour == 0)
+ {
+ //If hour is 0, display only remain minutes.
+ if (min == 0)
+ {
+ text = GetSetNowText();
+ }
+ else
+ {
+ text = GetRemainMinutesText(min);
+ }
+ }
+ else
+ {
+ text = GetRemainHourMinutesText(hour, min);
+ }
+
+ AlarmSetLabel.Text = text;
+ }
+
+
+ /// <summary>
+ /// Get localized string of alarm set now string .
+ /// </summary>
+ /// <returns>localized string </returns>
+ private string GetSetNowText()
+ {
+ string text;
+ if (App.Culture.Name == KoreanCultureInfoName)
+ {
+ text = AppResources.Now + " " + AppResources.AlarmSet;
+ }
+ else
+ {
+ text = AppResources.AlarmSet + " " + AppResources.Now;
+ }
+
+ return text;
+ }
+
+ /// <summary>
+ /// Get localized string of remaining minutes.
+ /// </summary>
+ /// <param name="minutes">remain minutes</param>
+ /// <returns>Localized string of remaining minutes</returns>
+ private string GetRemainMinutesText(int minutes)
+ {
+ string text;
+ if (App.Culture.Name == KoreanCultureInfoName)
+ {
+ text = AppResources.FromNow + " " + minutes + " " + AppResources.Minutes + " " + AppResources.After + " " + AppResources.AlarmSet;
+ }
+ else
+ {
+ if (minutes == 1)
+ {
+ text = AppResources.AlarmSet + " " + AppResources.Minute + " " + AppResources.After + " " + AppResources.FromNow;
+ }
+ else
+ {
+ text = AppResources.AlarmSet + " " + minutes + " " + AppResources.Minutes + " " + AppResources.After + " " + AppResources.FromNow;
+ }
+ }
+
+ return text;
+ }
+
+ /// <summary>
+ /// Get localized string of remaining hours and minutes.
+ /// </summary>
+ /// <param name="hours">remain hours</param>
+ /// <param name="minutes">remain minutes</param>
+ /// <returns>Localized string of remaining hours and minutes</returns>
+ private string GetRemainHourMinutesText(int hours, int minutes)
+ {
+ string text;
+ if (App.Culture.Name == KoreanCultureInfoName)
+ {
+ if (minutes == 0)
+ {
+ text = AppResources.FromNow + " " + hours + " " + AppResources.Hours + " " + AppResources.After + " " + AppResources.AlarmSet;
+ }
+ else
+ {
+ text = AppResources.FromNow + " " + hours + " " + AppResources.Hours + " " + minutes + " " + AppResources.Minutes + " " + AppResources.After + " " + AppResources.AlarmSet;
+ }
+ }
+ else
+ {
+
+ if (hours == 1)
+ {
+ text = AppResources.AlarmSet + " " + AppResources.Hour;
+ }
+ else
+ {
+ text = AppResources.AlarmSet + " " + hours + " " + AppResources.Hours;
+ }
+
+ if (minutes == 0)
+ {
+ text = text + " " + AppResources.After + " " + AppResources.FromNow;
+ }
+ else if (minutes == 1)
+ {
+ text = text + " " + AppResources.Minute + " " + AppResources.After + " " + AppResources.FromNow;
+ }
+ else
+ {
+ text = text + " " + minutes + " " + AppResources.Minutes + " " + AppResources.After + " " + AppResources.FromNow;
+ }
+ }
+
+ return text;
+ }
+
+ /// <summary>
+ /// After 2 seconds. this page closed and then go to the Mainpage
+ /// </summary>
+ async private void ClosePopup()
+ {
+ await Task.Delay(2000);
+ await Navigation.PopToRootAsync();
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<manifest package="org.tizen.example.Alarm" version="1.0.0" api-version="4" xmlns="http://tizen.org/ns/packages">
+ <profile name="wearable" />
+ <ui-application appid="org.tizen.example.Alarm" exec="Alarm.dll" multiple="false" nodisplay="false" taskmanage="true" splash-screen-display="true" type="dotnet" launch_mode="single">
+ <label>.NET Alarm</label>
+ <icon>Alarm.png</icon>
+ <metadata key="http://tizen.org/metadata/prefer_dotnet_aot" value="true" />
+ <label xml:lang="ko-kr">닷넷 알람</label>
+ <label xml:lang="en-us">.NET Alarm</label>
+ <splash-screens />
+ </ui-application>
+ <shortcut-list />
+ <privileges>
+ <privilege>http://tizen.org/privilege/alarm.set</privilege>
+ <privilege>http://tizen.org/privilege/alarm.get</privilege>
+ <privilege>http://tizen.org/privilege/haptic</privilege>
+ <privilege>http://tizen.org/privilege/appmanager.launch</privilege>
+ </privileges>
+ <provides-appdefined-privileges />
+</manifest>
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "XStopWatch", "XStopWatch", "{CE5F5031-BE26-42D9-8B86-87770CB01226}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Alarm", "Alarm", "{232D191B-486D-4EC3-BCB2-F776124AF705}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Alarm", "Test\Alarm\Alarm\Alarm.csproj", "{4653BFED-31B5-412F-8076-A609A4E5D851}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
{38700235-FC85-41F6-8801-5BBFDE5BCD65}.Debug|Any CPU.Build.0 = Debug|Any CPU
{38700235-FC85-41F6-8801-5BBFDE5BCD65}.Release|Any CPU.ActiveCfg = Release|Any CPU
{38700235-FC85-41F6-8801-5BBFDE5BCD65}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4653BFED-31B5-412F-8076-A609A4E5D851}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4653BFED-31B5-412F-8076-A609A4E5D851}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4653BFED-31B5-412F-8076-A609A4E5D851}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4653BFED-31B5-412F-8076-A609A4E5D851}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
{1D58F961-6843-474B-91AB-07140137C707} = {32A6D9FF-AFD0-4CAF-A6E9-A3BBE8C4AEBA}
{38700235-FC85-41F6-8801-5BBFDE5BCD65} = {CE5F5031-BE26-42D9-8B86-87770CB01226}
{CE5F5031-BE26-42D9-8B86-87770CB01226} = {087187F9-A361-4269-88EB-47A44185FA7F}
+ {232D191B-486D-4EC3-BCB2-F776124AF705} = {087187F9-A361-4269-88EB-47A44185FA7F}
+ {4653BFED-31B5-412F-8076-A609A4E5D851} = {232D191B-486D-4EC3-BCB2-F776124AF705}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {81158F7F-D003-4C6D-9937-5002C54D57EA}