Skip to content

Commit

Permalink
Sleep timer (jenius-apps#27)
Browse files Browse the repository at this point in the history
* Fixed automation name not updating on state change

* Added sleep timer
  • Loading branch information
dpaulino authored Nov 7, 2020
1 parent 4af5a1f commit 115547c
Show file tree
Hide file tree
Showing 9 changed files with 322 additions and 2 deletions.
8 changes: 8 additions & 0 deletions src/AmbientSounds.Uwp/AmbientSounds.Uwp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -127,10 +127,14 @@
<Compile Include="Controls\PlayerControl.xaml.cs">
<DependentUpon>PlayerControl.xaml</DependentUpon>
</Compile>
<Compile Include="Controls\SleepTimerControl.xaml.cs">
<DependentUpon>SleepTimerControl.xaml</DependentUpon>
</Compile>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Services\LocalSettings.cs" />
<Compile Include="Services\MediaPlayerService.cs" />
<Compile Include="Services\SoundDataProvider.cs" />
<Compile Include="Services\TimerService.cs" />
<Compile Include="Views\MainPage.xaml.cs">
<DependentUpon>MainPage.xaml</DependentUpon>
</Compile>
Expand Down Expand Up @@ -229,6 +233,10 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Controls\SleepTimerControl.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\MainPage.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
Expand Down
2 changes: 2 additions & 0 deletions src/AmbientSounds.Uwp/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,9 @@ private static IServiceProvider ConfigureServices()
return new ServiceCollection()
.AddTransient<MainPageViewModel>()
.AddTransient<IUserSettings, LocalSettings>()
.AddTransient<ITimerService, TimerService>()
.AddSingleton<PlayerViewModel>()
.AddSingleton<SleepTimerViewModel>()
.AddSingleton<IMediaPlayerService, MediaPlayerService>()
.AddSingleton<ISoundDataProvider, SoundDataProvider>()
.BuildServiceProvider();
Expand Down
6 changes: 4 additions & 2 deletions src/AmbientSounds.Uwp/Controls/PlayerControl.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:AmbientSounds.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
d:DesignHeight="300"
d:DesignWidth="400"
mc:Ignorable="d">
Expand All @@ -16,7 +16,8 @@
Height="60"
AutomationProperties.Name="{x:Bind ViewModel.AutomationName, Mode=OneWay}"
Command="{x:Bind ViewModel.TogglePlayStateCommand}"
CornerRadius="30">
CornerRadius="30"
ToolTipService.ToolTip="{x:Bind ViewModel.AutomationName, Mode=OneWay}">
<Grid>
<!-- play -->
<FontIcon
Expand All @@ -41,6 +42,7 @@
Margin="0,4,0,0"
Value="{x:Bind ViewModel.Volume, Mode=TwoWay}" />
</StackPanel>
<local:SleepTimerControl Margin="20,0,0,0" VerticalAlignment="Center" />
</StackPanel>
</Grid>
</UserControl>
91 changes: 91 additions & 0 deletions src/AmbientSounds.Uwp/Controls/SleepTimerControl.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<UserControl
x:Class="AmbientSounds.Controls.SleepTimerControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:AmbientSounds.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
d:DesignHeight="300"
d:DesignWidth="400"
mc:Ignorable="d">

<StackPanel Orientation="Horizontal">
<Button
Width="40"
Height="40"
AutomationProperties.Name="Click here to set up a sleep timer, where the sound will pause after the selected time."
Background="Transparent"
CornerRadius="20"
ToolTipService.ToolTip="Click here to set up a sleep timer, where the sound will pause after the selected time.">
<Grid>
<FontIcon FontFamily="Segoe MDL2 Assets" Glyph="&#xF0CE;" />
</Grid>
<Button.Flyout>
<muxc:CommandBarFlyout Placement="BottomEdgeAlignedLeft">
<muxc:CommandBarFlyout.PrimaryCommands>
<AppBarButton
AutomationProperties.Name="Continue timer"
Command="{x:Bind ViewModel.TimerPlayCommand}"
Icon="Play" />
<AppBarButton
AutomationProperties.Name="Pause timer"
Command="{x:Bind ViewModel.TimerPauseCommand}"
Icon="Pause" />
<AppBarButton
AutomationProperties.Name="Stop timer and clear time"
Command="{x:Bind ViewModel.TimerStopCommand}"
Icon="Stop" />
</muxc:CommandBarFlyout.PrimaryCommands>
<muxc:CommandBarFlyout.SecondaryCommands>
<AppBarButton
Command="{x:Bind ViewModel.TimerStartCommand}"
Icon="Clock"
Label="5 minutes">
<AppBarButton.CommandParameter>
<x:Int32>5</x:Int32>
</AppBarButton.CommandParameter>
</AppBarButton>
<AppBarButton
Command="{x:Bind ViewModel.TimerStartCommand}"
Icon="Clock"
Label="10 minutes">
<AppBarButton.CommandParameter>
<x:Int32>10</x:Int32>
</AppBarButton.CommandParameter>
</AppBarButton>
<AppBarButton
Command="{x:Bind ViewModel.TimerStartCommand}"
Icon="Clock"
Label="15 minutes">
<AppBarButton.CommandParameter>
<x:Int32>15</x:Int32>
</AppBarButton.CommandParameter>
</AppBarButton>
<AppBarButton
Command="{x:Bind ViewModel.TimerStartCommand}"
Icon="Clock"
Label="30 minutes">
<AppBarButton.CommandParameter>
<x:Int32>30</x:Int32>
</AppBarButton.CommandParameter>
</AppBarButton>
<AppBarButton
Command="{x:Bind ViewModel.TimerStartCommand}"
Icon="Clock"
Label="60 minutes">
<AppBarButton.CommandParameter>
<x:Int32>60</x:Int32>
</AppBarButton.CommandParameter>
</AppBarButton>
</muxc:CommandBarFlyout.SecondaryCommands>
</muxc:CommandBarFlyout>
</Button.Flyout>
</Button>
<TextBlock
Margin="12,0,0,0"
VerticalAlignment="Center"
Text="{x:Bind ViewModel.TimeLeft, Mode=OneWay}"
Visibility="{x:Bind ViewModel.CountdownVisible, Mode=OneWay}" />
</StackPanel>
</UserControl>
19 changes: 19 additions & 0 deletions src/AmbientSounds.Uwp/Controls/SleepTimerControl.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using AmbientSounds.ViewModels;
using Microsoft.Extensions.DependencyInjection;
using Windows.UI.Xaml.Controls;

// The User Control item template is documented at https://go.microsoft.com/fwlink/?LinkId=234236

namespace AmbientSounds.Controls
{
public sealed partial class SleepTimerControl : UserControl
{
public SleepTimerControl()
{
this.InitializeComponent();
this.DataContext = App.Services.GetRequiredService<SleepTimerViewModel>();
}

public SleepTimerViewModel ViewModel => (SleepTimerViewModel)this.DataContext;
}
}
53 changes: 53 additions & 0 deletions src/AmbientSounds.Uwp/Services/TimerService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using System;
using System.Timers;
using Windows.System;

namespace AmbientSounds.Services
{
/// <summary>
/// Class for handling a timer countdown.
/// </summary>
public class TimerService : ITimerService
{
/// <inheritdoc/>
public event EventHandler<int> IntervalElapsed;

private readonly Timer _timer;
private readonly DispatcherQueue _dispatcherQueue;

public TimerService()
{
_dispatcherQueue = DispatcherQueue.GetForCurrentThread();
_timer = new Timer();
_timer.Elapsed += TimerIntervalElapsed;
}

/// <inheritdoc/>
public int Interval
{
get => (int)_timer.Interval;
set => _timer.Interval = value;
}

/// <inheritdoc/>
public TimeSpan Remaining { get; set; }

/// <inheritdoc/>
public void Start()
{
_timer.Start();
}

/// <inheritdoc/>
public void Stop()
{
_timer.Stop();
}

private void TimerIntervalElapsed(object sender, object e)
{
Remaining -= new TimeSpan(0, 0, 0, 0, Interval);
_dispatcherQueue.TryEnqueue(() => IntervalElapsed?.Invoke(sender, Interval));
}
}
}
36 changes: 36 additions & 0 deletions src/AmbientSounds/Services/ITimerService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System;

namespace AmbientSounds.Services
{
/// <summary>
/// Interface for a timer.
/// </summary>
public interface ITimerService
{
/// <summary>
/// Triggered when the interval elapses.
/// Interval is milliseconds.
/// </summary>
event EventHandler<int> IntervalElapsed;

/// <summary>
/// Interval in milliseconds.
/// </summary>
int Interval { get; set; }

/// <summary>
/// The time remaining.
/// </summary>
TimeSpan Remaining { get; set; }

/// <summary>
/// Start the timer.
/// </summary>
void Start();

/// <summary>
/// Stop the timer.
/// </summary>
void Stop();
}
}
1 change: 1 addition & 0 deletions src/AmbientSounds/ViewModels/PlayerViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ private void UpdatePlayState()
{
OnPropertyChanged(nameof(IsPlaying));
OnPropertyChanged(nameof(IsPaused));
OnPropertyChanged(nameof(AutomationName));
}

private void NewSoundPlayed(object sender, EventArgs e)
Expand Down
108 changes: 108 additions & 0 deletions src/AmbientSounds/ViewModels/SleepTimerViewModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
using AmbientSounds.Services;
using Microsoft.Toolkit.Diagnostics;
using Microsoft.Toolkit.Mvvm.ComponentModel;
using Microsoft.Toolkit.Mvvm.Input;
using System;

namespace AmbientSounds.ViewModels
{
public class SleepTimerViewModel : ObservableObject
{
private const int DefaultTimerInterval = 1000;
private readonly IMediaPlayerService _player;
private readonly ITimerService _timer;

public SleepTimerViewModel(
IMediaPlayerService player,
ITimerService timer)
{
Guard.IsNotNull(player, nameof(player));
Guard.IsNotNull(timer, nameof(timer));

_player = player;
_timer = timer;
_timer.Interval = DefaultTimerInterval;
_timer.IntervalElapsed += TimerElapsed;

TimerStartCommand = new RelayCommand<int>(StartTimer);
TimerPlayCommand = new RelayCommand(PlayTimer);
TimerPauseCommand = new RelayCommand(PauseTimer);
TimerStopCommand = new RelayCommand(StopTimer);
}

/// <summary>
/// Starts the timer with the specified remainder time.
/// </summary>
public IRelayCommand<int> TimerStartCommand { get; }

/// <summary>
/// Plays the timer if it were paused.
/// </summary>
public IRelayCommand TimerPlayCommand { get; }

/// <summary>
/// Pauses the timer.
/// </summary>
public IRelayCommand TimerPauseCommand { get; }

/// <summary>
/// Stops the timer and clears the countdown.
/// </summary>
public IRelayCommand TimerStopCommand { get; }

/// <summary>
/// Determines if the sleep timer's countdown
/// is visible.
/// </summary>
public bool CountdownVisible
{
get => _countdownVisible;
set => SetProperty(ref _countdownVisible, value);
}
private bool _countdownVisible;

/// <summary>
/// String representation of time remaining.
/// E.g. 0:59:12 for 59 minutes and 12 seconds left.
/// </summary>
public string TimeLeft => _timer.Remaining.ToString("g");

private void StartTimer(int minutes)
{
_timer.Remaining = new TimeSpan(0, minutes, 0);
OnPropertyChanged(nameof(TimeLeft));
CountdownVisible = true;
_timer.Start();
}

private void PlayTimer()
{
if (_timer.Remaining > new TimeSpan(0))
{
_timer.Start();
}
}

private void PauseTimer()
{
_timer.Stop();
}

private void StopTimer()
{
_timer.Stop();
_timer.Remaining = new TimeSpan(0);
CountdownVisible = false;
}

private void TimerElapsed(object sender, int intervalInMs)
{
OnPropertyChanged(nameof(TimeLeft));
if (_timer.Remaining < new TimeSpan(0, 0, 1))
{
StopTimer();
_player.Pause();
}
}
}
}

0 comments on commit 115547c

Please sign in to comment.