From 6828b3238f1529aeb05105f0e5071e92a4475d67 Mon Sep 17 00:00:00 2001 From: =?utf8?q?=EB=B6=80=EC=A0=95=EA=B7=A0/Common=20Platform=20Lab=28SR=29?= =?utf8?q?/Staff=20Engineer/=EC=82=BC=EC=84=B1=EC=A0=84=EC=9E=90?= Date: Fri, 14 Feb 2020 11:24:54 +0900 Subject: [PATCH] Add HeartRateMonitor app (#147) --- Test/HeartRateMonitor/App.cs | 44 ++ Test/HeartRateMonitor/Controls/DialogOK.cs | 115 ++++ Test/HeartRateMonitor/HeartRateMonitor.cs | 55 ++ Test/HeartRateMonitor/HeartRateMonitor.csproj | 31 ++ .../Model/HeartRateMonitorModel.cs | 193 +++++++ Test/HeartRateMonitor/ViewModels/MainViewModel.cs | 578 +++++++++++++++++++++ Test/HeartRateMonitor/ViewModels/ViewModelBase.cs | 74 +++ Test/HeartRateMonitor/Views/MeasurementPage.xaml | 125 +++++ .../HeartRateMonitor/Views/MeasurementPage.xaml.cs | 103 ++++ Test/HeartRateMonitor/Views/SettingsPage.xaml | 31 ++ Test/HeartRateMonitor/Views/SettingsPage.xaml.cs | 36 ++ Test/HeartRateMonitor/res/heart.png | Bin 0 -> 2670 bytes Test/HeartRateMonitor/res/settings.png | Bin 0 -> 1264 bytes .../shared/res/HeartRateMonitor.png | Bin 0 -> 10097 bytes Test/HeartRateMonitor/tizen-manifest.xml | 15 + XSF.sln | 10 + 16 files changed, 1410 insertions(+) create mode 100644 Test/HeartRateMonitor/App.cs create mode 100644 Test/HeartRateMonitor/Controls/DialogOK.cs create mode 100644 Test/HeartRateMonitor/HeartRateMonitor.cs create mode 100644 Test/HeartRateMonitor/HeartRateMonitor.csproj create mode 100644 Test/HeartRateMonitor/Model/HeartRateMonitorModel.cs create mode 100644 Test/HeartRateMonitor/ViewModels/MainViewModel.cs create mode 100644 Test/HeartRateMonitor/ViewModels/ViewModelBase.cs create mode 100644 Test/HeartRateMonitor/Views/MeasurementPage.xaml create mode 100644 Test/HeartRateMonitor/Views/MeasurementPage.xaml.cs create mode 100644 Test/HeartRateMonitor/Views/SettingsPage.xaml create mode 100644 Test/HeartRateMonitor/Views/SettingsPage.xaml.cs create mode 100644 Test/HeartRateMonitor/res/heart.png create mode 100644 Test/HeartRateMonitor/res/settings.png create mode 100644 Test/HeartRateMonitor/shared/res/HeartRateMonitor.png create mode 100644 Test/HeartRateMonitor/tizen-manifest.xml mode change 100755 => 100644 XSF.sln diff --git a/Test/HeartRateMonitor/App.cs b/Test/HeartRateMonitor/App.cs new file mode 100644 index 0000000..b5e4c5b --- /dev/null +++ b/Test/HeartRateMonitor/App.cs @@ -0,0 +1,44 @@ + +//Copyright 2018 Samsung Electronics Co., Ltd +// +//Licensed under the Apache License, Version 2.0 (the "License"); +//you may not use this file except in compliance with the License. +//You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +//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 HeartRateMonitor.ViewModels; +using HeartRateMonitor.Views; + +namespace HeartRateMonitor +{ + public class App : Application + { + + /// + /// An instance of the MainViewModel class. + /// + public MainViewModel AppMainViewModel { private set; get; } + + public App() + { + AppMainViewModel = new MainViewModel(); + + MainPage = new NavigationPage(new MeasurementPage()); + } + + protected override async void OnStart() + { + base.OnStart(); + + await AppMainViewModel.Init(Properties, MainPage.Navigation); + } + } +} diff --git a/Test/HeartRateMonitor/Controls/DialogOK.cs b/Test/HeartRateMonitor/Controls/DialogOK.cs new file mode 100644 index 0000000..ac204e1 --- /dev/null +++ b/Test/HeartRateMonitor/Controls/DialogOK.cs @@ -0,0 +1,115 @@ + +//Copyright 2018 Samsung Electronics Co., Ltd +// +//Licensed under the Apache License, Version 2.0 (the "License"); +//you may not use this file except in compliance with the License. +//You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +//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.Windows.Input; +using Tizen; +using Xamarin.Forms; + +namespace HeartRateMonitor.Controls +{ + /// + /// Dialog control with OK button. + /// + public class DialogOK : BindableObject + { + #region properties + + /// + /// Execute command property definition. + /// + public static readonly BindableProperty ExecuteCommandProperty = BindableProperty.Create( + nameof(ExecuteCommand), typeof(ICommand), typeof(DialogOK), null, BindingMode.OneWayToSource); + + /// + /// Confirm command property definition. + /// + public static readonly BindableProperty ConfirmCommandProperty = BindableProperty.Create( + nameof(ConfirmCommand), typeof(ICommand), typeof(DialogOK)); + + /// + /// Dialog title bindable property definition. + /// + public static readonly BindableProperty TitleProperty = BindableProperty.Create( + nameof(Title), typeof(string), typeof(DialogOK), ""); + + /// + /// Dialog message bindable property definition. + /// + public static readonly BindableProperty MessageProperty = BindableProperty.Create( + nameof(Message), typeof(string), typeof(DialogOK), ""); + + /// + /// Command which shows the dialog. + /// + public ICommand ExecuteCommand + { + get => (ICommand)GetValue(ExecuteCommandProperty); + set => SetValue(ExecuteCommandProperty, value); + } + + /// + /// Command which is executed when user confirms the message (taps OK button). + /// + public ICommand ConfirmCommand + { + get => (ICommand)GetValue(ConfirmCommandProperty); + set => SetValue(ConfirmCommandProperty, value); + } + + /// + /// Dialog title. + /// + public string Title + { + get => (string)GetValue(TitleProperty); + set => SetValue(TitleProperty, value); + } + + /// + /// Dialog message. + /// + public string Message + { + get => (string)GetValue(MessageProperty); + set => SetValue(MessageProperty, value); + } + + #endregion + + #region methods + + /// + /// The control constructor. + /// + public DialogOK() + { + ExecuteCommand = new Command(Display); + } + + /// + /// Displays OK dialog with title from the Title property and message text from the Message property. + /// + public async void Display() + { + Log.Debug("HRM", "oh boy"); + await Application.Current.MainPage.DisplayAlert(Title, Message, "OK"); + + Log.Debug("HRM", "sure"); + ConfirmCommand?.Execute(null); + } + + #endregion + } +} diff --git a/Test/HeartRateMonitor/HeartRateMonitor.cs b/Test/HeartRateMonitor/HeartRateMonitor.cs new file mode 100644 index 0000000..2d7d7db --- /dev/null +++ b/Test/HeartRateMonitor/HeartRateMonitor.cs @@ -0,0 +1,55 @@ + +//Copyright 2018 Samsung Electronics Co., Ltd +// +//Licensed under the Apache License, Version 2.0 (the "License"); +//you may not use this file except in compliance with the License. +//You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +//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 HeartRateMonitor.Controls; +using System; +using System.Collections.Generic; +using Tizen.Wearable.CircularUI.Forms; +using Tizen.Wearable.CircularUI.Forms.Renderer; +using Xamarin.Forms; + +namespace HeartRateMonitor +{ + class Program : global::Xamarin.Forms.Platform.Tizen.ApplicationLifecycle + { + protected override void OnCreate() + { + base.OnCreate(); + + FormsApplication.LoadApplication(new App()); + } + + static void Main(string[] args) + { + var app = new Program(); + var customRenderers = new Dictionary>() + { + { typeof(CirclePage), ()=> new CirclePageRenderer() }, + { typeof(CircleStepper), () => new CircleStepperRenderer() } + }; + + var option = new InitializationOptions(app) + { + UseMessagingCenter = false, + StaticRegistarStrategy = StaticRegistrarStrategy.StaticRegistrarOnly, + CustomHandlers = customRenderers + }; + Forms.Init(option); + + Tizen.Wearable.CircularUI.Forms.Renderer.FormsCircularUI.Init(); + app.FormsApplication.Run(args); + } + } +} diff --git a/Test/HeartRateMonitor/HeartRateMonitor.csproj b/Test/HeartRateMonitor/HeartRateMonitor.csproj new file mode 100644 index 0000000..08c1bc8 --- /dev/null +++ b/Test/HeartRateMonitor/HeartRateMonitor.csproj @@ -0,0 +1,31 @@ + + + + Exe + tizen40 + + + + portable + + + None + + + + + + + + + + + + + + + + + + + diff --git a/Test/HeartRateMonitor/Model/HeartRateMonitorModel.cs b/Test/HeartRateMonitor/Model/HeartRateMonitorModel.cs new file mode 100644 index 0000000..91d4a09 --- /dev/null +++ b/Test/HeartRateMonitor/Model/HeartRateMonitorModel.cs @@ -0,0 +1,193 @@ + +//Copyright 2018 Samsung Electronics Co., Ltd +// +//Licensed under the Apache License, Version 2.0 (the "License"); +//you may not use this file except in compliance with the License. +//You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +//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 Tizen.Sensor; +using HRM = Tizen.Sensor.HeartRateMonitor; +using System.Threading.Tasks; +using Tizen.Security; +using Tizen; + +namespace HeartRateMonitor.Model +{ + /// + /// HeartRateMonitorModel class. + /// Provides methods that allow the application to use the Tizen Sensor API. + /// Implements IHeartRateMonitorModel interface to be available + /// from portable part of the application source code. + /// + public class HeartRateMonitorModel + { + #region fields + + /// + /// An instance of the HeartRateMonitor class provided by the Tizen Sensor API. + /// + private HRM heartRateMonitor; + + /// + /// Number representing value of the current heart rate. + /// + private int currentHeartRate; + + /// + /// The check privileges task. + /// + private TaskCompletionSource checkPrivilegesTask; + + #endregion + + #region properties + + /// + /// HeartRateMonitorDataChanged event. + /// Notifies UI about heart rate value update. + /// + public event EventHandler HeartRateMonitorDataChanged; + + /// + /// HeartRateSensorNotSupported event. + /// Notifies application about lack of heart rate sensor. + /// + public event EventHandler HeartRateSensorNotSupported; + + /// + /// Healthinfo privilege key. + /// + private const string HEALTHINFO_PRIVILEGE = "http://tizen.org/privilege/healthinfo"; + + #endregion + + #region methods + + /// + /// Initializes HeartRateMonitorModel class. + /// Invokes HeartRateSensorNotSupported event if heart rate sensor is not supported. + /// + public void Init() + { + try + { + heartRateMonitor = new HRM + { + Interval = 1000 + }; + + heartRateMonitor.DataUpdated += OnDataUpdated; + } + catch (Exception) + { + HeartRateSensorNotSupported?.Invoke(this, new EventArgs()); + } + } + + /// + /// Returns current heart rate value provided by the Tizen Sensor API. + /// + /// Current heart rate value provided by the Tizen Sensor API. + public int GetHeartRate() + { + return currentHeartRate; + } + + /// + /// Starts notification about changes of heart rate value. + /// + public void StartHeartRateMonitor() + { + heartRateMonitor.Start(); + } + + /// + /// Stops notification about changes of heart rate value. + /// + public void StopHeartRateMonitor() + { + heartRateMonitor.Stop(); + } + + /// + /// Handles "DataUpdated" event of the HeartRateMonitor object provided by the Tizen Sensor API. + /// Saves current heart rate value in the _currentHeartRate field. + /// Invokes "HeartRateMonitorDataChanged" event. + /// + /// Object firing the event. + /// An instance of the HeartRateMonitorDataUpdatedEventArgs class providing detailed information about the event. + private void OnDataUpdated(object sender, HeartRateMonitorDataUpdatedEventArgs e) + { + Log.Debug("HRM", $"Rate:{e.HeartRate}"); + currentHeartRate = e.HeartRate; + HeartRateMonitorDataChanged?.Invoke(this, new EventArgs()); + } + + /// + /// Handles privilege request response from the privacy privilege manager. + /// + /// Event sender. + /// Event arguments. + private void PrivilegeManagerOnResponseFetched(object sender, + RequestResponseEventArgs requestResponseEventArgs) + { + if (requestResponseEventArgs.cause == CallCause.Answer) + { + checkPrivilegesTask.SetResult(requestResponseEventArgs.result == RequestResult.AllowForever); + } + else + { + Log.Error("HeartRateMonitor", "Error occurred during requesting permission"); + checkPrivilegesTask.SetResult(false); + } + } + + /// + /// Returns true if all required privileges are granted, false otherwise. + /// + /// Task with check result. + public async Task CheckPrivileges() + { + CheckResult result = PrivacyPrivilegeManager.CheckPermission(HEALTHINFO_PRIVILEGE); + + switch (result) + { + case CheckResult.Allow: + return true; + case CheckResult.Deny: + return false; + case CheckResult.Ask: + PrivacyPrivilegeManager.ResponseContext context = null; + PrivacyPrivilegeManager.GetResponseContext(HEALTHINFO_PRIVILEGE) + .TryGetTarget(out context); + + if (context == null) + { + Log.Error("HeartRateMonitor", "Unable to get privilege response context"); + return false; + } + + checkPrivilegesTask = new TaskCompletionSource(); + + context.ResponseFetched += PrivilegeManagerOnResponseFetched; + + PrivacyPrivilegeManager.RequestPermission(HEALTHINFO_PRIVILEGE); + return await checkPrivilegesTask.Task; + default: + return false; + } + } + + #endregion + } +} \ No newline at end of file diff --git a/Test/HeartRateMonitor/ViewModels/MainViewModel.cs b/Test/HeartRateMonitor/ViewModels/MainViewModel.cs new file mode 100644 index 0000000..2f9884e --- /dev/null +++ b/Test/HeartRateMonitor/ViewModels/MainViewModel.cs @@ -0,0 +1,578 @@ + +//Copyright 2018 Samsung Electronics Co., Ltd +// +//Licensed under the Apache License, Version 2.0 (the "License"); +//you may not use this file except in compliance with the License. +//You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +//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.Linq; +using System.Windows.Input; +using Xamarin.Forms; +using System.Threading.Tasks; +using HeartRateMonitor.Model; +using Tizen; +using HeartRateMonitor.Views; + +namespace HeartRateMonitor.ViewModels +{ + /// + /// MainViewModel class. + /// Provides commands and methods responsible for application view model state. + /// + public class MainViewModel : ViewModelBase + { + #region fields + + /// + /// Flag indicating whether measurement process is in progress. + /// + private bool isMeasuring = false; + + /// + /// Flag indicating whether measurement process is finished. + /// + private bool isFinished = false; + + /// + /// Flag indicating whether measurements should be included in result. + /// + private bool isMeasurementCounted = false; + + /// + /// Number indicating whether measurement process can be stopped automatically or not. + /// If the value is equal to zero, the measurement process can be stopped automatically. + /// Otherwise not. + /// + private int measurementLock = 0; + + /// + /// Number representing the range to which the heart rate value is classified, + /// when the measurement is finished. + /// If the value is equal to 1, the heart rate value is above the defined heart rate limit. + /// If the value is equal to 0, the heart rate value is within the defined heart rate limit. + /// If the value is equal to -1, the heart rate value is within the average resting rate. + /// + private int measurementResultRange; + + /// + /// String representing value of measurement countdown. + /// + private int measurementCountdown; + + /// + /// DateTime object representing starting time point of the measurement process. + /// + private DateTime measurementStartTimestamp; + + /// + /// Flag indicating whether heart rate limit is exceeded. + /// + private bool isMeasurementResultAlert = false; + + /// + /// Number representing current heart rate value. + /// + private int currentHeartRate = 0; + + /// + /// Number representing heart rate limit value. + /// + private int heartRateLimitValue; + + /// + /// Number representing temporary buffered heart rate limit value. + /// + private int heartRateLimitBufferValue; + + /// + /// Number representing lower limit of the average resting rate. + /// + private const int AVERAGE_HEART_RATE_VALUE_LOWER_LIMIT = 61; + + /// + /// Number representing upper limit of the average resting rate. + /// + private const int AVERAGE_HEART_RATE_VALUE_UPPER_LIMIT = 76; + + /// + /// Number representing default value of the heart rate limit. + /// + private const int HEART_RATE_DEFAULT_LIMIT_VALUE = 180; + + /// + /// Number representing measurement time. + /// + private const int MEASUREMENT_TIME = 20; + + /// + /// Number representing measurement time from which heartRateMonitor values are used to determine heart rate. + /// + private const int COUNTED_MEASUREMENT_TIME = 5; + + /// + /// String representing key that is used by application's properties dictionary + /// to save current heart rate limit value. + /// + private const string HEART_RATE_LIMIT_KEY = "heartRateLimit"; + + /// + /// Reference to the object of the HeartRateMonitorModel class. + /// + private HeartRateMonitorModel heartRateMonitorModel; + + /// + /// Reference to the IDictionary object that is used to store some data, + /// so that they are available next time the applications runs. + /// + private IDictionary properties; + + /// + /// List of numbers storing measurement values. + /// It is used to calculate average value of the heart rate (at the end of the measurement process). + /// + private List measurementValues; + + + #endregion + + #region properties + + /// + /// An instance of PageNavigation class. + /// + public INavigation AppPageNavigation { private set; get; } + + /// + /// Starts and stops measurement process. + /// + public ICommand ToggleMeasurementCommand { private set; get; } + + /// + /// Prepares view model state before displaying SettingsPage. + /// + public ICommand ShowSettingsCommand { private set; get; } + + /// + /// Updates view model properties responsible for UI representation of heart rate limit. + /// + public ICommand UpdateHeartRateLimitCommand { private set; get; } + + /// + /// Command which shows message about denied privilege (application close). + /// The command is injected into view model. + /// + public ICommand PrivilegeDeniedInfoCommand { set; get; } + + /// + /// Command which shows message about lack of heart rate sensor (application close). + /// The command is injected into view model. + /// + public ICommand NotSupportedInfoCommand { set; get; } + + /// + /// Command which handles confirmation of privilege denied dialog. + /// + public ICommand AppUnusableConfirmedCommand { set; get; } + + /// + /// MeasurementStarted event. + /// It is fired when the measurement process starts. + /// + public event EventHandler MeasurementStarted; + + /// + /// MeasurementFinished event. + /// It is fired when the measurement finishes. + /// + public event EventHandler MeasurementFinished; + + /// + /// Property indicating whether measurement process is in progress. + /// + public bool IsMeasuring + { + set { SetProperty(ref isMeasuring, value); } + get { return isMeasuring; } + } + + /// + /// Property indicating whether measurement process is finished. + /// + public bool IsFinished + { + set { SetProperty(ref isFinished, value); } + get { return isFinished; } + } + + public bool IsMeasurementCounted + { + set { SetProperty(ref isMeasurementCounted, value); } + get { return isMeasurementCounted; } + } + + /// + /// Property indicating whether heart rate limit is exceeded. + /// + public bool IsMeasurementResultAlert + { + set { SetProperty(ref isMeasurementResultAlert, value); } + get { return isMeasurementResultAlert; } + } + + /// + /// Property with value representing measurement countdown. + /// + public int MeasurementCountdown + { + set { + SetProperty(ref measurementCountdown, value); + if (value <= COUNTED_MEASUREMENT_TIME) + IsMeasurementCounted = true; + } + get { return measurementCountdown; } + } + + /// + /// Property with value representing current heart rate. + /// + public int CurrentHeartRate + { + set + { + SetProperty(ref currentHeartRate, value); + UpdateMeasurementResultRange(); + UpdateMeasurementResultAlert(); + } + + get { return currentHeartRate; } + } + + /// + /// Property with value representing heart rate limit value. + /// + public int HeartRateLimitValue + { + set + { + SetProperty(ref heartRateLimitValue, value); + properties[HEART_RATE_LIMIT_KEY] = value; + UpdateMeasurementResultRange(); + UpdateMeasurementResultAlert(); + } + get { return heartRateLimitValue; } + } + + /// + /// Property with value representing temporary buffered heart rate limit value. + /// + public int HeartRateLimitBufferValue + { + set { SetProperty(ref heartRateLimitBufferValue, value); } + get { return heartRateLimitBufferValue; } + } + + /// + /// Property representing the range to which the heart rate value is classified, + /// when the measurement is finished. + /// + public int MeasurementResultRange + { + set { SetProperty(ref measurementResultRange, value); } + get { return measurementResultRange; } + } + #endregion + + #region methods + + /// + /// MainViewModel class constructor. + /// + public MainViewModel() + { + ToggleMeasurementCommand = new Command(ExecuteToggleMeasurementCommand); + ShowSettingsCommand = new Command(ExecuteShowSettingsCommand, CanExecuteShowSettingsCommand); + UpdateHeartRateLimitCommand = new Command(ExecuteUpdateHeartRateLimitCommand); + AppUnusableConfirmedCommand = new Command(ExecuteAppUnusableConfirmed); + } + + /// + /// Initializes the view model. + /// + /// View model instance properties. + /// Page navigation object. + /// The initialization task. + public async Task Init(IDictionary properties, INavigation pageMavigation) + { + this.properties = properties; + AppPageNavigation = pageMavigation; + + heartRateMonitorModel = new HeartRateMonitorModel(); + + heartRateMonitorModel.HeartRateMonitorDataChanged += ModelOnHeartRateMonitorDataChanged; + heartRateMonitorModel.HeartRateSensorNotSupported += ModelOnHeartRateSensorNotSupported; + + if (!await heartRateMonitorModel.CheckPrivileges()) + { + PrivilegeDeniedInfoCommand?.Execute(null); + + return; + } + + heartRateMonitorModel.Init(); + + RestoreHeartRateLimitSliderValue(); + } + + /// + /// Handles "HeartRateMonitorDataChanged" event of the HeartRateMonitorModel object. + /// Updates value of the MeasurementCountdown property. + /// Updates value of the CurrentHeartRate property. + /// Adds new value to the list of measurement values. + /// + /// Object firing the event. + /// Arguments passed to the event. + private void ModelOnHeartRateMonitorDataChanged(object sender, EventArgs e) + { + int heartRateValue = heartRateMonitorModel.GetHeartRate(); + + CurrentHeartRate = heartRateValue; + + if(IsMeasurementCounted) + measurementValues.Add(heartRateValue); + } + + /// + /// Handles "HeartRateSensorNotSupported" event of the HeartRateMonitorModel object. + /// Executes NotSupportedInfoCommand command. + /// + /// Object firing the event. + /// Arguments passed to the event. + private void ModelOnHeartRateSensorNotSupported(object sender, EventArgs e) + { + NotSupportedInfoCommand?.Execute(null); + } + + /// + /// Handles execution of command which occurs when user confirms apps unability to use heart rate sensor. + /// Closes the application. + /// + private void ExecuteAppUnusableConfirmed() + { + try + { + Tizen.Applications.Application.Current.Exit(); + } + catch (Exception) + { + Log.Error("HeartRateMonitor", "Unable to close the application"); + } + } + + /// + /// Executes UpdateHeartRateLimitCommand command. + /// Updates value of the HeartRateLimitSliderValue property. + /// Navigates back to the measurement page by executing NavigateBackCommand command + /// of the PageNavigation class. + /// + private void ExecuteUpdateHeartRateLimitCommand() + { + HeartRateLimitValue = HeartRateLimitBufferValue; + AppPageNavigation.PopAsync(); + } + + /// + /// Checks whether the ShowSettingsCommand command can be executed. + /// + /// Command parameter. + /// Returns true if the ShowSettingsCommand can be executed, false otherwise. + private bool CanExecuteShowSettingsCommand(object arg) + { + return !IsMeasuring; + } + + /// + /// Executes the ShowSettingsCommand command. + /// Updates value of the HeartRateLimitSliderBufferValue property. + /// Navigates to the settings page by executing NavigateToCommand command + /// of the PageNavigation class. + /// + /// Command parameter. + private void ExecuteShowSettingsCommand(object obj) + { + HeartRateLimitBufferValue = HeartRateLimitValue; + AppPageNavigation.PushAsync(new SettingsPage(), false); + } + + /// + /// Executes the ToggleMeasurementCommand command. + /// Depending on the IsMeasuring property state it starts or stops measurement process. + /// Additionally it calls ChangeCanExecute method + /// to update execution state of the ShowSettingsCommand command. + /// + private void ExecuteToggleMeasurementCommand() + { + if (IsMeasuring) + { + StopMeasurement(true); + measurementLock += 1; + } + else + { + StartMeasurement(); + } + + ((Command)ShowSettingsCommand).ChangeCanExecute(); + } + + /// + /// Starts measurement process. + /// Sets values of fields and properties + /// responsible for the correct flow of the measurement process. + /// Invokes "MeasurementStarted" event. + /// Sets timer to let the application know + /// when the measurement process should be stopped automatically. + /// Executes the StartHeartRateMonitor method of the HeartRateMonitorModel object. + /// + private void StartMeasurement() + { + IsFinished = false; + IsMeasuring = true; + MeasurementCountdown = MEASUREMENT_TIME; + measurementStartTimestamp = DateTime.Now; + measurementValues = new List(); + MeasurementStarted?.Invoke(this, new EventArgs()); + + Device.StartTimer(TimeSpan.FromSeconds(MEASUREMENT_TIME), () => + { + if (IsMeasuring && measurementLock == 0) + { + StopMeasurement(); + ((Command)ShowSettingsCommand).ChangeCanExecute(); + } + + if (measurementLock > 0) + { + measurementLock -= 1; + } + + return false; + }); + + Device.StartTimer(TimeSpan.FromSeconds(1), () => + { + if (!IsMeasuring) + { + return false; + } + + TimeSpan measurementElapsedTime = DateTime.Now - measurementStartTimestamp; + MeasurementCountdown = MEASUREMENT_TIME - measurementElapsedTime.Seconds; + return true; + }); + + heartRateMonitorModel.StartHeartRateMonitor(); + } + + /// + /// Stops measurement process. + /// Sets values of properties responsible for indicating that the measurement process is finished. + /// Invokes "MeasurementFinished" event. + /// Executes the StopHeartRateMonitor method of the HeartRateMonitorModel object. + /// Updates value of the CurrentHeartRate property by using GetAverageHeartRateValue method. + /// + /// Flag indicating if current heart rate value should be reset. + private void StopMeasurement(bool canceled = false) + { + IsMeasuring = false; + IsMeasurementCounted = false; + MeasurementFinished?.Invoke(this, new EventArgs()); + heartRateMonitorModel.StopHeartRateMonitor(); + + if (canceled) + { + CurrentHeartRate = 0; + return; + } + + CurrentHeartRate = GetAverageHeartRateValue(); + IsFinished = true; + } + + /// + /// Calculates average value of the heart rate based on values stored in the _measurementValues list. + /// + /// Average value of the heart rate. + private int GetAverageHeartRateValue() + { + int total = 0, + count = measurementValues.Count; + + if (count == 0) + { + return total; + } + + total = measurementValues.Sum(); + + return total / count; + } + + /// + /// Updates value of the MeasurementResultRange property. + /// + private void UpdateMeasurementResultRange() + { + if (CurrentHeartRate > HeartRateLimitValue) + { + MeasurementResultRange = 1; + } + else if (CurrentHeartRate >= AVERAGE_HEART_RATE_VALUE_LOWER_LIMIT && + CurrentHeartRate <= AVERAGE_HEART_RATE_VALUE_UPPER_LIMIT) + { + MeasurementResultRange = -1; + } + else + { + MeasurementResultRange = 0; + } + } + + /// + /// Updates value of the IsMeasurementResultAlert property. + /// + private void UpdateMeasurementResultAlert() + { + IsMeasurementResultAlert = MeasurementResultRange == 1; + } + + /// + /// Restores heart rate limit slider value. + /// It tries to obtain this value from application properties dictionary. + /// If it is not available in the dictionary, the value is set to app defined constant. + /// + private void RestoreHeartRateLimitSliderValue() + { + if (properties.ContainsKey(HEART_RATE_LIMIT_KEY)) + { + HeartRateLimitValue = (int)properties[HEART_RATE_LIMIT_KEY]; + } + else + { + HeartRateLimitValue = HEART_RATE_DEFAULT_LIMIT_VALUE; + } + } + + #endregion + } +} \ No newline at end of file diff --git a/Test/HeartRateMonitor/ViewModels/ViewModelBase.cs b/Test/HeartRateMonitor/ViewModels/ViewModelBase.cs new file mode 100644 index 0000000..41cae14 --- /dev/null +++ b/Test/HeartRateMonitor/ViewModels/ViewModelBase.cs @@ -0,0 +1,74 @@ + +//Copyright 2018 Samsung Electronics Co., Ltd +// +//Licensed under the Apache License, Version 2.0 (the "License"); +//you may not use this file except in compliance with the License. +//You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, +//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//See the License for the specific language governing permissions and +//limitations under the License. + + +using System; +using System.ComponentModel; +using System.Runtime.CompilerServices; + +namespace HeartRateMonitor.ViewModels +{ + /// + /// ViewModelBase class. + /// It implements INotifyPropertyChanged interface + /// so that supports notifying about properties changes. + /// + public class ViewModelBase : INotifyPropertyChanged + { + #region properties + + /// + /// PropertyChanged event handler. + /// + public event PropertyChangedEventHandler PropertyChanged; + + #endregion + + #region methods + + /// + /// Updates value of the "storage" argument with value given by the second argument. + /// Notifies the application about update of the property which has executed this method. + /// + /// Value storage object + /// Value to set + /// Automatically obtained property name + /// Property value type + /// Returns true if storage is successfully updated, false otherwise. + protected bool SetProperty(ref T storage, T value, + [CallerMemberName] string propertyName = null) + { + if (Object.Equals(storage, value)) + { + return false; + } + + storage = value; + OnPropertyChanged(propertyName); + return true; + } + + /// + /// Notifies the application about update of the property with name given as a parameter. + /// + /// Name of the changed property. + private void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + #endregion + } +} \ No newline at end of file diff --git a/Test/HeartRateMonitor/Views/MeasurementPage.xaml b/Test/HeartRateMonitor/Views/MeasurementPage.xaml new file mode 100644 index 0000000..43b2f16 --- /dev/null +++ b/Test/HeartRateMonitor/Views/MeasurementPage.xaml @@ -0,0 +1,125 @@ + + + + + + + + + + + + + + + + + + + + + + + + + +