// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. #pragma once #include "CalcManager/UnitConverter.h" #include "Common/Utils.h" #include "Common/NetworkManager.h" #include "Common/Automation/NarratorAnnouncement.h" #include "Common/ConversionResultTaskHelper.h" #include "Common/CalculatorButtonUser.h" #include "Common/NavCategory.h" namespace CalculatorApp { namespace ViewModel { [Windows::UI::Xaml::Data::Bindable] public ref class Category sealed : public Windows::UI::Xaml::Data::INotifyPropertyChanged { internal: Category(const UnitConversionManager::Category& category) : m_original(category) { } public: OBSERVABLE_OBJECT(); property Platform::String^ Name { Platform::String^ get() { return ref new Platform::String(m_original.name.c_str()); } } property Windows::UI::Xaml::Visibility NegateVisibility { Windows::UI::Xaml::Visibility get() { return m_original.supportsNegative ? Windows::UI::Xaml::Visibility::Visible : Windows::UI::Xaml::Visibility::Collapsed; } } internal: const UnitConversionManager::Category& GetModelCategory() const { return m_original; } private: const UnitConversionManager::Category m_original; }; [Windows::UI::Xaml::Data::Bindable] public ref class Unit sealed : public Windows::UI::Xaml::Data::INotifyPropertyChanged { internal: Unit(const UnitConversionManager::Unit& unit) : m_original(unit) { } public: OBSERVABLE_OBJECT(); property Platform::String^ Name { Platform::String^ get() { return ref new Platform::String(m_original.name.c_str()); } } property Platform::String^ AccessibleName { Platform::String^ get() { return ref new Platform::String(m_original.accessibleName.c_str()); } } property Platform::String^ Abbreviation { Platform::String^ get() { return ref new Platform::String(m_original.abbreviation.c_str()); } } // This method is used to return the desired autonamtion name for default unit in UnitConveter combo box. Platform::String^ ToString() override { return AccessibleName; } internal: const UnitConversionManager::Unit& GetModelUnit() const { return m_original; } private: const UnitConversionManager::Unit m_original; }; [Windows::UI::Xaml::Data::Bindable] public ref class SupplementaryResult sealed : public Windows::UI::Xaml::Data::INotifyPropertyChanged { internal: SupplementaryResult(Platform::String^ value, Unit^ unit) : m_Value(value), m_Unit(unit) {} bool IsWhimsical() const { return m_Unit->GetModelUnit().isWhimsical; } Platform::String^ GetLocalizedAutomationName(); public: OBSERVABLE_OBJECT(); OBSERVABLE_PROPERTY_R(Platform::String^, Value); OBSERVABLE_PROPERTY_R(CalculatorApp::ViewModel::Unit^, Unit); }; interface class IActivatable { virtual property bool IsActive; }; template ref class Activatable sealed : public IActivatable { private: TActivatable m_activatable; public: Activatable(TActivatable activatable) : m_activatable(activatable) { } virtual property bool IsActive { bool get() { return m_activatable->IsActive; } void set(bool value) { m_activatable->IsActive = value; } } }; template IActivatable^ AsActivatable(TActivatable activatable) { return ref new Activatable(activatable); } namespace UnitConverterViewModelProperties { extern Platform::StringReference CurrentCategory; extern Platform::StringReference Unit1; extern Platform::StringReference Unit2; extern Platform::StringReference Value1Active; extern Platform::StringReference Value2Active; extern Platform::StringReference SupplementaryVisibility; extern Platform::StringReference SupplementaryResults; extern Platform::StringReference CurrencySymbol1; extern Platform::StringReference CurrencySymbol2; extern Platform::StringReference CurrencySymbolVisibility; extern Platform::StringReference CurrencyRatioEquality; extern Platform::StringReference NetworkBehavior; extern Platform::StringReference CurrencyDataLoadFailed; extern Platform::StringReference CurrencyDataIsWeekOld; extern Platform::StringReference IsCurrencyLoadingVisible; } [Windows::UI::Xaml::Data::Bindable] public ref class UnitConverterViewModel sealed : public Windows::UI::Xaml::Data::INotifyPropertyChanged { internal: UnitConverterViewModel(const std::shared_ptr& model); public: OBSERVABLE_OBJECT_CALLBACK(OnPropertyChanged); OBSERVABLE_PROPERTY_R(Windows::Foundation::Collections::IObservableVector^, Categories); OBSERVABLE_PROPERTY_RW(Category^, CurrentCategory); OBSERVABLE_PROPERTY_RW(CalculatorApp::Common::ViewMode, Mode); OBSERVABLE_PROPERTY_R(Windows::Foundation::Collections::IObservableVector^, Units); OBSERVABLE_PROPERTY_RW(Platform::String^, CurrencySymbol1); OBSERVABLE_PROPERTY_RW(Unit^, Unit1); OBSERVABLE_PROPERTY_RW(Platform::String^, Value1); OBSERVABLE_PROPERTY_RW(Platform::String^, CurrencySymbol2); OBSERVABLE_PROPERTY_RW(Unit^, Unit2); OBSERVABLE_PROPERTY_RW(Platform::String^, Value2); OBSERVABLE_PROPERTY_R(Windows::Foundation::Collections::IObservableVector^, SupplementaryResults); OBSERVABLE_PROPERTY_RW(bool, Value1Active); OBSERVABLE_PROPERTY_RW(bool, Value2Active); OBSERVABLE_PROPERTY_RW(Platform::String^, Value1AutomationName); OBSERVABLE_PROPERTY_RW(Platform::String^, Value2AutomationName); OBSERVABLE_PROPERTY_RW(Platform::String^, Unit1AutomationName); OBSERVABLE_PROPERTY_RW(Platform::String^, Unit2AutomationName); OBSERVABLE_PROPERTY_RW(CalculatorApp::Common::Automation::NarratorAnnouncement^, Announcement); OBSERVABLE_PROPERTY_RW(bool, IsDecimalEnabled); OBSERVABLE_PROPERTY_RW(bool, IsDropDownOpen); OBSERVABLE_PROPERTY_RW(bool, IsDropDownEnabled); OBSERVABLE_PROPERTY_RW(bool, IsCurrencyLoadingVisible); OBSERVABLE_PROPERTY_RW(bool, IsCurrencyCurrentCategory); OBSERVABLE_PROPERTY_RW(Platform::String^, CurrencyRatioEquality); OBSERVABLE_PROPERTY_RW(Platform::String^, CurrencyRatioEqualityAutomationName); OBSERVABLE_PROPERTY_RW(Platform::String^, CurrencyTimestamp); OBSERVABLE_PROPERTY_RW(CalculatorApp::NetworkAccessBehavior, NetworkBehavior); OBSERVABLE_PROPERTY_RW(bool, CurrencyDataLoadFailed); OBSERVABLE_PROPERTY_RW(bool, CurrencyDataIsWeekOld); property Windows::UI::Xaml::Visibility SupplementaryVisibility { Windows::UI::Xaml::Visibility get() { return SupplementaryResults->Size > 0 ? Windows::UI::Xaml::Visibility::Visible : Windows::UI::Xaml::Visibility::Collapsed; } } property Windows::UI::Xaml::Visibility CurrencySymbolVisibility { Windows::UI::Xaml::Visibility get() { return (CurrencySymbol1->IsEmpty() || CurrencySymbol2->IsEmpty()) ? Windows::UI::Xaml::Visibility::Collapsed : Windows::UI::Xaml::Visibility::Visible; } } COMMAND_FOR_METHOD(CategoryChanged, UnitConverterViewModel::OnCategoryChanged); COMMAND_FOR_METHOD(UnitChanged, UnitConverterViewModel::OnUnitChanged); COMMAND_FOR_METHOD(SwitchActive, UnitConverterViewModel::OnSwitchActive); COMMAND_FOR_METHOD(ButtonPressed, UnitConverterViewModel::OnButtonPressed); COMMAND_FOR_METHOD(CopyCommand, UnitConverterViewModel::OnCopyCommand); COMMAND_FOR_METHOD(PasteCommand, UnitConverterViewModel::OnPasteCommand); void AnnounceConversionResult(); internal: void ResetView(); void PopulateData(); NumbersAndOperatorsEnum MapCharacterToButtonId(const wchar_t ch, bool& canSendNegate); void DisplayPasteError(); void OnValueActivated(IActivatable^ control); void OnPaste(Platform::String^ stringToPaste, CalculatorApp::Common::ViewMode mode); void OnCopyCommand(Platform::Object^ parameter); void OnPasteCommand(Platform::Object^ parameter); Platform::String^ GetLocalizedAutomationName(_In_ Platform::String^ displayvalue, _In_ Platform::String^ unitname, _In_ Platform::String^ format); Platform::String^ GetLocalizedConversionResultStringFormat(_In_ Platform::String^ fromValue, _In_ Platform::String^ fromUnit, _In_ Platform::String^ toValue, _In_ Platform::String^ toUnit); void UpdateValue1AutomationName(); void UpdateValue2AutomationName(); Platform::String^ Serialize(); void Deserialize(Platform::String^ state); //Saving And Restoring User Prefernces of Category and Associated-Units across Sessions. void SaveUserPreferences(); void RestoreUserPreferences(); void OnCurrencyDataLoadFinished(bool didLoad); void OnCurrencyTimestampUpdated(_In_ const std::wstring& timestamp, bool isWeekOld); void RefreshCurrencyRatios(); void OnNetworkBehaviorChanged(_In_ CalculatorApp::NetworkAccessBehavior newBehavior); const std::wstring& GetValueFromUnlocalized() const { return m_valueFromUnlocalized; } const std::wstring& GetValueToUnlocalized() const { return m_valueToUnlocalized; } // used by UnitConverterVMCallback void UpdateDisplay(const std::wstring& from, const std::wstring& to); void UpdateSupplementaryResults(const std::vector>& suggestedValues); void OnMaxDigitsReached(); void BuildUnitList(const std::vector& modelUnitList); Unit^ FindUnitInList(UnitConversionManager::Unit target); void SetSelectedUnits(); private: void InitializeView(); void OnPropertyChanged(Platform::String^ prop); void OnCategoryChanged(Platform::Object^ unused); void OnUnitChanged(Platform::Object^ unused); void OnSwitchActive(Platform::Object^ unused); UnitConversionManager::Command CommandFromButtonId(CalculatorApp::NumbersAndOperatorsEnum button); void SupplementaryResultsTimerTick(Windows::System::Threading::ThreadPoolTimer^ timer); void SupplementaryResultsTimerCancel(Windows::System::Threading::ThreadPoolTimer^ timer); void RefreshSupplementaryResults(); void UpdateInputBlocked(_In_ const std::wstring& currencyInput); bool UnitsAreValid(); void OnButtonPressed(Platform::Object^ parameter); Platform::String^ ConvertToLocalizedString(const std::wstring& stringToLocalize, bool allowPartialStrings); void StartConversionResultTimer(); std::shared_ptr m_model; wchar_t m_decimalSeparator; enum class ConversionParameter { Source, Target } m_value1cp; property Platform::String^ ValueFrom { Platform::String^ get() { return m_value1cp == ConversionParameter::Source ? Value1 : Value2; } void set(Platform::String^ value) { m_value1cp == ConversionParameter::Source ? Value1 = value : Value2 = value; } } property Unit^ UnitFrom { Unit^ get() { return m_value1cp == ConversionParameter::Source ? Unit1 : Unit2; } void set(Unit^ value) { m_value1cp == ConversionParameter::Source ? Unit1 = value : Unit2 = value; } } property Platform::String^ ValueTo { Platform::String^ get() { return m_value1cp == ConversionParameter::Target ? Value1 : Value2; } void set(Platform::String^ value) { m_value1cp == ConversionParameter::Target ? Value1 = value : Value2 = value; } } property Unit^ UnitTo { Unit^ get() { return m_value1cp == ConversionParameter::Target ? Unit1 : Unit2; } void set(Unit^ value) { m_value1cp == ConversionParameter::Target ? Unit1 = value : Unit2 = value; } } void SwitchConversionParameters() { m_value1cp = m_value1cp == ConversionParameter::Source ? ConversionParameter::Target : ConversionParameter::Source; } private: bool m_isInputBlocked; Windows::System::Threading::ThreadPoolTimer^ m_supplementaryResultsTimer; bool m_resettingTimer; std::vector> m_cachedSuggestedValues; std::mutex m_cacheMutex; Windows::Globalization::NumberFormatting::DecimalFormatter^ m_decimalFormatter; Windows::Globalization::NumberFormatting::CurrencyFormatter^ m_currencyFormatter; int m_currencyMaxFractionDigits; std::wstring m_valueFromUnlocalized; std::wstring m_valueToUnlocalized; bool m_relocalizeStringOnSwitch; // For Saving the User Preferences only if the Unit convertyer ViewModel is initialised for the first time bool m_IsFirstTime; Platform::String^ m_localizedValueFromFormat; Platform::String^ m_localizedValueFromDecimalFormat; Platform::String^ m_localizedValueToFormat; Platform::String^ m_localizedConversionResultFormat; Platform::String^ m_localizedInputUnitName; Platform::String^ m_localizedOutputUnitName; bool m_isValue1Updating; bool m_isValue2Updating; std::wstring m_lastAnnouncedFrom; std::wstring m_lastAnnouncedTo; Platform::String^ m_lastAnnouncedConversionResult; bool m_isCurrencyDataLoaded; std::unique_ptr m_conversionResultTaskHelper; }; class UnitConverterVMCallback : public UnitConversionManager::IUnitConverterVMCallback { public: UnitConverterVMCallback(UnitConverterViewModel^ viewModel) : m_viewModel(viewModel) {} void DisplayCallback(const std::wstring& from, const std::wstring& to) override { m_viewModel->UpdateDisplay(from, to); } void SuggestedValueCallback( const std::vector>& suggestedValues) override { m_viewModel->UpdateSupplementaryResults(suggestedValues); } void MaxDigitsReached() { m_viewModel->OnMaxDigitsReached(); } private: UnitConverterViewModel^ m_viewModel; }; class ViewModelCurrencyCallback : public UnitConversionManager::IViewModelCurrencyCallback { public: ViewModelCurrencyCallback(UnitConverterViewModel^ viewModel) : m_viewModel(viewModel) {} void CurrencyDataLoadFinished(bool didLoad) override { m_viewModel->OnCurrencyDataLoadFinished(didLoad); } void CurrencySymbolsCallback(const std::wstring& symbol1, const std::wstring& symbol2) override { Platform::String^ sym1 = Platform::StringReference(symbol1.c_str()); Platform::String^ sym2 = Platform::StringReference(symbol2.c_str()); bool value1Active = m_viewModel->Value1Active; m_viewModel->CurrencySymbol1 = value1Active ? sym1 : sym2; m_viewModel->CurrencySymbol2 = value1Active ? sym2 : sym1; } void CurrencyRatiosCallback(_In_ const std::wstring& ratioEquality, _In_ const std::wstring& accRatioEquality) override { m_viewModel->CurrencyRatioEquality = ref new Platform::String(ratioEquality.c_str()); m_viewModel->CurrencyRatioEqualityAutomationName = ref new Platform::String(accRatioEquality.c_str()); } void CurrencyTimestampCallback(_In_ const std::wstring& timestamp, bool isWeekOld) override { m_viewModel->OnCurrencyTimestampUpdated(timestamp, isWeekOld); } void NetworkBehaviorChanged(_In_ int newBehavior) override { m_viewModel->OnNetworkBehaviorChanged(static_cast(newBehavior)); } private: UnitConverterViewModel^ m_viewModel; }; } }