public MainWindowViewModel( IAppArguments appArguments, IApplicationAccessor applicationAccessor, IFactory <IStartupManager, StartupManagerArgs> startupManagerFactory, IMicSwitchOverlayViewModel overlay, IMicrophoneControllerViewModel microphoneControllerViewModel, IOverlayWindowController overlayWindowController, IWaveOutDeviceSelectorViewModel waveOutDeviceSelector, IAudioNotificationsManager audioNotificationsManager, IFactory <IAudioNotificationSelectorViewModel> audioSelectorFactory, IApplicationUpdaterViewModel appUpdater, [Dependency(WellKnownWindows.MainWindow)] IWindowTracker mainWindowTracker, IConfigProvider <MicSwitchConfig> configProvider, IConfigProvider <MicSwitchOverlayConfig> overlayConfigProvider, IImageProvider imageProvider, IErrorMonitorViewModel errorMonitor, IAudioNotificationsManager notificationsManager, IWindowViewController viewController, [Dependency(WellKnownSchedulers.UI)] IScheduler uiScheduler) { Title = $"{(appArguments.IsDebugMode ? "[D]" : "")} {appArguments.AppName} v{appArguments.Version}"; this.appArguments = appArguments; this.applicationAccessor = applicationAccessor; this.MicrophoneController = microphoneControllerViewModel.AddTo(Anchors); this.mainWindowTracker = mainWindowTracker; this.configProvider = configProvider; this.overlayConfigProvider = overlayConfigProvider; this.notificationsManager = notificationsManager; this.viewController = viewController; ApplicationUpdater = appUpdater.AddTo(Anchors); WaveOutDeviceSelector = waveOutDeviceSelector; ImageProvider = imageProvider; ErrorMonitor = errorMonitor; AudioSelectorWhenMuted = audioSelectorFactory.Create().AddTo(Anchors); AudioSelectorWhenUnmuted = audioSelectorFactory.Create().AddTo(Anchors); WindowState = WindowState.Minimized; Overlay = overlay.AddTo(Anchors); try { var startupManagerArgs = new StartupManagerArgs { UniqueAppName = $"{appArguments.AppName}{(appArguments.IsDebugMode ? "-debug" : string.Empty)}", ExecutablePath = appUpdater.LauncherExecutable.FullName, CommandLineArgs = appArguments.StartupArgs, AutostartFlag = appArguments.AutostartFlag }; this.startupManager = startupManagerFactory.Create(startupManagerArgs); RunAtLoginToggleCommand = CommandWrapper.Create <bool>(RunAtLoginCommandExecuted, Observable.Return(startupManager?.IsReady ?? false)); } catch (Exception e) { Log.Warn("Failed to initialize startup manager", e); } this.RaiseWhenSourceValue(x => x.IsActive, mainWindowTracker, x => x.IsActive, uiScheduler).AddTo(Anchors); this.RaiseWhenSourceValue(x => x.RunAtLogin, startupManager, x => x.IsRegistered, uiScheduler).AddTo(Anchors); this.RaiseWhenSourceValue(x => x.ShowOverlaySettings, Overlay, x => x.OverlayVisibilityMode).AddTo(Anchors); audioNotificationSource = Observable.Merge( AudioSelectorWhenMuted.ObservableForProperty(x => x.SelectedValue, skipInitial: true), AudioSelectorWhenUnmuted.ObservableForProperty(x => x.SelectedValue, skipInitial: true)) .Select(x => new TwoStateNotification { On = AudioSelectorWhenUnmuted.SelectedValue, Off = AudioSelectorWhenMuted.SelectedValue }) .ToProperty(this, x => x.AudioNotification) .AddTo(Anchors); this.WhenAnyValue(x => x.AudioNotificationVolume) .Subscribe(x => { AudioSelectorWhenUnmuted.Volume = AudioSelectorWhenMuted.Volume = x; }) .AddTo(Anchors); MicrophoneController.ObservableForProperty(x => x.MicrophoneMuted, skipInitial: true) .DistinctUntilChanged() .Where(x => !MicrophoneController.MicrophoneLine.IsEmpty) .Select(isMuted => (isMuted.Value ? AudioNotification.Off : AudioNotification.On) ?? default(AudioNotificationType).ToString()) .Where(notificationToPlay => !string.IsNullOrEmpty(notificationToPlay)) .Select(notificationToPlay => Observable.FromAsync(async token => { Log.Debug($"Playing notification {notificationToPlay}, volume: {audioNotificationVolume}"); try { await audioNotificationsManager.PlayNotification(notificationToPlay, audioNotificationVolume, waveOutDeviceSelector.SelectedItem, token); Log.Debug($"Played notification {notificationToPlay}"); } catch (Exception ex) { Log.Debug($"Failed to play notification {notificationToPlay}", ex); } })) .Switch() .SubscribeToErrors(Log.HandleUiException) .AddTo(Anchors); this.WhenAnyValue(x => x.WindowState) .SubscribeSafe(x => ShowInTaskbar = x != WindowState.Minimized, Log.HandleUiException) .AddTo(Anchors); viewController .WhenClosing .SubscribeSafe(x => HandleWindowClosing(viewController, x), Log.HandleUiException) .AddTo(Anchors); ToggleOverlayLockCommand = CommandWrapper.Create(ToggleOverlayCommandExecuted); ExitAppCommand = CommandWrapper.Create(ExitAppCommandExecuted); ShowAppCommand = CommandWrapper.Create(ShowAppCommandExecuted); OpenAppDataDirectoryCommand = CommandWrapper.Create(OpenAppDataDirectory); ResetOverlayPositionCommand = CommandWrapper.Create(ResetOverlayPositionCommandExecuted); RunAtLoginToggleCommand = CommandWrapper.Create <bool>(RunAtLoginCommandExecuted, startupManager.WhenAnyValue(x => x.IsReady)); SelectMicrophoneIconCommand = CommandWrapper.Create(SelectMicrophoneIconCommandExecuted); SelectMutedMicrophoneIconCommand = CommandWrapper.Create(SelectMutedMicrophoneIconCommandExecuted); ResetMicrophoneIconsCommand = CommandWrapper.Create(ResetMicrophoneIconsCommandExecuted); AddSoundCommand = CommandWrapper.Create(AddSoundCommandExecuted); Observable.Merge(configProvider.ListenTo(x => x.Notifications).ToUnit(), configProvider.ListenTo(x => x.NotificationVolume).ToUnit()) .Select(_ => new { configProvider.ActualConfig.Notifications, configProvider.ActualConfig.NotificationVolume }) .ObserveOn(uiScheduler) .SubscribeSafe(cfg => { Log.Debug($"Applying new notification configuration: {cfg.DumpToTextRaw()} (current: {AudioNotification.DumpToTextRaw()}, volume: {AudioNotificationVolume})"); AudioSelectorWhenMuted.SelectedValue = cfg.Notifications.Off; AudioSelectorWhenUnmuted.SelectedValue = cfg.Notifications.On; AudioNotificationVolume = cfg.NotificationVolume; }, Log.HandleException) .AddTo(Anchors); configProvider.ListenTo(x => x.MinimizeOnClose) .ObserveOn(uiScheduler) .SubscribeSafe(x => MinimizeOnClose = x, Log.HandleException) .AddTo(Anchors); configProvider.ListenTo(x => x.OutputDeviceId) .ObserveOn(uiScheduler) .SubscribeSafe(x => WaveOutDeviceSelector.SelectById(x), Log.HandleException) .AddTo(Anchors); viewController .WhenLoaded .Take(1) .Select(_ => configProvider.ListenTo(y => y.StartMinimized)) .Switch() .Take(1) .ObserveOn(uiScheduler) .SubscribeSafe( x => { if (x) { Log.Debug($"StartMinimized option is active - minimizing window, current state: {WindowState}"); StartMinimized = true; viewController.Hide(); } else { Log.Debug($"StartMinimized option is not active - showing window as Normal, current state: {WindowState}"); StartMinimized = false; viewController.Show(); } }, Log.HandleUiException) .AddTo(Anchors); Observable.Merge( microphoneControllerViewModel.ObservableForProperty(x => x.MuteMode, skipInitial: true).ToUnit(), waveOutDeviceSelector.ObservableForProperty(x => x.SelectedItem, skipInitial: true).ToUnit(), this.ObservableForProperty(x => x.AudioNotification, skipInitial: true).ToUnit(), this.ObservableForProperty(x => x.MinimizeOnClose, skipInitial: true).ToUnit(), this.ObservableForProperty(x => x.Width, skipInitial: true).ToUnit(), this.ObservableForProperty(x => x.Height, skipInitial: true).ToUnit(), this.ObservableForProperty(x => x.Top, skipInitial: true).ToUnit(), this.ObservableForProperty(x => x.Left, skipInitial: true).ToUnit(), this.ObservableForProperty(x => x.AudioNotificationVolume, skipInitial: true).ToUnit(), this.ObservableForProperty(x => x.StartMinimized, skipInitial: true).ToUnit()) .Throttle(ConfigThrottlingTimeout) .ObserveOn(uiScheduler) .SubscribeSafe(() => { var config = configProvider.ActualConfig.CloneJson(); config.Notifications = AudioNotification; config.NotificationVolume = AudioNotificationVolume; config.StartMinimized = StartMinimized; config.MinimizeOnClose = MinimizeOnClose; config.OutputDeviceId = waveOutDeviceSelector.SelectedItem?.Id; config.MainWindowBounds = new Rect(Left, Top, Width, Height); configProvider.Save(config); }, Log.HandleUiException) .AddTo(Anchors); viewController.WhenLoaded .SubscribeSafe(() => { Log.Debug($"Main window loaded - loading overlay, current process({CurrentProcess.ProcessName} 0x{CurrentProcess.Id:x8}) main window: {CurrentProcess.MainWindowHandle} ({CurrentProcess.MainWindowTitle})"); overlayWindowController.RegisterChild(Overlay).AddTo(Anchors); Log.Debug("Overlay loaded successfully"); }, Log.HandleUiException) .AddTo(Anchors); configProvider.ListenTo(x => x.MainWindowBounds) .WithPrevious() .ObserveOn(uiScheduler) .SubscribeSafe(x => { Log.Debug($"Main window config bounds updated: {x}"); Rect bounds; if (x.Current == null) { var monitorBounds = UnsafeNative.GetMonitorBounds(Rectangle.Empty).ScaleToWpf(); var monitorCenter = monitorBounds.Center(); bounds = new Rect( monitorCenter.X - DefaultSize.Width / 2f, monitorCenter.Y - DefaultSize.Height / 2f, DefaultSize.Width, DefaultSize.Height); } else { bounds = x.Current.Value; } Left = bounds.Left; Top = bounds.Top; Width = bounds.Width; Height = bounds.Height; }, Log.HandleUiException) .AddTo(Anchors); var theme = Theme.Create( Theme.Light, primary: SwatchHelper.Lookup[(MaterialDesignColor)PrimaryColor.BlueGrey], accent: SwatchHelper.Lookup[(MaterialDesignColor)SecondaryColor.LightBlue]); var paletteHelper = new PaletteHelper(); paletteHelper.SetTheme(theme); }
public MainWindowViewModel( IAppArguments appArguments, IFactory <IStartupManager, StartupManagerArgs> startupManagerFactory, IMicSwitchOverlayViewModel overlay, IMicrophoneControllerViewModel microphoneControllerViewModel, IOverlayWindowController overlayWindowController, IAudioNotificationsManager audioNotificationsManager, IFactory <IAudioNotificationSelectorViewModel> audioSelectorFactory, IApplicationUpdaterViewModel appUpdater, [Dependency(WellKnownWindows.MainWindow)] IWindowTracker mainWindowTracker, IConfigProvider <MicSwitchConfig> configProvider, IConfigProvider <MicSwitchOverlayConfig> overlayConfigProvider, IImageProvider imageProvider, IAudioNotificationsManager notificationsManager, IWindowViewController viewController, [Dependency(WellKnownSchedulers.UI)] IScheduler uiScheduler) { Title = $"{(appArguments.IsDebugMode ? "[D]" : "")} {appArguments.AppName} v{appArguments.Version}"; this.appArguments = appArguments; this.MicrophoneController = microphoneControllerViewModel.AddTo(Anchors); this.mainWindowTracker = mainWindowTracker; this.configProvider = configProvider; this.overlayConfigProvider = overlayConfigProvider; this.notificationsManager = notificationsManager; this.viewController = viewController; ApplicationUpdater = appUpdater.AddTo(Anchors); ImageProvider = imageProvider; AudioSelectorWhenMuted = audioSelectorFactory.Create().AddTo(Anchors); AudioSelectorWhenUnmuted = audioSelectorFactory.Create().AddTo(Anchors); WindowState = WindowState.Minimized; Overlay = overlay.AddTo(Anchors); var startupManagerArgs = new StartupManagerArgs { UniqueAppName = $"{appArguments.AppName}{(appArguments.IsDebugMode ? "-debug" : string.Empty)}", ExecutablePath = appUpdater.GetLatestExecutable().FullName, CommandLineArgs = appArguments.StartupArgs, AutostartFlag = appArguments.AutostartFlag }; this.startupManager = startupManagerFactory.Create(startupManagerArgs); this.RaiseWhenSourceValue(x => x.IsActive, mainWindowTracker, x => x.IsActive, uiScheduler).AddTo(Anchors); this.RaiseWhenSourceValue(x => x.RunAtLogin, startupManager, x => x.IsRegistered, uiScheduler).AddTo(Anchors); this.RaiseWhenSourceValue(x => x.ShowOverlaySettings, Overlay, x => x.OverlayVisibilityMode).AddTo(Anchors); audioNotificationSource = Observable.Merge( AudioSelectorWhenMuted.ObservableForProperty(x => x.SelectedValue, skipInitial: true), AudioSelectorWhenUnmuted.ObservableForProperty(x => x.SelectedValue, skipInitial: true)) .Select(x => new TwoStateNotification { On = AudioSelectorWhenUnmuted.SelectedValue, Off = AudioSelectorWhenMuted.SelectedValue }) .ToPropertyHelper(this, x => x.AudioNotification) .AddTo(Anchors); this.WhenAnyValue(x => x.AudioNotificationVolume) .Subscribe(x => { AudioSelectorWhenUnmuted.Volume = AudioSelectorWhenMuted.Volume = x; }) .AddTo(Anchors); MicrophoneController.ObservableForProperty(x => x.MicrophoneMuted, skipInitial: true) .DistinctUntilChanged() .Where(x => !MicrophoneController.MicrophoneLine.IsEmpty) .SubscribeSafe(x => { var notificationToPlay = (x.Value ? AudioNotification.On : AudioNotification.Off) ?? default(AudioNotificationType).ToString(); Log.Debug($"Playing notification {notificationToPlay} (cfg: {AudioNotification.DumpToTextRaw()})"); audioNotificationsManager.PlayNotification(notificationToPlay, audioNotificationVolume); }, Log.HandleUiException) .AddTo(Anchors); this.WhenAnyValue(x => x.WindowState) .SubscribeSafe(x => ShowInTaskbar = x != WindowState.Minimized, Log.HandleUiException) .AddTo(Anchors); viewController .WhenClosing .SubscribeSafe(x => HandleWindowClosing(viewController, x), Log.HandleUiException) .AddTo(Anchors); ToggleOverlayLockCommand = CommandWrapper.Create(ToggleOverlayCommandExecuted); ExitAppCommand = CommandWrapper.Create(ExitAppCommandExecuted); ShowAppCommand = CommandWrapper.Create(ShowAppCommandExecuted); OpenAppDataDirectoryCommand = CommandWrapper.Create(OpenAppDataDirectory); ResetOverlayPositionCommand = CommandWrapper.Create(ResetOverlayPositionCommandExecuted); RunAtLoginToggleCommand = CommandWrapper.Create <bool>(RunAtLoginCommandExecuted); SelectMicrophoneIconCommand = CommandWrapper.Create(SelectMicrophoneIconCommandExecuted); SelectMutedMicrophoneIconCommand = CommandWrapper.Create(SelectMutedMicrophoneIconCommandExecuted); ResetMicrophoneIconsCommand = CommandWrapper.Create(ResetMicrophoneIconsCommandExecuted); AddSoundCommand = CommandWrapper.Create(AddSoundCommandExecuted); Observable.Merge(configProvider.ListenTo(x => x.Notification).ToUnit(), configProvider.ListenTo(x => x.NotificationVolume).ToUnit()) .Select(_ => new { configProvider.ActualConfig.Notification, configProvider.ActualConfig.NotificationVolume }) .ObserveOn(uiScheduler) .SubscribeSafe(cfg => { Log.Debug($"Applying new notification configuration: {cfg.DumpToTextRaw()} (current: {AudioNotification.DumpToTextRaw()}, volume: {AudioNotificationVolume})"); AudioSelectorWhenMuted.SelectedValue = cfg.Notification.Off; AudioSelectorWhenUnmuted.SelectedValue = cfg.Notification.On; AudioNotificationVolume = cfg.NotificationVolume; }, Log.HandleException) .AddTo(Anchors); configProvider.ListenTo(x => x.MinimizeOnClose) .ObserveOn(uiScheduler) .SubscribeSafe(x => MinimizeOnClose = x, Log.HandleException) .AddTo(Anchors); viewController .WhenLoaded .Take(1) .Select(_ => configProvider.ListenTo(y => y.StartMinimized)) .Switch() .Take(1) .ObserveOn(uiScheduler) .SubscribeSafe( x => { if (x) { Log.Debug($"StartMinimized option is active - minimizing window, current state: {WindowState}"); StartMinimized = true; viewController.Hide(); } else { Log.Debug($"StartMinimized option is not active - showing window as Normal, current state: {WindowState}"); StartMinimized = false; viewController.Show(); } }, Log.HandleUiException) .AddTo(Anchors); configProvider.WhenChanged .Subscribe() .AddTo(Anchors); Observable.Merge( microphoneControllerViewModel.ObservableForProperty(x => x.MuteMode, skipInitial: true).ToUnit(), this.ObservableForProperty(x => x.AudioNotification, skipInitial: true).ToUnit(), this.ObservableForProperty(x => x.MinimizeOnClose, skipInitial: true).ToUnit(), this.ObservableForProperty(x => x.AudioNotificationVolume, skipInitial: true).ToUnit(), this.ObservableForProperty(x => x.StartMinimized, skipInitial: true).ToUnit()) .Throttle(ConfigThrottlingTimeout) .ObserveOn(uiScheduler) .SubscribeSafe(() => { var config = configProvider.ActualConfig.CloneJson(); config.Notification = AudioNotification; config.NotificationVolume = AudioNotificationVolume; config.StartMinimized = StartMinimized; config.MinimizeOnClose = MinimizeOnClose; configProvider.Save(config); }, Log.HandleUiException) .AddTo(Anchors); viewController.WhenLoaded .SubscribeSafe(() => { Log.Debug($"Main window loaded - loading overlay, current process({CurrentProcess.ProcessName} 0x{CurrentProcess.Id:x8}) main window: {CurrentProcess.MainWindowHandle} ({CurrentProcess.MainWindowTitle})"); overlayWindowController.RegisterChild(Overlay).AddTo(Anchors); Log.Debug("Overlay loaded successfully"); }, Log.HandleUiException) .AddTo(Anchors); var theme = Theme.Create( Theme.Light, primary: SwatchHelper.Lookup[(MaterialDesignColor)PrimaryColor.BlueGrey], accent: SwatchHelper.Lookup[(MaterialDesignColor)SecondaryColor.LightBlue]); var paletteHelper = new PaletteHelper(); paletteHelper.SetTheme(theme); }