diff --git a/src/CalcViewModel/ApplicationViewModel.cpp b/src/CalcViewModel/ApplicationViewModel.cpp deleted file mode 100644 index 929ab034..00000000 --- a/src/CalcViewModel/ApplicationViewModel.cpp +++ /dev/null @@ -1,274 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -#include "pch.h" -#include "ApplicationViewModel.h" -#include "Common/TraceLogger.h" -#include "Common/AppResourceProvider.h" -#include "StandardCalculatorViewModel.h" -#include "DateCalculatorViewModel.h" -#include "DataLoaders/CurrencyHttpClient.h" -#include "DataLoaders/CurrencyDataLoader.h" -#include "DataLoaders/UnitConverterDataLoader.h" - -using namespace CalculatorApp; -using namespace CalculatorApp::ViewModelNative::Common; -using namespace CalculatorApp::ViewModelNative::DataLoaders; -using namespace CalculatorApp::ViewModelNative; -using namespace CalculationManager; -using namespace Platform; -using namespace Platform::Collections; -using namespace std; -using namespace Windows::System; -using namespace Windows::Storage; -using namespace Utils; -using namespace Windows::Foundation::Collections; -using namespace Windows::Globalization; -using namespace Windows::UI::ViewManagement; -using namespace Windows::UI::Core; -using namespace Windows::UI::Xaml::Automation; -using namespace Windows::UI::Xaml::Controls; -using namespace Windows::UI::Xaml::Data; -using namespace Windows::UI::Xaml::Input; -using namespace Windows::UI::Xaml::Media; -using namespace Windows::Foundation; -using namespace Concurrency; - -namespace -{ - StringReference CategoriesPropertyName(L"Categories"); - StringReference ClearMemoryVisibilityPropertyName(L"ClearMemoryVisibility"); -} - -ApplicationViewModel::ApplicationViewModel() - : m_CalculatorViewModel(nullptr) - , m_DateCalcViewModel(nullptr) - , m_GraphingCalcViewModel(nullptr) - , m_ConverterViewModel(nullptr) - , m_PreviousMode(ViewMode::None) - , m_mode(ViewMode::None) - , m_categories(nullptr) -{ - SetMenuCategories(); -} - -void ApplicationViewModel::Mode::set(ViewMode value) -{ - if (m_mode != value) - { - PreviousMode = m_mode; - m_mode = value; - SetDisplayNormalAlwaysOnTopOption(); - OnModeChanged(); - RaisePropertyChanged(ModePropertyName); - } -} - -void ApplicationViewModel::Categories::set(IObservableVector ^ value) -{ - if (m_categories != value) - { - m_categories = value; - RaisePropertyChanged(CategoriesPropertyName); - } -} - -void ApplicationViewModel::Initialize(ViewMode mode) -{ - if (!NavCategoryStates::IsValidViewMode(mode) || !NavCategoryStates::IsViewModeEnabled(mode)) - { - mode = ViewMode::Standard; - } - - try - { - Mode = mode; - } - catch (const std::exception& e) - { - TraceLogger::GetInstance()->LogStandardException(mode, __FUNCTIONW__, e); - if (!TryRecoverFromNavigationModeFailure()) - { - // Could not navigate to standard mode either. - // Throw the original exception so we have a good stack to debug. - throw; - } - } - catch (Exception ^ e) - { - TraceLogger::GetInstance()->LogPlatformException(mode, __FUNCTIONW__, e); - if (!TryRecoverFromNavigationModeFailure()) - { - // Could not navigate to standard mode either. - // Throw the original exception so we have a good stack to debug. - throw; - } - } -} - -bool ApplicationViewModel::TryRecoverFromNavigationModeFailure() -{ - // Here we are simply trying to recover from being unable to navigate to a mode. - // Try falling back to standard mode and if there are *any* exceptions, we should - // fail because something is seriously wrong. - try - { - Mode = ViewMode::Standard; - return true; - } - catch (...) - { - return false; - } -} - -void ApplicationViewModel::OnModeChanged() -{ - assert(NavCategoryStates::IsValidViewMode(m_mode)); - if (NavCategory::IsCalculatorViewMode(m_mode)) - { - if (!m_CalculatorViewModel) - { - m_CalculatorViewModel = ref new StandardCalculatorViewModel(); - } - m_CalculatorViewModel->SetCalculatorType(m_mode); - } - else if (NavCategory::IsGraphingCalculatorViewMode(m_mode)) - { - if (!m_GraphingCalcViewModel) - { - m_GraphingCalcViewModel = ref new GraphingCalculatorViewModel(); - } - } - else if (NavCategory::IsDateCalculatorViewMode(m_mode)) - { - if (!m_DateCalcViewModel) - { - m_DateCalcViewModel = ref new DateCalculatorViewModel(); - } - } - else if (NavCategory::IsConverterViewMode(m_mode)) - { - if (!m_ConverterViewModel) - { - auto dataLoader = make_shared(ref new GeographicRegion()); - auto currencyDataLoader = make_shared(make_unique()); - m_ConverterViewModel = ref new UnitConverterViewModel(make_shared(dataLoader, currencyDataLoader)); - } - - m_ConverterViewModel->Mode = m_mode; - } - - auto resProvider = AppResourceProvider::GetInstance(); - CategoryName = resProvider->GetResourceString(NavCategoryStates::GetNameResourceKey(m_mode)); - - // Cast mode to an int in order to save it to app data. - // Save the changed mode, so that the new window launches in this mode. - // Don't save until after we have adjusted to the new mode, so we don't save a mode that fails to load. - ApplicationData::Current->LocalSettings->Values->Insert(ModePropertyName, NavCategoryStates::Serialize(m_mode)); - - // Log ModeChange event when not first launch, log WindowCreated on first launch - if (NavCategoryStates::IsValidViewMode(m_PreviousMode)) - { - TraceLogger::GetInstance()->LogModeChange(m_mode); - } - else - { - TraceLogger::GetInstance()->LogWindowCreated(m_mode, ApplicationView::GetApplicationViewIdForWindow(CoreWindow::GetForCurrentThread())); - } - - RaisePropertyChanged(ClearMemoryVisibilityPropertyName); -} - -void ApplicationViewModel::OnCopyCommand(Object ^ parameter) -{ - if (NavCategory::IsConverterViewMode(m_mode)) - { - ConverterViewModel->OnCopyCommand(parameter); - } - else if (NavCategory::IsDateCalculatorViewMode(m_mode)) - { - DateCalcViewModel->OnCopyCommand(parameter); - } - else if (NavCategory::IsCalculatorViewMode(m_mode)) - { - CalculatorViewModel->OnCopyCommand(parameter); - } -} - -void ApplicationViewModel::OnPasteCommand(Object ^ parameter) -{ - if (NavCategory::IsConverterViewMode(m_mode)) - { - ConverterViewModel->OnPasteCommand(parameter); - } - else if (NavCategory::IsCalculatorViewMode(m_mode)) - { - CalculatorViewModel->OnPasteCommand(parameter); - } -} - -void ApplicationViewModel::SetMenuCategories() -{ - // Use the Categories property instead of the backing variable - // because we want to take advantage of binding updates and - // property setter logic. - Categories = NavCategoryStates::CreateMenuOptions(); -} - -void ApplicationViewModel::ToggleAlwaysOnTop(float width, float height) -{ - HandleToggleAlwaysOnTop(width, height); -} - -#pragma optimize("", off) -task ApplicationViewModel::HandleToggleAlwaysOnTop(float width, float height) -{ - if (ApplicationView::GetForCurrentView()->ViewMode == ApplicationViewMode::CompactOverlay) - { - ApplicationDataContainer ^ localSettings = ApplicationData::Current->LocalSettings; - localSettings->Values->Insert(WidthLocalSettings, width); - localSettings->Values->Insert(HeightLocalSettings, height); - - bool success = co_await ApplicationView::GetForCurrentView()->TryEnterViewModeAsync(ApplicationViewMode::Default); - CalculatorViewModel->HistoryVM->AreHistoryShortcutsEnabled = success; - CalculatorViewModel->IsAlwaysOnTop = !success; - IsAlwaysOnTop = !success; - } - else - { - ApplicationDataContainer ^ localSettings = ApplicationData::Current->LocalSettings; - ViewModePreferences ^ compactOptions = ViewModePreferences::CreateDefault(ApplicationViewMode::CompactOverlay); - if (!localSettings->Values->GetView()->HasKey(LaunchedLocalSettings)) - { - compactOptions->CustomSize = Size(320, 394); - localSettings->Values->Insert(LaunchedLocalSettings, true); - } - else - { - if (localSettings->Values->GetView()->HasKey(WidthLocalSettings) && localSettings->Values->GetView()->HasKey(HeightLocalSettings)) - { - float oldWidth = safe_cast(localSettings->Values->GetView()->Lookup(WidthLocalSettings))->GetSingle(); - float oldHeight = safe_cast(localSettings->Values->GetView()->Lookup(HeightLocalSettings))->GetSingle(); - compactOptions->CustomSize = Size(oldWidth, oldHeight); - } - else - { - compactOptions->CustomSize = Size(320, 394); - } - } - - bool success = co_await ApplicationView::GetForCurrentView()->TryEnterViewModeAsync(ApplicationViewMode::CompactOverlay, compactOptions); - CalculatorViewModel->HistoryVM->AreHistoryShortcutsEnabled = !success; - CalculatorViewModel->IsAlwaysOnTop = success; - IsAlwaysOnTop = success; - } - SetDisplayNormalAlwaysOnTopOption(); -}; -#pragma optimize("", on) - -void ApplicationViewModel::SetDisplayNormalAlwaysOnTopOption() -{ - DisplayNormalAlwaysOnTopOption = - m_mode == ViewMode::Standard && ApplicationView::GetForCurrentView()->IsViewModeSupported(ApplicationViewMode::CompactOverlay) && !IsAlwaysOnTop; -} diff --git a/src/CalcViewModel/ApplicationViewModel.h b/src/CalcViewModel/ApplicationViewModel.h deleted file mode 100644 index f3fd4afb..00000000 --- a/src/CalcViewModel/ApplicationViewModel.h +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -#pragma once - -#include "StandardCalculatorViewModel.h" -#include "DateCalculatorViewModel.h" -#include "GraphingCalculator/GraphingCalculatorViewModel.h" -#include "UnitConverterViewModel.h" - -namespace CalculatorApp -{ - namespace ViewModelNative - { - [Windows::UI::Xaml::Data::Bindable] public ref class ApplicationViewModel sealed : public Windows::UI::Xaml::Data::INotifyPropertyChanged - { - public: - ApplicationViewModel(); - - void Initialize(CalculatorApp::ViewModelNative::Common::ViewMode mode); // Use for first init, use deserialize for rehydration - - OBSERVABLE_OBJECT(); - OBSERVABLE_PROPERTY_RW(StandardCalculatorViewModel ^, CalculatorViewModel); - OBSERVABLE_PROPERTY_RW(DateCalculatorViewModel ^, DateCalcViewModel); - OBSERVABLE_PROPERTY_RW(GraphingCalculatorViewModel ^, GraphingCalcViewModel); - OBSERVABLE_PROPERTY_RW(UnitConverterViewModel ^, ConverterViewModel); - OBSERVABLE_PROPERTY_RW(CalculatorApp::ViewModelNative::Common::ViewMode, PreviousMode); - OBSERVABLE_PROPERTY_R(bool, IsAlwaysOnTop); - OBSERVABLE_NAMED_PROPERTY_RW(Platform::String ^, CategoryName); - - // Indicates whether calculator is currently in standard mode _and_ supports CompactOverlay _and_ is not in Always-on-Top mode - OBSERVABLE_PROPERTY_R(bool, DisplayNormalAlwaysOnTopOption); - - COMMAND_FOR_METHOD(CopyCommand, ApplicationViewModel::OnCopyCommand); - COMMAND_FOR_METHOD(PasteCommand, ApplicationViewModel::OnPasteCommand); - - property CalculatorApp::ViewModelNative::Common::ViewMode Mode - { - CalculatorApp::ViewModelNative::Common::ViewMode get() - { - return m_mode; - } - - void set(CalculatorApp::ViewModelNative::Common::ViewMode value); - } - static property Platform::String^ ModePropertyName - { - Platform::String^ get() - { - return Platform::StringReference(L"Mode"); - } - } - - property Windows::Foundation::Collections::IObservableVector^ Categories - { - Windows::Foundation::Collections::IObservableVector^ get() - { - return m_categories; - } - - void set(Windows::Foundation::Collections::IObservableVector^ value); - } - - property Windows::UI::Xaml::Visibility ClearMemoryVisibility - { - Windows::UI::Xaml::Visibility get() - { - return CalculatorApp::ViewModelNative::Common::NavCategory::IsCalculatorViewMode(Mode) ? Windows::UI::Xaml::Visibility::Visible - : Windows::UI::Xaml::Visibility::Collapsed; - } - } - - static property Platform::String ^ LaunchedLocalSettings - { - Platform::String ^ get() - { - return Platform::StringReference(L"calculatorAlwaysOnTopLaunched"); - } - } - - static property Platform::String ^ WidthLocalSettings - { - Platform::String ^ get() - { - return Platform::StringReference(L"calculatorAlwaysOnTopLastWidth"); - } - } - - static property Platform::String ^ HeightLocalSettings - { - Platform::String ^ get() - { - return Platform::StringReference(L"calculatorAlwaysOnTopLastHeight"); - } - } - - void ToggleAlwaysOnTop(float width, float height); - - private: - bool TryRecoverFromNavigationModeFailure(); - - void OnModeChanged(); - - void OnCopyCommand(Platform::Object ^ parameter); - void OnPasteCommand(Platform::Object ^ parameter); - - void SetMenuCategories(); - - CalculatorApp::ViewModelNative::Common::ViewMode m_mode; - Windows::Foundation::Collections::IObservableVector ^ m_categories; - Concurrency::task HandleToggleAlwaysOnTop(float width, float height); - void SetDisplayNormalAlwaysOnTopOption(); - }; - } -} diff --git a/src/CalcViewModel/CalcViewModel.vcxproj b/src/CalcViewModel/CalcViewModel.vcxproj index f94732b7..3c65e932 100644 --- a/src/CalcViewModel/CalcViewModel.vcxproj +++ b/src/CalcViewModel/CalcViewModel.vcxproj @@ -298,7 +298,6 @@ - @@ -343,7 +342,6 @@ - @@ -417,4 +415,4 @@ - + \ No newline at end of file diff --git a/src/CalcViewModel/CalcViewModel.vcxproj.filters b/src/CalcViewModel/CalcViewModel.vcxproj.filters index cfaa786f..64c374ef 100644 --- a/src/CalcViewModel/CalcViewModel.vcxproj.filters +++ b/src/CalcViewModel/CalcViewModel.vcxproj.filters @@ -82,7 +82,6 @@ GraphingCalculator - @@ -194,7 +193,6 @@ GraphingCalculator - @@ -209,4 +207,7 @@ DataLoaders + + + \ No newline at end of file diff --git a/src/CalcViewModel/Common/NavCategory.cpp b/src/CalcViewModel/Common/NavCategory.cpp index fee460b9..0b813737 100644 --- a/src/CalcViewModel/Common/NavCategory.cpp +++ b/src/CalcViewModel/Common/NavCategory.cpp @@ -322,7 +322,7 @@ void NavCategoryStates::SetCurrentUser(Platform::String^ userId) CurrentUserId = userId; } -IObservableVector ^ NavCategoryStates::CreateMenuOptions() +IVector ^ NavCategoryStates::CreateMenuOptions() { auto menuOptions = ref new Vector(); menuOptions->Append(CreateCalculatorCategoryGroup()); diff --git a/src/CalcViewModel/Common/NavCategory.h b/src/CalcViewModel/Common/NavCategory.h index b80a7a0e..da583655 100644 --- a/src/CalcViewModel/Common/NavCategory.h +++ b/src/CalcViewModel/Common/NavCategory.h @@ -153,7 +153,7 @@ namespace CalculatorApp::ViewModelNative { public: static void SetCurrentUser(Platform::String^ user); - static Windows::Foundation::Collections::IObservableVector ^ CreateMenuOptions(); + static Windows::Foundation::Collections::IVector ^ CreateMenuOptions(); static NavCategoryGroup ^ CreateCalculatorCategoryGroup(); static NavCategoryGroup ^ CreateConverterCategoryGroup(); diff --git a/src/CalcViewModel/StandardCalculatorViewModel.h b/src/CalcViewModel/StandardCalculatorViewModel.h index 466844fd..2c4c2f2d 100644 --- a/src/CalcViewModel/StandardCalculatorViewModel.h +++ b/src/CalcViewModel/StandardCalculatorViewModel.h @@ -259,11 +259,14 @@ namespace CalculatorApp void SwitchAngleType(CalculatorApp::ViewModelNative::Common::NumbersAndOperatorsEnum num); void FtoEButtonToggled(); - internal: - void OnPaste(Platform::String ^ pastedString); + void SetCalculatorType(CalculatorApp::ViewModelNative::Common::ViewMode targetState); + void OnCopyCommand(Platform::Object ^ parameter); void OnPasteCommand(Platform::Object ^ parameter); + internal: + void OnPaste(Platform::String ^ pastedString); + ButtonInfo MapCharacterToButtonId(char16 ch); void OnInputChanged(); @@ -277,7 +280,6 @@ namespace CalculatorApp Platform::String ^ GetLocalizedStringFormat(Platform::String ^ format, Platform::String ^ displayValue); void OnPropertyChanged(Platform::String ^ propertyname); - void SetCalculatorType(CalculatorApp::ViewModelNative::Common::ViewMode targetState); Platform::String ^ GetRawDisplayValue(); void Recalculate(bool fromHistory = false); diff --git a/src/CalcViewModel/UnitConverterViewModel.cpp b/src/CalcViewModel/UnitConverterViewModel.cpp index 118d278a..aa08bf0a 100644 --- a/src/CalcViewModel/UnitConverterViewModel.cpp +++ b/src/CalcViewModel/UnitConverterViewModel.cpp @@ -152,6 +152,17 @@ UnitConverterViewModel::UnitConverterViewModel(const shared_ptr( + make_shared(ref new Windows::Globalization::GeographicRegion()), + make_shared(make_unique()))) +{ +} + +UnitConverterViewModel::~UnitConverterViewModel() +{ +} + void UnitConverterViewModel::ResetView() { m_model->SendCommand(UCM::Command::Reset); diff --git a/src/CalcViewModel/UnitConverterViewModel.h b/src/CalcViewModel/UnitConverterViewModel.h index 18b0a181..73f2d91c 100644 --- a/src/CalcViewModel/UnitConverterViewModel.h +++ b/src/CalcViewModel/UnitConverterViewModel.h @@ -158,6 +158,9 @@ namespace CalculatorApp internal : UnitConverterViewModel(const std::shared_ptr& model); public: + UnitConverterViewModel(); + virtual ~UnitConverterViewModel(); + OBSERVABLE_OBJECT_CALLBACK(OnPropertyChanged); OBSERVABLE_PROPERTY_R(Windows::Foundation::Collections::IObservableVector ^, Categories); @@ -239,14 +242,14 @@ namespace CalculatorApp void RefreshCurrencyRatios(); void OnValueActivated(IActivatable ^ control); + void OnCopyCommand(Platform::Object ^ parameter); + void OnPasteCommand(Platform::Object ^ parameter); + internal : void ResetView(); void PopulateData(); CalculatorApp::ViewModelNative::Common::NumbersAndOperatorsEnum MapCharacterToButtonId(const wchar_t ch, bool& canSendNegate); void DisplayPasteError(); - void OnCopyCommand(Platform::Object ^ parameter); - void OnPasteCommand(Platform::Object ^ parameter); - enum class CurrencyFormatterParameter { Default, diff --git a/src/CalcViewModelCopyForUT/CalcViewModelCopyForUT.vcxproj b/src/CalcViewModelCopyForUT/CalcViewModelCopyForUT.vcxproj index b77fd624..dad9bdff 100644 --- a/src/CalcViewModelCopyForUT/CalcViewModelCopyForUT.vcxproj +++ b/src/CalcViewModelCopyForUT/CalcViewModelCopyForUT.vcxproj @@ -292,7 +292,6 @@ - @@ -338,7 +337,6 @@ - @@ -411,4 +409,4 @@ - + \ No newline at end of file diff --git a/src/CalcViewModelCopyForUT/CalcViewModelCopyForUT.vcxproj.filters b/src/CalcViewModelCopyForUT/CalcViewModelCopyForUT.vcxproj.filters index 19ca2cee..bf6737aa 100644 --- a/src/CalcViewModelCopyForUT/CalcViewModelCopyForUT.vcxproj.filters +++ b/src/CalcViewModelCopyForUT/CalcViewModelCopyForUT.vcxproj.filters @@ -24,7 +24,6 @@ Common\Automation - @@ -98,7 +97,6 @@ Common\Automation - diff --git a/src/Calculator/Common/KeyboardShortcutManager.cs b/src/Calculator/Common/KeyboardShortcutManager.cs index 9e694775..ab9863f1 100644 --- a/src/Calculator/Common/KeyboardShortcutManager.cs +++ b/src/Calculator/Common/KeyboardShortcutManager.cs @@ -4,6 +4,7 @@ using CalculatorApp.ViewModel.Common; using CalculatorApp.ViewModelNative; using CalculatorApp.ViewModelNative.Common; +using ApplicationViewModel = CalculatorApp.ViewModel.ApplicationViewModel; using Utilities = CalculatorApp.ViewModel.Common.Utilities; using System; diff --git a/src/Calculator/Views/MainPage.xaml.cs b/src/Calculator/Views/MainPage.xaml.cs index 7a8c34fe..c40e0ff7 100644 --- a/src/Calculator/Views/MainPage.xaml.cs +++ b/src/Calculator/Views/MainPage.xaml.cs @@ -3,6 +3,7 @@ using CalculatorApp.Converters; using CalculatorApp.ViewModelNative; using CalculatorApp.ViewModelNative.Common; using CalculatorApp.ViewModelNative.Common.Automation; +using ApplicationViewModel = CalculatorApp.ViewModel.ApplicationViewModel; using System; using System.Collections.Generic; @@ -131,9 +132,9 @@ namespace CalculatorApp else { ApplicationDataContainer localSettings = ApplicationData.Current.LocalSettings; - if (localSettings.Values.ContainsKey(ApplicationViewModel.ModePropertyName)) + if (localSettings.Values.ContainsKey(ApplicationViewModel.ModeLocalSettings)) { - initialMode = NavCategoryStates.Deserialize(localSettings.Values[ApplicationViewModel.ModePropertyName]); + initialMode = NavCategoryStates.Deserialize(localSettings.Values[ApplicationViewModel.ModeLocalSettings]); } } @@ -143,11 +144,6 @@ namespace CalculatorApp private void InitializeNavViewCategoriesSource() { NavViewCategoriesSource = ExpandNavViewCategoryGroups(Model.Categories); - Model.Categories.VectorChanged += (sender, args) => - { - NavViewCategoriesSource.Clear(); - NavViewCategoriesSource = ExpandNavViewCategoryGroups(Model.Categories); - }; _ = Window.Current.Dispatcher.RunAsync(CoreDispatcherPriority.Low, () => { @@ -199,7 +195,7 @@ namespace CalculatorApp private void OnAppPropertyChanged(object sender, PropertyChangedEventArgs e) { string propertyName = e.PropertyName; - if (propertyName == ApplicationViewModel.ModePropertyName) + if (propertyName == nameof(ApplicationViewModel.Mode)) { ViewMode newValue = Model.Mode; ViewMode previousMode = Model.PreviousMode; @@ -265,7 +261,7 @@ namespace CalculatorApp UpdateViewState(); SetDefaultFocus(); } - else if (propertyName == ApplicationViewModel.CategoryNamePropertyName) + else if (propertyName == nameof(ApplicationViewModel.CategoryName)) { SetHeaderAutomationName(); AnnounceCategoryName(); diff --git a/src/CalculatorApp.ViewModel/ApplicationViewModel.cs b/src/CalculatorApp.ViewModel/ApplicationViewModel.cs new file mode 100644 index 00000000..e9a7ec5d --- /dev/null +++ b/src/CalculatorApp.ViewModel/ApplicationViewModel.cs @@ -0,0 +1,323 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using CalculatorApp.ViewModelNative; +using CalculatorApp.ViewModelNative.Common; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Windows.Input; +using Windows.Foundation; +using Windows.Storage; +using Windows.UI.Core; +using Windows.UI.ViewManagement; +using Windows.UI.Xaml; + +namespace CalculatorApp.ViewModel +{ + public class ApplicationViewModel : INotifyPropertyChanged + { + private class CommandWrapper : ICommand + { + private readonly Action action; + + public CommandWrapper(Action action) + { + this.action = action; + } + + public event EventHandler CanExecuteChanged + { + add { } + remove { } + } + + public bool CanExecute(object parameter) + { + return true; + } + + public void Execute(object parameter) + { + this.action(); + } + } + + public const string ModeLocalSettings = "Mode"; + public const string AlwaysOnTopLaunchedLocalSettings = "calculatorAlwaysOnTopLaunched"; + public const string WidthLocalSettings = "calculatorAlwaysOnTopLastWidth"; + public const string HeightLocalSettings = "calculatorAlwaysOnTopLastHeight"; + + public StandardCalculatorViewModel CalculatorViewModel { get; private set; } + public GraphingCalculatorViewModel GraphingCalcViewModel { get; private set; } + public DateCalculatorViewModel DateCalcViewModel { get; private set; } + public UnitConverterViewModel ConverterViewModel { get; private set; } + + public event PropertyChangedEventHandler PropertyChanged; + + private ViewMode mode = ViewMode.None; + public ViewMode Mode + { + get => this.mode; + set + { + if (this.mode != value) + { + this.PreviousMode = this.mode; + this.mode = value; + SetDisplayNormalAlwaysOnTopOption(); + OnModeChanged(); + RaisePropertyChanged(); + } + } + } + + private ViewMode previousMode = ViewMode.None; + public ViewMode PreviousMode + { + get => this.previousMode; + set + { + if (this.previousMode != value) + { + this.previousMode = value; + RaisePropertyChanged(); + } + } + } + + public IList Categories => NavCategoryStates.CreateMenuOptions(); + + private string categoryName; + public string CategoryName + { + get => this.categoryName; + private set + { + if (this.categoryName != value) + { + this.categoryName = value; + RaisePropertyChanged(); + } + } + } + + public Visibility ClearMemoryVisibility + { + get => NavCategory.IsCalculatorViewMode(Mode) ? Visibility.Visible : Visibility.Collapsed; + } + + private bool isAlwaysOnTop; + public bool IsAlwaysOnTop + { + get => this.isAlwaysOnTop; + private set + { + if (this.isAlwaysOnTop != value) + { + this.isAlwaysOnTop = value; + RaisePropertyChanged(); + } + } + } + + private bool displayNormalAlwaysOnTopOption; + // Indicates whether calculator is currently in standard mode _and_ supports CompactOverlay _and_ is not in Always-on-Top mode + public bool DisplayNormalAlwaysOnTopOption + { + get => this.displayNormalAlwaysOnTopOption; + private set + { + if (this.displayNormalAlwaysOnTopOption != value) + { + this.displayNormalAlwaysOnTopOption = value; + RaisePropertyChanged(); + } + } + } + + public void Initialize(ViewMode mode) + { + if (!NavCategoryStates.IsValidViewMode(mode) || !NavCategoryStates.IsViewModeEnabled(mode)) + { + mode = ViewMode.Standard; + } + + try + { + this.Mode = mode; + } + catch (Exception e) + { + TraceLogger.GetInstance().LogError(mode, "ApplicationViewModel::Initialize", e.Message); + if (!TryRecoverFromNavigationModeFailure()) + { + // Could not navigate to standard mode either. + // Throw the original exception so we have a good stack to debug. + throw; + } + } + } + + public ICommand CopyCommand => new CommandWrapper(this.Copy); + public ICommand PasteCommand => new CommandWrapper(this.Paste); + + public void Copy() + { + if (NavCategory.IsConverterViewMode(this.mode)) + { + ConverterViewModel.OnCopyCommand(null); + } + else if (NavCategory.IsDateCalculatorViewMode(this.mode)) + { + DateCalcViewModel.OnCopyCommand(null); + } + else if (NavCategory.IsCalculatorViewMode(this.mode)) + { + CalculatorViewModel.OnCopyCommand(null); + } + } + + public void Paste() + { + if (NavCategory.IsConverterViewMode(this.mode)) + { + ConverterViewModel.OnPasteCommand(null); + } + else if (NavCategory.IsCalculatorViewMode(this.mode)) + { + CalculatorViewModel.OnPasteCommand(null); + } + } + + public async void ToggleAlwaysOnTop(float width, float height) + { + if (ApplicationView.GetForCurrentView().ViewMode == ApplicationViewMode.CompactOverlay) + { + ApplicationDataContainer localSettings = ApplicationData.Current.LocalSettings; + localSettings.Values[WidthLocalSettings] = width; + localSettings.Values[HeightLocalSettings] = height; + + bool success = await ApplicationView.GetForCurrentView().TryEnterViewModeAsync(ApplicationViewMode.Default); + CalculatorViewModel.HistoryVM.AreHistoryShortcutsEnabled = success; + CalculatorViewModel.IsAlwaysOnTop = !success; + this.IsAlwaysOnTop = !success; + } + else + { + ApplicationDataContainer localSettings = ApplicationData.Current.LocalSettings; + ViewModePreferences compactOptions = ViewModePreferences.CreateDefault(ApplicationViewMode.CompactOverlay); + if (!localSettings.Values.ContainsKey(AlwaysOnTopLaunchedLocalSettings)) + { + compactOptions.CustomSize = new Size(320, 394); + localSettings.Values[AlwaysOnTopLaunchedLocalSettings] = true; + } + else + { + if (localSettings.Values.TryGetValue(WidthLocalSettings, out object widthSetting) && localSettings.Values.TryGetValue(HeightLocalSettings, out object heightSetting)) + { + float oldWidth = (float)widthSetting; + float oldHeight = (float)heightSetting; + compactOptions.CustomSize = new Size(oldWidth, oldHeight); + } + else + { + compactOptions.CustomSize = new Size(320, 394); + } + } + + bool success = await ApplicationView.GetForCurrentView().TryEnterViewModeAsync(ApplicationViewMode.CompactOverlay, compactOptions); + CalculatorViewModel.HistoryVM.AreHistoryShortcutsEnabled = !success; + CalculatorViewModel.IsAlwaysOnTop = success; + this.IsAlwaysOnTop = success; + } + SetDisplayNormalAlwaysOnTopOption(); + } + + private void OnModeChanged() + { + Debug.Assert(NavCategoryStates.IsValidViewMode(this.mode)); + if (NavCategory.IsCalculatorViewMode(this.mode)) + { + if (this.CalculatorViewModel is null) + { + this.CalculatorViewModel = new StandardCalculatorViewModel(); + } + this.CalculatorViewModel.SetCalculatorType(this.mode); + } + else if (NavCategory.IsGraphingCalculatorViewMode(this.mode)) + { + if (this.GraphingCalcViewModel is null) + { + this.GraphingCalcViewModel = new GraphingCalculatorViewModel(); + } + } + else if (NavCategory.IsDateCalculatorViewMode(this.mode)) + { + if (this.DateCalcViewModel is null) + { + this.DateCalcViewModel = new DateCalculatorViewModel(); + } + } + else if (NavCategory.IsConverterViewMode(this.mode)) + { + if (this.ConverterViewModel is null) + { + this.ConverterViewModel = new UnitConverterViewModel(); + } + + this.ConverterViewModel.Mode = this.mode; + } + + var resProvider = AppResourceProvider.GetInstance(); + this.CategoryName = resProvider.GetResourceString(NavCategoryStates.GetNameResourceKey(this.mode)); + + // Cast mode to an int in order to save it to app data. + // Save the changed mode, so that the new window launches in this mode. + // Don't save until after we have adjusted to the new mode, so we don't save a mode that fails to load. + ApplicationData.Current.LocalSettings.Values[ModeLocalSettings] = NavCategoryStates.Serialize(this.mode); + + // Log ModeChange event when not first launch, log WindowCreated on first launch + if (NavCategoryStates.IsValidViewMode(this.PreviousMode)) + { + TraceLogger.GetInstance().LogModeChange(this.mode); + } + else + { + TraceLogger.GetInstance().LogWindowCreated(this.mode, ApplicationView.GetApplicationViewIdForWindow(CoreWindow.GetForCurrentThread())); + } + + RaisePropertyChanged(nameof(this.ClearMemoryVisibility)); + } + + private bool TryRecoverFromNavigationModeFailure() + { + // Here we are simply trying to recover from being unable to navigate to a mode. + // Try falling back to standard mode and if there are *any* exceptions, we should + // fail because something is seriously wrong. + try + { + this.Mode = ViewMode.Standard; + return true; + } + catch + { + return false; + } + } + + private void SetDisplayNormalAlwaysOnTopOption() + { + this.DisplayNormalAlwaysOnTopOption = + this.mode == ViewMode.Standard && ApplicationView.GetForCurrentView().IsViewModeSupported(ApplicationViewMode.CompactOverlay) && !this.IsAlwaysOnTop; + } + + private void RaisePropertyChanged([CallerMemberName] string propertyName = null) + { + this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + } +} diff --git a/src/CalculatorApp.ViewModel/CalculatorApp.ViewModel.csproj b/src/CalculatorApp.ViewModel/CalculatorApp.ViewModel.csproj index 4afeea4b..9635bea6 100644 --- a/src/CalculatorApp.ViewModel/CalculatorApp.ViewModel.csproj +++ b/src/CalculatorApp.ViewModel/CalculatorApp.ViewModel.csproj @@ -1,4 +1,4 @@ - + @@ -101,6 +101,7 @@ PackageReference + @@ -131,4 +132,4 @@ --> - + \ No newline at end of file diff --git a/src/CalculatorUnitTests/NavCategoryUnitTests.cpp b/src/CalculatorUnitTests/NavCategoryUnitTests.cpp index e2142e82..73ec37a9 100644 --- a/src/CalculatorUnitTests/NavCategoryUnitTests.cpp +++ b/src/CalculatorUnitTests/NavCategoryUnitTests.cpp @@ -98,7 +98,7 @@ namespace CalculatorUnitTests public: TEST_METHOD(CreateNavCategoryGroup) { - IObservableVector ^ menuOptions = NavCategoryStates::CreateMenuOptions(); + IVector ^ menuOptions = NavCategoryStates::CreateMenuOptions(); VERIFY_ARE_EQUAL(2, menuOptions->Size);