diff --git a/src/CalcViewModel/UnitConverterViewModel.h b/src/CalcViewModel/UnitConverterViewModel.h
index fd62940d..cc6e3a9c 100644
--- a/src/CalcViewModel/UnitConverterViewModel.h
+++ b/src/CalcViewModel/UnitConverterViewModel.h
@@ -35,6 +35,11 @@ namespace CalculatorApp
}
}
+ int GetModelCategoryId()
+ {
+ return GetModelCategory().id;
+ }
+
internal : const UnitConversionManager::Category& GetModelCategory() const
{
return m_original;
@@ -116,7 +121,7 @@ namespace CalculatorApp
OBSERVABLE_PROPERTY_R(CalculatorApp::ViewModel::Unit ^, Unit);
};
- interface class IActivatable
+ public interface class IActivatable
{
virtual property bool IsActive;
};
@@ -232,12 +237,14 @@ namespace CalculatorApp
void AnnounceConversionResult();
+ void OnPaste(Platform::String ^ stringToPaste);
+ void RefreshCurrencyRatios();
+ void OnValueActivated(IActivatable ^ control);
+
internal : void ResetView();
void PopulateData();
NumbersAndOperatorsEnum MapCharacterToButtonId(const wchar_t ch, bool& canSendNegate);
void DisplayPasteError();
- void OnValueActivated(IActivatable ^ control);
- void OnPaste(Platform::String ^ stringToPaste);
void OnCopyCommand(Platform::Object ^ parameter);
void OnPasteCommand(Platform::Object ^ parameter);
@@ -270,7 +277,6 @@ namespace CalculatorApp
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
diff --git a/src/Calculator/Calculator.csproj b/src/Calculator/Calculator.csproj
index 40a0b03b..3fc22df9 100644
--- a/src/Calculator/Calculator.csproj
+++ b/src/Calculator/Calculator.csproj
@@ -227,6 +227,9 @@
TitleBar.xaml
+
+ UnitConverter.xaml
+
@@ -631,6 +634,10 @@
Designer
MSBuild:Compile
+
+ MSBuild:Compile
+ Designer
+
Designer
MSBuild:Compile
@@ -687,6 +694,10 @@
Designer
MSBuild:Compile
+
+ MSBuild:Compile
+ Designer
+
diff --git a/src/Calculator/Views/DelighterUnitStyles.xaml b/src/Calculator/Views/DelighterUnitStyles.xaml
new file mode 100644
index 00000000..ed8c376e
--- /dev/null
+++ b/src/Calculator/Views/DelighterUnitStyles.xaml
@@ -0,0 +1,216 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Calculator/Views/MainPage.xaml b/src/Calculator/Views/MainPage.xaml
index cba7d6bc..c0febd59 100644
--- a/src/Calculator/Views/MainPage.xaml
+++ b/src/Calculator/Views/MainPage.xaml
@@ -20,11 +20,11 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Calculator/Views/UnitConverter.xaml.cs b/src/Calculator/Views/UnitConverter.xaml.cs
new file mode 100644
index 00000000..f1847ecf
--- /dev/null
+++ b/src/Calculator/Views/UnitConverter.xaml.cs
@@ -0,0 +1,386 @@
+using CalculatorApp.Common;
+using CalculatorApp.Controls;
+using CalculatorApp.ViewModel;
+using System;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Threading.Tasks;
+using Windows.Foundation;
+using Windows.System;
+using Windows.UI.ViewManagement;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Automation;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Input;
+
+// The User Control item template is documented at https://go.microsoft.com/fwlink/?LinkId=234236
+
+namespace CalculatorApp
+{
+ class Activatable : ViewModel.IActivatable
+ {
+ public Activatable(Func getter, Action setter)
+ {
+ m_getter = getter;
+ m_setter = setter;
+ }
+
+ public bool IsActive
+ {
+ get => m_getter();
+ set => m_setter(value);
+ }
+
+ private Func m_getter;
+ private Action m_setter;
+ }
+
+ public sealed partial class UnitConverter : UserControl
+ {
+ public UnitConverter()
+ {
+ this.InitializeComponent();
+ }
+
+ public Windows.UI.Xaml.HorizontalAlignment FlowDirectionHorizontalAlignment
+ {
+ get => this.m_FlowDirectionHorizontalAlignment;
+ }
+
+ private Windows.UI.Xaml.HorizontalAlignment m_FlowDirectionHorizontalAlignment;
+
+ public void AnimateConverter()
+ {
+ if (uiSettings.Value.AnimationsEnabled)
+ {
+ AnimationStory.Begin();
+ }
+ }
+
+ public CalculatorApp.ViewModel.UnitConverterViewModel Model
+ {
+ get => (CalculatorApp.ViewModel.UnitConverterViewModel)this.DataContext;
+ }
+
+ public Windows.UI.Xaml.FlowDirection LayoutDirection
+ {
+ get => this.m_layoutDirection;
+ }
+
+ public void SetDefaultFocus()
+ {
+ Control[] focusPrecedence = new Control[] { Value1, CurrencyRefreshBlockControl, OfflineBlock, ClearEntryButtonPos0 };
+
+ foreach (Control control in focusPrecedence)
+ {
+ if (control.Focus(FocusState.Programmatic))
+ {
+ break;
+ }
+ }
+ }
+
+ private void OnValueKeyDown(object sender, Windows.UI.Xaml.Input.KeyRoutedEventArgs e)
+ {
+ if (e.Key == VirtualKey.Space)
+ {
+ OnValueSelected(sender);
+ }
+ }
+
+ private void OnContextRequested(UIElement sender, ContextRequestedEventArgs e)
+ {
+ OnValueSelected(sender);
+ var requestedElement = ((FrameworkElement)sender);
+
+ PasteMenuItem.IsEnabled = CopyPasteManager.HasStringToPaste();
+
+ Point point;
+ if (e.TryGetPosition(requestedElement, out point))
+ {
+ m_resultsFlyout.ShowAt(requestedElement, point);
+ }
+ else
+ {
+ // Not invoked via pointer, so let XAML choose a default location.
+ m_resultsFlyout.ShowAt(requestedElement);
+ }
+
+ e.Handled = true;
+ }
+
+ private void OnContextCanceled(UIElement sender, RoutedEventArgs e)
+ {
+ m_resultsFlyout.Hide();
+ }
+
+ private void OnCopyMenuItemClicked(object sender, RoutedEventArgs e)
+ {
+ var calcResult = ((CalculationResult)m_resultsFlyout.Target);
+ CopyPasteManager.CopyToClipboard(calcResult.GetRawDisplayValue());
+ }
+
+ void OnPasteMenuItemClicked(object sender, RoutedEventArgs e)
+ {
+ UnitConverter that = this;
+ _ = Task.Run(async () =>
+ {
+ string pastedString = await CopyPasteManager.GetStringToPaste(Model.Mode, CategoryGroupType.Converter, NumberBase.Unknown, BitLength.BitLengthUnknown);
+ that.Model.OnPaste(pastedString);
+ });
+ }
+
+ private void OnValueSelected(object sender)
+ {
+ var value = ((CalculationResult)sender);
+ // update the font size since the font is changed to bold
+ value.UpdateTextState();
+ ((UnitConverterViewModel)this.DataContext).OnValueActivated(new Activatable(() => value.IsActive, flag => value.IsActive = flag));
+ }
+
+ private void UpdateDropDownState(object sender, object e)
+ {
+ ((UnitConverterViewModel)this.DataContext).IsDropDownOpen = (Units1.IsDropDownOpen) || (Units2.IsDropDownOpen);
+ KeyboardShortcutManager.UpdateDropDownState((Units1.IsDropDownOpen) || (Units2.IsDropDownOpen));
+ }
+
+ private void OnLoaded(object sender, RoutedEventArgs e)
+ {
+ }
+
+ private void CurrencyRefreshButton_Click(object sender, RoutedEventArgs e)
+ {
+ // If IsCurrencyLoadingVisible is true that means CurrencyRefreshButton_Click was recently called
+ // and is still executing. In this case there is no reason to process the click.
+ if (!Model.IsCurrencyLoadingVisible)
+ {
+ if (Model.NetworkBehavior == NetworkAccessBehavior.OptIn)
+ {
+ m_meteredConnectionOverride = true;
+ }
+
+ Model.RefreshCurrencyRatios();
+ }
+ }
+
+ private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ string propertyName = e.PropertyName;
+ if (propertyName == UnitConverterViewModel.NetworkBehaviorPropertyName || propertyName == UnitConverterViewModel.CurrencyDataLoadFailedPropertyName)
+ {
+ OnNetworkBehaviorChanged();
+ }
+ else if (propertyName == UnitConverterViewModel.CurrencyDataIsWeekOldPropertyName)
+ {
+ SetCurrencyTimestampFontWeight();
+ }
+ else if (
+ propertyName == UnitConverterViewModel.IsCurrencyLoadingVisiblePropertyName
+ || propertyName == UnitConverterViewModel.IsCurrencyCurrentCategoryPropertyName)
+ {
+ OnIsDisplayVisibleChanged();
+ }
+ }
+
+ private void OnDataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args)
+ {
+ // CSHARP_MIGRATION: TODO: double check the original code with below migrated code
+ Model.PropertyChanged -= OnPropertyChanged;
+ Model.PropertyChanged += OnPropertyChanged;
+
+ OnNetworkBehaviorChanged();
+ }
+
+ private void OnIsDisplayVisibleChanged()
+ {
+ if (!Model.IsCurrencyCurrentCategory)
+ {
+ VisualStateManager.GoToState(this, UnitLoadedState.Name, false);
+ }
+ else
+ {
+ if (Model.IsCurrencyLoadingVisible)
+ {
+ VisualStateManager.GoToState(this, UnitNotLoadedState.Name, false);
+ StartProgressRingWithDelay();
+ }
+ else
+ {
+ HideProgressRing();
+ VisualStateManager.GoToState(this, !string.IsNullOrEmpty(Model.CurrencyTimestamp) ? UnitLoadedState.Name : UnitNotLoadedState.Name, true);
+ }
+ }
+ }
+
+ private void Units1_IsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e)
+ {
+ if ((Units1.Visibility == Visibility.Visible) && Units1.IsEnabled)
+ {
+ SetDefaultFocus();
+ }
+ }
+
+ private void OnNetworkBehaviorChanged()
+ {
+ switch (Model.NetworkBehavior)
+ {
+ case NetworkAccessBehavior.Normal:
+ OnNormalNetworkAccess();
+ break;
+ case NetworkAccessBehavior.OptIn:
+ OnOptInNetworkAccess();
+ break;
+ case NetworkAccessBehavior.Offline:
+ OnOfflineNetworkAccess();
+ break;
+ }
+ }
+
+ private void OnNormalNetworkAccess()
+ {
+ CurrencyRefreshBlockControl.Visibility = Visibility.Visible;
+ OfflineBlock.Visibility = Visibility.Collapsed;
+
+ if (Model.CurrencyDataLoadFailed)
+ {
+ SetFailedToRefreshStatus();
+ }
+ else
+ {
+ SetNormalCurrencyStatus();
+ }
+ }
+
+ void OnOptInNetworkAccess()
+ {
+ CurrencyRefreshBlockControl.Visibility = Visibility.Visible;
+ OfflineBlock.Visibility = Visibility.Collapsed;
+
+ if (m_meteredConnectionOverride && Model.CurrencyDataLoadFailed)
+ {
+ SetFailedToRefreshStatus();
+ }
+ else
+ {
+ SetChargesMayApplyStatus();
+ }
+ }
+
+ void OnOfflineNetworkAccess()
+ {
+ CurrencyRefreshBlockControl.Visibility = Visibility.Collapsed;
+ OfflineBlock.Visibility = Visibility.Visible;
+ }
+
+ private void InitializeOfflineStatusTextBlock()
+ {
+ var resProvider = AppResourceProvider.GetInstance();
+ string offlineStatusHyperlinkText = resProvider.GetResourceString("OfflineStatusHyperlinkText");
+
+ // The resource string has the 'NetworkSettings' hyperlink wrapped with '%HL%'.
+ // Break the string and assign pieces appropriately.
+ const string delimiter = "%HL%";
+ int delimiterLength = delimiter.Length;
+
+ // Find the delimiters.
+ int firstSplitPosition = offlineStatusHyperlinkText.IndexOf(delimiter);
+ Debug.Assert(firstSplitPosition != -1);
+ int secondSplitPosition = offlineStatusHyperlinkText.IndexOf(delimiter, firstSplitPosition + 1);
+ Debug.Assert(secondSplitPosition != -1);
+ int hyperlinkTextLength = secondSplitPosition - (firstSplitPosition + delimiterLength);
+
+ // Assign pieces.
+ var offlineStatusTextBeforeHyperlink = offlineStatusHyperlinkText.Substring(0, firstSplitPosition);
+ var offlineStatusTextLink = offlineStatusHyperlinkText.Substring(firstSplitPosition + delimiterLength, hyperlinkTextLength);
+ var offlineStatusTextAfterHyperlink = offlineStatusHyperlinkText.Substring(secondSplitPosition + delimiterLength);
+
+ OfflineRunBeforeLink.Text = offlineStatusTextBeforeHyperlink;
+ OfflineRunLink.Text = offlineStatusTextLink;
+ OfflineRunAfterLink.Text = offlineStatusTextAfterHyperlink;
+
+ AutomationProperties.SetName(OfflineBlock, offlineStatusTextBeforeHyperlink + " " + offlineStatusTextLink + " " + offlineStatusTextAfterHyperlink);
+ }
+
+ private void SetNormalCurrencyStatus()
+ {
+ CurrencySecondaryStatus.Text = "";
+ }
+
+ private void SetChargesMayApplyStatus()
+ {
+ VisualStateManager.GoToState(this, "ChargesMayApplyCurrencyStatus", false);
+ CurrencySecondaryStatus.Text = m_chargesMayApplyText;
+ }
+
+ private void SetFailedToRefreshStatus()
+ {
+ VisualStateManager.GoToState(this, "FailedCurrencyStatus", false);
+ CurrencySecondaryStatus.Text = m_failedToRefreshText;
+ }
+
+ private void SetCurrencyTimestampFontWeight()
+ {
+ if (Model.CurrencyDataIsWeekOld)
+ {
+ VisualStateManager.GoToState(this, "WeekOldTimestamp", false);
+ }
+ else
+ {
+ VisualStateManager.GoToState(this, "DefaultTimestamp", false);
+ }
+ }
+
+ private void StartProgressRingWithDelay()
+ {
+ HideProgressRing();
+
+ TimeSpan delay = TimeSpan.FromMilliseconds(500);
+
+ m_delayTimer = new DispatcherTimer();
+ m_delayTimer.Interval = delay;
+ m_delayTimer.Tick += OnDelayTimerTick;
+
+ m_delayTimer.Start();
+ }
+
+ private void OnDelayTimerTick(object sender, object e)
+ {
+ CurrencyLoadingProgressRing.IsActive = true;
+ m_delayTimer.Stop();
+ }
+
+ private void HideProgressRing()
+ {
+ if (m_delayTimer != null)
+ {
+ m_delayTimer.Stop();
+ }
+
+ CurrencyLoadingProgressRing.IsActive = false;
+ }
+
+ private void SupplementaryResultsPanelInGrid_SizeChanged(object sender, Windows.UI.Xaml.SizeChangedEventArgs e)
+ {
+ // We add 0.01 to be sure to not create an infinite loop with SizeChanged events cascading due to float approximation
+ RowDltrUnits.MinHeight = Math.Max(48.0, e.NewSize.Height + 0.01);
+ }
+
+ private void OnVisualStateChanged(object sender, Windows.UI.Xaml.VisualStateChangedEventArgs e)
+ {
+ var mode = NavCategory.Deserialize(Model.CurrentCategory.GetModelCategoryId());
+ TraceLogger.GetInstance().LogVisualStateChanged(mode, e.NewState.Name, false);
+ }
+
+ private static Lazy uiSettings = new Lazy(true);
+ private Windows.UI.Xaml.FlowDirection m_layoutDirection;
+ private Windows.UI.Xaml.Controls.MenuFlyout m_resultsFlyout;
+
+ private string m_chargesMayApplyText;
+ private string m_failedToRefreshText;
+
+ private bool m_meteredConnectionOverride;
+
+ private Windows.UI.Xaml.DispatcherTimer m_delayTimer;
+ }
+}
+