diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterAudioControl.xaml b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterAudioControl.xaml
new file mode 100644
index 00000000..d15849b7
--- /dev/null
+++ b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterAudioControl.xaml
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterAudioControl.xaml.cs b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterAudioControl.xaml.cs
new file mode 100644
index 00000000..9d9a22c4
--- /dev/null
+++ b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterAudioControl.xaml.cs
@@ -0,0 +1,161 @@
+/*
+ * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+using System;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Controls.Primitives;
+using System.Windows.Media;
+using System.Windows.Threading;
+using SafeExamBrowser.Contracts.I18n;
+using SafeExamBrowser.Contracts.UserInterface.Shell;
+using SafeExamBrowser.Contracts.UserInterface.Shell.Events;
+using SafeExamBrowser.UserInterface.Shared.Utilities;
+
+namespace SafeExamBrowser.UserInterface.Mobile.Controls
+{
+ public partial class ActionCenterAudioControl : UserControl, ISystemAudioControl
+ {
+ private readonly IText text;
+ private bool muted;
+ private XamlIconResource MutedIcon;
+ private XamlIconResource NoDeviceIcon;
+
+ public event AudioMuteRequestedEventHandler MuteRequested;
+ public event AudioVolumeSelectedEventHandler VolumeSelected;
+
+ public ActionCenterAudioControl(IText text)
+ {
+ this.text = text;
+
+ InitializeComponent();
+ InitializeAudioControl();
+ }
+
+ public bool HasOutputDevice
+ {
+ set
+ {
+ Dispatcher.InvokeAsync(() =>
+ {
+ Button.IsEnabled = value;
+
+ if (!value)
+ {
+ TaskbarIcon.Content = IconResourceLoader.Load(NoDeviceIcon);
+ }
+ });
+ }
+ }
+
+ public bool OutputDeviceMuted
+ {
+ set
+ {
+ Dispatcher.InvokeAsync(() =>
+ {
+ muted = value;
+
+ if (value)
+ {
+ MuteButton.ToolTip = text.Get(TextKey.SystemControl_AudioDeviceUnmuteTooltip);
+ PopupIcon.Content = IconResourceLoader.Load(MutedIcon);
+ TaskbarIcon.Content = IconResourceLoader.Load(MutedIcon);
+ }
+ else
+ {
+ MuteButton.ToolTip = text.Get(TextKey.SystemControl_AudioDeviceMuteTooltip);
+ TaskbarIcon.Content = LoadIcon(Volume.Value / 100);
+ }
+ });
+ }
+ }
+
+ public string OutputDeviceName
+ {
+ set
+ {
+ Dispatcher.InvokeAsync(() => AudioDeviceName.Text = value);
+ }
+ }
+
+ public double OutputDeviceVolume
+ {
+ set
+ {
+ Dispatcher.InvokeAsync(() =>
+ {
+ Volume.ValueChanged -= Volume_ValueChanged;
+ Volume.Value = Math.Round(value * 100);
+ Volume.ValueChanged += Volume_ValueChanged;
+
+ if (!muted)
+ {
+ PopupIcon.Content = LoadIcon(value);
+ TaskbarIcon.Content = LoadIcon(value);
+ }
+ });
+ }
+ }
+
+ public void Close()
+ {
+ Popup.IsOpen = false;
+ }
+
+ public void SetInformation(string text)
+ {
+ Dispatcher.InvokeAsync(() =>
+ {
+ Button.ToolTip = text;
+ Text.Text = text;
+ });
+ }
+
+ private void InitializeAudioControl()
+ {
+ var originalBrush = Grid.Background;
+
+ Button.Click += (o, args) => Popup.IsOpen = !Popup.IsOpen;
+ Button.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => Popup.IsOpen = Popup.IsMouseOver));
+ MuteButton.Click += (o, args) => MuteRequested?.Invoke(!muted);
+ MutedIcon = new XamlIconResource(new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/Audio_Muted.xaml"));
+ NoDeviceIcon = new XamlIconResource(new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/Audio_Light_NoDevice.xaml"));
+ Popup.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => Popup.IsOpen = IsMouseOver));
+ Popup.Opened += (o, args) => Grid.Background = Brushes.Gray;
+ Popup.Closed += (o, args) => Grid.Background = originalBrush;
+ Volume.ValueChanged += Volume_ValueChanged;
+ }
+
+ private void Volume_DragStarted(object sender, DragStartedEventArgs e)
+ {
+ Volume.ValueChanged -= Volume_ValueChanged;
+ }
+
+ private void Volume_DragCompleted(object sender, DragCompletedEventArgs e)
+ {
+ VolumeSelected?.Invoke(Volume.Value / 100);
+ Volume.ValueChanged += Volume_ValueChanged;
+ }
+
+ private void Volume_ValueChanged(object sender, RoutedPropertyChangedEventArgs e)
+ {
+ VolumeSelected?.Invoke(Volume.Value / 100);
+ }
+
+ private UIElement LoadIcon(double volume)
+ {
+ var icon = volume > 0.66 ? "100" : (volume > 0.33 ? "66" : "33");
+ var uri = new Uri($"pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/Audio_Light_{icon}.xaml");
+ var resource = new XamlIconResource(uri);
+
+ return IconResourceLoader.Load(resource);
+ }
+ }
+}
diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarAudioControl.xaml b/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarAudioControl.xaml
new file mode 100644
index 00000000..ab59622d
--- /dev/null
+++ b/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarAudioControl.xaml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarAudioControl.xaml.cs b/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarAudioControl.xaml.cs
new file mode 100644
index 00000000..83b864ff
--- /dev/null
+++ b/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarAudioControl.xaml.cs
@@ -0,0 +1,166 @@
+/*
+ * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+using System;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Controls.Primitives;
+using System.Windows.Media;
+using SafeExamBrowser.Contracts.I18n;
+using SafeExamBrowser.Contracts.UserInterface.Shell;
+using SafeExamBrowser.Contracts.UserInterface.Shell.Events;
+using SafeExamBrowser.UserInterface.Shared.Utilities;
+
+namespace SafeExamBrowser.UserInterface.Mobile.Controls
+{
+ public partial class TaskbarAudioControl : UserControl, ISystemAudioControl
+ {
+ private readonly IText text;
+ private bool muted;
+ private XamlIconResource MutedIcon;
+ private XamlIconResource NoDeviceIcon;
+
+ public event AudioMuteRequestedEventHandler MuteRequested;
+ public event AudioVolumeSelectedEventHandler VolumeSelected;
+
+ public TaskbarAudioControl(IText text)
+ {
+ this.text = text;
+
+ InitializeComponent();
+ InitializeAudioControl();
+ }
+
+ public bool HasOutputDevice
+ {
+ set
+ {
+ Dispatcher.InvokeAsync(() =>
+ {
+ Button.IsEnabled = value;
+
+ if (!value)
+ {
+ TaskbarIcon.Content = IconResourceLoader.Load(NoDeviceIcon);
+ }
+ });
+ }
+ }
+
+ public bool OutputDeviceMuted
+ {
+ set
+ {
+ Dispatcher.InvokeAsync(() =>
+ {
+ muted = value;
+
+ if (value)
+ {
+ MuteButton.ToolTip = text.Get(TextKey.SystemControl_AudioDeviceUnmuteTooltip);
+ PopupIcon.Content = IconResourceLoader.Load(MutedIcon);
+ TaskbarIcon.Content = IconResourceLoader.Load(MutedIcon);
+ }
+ else
+ {
+ MuteButton.ToolTip = text.Get(TextKey.SystemControl_AudioDeviceMuteTooltip);
+ TaskbarIcon.Content = LoadIcon(Volume.Value / 100);
+ }
+ });
+ }
+ }
+
+ public string OutputDeviceName
+ {
+ set
+ {
+ Dispatcher.InvokeAsync(() => AudioDeviceName.Text = value);
+ }
+ }
+
+ public double OutputDeviceVolume
+ {
+ set
+ {
+ Dispatcher.InvokeAsync(() =>
+ {
+ Volume.ValueChanged -= Volume_ValueChanged;
+ Volume.Value = Math.Round(value * 100);
+ Volume.ValueChanged += Volume_ValueChanged;
+
+ if (!muted)
+ {
+ PopupIcon.Content = LoadIcon(value);
+ TaskbarIcon.Content = LoadIcon(value);
+ }
+ });
+ }
+ }
+
+ public void Close()
+ {
+ Popup.IsOpen = false;
+ }
+
+ public void SetInformation(string text)
+ {
+ Dispatcher.InvokeAsync(() => Button.ToolTip = text);
+ }
+
+ private void InitializeAudioControl()
+ {
+ var originalBrush = Button.Background;
+
+ Button.Click += (o, args) => Popup.IsOpen = !Popup.IsOpen;
+ Button.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => Popup.IsOpen = Popup.IsMouseOver));
+ MuteButton.Click += (o, args) => MuteRequested?.Invoke(!muted);
+ MutedIcon = new XamlIconResource(new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/Audio_Muted.xaml"));
+ NoDeviceIcon = new XamlIconResource(new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/Audio_NoDevice.xaml"));
+ Popup.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => Popup.IsOpen = IsMouseOver));
+ Volume.ValueChanged += Volume_ValueChanged;
+
+ Popup.Opened += (o, args) =>
+ {
+ Background = Brushes.LightGray;
+ Button.Background = Brushes.LightGray;
+ };
+
+ Popup.Closed += (o, args) =>
+ {
+ Background = originalBrush;
+ Button.Background = originalBrush;
+ };
+ }
+
+ private void Volume_DragStarted(object sender, DragStartedEventArgs e)
+ {
+ Volume.ValueChanged -= Volume_ValueChanged;
+ }
+
+ private void Volume_DragCompleted(object sender, DragCompletedEventArgs e)
+ {
+ VolumeSelected?.Invoke(Volume.Value / 100);
+ Volume.ValueChanged += Volume_ValueChanged;
+ }
+
+ private void Volume_ValueChanged(object sender, RoutedPropertyChangedEventArgs e)
+ {
+ VolumeSelected?.Invoke(Volume.Value / 100);
+ }
+
+ private UIElement LoadIcon(double volume)
+ {
+ var icon = volume > 0.66 ? "100" : (volume > 0.33 ? "66" : "33");
+ var uri = new Uri($"pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/Audio_{icon}.xaml");
+ var resource = new XamlIconResource(uri);
+
+ return IconResourceLoader.Load(resource);
+ }
+ }
+}
diff --git a/SafeExamBrowser.UserInterface.Mobile/Images/Audio_100.xaml b/SafeExamBrowser.UserInterface.Mobile/Images/Audio_100.xaml
new file mode 100644
index 00000000..00883e6a
--- /dev/null
+++ b/SafeExamBrowser.UserInterface.Mobile/Images/Audio_100.xaml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
diff --git a/SafeExamBrowser.UserInterface.Mobile/Images/Audio_33.xaml b/SafeExamBrowser.UserInterface.Mobile/Images/Audio_33.xaml
new file mode 100644
index 00000000..5e81559a
--- /dev/null
+++ b/SafeExamBrowser.UserInterface.Mobile/Images/Audio_33.xaml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
diff --git a/SafeExamBrowser.UserInterface.Mobile/Images/Audio_66.xaml b/SafeExamBrowser.UserInterface.Mobile/Images/Audio_66.xaml
new file mode 100644
index 00000000..11b04a28
--- /dev/null
+++ b/SafeExamBrowser.UserInterface.Mobile/Images/Audio_66.xaml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
diff --git a/SafeExamBrowser.UserInterface.Mobile/Images/Audio_Light_100.xaml b/SafeExamBrowser.UserInterface.Mobile/Images/Audio_Light_100.xaml
new file mode 100644
index 00000000..00883e6a
--- /dev/null
+++ b/SafeExamBrowser.UserInterface.Mobile/Images/Audio_Light_100.xaml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
diff --git a/SafeExamBrowser.UserInterface.Mobile/Images/Audio_Light_33.xaml b/SafeExamBrowser.UserInterface.Mobile/Images/Audio_Light_33.xaml
new file mode 100644
index 00000000..92834be1
--- /dev/null
+++ b/SafeExamBrowser.UserInterface.Mobile/Images/Audio_Light_33.xaml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
diff --git a/SafeExamBrowser.UserInterface.Mobile/Images/Audio_Light_66.xaml b/SafeExamBrowser.UserInterface.Mobile/Images/Audio_Light_66.xaml
new file mode 100644
index 00000000..f30ed5a9
--- /dev/null
+++ b/SafeExamBrowser.UserInterface.Mobile/Images/Audio_Light_66.xaml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
diff --git a/SafeExamBrowser.UserInterface.Mobile/Images/Audio_Light_NoDevice.xaml b/SafeExamBrowser.UserInterface.Mobile/Images/Audio_Light_NoDevice.xaml
new file mode 100644
index 00000000..96c16524
--- /dev/null
+++ b/SafeExamBrowser.UserInterface.Mobile/Images/Audio_Light_NoDevice.xaml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
diff --git a/SafeExamBrowser.UserInterface.Mobile/Images/Audio_Muted.xaml b/SafeExamBrowser.UserInterface.Mobile/Images/Audio_Muted.xaml
new file mode 100644
index 00000000..f5479c85
--- /dev/null
+++ b/SafeExamBrowser.UserInterface.Mobile/Images/Audio_Muted.xaml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
diff --git a/SafeExamBrowser.UserInterface.Mobile/Images/Audio_NoDevice.xaml b/SafeExamBrowser.UserInterface.Mobile/Images/Audio_NoDevice.xaml
new file mode 100644
index 00000000..ef6abd7c
--- /dev/null
+++ b/SafeExamBrowser.UserInterface.Mobile/Images/Audio_NoDevice.xaml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
diff --git a/SafeExamBrowser.UserInterface.Mobile/SafeExamBrowser.UserInterface.Mobile.csproj b/SafeExamBrowser.UserInterface.Mobile/SafeExamBrowser.UserInterface.Mobile.csproj
index 486d58cb..341077c8 100644
--- a/SafeExamBrowser.UserInterface.Mobile/SafeExamBrowser.UserInterface.Mobile.csproj
+++ b/SafeExamBrowser.UserInterface.Mobile/SafeExamBrowser.UserInterface.Mobile.csproj
@@ -82,6 +82,9 @@
ActionCenterApplicationControl.xaml
+
+ ActionCenterAudioControl.xaml
+
ActionCenterClock.xaml
@@ -112,6 +115,9 @@
TaskbarApplicationInstanceButton.xaml
+
+ TaskbarAudioControl.xaml
+
TaskbarClock.xaml
@@ -195,6 +201,10 @@
Designer
MSBuild:Compile
+
+ MSBuild:Compile
+ Designer
+
MSBuild:Compile
Designer
@@ -235,6 +245,10 @@
MSBuild:Compile
Designer
+
+ MSBuild:Compile
+ Designer
+
MSBuild:Compile
Designer
@@ -267,6 +281,42 @@
MSBuild:Compile
Designer
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
+
MSBuild:Compile
Designer
diff --git a/SafeExamBrowser.UserInterface.Mobile/UserInterfaceFactory.cs b/SafeExamBrowser.UserInterface.Mobile/UserInterfaceFactory.cs
index 4fdf8d08..4b5ef91d 100644
--- a/SafeExamBrowser.UserInterface.Mobile/UserInterfaceFactory.cs
+++ b/SafeExamBrowser.UserInterface.Mobile/UserInterfaceFactory.cs
@@ -54,8 +54,14 @@ namespace SafeExamBrowser.UserInterface.Mobile
public ISystemAudioControl CreateAudioControl(Location location)
{
- // TODO
- throw new System.NotImplementedException();
+ if (location == Location.ActionCenter)
+ {
+ return new ActionCenterAudioControl(text);
+ }
+ else
+ {
+ return new TaskbarAudioControl(text);
+ }
}
public IBrowserWindow CreateBrowserWindow(IBrowserControl control, BrowserSettings settings, bool isMainWindow)