diff --git a/SafeExamBrowser.Client/CompositionRoot.cs b/SafeExamBrowser.Client/CompositionRoot.cs index a5b11842..5cb68264 100644 --- a/SafeExamBrowser.Client/CompositionRoot.cs +++ b/SafeExamBrowser.Client/CompositionRoot.cs @@ -23,6 +23,7 @@ using SafeExamBrowser.Contracts.Client; using SafeExamBrowser.Contracts.Communication.Hosts; using SafeExamBrowser.Contracts.Communication.Proxies; using SafeExamBrowser.Contracts.Configuration; +using SafeExamBrowser.Contracts.Configuration.Settings; using SafeExamBrowser.Contracts.Core.OperationModel; using SafeExamBrowser.Contracts.I18n; using SafeExamBrowser.Contracts.Logging; @@ -42,7 +43,6 @@ using SafeExamBrowser.Monitoring.Mouse; using SafeExamBrowser.Monitoring.Processes; using SafeExamBrowser.Monitoring.Windows; using SafeExamBrowser.SystemComponents; -using SafeExamBrowser.UserInterface.Desktop; using SafeExamBrowser.WindowsApi; namespace SafeExamBrowser.Client @@ -54,6 +54,7 @@ namespace SafeExamBrowser.Client private LogLevel logLevel; private string runtimeHostUri; private Guid startupToken; + private UserInterfaceMode uiMode; private IActionCenter actionCenter; private IBrowserApplicationController browserController; @@ -87,14 +88,14 @@ namespace SafeExamBrowser.Client InitializeLogging(); InitializeText(); - actionCenter = new ActionCenter(); + actionCenter = BuildActionCenter(); keyboardLayout = new KeyboardLayout(new ModuleLogger(logger, nameof(KeyboardLayout)), text); - messageBox = new MessageBox(text); + messageBox = BuildMessageBox(); powerSupply = new PowerSupply(new ModuleLogger(logger, nameof(PowerSupply)), text); processMonitor = new ProcessMonitor(new ModuleLogger(logger, nameof(ProcessMonitor)), nativeMethods); - uiFactory = new UserInterfaceFactory(text); + uiFactory = BuildUserInterfaceFactory(); runtimeProxy = new RuntimeProxy(runtimeHostUri, new ProxyObjectFactory(), new ModuleLogger(logger, nameof(RuntimeProxy))); - taskbar = new Taskbar(new ModuleLogger(logger, nameof(Taskbar))); + taskbar = BuildTaskbar(); windowMonitor = new WindowMonitor(new ModuleLogger(logger, nameof(WindowMonitor)), nativeMethods); wirelessNetwork = new WirelessNetwork(new ModuleLogger(logger, nameof(WirelessNetwork)), text); @@ -153,7 +154,7 @@ namespace SafeExamBrowser.Client private void ValidateCommandLineArguments() { var args = Environment.GetCommandLineArgs(); - var hasFive = args?.Length == 5; + var hasFive = args?.Length >= 5; if (hasFive) { @@ -168,6 +169,7 @@ namespace SafeExamBrowser.Client logLevel = level; runtimeHostUri = args[3]; startupToken = token; + uiMode = args.Length == 6 && Enum.TryParse(args[5], out uiMode) ? uiMode : UserInterfaceMode.Desktop; return; } @@ -286,6 +288,50 @@ namespace SafeExamBrowser.Client return new WindowMonitorOperation(configuration.Settings.KioskMode, logger, windowMonitor); } + private IActionCenter BuildActionCenter() + { + switch (uiMode) + { + case UserInterfaceMode.Mobile: + return new UserInterface.Mobile.ActionCenter(); + default: + return new UserInterface.Desktop.ActionCenter(); + } + } + + private IMessageBox BuildMessageBox() + { + switch (uiMode) + { + case UserInterfaceMode.Mobile: + return new UserInterface.Mobile.MessageBox(text); + default: + return new UserInterface.Desktop.MessageBox(text); + } + } + + private ITaskbar BuildTaskbar() + { + switch (uiMode) + { + case UserInterfaceMode.Mobile: + return new UserInterface.Mobile.Taskbar(new ModuleLogger(logger, nameof(UserInterface.Mobile.Taskbar))); + default: + return new UserInterface.Desktop.Taskbar(new ModuleLogger(logger, nameof(UserInterface.Desktop.Taskbar))); + } + } + + private IUserInterfaceFactory BuildUserInterfaceFactory() + { + switch (uiMode) + { + case UserInterfaceMode.Mobile: + return new UserInterface.Mobile.UserInterfaceFactory(text); + default: + return new UserInterface.Desktop.UserInterfaceFactory(text); + } + } + private void UpdateAppConfig() { ClientController.AppConfig = configuration.AppConfig; diff --git a/SafeExamBrowser.Client/SafeExamBrowser.Client.csproj b/SafeExamBrowser.Client/SafeExamBrowser.Client.csproj index 23c1b104..fc59b7b9 100644 --- a/SafeExamBrowser.Client/SafeExamBrowser.Client.csproj +++ b/SafeExamBrowser.Client/SafeExamBrowser.Client.csproj @@ -160,6 +160,10 @@ {A502DF54-7169-4647-94BD-18B192924866} SafeExamBrowser.UserInterface.Desktop + + {89bc24dd-ff31-496e-9816-a160b686a3d4} + SafeExamBrowser.UserInterface.Mobile + {73724659-4150-4792-A94E-42F5F3C1B696} SafeExamBrowser.WindowsApi diff --git a/SafeExamBrowser.Configuration/ConfigurationData/DataValues.cs b/SafeExamBrowser.Configuration/ConfigurationData/DataValues.cs index a9a3b9ec..43d5ad77 100644 --- a/SafeExamBrowser.Configuration/ConfigurationData/DataValues.cs +++ b/SafeExamBrowser.Configuration/ConfigurationData/DataValues.cs @@ -148,6 +148,8 @@ namespace SafeExamBrowser.Configuration.ConfigurationData settings.Taskbar.EnableTaskbar = true; settings.Taskbar.ShowClock = true; + settings.UserInterfaceMode = UserInterfaceMode.Desktop; + // TODO: Default values for testing of alpha version only, remove for final release! settings.ActionCenter.ShowApplicationLog = true; settings.ActionCenter.ShowKeyboardLayout = true; diff --git a/SafeExamBrowser.Runtime/Operations/ClientOperation.cs b/SafeExamBrowser.Runtime/Operations/ClientOperation.cs index ff46d4d0..a56921e8 100644 --- a/SafeExamBrowser.Runtime/Operations/ClientOperation.cs +++ b/SafeExamBrowser.Runtime/Operations/ClientOperation.cs @@ -105,11 +105,12 @@ namespace SafeExamBrowser.Runtime.Operations var clientLogLevel = Context.Next.Settings.LogLevel.ToString(); var runtimeHostUri = Context.Next.AppConfig.RuntimeAddress; var startupToken = Context.Next.StartupToken.ToString("D"); + var uiMode = Context.Next.Settings.UserInterfaceMode.ToString(); logger.Info("Starting new client process..."); runtimeHost.AllowConnection = true; runtimeHost.ClientReady += clientReadyEventHandler; - ClientProcess = processFactory.StartNew(clientExecutable, clientLogFile, clientLogLevel, runtimeHostUri, startupToken); + ClientProcess = processFactory.StartNew(clientExecutable, clientLogFile, clientLogLevel, runtimeHostUri, startupToken, uiMode); logger.Info("Waiting for client to complete initialization..."); clientReady = clientReadyEvent.WaitOne(timeout_ms); diff --git a/SafeExamBrowser.UserInterface.Desktop/Controls/ActionCenterApplicationControl.xaml.cs b/SafeExamBrowser.UserInterface.Desktop/Controls/ActionCenterApplicationControl.xaml.cs index 25526b32..d4c577c9 100644 --- a/SafeExamBrowser.UserInterface.Desktop/Controls/ActionCenterApplicationControl.xaml.cs +++ b/SafeExamBrowser.UserInterface.Desktop/Controls/ActionCenterApplicationControl.xaml.cs @@ -11,7 +11,6 @@ using System.Windows.Controls; using SafeExamBrowser.Contracts.Applications; using SafeExamBrowser.Contracts.UserInterface.Shell; using SafeExamBrowser.Contracts.UserInterface.Shell.Events; -using SafeExamBrowser.UserInterface.Desktop.Utilities; namespace SafeExamBrowser.UserInterface.Desktop.Controls { diff --git a/SafeExamBrowser.UserInterface.Desktop/Images/Chromium.ico b/SafeExamBrowser.UserInterface.Desktop/Images/Chromium.ico deleted file mode 100644 index 46025b80..00000000 Binary files a/SafeExamBrowser.UserInterface.Desktop/Images/Chromium.ico and /dev/null differ diff --git a/SafeExamBrowser.UserInterface.Desktop/Properties/Resources.Designer.cs b/SafeExamBrowser.UserInterface.Desktop/Properties/Resources.Designer.cs deleted file mode 100644 index fd280764..00000000 --- a/SafeExamBrowser.UserInterface.Desktop/Properties/Resources.Designer.cs +++ /dev/null @@ -1,63 +0,0 @@ -//------------------------------------------------------------------------------ -// -// 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. -// -//------------------------------------------------------------------------------ - -namespace SafeExamBrowser.UserInterface.Desktop.Properties { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // 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()] - internal class Resources { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("SafeExamBrowser.UserInterface.Desktop.Properties.Resources", typeof(Resources).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - } -} diff --git a/SafeExamBrowser.UserInterface.Desktop/Properties/Resources.resx b/SafeExamBrowser.UserInterface.Desktop/Properties/Resources.resx deleted file mode 100644 index af7dbebb..00000000 --- a/SafeExamBrowser.UserInterface.Desktop/Properties/Resources.resx +++ /dev/null @@ -1,117 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - \ No newline at end of file diff --git a/SafeExamBrowser.UserInterface.Desktop/Properties/Settings.Designer.cs b/SafeExamBrowser.UserInterface.Desktop/Properties/Settings.Designer.cs deleted file mode 100644 index a6f031d0..00000000 --- a/SafeExamBrowser.UserInterface.Desktop/Properties/Settings.Designer.cs +++ /dev/null @@ -1,26 +0,0 @@ -//------------------------------------------------------------------------------ -// -// 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. -// -//------------------------------------------------------------------------------ - -namespace SafeExamBrowser.UserInterface.Desktop.Properties { - - - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.9.0.0")] - internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { - - private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); - - public static Settings Default { - get { - return defaultInstance; - } - } - } -} diff --git a/SafeExamBrowser.UserInterface.Desktop/Properties/Settings.settings b/SafeExamBrowser.UserInterface.Desktop/Properties/Settings.settings deleted file mode 100644 index 033d7a5e..00000000 --- a/SafeExamBrowser.UserInterface.Desktop/Properties/Settings.settings +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/SafeExamBrowser.UserInterface.Desktop/SafeExamBrowser.UserInterface.Desktop.csproj b/SafeExamBrowser.UserInterface.Desktop/SafeExamBrowser.UserInterface.Desktop.csproj index 2d223ea0..aea4f425 100644 --- a/SafeExamBrowser.UserInterface.Desktop/SafeExamBrowser.UserInterface.Desktop.csproj +++ b/SafeExamBrowser.UserInterface.Desktop/SafeExamBrowser.UserInterface.Desktop.csproj @@ -364,27 +364,9 @@ Code - - True - True - Resources.resx - - - True - Settings.settings - True - - - ResXFileCodeGenerator - Resources.Designer.cs - Designer - - SettingsSingleFileGenerator - Settings.Designer.cs - @@ -392,9 +374,6 @@ SafeExamBrowser.Contracts - - - diff --git a/SafeExamBrowser.UserInterface.Mobile/AboutWindow.xaml b/SafeExamBrowser.UserInterface.Mobile/AboutWindow.xaml new file mode 100644 index 00000000..88cccf7e --- /dev/null +++ b/SafeExamBrowser.UserInterface.Mobile/AboutWindow.xaml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + This application is subject to the terms of the Mozilla Public License, version 2.0. If a copy of the MPL was not + distributed with this application, you can obtain one at http://mozilla.org/MPL/2.0/. + + + CefSharp (.NET bindings for the Chromium Embedded Framework) + + Copyright © 2010-2019 The CefSharp Authors. All rights reserved. + + + CEF (Chromium Embedded Framework) + + Copyright © 2008-2019 Marshall A. Greenblatt. Portions Copyright © 2006-2009 Google Inc. All rights reserved. + + + + diff --git a/SafeExamBrowser.UserInterface.Mobile/AboutWindow.xaml.cs b/SafeExamBrowser.UserInterface.Mobile/AboutWindow.xaml.cs new file mode 100644 index 00000000..bc3f2420 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Mobile/AboutWindow.xaml.cs @@ -0,0 +1,53 @@ +/* + * 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.Windows; +using System.Windows.Documents; +using SafeExamBrowser.Contracts.Configuration; +using SafeExamBrowser.Contracts.I18n; +using SafeExamBrowser.Contracts.UserInterface.Windows; +using SafeExamBrowser.Contracts.UserInterface.Windows.Events; + +namespace SafeExamBrowser.UserInterface.Mobile +{ + public partial class AboutWindow : Window, IWindow + { + private AppConfig appConfig; + private IText text; + private WindowClosingEventHandler closing; + + event WindowClosingEventHandler IWindow.Closing + { + add { closing += value; } + remove { closing -= value; } + } + + public AboutWindow(AppConfig appConfig, IText text) + { + this.appConfig = appConfig; + this.text = text; + + InitializeComponent(); + InitializeAboutWindow(); + } + + public void BringToForeground() + { + Activate(); + } + + private void InitializeAboutWindow() + { + Closing += (o, args) => closing?.Invoke(); + VersionInfo.Inlines.Add(new Run($"{text.Get(TextKey.Version)} {appConfig.ProgramVersion}") { FontStyle = FontStyles.Italic }); + VersionInfo.Inlines.Add(new LineBreak()); + VersionInfo.Inlines.Add(new LineBreak()); + VersionInfo.Inlines.Add(new Run(appConfig.ProgramCopyright) { FontSize = 10 }); + } + } +} diff --git a/SafeExamBrowser.UserInterface.Mobile/ActionCenter.xaml b/SafeExamBrowser.UserInterface.Mobile/ActionCenter.xaml new file mode 100644 index 00000000..83088909 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Mobile/ActionCenter.xaml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/SafeExamBrowser.UserInterface.Mobile/ActionCenter.xaml.cs b/SafeExamBrowser.UserInterface.Mobile/ActionCenter.xaml.cs new file mode 100644 index 00000000..df54e78a --- /dev/null +++ b/SafeExamBrowser.UserInterface.Mobile/ActionCenter.xaml.cs @@ -0,0 +1,195 @@ +/* + * 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.Windows; +using System.Windows.Media.Animation; +using SafeExamBrowser.Contracts.I18n; +using SafeExamBrowser.Contracts.UserInterface.Shell; +using SafeExamBrowser.Contracts.UserInterface.Shell.Events; + +namespace SafeExamBrowser.UserInterface.Mobile +{ + public partial class ActionCenter : Window, IActionCenter + { + public bool ShowClock + { + set { Dispatcher.Invoke(() => Clock.Visibility = value ? Visibility.Visible : Visibility.Collapsed); } + } + + public event QuitButtonClickedEventHandler QuitButtonClicked; + + public ActionCenter() + { + InitializeComponent(); + InitializeActionCenter(); + } + + public void AddApplicationControl(IApplicationControl control) + { + if (control is UIElement uiElement) + { + ApplicationPanel.Children.Add(uiElement); + } + } + + public void AddNotificationControl(INotificationControl control) + { + if (control is UIElement uiElement) + { + ControlPanel.Children.Insert(ControlPanel.Children.Count - 2, uiElement); + } + } + + public void AddSystemControl(ISystemControl control) + { + if (control is UIElement uiElement) + { + ControlPanel.Children.Insert(ControlPanel.Children.Count - 2, uiElement); + } + } + + public new void Close() + { + Dispatcher.Invoke(base.Close); + } + + public new void Hide() + { + Dispatcher.Invoke(HideAnimated); + } + + public void InitializeBounds() + { + Dispatcher.Invoke(() => + { + Height = SystemParameters.WorkArea.Height; + Top = 0; + Left = -Width; + }); + } + + public void InitializeText(IText text) + { + QuitButton.ToolTip = text.Get(TextKey.Shell_QuitButton); + QuitButton.Text.Text = text.Get(TextKey.Shell_QuitButton); + } + + public void Register(IActionCenterActivator activator) + { + activator.Activate += Activator_Activate; + activator.Deactivate += Activator_Deactivate; + activator.Toggle += Activator_Toggle; + } + + public new void Show() + { + Dispatcher.Invoke(ShowAnimated); + } + + private void HideAnimated() + { + var storyboard = new Storyboard(); + var animation = new DoubleAnimation + { + EasingFunction = new CircleEase { EasingMode = EasingMode.EaseOut }, + From = 0, + To = -Width, + Duration = new Duration(TimeSpan.FromMilliseconds(500)) + }; + + Storyboard.SetTarget(animation, this); + Storyboard.SetTargetProperty(animation, new PropertyPath(LeftProperty)); + + storyboard.Children.Add(animation); + storyboard.Completed += HideAnimation_Completed; + storyboard.Begin(); + } + + private void ShowAnimated() + { + var storyboard = new Storyboard(); + var animation = new DoubleAnimation + { + EasingFunction = new CircleEase { EasingMode = EasingMode.EaseOut }, + From = -Width, + To = 0, + Duration = new Duration(TimeSpan.FromMilliseconds(500)) + }; + + Storyboard.SetTarget(animation, this); + Storyboard.SetTargetProperty(animation, new PropertyPath(LeftProperty)); + + InitializeBounds(); + base.Show(); + + storyboard.Children.Add(animation); + storyboard.Completed += ShowAnimation_Completed; + storyboard.Begin(); + } + + private void ShowAnimation_Completed(object sender, EventArgs e) + { + Activate(); + Deactivated += ActionCenter_Deactivated; + } + + private void HideAnimation_Completed(object sender, EventArgs e) + { + Deactivated -= ActionCenter_Deactivated; + base.Hide(); + } + + private void ActionCenter_Deactivated(object sender, EventArgs e) + { + HideAnimated(); + } + + private void Activator_Activate() + { + Dispatcher.InvokeAsync(() => + { + if (Visibility != Visibility.Visible) + { + ShowAnimated(); + } + }); + } + + private void Activator_Deactivate() + { + Dispatcher.InvokeAsync(() => + { + if (Visibility == Visibility.Visible) + { + HideAnimated(); + } + }); + } + + private void Activator_Toggle() + { + Dispatcher.InvokeAsync(() => + { + if (Visibility != Visibility.Visible) + { + ShowAnimated(); + } + else + { + HideAnimated(); + } + }); + } + + private void InitializeActionCenter() + { + QuitButton.Clicked += (args) => QuitButtonClicked?.Invoke(args); + } + } +} diff --git a/SafeExamBrowser.UserInterface.Mobile/BrowserWindow.xaml b/SafeExamBrowser.UserInterface.Mobile/BrowserWindow.xaml new file mode 100644 index 00000000..bdc6ece4 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Mobile/BrowserWindow.xaml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SafeExamBrowser.UserInterface.Mobile/BrowserWindow.xaml.cs b/SafeExamBrowser.UserInterface.Mobile/BrowserWindow.xaml.cs new file mode 100644 index 00000000..812e4240 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Mobile/BrowserWindow.xaml.cs @@ -0,0 +1,282 @@ +/* + * 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.ComponentModel; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls.Primitives; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using SafeExamBrowser.Contracts.Configuration.Settings; +using SafeExamBrowser.Contracts.Core; +using SafeExamBrowser.Contracts.I18n; +using SafeExamBrowser.Contracts.UserInterface; +using SafeExamBrowser.Contracts.UserInterface.Browser; +using SafeExamBrowser.Contracts.UserInterface.Browser.Events; +using SafeExamBrowser.Contracts.UserInterface.Windows; +using SafeExamBrowser.Contracts.UserInterface.Windows.Events; +using SafeExamBrowser.UserInterface.Mobile.Utilities; + +namespace SafeExamBrowser.UserInterface.Mobile +{ + public partial class BrowserWindow : Window, IBrowserWindow + { + private bool isMainWindow; + private BrowserSettings settings; + private IText text; + private WindowClosingEventHandler closing; + + private BrowserWindowSettings WindowSettings + { + get { return isMainWindow ? settings.MainWindowSettings : settings.AdditionalWindowSettings; } + } + + public bool CanNavigateBackwards { set => Dispatcher.Invoke(() => BackwardButton.IsEnabled = value); } + public bool CanNavigateForwards { set => Dispatcher.Invoke(() => ForwardButton.IsEnabled = value); } + + public event AddressChangedEventHandler AddressChanged; + public event ActionRequestedEventHandler BackwardNavigationRequested; + public event ActionRequestedEventHandler ForwardNavigationRequested; + public event ActionRequestedEventHandler ReloadRequested; + public event ActionRequestedEventHandler ZoomInRequested; + public event ActionRequestedEventHandler ZoomOutRequested; + public event ActionRequestedEventHandler ZoomResetRequested; + + event WindowClosingEventHandler IWindow.Closing + { + add { closing += value; } + remove { closing -= value; } + } + + public BrowserWindow(IBrowserControl browserControl, BrowserSettings settings, bool isMainWindow, IText text) + { + this.isMainWindow = isMainWindow; + this.settings = settings; + this.text = text; + + InitializeComponent(); + InitializeBrowserWindow(browserControl); + } + + public void BringToForeground() + { + Dispatcher.Invoke(() => + { + if (WindowState == WindowState.Minimized) + { + WindowState = WindowState.Normal; + } + + Activate(); + }); + } + + public new void Close() + { + Dispatcher.Invoke(() => + { + Closing -= BrowserWindow_Closing; + closing?.Invoke(); + base.Close(); + }); + } + + public new void Hide() + { + Dispatcher.Invoke(base.Hide); + } + + public new void Show() + { + Dispatcher.Invoke(base.Show); + } + + public void UpdateAddress(string url) + { + Dispatcher.Invoke(() => UrlTextBox.Text = url); + } + + public void UpdateIcon(IIconResource icon) + { + Dispatcher.InvokeAsync(() => Icon = new BitmapImage(icon.Uri)); + } + + public void UpdateLoadingState(bool isLoading) + { + Dispatcher.Invoke(() => + { + LoadingIcon.Visibility = isLoading ? Visibility.Visible : Visibility.Collapsed; + LoadingIcon.Spin = isLoading; + }); + } + + public void UpdateTitle(string title) + { + Dispatcher.Invoke(() => Title = title); + } + + private void BrowserWindow_Closing(object sender, CancelEventArgs e) + { + if (isMainWindow) + { + e.Cancel = true; + } + else + { + closing?.Invoke(); + } + } + + private void BrowserWindow_KeyUp(object sender, KeyEventArgs e) + { + if (e.Key == Key.F5) + { + ReloadRequested?.Invoke(); + } + } + + private CustomPopupPlacement[] MenuPopup_PlacementCallback(Size popupSize, Size targetSize, Point offset) + { + return new[] + { + new CustomPopupPlacement(new Point(targetSize.Width - Toolbar.Margin.Right - popupSize.Width, -2), PopupPrimaryAxis.None) + }; + } + + private void SystemParameters_StaticPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(SystemParameters.WorkArea)) + { + Dispatcher.InvokeAsync(InitializeBounds); + } + } + + private void UrlTextBox_GotMouseCapture(object sender, MouseEventArgs e) + { + if (UrlTextBox.Tag as bool? != true) + { + UrlTextBox.SelectAll(); + UrlTextBox.Tag = true; + } + } + + private void UrlTextBox_KeyUp(object sender, KeyEventArgs e) + { + if (e.Key == Key.Enter) + { + AddressChanged?.Invoke(UrlTextBox.Text); + } + } + + private void InitializeBrowserWindow(IBrowserControl browserControl) + { + if (browserControl is System.Windows.Forms.Control control) + { + BrowserControlHost.Child = control; + } + + RegisterEvents(); + InitializeBounds(); + ApplySettings(); + LoadIcons(); + LoadText(); + } + + private void RegisterEvents() + { + var originalBrush = MenuButton.Background; + + BackwardButton.Click += (o, args) => BackwardNavigationRequested?.Invoke(); + Closing += BrowserWindow_Closing; + ForwardButton.Click += (o, args) => ForwardNavigationRequested?.Invoke(); + MenuButton.Click += (o, args) => MenuPopup.IsOpen = !MenuPopup.IsOpen; + MenuButton.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => MenuPopup.IsOpen = MenuPopup.IsMouseOver)); + MenuPopup.CustomPopupPlacementCallback = new CustomPopupPlacementCallback(MenuPopup_PlacementCallback); + MenuPopup.Closed += (o, args) => MenuButton.Background = originalBrush; + MenuPopup.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => MenuPopup.IsOpen = IsMouseOver)); + MenuPopup.Opened += (o, args) => MenuButton.Background = Brushes.LightGray; + KeyUp += BrowserWindow_KeyUp; + ReloadButton.Click += (o, args) => ReloadRequested?.Invoke(); + SystemParameters.StaticPropertyChanged += SystemParameters_StaticPropertyChanged; + UrlTextBox.GotKeyboardFocus += (o, args) => UrlTextBox.SelectAll(); + UrlTextBox.GotMouseCapture += UrlTextBox_GotMouseCapture; + UrlTextBox.LostKeyboardFocus += (o, args) => UrlTextBox.Tag = null; + UrlTextBox.LostFocus += (o, args) => UrlTextBox.Tag = null; + UrlTextBox.KeyUp += UrlTextBox_KeyUp; + UrlTextBox.MouseDoubleClick += (o, args) => UrlTextBox.SelectAll(); + ZoomInButton.Click += (o, args) => ZoomInRequested?.Invoke(); + ZoomOutButton.Click += (o, args) => ZoomOutRequested?.Invoke(); + ZoomResetButton.Click += (o, args) => ZoomResetRequested?.Invoke(); + } + + private void ApplySettings() + { + UrlTextBox.IsEnabled = WindowSettings.AllowAddressBar; + + ReloadButton.IsEnabled = WindowSettings.AllowReloading; + ReloadButton.Visibility = WindowSettings.AllowReloading ? Visibility.Visible : Visibility.Collapsed; + + BackwardButton.IsEnabled = WindowSettings.AllowBackwardNavigation; + BackwardButton.Visibility = WindowSettings.AllowBackwardNavigation ? Visibility.Visible : Visibility.Collapsed; + + ForwardButton.IsEnabled = WindowSettings.AllowForwardNavigation; + ForwardButton.Visibility = WindowSettings.AllowForwardNavigation ? Visibility.Visible : Visibility.Collapsed; + } + + private void InitializeBounds() + { + if (isMainWindow) + { + if (WindowSettings.FullScreenMode) + { + Top = 0; + Left = 0; + Height = SystemParameters.WorkArea.Height; + Width = SystemParameters.WorkArea.Width; + ResizeMode = ResizeMode.NoResize; + WindowStyle = WindowStyle.None; + } + else + { + WindowState = WindowState.Maximized; + } + } + else + { + Top = 0; + Left = SystemParameters.WorkArea.Width / 2; + Height = SystemParameters.WorkArea.Height; + Width = SystemParameters.WorkArea.Width / 2; + } + } + + private void LoadIcons() + { + var backUri = new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/NavigateBack.xaml"); + var forwardUri = new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/NavigateForward.xaml"); + var menuUri = new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/Menu.xaml"); + var reloadUri = new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/Reload.xaml"); + var backward = new XamlIconResource(backUri); + var forward = new XamlIconResource(forwardUri); + var menu = new XamlIconResource(menuUri); + var reload = new XamlIconResource(reloadUri); + + BackwardButton.Content = IconResourceLoader.Load(backward); + ForwardButton.Content = IconResourceLoader.Load(forward); + MenuButton.Content = IconResourceLoader.Load(menu); + ReloadButton.Content = IconResourceLoader.Load(reload); + } + + private void LoadText() + { + ZoomText.Text = text.Get(TextKey.BrowserWindow_ZoomMenuItem); + } + } +} diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterApplicationButton.xaml b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterApplicationButton.xaml new file mode 100644 index 00000000..00092feb --- /dev/null +++ b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterApplicationButton.xaml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterApplicationButton.xaml.cs b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterApplicationButton.xaml.cs new file mode 100644 index 00000000..044d4de7 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterApplicationButton.xaml.cs @@ -0,0 +1,66 @@ +/* + * 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.Windows; +using System.Windows.Controls; +using SafeExamBrowser.Contracts.Applications; +using SafeExamBrowser.Contracts.Core; +using SafeExamBrowser.Contracts.UserInterface.Shell.Events; +using SafeExamBrowser.UserInterface.Mobile.Utilities; + +namespace SafeExamBrowser.UserInterface.Mobile.Controls +{ + public partial class ActionCenterApplicationButton : UserControl + { + private IApplicationInfo info; + private IApplicationInstance instance; + + internal event ApplicationControlClickedEventHandler Clicked; + + public ActionCenterApplicationButton(IApplicationInfo info, IApplicationInstance instance = null) + { + this.info = info; + this.instance = instance; + + InitializeComponent(); + InitializeApplicationInstanceButton(); + } + + private void InitializeApplicationInstanceButton() + { + Icon.Content = IconResourceLoader.Load(info.IconResource); + Text.Text = instance?.Name ?? info.Name; + Button.ToolTip = instance?.Name ?? info.Tooltip; + + if (instance != null) + { + instance.IconChanged += Instance_IconChanged; + instance.NameChanged += Instance_NameChanged; + } + } + + private void Instance_IconChanged(IIconResource icon) + { + Dispatcher.InvokeAsync(() => Icon.Content = IconResourceLoader.Load(icon)); + } + + private void Instance_NameChanged(string name) + { + Dispatcher.Invoke(() => + { + Text.Text = name; + Button.ToolTip = name; + }); + } + + private void Button_Click(object sender, RoutedEventArgs e) + { + Clicked?.Invoke(instance?.Id); + } + } +} diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterApplicationControl.xaml b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterApplicationControl.xaml new file mode 100644 index 00000000..0ffea172 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterApplicationControl.xaml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterApplicationControl.xaml.cs b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterApplicationControl.xaml.cs new file mode 100644 index 00000000..16a82641 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterApplicationControl.xaml.cs @@ -0,0 +1,69 @@ +/* + * 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.Windows; +using System.Windows.Controls; +using SafeExamBrowser.Contracts.Applications; +using SafeExamBrowser.Contracts.UserInterface.Shell; +using SafeExamBrowser.Contracts.UserInterface.Shell.Events; + +namespace SafeExamBrowser.UserInterface.Mobile.Controls +{ + public partial class ActionCenterApplicationControl : UserControl, IApplicationControl + { + private IApplicationInfo info; + + public event ApplicationControlClickedEventHandler Clicked; + + public ActionCenterApplicationControl(IApplicationInfo info) + { + this.info = info; + + InitializeComponent(); + InitializeApplicationControl(info); + } + + public void RegisterInstance(IApplicationInstance instance) + { + Dispatcher.Invoke(() => + { + var button = new ActionCenterApplicationButton(info, instance); + + button.Clicked += (id) => Clicked?.Invoke(id); + instance.Terminated += (id) => Instance_OnTerminated(id, button); + InstancePanel.Children.Add(button); + + ApplicationName.Visibility = Visibility.Visible; + ApplicationButton.Visibility = Visibility.Collapsed; + }); + } + + private void InitializeApplicationControl(IApplicationInfo info) + { + var button = new ActionCenterApplicationButton(info); + + button.Button.Click += (o, args) => Clicked?.Invoke(); + ApplicationName.Text = info.Name; + ApplicationButton.Content = button; + } + + private void Instance_OnTerminated(InstanceIdentifier id, ActionCenterApplicationButton button) + { + Dispatcher.InvokeAsync(() => + { + InstancePanel.Children.Remove(button); + + if (InstancePanel.Children.Count == 0) + { + ApplicationName.Visibility = Visibility.Collapsed; + ApplicationButton.Visibility = Visibility.Visible; + } + }); + } + } +} diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterClock.xaml b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterClock.xaml new file mode 100644 index 00000000..a881ffff --- /dev/null +++ b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterClock.xaml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterClock.xaml.cs b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterClock.xaml.cs new file mode 100644 index 00000000..adc709c1 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterClock.xaml.cs @@ -0,0 +1,32 @@ +/* + * 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.Windows.Controls; +using SafeExamBrowser.UserInterface.Mobile.ViewModels; + +namespace SafeExamBrowser.UserInterface.Mobile.Controls +{ + public partial class ActionCenterClock : UserControl + { + private DateTimeViewModel model; + + public ActionCenterClock() + { + InitializeComponent(); + InitializeControl(); + } + + private void InitializeControl() + { + model = new DateTimeViewModel(true); + DataContext = model; + TimeTextBlock.DataContext = model; + DateTextBlock.DataContext = model; + } + } +} diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterKeyboardLayoutButton.xaml b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterKeyboardLayoutButton.xaml new file mode 100644 index 00000000..58d50726 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterKeyboardLayoutButton.xaml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterKeyboardLayoutButton.xaml.cs b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterKeyboardLayoutButton.xaml.cs new file mode 100644 index 00000000..7777afbc --- /dev/null +++ b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterKeyboardLayoutButton.xaml.cs @@ -0,0 +1,56 @@ +/* + * 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.Windows; +using System.Windows.Controls; +using SafeExamBrowser.Contracts.SystemComponents; +using SafeExamBrowser.Contracts.UserInterface.Shell.Events; + +namespace SafeExamBrowser.UserInterface.Mobile.Controls +{ + public partial class ActionCenterKeyboardLayoutButton : UserControl + { + private IKeyboardLayout layout; + + public event KeyboardLayoutSelectedEventHandler LayoutSelected; + + public string CultureCode + { + set { CultureCodeTextBlock.Text = value; } + } + + public bool IsCurrent + { + set { IsCurrentTextBlock.Visibility = value ? Visibility.Visible : Visibility.Hidden; } + } + + public string LayoutName + { + set { LayoutNameTextBlock.Text = value; } + } + + public Guid LayoutId + { + get { return layout.Id; } + } + + public ActionCenterKeyboardLayoutButton(IKeyboardLayout layout) + { + this.layout = layout; + + InitializeComponent(); + InitializeEvents(); + } + + private void InitializeEvents() + { + Button.Click += (o, args) => LayoutSelected?.Invoke(layout.Id); + } + } +} diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterKeyboardLayoutControl.xaml b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterKeyboardLayoutControl.xaml new file mode 100644 index 00000000..d2afcb4a --- /dev/null +++ b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterKeyboardLayoutControl.xaml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterKeyboardLayoutControl.xaml.cs b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterKeyboardLayoutControl.xaml.cs new file mode 100644 index 00000000..1807cc17 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterKeyboardLayoutControl.xaml.cs @@ -0,0 +1,86 @@ +/* + * 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.Controls; +using System.Windows.Media; +using SafeExamBrowser.Contracts.SystemComponents; +using SafeExamBrowser.Contracts.UserInterface.Shell; +using SafeExamBrowser.Contracts.UserInterface.Shell.Events; + +namespace SafeExamBrowser.UserInterface.Mobile.Controls +{ + public partial class ActionCenterKeyboardLayoutControl : UserControl, ISystemKeyboardLayoutControl + { + public event KeyboardLayoutSelectedEventHandler LayoutSelected; + + public ActionCenterKeyboardLayoutControl() + { + InitializeComponent(); + InitializeKeyboardLayoutControl(); + } + + public void Add(IKeyboardLayout layout) + { + Dispatcher.Invoke(() => + { + var button = new ActionCenterKeyboardLayoutButton(layout); + + button.LayoutSelected += Button_LayoutSelected; + button.CultureCode = layout.CultureCode; + button.LayoutName = layout.Name; + + LayoutsStackPanel.Children.Add(button); + }); + } + + public void Close() + { + Dispatcher.Invoke(() => Popup.IsOpen = false); + } + + public void SetCurrent(IKeyboardLayout layout) + { + Dispatcher.Invoke(() => + { + foreach (var child in LayoutsStackPanel.Children) + { + if (child is ActionCenterKeyboardLayoutButton layoutButton) + { + layoutButton.IsCurrent = layout.Id == layoutButton.LayoutId; + } + } + + Text.Text = layout.Name; + }); + } + + public void SetInformation(string text) + { + Dispatcher.Invoke(() => Button.ToolTip = text); + } + + private void InitializeKeyboardLayoutControl() + { + 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)); + 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; + } + + private void Button_LayoutSelected(Guid id) + { + Popup.IsOpen = false; + LayoutSelected?.Invoke(id); + } + } +} diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterNotificationButton.xaml b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterNotificationButton.xaml new file mode 100644 index 00000000..578d84a0 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterNotificationButton.xaml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterNotificationButton.xaml.cs b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterNotificationButton.xaml.cs new file mode 100644 index 00000000..8f7ec048 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterNotificationButton.xaml.cs @@ -0,0 +1,40 @@ +/* + * 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.Windows; +using System.Windows.Controls; +using SafeExamBrowser.Contracts.Client; +using SafeExamBrowser.Contracts.UserInterface.Shell; +using SafeExamBrowser.Contracts.UserInterface.Shell.Events; +using SafeExamBrowser.UserInterface.Mobile.Utilities; + +namespace SafeExamBrowser.UserInterface.Mobile.Controls +{ + public partial class ActionCenterNotificationButton : UserControl, INotificationControl + { + public event NotificationControlClickedEventHandler Clicked; + + public ActionCenterNotificationButton(INotificationInfo info) + { + InitializeComponent(); + InitializeNotificationIcon(info); + } + + private void Icon_Click(object sender, RoutedEventArgs e) + { + Clicked?.Invoke(); + } + + private void InitializeNotificationIcon(INotificationInfo info) + { + Icon.Content = IconResourceLoader.Load(info.IconResource); + IconButton.ToolTip = info.Tooltip; + Text.Text = info.Tooltip; + } + } +} diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterPowerSupplyControl.xaml b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterPowerSupplyControl.xaml new file mode 100644 index 00000000..7c5c6725 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterPowerSupplyControl.xaml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterPowerSupplyControl.xaml.cs b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterPowerSupplyControl.xaml.cs new file mode 100644 index 00000000..5a6c269d --- /dev/null +++ b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterPowerSupplyControl.xaml.cs @@ -0,0 +1,67 @@ +/* + * 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.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using SafeExamBrowser.Contracts.SystemComponents; +using SafeExamBrowser.Contracts.UserInterface.Shell; + +namespace SafeExamBrowser.UserInterface.Mobile.Controls +{ + public partial class ActionCenterPowerSupplyControl : UserControl, ISystemPowerSupplyControl + { + private double BATTERY_CHARGE_MAX_WIDTH; + + public ActionCenterPowerSupplyControl() + { + InitializeComponent(); + BATTERY_CHARGE_MAX_WIDTH = BatteryCharge.Width; + } + + public void Close() + { + } + + public void SetBatteryCharge(double charge, BatteryChargeStatus status) + { + Dispatcher.InvokeAsync(() => + { + var width = BATTERY_CHARGE_MAX_WIDTH * charge; + + width = width > BATTERY_CHARGE_MAX_WIDTH ? BATTERY_CHARGE_MAX_WIDTH : width; + width = width < 0 ? 0 : width; + + BatteryCharge.Width = width; + BatteryCharge.Fill = status == BatteryChargeStatus.Low ? Brushes.Orange : BatteryCharge.Fill; + BatteryCharge.Fill = status == BatteryChargeStatus.Critical ? Brushes.Red : BatteryCharge.Fill; + Warning.Visibility = status == BatteryChargeStatus.Critical ? Visibility.Visible : Visibility.Collapsed; + }); + } + + public void SetPowerGridConnection(bool connected) + { + Dispatcher.InvokeAsync(() => PowerPlug.Visibility = connected ? Visibility.Visible : Visibility.Collapsed); + } + + public void SetInformation(string text) + { + Dispatcher.InvokeAsync(() => Text.Text = text); + } + + public void ShowCriticalBatteryWarning(string warning) + { + Dispatcher.InvokeAsync(() => Button.ToolTip = warning); + } + + public void ShowLowBatteryInfo(string info) + { + Dispatcher.InvokeAsync(() => Button.ToolTip = info); + } + } +} diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterQuitButton.xaml b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterQuitButton.xaml new file mode 100644 index 00000000..9d924722 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterQuitButton.xaml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterQuitButton.xaml.cs b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterQuitButton.xaml.cs new file mode 100644 index 00000000..3ebcc509 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterQuitButton.xaml.cs @@ -0,0 +1,36 @@ +/* + * 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.ComponentModel; +using System.Windows.Controls; +using SafeExamBrowser.Contracts.UserInterface.Shell.Events; +using SafeExamBrowser.UserInterface.Mobile.Utilities; + +namespace SafeExamBrowser.UserInterface.Mobile.Controls +{ + public partial class ActionCenterQuitButton : UserControl + { + public event QuitButtonClickedEventHandler Clicked; + + public ActionCenterQuitButton() + { + InitializeComponent(); + InitializeControl(); + } + + private void InitializeControl() + { + var uri = new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/ShutDown.xaml"); + var resource = new XamlIconResource(uri); + + Icon.Content = IconResourceLoader.Load(resource); + Button.Click += (o, args) => Clicked?.Invoke(new CancelEventArgs()); + } + } +} diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterWirelessNetworkButton.xaml b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterWirelessNetworkButton.xaml new file mode 100644 index 00000000..0d4451ea --- /dev/null +++ b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterWirelessNetworkButton.xaml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterWirelessNetworkButton.xaml.cs b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterWirelessNetworkButton.xaml.cs new file mode 100644 index 00000000..04929d32 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterWirelessNetworkButton.xaml.cs @@ -0,0 +1,50 @@ +/* + * 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.Windows; +using System.Windows.Controls; +using SafeExamBrowser.Contracts.SystemComponents; +using SafeExamBrowser.Contracts.UserInterface.Shell.Events; + +namespace SafeExamBrowser.UserInterface.Mobile.Controls +{ + public partial class ActionCenterWirelessNetworkButton : UserControl + { + private IWirelessNetwork network; + + public bool IsCurrent + { + set { IsCurrentTextBlock.Visibility = value ? Visibility.Visible : Visibility.Hidden; } + } + + public string NetworkName + { + set { NetworkNameTextBlock.Text = value; } + } + + public int SignalStrength + { + set { SignalStrengthTextBlock.Text = $"{value}%"; } + } + + public event WirelessNetworkSelectedEventHandler NetworkSelected; + + public ActionCenterWirelessNetworkButton(IWirelessNetwork network) + { + this.network = network; + + InitializeComponent(); + InitializeEvents(); + } + + private void InitializeEvents() + { + Button.Click += (o, args) => NetworkSelected?.Invoke(network.Id); + } + } +} diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterWirelessNetworkControl.xaml b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterWirelessNetworkControl.xaml new file mode 100644 index 00000000..5bdf5973 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterWirelessNetworkControl.xaml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterWirelessNetworkControl.xaml.cs b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterWirelessNetworkControl.xaml.cs new file mode 100644 index 00000000..38b07766 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterWirelessNetworkControl.xaml.cs @@ -0,0 +1,139 @@ +/* + * 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.Collections.Generic; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using FontAwesome.WPF; +using SafeExamBrowser.Contracts.SystemComponents; +using SafeExamBrowser.Contracts.UserInterface.Shell; +using SafeExamBrowser.Contracts.UserInterface.Shell.Events; +using SafeExamBrowser.UserInterface.Mobile.Utilities; + +namespace SafeExamBrowser.UserInterface.Mobile.Controls +{ + public partial class ActionCenterWirelessNetworkControl : UserControl, ISystemWirelessNetworkControl + { + public bool HasWirelessNetworkAdapter + { + set + { + Dispatcher.Invoke(() => + { + Button.IsEnabled = value; + NoAdapterIcon.Visibility = value ? Visibility.Collapsed : Visibility.Visible; + }); + } + } + + public bool IsConnecting + { + set + { + Dispatcher.Invoke(() => + { + LoadingIcon.Visibility = value ? Visibility.Visible : Visibility.Collapsed; + SignalStrengthIcon.Visibility = value ? Visibility.Collapsed : Visibility.Visible; + NetworkStatusIcon.Visibility = value ? Visibility.Collapsed : Visibility.Visible; + }); + } + } + + public WirelessNetworkStatus NetworkStatus + { + set + { + Dispatcher.Invoke(() => + { + var icon = value == WirelessNetworkStatus.Connected ? FontAwesomeIcon.Check : FontAwesomeIcon.Close; + var brush = value == WirelessNetworkStatus.Connected ? Brushes.Green : Brushes.Orange; + + if (value == WirelessNetworkStatus.Disconnected) + { + SignalStrengthIcon.Child = GetIcon(0); + } + + NetworkStatusIcon.Source = ImageAwesome.CreateImageSource(icon, brush); + }); + } + } + + public event WirelessNetworkSelectedEventHandler NetworkSelected; + + public ActionCenterWirelessNetworkControl() + { + InitializeComponent(); + InitializeWirelessNetworkControl(); + } + + public void Close() + { + Dispatcher.Invoke(() => Popup.IsOpen = false); + } + + public void SetInformation(string text) + { + Dispatcher.Invoke(() => + { + Button.ToolTip = text; + Text.Text = text; + }); + } + + public void Update(IEnumerable networks) + { + Dispatcher.Invoke(() => + { + NetworksStackPanel.Children.Clear(); + + foreach (var network in networks) + { + var button = new ActionCenterWirelessNetworkButton(network); + var isCurrent = network.Status == WirelessNetworkStatus.Connected; + + button.IsCurrent = isCurrent; + button.NetworkName = network.Name; + button.SignalStrength = network.SignalStrength; + button.NetworkSelected += (id) => NetworkSelected?.Invoke(id); + + if (isCurrent) + { + NetworkStatus = network.Status; + SignalStrengthIcon.Child = GetIcon(network.SignalStrength); + } + + NetworksStackPanel.Children.Add(button); + } + }); + } + + private void InitializeWirelessNetworkControl() + { + var originalBrush = Grid.Background; + + SignalStrengthIcon.Child = GetIcon(0); + Button.Click += (o, args) => Popup.IsOpen = !Popup.IsOpen; + Button.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => Popup.IsOpen = Popup.IsMouseOver)); + 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; + } + + private UIElement GetIcon(int signalStrength) + { + var icon = signalStrength > 66 ? "100" : (signalStrength > 33 ? "66" : (signalStrength > 0 ? "33" : "0")); + var uri = new Uri($"pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/WiFi_Light_{icon}.xaml"); + var resource = new XamlIconResource(uri); + + return IconResourceLoader.Load(resource); + } + } +} diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarApplicationControl.xaml b/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarApplicationControl.xaml new file mode 100644 index 00000000..c2eca719 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarApplicationControl.xaml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarApplicationInstanceButton.xaml.cs b/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarApplicationInstanceButton.xaml.cs new file mode 100644 index 00000000..8aab1fee --- /dev/null +++ b/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarApplicationInstanceButton.xaml.cs @@ -0,0 +1,63 @@ +/* + * 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.Windows; +using System.Windows.Controls; +using SafeExamBrowser.Contracts.Applications; +using SafeExamBrowser.Contracts.Core; +using SafeExamBrowser.Contracts.UserInterface.Shell.Events; +using SafeExamBrowser.UserInterface.Mobile.Utilities; + +namespace SafeExamBrowser.UserInterface.Mobile.Controls +{ + public partial class TaskbarApplicationInstanceButton : UserControl + { + private IApplicationInfo info; + private IApplicationInstance instance; + + internal event ApplicationControlClickedEventHandler Clicked; + + public TaskbarApplicationInstanceButton(IApplicationInstance instance, IApplicationInfo info) + { + this.info = info; + this.instance = instance; + + InitializeComponent(); + InitializeApplicationInstanceButton(); + } + + private void InitializeApplicationInstanceButton() + { + Icon.Content = IconResourceLoader.Load(info.IconResource); + Text.Text = instance.Name; + Button.ToolTip = instance.Name; + + instance.IconChanged += Instance_IconChanged; + instance.NameChanged += Instance_NameChanged; + } + + private void Instance_IconChanged(IIconResource icon) + { + Dispatcher.InvokeAsync(() => Icon.Content = IconResourceLoader.Load(icon)); + } + + private void Instance_NameChanged(string name) + { + Dispatcher.Invoke(() => + { + Text.Text = name; + Button.ToolTip = name; + }); + } + + private void Button_Click(object sender, RoutedEventArgs e) + { + Clicked?.Invoke(instance.Id); + } + } +} diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarClock.xaml b/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarClock.xaml new file mode 100644 index 00000000..8569e435 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarClock.xaml @@ -0,0 +1,17 @@ + + + + + + + + + + diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarClock.xaml.cs b/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarClock.xaml.cs new file mode 100644 index 00000000..fca0aa72 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarClock.xaml.cs @@ -0,0 +1,32 @@ +/* + * 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.Windows.Controls; +using SafeExamBrowser.UserInterface.Mobile.ViewModels; + +namespace SafeExamBrowser.UserInterface.Mobile.Controls +{ + public partial class TaskbarClock : UserControl + { + private DateTimeViewModel model; + + public TaskbarClock() + { + InitializeComponent(); + InitializeControl(); + } + + private void InitializeControl() + { + model = new DateTimeViewModel(false); + DataContext = model; + TimeTextBlock.DataContext = model; + DateTextBlock.DataContext = model; + } + } +} diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarKeyboardLayoutButton.xaml b/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarKeyboardLayoutButton.xaml new file mode 100644 index 00000000..c4092845 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarKeyboardLayoutButton.xaml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarKeyboardLayoutButton.xaml.cs b/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarKeyboardLayoutButton.xaml.cs new file mode 100644 index 00000000..0d046da2 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarKeyboardLayoutButton.xaml.cs @@ -0,0 +1,56 @@ +/* + * 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.Windows; +using System.Windows.Controls; +using SafeExamBrowser.Contracts.SystemComponents; +using SafeExamBrowser.Contracts.UserInterface.Shell.Events; + +namespace SafeExamBrowser.UserInterface.Mobile.Controls +{ + public partial class TaskbarKeyboardLayoutButton : UserControl + { + private IKeyboardLayout layout; + + public event KeyboardLayoutSelectedEventHandler LayoutSelected; + + public string CultureCode + { + set { CultureCodeTextBlock.Text = value; } + } + + public bool IsCurrent + { + set { IsCurrentTextBlock.Visibility = value ? Visibility.Visible : Visibility.Hidden; } + } + + public string LayoutName + { + set { LayoutNameTextBlock.Text = value; } + } + + public Guid LayoutId + { + get { return layout.Id; } + } + + public TaskbarKeyboardLayoutButton(IKeyboardLayout layout) + { + this.layout = layout; + + InitializeComponent(); + InitializeEvents(); + } + + private void InitializeEvents() + { + Button.Click += (o, args) => LayoutSelected?.Invoke(layout.Id); + } + } +} diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarKeyboardLayoutControl.xaml b/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarKeyboardLayoutControl.xaml new file mode 100644 index 00000000..7145d747 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarKeyboardLayoutControl.xaml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarKeyboardLayoutControl.xaml.cs b/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarKeyboardLayoutControl.xaml.cs new file mode 100644 index 00000000..30aedb1c --- /dev/null +++ b/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarKeyboardLayoutControl.xaml.cs @@ -0,0 +1,99 @@ +/* + * 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.Linq; +using System.Threading.Tasks; +using System.Windows.Controls; +using System.Windows.Media; +using SafeExamBrowser.Contracts.SystemComponents; +using SafeExamBrowser.Contracts.UserInterface.Shell; +using SafeExamBrowser.Contracts.UserInterface.Shell.Events; + +namespace SafeExamBrowser.UserInterface.Mobile.Controls +{ + public partial class TaskbarKeyboardLayoutControl : UserControl, ISystemKeyboardLayoutControl + { + public event KeyboardLayoutSelectedEventHandler LayoutSelected; + + public TaskbarKeyboardLayoutControl() + { + InitializeComponent(); + InitializeKeyboardLayoutControl(); + } + + public void Add(IKeyboardLayout layout) + { + Dispatcher.Invoke(() => + { + var button = new TaskbarKeyboardLayoutButton(layout); + + button.LayoutSelected += Button_LayoutSelected; + button.CultureCode = layout.CultureCode; + button.LayoutName = layout.Name; + + LayoutsStackPanel.Children.Add(button); + }); + } + + public void Close() + { + Dispatcher.Invoke(() => Popup.IsOpen = false); + } + + public void SetCurrent(IKeyboardLayout layout) + { + Dispatcher.Invoke(() => + { + var name = layout.Name?.Length > 3 ? String.Join(string.Empty, layout.Name.Split(' ').Where(s => Char.IsLetter(s.First())).Select(s => s.First())) : layout.Name; + + foreach (var child in LayoutsStackPanel.Children) + { + if (child is TaskbarKeyboardLayoutButton layoutButton) + { + layoutButton.IsCurrent = layout.Id == layoutButton.LayoutId; + } + } + + LayoutCultureCode.Text = layout.CultureCode; + }); + } + + public void SetInformation(string text) + { + Dispatcher.Invoke(() => Button.ToolTip = text); + } + + private void InitializeKeyboardLayoutControl() + { + 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)); + Popup.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => Popup.IsOpen = IsMouseOver)); + + Popup.Opened += (o, args) => + { + Background = Brushes.LightGray; + Button.Background = Brushes.LightGray; + }; + + Popup.Closed += (o, args) => + { + Background = originalBrush; + Button.Background = originalBrush; + }; + } + + private void Button_LayoutSelected(Guid id) + { + Popup.IsOpen = false; + LayoutSelected?.Invoke(id); + } + } +} diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarNotificationButton.xaml b/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarNotificationButton.xaml new file mode 100644 index 00000000..7486e8b3 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarNotificationButton.xaml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarPowerSupplyControl.xaml.cs b/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarPowerSupplyControl.xaml.cs new file mode 100644 index 00000000..7a564e5f --- /dev/null +++ b/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarPowerSupplyControl.xaml.cs @@ -0,0 +1,82 @@ +/* + * 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.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using System.Windows.Threading; +using SafeExamBrowser.Contracts.SystemComponents; +using SafeExamBrowser.Contracts.UserInterface.Shell; + +namespace SafeExamBrowser.UserInterface.Mobile.Controls +{ + public partial class TaskbarPowerSupplyControl : UserControl, ISystemPowerSupplyControl + { + private double BATTERY_CHARGE_MAX_WIDTH; + + public TaskbarPowerSupplyControl() + { + InitializeComponent(); + BATTERY_CHARGE_MAX_WIDTH = BatteryCharge.Width; + } + + public void Close() + { + Popup.IsOpen = false; + } + + public void SetBatteryCharge(double charge, BatteryChargeStatus status) + { + Dispatcher.InvokeAsync(() => + { + var width = BATTERY_CHARGE_MAX_WIDTH * charge; + + width = width > BATTERY_CHARGE_MAX_WIDTH ? BATTERY_CHARGE_MAX_WIDTH : width; + width = width < 0 ? 0 : width; + + BatteryCharge.Width = width; + BatteryCharge.Fill = status == BatteryChargeStatus.Low ? Brushes.Orange : BatteryCharge.Fill; + BatteryCharge.Fill = status == BatteryChargeStatus.Critical ? Brushes.Red : BatteryCharge.Fill; + Warning.Visibility = status == BatteryChargeStatus.Critical ? Visibility.Visible : Visibility.Collapsed; + }); + } + + public void SetPowerGridConnection(bool connected) + { + Dispatcher.InvokeAsync(() => PowerPlug.Visibility = connected ? Visibility.Visible : Visibility.Collapsed); + } + + public void SetInformation(string text) + { + Dispatcher.InvokeAsync(() => Button.ToolTip = text); + } + + public void ShowCriticalBatteryWarning(string warning) + { + Dispatcher.InvokeAsync(() => ShowPopup(warning)); + } + + public void ShowLowBatteryInfo(string info) + { + Dispatcher.InvokeAsync(() => ShowPopup(info)); + } + + private void ShowPopup(string text) + { + Popup.IsOpen = true; + PopupText.Text = text; + Background = Brushes.LightGray; + } + + private void Button_Click(object sender, RoutedEventArgs e) + { + Popup.IsOpen = false; + Background = Brushes.Transparent; + } + } +} diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarQuitButton.xaml b/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarQuitButton.xaml new file mode 100644 index 00000000..06d89d2e --- /dev/null +++ b/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarQuitButton.xaml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarWirelessNetworkButton.xaml.cs b/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarWirelessNetworkButton.xaml.cs new file mode 100644 index 00000000..40d9da00 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarWirelessNetworkButton.xaml.cs @@ -0,0 +1,50 @@ +/* + * 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.Windows; +using System.Windows.Controls; +using SafeExamBrowser.Contracts.SystemComponents; +using SafeExamBrowser.Contracts.UserInterface.Shell.Events; + +namespace SafeExamBrowser.UserInterface.Mobile.Controls +{ + public partial class TaskbarWirelessNetworkButton : UserControl + { + private readonly IWirelessNetwork network; + + public bool IsCurrent + { + set { IsCurrentTextBlock.Visibility = value ? Visibility.Visible : Visibility.Hidden; } + } + + public string NetworkName + { + set { NetworkNameTextBlock.Text = value; } + } + + public int SignalStrength + { + set { SignalStrengthTextBlock.Text = $"{value}%"; } + } + + public event WirelessNetworkSelectedEventHandler NetworkSelected; + + public TaskbarWirelessNetworkButton(IWirelessNetwork network) + { + this.network = network; + + InitializeComponent(); + InitializeEvents(); + } + + private void InitializeEvents() + { + Button.Click += (o, args) => NetworkSelected?.Invoke(network.Id); + } + } +} diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarWirelessNetworkControl.xaml b/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarWirelessNetworkControl.xaml new file mode 100644 index 00000000..402b5750 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarWirelessNetworkControl.xaml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarWirelessNetworkControl.xaml.cs b/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarWirelessNetworkControl.xaml.cs new file mode 100644 index 00000000..4042bac0 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarWirelessNetworkControl.xaml.cs @@ -0,0 +1,145 @@ +/* + * 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.Collections.Generic; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using FontAwesome.WPF; +using SafeExamBrowser.Contracts.SystemComponents; +using SafeExamBrowser.Contracts.UserInterface.Shell; +using SafeExamBrowser.Contracts.UserInterface.Shell.Events; +using SafeExamBrowser.UserInterface.Mobile.Utilities; + +namespace SafeExamBrowser.UserInterface.Mobile.Controls +{ + public partial class TaskbarWirelessNetworkControl : UserControl, ISystemWirelessNetworkControl + { + public bool HasWirelessNetworkAdapter + { + set + { + Dispatcher.InvokeAsync(() => + { + Button.IsEnabled = value; + NoAdapterIcon.Visibility = value ? Visibility.Collapsed : Visibility.Visible; + }); + } + } + + public bool IsConnecting + { + set + { + Dispatcher.Invoke(() => + { + LoadingIcon.Visibility = value ? Visibility.Visible : Visibility.Collapsed; + SignalStrengthIcon.Visibility = value ? Visibility.Collapsed : Visibility.Visible; + NetworkStatusIcon.Visibility = value ? Visibility.Collapsed : Visibility.Visible; + }); + } + } + + public WirelessNetworkStatus NetworkStatus + { + set + { + Dispatcher.InvokeAsync(() => + { + var icon = value == WirelessNetworkStatus.Connected ? FontAwesomeIcon.Check : FontAwesomeIcon.Close; + var brush = value == WirelessNetworkStatus.Connected ? Brushes.Green : Brushes.Orange; + + if (value == WirelessNetworkStatus.Disconnected) + { + SignalStrengthIcon.Child = GetIcon(0); + } + + NetworkStatusIcon.Source = ImageAwesome.CreateImageSource(icon, brush); + }); + } + } + + public event WirelessNetworkSelectedEventHandler NetworkSelected; + + public TaskbarWirelessNetworkControl() + { + InitializeComponent(); + InitializeWirelessNetworkControl(); + } + + public void Close() + { + Popup.IsOpen = false; + } + + public void SetInformation(string text) + { + Dispatcher.InvokeAsync(() => Button.ToolTip = text); + } + + public void Update(IEnumerable networks) + { + Dispatcher.InvokeAsync(() => + { + NetworksStackPanel.Children.Clear(); + + foreach (var network in networks) + { + var button = new TaskbarWirelessNetworkButton(network); + var isCurrent = network.Status == WirelessNetworkStatus.Connected; + + button.IsCurrent = isCurrent; + button.NetworkName = network.Name; + button.SignalStrength = network.SignalStrength; + button.NetworkSelected += (id) => NetworkSelected?.Invoke(id); + + if (isCurrent) + { + NetworkStatus = network.Status; + SignalStrengthIcon.Child = GetIcon(network.SignalStrength); + } + + NetworksStackPanel.Children.Add(button); + } + }); + } + + private void InitializeWirelessNetworkControl() + { + var originalBrush = Button.Background; + + SignalStrengthIcon.Child = GetIcon(0); + Button.Click += (o, args) => Popup.IsOpen = !Popup.IsOpen; + Button.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => Popup.IsOpen = Popup.IsMouseOver)); + Popup.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => Popup.IsOpen = IsMouseOver)); + + Popup.Opened += (o, args) => + { + Background = Brushes.LightGray; + Button.Background = Brushes.LightGray; + }; + + Popup.Closed += (o, args) => + { + Background = originalBrush; + Button.Background = originalBrush; + }; + } + + private UIElement GetIcon(int signalStrength) + { + var icon = signalStrength > 66 ? "100" : (signalStrength > 33 ? "66" : (signalStrength > 0 ? "33" : "0")); + var uri = new Uri($"pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/WiFi_{icon}.xaml"); + var resource = new XamlIconResource(uri); + + return IconResourceLoader.Load(resource); + } + } +} diff --git a/SafeExamBrowser.UserInterface.Mobile/Images/AboutNotification.xaml b/SafeExamBrowser.UserInterface.Mobile/Images/AboutNotification.xaml new file mode 100644 index 00000000..7d70c974 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Mobile/Images/AboutNotification.xaml @@ -0,0 +1,14 @@ + + + + + + + + + + + + diff --git a/SafeExamBrowser.UserInterface.Mobile/Images/Battery.xaml b/SafeExamBrowser.UserInterface.Mobile/Images/Battery.xaml new file mode 100644 index 00000000..998b72d4 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Mobile/Images/Battery.xaml @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/SafeExamBrowser.UserInterface.Mobile/Images/Keyboard.xaml b/SafeExamBrowser.UserInterface.Mobile/Images/Keyboard.xaml new file mode 100644 index 00000000..f90b0365 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Mobile/Images/Keyboard.xaml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SafeExamBrowser.UserInterface.Mobile/Images/LogNotification.ico b/SafeExamBrowser.UserInterface.Mobile/Images/LogNotification.ico new file mode 100644 index 00000000..1c7fb20f Binary files /dev/null and b/SafeExamBrowser.UserInterface.Mobile/Images/LogNotification.ico differ diff --git a/SafeExamBrowser.UserInterface.Mobile/Images/Menu.xaml b/SafeExamBrowser.UserInterface.Mobile/Images/Menu.xaml new file mode 100644 index 00000000..f9188146 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Mobile/Images/Menu.xaml @@ -0,0 +1,7 @@ + + + + + diff --git a/SafeExamBrowser.UserInterface.Mobile/Images/NavigateBack.xaml b/SafeExamBrowser.UserInterface.Mobile/Images/NavigateBack.xaml new file mode 100644 index 00000000..571e4427 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Mobile/Images/NavigateBack.xaml @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/SafeExamBrowser.UserInterface.Mobile/Images/NavigateForward.xaml b/SafeExamBrowser.UserInterface.Mobile/Images/NavigateForward.xaml new file mode 100644 index 00000000..957c1f83 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Mobile/Images/NavigateForward.xaml @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/SafeExamBrowser.UserInterface.Mobile/Images/Reload.xaml b/SafeExamBrowser.UserInterface.Mobile/Images/Reload.xaml new file mode 100644 index 00000000..eb620713 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Mobile/Images/Reload.xaml @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/SafeExamBrowser.UserInterface.Mobile/Images/SafeExamBrowser.ico b/SafeExamBrowser.UserInterface.Mobile/Images/SafeExamBrowser.ico new file mode 100644 index 00000000..abdc4635 Binary files /dev/null and b/SafeExamBrowser.UserInterface.Mobile/Images/SafeExamBrowser.ico differ diff --git a/SafeExamBrowser.UserInterface.Mobile/Images/ShutDown.xaml b/SafeExamBrowser.UserInterface.Mobile/Images/ShutDown.xaml new file mode 100644 index 00000000..a73e0262 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Mobile/Images/ShutDown.xaml @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/SafeExamBrowser.UserInterface.Mobile/Images/SkipBack.xaml b/SafeExamBrowser.UserInterface.Mobile/Images/SkipBack.xaml new file mode 100644 index 00000000..f7ff20bf --- /dev/null +++ b/SafeExamBrowser.UserInterface.Mobile/Images/SkipBack.xaml @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/SafeExamBrowser.UserInterface.Mobile/Images/SplashScreen.png b/SafeExamBrowser.UserInterface.Mobile/Images/SplashScreen.png new file mode 100644 index 00000000..c56dd2b0 Binary files /dev/null and b/SafeExamBrowser.UserInterface.Mobile/Images/SplashScreen.png differ diff --git a/SafeExamBrowser.UserInterface.Mobile/Images/WiFi_0.xaml b/SafeExamBrowser.UserInterface.Mobile/Images/WiFi_0.xaml new file mode 100644 index 00000000..ca328bff --- /dev/null +++ b/SafeExamBrowser.UserInterface.Mobile/Images/WiFi_0.xaml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SafeExamBrowser.UserInterface.Mobile/Images/WiFi_100.xaml b/SafeExamBrowser.UserInterface.Mobile/Images/WiFi_100.xaml new file mode 100644 index 00000000..74fd45d8 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Mobile/Images/WiFi_100.xaml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SafeExamBrowser.UserInterface.Mobile/Images/WiFi_33.xaml b/SafeExamBrowser.UserInterface.Mobile/Images/WiFi_33.xaml new file mode 100644 index 00000000..da1e2a15 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Mobile/Images/WiFi_33.xaml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SafeExamBrowser.UserInterface.Mobile/Images/WiFi_66.xaml b/SafeExamBrowser.UserInterface.Mobile/Images/WiFi_66.xaml new file mode 100644 index 00000000..f6b214bc --- /dev/null +++ b/SafeExamBrowser.UserInterface.Mobile/Images/WiFi_66.xaml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SafeExamBrowser.UserInterface.Mobile/Images/WiFi_Light_0.xaml b/SafeExamBrowser.UserInterface.Mobile/Images/WiFi_Light_0.xaml new file mode 100644 index 00000000..07d4c0f4 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Mobile/Images/WiFi_Light_0.xaml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SafeExamBrowser.UserInterface.Mobile/Images/WiFi_Light_100.xaml b/SafeExamBrowser.UserInterface.Mobile/Images/WiFi_Light_100.xaml new file mode 100644 index 00000000..849e6b15 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Mobile/Images/WiFi_Light_100.xaml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SafeExamBrowser.UserInterface.Mobile/Images/WiFi_Light_33.xaml b/SafeExamBrowser.UserInterface.Mobile/Images/WiFi_Light_33.xaml new file mode 100644 index 00000000..3c1820c4 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Mobile/Images/WiFi_Light_33.xaml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SafeExamBrowser.UserInterface.Mobile/Images/WiFi_Light_66.xaml b/SafeExamBrowser.UserInterface.Mobile/Images/WiFi_Light_66.xaml new file mode 100644 index 00000000..006337de --- /dev/null +++ b/SafeExamBrowser.UserInterface.Mobile/Images/WiFi_Light_66.xaml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SafeExamBrowser.UserInterface.Mobile/Images/ZoomPageIn.xaml b/SafeExamBrowser.UserInterface.Mobile/Images/ZoomPageIn.xaml new file mode 100644 index 00000000..e7f4acab --- /dev/null +++ b/SafeExamBrowser.UserInterface.Mobile/Images/ZoomPageIn.xaml @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/SafeExamBrowser.UserInterface.Mobile/Images/ZoomPageOut.xaml b/SafeExamBrowser.UserInterface.Mobile/Images/ZoomPageOut.xaml new file mode 100644 index 00000000..74a53e6c --- /dev/null +++ b/SafeExamBrowser.UserInterface.Mobile/Images/ZoomPageOut.xaml @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/SafeExamBrowser.UserInterface.Mobile/LogWindow.xaml b/SafeExamBrowser.UserInterface.Mobile/LogWindow.xaml new file mode 100644 index 00000000..fa88dc2a --- /dev/null +++ b/SafeExamBrowser.UserInterface.Mobile/LogWindow.xaml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + diff --git a/SafeExamBrowser.UserInterface.Mobile/LogWindow.xaml.cs b/SafeExamBrowser.UserInterface.Mobile/LogWindow.xaml.cs new file mode 100644 index 00000000..9302f8fe --- /dev/null +++ b/SafeExamBrowser.UserInterface.Mobile/LogWindow.xaml.cs @@ -0,0 +1,97 @@ +/* + * 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.ComponentModel; +using System.Windows; +using SafeExamBrowser.Contracts.I18n; +using SafeExamBrowser.Contracts.Logging; +using SafeExamBrowser.Contracts.UserInterface.Windows; +using SafeExamBrowser.Contracts.UserInterface.Windows.Events; +using SafeExamBrowser.UserInterface.Mobile.ViewModels; + +namespace SafeExamBrowser.UserInterface.Mobile +{ + public partial class LogWindow : Window, IWindow + { + private ILogger logger; + private LogViewModel model; + private WindowClosingEventHandler closing; + + event WindowClosingEventHandler IWindow.Closing + { + add { closing += value; } + remove { closing -= value; } + } + + public LogWindow(ILogger logger, IText text) + { + InitializeComponent(); + + this.logger = logger; + this.model = new LogViewModel(text, ScrollViewer, LogContent); + + InitializeLogWindow(); + } + + public void BringToForeground() + { + Dispatcher.Invoke(() => + { + if (WindowState == WindowState.Minimized) + { + WindowState = WindowState.Normal; + } + + Activate(); + }); + } + + public new void Close() + { + Dispatcher.Invoke(base.Close); + } + + public new void Hide() + { + Dispatcher.Invoke(base.Hide); + } + + public new void Show() + { + Dispatcher.Invoke(base.Show); + } + + private void InitializeLogWindow() + { + DataContext = model; + Closing += LogWindow_Closing; + Loaded += LogWindow_Loaded; + } + + private void LogWindow_Loaded(object sender, RoutedEventArgs e) + { + var log = logger.GetLog(); + + foreach (var content in log) + { + model.Notify(content); + } + + logger.Subscribe(model); + logger.Debug("Opened log window."); + } + + private void LogWindow_Closing(object sender, CancelEventArgs e) + { + logger.Unsubscribe(model); + logger.Debug("Closed log window."); + + closing?.Invoke(); + } + } +} diff --git a/SafeExamBrowser.UserInterface.Mobile/MessageBox.cs b/SafeExamBrowser.UserInterface.Mobile/MessageBox.cs new file mode 100644 index 00000000..81aad442 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Mobile/MessageBox.cs @@ -0,0 +1,90 @@ +/* + * 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.Windows; +using SafeExamBrowser.Contracts.I18n; +using SafeExamBrowser.Contracts.UserInterface.MessageBox; +using SafeExamBrowser.Contracts.UserInterface.Windows; +using MessageBoxResult = SafeExamBrowser.Contracts.UserInterface.MessageBox.MessageBoxResult; + +namespace SafeExamBrowser.UserInterface.Mobile +{ + public class MessageBox : IMessageBox + { + private IText text; + + public MessageBox(IText text) + { + this.text = text; + } + + public MessageBoxResult Show(string message, string title, MessageBoxAction action = MessageBoxAction.Confirm, MessageBoxIcon icon = MessageBoxIcon.Information, IWindow parent = null) + { + var result = default(System.Windows.MessageBoxResult); + + if (parent is Window window) + { + result = window.Dispatcher.Invoke(() => System.Windows.MessageBox.Show(window, message, title, ToButton(action), ToImage(icon))); + } + else + { + result = System.Windows.MessageBox.Show(message, title, ToButton(action), ToImage(icon)); + } + + return ToResult(result); + } + + public MessageBoxResult Show(TextKey message, TextKey title, MessageBoxAction action = MessageBoxAction.Confirm, MessageBoxIcon icon = MessageBoxIcon.Information, IWindow parent = null) + { + return Show(text.Get(message), text.Get(title), action, icon, parent); + } + + private MessageBoxButton ToButton(MessageBoxAction action) + { + switch (action) + { + case MessageBoxAction.YesNo: + return MessageBoxButton.YesNo; + default: + return MessageBoxButton.OK; + } + } + + private MessageBoxImage ToImage(MessageBoxIcon icon) + { + switch (icon) + { + case MessageBoxIcon.Error: + return MessageBoxImage.Error; + case MessageBoxIcon.Question: + return MessageBoxImage.Question; + case MessageBoxIcon.Warning: + return MessageBoxImage.Warning; + default: + return MessageBoxImage.Information; + } + } + + private MessageBoxResult ToResult(System.Windows.MessageBoxResult result) + { + switch (result) + { + case System.Windows.MessageBoxResult.Cancel: + return MessageBoxResult.Cancel; + case System.Windows.MessageBoxResult.No: + return MessageBoxResult.No; + case System.Windows.MessageBoxResult.OK: + return MessageBoxResult.Ok; + case System.Windows.MessageBoxResult.Yes: + return MessageBoxResult.Yes; + default: + return MessageBoxResult.None; + } + } + } +} diff --git a/SafeExamBrowser.UserInterface.Mobile/PasswordDialog.xaml b/SafeExamBrowser.UserInterface.Mobile/PasswordDialog.xaml new file mode 100644 index 00000000..45d7b52f --- /dev/null +++ b/SafeExamBrowser.UserInterface.Mobile/PasswordDialog.xaml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +