diff --git a/src/Calculator.Droid/Calculator.Droid.csproj b/src/Calculator.Droid/Calculator.Droid.csproj
index b9661773..799ce13e 100644
--- a/src/Calculator.Droid/Calculator.Droid.csproj
+++ b/src/Calculator.Droid/Calculator.Droid.csproj
@@ -20,6 +20,7 @@
Properties\AndroidManifest.xml
True
..\Calculator.Shared\Strings
+ 7.3
true
diff --git a/src/Calculator.Shared/CalcManager/CalculatorList.cs b/src/Calculator.Shared/CalcManager/CalculatorList.cs
index 00b58e90..5b77304c 100644
--- a/src/Calculator.Shared/CalcManager/CalculatorList.cs
+++ b/src/Calculator.Shared/CalcManager/CalculatorList.cs
@@ -5,7 +5,7 @@ using System.Text;
namespace CalculatorApp
{
- public class CalculatorList : IEnumerable, IEnumerable
+ public class CalculatorList : IEnumerable, IEnumerable
{
List m_vector;
@@ -19,7 +19,22 @@ namespace CalculatorApp
m_vector = new List(source.m_vector);
}
- public bool GetAt(int index, out TType item)
+ public TType this[int index]
+ {
+ get => m_vector[index];
+ }
+
+ public TType this[uint index]
+ {
+ get => m_vector[(int)index];
+ }
+
+ public TType At(int index)
+ {
+ return m_vector[index];
+ }
+
+ public bool GetAt(int index, out TType item)
{
item = m_vector[index];
return true;
@@ -70,12 +85,27 @@ namespace CalculatorApp
return true;
}
- public bool Append(TType item)
+ public void Add(TType item)
{
m_vector.Add(item);
- return true;
}
+ public bool Append(TType item)
+ {
+ m_vector.Add(item);
+ return true;
+ }
+
+ public void EmplaceBack(TType item)
+ {
+ m_vector.Add(item);
+ }
+
+ public void PushBack(TType item)
+ {
+ m_vector.Add(item);
+ }
+
public bool RemoveAtEnd()
{
m_vector.RemoveAt(m_vector.Count - 1);
@@ -88,6 +118,28 @@ namespace CalculatorApp
return true;
}
+ public bool IsEmpty()
+ {
+ return m_vector.Count == 0;
+ }
+
+ public uint Size()
+ {
+ return (uint)m_vector.Count;
+ }
+
+ public void Sort(Func comparison)
+ {
+ m_vector.Sort((t1, t2) => comparison(t1, t2) ? -1 : 1);
+ }
+
+ public int IndexOf(TType item)
+ {
+ return m_vector.IndexOf(item);
+ }
+
+ public int Count => m_vector.Count;
+
public bool GetString(out string expression)
{
// UNO TODO
diff --git a/src/Calculator.Shared/CalcManager/CalculatorObservableCollection.cs b/src/Calculator.Shared/CalcManager/CalculatorObservableCollection.cs
new file mode 100644
index 00000000..0e02de9a
--- /dev/null
+++ b/src/Calculator.Shared/CalcManager/CalculatorObservableCollection.cs
@@ -0,0 +1,19 @@
+using System;
+using System.Collections.ObjectModel;
+using System.Linq;
+
+namespace CalculatorApp
+{
+ public class CalculatorObservableCollection : ObservableCollection
+ {
+ public void Append(TType item)
+ {
+ Add(item);
+ }
+
+ public TType GetAt(int index)
+ {
+ return this[index];
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Calculator.Shared/CalcManager/UnitConversionManager.cs b/src/Calculator.Shared/CalcManager/UnitConversionManager.cs
index d193f26e..6aeaec41 100644
--- a/src/Calculator.Shared/CalcManager/UnitConversionManager.cs
+++ b/src/Calculator.Shared/CalcManager/UnitConversionManager.cs
@@ -1,10 +1,30 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using Calculator;
+using CalculatorApp;
+using CategorySelectionInitializer = System.Tuple, UnitConversionManager.Unit, UnitConversionManager.Unit>;
+using UnitToUnitToConversionDataMap = System.Collections.Generic.Dictionary>;
+using CategoryToUnitVectorMap = System.Collections.Generic.Dictionary>;
namespace UnitConversionManager
{
- public struct Unit
+ public class Unit
{
- public Unit(int id, string name, string abbreviation, bool isConversionSource, bool isConversionTarget, bool isWhimsical)
+ public static readonly Unit EMPTY_UNIT = new Unit(-1, "", "", true, true, false);
+
+ public Unit()
+ {
+ }
+
+ public Unit(int id, string name, string abbreviation, bool isConversionSource, bool isConversionTarget, bool isWhimsical)
{
this.id = id;
this.name = name;
@@ -37,22 +57,1420 @@ namespace UnitConversionManager
accessibleName = nameValue1 + " " + nameValue2;
}
- public readonly int id;
- public readonly string name;
- public readonly string accessibleName;
- public readonly string abbreviation;
- public readonly bool isConversionSource;
- public readonly bool isConversionTarget;
- public readonly bool isWhimsical;
+ public int id;
+ public string name;
+ public string accessibleName;
+ public string abbreviation;
+ public bool isConversionSource;
+ public bool isConversionTarget;
+ public bool isWhimsical;
- // bool operator !=(Unit that) const
- // {
- // return that.id != id;
- // }
+ ///
+ public override bool Equals(object obj)
+ => obj is Unit other && id == other.id;
- //bool operator ==(Unit that) const
- // {
- // return that.id == id;
- // }
- }
-}
\ No newline at end of file
+ public override int GetHashCode()
+ => id;
+
+ public static bool operator !=(Unit left, Unit right)
+ {
+ return left.id != right.id;
+ }
+
+ public static bool operator ==(Unit left, Unit right)
+ {
+ return left.id == right.id;
+ }
+
+ // class UnitHash
+ public static explicit operator int(Unit x)
+ {
+ return x.id;
+ }
+ }
+
+ public struct Category
+ {
+ public Category(int id, string name, bool supportsNegative)
+ {
+ this.id = id;
+ this.name = name;
+ this.supportsNegative = supportsNegative;
+ }
+
+ public int id;
+ public string name;
+ public bool supportsNegative;
+
+ ///
+ public override bool Equals(object obj)
+ => obj is Category other
+ && id == other.id;
+
+ ///
+ public override int GetHashCode()
+ => id;
+
+ public static bool operator!=(Category left, Category right)
+ {
+ return left.id != right.id;
+ }
+
+ public static bool operator ==(Category left, Category right)
+ {
+ return left.id == right.id;
+ }
+
+ // class CategoryHash
+ public static explicit operator int(Category x)
+ {
+ return x.id;
+ }
+ }
+
+ public struct SuggestedValueIntermediate
+ {
+ public double magnitude;
+ public double value;
+ public Unit type;
+ };
+
+ public class ConversionData
+ {
+ public ConversionData()
+ {
+ }
+
+ public ConversionData(double ratio, double offset, bool offsetFirst)
+ {
+ this.ratio = ratio;
+ this.offset = offset;
+ this.offsetFirst = offsetFirst;
+ }
+
+ public double ratio;
+ public double offset;
+ public bool offsetFirst;
+ };
+
+ public struct CurrencyStaticData
+ {
+ public CurrencyStaticData(
+ string countryCode,
+ string countryName,
+ string currencyCode,
+ string currencyName,
+ string currencySymbol)
+ {
+ this.countryCode = countryCode;
+ this.countryName = countryName;
+ this.currencyCode = currencyCode;
+ this.currencyName = currencyName;
+ this.currencySymbol = currencySymbol;
+ }
+
+ public string countryCode;
+ public string countryName;
+ public string currencyCode;
+ public string currencyName;
+ public string currencySymbol;
+ };
+
+ public struct CurrencyRatio
+ {
+ public CurrencyRatio(
+ double ratio,
+ string sourceCurrencyCode,
+ string targetCurrencyCode)
+ {
+ this.ratio = ratio;
+ this.sourceCurrencyCode = sourceCurrencyCode;
+ this.targetCurrencyCode = targetCurrencyCode;
+ }
+
+ public double ratio;
+ public string sourceCurrencyCode;
+ public string targetCurrencyCode;
+ };
+
+ // typedef std.tuple, UnitConversionManager.Unit, UnitConversionManager.Unit> CategorySelectionInitializer;
+ // typedef std.unordered_map<
+ // UnitConversionManager.Unit,
+ // std.unordered_map,
+ // UnitConversionManager.UnitHash>
+ // UnitToUnitToConversionDataMap;
+ // typedef std.unordered_map, UnitConversionManager.CategoryHash>
+ // CategoryToUnitVectorMap;
+ //
+
+ public interface IViewModelCurrencyCallback
+ {
+ void CurrencyDataLoadFinished(bool didLoad);
+ void CurrencySymbolsCallback(string fromSymbol, string toSymbol);
+ void CurrencyRatiosCallback(string ratioEquality, string accRatioEquality);
+ void CurrencyTimestampCallback(string timestamp, bool isWeekOldData);
+ void NetworkBehaviorChanged( int newBehavior);
+ };
+
+ public interface IConverterDataLoader
+ {
+ void LoadData(); // prepare data if necessary before calling other functions
+ CalculatorList LoadOrderedCategories();
+ CalculatorList LoadOrderedUnits(Category c);
+ Dictionary LoadOrderedRatios(Unit u);
+ bool SupportsCategory(Category target);
+ };
+
+ public interface ICurrencyConverterDataLoader
+ {
+ void SetViewModelCallback(IViewModelCurrencyCallback callback);
+ KeyValuePair GetCurrencySymbols( Unit unit1, Unit unit2);
+ KeyValuePair GetCurrencyRatioEquality( Unit unit1, Unit unit2);
+ string GetCurrencyTimestamp();
+
+ Task TryLoadDataFromCacheAsync();
+ Task TryLoadDataFromWebAsync();
+ Task TryLoadDataFromWebOverrideAsync();
+ };
+
+ public interface IUnitConverterVMCallback
+ {
+ void DisplayCallback(string from, string to);
+ void SuggestedValueCallback(CalculatorList> suggestedValues);
+ void MaxDigitsReached();
+ };
+
+ public interface IUnitConverter
+ {
+ void Initialize(); // Use to initialize first time, use deserialize instead to rehydrate
+ CalculatorList GetCategories();
+ CategorySelectionInitializer SetCurrentCategory(Category input);
+ Category GetCurrentCategory();
+ void SetCurrentUnitTypes(Unit fromType, Unit toType);
+ void SwitchActive(string newValue);
+ string Serialize();
+ void DeSerialize(string serializedData);
+ string SaveUserPreferences();
+ void RestoreUserPreferences( string userPreferences);
+ void SendCommand(Command command);
+ void SetViewModelCallback(IUnitConverterVMCallback newCallback);
+ void SetViewModelCurrencyCallback(IViewModelCurrencyCallback newCallback);
+ Task> RefreshCurrencyRatios();
+ void Calculate();
+ void ResetCategoriesAndRatios();
+ };
+
+ // .h
+ public partial class UnitConverter : IUnitConverter//, public std.enable_shared_from_this
+ {
+ //public UnitConverter(IConverterDataLoader dataLoader);
+ //public UnitConverter(IConverterDataLoader dataLoader, IConverterDataLoader currencyDataLoader);
+
+ // IUnitConverter
+ //public partial void Initialize() ;
+ //public partial CalculatorList GetCategories() ;
+ //public partial CategorySelectionInitializer SetCurrentCategory(Category input) ;
+ //public partial Category GetCurrentCategory() ;
+ //public partial void SetCurrentUnitTypes(Unit fromType, Unit toType) ;
+ //public partial void SwitchActive(string newValue) ;
+ //public partial string Serialize() ;
+ //public partial void DeSerialize(string serializedData) ;
+ //public partial string SaveUserPreferences() ;
+ //public partial void RestoreUserPreferences(string userPreference) ;
+ //public partial void SendCommand(Command command) ;
+ //public partial void SetViewModelCallback( IUnitConverterVMCallback newCallback) ;
+ //public partial void SetViewModelCurrencyCallback( IViewModelCurrencyCallback newCallback) ;
+ //public partial Task> RefreshCurrencyRatios() ;
+ //public partial void Calculate() ;
+ //public partial void ResetCategoriesAndRatios() ;
+ //// IUnitConverter
+
+ //static CalculatorList StringToVector(string w, char delimiter, bool addRemainder = false);
+ //static string Quote(string s);
+ //static string Unquote(string s);
+
+ //private:
+ // bool CheckLoad();
+ // double Convert(double value, ConversionData conversionData);
+ // CalculatorList> CalculateSuggested();
+ // void ClearValues();
+ // void TrimString(string& input);
+ // void InitializeSelectedUnits();
+ // string RoundSignificant(double num, int numSignificant);
+ // Category StringToCategory(string w);
+ // string CategoryToString(Category c, char delimiter);
+ // string UnitToString(Unit u, char delimiter);
+ // Unit StringToUnit(string w);
+ // ConversionData StringToConversionData(string w);
+ // string ConversionDataToString(ConversionData d, char delimiter);
+ // void UpdateCurrencySymbols();
+ // void UpdateViewModel();
+ // bool AnyUnitIsEmpty();
+ // std.IConverterDataLoader GetDataLoaderForCategory(Category category);
+ // std.ICurrencyConverterDataLoader GetCurrencyConverterDataLoader();
+
+ private IConverterDataLoader m_dataLoader;
+ private IConverterDataLoader m_currencyDataLoader;
+ private IUnitConverterVMCallback m_vmCallback;
+ private IViewModelCurrencyCallback m_vmCurrencyCallback;
+ private CalculatorList m_categories = new CalculatorList();
+ private CategoryToUnitVectorMap m_categoryToUnits = new CategoryToUnitVectorMap(EqualityComparer.Default);
+ private UnitToUnitToConversionDataMap m_ratioMap = new UnitToUnitToConversionDataMap(EqualityComparer.Default);
+ private Category m_currentCategory;
+ private Unit m_fromType = Unit.EMPTY_UNIT;
+ private Unit m_toType = Unit.EMPTY_UNIT;
+ private string m_currentDisplay;
+ private string m_returnDisplay;
+ private bool m_currentHasDecimal;
+ private bool m_returnHasDecimal;
+ private bool m_switchedActive;
+ };
+
+ // .cpp
+ partial class UnitConverter
+ {
+ private static readonly Unit EMPTY_UNIT = Unit.EMPTY_UNIT;
+
+ const uint EXPECTEDSERIALIZEDTOKENCOUNT = 7;
+ const uint EXPECTEDSERIALIZEDCONVERSIONDATATOKENCOUNT = 3;
+ const uint EXPECTEDSERIALIZEDCATEGORYTOKENCOUNT = 3;
+ const uint EXPECTEDSERIALIZEDUNITTOKENCOUNT = 6;
+ const uint EXPECTEDSTATEDATATOKENCOUNT = 5;
+ const uint EXPECTEDMAPCOMPONENTTOKENCOUNT = 2;
+
+ static int MAXIMUMDIGITSALLOWED = 15;
+ static int OPTIMALDIGITSALLOWED = 7;
+
+ static char LEFTESCAPECHAR = '{';
+ static char RIGHTESCAPECHAR = '}';
+
+ static readonly double OPTIMALDECIMALALLOWED = Math.Pow(10, -1 * (OPTIMALDIGITSALLOWED - 1));
+ static readonly double MINIMUMDECIMALALLOWED = Math.Pow(10, -1 * (MAXIMUMDIGITSALLOWED - 1));
+
+ Dictionary quoteConversions = new Dictionary();
+ Dictionary unquoteConversions = new Dictionary();
+
+ ///
+ /// Constructor, sets up all the variables and requires a configLoader
+ ///
+ /// An instance of the IConverterDataLoader interface which we use to read in category/unit names and conversion data
+ public UnitConverter( IConverterDataLoader dataLoader)
+ : this(dataLoader, null)
+ {
+ }
+
+ ///
+ /// Constructor, sets up all the variables and requires two configLoaders
+ ///
+ /// An instance of the IConverterDataLoader interface which we use to read in category/unit names and conversion data
+ /// An instance of the IConverterDataLoader interface, specialized for loading currency data from an internet service
+ public UnitConverter( IConverterDataLoader dataLoader, IConverterDataLoader currencyDataLoader)
+ {
+ m_dataLoader = dataLoader;
+ m_currencyDataLoader = currencyDataLoader;
+ // declaring the delimiter character conversion map
+ quoteConversions['|'] = "{p}";
+ quoteConversions['['] = "{lc}";
+ quoteConversions[']'] = "{rc}";
+ quoteConversions[':'] = "{co}";
+ quoteConversions[','] = "{cm}";
+ quoteConversions[';'] = "{sc}";
+ quoteConversions[LEFTESCAPECHAR] = "{lb}";
+ quoteConversions[RIGHTESCAPECHAR] = "{rb}";
+ unquoteConversions["{p}"] = '|';
+ unquoteConversions["{lc}"] = '[';
+ unquoteConversions["{rc}"] = ']';
+ unquoteConversions["{co}"] = ':';
+ unquoteConversions["{cm}"] = ',';
+ unquoteConversions["{sc}"] = ';';
+ unquoteConversions["{lb}"] = LEFTESCAPECHAR;
+ unquoteConversions["{rb}"] = RIGHTESCAPECHAR;
+ ClearValues();
+ ResetCategoriesAndRatios();
+ }
+
+ public void Initialize()
+ {
+ m_dataLoader.LoadData();
+ }
+
+ bool CheckLoad()
+ {
+ if (m_categories.IsEmpty())
+ {
+ ResetCategoriesAndRatios();
+ }
+ return !m_categories.IsEmpty();
+ }
+
+ ///
+ /// Returns a list of the categories in use by this converter
+ ///
+ public CalculatorList GetCategories()
+ {
+ CheckLoad();
+ return m_categories;
+ }
+
+ ///
+ /// Sets the current category in use by this converter,
+ /// and returns a list of unit types that exist under the given category.
+ ///
+ /// Category struct which we are setting
+ public CategorySelectionInitializer SetCurrentCategory(Category input)
+ {
+ if (m_currencyDataLoader != null && m_currencyDataLoader.SupportsCategory(input))
+ {
+ m_currencyDataLoader.LoadData();
+ }
+
+ CalculatorList newUnitList = new CalculatorList();
+ if (CheckLoad())
+ {
+ if (m_currentCategory.id != input.id)
+ {
+ CalculatorList unitVector = m_categoryToUnits[m_currentCategory];
+ for (uint i = 0; i < unitVector.Size(); i++)
+ {
+ unitVector[i].isConversionSource = (unitVector[i].id == m_fromType.id);
+ unitVector[i].isConversionTarget = (unitVector[i].id == m_toType.id);
+ }
+ m_currentCategory = input;
+ if (!m_currentCategory.supportsNegative && m_currentDisplay.First() == '-')
+ {
+ m_currentDisplay = m_currentDisplay.Remove(0, 1);
+ }
+ }
+
+ newUnitList = m_categoryToUnits[input];
+ }
+
+ InitializeSelectedUnits();
+ return Tuple.Create(newUnitList, m_fromType, m_toType);
+ }
+
+ ///
+ /// Gets the category currently being used
+ ///
+ public Category GetCurrentCategory()
+ {
+ return m_currentCategory;
+ }
+
+ ///
+ /// Sets the current unit types to be used, indicates a likely change in the
+ /// display values, so we re-calculate and callback the updated values
+ ///
+ /// Unit struct which the user is modifying
+ /// Unit struct we are converting to
+ public void SetCurrentUnitTypes(Unit fromType, Unit toType)
+ {
+ if (!CheckLoad())
+ {
+ return;
+ }
+
+ m_fromType = fromType;
+ m_toType = toType;
+ Calculate();
+
+ UpdateCurrencySymbols();
+ }
+
+ ///
+ /// Switches the active field, indicating that we are now entering data into
+ /// what was originally the return field, and storing results into what was
+ /// originally the current field. We swap appropriate values,
+ /// but do not callback, as values have not changed.
+ ///
+ ///
+ /// string representing the value user had in the field they've just activated.
+ /// We use this to handle cases where the front-end may choose to trim more digits
+ /// than we have been storing internally, in which case appending will not function
+ /// as expected without the use of this parameter.
+ ///
+ public void SwitchActive(string newValue)
+ {
+ if (!CheckLoad())
+ {
+ return;
+ }
+
+ Utils.Swap(ref m_fromType, ref m_toType);
+ Utils.Swap(ref m_currentHasDecimal, ref m_returnHasDecimal);
+ m_returnDisplay = m_currentDisplay;
+ m_currentDisplay = newValue;
+ m_currentHasDecimal = (m_currentDisplay.IndexOf('.') != m_currentDisplay.npos());
+ m_switchedActive = true;
+
+ if (m_currencyDataLoader != null && m_vmCurrencyCallback != null)
+ {
+ ICurrencyConverterDataLoader currencyDataLoader = GetCurrencyConverterDataLoader();
+ KeyValuePair currencyRatios = currencyDataLoader.GetCurrencyRatioEquality(m_fromType, m_toType);
+
+ m_vmCurrencyCallback.CurrencyRatiosCallback(currencyRatios.Key, currencyRatios.Value);
+ }
+ }
+
+ string CategoryToString(Category c, char delimiter)
+ {
+ using (var @out = new StringWriter())
+ {
+
+ @out.Write(Quote(c.id.ToString()));
+ @out.Write(delimiter);
+ @out.Write(Quote(c.supportsNegative.ToString()));
+ @out.Write(delimiter);
+ @out.Write(Quote(c.name));
+ @out.Write(delimiter);
+
+ return @out.ToString();
+ }
+ }
+
+ public static CalculatorList StringToVector(string w, char delimiter, bool addRemainder = false)
+ {
+ int delimiterIndex = w.IndexOf(delimiter);
+ int startIndex = 0;
+ CalculatorList serializedTokens = new CalculatorList();
+ while (delimiterIndex != w.npos())
+ {
+ serializedTokens.PushBack(w.substr(startIndex, delimiterIndex - startIndex));
+ startIndex = delimiterIndex + 1 /*(int)wcslen(delimiter)*/;
+ delimiterIndex = w.IndexOf(delimiter, startIndex);
+ }
+ if (addRemainder)
+ {
+ delimiterIndex = w.Length;
+ serializedTokens.PushBack(w.substr(startIndex, delimiterIndex - startIndex));
+ }
+ return serializedTokens;
+ }
+
+ public static CalculatorList StringToVector(string w, string delimiter, bool addRemainder = false)
+ {
+ int delimiterIndex = w.IndexOf(delimiter);
+ int startIndex = 0;
+ CalculatorList serializedTokens = new CalculatorList();
+ while (delimiterIndex != w.npos())
+ {
+ serializedTokens.PushBack(w.substr(startIndex, delimiterIndex - startIndex));
+ startIndex = delimiterIndex + 1 /*(int)wcslen(delimiter)*/;
+ delimiterIndex = w.IndexOf(delimiter, startIndex);
+ }
+ if (addRemainder)
+ {
+ delimiterIndex = w.Length;
+ serializedTokens.PushBack(w.substr(startIndex, delimiterIndex - startIndex));
+ }
+ return serializedTokens;
+ }
+
+ Category StringToCategory(string w)
+ {
+ CalculatorList tokenList = StringToVector(w, ';');
+ Debug.Assert(tokenList.Size() == EXPECTEDSERIALIZEDCATEGORYTOKENCOUNT);
+ Category serializedCategory = new Category();
+ serializedCategory.id = System.Convert.ToInt32(Unquote(tokenList[0]));
+ serializedCategory.supportsNegative = (tokenList[1].CompareTo("1") == 0);
+ serializedCategory.name = Unquote(tokenList[2]);
+ return serializedCategory;
+ }
+
+ string UnitToString(Unit u, char delimiter)
+ {
+ using (var @out = new StringWriter())
+ {
+ @out.Write(Quote(u.id.ToString()));
+ @out.Write(delimiter);
+ @out.Write(Quote(u.name));
+ @out.Write(delimiter);
+ @out.Write(Quote(u.abbreviation));
+ @out.Write(delimiter);
+ @out.Write(u.isConversionSource.ToString());
+ @out.Write(delimiter);
+ @out.Write(u.isConversionTarget.ToString());
+ @out.Write(delimiter);
+ @out.Write(u.isWhimsical.ToString());
+ @out.Write(delimiter);
+
+ return @out.ToString();
+ }
+ }
+
+ Unit StringToUnit(string w)
+ {
+ CalculatorList tokenList = StringToVector(w, ';');
+ Debug.Assert(tokenList.Size() == EXPECTEDSERIALIZEDUNITTOKENCOUNT);
+ Unit serializedUnit = new Unit();
+ serializedUnit.id = System.Convert.ToInt32(Unquote(tokenList[0]));
+ serializedUnit.name = Unquote(tokenList[1]);
+ serializedUnit.accessibleName = serializedUnit.name;
+ serializedUnit.abbreviation = Unquote(tokenList[2]);
+ serializedUnit.isConversionSource = (tokenList[3].CompareTo("1") == 0);
+ serializedUnit.isConversionTarget = (tokenList[4].CompareTo("1") == 0);
+ serializedUnit.isWhimsical = (tokenList[5].CompareTo("1") == 0);
+ return serializedUnit;
+ }
+
+ ConversionData StringToConversionData(string w)
+ {
+ CalculatorList tokenList = StringToVector(w, ';');
+ Debug.Assert(tokenList.Size() == EXPECTEDSERIALIZEDCONVERSIONDATATOKENCOUNT);
+ ConversionData serializedConversionData = new ConversionData();
+ serializedConversionData.ratio = System.Convert.ToDouble(Unquote(tokenList[0]));
+ serializedConversionData.offset = System.Convert.ToDouble(Unquote(tokenList[1]));
+ serializedConversionData.offsetFirst = (tokenList[2].CompareTo("1") == 0);
+ return serializedConversionData;
+ }
+
+ string ConversionDataToString(ConversionData d, char delimiter)
+ {
+ using (var @out = new StringWriter())
+ {
+ string ratio = d.ratio.ToString("F32", CultureInfo.InvariantCulture);
+ string offset = d.offset.ToString("F32", CultureInfo.InvariantCulture);
+ TrimString(ref ratio);
+ TrimString(ref offset);
+ @out.Write(Quote(ratio));
+ @out.Write(delimiter);
+ @out.Write(Quote(offset));
+ @out.Write(delimiter);
+ @out.Write(d.offsetFirst);
+ @out.Write(delimiter);
+
+ return @out.ToString();
+ }
+ }
+
+ ///
+ /// Serializes the data in the converter and returns it as a string
+ ///
+ public string Serialize()
+ {
+ if (!CheckLoad())
+ {
+ return string.Empty;
+ }
+
+ using (var @out = new StringWriter())
+ {
+ char delimiter = ';';
+
+ @out.Write(UnitToString(m_fromType, delimiter));
+ @out.Write('|');
+ @out.Write(UnitToString(m_toType, delimiter));
+ @out.Write('|');
+ @out.Write(CategoryToString(m_currentCategory, delimiter));
+ @out.Write('|');
+ @out.Write(m_currentHasDecimal);
+ @out.Write(delimiter);
+ @out.Write(m_returnHasDecimal);
+ @out.Write(delimiter);
+ @out.Write(m_switchedActive);
+ @out.Write(delimiter);
+ @out.Write(m_currentDisplay);
+ @out.Write(delimiter);
+ @out.Write(m_returnDisplay);
+ @out.Write(delimiter);
+ @out.Write('|');
+
+ using (var categoryString = new StringWriter())
+ using (var categoryToUnitString = new StringWriter())
+ using (var unitToUnitToDoubleString = new StringWriter())
+ {
+ foreach (Category c in m_categories)
+ {
+ categoryString.Write(CategoryToString(c, delimiter));
+ categoryString.Write(',');
+ }
+
+ foreach (var cur in m_categoryToUnits)
+ {
+ categoryToUnitString.Write(CategoryToString(cur.Key, delimiter));
+ categoryToUnitString.Write('[');
+ foreach (Unit u in cur.Value)
+ {
+ categoryToUnitString.Write(UnitToString(u, delimiter));
+ categoryToUnitString.Write(',');
+ }
+ categoryToUnitString.Write("[]");
+ }
+
+ foreach (var cur in m_ratioMap)
+ {
+ unitToUnitToDoubleString.Write(UnitToString(cur.Key, delimiter));
+ categoryToUnitString.Write('[');
+ foreach (var curConversion in cur.Value)
+ {
+ unitToUnitToDoubleString.Write(UnitToString(curConversion.Key, delimiter));
+ unitToUnitToDoubleString.Write(':');
+ unitToUnitToDoubleString.Write(ConversionDataToString(curConversion.Value, delimiter));
+ unitToUnitToDoubleString.Write(":,");
+ }
+ unitToUnitToDoubleString.Write("[]");
+ }
+
+ @out.Write(categoryString.ToString());
+ @out.Write('|');
+ @out.Write(categoryToUnitString.ToString());
+ @out.Write('|');
+ @out.Write(unitToUnitToDoubleString.ToString());
+ @out.Write('|');
+
+ return @out.ToString();
+ }
+ }
+ }
+
+ ///
+ /// De-Serializes the data in the converter from a string
+ ///
+ /// string holding the serialized data. If it does not have expected number of parameters, we will ignore it
+ public void DeSerialize(string serializedData)
+ {
+ ClearValues();
+ ResetCategoriesAndRatios();
+
+ if (string.IsNullOrEmpty(serializedData))
+ {
+ return;
+ }
+
+ CalculatorList outerTokens = StringToVector(serializedData, '|');
+ Debug.Assert(outerTokens.Size() == EXPECTEDSERIALIZEDTOKENCOUNT);
+ m_fromType = StringToUnit(outerTokens[0]);
+ m_toType = StringToUnit(outerTokens[1]);
+ m_currentCategory = StringToCategory(outerTokens[2]);
+ CalculatorList stateDataTokens = StringToVector(outerTokens[3], ';');
+ Debug.Assert(stateDataTokens.Size() == EXPECTEDSTATEDATATOKENCOUNT);
+ m_currentHasDecimal = (stateDataTokens[0].CompareTo('1') == 0);
+ m_returnHasDecimal = (stateDataTokens[1].CompareTo('1') == 0);
+ m_switchedActive = (stateDataTokens[2].CompareTo('1') == 0);
+ m_currentDisplay = stateDataTokens[3];
+ m_returnDisplay = stateDataTokens[4];
+ CalculatorList categoryListTokens = StringToVector(outerTokens[4], ',');
+ foreach (string token in categoryListTokens)
+ {
+ m_categories.PushBack(StringToCategory(token));
+ }
+ CalculatorList unitVectorTokens = StringToVector(outerTokens[5], ']');
+ foreach (string unitVector in unitVectorTokens)
+ {
+ CalculatorList mapcomponents = StringToVector(unitVector, '[');
+ Debug.Assert(mapcomponents.Size() == EXPECTEDMAPCOMPONENTTOKENCOUNT);
+ Category key = StringToCategory(mapcomponents[0]);
+ CalculatorList units = StringToVector(mapcomponents[1], ',');
+ foreach (string unit in units)
+ {
+ m_categoryToUnits[key].PushBack(StringToUnit(unit));
+ }
+ }
+ CalculatorList ratioMapTokens = StringToVector(outerTokens[6], ']');
+ foreach (string token in ratioMapTokens)
+ {
+ CalculatorList ratioMapComponentTokens = StringToVector(token, '[');
+ Debug.Assert(ratioMapComponentTokens.Size() == EXPECTEDMAPCOMPONENTTOKENCOUNT);
+ Unit key = StringToUnit(ratioMapComponentTokens[0]);
+ CalculatorList ratioMapList = StringToVector(ratioMapComponentTokens[1], ',');
+ foreach (string subtoken in ratioMapList)
+ {
+ CalculatorList ratioMapSubComponentTokens = StringToVector(subtoken, ':');
+ Debug.Assert(ratioMapSubComponentTokens.Size() == EXPECTEDMAPCOMPONENTTOKENCOUNT);
+ Unit subkey = StringToUnit(ratioMapSubComponentTokens[0]);
+ ConversionData conversion = StringToConversionData(ratioMapSubComponentTokens[1]);
+ m_ratioMap[key][subkey] = conversion;
+ }
+ }
+ UpdateViewModel();
+ }
+
+ ///
+ /// De-Serializes the data in the converter from a string
+ ///
+ /// string holding the serialized data. If it does not have expected number of parameters, we will ignore it
+ public void RestoreUserPreferences(string userPreferences)
+ {
+ if (string.IsNullOrEmpty(userPreferences))
+ {
+ return;
+ }
+
+ CalculatorList outerTokens = StringToVector(userPreferences, '|');
+ if (outerTokens.Size() != 3)
+ {
+ return;
+ }
+
+ var fromType = StringToUnit(outerTokens[0]);
+ var toType = StringToUnit(outerTokens[1]);
+ m_currentCategory = StringToCategory(outerTokens[2]);
+
+ // Only restore from the saved units if they are valid in the current available units.
+ if (m_categoryToUnits.TryGetValue(m_currentCategory, out var curUnits))
+ {
+ if (curUnits.IndexOf(fromType) != curUnits.Count)
+ {
+ m_fromType = fromType;
+ }
+ if (curUnits.IndexOf(toType) != curUnits.Count)
+ {
+ m_toType = toType;
+ }
+ }
+ }
+
+ ///
+ /// Serializes the Category and Associated Units in the converter and returns it as a string
+ ///
+ public string SaveUserPreferences()
+ {
+ char delimiter = ';';
+
+ using (var writer = new StringWriter())
+ {
+ writer.Write(UnitToString(m_fromType, delimiter));
+ writer.Write('|');
+ writer.Write(UnitToString(m_toType, delimiter));
+ writer.Write('|');
+ writer.Write(CategoryToString(m_currentCategory, delimiter));
+ writer.Write('|');
+
+ return writer.ToString();
+ }
+ }
+
+ ///
+ /// Sanitizes the input string, escape quoting any symbols we rely on for our delimiters, and returns the sanitized string.
+ ///
+ /// string to be sanitized
+ public string Quote(string s)
+ {
+ using (var quotedString = new StringWriter())
+ using (var cursor = s.GetEnumerator())
+ {
+ while (cursor.MoveNext())
+ {
+ if (quoteConversions.TryGetValue(cursor.Current, out var conversion))
+ {
+ quotedString.Write(conversion);
+ }
+ else
+ {
+ quotedString.Write(cursor.Current);
+ }
+ }
+
+ return quotedString.ToString();
+ }
+ }
+
+ ///
+ /// Unsanitizes the sanitized input string, returning it to its original contents before we had quoted it.
+ ///
+ /// string to be unsanitized
+ public string Unquote(string s)
+ {
+ using (var unquotedString = new StringWriter())
+ using (var cursor = s.GetEnumerator())
+ {
+ while (cursor.MoveNext())
+ {
+ if (cursor.Current == LEFTESCAPECHAR)
+ {
+ using (var quotedSubString = new StringWriter())
+ {
+ while (cursor.MoveNext() && cursor.Current != RIGHTESCAPECHAR)
+ {
+ quotedSubString.Write(cursor.Current);
+ }
+
+ if (unquoteConversions.TryGetValue(quotedSubString.ToString(), out var conversion))
+ {
+ unquotedString.Write(conversion);
+ }
+ else
+ {
+ // Badly formatted
+ break;
+ }
+ }
+ }
+ else
+ {
+ unquotedString.Write(cursor.Current);
+ }
+ }
+
+ return unquotedString.ToString();
+ }
+ }
+
+ ///
+ /// Handles inputs to the converter from the view-model, corresponding to a given button or keyboard press
+ ///
+ /// Command enum representing the command that was entered
+ public void SendCommand(Command command)
+ {
+ if (!CheckLoad())
+ {
+ return;
+ }
+
+ // TODO: Localization of characters
+ bool clearFront = false;
+ if (m_currentDisplay == "0")
+ {
+ clearFront = true;
+ }
+ bool clearBack = false;
+ if ((m_currentHasDecimal && m_currentDisplay.Length - 1 >= MAXIMUMDIGITSALLOWED)
+ || (!m_currentHasDecimal && m_currentDisplay.Length >= MAXIMUMDIGITSALLOWED))
+ {
+ clearBack = true;
+ }
+ if (command != Command.Negate && m_switchedActive)
+ {
+ ClearValues();
+ m_switchedActive = false;
+ clearFront = true;
+ clearBack = false;
+ }
+ switch (command)
+ {
+ case Command.Zero:
+ m_currentDisplay += "0";
+ break;
+
+ case Command.One:
+ m_currentDisplay += "1";
+ break;
+
+ case Command.Two:
+ m_currentDisplay += "2";
+ break;
+
+ case Command.Three:
+ m_currentDisplay += "3";
+ break;
+
+ case Command.Four:
+ m_currentDisplay += "4";
+ break;
+
+ case Command.Five:
+ m_currentDisplay += "5";
+ break;
+
+ case Command.Six:
+ m_currentDisplay += "6";
+ break;
+
+ case Command.Seven:
+ m_currentDisplay += "7";
+ break;
+
+ case Command.Eight:
+ m_currentDisplay += "8";
+ break;
+
+ case Command.Nine:
+ m_currentDisplay += "9";
+ break;
+
+ case Command.Decimal:
+ clearFront = false;
+ clearBack = false;
+ if (!m_currentHasDecimal)
+ {
+ m_currentDisplay += ".";
+ m_currentHasDecimal = true;
+ }
+ break;
+
+ case Command.Backspace:
+ clearFront = false;
+ clearBack = false;
+ if ((m_currentDisplay.First() != '-' && m_currentDisplay.Length > 1) || m_currentDisplay.Length > 2)
+ {
+ if (m_currentDisplay.Last() == '.')
+ {
+ m_currentHasDecimal = false;
+ }
+ m_currentDisplay = m_currentDisplay.Remove(m_currentDisplay.Length - 1);
+ }
+ else
+ {
+ m_currentDisplay = "0";
+ m_currentHasDecimal = false;
+ }
+ break;
+
+ case Command.Negate:
+ clearFront = false;
+ clearBack = false;
+ if (m_currentCategory.supportsNegative)
+ {
+ if (m_currentDisplay.First() == '-')
+ {
+ m_currentDisplay = m_currentDisplay.Remove(0, 1);
+ }
+ else
+ {
+ m_currentDisplay = m_currentDisplay.Insert(0, "-");
+ }
+ }
+ break;
+
+ case Command.Clear:
+ clearFront = false;
+ clearBack = false;
+ ClearValues();
+ break;
+
+ case Command.Reset:
+ clearFront = false;
+ clearBack = false;
+ ClearValues();
+ ResetCategoriesAndRatios();
+ break;
+
+ default:
+ break;
+ }
+
+ if (clearFront)
+ {
+ m_currentDisplay = m_currentDisplay.Remove(0, 1);
+ }
+ if (clearBack)
+ {
+ m_currentDisplay = m_currentDisplay.Remove(m_currentDisplay.Length - 1, 1);
+ m_vmCallback.MaxDigitsReached();
+ }
+
+ Calculate();
+ }
+
+ ///
+ /// Sets the callback interface to send display update calls to
+ ///
+ /// instance of IDisplayCallback interface that receives our update calls
+ public void SetViewModelCallback( IUnitConverterVMCallback newCallback)
+ {
+ m_vmCallback = newCallback;
+ if (CheckLoad())
+ {
+ UpdateViewModel();
+ }
+ }
+
+ public void SetViewModelCurrencyCallback( IViewModelCurrencyCallback newCallback)
+ {
+ m_vmCurrencyCallback = newCallback;
+
+ ICurrencyConverterDataLoader currencyDataLoader = GetCurrencyConverterDataLoader();
+ if (currencyDataLoader != null)
+ {
+ currencyDataLoader.SetViewModelCallback(newCallback);
+ }
+ }
+
+ public async Task> RefreshCurrencyRatios()
+ {
+ ICurrencyConverterDataLoader currencyDataLoader = GetCurrencyConverterDataLoader();
+ bool didLoad;
+ if (currencyDataLoader != null)
+ {
+ didLoad = await currencyDataLoader.TryLoadDataFromWebOverrideAsync();
+ }
+ else
+ {
+ didLoad = false;
+ }
+
+ string timestamp = "";
+ if (currencyDataLoader != null)
+ {
+ timestamp = currencyDataLoader.GetCurrencyTimestamp();
+ }
+
+ return new KeyValuePair(didLoad, timestamp);
+ }
+
+ ICurrencyConverterDataLoader GetCurrencyConverterDataLoader()
+ {
+ return (ICurrencyConverterDataLoader)(m_currencyDataLoader);
+ }
+
+ ///
+ /// Converts a double value into another unit type, currently by multiplying by the given double ratio
+ ///
+ /// double input value to convert
+ /// double conversion ratio to use
+ double Convert(double value, ConversionData conversionData)
+ {
+ if (conversionData.offsetFirst)
+ {
+ return (value + conversionData.offset) * conversionData.ratio;
+ }
+ else
+ {
+ return (value * conversionData.ratio) + conversionData.offset;
+ }
+ }
+
+ ///
+ /// Calculates the suggested values for the current display value and returns them as a vector
+ ///
+ CalculatorList> CalculateSuggested()
+ {
+ if (m_currencyDataLoader != null && m_currencyDataLoader.SupportsCategory(m_currentCategory))
+ {
+ return new CalculatorList>();
+ }
+
+ CalculatorList> returnVector = new CalculatorList>();
+ CalculatorList intermediateVector = new CalculatorList();
+ CalculatorList intermediateWhimsicalVector = new CalculatorList();
+
+ Dictionary ratios = m_ratioMap[m_fromType];
+ // Calculate converted values for every other unit type in this category, along with their magnitude
+ foreach (var cur in ratios)
+ {
+ if (cur.Key != m_fromType && cur.Key != m_toType)
+ {
+ double convertedValue = Convert(System.Convert.ToDouble(m_currentDisplay), cur.Value);
+ SuggestedValueIntermediate newEntry;
+ newEntry.magnitude = Math.Log10(convertedValue);
+ newEntry.value = convertedValue;
+ newEntry.type = cur.Key;
+ if (newEntry.type.isWhimsical == false)
+ intermediateVector.PushBack(newEntry);
+ else
+ intermediateWhimsicalVector.PushBack(newEntry);
+ }
+ }
+
+ // Sort the resulting list by absolute magnitude, breaking ties by choosing the positive value
+ intermediateVector.Sort((SuggestedValueIntermediate first, SuggestedValueIntermediate second) => {
+ if (Math.Abs(first.magnitude) == Math.Abs(second.magnitude))
+ {
+ return first.magnitude > second.magnitude;
+ }
+ else
+ {
+ return Math.Abs(first.magnitude) < Math.Abs(second.magnitude);
+ }
+ });
+
+ // Now that the list is sorted, iterate over it and populate the return vector with properly rounded and formatted return strings
+ foreach (var entry in intermediateVector)
+ {
+ string roundedString;
+ if (Math.Abs(entry.value) < 100)
+ {
+ roundedString = RoundSignificant(entry.value, 2);
+ }
+ else if (Math.Abs(entry.value) < 1000)
+ {
+ roundedString = RoundSignificant(entry.value, 1);
+ }
+ else
+ {
+ roundedString = RoundSignificant(entry.value, 0);
+ }
+ if (System.Convert.ToDouble(roundedString) != 0.0 || m_currentCategory.supportsNegative)
+ {
+ TrimString(ref roundedString);
+ returnVector.PushBack(Tuple.Create(roundedString, entry.type));
+ }
+ }
+
+ // The Whimsicals are determined differently
+ // Sort the resulting list by absolute magnitude, breaking ties by choosing the positive value
+ intermediateWhimsicalVector.Sort((SuggestedValueIntermediate first, SuggestedValueIntermediate second) => {
+ if (Math.Abs(first.magnitude) == Math.Abs(second.magnitude))
+ {
+ return first.magnitude > second.magnitude;
+ }
+ else
+ {
+ return Math.Abs(first.magnitude) < Math.Abs(second.magnitude);
+ }
+ });
+
+ // Now that the list is sorted, iterate over it and populate the return vector with properly rounded and formatted return strings
+ CalculatorList> whimsicalReturnVector = new CalculatorList>();
+
+ foreach (var entry in intermediateWhimsicalVector)
+ {
+ string roundedString;
+ if (Math.Abs(entry.value) < 100)
+ {
+ roundedString = RoundSignificant(entry.value, 2);
+ }
+ else if (Math.Abs(entry.value) < 1000)
+ {
+ roundedString = RoundSignificant(entry.value, 1);
+ }
+ else
+ {
+ roundedString = RoundSignificant(entry.value, 0);
+ }
+
+ // How to work out which is the best whimsical value to add to the vector?
+ if (System.Convert.ToDouble(roundedString) != 0.0)
+ {
+ TrimString(ref roundedString);
+ whimsicalReturnVector.PushBack(Tuple.Create(roundedString, entry.type));
+ }
+ }
+ // Pickup the 'best' whimsical value - currently the first one
+ if (whimsicalReturnVector.Size() != 0)
+ {
+ returnVector.PushBack(whimsicalReturnVector.At(0));
+ }
+
+ return returnVector;
+ }
+
+ ///
+ /// Resets categories and ratios
+ ///
+ public void ResetCategoriesAndRatios()
+ {
+ m_categories = m_dataLoader.LoadOrderedCategories();
+
+ m_switchedActive = false;
+
+ if (m_categories.IsEmpty())
+ {
+ return;
+ }
+
+ m_currentCategory = m_categories[0];
+
+ m_categoryToUnits.Clear();
+ m_ratioMap.Clear();
+ bool readyCategoryFound = false;
+ foreach (Category category in m_categories)
+ {
+ IConverterDataLoader activeDataLoader = GetDataLoaderForCategory(category);
+ if (activeDataLoader == null)
+ {
+ // The data loader is different depending on the category, e.g. currency data loader
+ // is different from the static data loader.
+ // If there is no data loader for this category, continue.
+ continue;
+ }
+
+ CalculatorList units = activeDataLoader.LoadOrderedUnits(category);
+ m_categoryToUnits[category] = units;
+
+ // Just because the units are empty, doesn't mean the user can't select this category,
+ // we just want to make sure we don't let an unready category be the default.
+ if (!units.IsEmpty())
+ {
+ foreach (Unit u in units)
+ {
+ m_ratioMap[u] = activeDataLoader.LoadOrderedRatios(u);
+ }
+
+ if (!readyCategoryFound)
+ {
+ m_currentCategory = category;
+ readyCategoryFound = true;
+ }
+ }
+ }
+
+ InitializeSelectedUnits();
+ }
+
+ ///
+ /// Sets the active data loader based on the input category.
+ ///
+ IConverterDataLoader GetDataLoaderForCategory(Category category)
+ {
+ if (m_currencyDataLoader != null && m_currencyDataLoader.SupportsCategory(category))
+ {
+ return m_currencyDataLoader;
+ }
+ else
+ {
+ return m_dataLoader;
+ }
+ }
+
+ ///
+ /// Sets the initial values for m_fromType and m_toType.
+ /// This is an internal helper method as opposed to SetCurrentUnits
+ /// which is for external use by clients.
+ /// If we fail to set units, we will fallback to the EMPTY_UNIT.
+ ///
+ void InitializeSelectedUnits()
+ {
+ if (m_categoryToUnits.Count == 0)
+ {
+ return;
+ }
+
+ if (!m_categoryToUnits.TryGetValue(m_currentCategory, out var curUnits))
+ {
+ return;
+ }
+
+ if (!curUnits.IsEmpty())
+ {
+ bool conversionSourceSet = false;
+ bool conversionTargetSet = false;
+ foreach (Unit cur in curUnits)
+ {
+ if (!conversionSourceSet && cur.isConversionSource)
+ {
+ m_fromType = cur;
+ conversionSourceSet = true;
+ }
+
+ if (!conversionTargetSet && cur.isConversionTarget)
+ {
+ m_toType = cur;
+ conversionTargetSet = true;
+ }
+
+ if (conversionSourceSet && conversionTargetSet)
+ {
+ return;
+ }
+ }
+ }
+
+ m_fromType = EMPTY_UNIT;
+ m_toType = EMPTY_UNIT;
+ }
+
+ ///
+ /// Resets the value fields to 0
+ ///
+ void ClearValues()
+ {
+ m_currentHasDecimal = false;
+ m_returnHasDecimal = false;
+ m_currentDisplay = "0";
+ }
+
+ ///
+ /// Checks if either unit is EMPTY_UNIT.
+ ///
+ bool AnyUnitIsEmpty()
+ {
+ return m_fromType == EMPTY_UNIT || m_toType == EMPTY_UNIT;
+ }
+
+ ///
+ /// Calculates a new return value based on the current display value
+ ///
+ public void Calculate()
+ {
+ if (AnyUnitIsEmpty())
+ {
+ m_returnDisplay = m_currentDisplay;
+ m_returnHasDecimal = m_currentHasDecimal;
+ TrimString(ref m_returnDisplay);
+ UpdateViewModel();
+ return;
+ }
+
+ Dictionary conversionTable = m_ratioMap[m_fromType];
+ double returnValue = System.Convert.ToDouble(m_currentDisplay);
+ if (conversionTable[m_toType].ratio == 1.0 && conversionTable[m_toType].offset == 0.0)
+ {
+ m_returnDisplay = m_currentDisplay;
+ m_returnHasDecimal = m_currentHasDecimal;
+ TrimString(ref m_returnDisplay);
+ }
+ else
+ {
+ returnValue = Convert(returnValue, conversionTable[m_toType]);
+ m_returnDisplay = RoundSignificant(returnValue, MAXIMUMDIGITSALLOWED);
+ TrimString(ref m_returnDisplay);
+ int numPreDecimal = (int)m_returnDisplay.size();
+ if (m_returnDisplay.IndexOf('.') != m_returnDisplay.npos())
+ {
+ numPreDecimal = (int)m_returnDisplay.find('.');
+ }
+ if (returnValue < 0)
+ {
+ numPreDecimal--;
+ }
+
+ if (numPreDecimal > MAXIMUMDIGITSALLOWED || (returnValue != 0 && Math.Abs(returnValue) < MINIMUMDECIMALALLOWED))
+ {
+ m_returnDisplay = returnValue.ToString("e");
+ }
+ else
+ {
+ returnValue = System.Convert.ToDouble(m_returnDisplay);
+ string returnString;
+ if (m_currentDisplay.size() <= OPTIMALDIGITSALLOWED && Math.Abs(returnValue) >= OPTIMALDECIMALALLOWED)
+ {
+ returnString = RoundSignificant(returnValue, OPTIMALDIGITSALLOWED - Math.Min(numPreDecimal, OPTIMALDIGITSALLOWED));
+ }
+ else
+ {
+ returnString = RoundSignificant(returnValue, MAXIMUMDIGITSALLOWED - Math.Min(numPreDecimal, MAXIMUMDIGITSALLOWED));
+ }
+ m_returnDisplay = returnString;
+ TrimString(ref m_returnDisplay);
+ }
+ m_returnHasDecimal = (m_returnDisplay.IndexOf('.') != m_returnDisplay.npos());
+ }
+ UpdateViewModel();
+ }
+
+ ///
+ /// Trims out any trailing zeros or decimals in the given input string
+ ///
+ /// string to trim
+ void TrimString(ref string returnString)
+ {
+ if (returnString.IndexOf('.') == m_returnDisplay.Length)
+ {
+ return;
+ }
+
+ returnString = returnString.TrimEnd('0');
+ returnString = returnString.TrimEnd('.');
+ }
+
+ ///
+ /// Rounds the given double to the given number of significant digits
+ ///
+ /// input double
+ /// int number of significant digits to round to
+ string RoundSignificant(double num, int numSignificant)
+ {
+ return num.ToString($"F{numSignificant}");
+ }
+
+ void UpdateCurrencySymbols()
+ {
+ if (m_currencyDataLoader != null && m_vmCurrencyCallback != null)
+ {
+ ICurrencyConverterDataLoader currencyDataLoader = GetCurrencyConverterDataLoader();
+ KeyValuePair currencySymbols = currencyDataLoader.GetCurrencySymbols(m_fromType, m_toType);
+ KeyValuePair currencyRatios = currencyDataLoader.GetCurrencyRatioEquality(m_fromType, m_toType);
+
+ m_vmCurrencyCallback.CurrencySymbolsCallback(currencySymbols.Key, currencySymbols.Value);
+ m_vmCurrencyCallback.CurrencyRatiosCallback(currencyRatios.Key, currencyRatios.Value);
+ }
+ }
+
+ void UpdateViewModel()
+ {
+ m_vmCallback.DisplayCallback(m_currentDisplay, m_returnDisplay);
+ m_vmCallback.SuggestedValueCallback(CalculateSuggested());
+ }
+ }
+}
diff --git a/src/Calculator.Shared/Calculator.Shared.projitems b/src/Calculator.Shared/Calculator.Shared.projitems
index 6004fdf4..ecef1078 100644
--- a/src/Calculator.Shared/Calculator.Shared.projitems
+++ b/src/Calculator.Shared/Calculator.Shared.projitems
@@ -22,6 +22,7 @@
+
@@ -34,10 +35,12 @@
+
+
@@ -48,13 +51,16 @@
+
+
+
@@ -72,6 +78,11 @@
+
+
+
+
+
@@ -138,6 +149,7 @@
UnitConverter.xaml
+
diff --git a/src/Calculator.Shared/Common/AlwaysSelectedCollectionView.cs b/src/Calculator.Shared/Common/AlwaysSelectedCollectionView.cs
index a76eb105..4ce82a31 100644
--- a/src/Calculator.Shared/Common/AlwaysSelectedCollectionView.cs
+++ b/src/Calculator.Shared/Common/AlwaysSelectedCollectionView.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections;
using System.Collections.Generic;
using System.Text;
using Windows.Foundation;
@@ -8,5 +9,9 @@ namespace WindowsCalculator.Shared.Common
{
class AlwaysSelectedCollectionView
{
+ public AlwaysSelectedCollectionView(IEnumerable result)
+ {
+
+ }
}
}
diff --git a/src/Calculator.Shared/Common/Automation/NarratorNotifier.cs b/src/Calculator.Shared/Common/Automation/NarratorNotifier.cs
new file mode 100644
index 00000000..47ce0086
--- /dev/null
+++ b/src/Calculator.Shared/Common/Automation/NarratorNotifier.cs
@@ -0,0 +1,62 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Windows.UI.Xaml;
+
+namespace CalculatorApp.Common.Automation
+{
+ public sealed partial class NarratorNotifier : DependencyObject
+ {
+ public static DependencyProperty AnnouncementProperty { get; } = DependencyProperty.RegisterAttached(
+ "Announcement",
+ typeof(NarratorAnnouncement),
+ typeof(NarratorNotifier),
+ new PropertyMetadata(default(NarratorAnnouncement), OnAnnouncementChanged));
+
+ public static NarratorAnnouncement GetAnnouncement(Windows.UI.Xaml.DependencyObject element)
+ {
+ return element.GetValue(AnnouncementProperty) as NarratorAnnouncement;
+ }
+
+ public static void SetAnnouncement(Windows.UI.Xaml.DependencyObject element, NarratorAnnouncement value)
+ {
+ element.SetValue(AnnouncementProperty, value);
+ }
+
+ public NarratorAnnouncement Announcement
+ {
+ get { return GetAnnouncement(this); }
+ set { SetAnnouncement(this, value); }
+ }
+
+
+ //TODO UNO INarratorAnnouncementHost m_announcementHost;
+
+ public NarratorNotifier()
+ {
+ // TODO UNO
+ // m_announcementHost = NarratorAnnouncementHostFactory.MakeHost();
+ }
+
+ public void Announce(NarratorAnnouncement announcement)
+ {
+ // TODO UNO:
+ //if (NarratorAnnouncement.IsValid(announcement) && m_announcementHost != null)
+ //{
+ // m_announcementHost.Announce(announcement);
+ //}
+ }
+
+ static void OnAnnouncementChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
+ {
+ var instance = dependencyObject as NarratorNotifier;
+ if (instance != null)
+ {
+ instance.Announce((NarratorAnnouncement)(e.NewValue));
+ }
+ }
+ }
+}
diff --git a/src/Calculator.Shared/Common/ConversionResultTaskHelper.cs b/src/Calculator.Shared/Common/ConversionResultTaskHelper.cs
new file mode 100644
index 00000000..4e792ae1
--- /dev/null
+++ b/src/Calculator.Shared/Common/ConversionResultTaskHelper.cs
@@ -0,0 +1,44 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace CalculatorApp.Common
+{
+ class ConversionResultTaskHelper
+ {
+ uint m_delay;
+ CancellationTokenSource m_cts = new CancellationTokenSource();
+ Action m_storedFunction;
+
+ public ConversionResultTaskHelper(uint delay, Action functionToRun)
+ {
+ m_delay = delay;
+ m_storedFunction = functionToRun;
+
+ var delayTask = CompleteAfter(delay);
+ }
+
+ ~ConversionResultTaskHelper()
+ {
+ m_cts.Cancel();
+ }
+
+ // Creates a task that completes after the specified delay.
+ //
+ // Taken from: How to: Create a Task that Completes After a Delay
+ // https://msdn.microsoft.com/en-us/library/hh873170.aspx
+ async Task CompleteAfter(uint timeout)
+ {
+ await Task.Delay(TimeSpan.FromMilliseconds(timeout));
+ if (!m_cts.Token.IsCancellationRequested)
+ {
+ m_storedFunction();
+ }
+ }
+ }
+}
diff --git a/src/Calculator.Shared/Common/LocalizationService.cs b/src/Calculator.Shared/Common/LocalizationService.cs
index e715fd9d..1577467f 100644
--- a/src/Calculator.Shared/Common/LocalizationService.cs
+++ b/src/Calculator.Shared/Common/LocalizationService.cs
@@ -28,7 +28,7 @@ namespace CalculatorApp.Common
public sealed class LocalizationService
{
- static string DefaultCurrencyCode = "USD";
+ internal static string DefaultCurrencyCode = "USD";
@@ -167,7 +167,7 @@ namespace CalculatorApp.Common
}
}
- FlowDirection GetFlowDirection()
+ public FlowDirection GetFlowDirection()
{
return m_flowDirection;
}
diff --git a/src/Calculator.Shared/Common/LocalizationSettings.cs b/src/Calculator.Shared/Common/LocalizationSettings.cs
index 902d00e8..d734332d 100644
--- a/src/Calculator.Shared/Common/LocalizationSettings.cs
+++ b/src/Calculator.Shared/Common/LocalizationSettings.cs
@@ -335,7 +335,7 @@ namespace CalculatorApp
return m_currencyTrailingDigits;
}
- int GetCurrencySymbolPrecedence()
+ public int GetCurrencySymbolPrecedence()
{
return m_currencySymbolPrecedence;
}
diff --git a/src/Calculator.Shared/Common/NavCategory.cs b/src/Calculator.Shared/Common/NavCategory.cs
index 8570ae16..3dc8cb01 100644
--- a/src/Calculator.Shared/Common/NavCategory.cs
+++ b/src/Calculator.Shared/Common/NavCategory.cs
@@ -140,7 +140,7 @@ namespace CalculatorApp
public string AccessKey => m_accessKey;
- bool SupportsNegative
+ public bool SupportsNegative
{
get
{
@@ -531,13 +531,13 @@ namespace CalculatorApp
public CategoryGroupType GroupType { get => m_GroupType; private set { m_GroupType = value; RaisePropertyChanged("GroupType"); } }
- private ObservableCollection m_Categories;
- public ObservableCollection Categories { get => m_Categories; private set { m_Categories = value; RaisePropertyChanged("Categories"); } }
+ private CalculatorObservableCollection m_Categories;
+ public CalculatorObservableCollection Categories { get => m_Categories; private set { m_Categories = value; RaisePropertyChanged("Categories"); } }
internal NavCategoryGroup(NavCategoryGroupInitializer groupInitializer)
{
- m_Categories = new ObservableCollection();
+ m_Categories = new CalculatorObservableCollection();
m_GroupType = groupInitializer.type;
var resProvider = AppResourceProvider.GetInstance();
@@ -573,9 +573,9 @@ namespace CalculatorApp
}
}
- public static ObservableCollection CreateMenuOptions()
+ public static CalculatorObservableCollection CreateMenuOptions()
{
- var menuOptions = new ObservableCollection();
+ var menuOptions = new CalculatorObservableCollection();
menuOptions.Add(CreateCalculatorCategory());
menuOptions.Add(CreateConverterCategory());
return menuOptions;
@@ -586,7 +586,7 @@ namespace CalculatorApp
return new NavCategoryGroup(NavCategory.s_categoryGroupManifest[0].Value);
}
- static NavCategoryGroup CreateConverterCategory()
+ public static NavCategoryGroup CreateConverterCategory()
{
return new NavCategoryGroup(NavCategory.s_categoryGroupManifest[1].Value);
}
diff --git a/src/Calculator.Shared/Common/NetworkManager.cs b/src/Calculator.Shared/Common/NetworkManager.cs
new file mode 100644
index 00000000..8361c09e
--- /dev/null
+++ b/src/Calculator.Shared/Common/NetworkManager.cs
@@ -0,0 +1,68 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Windows.Networking.Connectivity;
+
+namespace CalculatorApp
+{
+ public enum NetworkAccessBehavior
+ {
+ Normal = 0,
+ OptIn = 1,
+ Offline = 2
+ };
+
+ public delegate void NetworkBehaviorChangedHandler(NetworkAccessBehavior behavior);
+
+ public sealed class NetworkManager
+ {
+ public event NetworkBehaviorChangedHandler NetworkBehaviorChanged;
+
+ public NetworkManager()
+ {
+ NetworkInformation.NetworkStatusChanged += new NetworkStatusChangedEventHandler(OnNetworkStatusChange);
+ }
+
+ ~NetworkManager()
+ {
+ NetworkInformation.NetworkStatusChanged -= OnNetworkStatusChange;
+ }
+
+ public static NetworkAccessBehavior GetNetworkAccessBehavior()
+ {
+ NetworkAccessBehavior behavior = NetworkAccessBehavior.Offline;
+ ConnectionProfile connectionProfile = NetworkInformation.GetInternetConnectionProfile();
+ if (connectionProfile != null)
+ {
+ NetworkConnectivityLevel connectivityLevel = connectionProfile.GetNetworkConnectivityLevel();
+ if (connectivityLevel == NetworkConnectivityLevel.InternetAccess || connectivityLevel == NetworkConnectivityLevel.ConstrainedInternetAccess)
+ {
+ ConnectionCost connectionCost = connectionProfile.GetConnectionCost();
+ behavior = ConvertCostInfoToBehavior(connectionCost);
+ }
+ }
+
+ return behavior;
+ }
+
+ void OnNetworkStatusChange(object sender)
+ {
+ NetworkBehaviorChanged?.Invoke(GetNetworkAccessBehavior());
+ }
+
+ // See app behavior guidelines at https://msdn.microsoft.com/en-us/library/windows/apps/xaml/jj835821(v=win.10).aspx
+ static NetworkAccessBehavior ConvertCostInfoToBehavior(ConnectionCost connectionCost)
+ {
+ if (connectionCost.Roaming || connectionCost.OverDataLimit || connectionCost.NetworkCostType == NetworkCostType.Variable
+ || connectionCost.NetworkCostType == NetworkCostType.Fixed)
+ {
+ return NetworkAccessBehavior.OptIn;
+ }
+
+ return NetworkAccessBehavior.Normal;
+ }
+ }
+}
diff --git a/src/Calculator.Shared/Common/TraceLogger.cs b/src/Calculator.Shared/Common/TraceLogger.cs
index 8b769748..2605b19d 100644
--- a/src/Calculator.Shared/Common/TraceLogger.cs
+++ b/src/Calculator.Shared/Common/TraceLogger.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Runtime.CompilerServices;
using System.Text;
using Windows.Globalization;
using CalculatorApp.Common;
@@ -63,7 +64,7 @@ namespace CalculatorApp
public void LogWindowActivated() {}
public void LogWindowLaunched() {}
public void LogUserRequestedRefreshFailed() {}
- //public void LogConversionResult(std::wstring_view fromValue, std::wstring_view fromUnit, std::wstring_view toValue, std::wstring_view toUnit) {}
+ public void LogConversionResult(string fromValue, string fromUnit, string toValue, string toUnit) {}
public void LogAboutFlyoutOpened() {}
public void LogNavBarOpened() {}
public void LogViewClosingTelemetry(int i) {}
@@ -74,8 +75,8 @@ namespace CalculatorApp
public void LogDateAddSubtractModeUsed(int windowId, bool isAddMode) {}
public void LogDateClippedTimeDifferenceFound(Calendar today, DateTime clippedTime) {}
- //public void LogStandardException(std::wstring_view functionName, _In_ const std::exception& e) {}
- //public void LogWinRTException(std::wstring_view functionName, _In_ winrt::hresult_error const& e) {}
- //public void LogPlatformException(std::wstring_view functionName, _In_ Platform::Exception ^ e) {}
+ public void LogStandardException(Exception e, [CallerMemberName] string functionName = null) { }
+ public void LogWinRTException(Exception e, [CallerMemberName] string functionName = null) { }
+ public void LogPlatformException(Exception e, [CallerMemberName] string functionName = null) {}
}
}
diff --git a/src/Calculator.Shared/Common/Utils.cs b/src/Calculator.Shared/Common/Utils.cs
index 158784e8..56670e63 100644
--- a/src/Calculator.Shared/Common/Utils.cs
+++ b/src/Calculator.Shared/Common/Utils.cs
@@ -1,7 +1,10 @@
using System;
using System.Collections.Generic;
+using System.IO;
using System.Linq;
using System.Text;
+using System.Threading.Tasks;
+using Windows.Storage;
using Windows.UI.Core;
using Windows.UI.ViewManagement;
@@ -31,5 +34,47 @@ namespace CalculatorApp
{
return !string.IsNullOrEmpty(input) && input.Last() == target;
}
- }
+
+ public static void Swap(ref T field1, ref T field2)
+ {
+ var tmp = field1;
+ field1 = field2;
+ field2 = tmp;
+ }
+
+ public static bool IsDateTimeOlderThan(DateTime dateTime, long duration)
+ {
+ return dateTime + TimeSpan.FromTicks(duration) < DateTime.Now;
+ }
+
+ public static async Task ReadFileFromFolder(StorageFolder folder, string filename)
+ {
+ if (folder == null)
+ {
+ return null;
+ }
+
+ var filePath = Path.Combine(folder.Path, filename);
+ using (var reader = new StreamReader(filePath))
+ {
+ return await reader.ReadToEndAsync();
+ }
+ }
+
+ public static async Task WriteFileToFolder(StorageFolder folder, string filename, string contents, CreationCollisionOption colisionOption)
+ {
+ if (folder== null)
+ {
+ return;
+ }
+
+ var filePath = Path.Combine(folder.Path, filename);
+ using (var writer = new StreamWriter(filePath, append: false))
+ {
+ await writer.WriteAsync(contents);
+ }
+ }
+
+ public static DateTime GetUniversalSystemTime() => DateTime.UtcNow;
+ }
}
diff --git a/src/Calculator.Shared/Common/ValidSelectedItemConverter.cs b/src/Calculator.Shared/Common/ValidSelectedItemConverter.cs
new file mode 100644
index 00000000..1d77caba
--- /dev/null
+++ b/src/Calculator.Shared/Common/ValidSelectedItemConverter.cs
@@ -0,0 +1,37 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Windows.UI.Xaml.Data;
+
+namespace CalculatorApp.Common
+{
+ public sealed class ValidSelectedItemConverter : IValueConverter
+ {
+ public ValidSelectedItemConverter()
+ {
+ }
+
+ public object Convert(object value, Type targetType, object parameter, string language)
+ {
+ // Pass through as we don't want to change the value from the source
+ return value;
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, string language)
+ {
+ if (value != null)
+ {
+ if (value is int i && i > 0)
+ {
+ return value;
+ }
+ }
+
+ // Stop the binding if the object is null
+ return Windows.UI.Xaml.DependencyProperty.UnsetValue;
+ }
+ }
+}
diff --git a/src/Calculator.Shared/Controls/CalculationResult.cs b/src/Calculator.Shared/Controls/CalculationResult.cs
index eee528ed..e2357a09 100644
--- a/src/Calculator.Shared/Controls/CalculationResult.cs
+++ b/src/Calculator.Shared/Controls/CalculationResult.cs
@@ -10,6 +10,7 @@ using Windows.UI.Xaml.Automation.Peers;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
+using CalculatorApp.ViewModel;
namespace CalculatorApp
{
@@ -17,7 +18,7 @@ namespace CalculatorApp
{
public delegate void SelectedEventHandler(object sender);
- public sealed partial class CalculationResult : Windows.UI.Xaml.Controls.Control
+ public sealed partial class CalculationResult : Windows.UI.Xaml.Controls.Control, IActivatable
{
public Visibility ExpressionVisibility
{
@@ -130,7 +131,7 @@ namespace CalculatorApp
public static readonly DependencyProperty IsOperatorCommandProperty =
DependencyProperty.Register("IsOperatorCommand", typeof(bool), typeof(CalculationResult), new PropertyMetadata(false));
- event SelectedEventHandler Selected;
+ public event SelectedEventHandler Selected;
private Windows.UI.Xaml.Controls.ScrollViewer m_textContainer;
@@ -163,7 +164,7 @@ namespace CalculatorApp
m_haveCalculatedMax = false;
}
- string GetRawDisplayValue()
+ public string GetRawDisplayValue()
{
string rawValue = null;
@@ -298,7 +299,7 @@ namespace CalculatorApp
}
}
- void UpdateTextState()
+ public void UpdateTextState()
{
if ((m_textContainer == null) || (m_textBlock == null))
{
diff --git a/src/Calculator.Shared/Controls/HorizontalNoOverflowStackPanel.cs b/src/Calculator.Shared/Controls/HorizontalNoOverflowStackPanel.cs
new file mode 100644
index 00000000..8d6f4114
--- /dev/null
+++ b/src/Calculator.Shared/Controls/HorizontalNoOverflowStackPanel.cs
@@ -0,0 +1,73 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Windows.Foundation;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+
+namespace CalculatorApp.Controls
+{
+ public partial class HorizontalNoOverflowStackPanel : Panel
+ {
+ // TODO UNO: DEPENDENCY_PROPERTY_OWNER(HorizontalNoOverflowStackPanel);
+
+ protected override Size MeasureOverride(Size availableSize)
+ {
+ double maxHeight = 0;
+ double width = 0;
+ foreach (UIElement child in Children)
+ {
+ child.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
+ maxHeight = Math.Max(maxHeight, child.DesiredSize.Height);
+ width += child.DesiredSize.Width;
+ }
+ return new Size(Math.Min(width, availableSize.Width), Math.Min(availableSize.Height, maxHeight));
+ }
+
+ protected virtual bool ShouldPrioritizeLastItem()
+ {
+ return false;
+ }
+
+ protected override Size ArrangeOverride(Size finalSize)
+ {
+ if (Children.Count == 0)
+ {
+ return finalSize;
+ }
+
+ double posX = 0;
+ var lastChild = (UIElement)Children.Last();
+ double lastChildWidth = 0;
+ if (Children.Count > 2 && ShouldPrioritizeLastItem())
+ {
+ lastChildWidth = lastChild.DesiredSize.Width;
+ }
+ foreach (UIElement item in Children)
+ {
+ var widthAvailable = finalSize.Width - posX;
+ if (item != lastChild)
+ {
+ widthAvailable -= lastChildWidth;
+ }
+ double itemWidth = item.DesiredSize.Width;
+ if (widthAvailable > 0 && itemWidth <= widthAvailable)
+ {
+ // stack the items horizontally (left to right)
+ item.Arrange(new Rect(posX, 0, itemWidth, finalSize.Height));
+ posX += item.RenderSize.Width;
+ }
+ else
+ {
+ // Not display the item
+ item.Arrange(new Rect(0, 0, 0, 0));
+ }
+ }
+ return finalSize;
+ }
+ };
+}
diff --git a/src/Calculator.Shared/Controls/SupplementaryItemsControl.cs b/src/Calculator.Shared/Controls/SupplementaryItemsControl.cs
index a4b9adbb..4d48f748 100644
--- a/src/Calculator.Shared/Controls/SupplementaryItemsControl.cs
+++ b/src/Calculator.Shared/Controls/SupplementaryItemsControl.cs
@@ -1,3 +1,6 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
using System;
using System.Collections.Generic;
using System.Text;
@@ -5,6 +8,7 @@ using Windows.UI.Xaml;
using Windows.UI.Xaml.Automation;
using Windows.UI.Xaml.Automation.Peers;
using Windows.UI.Xaml.Controls;
+using CalculatorApp.ViewModel;
namespace CalculatorApp.Controls
{
@@ -20,13 +24,12 @@ namespace CalculatorApp.Controls
{
base.PrepareContainerForItemOverride(element, item);
- // UNO TODO
- //var supplementaryResult = (SupplementaryResult)(item);
- //if (supplementaryResult)
- //{
- // AutomationProperties.SetName(element, supplementaryResult.GetLocalizedAutomationName());
- //}
- }
+ var supplementaryResult = (SupplementaryResult)(item);
+ if (supplementaryResult != null)
+ {
+ AutomationProperties.SetName(element, supplementaryResult.GetLocalizedAutomationName());
+ }
+ }
}
public sealed partial class SupplementaryContentPresenter : ContentPresenter
diff --git a/src/Calculator.Shared/Converters/AlwaysSelectedCollectionViewConverter.cs b/src/Calculator.Shared/Converters/AlwaysSelectedCollectionViewConverter.cs
index 5be2b44a..9cd34a25 100644
--- a/src/Calculator.Shared/Converters/AlwaysSelectedCollectionViewConverter.cs
+++ b/src/Calculator.Shared/Converters/AlwaysSelectedCollectionViewConverter.cs
@@ -1,39 +1,44 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
using System;
+using System.Collections;
using System.Collections.Generic;
using System.Text;
+using WindowsCalculator.Shared.Common;
namespace CalculatorApp
{
namespace Common
{
- //class AlwaysSelectedCollectionViewConverter : Windows.UI.Xaml.Data.IValueConverter
- //{
- // public AlwaysSelectedCollectionViewConverter()
- // {
- // }
+ class AlwaysSelectedCollectionViewConverter : Windows.UI.Xaml.Data.IValueConverter
+ {
+ public AlwaysSelectedCollectionViewConverter()
+ {
+ }
- // object Convert(
- // object value,
- // Type targetType,
- // object parameter,
- // string language)
- // {
- // var result = (IEnumerable)(value);
- // if (result)
- // {
- // return new AlwaysSelectedCollectionView(result);
- // }
- // return Windows.UI.Xaml.DependencyProperty.UnsetValue; // Can't convert
- // }
+ public object Convert(
+ object value,
+ Type targetType,
+ object parameter,
+ string language)
+ {
+ var result = value as IEnumerable;
+ if (result != null)
+ {
+ return new AlwaysSelectedCollectionView(result);
+ }
+ return Windows.UI.Xaml.DependencyProperty.UnsetValue; // Can't convert
+ }
- // object ConvertBack(
- // object value,
- // Type targetType,
- // object parameter,
- // string language)
- // {
- // return Windows.UI.Xaml.DependencyProperty.UnsetValue;
- // }
- //}
- }
-}
\ No newline at end of file
+ public object ConvertBack(
+ object value,
+ Type targetType,
+ object parameter,
+ string language)
+ {
+ return Windows.UI.Xaml.DependencyProperty.UnsetValue;
+ }
+ }
+ }
+}
diff --git a/src/Calculator.Shared/Converters/VisibilityNegationConverter.cs b/src/Calculator.Shared/Converters/VisibilityNegationConverter.cs
index 59e829ec..43b21a1a 100644
--- a/src/Calculator.Shared/Converters/VisibilityNegationConverter.cs
+++ b/src/Calculator.Shared/Converters/VisibilityNegationConverter.cs
@@ -1,4 +1,7 @@
-using System;
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
using System.Collections.Generic;
using System.Text;
using Windows.UI.Xaml.Interop;
@@ -6,7 +9,7 @@ using Windows.Foundation.Metadata;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml;
-namespace CalculatorApp.Converters
+namespace CalculatorApp.Common
{
[WebHostHidden]
public sealed class VisibilityNegationConverter : IValueConverter
diff --git a/src/Calculator.Shared/DataLoaders/CurrencyDataLoader.cs b/src/Calculator.Shared/DataLoaders/CurrencyDataLoader.cs
new file mode 100644
index 00000000..25795257
--- /dev/null
+++ b/src/Calculator.Shared/DataLoaders/CurrencyDataLoader.cs
@@ -0,0 +1,792 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Windows.Data.Json;
+using Windows.Foundation.Collections;
+using Windows.Globalization.DateTimeFormatting;
+using Windows.Storage;
+using Windows.System.UserProfile;
+using Windows.UI.Core;
+using CalculatorApp.Common;
+using CalculatorApp.DataLoaders;
+using UCM = UnitConversionManager;
+using CurrencyRatioMap = System.Collections.Generic.Dictionary;
+using SelectedUnits = System.Collections.Generic.KeyValuePair;
+using CategorySelectionInitializer = System.Tuple, UnitConversionManager.Unit, UnitConversionManager.Unit>;
+using UnitToUnitToConversionDataMap = System.Collections.Generic.Dictionary>;
+using CategoryToUnitVectorMap = System.Collections.Generic.Dictionary>;
+
+namespace CalculatorApp.ViewModel
+{
+ public enum CurrencyLoadStatus
+ {
+ NotLoaded = 0,
+ FailedToLoad = 1,
+ LoadedFromCache = 2,
+ LoadedFromWeb = 3
+ }
+
+ public static class UnitConverterResourceKeys
+ {
+ public const string CurrencyUnitFromKey = CurrencyDataLoader.CURRENCY_UNIT_FROM_KEY;
+ public const string CurrencyUnitToKey = CurrencyDataLoader.CURRENCY_UNIT_TO_KEY;
+ }
+
+ public static class CurrencyDataLoaderConstants
+ {
+ public const string CacheTimestampKey = CurrencyDataLoader.CACHE_TIMESTAMP_KEY;
+ public const string CacheLangcodeKey = CurrencyDataLoader.CACHE_LANGCODE_KEY;
+ public const string CacheDelimiter = CurrencyDataLoader.CACHE_DELIMITER;
+ public const string StaticDataFilename = CurrencyDataLoader.STATIC_DATA_FILENAME;
+ public const string AllRatiosDataFilename = CurrencyDataLoader.ALL_RATIOS_DATA_FILENAME;
+ public const long DayDuration = CurrencyDataLoader.DAY_DURATION;
+ }
+
+ struct CurrencyUnitMetadata
+ {
+ public CurrencyUnitMetadata(string s)
+ {
+ symbol = s;
+ }
+
+ public string symbol;
+ };
+
+ public class CurrencyDataLoader : UCM.IConverterDataLoader, UCM.ICurrencyConverterDataLoader
+ {
+ //private:
+ string m_responseLanguage;
+ CalculatorApp.DataLoaders.ICurrencyHttpClient m_client;
+
+ bool m_isRtlLanguage;
+
+ //Mutex m_currencyUnitsMutex;
+ object m_currencyUnitsMutex = new object();
+ CalculatorList m_currencyUnits = new CalculatorList();
+ UnitToUnitToConversionDataMap m_currencyRatioMap = new UnitToUnitToConversionDataMap(EqualityComparer.Default);
+ Dictionary m_currencyMetadata = new Dictionary(EqualityComparer.Default);
+
+ UCM.IViewModelCurrencyCallback m_vmCallback;
+
+ Windows.Globalization.NumberFormatting.DecimalFormatter m_ratioFormatter;
+ string m_ratioFormat;
+ DateTime m_cacheTimestamp;
+ string m_timestampFormat;
+
+ CurrencyLoadStatus m_loadStatus;
+
+ CalculatorApp.NetworkManager m_networkManager;
+
+ CalculatorApp.NetworkAccessBehavior m_networkAccessBehavior;
+
+ //Windows.Foundation.EventRegistrationToken m_networkBehaviorToken;
+ bool m_meteredOverrideSet;
+
+ internal const string CURRENCY_UNIT_FROM_KEY = "CURRENCY_UNIT_FROM_KEY";
+ internal const string CURRENCY_UNIT_TO_KEY = "CURRENCY_UNIT_TO_KEY";
+
+ // Calculate number of 100-nanosecond intervals-per-day
+ // (1 interval/100 nanosecond)(100 nanosecond/1e-7 s)(60 s/1 min)(60 min/1 hr)(24 hr/1 day) = (interval/day)
+ internal const long DAY_DURATION = 1L * 60 * 60 * 24 * 10000000;
+ internal const long WEEK_DURATION = DAY_DURATION * 7;
+
+ static int FORMATTER_DIGIT_COUNT = 4;
+
+ internal const string CACHE_TIMESTAMP_KEY = "CURRENCY_CONVERTER_TIMESTAMP";
+ internal const string CACHE_LANGCODE_KEY = "CURRENCY_CONVERTER_LANGCODE";
+ internal const string CACHE_DELIMITER = "%";
+
+ internal const string STATIC_DATA_FILENAME = "CURRENCY_CONVERTER_STATIC_DATA.txt";
+
+ private static string[] STATIC_DATA_PROPERTIES =
+ {
+ "CountryCode",
+ "CountryName",
+ "CurrencyCode",
+ "CurrencyName",
+ "CurrencySymbol"
+ };
+
+ internal const string ALL_RATIOS_DATA_FILENAME = "CURRENCY_CONVERTER_ALL_RATIOS_DATA.txt";
+ internal const string RATIO_KEY = "Rt";
+ internal const string CURRENCY_CODE_KEY = "An";
+ internal static string[] ALL_RATIOS_DATA_PROPERTIES = {RATIO_KEY, CURRENCY_CODE_KEY};
+
+ static string DEFAULT_FROM_TO_CURRENCY_FILE_URI = "ms-appx:///DataLoaders/DefaultFromToCurrency.json";
+ static string FROM_KEY = "from";
+ static string TO_KEY = "to";
+
+ // Fallback default values.
+ static string DEFAULT_FROM_CURRENCY = DefaultCurrencyCode;
+ static string DEFAULT_TO_CURRENCY = "EUR";
+
+ private static string DefaultCurrencyCode = LocalizationService.DefaultCurrencyCode;
+
+ public CurrencyDataLoader(ICurrencyHttpClient client)
+ {
+ this.m_client = client;
+ ;
+ this.m_loadStatus = CurrencyLoadStatus.NotLoaded;
+ this.m_responseLanguage = "en-US";
+ this.m_ratioFormat = "";
+ this.m_timestampFormat = "";
+ this.m_networkManager = new NetworkManager();
+ this.m_meteredOverrideSet = false;
+
+
+ if (GlobalizationPreferences.Languages.Count > 0)
+ {
+ m_responseLanguage = GlobalizationPreferences.Languages[0];
+ }
+
+ if (m_client != null)
+ {
+ m_client.SetSourceCurrencyCode(DefaultCurrencyCode);
+ m_client.SetResponseLanguage(m_responseLanguage);
+ }
+
+ if (CoreWindow.GetForCurrentThread() != null)
+ {
+ // Must have a CoreWindow to access the resource context.
+ m_isRtlLanguage = LocalizationService.GetInstance().IsRtlLayout();
+ }
+
+ m_ratioFormatter = LocalizationService.GetRegionalSettingsAwareDecimalFormatter();
+ m_ratioFormatter.IsGrouped = true;
+ m_ratioFormatter.IsDecimalPointAlwaysDisplayed = true;
+ m_ratioFormatter.FractionDigits = FORMATTER_DIGIT_COUNT;
+
+ m_ratioFormat = AppResourceProvider.GetInstance().GetResourceString("CurrencyFromToRatioFormat");
+ m_timestampFormat = AppResourceProvider.GetInstance().GetResourceString("CurrencyTimestampFormat");
+ }
+
+ ~CurrencyDataLoader()
+ {
+ UnregisterForNetworkBehaviorChanges();
+ }
+
+ void UnregisterForNetworkBehaviorChanges()
+ {
+ m_networkManager.NetworkBehaviorChanged -= OnNetworkBehaviorChanged;
+ }
+
+ void RegisterForNetworkBehaviorChanges()
+ {
+ UnregisterForNetworkBehaviorChanges();
+
+ m_networkManager.NetworkBehaviorChanged += new NetworkBehaviorChangedHandler(OnNetworkBehaviorChanged);
+
+ OnNetworkBehaviorChanged(NetworkManager.GetNetworkAccessBehavior());
+ }
+
+ void OnNetworkBehaviorChanged(NetworkAccessBehavior newBehavior)
+ {
+ m_networkAccessBehavior = newBehavior;
+ if (m_vmCallback != null)
+ {
+ m_vmCallback.NetworkBehaviorChanged((int)(m_networkAccessBehavior));
+ }
+ }
+
+ bool LoadFinished()
+ {
+ return m_loadStatus != CurrencyLoadStatus.NotLoaded;
+ }
+
+ bool LoadedFromCache()
+ {
+ return m_loadStatus == CurrencyLoadStatus.LoadedFromCache;
+ }
+
+ bool LoadedFromWeb()
+ {
+ return m_loadStatus == CurrencyLoadStatus.LoadedFromWeb;
+ }
+
+ void ResetLoadStatus()
+ {
+ m_loadStatus = CurrencyLoadStatus.NotLoaded;
+ }
+
+ public async void LoadData()
+ {
+ RegisterForNetworkBehaviorChanges();
+
+ if (!LoadFinished())
+ {
+ var loadFunctions = new Func>[]
+ {
+ TryLoadDataFromCacheAsync,
+ TryLoadDataFromWebAsync
+ };
+
+ bool didLoad = false;
+ foreach (var f in loadFunctions)
+ {
+ didLoad = await f();
+ if (didLoad)
+ {
+ break;
+ }
+ }
+
+ UpdateDisplayedTimestamp();
+ NotifyDataLoadFinished(didLoad);
+ }
+ }
+
+ public CalculatorList LoadOrderedCategories()
+ {
+ // This function should not be called
+ // The model will use the categories from UnitConverterDataLoader
+ return new CalculatorList();
+ }
+
+ public CalculatorList LoadOrderedUnits(UCM.Category category)
+ {
+ lock (m_currencyUnitsMutex)
+ {
+ return m_currencyUnits;
+ }
+ }
+
+ public Dictionary LoadOrderedRatios(UCM.Unit unit)
+ {
+ lock (m_currencyUnitsMutex) ;
+ {
+ return m_currencyRatioMap[unit];
+ }
+ }
+
+ public bool SupportsCategory(UCM.Category target)
+ {
+ int currencyId = NavCategory.Serialize(ViewMode.Currency);
+ return target.id == currencyId;
+ }
+
+ public void SetViewModelCallback(UCM.IViewModelCurrencyCallback callback)
+ {
+ m_vmCallback = callback;
+ OnNetworkBehaviorChanged(m_networkAccessBehavior);
+ }
+
+ public KeyValuePair GetCurrencySymbols(UCM.Unit unit1, UCM.Unit unit2)
+ {
+ lock (m_currencyUnitsMutex)
+ {
+
+ string symbol1 = "";
+ string symbol2 = "";
+
+ if (m_currencyMetadata.TryGetValue(unit1, out var itr1) && m_currencyMetadata.TryGetValue(unit2, out var itr2))
+ {
+ symbol1 = itr1.symbol;
+ symbol2 = itr2.symbol;
+ }
+
+ return new KeyValuePair(symbol1, symbol2);
+ }
+ }
+
+ public KeyValuePair GetCurrencyRatioEquality(UCM.Unit unit1, UCM.Unit unit2)
+ {
+ try
+ {
+ if (m_currencyRatioMap.TryGetValue(unit1, out var ratioMap))
+ {
+ if (ratioMap.TryGetValue(unit2, out var iter2))
+ {
+ var ratio = iter2.ratio;
+
+ // Round the ratio to FORMATTER_DIGIT_COUNT digits using int math.
+ // Ex: to round 1.23456 to three digits, use
+ // ((int) 1.23456 * (10^3)) / (10^3)
+ double scale = Math.Pow(10, FORMATTER_DIGIT_COUNT);
+ double rounded = (int)(ratio * (int)(scale)) / scale;
+
+ string digitSymbol = LocalizationSettings.GetInstance().GetDigitSymbolFromEnUsDigit('1').ToString();
+ string roundedFormat = m_ratioFormatter.Format(rounded);
+
+ string ratioString = LocalizationStringUtil.GetLocalizedString(
+ m_ratioFormat,
+ digitSymbol,
+ unit1.abbreviation,
+ roundedFormat,
+ unit2.abbreviation);
+
+ string accessibleRatioString = LocalizationStringUtil.GetLocalizedString(
+ m_ratioFormat,
+ digitSymbol,
+ unit1.accessibleName,
+ roundedFormat,
+ unit2.accessibleName);
+
+ return new KeyValuePair(ratioString, accessibleRatioString);
+ }
+ }
+ }
+ catch
+ {
+ }
+
+ return new KeyValuePair("", "");
+ }
+
+#pragma optimize("", off) // Turn off optimizations to work around DevDiv 393321
+ public async Task TryLoadDataFromCacheAsync()
+ {
+ try
+ {
+ ResetLoadStatus();
+
+ var localSettings = ApplicationData.Current.LocalSettings;
+ if (localSettings == null || !localSettings.Values.ContainsKey(CurrencyDataLoaderConstants.CacheTimestampKey))
+ {
+ return false;
+ }
+
+ bool loadComplete = false;
+ m_cacheTimestamp = DateTime.Parse((string)localSettings.Values[CurrencyDataLoaderConstants.CacheTimestampKey]);
+ if (Utils.IsDateTimeOlderThan(m_cacheTimestamp, DAY_DURATION) && m_networkAccessBehavior == NetworkAccessBehavior.Normal)
+ {
+ loadComplete = await TryLoadDataFromWebAsync();
+ }
+
+ if (!loadComplete)
+ {
+ loadComplete = await TryFinishLoadFromCacheAsync();
+ }
+
+ return loadComplete;
+ }
+ catch (Exception e)
+ {
+ TraceLogger.GetInstance().LogStandardException(e);
+ return false;
+ }
+ }
+
+ public async Task TryFinishLoadFromCacheAsync()
+ {
+ var localSettings = ApplicationData.Current.LocalSettings;
+ if (localSettings == null)
+ {
+ return false;
+ }
+
+ if (!localSettings.Values.ContainsKey(CurrencyDataLoaderConstants.CacheLangcodeKey) || !((string)localSettings.Values[CurrencyDataLoaderConstants.CacheLangcodeKey]).Equals(m_responseLanguage))
+ {
+ return false;
+ }
+
+ StorageFolder localCacheFolder = ApplicationData.Current.LocalCacheFolder;
+ if (localCacheFolder == null)
+ {
+ return false;
+ }
+
+ string staticDataResponse = await Utils.ReadFileFromFolder(localCacheFolder, CurrencyDataLoaderConstants.StaticDataFilename);
+ string allRatiosResponse = await Utils.ReadFileFromFolder(localCacheFolder, CurrencyDataLoaderConstants.AllRatiosDataFilename);
+
+ CalculatorList staticData = new CalculatorList();
+ CurrencyRatioMap ratioMap = new CurrencyRatioMap();
+
+ bool didParse = TryParseWebResponses(staticDataResponse, allRatiosResponse, staticData, ratioMap);
+ if (!didParse)
+ {
+ return false;
+ }
+
+ m_loadStatus = CurrencyLoadStatus.LoadedFromCache;
+ await FinalizeUnits(staticData, ratioMap);
+
+ return true;
+ }
+
+ public async Task TryLoadDataFromWebAsync()
+ {
+ try
+ {
+ ResetLoadStatus();
+
+ if (m_client == null)
+ {
+ return false;
+ }
+
+ if (m_networkAccessBehavior == NetworkAccessBehavior.Offline || (m_networkAccessBehavior == NetworkAccessBehavior.OptIn && !m_meteredOverrideSet))
+ {
+ return false;
+ }
+
+ String staticDataResponse = await m_client.GetCurrencyMetadata();
+ String allRatiosResponse = await m_client.GetCurrencyRatios();
+ if (staticDataResponse == null || allRatiosResponse == null)
+ {
+ return false;
+ }
+
+ CalculatorList staticData = new CalculatorList();
+ CurrencyRatioMap ratioMap = new CurrencyRatioMap();
+
+ bool didParse = TryParseWebResponses(staticDataResponse, allRatiosResponse, staticData, ratioMap);
+ if (!didParse)
+ {
+ return false;
+ }
+
+ // Set the timestamp before saving it below.
+ m_cacheTimestamp = Utils.GetUniversalSystemTime();
+
+ try
+ {
+ CalculatorList> cachedFiles = new CalculatorList>
+ {
+ new KeyValuePair(CurrencyDataLoaderConstants.StaticDataFilename, staticDataResponse),
+ new KeyValuePair(CurrencyDataLoaderConstants.AllRatiosDataFilename, allRatiosResponse)
+ };
+
+ StorageFolder localCacheFolder = ApplicationData.Current.LocalCacheFolder;
+ foreach (var fileInfo in cachedFiles)
+ {
+ await Utils.WriteFileToFolder(localCacheFolder, fileInfo.Key, fileInfo.Value, CreationCollisionOption.ReplaceExisting);
+ }
+
+ SaveLangCodeAndTimestamp();
+ }
+ catch
+ {
+ // If we fail to save to cache it's okay, we should still continue.
+ }
+
+ m_loadStatus = CurrencyLoadStatus.LoadedFromWeb;
+ await FinalizeUnits(staticData, ratioMap);
+
+ return true;
+ }
+ catch (Exception e)
+ {
+ TraceLogger.GetInstance().LogStandardException(e);
+ return false;
+ }
+ }
+
+ public async Task TryLoadDataFromWebOverrideAsync()
+ {
+ m_meteredOverrideSet = true;
+ bool didLoad = await TryLoadDataFromWebAsync();
+ if (!didLoad)
+ {
+ m_loadStatus = CurrencyLoadStatus.FailedToLoad;
+ TraceLogger.GetInstance().LogUserRequestedRefreshFailed();
+ }
+
+ return didLoad;
+ }
+
+ bool TryParseWebResponses(
+ String staticDataJson,
+ String allRatiosJson,
+ CalculatorList staticData,
+ CurrencyRatioMap allRatiosData)
+ {
+ return TryParseStaticData(staticDataJson, staticData) && TryParseAllRatiosData(allRatiosJson, allRatiosData);
+ }
+
+ bool TryParseStaticData(String rawJson, CalculatorList staticData)
+ {
+ JsonArray data = null;
+ if (!JsonArray.TryParse(rawJson, out data))
+ {
+ return false;
+ }
+
+ string countryCode = "";
+ string countryName = "";
+ string currencyCode = "";
+ string currencyName = "";
+ string currencySymbol = "";
+
+ string[] values = {countryCode, countryName, currencyCode, currencyName, currencySymbol};
+
+ Debug.Assert(values.Length == STATIC_DATA_PROPERTIES.Length);
+ staticData.Clear();
+ for (int i = 0; i < data.Count; i++)
+ {
+ JsonObject obj = data[i].GetObject();
+
+ countryCode = obj.GetNamedString(STATIC_DATA_PROPERTIES[0]);
+ countryName = obj.GetNamedString(STATIC_DATA_PROPERTIES[1]);
+ currencyCode = obj.GetNamedString(STATIC_DATA_PROPERTIES[2]);
+ currencyName = obj.GetNamedString(STATIC_DATA_PROPERTIES[3]);
+ currencySymbol = obj.GetNamedString(STATIC_DATA_PROPERTIES[4]);
+
+ staticData.Add(new UCM.CurrencyStaticData(countryCode, countryName, currencyCode, currencyName, currencySymbol));
+ }
+
+ // TODO - MSFT 8533667: this sort will be replaced by a WinRT call to sort localized strings
+ staticData.Sort((UCM.CurrencyStaticData unit1, UCM.CurrencyStaticData unit2) => { return unit1.countryName.CompareTo(unit2.countryName) < 0; });
+
+ return true;
+ }
+
+ public bool TryParseAllRatiosData(String rawJson, CurrencyRatioMap allRatios)
+ {
+ JsonArray data = null;
+ if (!JsonArray.TryParse(rawJson, out data))
+ {
+ return false;
+ }
+
+ string sourceCurrencyCode = DefaultCurrencyCode;
+
+ allRatios.Clear();
+ for (int i = 0; i < data.Count; i++)
+ {
+ JsonObject obj = data[i].GetObject();
+
+ // Rt is ratio, An is target currency ISO code.
+ double relativeRatio = obj.GetNamedNumber(RATIO_KEY);
+ string targetCurrencyCode = obj.GetNamedString(CURRENCY_CODE_KEY);
+
+ allRatios.Add(targetCurrencyCode, new UCM.CurrencyRatio(relativeRatio, sourceCurrencyCode, targetCurrencyCode));
+ }
+
+ return true;
+ }
+
+ // FinalizeUnits
+ //
+ // There are a few ways we can get the data needed for Currency Converter, including from cache or from web.
+ // This function accepts the data from any source, and acts as a 'last-steps' for the converter to be ready.
+ // This includes identifying which units will be selected and building the map of currency ratios.
+ public async Task FinalizeUnits(CalculatorList staticData, CurrencyRatioMap ratioMap)
+ {
+ Dictionary> idToUnit = new Dictionary>();
+
+ SelectedUnits defaultCurrencies = await GetDefaultFromToCurrency();
+ string fromCurrency = defaultCurrencies.Key;
+ string toCurrency = defaultCurrencies.Value;
+
+ lock (m_currencyUnitsMutex)
+ {
+
+ int i = 1;
+ m_currencyUnits.Clear();
+ m_currencyMetadata.Clear();
+ bool isConversionSourceSet = false;
+ bool isConversionTargetSet = false;
+ foreach (UCM.CurrencyStaticData currencyUnit in staticData)
+ {
+ if (ratioMap.TryGetValue(currencyUnit.currencyCode, out var itr) && itr.ratio > 0)
+ {
+ int id = (int)(UnitConverterUnits.UnitEnd + i);
+
+ bool isConversionSource = (fromCurrency == currencyUnit.currencyCode);
+ isConversionSourceSet = isConversionSourceSet || isConversionSource;
+
+ bool isConversionTarget = (toCurrency == currencyUnit.currencyCode);
+ isConversionTargetSet = isConversionTargetSet || isConversionTarget;
+
+ UCM.Unit unit = new UCM.Unit(
+ id, // id
+ currencyUnit.currencyName, // currencyName
+ currencyUnit.countryName, // countryName
+ currencyUnit.currencyCode, // abbreviation
+ m_isRtlLanguage, // isRtlLanguage
+ isConversionSource, // isConversionSource
+ isConversionTarget // isConversionTarget
+ );
+
+ m_currencyUnits.PushBack(unit);
+ m_currencyMetadata.Add(unit, new CurrencyUnitMetadata(currencyUnit.currencySymbol));
+ idToUnit.Add(unit.id, new KeyValuePair(unit, itr.ratio));
+ i++;
+ }
+ }
+
+ if (!isConversionSourceSet || !isConversionTargetSet)
+ {
+ GuaranteeSelectedUnits();
+ defaultCurrencies = new SelectedUnits(DEFAULT_FROM_CURRENCY, DEFAULT_TO_CURRENCY);
+ }
+
+ m_currencyRatioMap.Clear();
+ foreach (var unit in m_currencyUnits)
+ {
+ Dictionary conversions = new Dictionary(EqualityComparer.Default);
+ double unitFactor = idToUnit[unit.id].Value;
+ foreach (var itr in idToUnit)
+ {
+ UCM.Unit targetUnit = (itr.Value).Key;
+ double conversionRatio = (itr.Value).Value;
+ UCM.ConversionData parsedData = new UCM.ConversionData(1.0, 0.0, false);
+ Debug.Assert(unitFactor > 0); // divide by zero assert
+ parsedData.ratio = conversionRatio / unitFactor;
+ conversions.Add(targetUnit, parsedData);
+ }
+
+ m_currencyRatioMap.Add(unit, conversions);
+ }
+ } // unlocked m_currencyUnitsMutex
+
+ SaveSelectedUnitsToLocalSettings(defaultCurrencies);
+ }
+
+
+ void GuaranteeSelectedUnits()
+ {
+ bool isConversionSourceSet = false;
+ bool isConversionTargetSet = false;
+ foreach (UCM.Unit unit in m_currencyUnits)
+ {
+ unit.isConversionSource = false;
+ unit.isConversionTarget = false;
+
+ if (!isConversionSourceSet && unit.abbreviation == DEFAULT_FROM_CURRENCY)
+ {
+ unit.isConversionSource = true;
+ isConversionSourceSet = true;
+ }
+
+ if (!isConversionTargetSet && unit.abbreviation == DEFAULT_TO_CURRENCY)
+ {
+ unit.isConversionTarget = true;
+ isConversionTargetSet = true;
+ }
+ }
+ }
+
+ void NotifyDataLoadFinished(bool didLoad)
+ {
+ if (!didLoad)
+ {
+ m_loadStatus = CurrencyLoadStatus.FailedToLoad;
+ }
+
+ if (m_vmCallback != null)
+ {
+ m_vmCallback.CurrencyDataLoadFinished(didLoad);
+ }
+ }
+
+ void SaveLangCodeAndTimestamp()
+ {
+ ApplicationDataContainer localSettings = ApplicationData.Current.LocalSettings;
+ if (localSettings == null)
+ {
+ return;
+ }
+
+ localSettings.Values[CurrencyDataLoaderConstants.CacheTimestampKey] = m_cacheTimestamp.ToString("R");
+ localSettings.Values[CurrencyDataLoaderConstants.CacheLangcodeKey] = m_responseLanguage;
+ }
+
+ void UpdateDisplayedTimestamp()
+ {
+ if (m_vmCallback != null)
+ {
+ string timestamp = GetCurrencyTimestamp();
+ bool isWeekOld = Utils.IsDateTimeOlderThan(m_cacheTimestamp, WEEK_DURATION);
+
+ m_vmCallback.CurrencyTimestampCallback(timestamp, isWeekOld);
+ }
+ }
+
+ public string GetCurrencyTimestamp()
+ {
+ string timestamp = "";
+
+ DateTime epoch = default(DateTime);
+ if (m_cacheTimestamp.ToUniversalTime() != epoch.ToUniversalTime())
+ {
+ DateTimeFormatter dateFormatter = new DateTimeFormatter("{month.abbreviated} {day.integer}, {year.full}");
+ string date = dateFormatter.Format(m_cacheTimestamp);
+
+ DateTimeFormatter timeFormatter = new DateTimeFormatter("shorttime");
+ string time = timeFormatter.Format(m_cacheTimestamp);
+
+ timestamp = LocalizationStringUtil.GetLocalizedString(m_timestampFormat, date, time);
+ }
+
+ return timestamp;
+ }
+
+ async Task GetDefaultFromToCurrency()
+ {
+ string fromCurrency = DEFAULT_FROM_CURRENCY;
+ string toCurrency = DEFAULT_TO_CURRENCY;
+
+ // First, check if we previously stored the last used currencies.
+ bool foundInLocalSettings = TryGetLastUsedCurrenciesFromLocalSettings(out fromCurrency, out toCurrency);
+ if (!foundInLocalSettings)
+ {
+ try
+ {
+ // Second, see if the current locale has preset defaults in DefaultFromToCurrency.json.
+ Uri fileUri = new Uri(DEFAULT_FROM_TO_CURRENCY_FILE_URI);
+ StorageFile defaultFromToCurrencyFile = await StorageFile.GetFileFromApplicationUriAsync(fileUri); // TODO UNO
+ if (defaultFromToCurrencyFile != null)
+ {
+ String fileContents = await FileIO.ReadTextAsync(defaultFromToCurrencyFile);
+ JsonObject fromToObject = JsonObject.Parse(fileContents);
+ JsonObject regionalDefaults = fromToObject.GetNamedObject(m_responseLanguage);
+
+ // Get both values before assignment in-case either fails.
+ String selectedFrom = regionalDefaults.GetNamedString(FROM_KEY);
+ String selectedTo = regionalDefaults.GetNamedString(TO_KEY);
+
+ fromCurrency = selectedFrom;
+ toCurrency = selectedTo;
+ }
+ }
+ catch
+ {
+ }
+ }
+
+ return new SelectedUnits(fromCurrency, toCurrency);
+ }
+
+ bool TryGetLastUsedCurrenciesFromLocalSettings(out string fromCurrency, out string toCurrency)
+ {
+ fromCurrency = toCurrency = null;
+
+ String fromKey = UnitConverterResourceKeys.CurrencyUnitFromKey;
+ String toKey = UnitConverterResourceKeys.CurrencyUnitToKey;
+ ApplicationDataContainer localSettings = ApplicationData.Current.LocalSettings;
+ if (localSettings != null && localSettings.Values != null)
+ {
+ IPropertySet values = localSettings.Values;
+ if (values.ContainsKey(fromKey) && values.ContainsKey(toKey))
+ {
+ fromCurrency = (String)(values[fromKey]);
+ toCurrency = (String)(values[toKey]);
+
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ void SaveSelectedUnitsToLocalSettings(SelectedUnits selectedUnits)
+ {
+ String fromKey = UnitConverterResourceKeys.CurrencyUnitFromKey;
+ String toKey = UnitConverterResourceKeys.CurrencyUnitToKey;
+ ApplicationDataContainer localSettings = ApplicationData.Current.LocalSettings;
+ if (localSettings != null && localSettings.Values != null)
+ {
+ IPropertySet values = localSettings.Values;
+ values[fromKey] = selectedUnits.Key;
+ values[toKey] = selectedUnits.Value;
+ }
+ }
+ }
+}
diff --git a/src/Calculator.Shared/DataLoaders/CurrencyHttpClient.cs b/src/Calculator.Shared/DataLoaders/CurrencyHttpClient.cs
new file mode 100644
index 00000000..8472e6bf
--- /dev/null
+++ b/src/Calculator.Shared/DataLoaders/CurrencyHttpClient.cs
@@ -0,0 +1,53 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Windows.Foundation;
+using Windows.Web.Http;
+
+namespace CalculatorApp.DataLoaders
+{
+ public class CurrencyHttpClient : ICurrencyHttpClient
+ {
+ Windows.Web.Http.HttpClient m_client;
+ string m_responseLanguage;
+ string m_sourceCurrencyCode;
+
+ static string sc_MetadataUriLocalizeFor = "https://go.microsoft.com/fwlink/?linkid=2041093&localizeFor=";
+ static string sc_RatiosUriRelativeTo = "https://go.microsoft.com/fwlink/?linkid=2041339&localCurrency=";
+
+ public CurrencyHttpClient()
+ {
+ m_client = new HttpClient();
+ m_responseLanguage = "en-US";
+ }
+
+ public void SetSourceCurrencyCode(String sourceCurrencyCode)
+ {
+ m_sourceCurrencyCode = sourceCurrencyCode;
+ }
+
+ public void SetResponseLanguage(String responseLanguage)
+ {
+ m_responseLanguage = responseLanguage;
+ }
+
+ public IAsyncOperationWithProgress GetCurrencyMetadata()
+ {
+ string uri = sc_MetadataUriLocalizeFor + m_responseLanguage;
+ var metadataUri = new Uri(uri);
+
+ return m_client.GetStringAsync(metadataUri);
+ }
+
+ public IAsyncOperationWithProgress GetCurrencyRatios()
+ {
+ string uri = sc_RatiosUriRelativeTo + m_sourceCurrencyCode;
+ var ratiosUri = new Uri(uri);
+
+ return m_client.GetStringAsync(ratiosUri);
+ }
+ }
+}
diff --git a/src/Calculator.Shared/DataLoaders/ICurrencyHttpClient.cs b/src/Calculator.Shared/DataLoaders/ICurrencyHttpClient.cs
new file mode 100644
index 00000000..cf4a18f9
--- /dev/null
+++ b/src/Calculator.Shared/DataLoaders/ICurrencyHttpClient.cs
@@ -0,0 +1,18 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace CalculatorApp.DataLoaders
+{
+ public interface ICurrencyHttpClient
+ {
+ void SetSourceCurrencyCode(string sourceCurrencyCode);
+ void SetResponseLanguage(string responseLanguage);
+
+ Windows.Foundation.IAsyncOperationWithProgress GetCurrencyMetadata();
+ Windows.Foundation.IAsyncOperationWithProgress GetCurrencyRatios();
+ }
+}
diff --git a/src/Calculator.Shared/DataLoaders/UnitConverterDataConstants.cs b/src/Calculator.Shared/DataLoaders/UnitConverterDataConstants.cs
new file mode 100644
index 00000000..c1452ca4
--- /dev/null
+++ b/src/Calculator.Shared/DataLoaders/UnitConverterDataConstants.cs
@@ -0,0 +1,170 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace CalculatorApp.ViewModel
+{
+ public enum UnitConverterUnits
+ {
+ UnitStart = 0,
+ Area_Acre = UnitStart + 1,
+ Area_Hectare = UnitStart + 2,
+ Area_SquareCentimeter = UnitStart + 3,
+ Area_SquareFoot = UnitStart + 4,
+ Area_SquareInch = UnitStart + 5,
+ Area_SquareKilometer = UnitStart + 6,
+ Area_SquareMeter = UnitStart + 7,
+ Area_SquareMile = UnitStart + 8,
+ Area_SquareMillimeter = UnitStart + 9,
+ Area_SquareYard = UnitStart + 10,
+ Data_Bit = UnitStart + 11,
+ Data_Byte = UnitStart + 12,
+ Data_Gigabit = UnitStart + 13,
+ Data_Gigabyte = UnitStart + 14,
+ Data_Kilobit = UnitStart + 15,
+ Data_Kilobyte = UnitStart + 16,
+ Data_Megabit = UnitStart + 17,
+ Data_Megabyte = UnitStart + 18,
+ Data_Petabit = UnitStart + 19,
+ Data_Petabyte = UnitStart + 20,
+ Data_Terabit = UnitStart + 21,
+ Data_Terabyte = UnitStart + 22,
+ Energy_BritishThermalUnit = UnitStart + 23,
+ Energy_Calorie = UnitStart + 24,
+ Energy_ElectronVolt = UnitStart + 25,
+ Energy_FootPound = UnitStart + 26,
+ Energy_Joule = UnitStart + 27,
+ Energy_Kilocalorie = UnitStart + 28,
+ Energy_Kilojoule = UnitStart + 29,
+ Length_Centimeter = UnitStart + 30,
+ Length_Foot = UnitStart + 31,
+ Length_Inch = UnitStart + 32,
+ Length_Kilometer = UnitStart + 33,
+ Length_Meter = UnitStart + 34,
+ Length_Micron = UnitStart + 35,
+ Length_Mile = UnitStart + 36,
+ Length_Millimeter = UnitStart + 37,
+ Length_Nanometer = UnitStart + 38,
+ Length_NauticalMile = UnitStart + 39,
+ Length_Yard = UnitStart + 40,
+ Power_BritishThermalUnitPerMinute = UnitStart + 41,
+ Power_FootPoundPerMinute = UnitStart + 42,
+ Power_Horsepower = UnitStart + 43,
+ Power_Kilowatt = UnitStart + 44,
+ Power_Watt = UnitStart + 45,
+ Temperature_DegreesCelsius = UnitStart + 46,
+ Temperature_DegreesFahrenheit = UnitStart + 47,
+ Temperature_Kelvin = UnitStart + 48,
+ Time_Day = UnitStart + 49,
+ Time_Hour = UnitStart + 50,
+ Time_Microsecond = UnitStart + 51,
+ Time_Millisecond = UnitStart + 52,
+ Time_Minute = UnitStart + 53,
+ Time_Second = UnitStart + 54,
+ Time_Week = UnitStart + 55,
+ Time_Year = UnitStart + 56,
+ Speed_CentimetersPerSecond = UnitStart + 57,
+ Speed_FeetPerSecond = UnitStart + 58,
+ Speed_KilometersPerHour = UnitStart + 59,
+ Speed_Knot = UnitStart + 60,
+ Speed_Mach = UnitStart + 61,
+ Speed_MetersPerSecond = UnitStart + 62,
+ Speed_MilesPerHour = UnitStart + 63,
+ Volume_CubicCentimeter = UnitStart + 64,
+ Volume_CubicFoot = UnitStart + 65,
+ Volume_CubicInch = UnitStart + 66,
+ Volume_CubicMeter = UnitStart + 67,
+ Volume_CubicYard = UnitStart + 68,
+ Volume_CupUS = UnitStart + 69,
+ Volume_FluidOunceUK = UnitStart + 70,
+ Volume_FluidOunceUS = UnitStart + 71,
+ Volume_GallonUK = UnitStart + 72,
+ Volume_GallonUS = UnitStart + 73,
+ Volume_Liter = UnitStart + 74,
+ Volume_Milliliter = UnitStart + 75,
+ Volume_PintUK = UnitStart + 76,
+ Volume_PintUS = UnitStart + 77,
+ Volume_TablespoonUS = UnitStart + 78,
+ Volume_TeaspoonUS = UnitStart + 79,
+ Volume_QuartUK = UnitStart + 80,
+ Volume_QuartUS = UnitStart + 81,
+ Weight_Carat = UnitStart + 82,
+ Weight_Centigram = UnitStart + 83,
+ Weight_Decigram = UnitStart + 84,
+ Weight_Decagram = UnitStart + 85,
+ Weight_Gram = UnitStart + 86,
+ Weight_Hectogram = UnitStart + 87,
+ Weight_Kilogram = UnitStart + 88,
+ Weight_LongTon = UnitStart + 89,
+ Weight_Milligram = UnitStart + 90,
+ Weight_Ounce = UnitStart + 91,
+ Weight_Pound = UnitStart + 92,
+ Weight_ShortTon = UnitStart + 93,
+ Weight_Stone = UnitStart + 94,
+ Weight_Tonne = UnitStart + 95,
+ Area_SoccerField = UnitStart + 99,
+ Data_FloppyDisk = UnitStart + 100,
+ Data_CD = UnitStart + 101,
+ Data_DVD = UnitStart + 102,
+ Energy_Battery = UnitStart + 103,
+ Length_Paperclip = UnitStart + 105,
+ Length_JumboJet = UnitStart + 107,
+ Power_LightBulb = UnitStart + 108,
+ Power_Horse = UnitStart + 109,
+ Volume_Bathtub = UnitStart + 111,
+ Weight_Snowflake = UnitStart + 113,
+ Weight_Elephant = UnitStart + 114,
+ Volume_TeaspoonUK = UnitStart + 115,
+ Volume_TablespoonUK = UnitStart + 116,
+ Area_Hand = UnitStart + 118,
+ Speed_Turtle = UnitStart + 121,
+ Speed_Jet = UnitStart + 122,
+ Volume_CoffeeCup = UnitStart + 124,
+ Weight_Whale = UnitStart + 123,
+ Volume_SwimmingPool = UnitStart + 125,
+ Speed_Horse = UnitStart + 126,
+ Area_Paper = UnitStart + 127,
+ Area_Castle = UnitStart + 128,
+ Energy_Banana = UnitStart + 129,
+ Energy_SliceOfCake = UnitStart + 130,
+ Length_Hand = UnitStart + 131,
+ Power_TrainEngine = UnitStart + 132,
+ Weight_SoccerBall = UnitStart + 133,
+ Angle_Degree = UnitStart + 134,
+ Angle_Radian = UnitStart + 135,
+ Angle_Gradian = UnitStart + 136,
+ Pressure_Atmosphere = UnitStart + 137,
+ Pressure_Bar = UnitStart + 138,
+ Pressure_KiloPascal = UnitStart + 139,
+ Pressure_MillimeterOfMercury = UnitStart + 140,
+ Pressure_Pascal = UnitStart + 141,
+ Pressure_PSI = UnitStart + 142,
+ Data_Exabits = UnitStart + 143,
+ Data_Exabytes = UnitStart + 144,
+ Data_Exbibits = UnitStart + 145,
+ Data_Exbibytes = UnitStart + 146,
+ Data_Gibibits = UnitStart + 147,
+ Data_Gibibytes = UnitStart + 148,
+ Data_Kibibits = UnitStart + 149,
+ Data_Kibibytes = UnitStart + 150,
+ Data_Mebibits = UnitStart + 151,
+ Data_Mebibytes = UnitStart + 152,
+ Data_Pebibits = UnitStart + 153,
+ Data_Pebibytes = UnitStart + 154,
+ Data_Tebibits = UnitStart + 155,
+ Data_Tebibytes = UnitStart + 156,
+ Data_Yobibits = UnitStart + 157,
+ Data_Yobibytes = UnitStart + 158,
+ Data_Yottabit = UnitStart + 159,
+ Data_Yottabyte = UnitStart + 160,
+ Data_Zebibits = UnitStart + 161,
+ Data_Zebibytes = UnitStart + 162,
+ Data_Zetabits = UnitStart + 163,
+ Data_Zetabytes = UnitStart + 164,
+ Area_Pyeong = UnitStart + 165,
+ UnitEnd = Area_Pyeong
+ };
+}
diff --git a/src/Calculator.Shared/DataLoaders/UnitConverterDataLoader.cs b/src/Calculator.Shared/DataLoaders/UnitConverterDataLoader.cs
new file mode 100644
index 00000000..3f50eb12
--- /dev/null
+++ b/src/Calculator.Shared/DataLoaders/UnitConverterDataLoader.cs
@@ -0,0 +1,1322 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using Windows.Globalization;
+using CalculatorApp;
+using CalculatorApp.Common;
+using CalculatorApp.ViewModel;
+using CategorySelectionInitializer = System.Tuple, UnitConversionManager.Unit, UnitConversionManager.Unit>;
+using UnitToUnitToConversionDataMap = System.Collections.Generic.Dictionary>;
+using CategoryToUnitVectorMap = System.Collections.Generic.Dictionary>;
+using UCM = UnitConversionManager;
+
+namespace CalculatorApp.ViewModel
+{
+ public class OrderedUnit : UnitConversionManager.Unit
+ {
+ public OrderedUnit()
+ {
+ }
+
+ public OrderedUnit(
+ UnitConverterUnits id,
+ string name,
+ string abbreviation,
+ int order,
+ bool isConversionSource = false,
+ bool isConversionTarget = false,
+ bool isWhimsical = false)
+ : this((int)id, name, abbreviation, order, isConversionSource, isConversionTarget, isWhimsical)
+ {
+ }
+
+ public OrderedUnit(
+ int id,
+ string name,
+ string abbreviation,
+ int order,
+ bool isConversionSource = false,
+ bool isConversionTarget = false,
+ bool isWhimsical = false)
+ : base(id, name, abbreviation, isConversionSource, isConversionTarget, isWhimsical)
+ {
+ this.order = order;
+ }
+
+ public int order;
+ };
+
+ public struct UnitData
+ {
+ public UnitData(ViewMode categoryId, UnitConverterUnits unit, double factor)
+ {
+ this.categoryId = categoryId;
+ this.unitId = (int)unit;
+ this.factor = factor;
+ }
+
+ public CalculatorApp.Common.ViewMode categoryId;
+ public int unitId;
+ public double factor;
+ };
+
+ class ExplicitUnitConversionData : UnitConversionManager.ConversionData
+ {
+ //public ExplicitUnitConversionData()
+ //{
+ //}
+ public ExplicitUnitConversionData(
+ CalculatorApp.Common.ViewMode categoryId,
+ UnitConverterUnits parentUnitId,
+ UnitConverterUnits unitId,
+ double ratio,
+ double offset,
+ bool offsetFirst = false)
+ : this(categoryId, (int)parentUnitId, (int)unitId, ratio, offset, offsetFirst)
+ {
+
+ }
+
+
+ public ExplicitUnitConversionData(
+ CalculatorApp.Common.ViewMode categoryId,
+ int parentUnitId,
+ int unitId,
+ double ratio,
+ double offset,
+ bool offsetFirst = false)
+ : base(ratio, offset, offsetFirst)
+ {
+ this.categoryId = categoryId;
+ this.parentUnitId = parentUnitId;
+ this.unitId = unitId;
+ }
+
+ public CalculatorApp.Common.ViewMode categoryId;
+ public int parentUnitId;
+ public int unitId;
+ };
+
+ class UnitConverterDataLoader : UnitConversionManager.IConverterDataLoader
+ {
+ CalculatorList m_categoryList;
+ CategoryToUnitVectorMap m_categoryToUnits;
+ UnitToUnitToConversionDataMap m_ratioMap;
+ string m_currentRegionCode;
+
+
+ static bool CONVERT_WITH_OFFSET_FIRST = true;
+
+ public UnitConverterDataLoader(GeographicRegion region)
+ {
+ m_currentRegionCode = region.CodeTwoLetter;
+
+ m_categoryList = new CalculatorList();
+ m_categoryToUnits = new CategoryToUnitVectorMap(EqualityComparer.Default);
+ m_ratioMap = new UnitToUnitToConversionDataMap(EqualityComparer.Default);
+ }
+
+ public CalculatorList LoadOrderedCategories()
+ {
+ return new CalculatorList(m_categoryList);
+ }
+
+ public CalculatorList LoadOrderedUnits(UCM.Category category)
+ {
+ return m_categoryToUnits[category];
+ }
+
+ public Dictionary LoadOrderedRatios(UCM.Unit unit)
+ {
+ return m_ratioMap[unit];
+ }
+
+ public bool SupportsCategory(UCM.Category target)
+ {
+ CalculatorList supportedCategories = null;
+ if (!m_categoryList.IsEmpty())
+ {
+ supportedCategories = m_categoryList;
+ }
+ else
+ {
+ GetCategories(supportedCategories);
+ }
+
+ int currencyId = NavCategory.Serialize(ViewMode.Currency);
+ var itr = !supportedCategories.Any(category => currencyId != category.id && target.id == category.id);
+ return itr;
+ }
+
+ public void LoadData()
+ {
+ Dictionary idToUnit = new Dictionary();
+
+ Dictionary> orderedUnitMap = new Dictionary>();
+ Dictionary> categoryToUnitConversionDataMap = new Dictionary>();
+ Dictionary> explicitConversionData = new Dictionary>();
+
+ // Load categories, units and conversion data into data structures. This will be then used to populate hashmaps used by CalcEngine and UI layer
+ GetCategories(m_categoryList);
+ GetUnits(orderedUnitMap);
+ GetConversionData(categoryToUnitConversionDataMap);
+ GetExplicitConversionData(explicitConversionData); // This is needed for temperature conversions
+
+ m_categoryToUnits.Clear();
+ m_ratioMap.Clear();
+ foreach (UCM.Category objectCategory in m_categoryList)
+ {
+ ViewMode categoryViewMode = NavCategory.Deserialize(objectCategory.id);
+ Debug.Assert(NavCategory.IsConverterViewMode(categoryViewMode));
+ if (categoryViewMode == ViewMode.Currency)
+ {
+ // Currency is an ordered category but we do not want to process it here
+ // because this function is not thread-safe and currency data is asynchronously
+ // loaded.
+ m_categoryToUnits.Add(objectCategory, new CalculatorList());
+ continue;
+ }
+
+ CalculatorList orderedUnits = orderedUnitMap[categoryViewMode];
+ CalculatorList unitList = new CalculatorList();
+
+ // Sort the units by order
+ orderedUnits.Sort((OrderedUnit first, OrderedUnit second) => { return first.order < second.order; });
+
+ foreach (OrderedUnit u in orderedUnits)
+ {
+ unitList.PushBack((UCM.Unit)(u));
+ idToUnit.Add(u.id, u);
+ }
+
+ // Save units per category
+ m_categoryToUnits.Add(objectCategory, unitList);
+
+ // For each unit, populate the conversion data
+ foreach (UCM.Unit unit in unitList)
+ {
+ Dictionary conversions = new Dictionary(EqualityComparer.Default);
+
+ //if (explicitConversionData.find(unit.id) == explicitConversionData.end())
+ if (!explicitConversionData.TryGetValue(unit.id, out var unitConversions))
+ {
+ // Get the associated units for a category id
+ Dictionary unitConversions2 = categoryToUnitConversionDataMap[categoryViewMode];
+ double unitFactor = unitConversions2[unit.id];
+
+ foreach (var kvp in unitConversions2)
+ {
+ var id = kvp.Key;
+ var conversionFactor = kvp.Value;
+ if (!idToUnit.ContainsKey(id))
+ {
+ // Optional units will not be in idToUnit but can be in unitConversions.
+ // For optional units that did not make it to the current set of units, just continue.
+ continue;
+ }
+
+ UCM.ConversionData parsedData = new UCM.ConversionData(1.0, 0.0, false);
+ Debug.Assert(conversionFactor > 0); // divide by zero assert
+ parsedData.ratio = unitFactor / conversionFactor;
+ conversions.Add(idToUnit[id], parsedData);
+ }
+ }
+ else
+ {
+ //Dictionary unitConversions = explicitConversionData.at(unit.id);
+ foreach (var itr in unitConversions)
+ {
+ conversions.Add(idToUnit[itr.Key], itr.Value);
+ }
+ }
+
+ m_ratioMap.Add(unit, conversions);
+ }
+ }
+ }
+
+ void GetCategories(CalculatorList categoriesList)
+ {
+ categoriesList.Clear();
+ var converterCategory = NavCategoryGroup.CreateConverterCategory();
+ foreach (var category in converterCategory.Categories)
+ {
+ /* Id, CategoryName, SupportsNegative */
+ categoriesList.EmplaceBack(new UCM.Category(NavCategory.Serialize(category.Mode), category.Name, category.SupportsNegative));
+ }
+ }
+
+ void GetUnits(Dictionary> unitMap)
+ {
+ // US + Federated States of Micronesia, Marshall Islands, Palau
+ bool useUSCustomaryAndFahrenheit =
+ m_currentRegionCode == "US" || m_currentRegionCode == "FM" || m_currentRegionCode == "MH" || m_currentRegionCode == "PW";
+
+ // useUSCustomaryAndFahrenheit + Liberia
+ // Source: https://en.wikipedia.org/wiki/Metrication
+ bool useUSCustomary = useUSCustomaryAndFahrenheit || m_currentRegionCode == "LR";
+
+ // Use 'Système International' (International System of Units - Metrics)
+ bool useSI = !useUSCustomary;
+
+ // useUSCustomaryAndFahrenheit + the Bahamas, the Cayman Islands and Liberia
+ // Source: http://en.wikipedia.org/wiki/Fahrenheit
+ bool useFahrenheit = useUSCustomaryAndFahrenheit || m_currentRegionCode == "BS" || m_currentRegionCode == "KY" || m_currentRegionCode == "LR";
+
+ bool useWattInsteadOfKilowatt = m_currentRegionCode == "GB";
+
+ // Use Pyeong, a Korean floorspace unit.
+ // https://en.wikipedia.org/wiki/Korean_units_of_measurement#Area
+ bool usePyeong = m_currentRegionCode == "KP" || m_currentRegionCode == "KR";
+
+ CalculatorList areaUnits = new CalculatorList();
+ areaUnits.PushBack(
+ new OrderedUnit(UnitConverterUnits.Area_Acre, GetLocalizedStringName("UnitName_Acre"), GetLocalizedStringName("UnitAbbreviation_Acre"), 9));
+ areaUnits.PushBack(
+ new OrderedUnit(UnitConverterUnits.Area_Hectare, GetLocalizedStringName("UnitName_Hectare"), GetLocalizedStringName("UnitAbbreviation_Hectare"), 4));
+ areaUnits.PushBack(new OrderedUnit(UnitConverterUnits.Area_SquareCentimeter,
+ GetLocalizedStringName("UnitName_SquareCentimeter"),
+ GetLocalizedStringName("UnitAbbreviation_SquareCentimeter"),
+ 2));
+ areaUnits.PushBack(new OrderedUnit(UnitConverterUnits.Area_SquareFoot,
+ GetLocalizedStringName("UnitName_SquareFoot"),
+ GetLocalizedStringName("UnitAbbreviation_SquareFoot"),
+ 7,
+ useSI,
+ useUSCustomary,
+ false));
+ areaUnits.PushBack(new OrderedUnit(
+ UnitConverterUnits.Area_SquareInch,
+ GetLocalizedStringName("UnitName_SquareInch"),
+ GetLocalizedStringName("UnitAbbreviation_SquareInch"),
+ 6));
+ areaUnits.PushBack(new OrderedUnit(UnitConverterUnits.Area_SquareKilometer,
+ GetLocalizedStringName("UnitName_SquareKilometer"),
+ GetLocalizedStringName("UnitAbbreviation_SquareKilometer"),
+ 5));
+ areaUnits.PushBack(new OrderedUnit(UnitConverterUnits.Area_SquareMeter,
+ GetLocalizedStringName("UnitName_SquareMeter"),
+ GetLocalizedStringName("UnitAbbreviation_SquareMeter"),
+ 3,
+ useUSCustomary,
+ useSI,
+ false));
+ areaUnits.PushBack(new OrderedUnit(
+ UnitConverterUnits.Area_SquareMile,
+ GetLocalizedStringName("UnitName_SquareMile"),
+ GetLocalizedStringName("UnitAbbreviation_SquareMile"),
+ 10));
+ areaUnits.PushBack(new OrderedUnit(UnitConverterUnits.Area_SquareMillimeter,
+ GetLocalizedStringName("UnitName_SquareMillimeter"),
+ GetLocalizedStringName("UnitAbbreviation_SquareMillimeter"),
+ 1));
+ areaUnits.PushBack(new OrderedUnit(
+ UnitConverterUnits.Area_SquareYard,
+ GetLocalizedStringName("UnitName_SquareYard"),
+ GetLocalizedStringName("UnitAbbreviation_SquareYard"),
+ 8));
+ areaUnits.PushBack(new OrderedUnit(
+ UnitConverterUnits.Area_Hand,
+ GetLocalizedStringName("UnitName_Hand"),
+ GetLocalizedStringName("UnitAbbreviation_Hand"),
+ 11,
+ false,
+ false,
+ true));
+ areaUnits.PushBack(new OrderedUnit(
+ UnitConverterUnits.Area_Paper,
+ GetLocalizedStringName("UnitName_Paper"),
+ GetLocalizedStringName("UnitAbbreviation_Paper"),
+ 12,
+ false,
+ false,
+ true));
+ areaUnits.PushBack(new OrderedUnit(UnitConverterUnits.Area_SoccerField,
+ GetLocalizedStringName("UnitName_SoccerField"),
+ GetLocalizedStringName("UnitAbbreviation_SoccerField"),
+ 13,
+ false,
+ false,
+ true));
+ areaUnits.PushBack(new OrderedUnit(UnitConverterUnits.Area_Castle,
+ GetLocalizedStringName("UnitName_Castle"),
+ GetLocalizedStringName("UnitAbbreviation_Castle"),
+ 14,
+ false,
+ false,
+ true));
+ if (usePyeong)
+ {
+ areaUnits.PushBack(new OrderedUnit(UnitConverterUnits.Area_Pyeong,
+ GetLocalizedStringName("UnitName_Pyeong"),
+ GetLocalizedStringName("UnitAbbreviation_Pyeong"),
+ 15,
+ false,
+ false,
+ false));
+ }
+
+ unitMap.Add(ViewMode.Area, areaUnits);
+
+ CalculatorList dataUnits = new CalculatorList();
+ dataUnits.PushBack(
+ new OrderedUnit(UnitConverterUnits.Data_Bit, GetLocalizedStringName("UnitName_Bit"), GetLocalizedStringName("UnitAbbreviation_Bit"), 1));
+ dataUnits.PushBack(
+ new OrderedUnit(UnitConverterUnits.Data_Byte, GetLocalizedStringName("UnitName_Byte"), GetLocalizedStringName("UnitAbbreviation_Byte"), 2));
+ dataUnits.PushBack(
+ new OrderedUnit(UnitConverterUnits.Data_Exabits, GetLocalizedStringName("UnitName_Exabits"), GetLocalizedStringName("UnitAbbreviation_Exabits"), 23));
+ dataUnits.PushBack(new OrderedUnit(
+ UnitConverterUnits.Data_Exabytes,
+ GetLocalizedStringName("UnitName_Exabytes"),
+ GetLocalizedStringName("UnitAbbreviation_Exabytes"),
+ 25));
+ dataUnits.PushBack(new OrderedUnit(
+ UnitConverterUnits.Data_Exbibits,
+ GetLocalizedStringName("UnitName_Exbibits"),
+ GetLocalizedStringName("UnitAbbreviation_Exbibits"),
+ 24));
+ dataUnits.PushBack(new OrderedUnit(
+ UnitConverterUnits.Data_Exbibytes,
+ GetLocalizedStringName("UnitName_Exbibytes"),
+ GetLocalizedStringName("UnitAbbreviation_Exbibytes"),
+ 26));
+ dataUnits.PushBack(new OrderedUnit(
+ UnitConverterUnits.Data_Gibibits,
+ GetLocalizedStringName("UnitName_Gibibits"),
+ GetLocalizedStringName("UnitAbbreviation_Gibibits"),
+ 12));
+ dataUnits.PushBack(new OrderedUnit(
+ UnitConverterUnits.Data_Gibibytes,
+ GetLocalizedStringName("UnitName_Gibibytes"),
+ GetLocalizedStringName("UnitAbbreviation_Gibibytes"),
+ 14));
+ dataUnits.PushBack(
+ new OrderedUnit(UnitConverterUnits.Data_Gigabit, GetLocalizedStringName("UnitName_Gigabit"), GetLocalizedStringName("UnitAbbreviation_Gigabit"), 11));
+ dataUnits.PushBack(new OrderedUnit(UnitConverterUnits.Data_Gigabyte,
+ GetLocalizedStringName("UnitName_Gigabyte"),
+ GetLocalizedStringName("UnitAbbreviation_Gigabyte"),
+ 13,
+ true,
+ false,
+ false));
+ dataUnits.PushBack(new OrderedUnit(
+ UnitConverterUnits.Data_Kibibits,
+ GetLocalizedStringName("UnitName_Kibibits"),
+ GetLocalizedStringName("UnitAbbreviation_Kibibits"),
+ 4));
+ dataUnits.PushBack(new OrderedUnit(
+ UnitConverterUnits.Data_Kibibytes,
+ GetLocalizedStringName("UnitName_Kibibytes"),
+ GetLocalizedStringName("UnitAbbreviation_Kibibytes"),
+ 6));
+ dataUnits.PushBack(
+ new OrderedUnit(UnitConverterUnits.Data_Kilobit, GetLocalizedStringName("UnitName_Kilobit"), GetLocalizedStringName("UnitAbbreviation_Kilobit"), 3));
+ dataUnits.PushBack(new OrderedUnit(
+ UnitConverterUnits.Data_Kilobyte,
+ GetLocalizedStringName("UnitName_Kilobyte"),
+ GetLocalizedStringName("UnitAbbreviation_Kilobyte"),
+ 5));
+ dataUnits.PushBack(new OrderedUnit(
+ UnitConverterUnits.Data_Mebibits,
+ GetLocalizedStringName("UnitName_Mebibits"),
+ GetLocalizedStringName("UnitAbbreviation_Mebibits"),
+ 8));
+ dataUnits.PushBack(new OrderedUnit(
+ UnitConverterUnits.Data_Mebibytes,
+ GetLocalizedStringName("UnitName_Mebibytes"),
+ GetLocalizedStringName("UnitAbbreviation_Mebibytes"),
+ 10));
+ dataUnits.PushBack(
+ new OrderedUnit(UnitConverterUnits.Data_Megabit, GetLocalizedStringName("UnitName_Megabit"), GetLocalizedStringName("UnitAbbreviation_Megabit"), 7));
+ dataUnits.PushBack(new OrderedUnit(UnitConverterUnits.Data_Megabyte,
+ GetLocalizedStringName("UnitName_Megabyte"),
+ GetLocalizedStringName("UnitAbbreviation_Megabyte"),
+ 9,
+ false,
+ true,
+ false));
+ dataUnits.PushBack(new OrderedUnit(
+ UnitConverterUnits.Data_Pebibits,
+ GetLocalizedStringName("UnitName_Pebibits"),
+ GetLocalizedStringName("UnitAbbreviation_Pebibits"),
+ 20));
+ dataUnits.PushBack(new OrderedUnit(
+ UnitConverterUnits.Data_Pebibytes,
+ GetLocalizedStringName("UnitName_Pebibytes"),
+ GetLocalizedStringName("UnitAbbreviation_Pebibytes"),
+ 22));
+ dataUnits.PushBack(
+ new OrderedUnit(UnitConverterUnits.Data_Petabit, GetLocalizedStringName("UnitName_Petabit"), GetLocalizedStringName("UnitAbbreviation_Petabit"), 19));
+ dataUnits.PushBack(new OrderedUnit(
+ UnitConverterUnits.Data_Petabyte,
+ GetLocalizedStringName("UnitName_Petabyte"),
+ GetLocalizedStringName("UnitAbbreviation_Petabyte"),
+ 21));
+ dataUnits.PushBack(new OrderedUnit(
+ UnitConverterUnits.Data_Tebibits,
+ GetLocalizedStringName("UnitName_Tebibits"),
+ GetLocalizedStringName("UnitAbbreviation_Tebibits"),
+ 16));
+ dataUnits.PushBack(new OrderedUnit(
+ UnitConverterUnits.Data_Tebibytes,
+ GetLocalizedStringName("UnitName_Tebibytes"),
+ GetLocalizedStringName("UnitAbbreviation_Tebibytes"),
+ 18));
+ dataUnits.PushBack(
+ new OrderedUnit(UnitConverterUnits.Data_Terabit, GetLocalizedStringName("UnitName_Terabit"), GetLocalizedStringName("UnitAbbreviation_Terabit"), 15));
+ dataUnits.PushBack(new OrderedUnit(
+ UnitConverterUnits.Data_Terabyte,
+ GetLocalizedStringName("UnitName_Terabyte"),
+ GetLocalizedStringName("UnitAbbreviation_Terabyte"),
+ 17));
+ dataUnits.PushBack(new OrderedUnit(
+ UnitConverterUnits.Data_Yobibits,
+ GetLocalizedStringName("UnitName_Yobibits"),
+ GetLocalizedStringName("UnitAbbreviation_Yobibits"),
+ 32));
+ dataUnits.PushBack(new OrderedUnit(
+ UnitConverterUnits.Data_Yobibytes,
+ GetLocalizedStringName("UnitName_Yobibytes"),
+ GetLocalizedStringName("UnitAbbreviation_Yobibytes"),
+ 34));
+ dataUnits.PushBack(new OrderedUnit(
+ UnitConverterUnits.Data_Yottabit,
+ GetLocalizedStringName("UnitName_Yottabit"),
+ GetLocalizedStringName("UnitAbbreviation_Yottabit"),
+ 31));
+ dataUnits.PushBack(new OrderedUnit(
+ UnitConverterUnits.Data_Yottabyte,
+ GetLocalizedStringName("UnitName_Yottabyte"),
+ GetLocalizedStringName("UnitAbbreviation_Yottabyte"),
+ 33));
+ dataUnits.PushBack(new OrderedUnit(
+ UnitConverterUnits.Data_Zebibits,
+ GetLocalizedStringName("UnitName_Zebibits"),
+ GetLocalizedStringName("UnitAbbreviation_Zebibits"),
+ 28));
+ dataUnits.PushBack(new OrderedUnit(
+ UnitConverterUnits.Data_Zebibytes,
+ GetLocalizedStringName("UnitName_Zebibytes"),
+ GetLocalizedStringName("UnitAbbreviation_Zebibytes"),
+ 30));
+ dataUnits.PushBack(new OrderedUnit(
+ UnitConverterUnits.Data_Zetabits,
+ GetLocalizedStringName("UnitName_Zetabits"),
+ GetLocalizedStringName("UnitAbbreviation_Zetabits"),
+ 27));
+ dataUnits.PushBack(new OrderedUnit(
+ UnitConverterUnits.Data_Zetabytes,
+ GetLocalizedStringName("UnitName_Zetabytes"),
+ GetLocalizedStringName("UnitAbbreviation_Zetabytes"),
+ 29));
+ dataUnits.PushBack(new OrderedUnit(UnitConverterUnits.Data_FloppyDisk,
+ GetLocalizedStringName("UnitName_FloppyDisk"),
+ GetLocalizedStringName("UnitAbbreviation_FloppyDisk"),
+ 13,
+ false,
+ false,
+ true));
+ dataUnits.PushBack(new OrderedUnit(
+ UnitConverterUnits.Data_CD,
+ GetLocalizedStringName("UnitName_CD"),
+ GetLocalizedStringName("UnitAbbreviation_CD"),
+ 14,
+ false,
+ false,
+ true));
+ dataUnits.PushBack(new OrderedUnit(
+ UnitConverterUnits.Data_DVD,
+ GetLocalizedStringName("UnitName_DVD"),
+ GetLocalizedStringName("UnitAbbreviation_DVD"),
+ 15,
+ false,
+ false,
+ true));
+ unitMap.Add(ViewMode.Data, dataUnits);
+
+ CalculatorList energyUnits = new CalculatorList();
+ energyUnits.PushBack(new OrderedUnit(UnitConverterUnits.Energy_BritishThermalUnit,
+ GetLocalizedStringName("UnitName_BritishThermalUnit"),
+ GetLocalizedStringName("UnitAbbreviation_BritishThermalUnit"),
+ 7));
+ energyUnits.PushBack(
+ new OrderedUnit(UnitConverterUnits.Energy_Calorie, GetLocalizedStringName("UnitName_Calorie"), GetLocalizedStringName("UnitAbbreviation_Calorie"), 4));
+ energyUnits.PushBack(new OrderedUnit(UnitConverterUnits.Energy_ElectronVolt,
+ GetLocalizedStringName("UnitName_Electron-Volt"),
+ GetLocalizedStringName("UnitAbbreviation_Electron-Volt"),
+ 1));
+ energyUnits.PushBack(new OrderedUnit(
+ UnitConverterUnits.Energy_FootPound,
+ GetLocalizedStringName("UnitName_Foot-Pound"),
+ GetLocalizedStringName("UnitAbbreviation_Foot-Pound"),
+ 6));
+ energyUnits.PushBack(new OrderedUnit(UnitConverterUnits.Energy_Joule,
+ GetLocalizedStringName("UnitName_Joule"),
+ GetLocalizedStringName("UnitAbbreviation_Joule"),
+ 2,
+ true,
+ false,
+ false));
+ energyUnits.PushBack(new OrderedUnit(UnitConverterUnits.Energy_Kilocalorie,
+ GetLocalizedStringName("UnitName_Kilocalorie"),
+ GetLocalizedStringName("UnitAbbreviation_Kilocalorie"),
+ 5,
+ false,
+ true,
+ false));
+ energyUnits.PushBack(new OrderedUnit(
+ UnitConverterUnits.Energy_Kilojoule,
+ GetLocalizedStringName("UnitName_Kilojoule"),
+ GetLocalizedStringName("UnitAbbreviation_Kilojoule"),
+ 3));
+ energyUnits.PushBack(new OrderedUnit(UnitConverterUnits.Energy_Battery,
+ GetLocalizedStringName("UnitName_Battery"),
+ GetLocalizedStringName("UnitAbbreviation_Battery"),
+ 8,
+ false,
+ false,
+ true));
+ energyUnits.PushBack(new OrderedUnit(UnitConverterUnits.Energy_Banana,
+ GetLocalizedStringName("UnitName_Banana"),
+ GetLocalizedStringName("UnitAbbreviation_Banana"),
+ 9,
+ false,
+ false,
+ true));
+ energyUnits.PushBack(new OrderedUnit(UnitConverterUnits.Energy_SliceOfCake,
+ GetLocalizedStringName("UnitName_SliceOfCake"),
+ GetLocalizedStringName("UnitAbbreviation_SliceOfCake"),
+ 10,
+ false,
+ false,
+ true));
+ unitMap.Add(ViewMode.Energy, energyUnits);
+
+ CalculatorList lengthUnits = new CalculatorList();
+ lengthUnits.PushBack(new OrderedUnit(UnitConverterUnits.Length_Centimeter,
+ GetLocalizedStringName("UnitName_Centimeter"),
+ GetLocalizedStringName("UnitAbbreviation_Centimeter"),
+ 4,
+ useUSCustomary,
+ useSI,
+ false));
+ lengthUnits.PushBack(
+ new OrderedUnit(UnitConverterUnits.Length_Foot, GetLocalizedStringName("UnitName_Foot"), GetLocalizedStringName("UnitAbbreviation_Foot"), 8));
+ lengthUnits.PushBack(new OrderedUnit(UnitConverterUnits.Length_Inch,
+ GetLocalizedStringName("UnitName_Inch"),
+ GetLocalizedStringName("UnitAbbreviation_Inch"),
+ 7,
+ useSI,
+ useUSCustomary,
+ false));
+ lengthUnits.PushBack(new OrderedUnit(
+ UnitConverterUnits.Length_Kilometer,
+ GetLocalizedStringName("UnitName_Kilometer"),
+ GetLocalizedStringName("UnitAbbreviation_Kilometer"),
+ 6));
+ lengthUnits.PushBack(
+ new OrderedUnit(UnitConverterUnits.Length_Meter, GetLocalizedStringName("UnitName_Meter"), GetLocalizedStringName("UnitAbbreviation_Meter"), 5));
+ lengthUnits.PushBack(
+ new OrderedUnit(UnitConverterUnits.Length_Micron, GetLocalizedStringName("UnitName_Micron"), GetLocalizedStringName("UnitAbbreviation_Micron"), 2));
+ lengthUnits.PushBack(
+ new OrderedUnit(UnitConverterUnits.Length_Mile, GetLocalizedStringName("UnitName_Mile"), GetLocalizedStringName("UnitAbbreviation_Mile"), 10));
+ lengthUnits.PushBack(new OrderedUnit(
+ UnitConverterUnits.Length_Millimeter,
+ GetLocalizedStringName("UnitName_Millimeter"),
+ GetLocalizedStringName("UnitAbbreviation_Millimeter"),
+ 3));
+ lengthUnits.PushBack(new OrderedUnit(
+ UnitConverterUnits.Length_Nanometer,
+ GetLocalizedStringName("UnitName_Nanometer"),
+ GetLocalizedStringName("UnitAbbreviation_Nanometer"),
+ 1));
+ lengthUnits.PushBack(new OrderedUnit(UnitConverterUnits.Length_NauticalMile,
+ GetLocalizedStringName("UnitName_NauticalMile"),
+ GetLocalizedStringName("UnitAbbreviation_NauticalMile"),
+ 11));
+ lengthUnits.PushBack(
+ new OrderedUnit(UnitConverterUnits.Length_Yard, GetLocalizedStringName("UnitName_Yard"), GetLocalizedStringName("UnitAbbreviation_Yard"), 9));
+ lengthUnits.PushBack(new OrderedUnit(UnitConverterUnits.Length_Paperclip,
+ GetLocalizedStringName("UnitName_Paperclip"),
+ GetLocalizedStringName("UnitAbbreviation_Paperclip"),
+ 12,
+ false,
+ false,
+ true));
+ lengthUnits.PushBack(new OrderedUnit(
+ UnitConverterUnits.Length_Hand,
+ GetLocalizedStringName("UnitName_Hand"),
+ GetLocalizedStringName("UnitAbbreviation_Hand"),
+ 13,
+ false,
+ false,
+ true));
+ lengthUnits.PushBack(new OrderedUnit(UnitConverterUnits.Length_JumboJet,
+ GetLocalizedStringName("UnitName_JumboJet"),
+ GetLocalizedStringName("UnitAbbreviation_JumboJet"),
+ 14,
+ false,
+ false,
+ true));
+ unitMap.Add(ViewMode.Length, lengthUnits);
+
+ CalculatorList powerUnits = new CalculatorList();
+ powerUnits.PushBack(new OrderedUnit(UnitConverterUnits.Power_BritishThermalUnitPerMinute,
+ GetLocalizedStringName("UnitName_BTUPerMinute"),
+ GetLocalizedStringName("UnitAbbreviation_BTUPerMinute"),
+ 5));
+ powerUnits.PushBack(new OrderedUnit(UnitConverterUnits.Power_FootPoundPerMinute,
+ GetLocalizedStringName("UnitName_Foot-PoundPerMinute"),
+ GetLocalizedStringName("UnitAbbreviation_Foot-PoundPerMinute"),
+ 4));
+ powerUnits.PushBack(new OrderedUnit(UnitConverterUnits.Power_Horsepower,
+ GetLocalizedStringName("UnitName_Horsepower"),
+ GetLocalizedStringName("UnitAbbreviation_Horsepower"),
+ 3,
+ false,
+ true,
+ false));
+ powerUnits.PushBack(new OrderedUnit(UnitConverterUnits.Power_Kilowatt,
+ GetLocalizedStringName("UnitName_Kilowatt"),
+ GetLocalizedStringName("UnitAbbreviation_Kilowatt"),
+ 2,
+ !useWattInsteadOfKilowatt));
+ powerUnits.PushBack(new OrderedUnit(UnitConverterUnits.Power_Watt,
+ GetLocalizedStringName("UnitName_Watt"),
+ GetLocalizedStringName("UnitAbbreviation_Watt"),
+ 1,
+ useWattInsteadOfKilowatt));
+ powerUnits.PushBack(new OrderedUnit(UnitConverterUnits.Power_LightBulb,
+ GetLocalizedStringName("UnitName_LightBulb"),
+ GetLocalizedStringName("UnitAbbreviation_LightBulb"),
+ 6,
+ false,
+ false,
+ true));
+ powerUnits.PushBack(new OrderedUnit(
+ UnitConverterUnits.Power_Horse,
+ GetLocalizedStringName("UnitName_Horse"),
+ GetLocalizedStringName("UnitAbbreviation_Horse"),
+ 7,
+ false,
+ false,
+ true));
+ powerUnits.PushBack(new OrderedUnit(UnitConverterUnits.Power_TrainEngine,
+ GetLocalizedStringName("UnitName_TrainEngine"),
+ GetLocalizedStringName("UnitAbbreviation_TrainEngine"),
+ 8,
+ false,
+ false,
+ true));
+ unitMap.Add(ViewMode.Power, powerUnits);
+
+ CalculatorList tempUnits = new CalculatorList();
+ tempUnits.PushBack(new OrderedUnit(UnitConverterUnits.Temperature_DegreesCelsius,
+ GetLocalizedStringName("UnitName_DegreesCelsius"),
+ GetLocalizedStringName("UnitAbbreviation_DegreesCelsius"),
+ 1,
+ useFahrenheit,
+ !useFahrenheit,
+ false));
+ tempUnits.PushBack(new OrderedUnit(UnitConverterUnits.Temperature_DegreesFahrenheit,
+ GetLocalizedStringName("UnitName_DegreesFahrenheit"),
+ GetLocalizedStringName("UnitAbbreviation_DegreesFahrenheit"),
+ 2,
+ !useFahrenheit,
+ useFahrenheit,
+ false));
+ tempUnits.PushBack(new OrderedUnit(
+ UnitConverterUnits.Temperature_Kelvin,
+ GetLocalizedStringName("UnitName_Kelvin"),
+ GetLocalizedStringName("UnitAbbreviation_Kelvin"),
+ 3));
+ unitMap.Add(ViewMode.Temperature, tempUnits);
+
+ CalculatorList timeUnits = new CalculatorList();
+ timeUnits.PushBack(
+ new OrderedUnit(UnitConverterUnits.Time_Day, GetLocalizedStringName("UnitName_Day"), GetLocalizedStringName("UnitAbbreviation_Day"), 6));
+ timeUnits.PushBack(new OrderedUnit(
+ UnitConverterUnits.Time_Hour,
+ GetLocalizedStringName("UnitName_Hour"),
+ GetLocalizedStringName("UnitAbbreviation_Hour"),
+ 5,
+ true,
+ false,
+ false));
+ timeUnits.PushBack(new OrderedUnit(
+ UnitConverterUnits.Time_Microsecond,
+ GetLocalizedStringName("UnitName_Microsecond"),
+ GetLocalizedStringName("UnitAbbreviation_Microsecond"),
+ 1));
+ timeUnits.PushBack(new OrderedUnit(
+ UnitConverterUnits.Time_Millisecond,
+ GetLocalizedStringName("UnitName_Millisecond"),
+ GetLocalizedStringName("UnitAbbreviation_Millisecond"),
+ 2));
+ timeUnits.PushBack(new OrderedUnit(UnitConverterUnits.Time_Minute,
+ GetLocalizedStringName("UnitName_Minute"),
+ GetLocalizedStringName("UnitAbbreviation_Minute"),
+ 4,
+ false,
+ true,
+ false));
+ timeUnits.PushBack(
+ new OrderedUnit(UnitConverterUnits.Time_Second, GetLocalizedStringName("UnitName_Second"), GetLocalizedStringName("UnitAbbreviation_Second"), 3));
+ timeUnits.PushBack(
+ new OrderedUnit(UnitConverterUnits.Time_Week, GetLocalizedStringName("UnitName_Week"), GetLocalizedStringName("UnitAbbreviation_Week"), 7));
+ timeUnits.PushBack(
+ new OrderedUnit(UnitConverterUnits.Time_Year, GetLocalizedStringName("UnitName_Year"), GetLocalizedStringName("UnitAbbreviation_Year"), 8));
+ unitMap.Add(ViewMode.Time, timeUnits);
+
+ CalculatorList speedUnits = new CalculatorList();
+ speedUnits.PushBack(new OrderedUnit(UnitConverterUnits.Speed_CentimetersPerSecond,
+ GetLocalizedStringName("UnitName_CentimetersPerSecond"),
+ GetLocalizedStringName("UnitAbbreviation_CentimetersPerSecond"),
+ 1));
+ speedUnits.PushBack(new OrderedUnit(UnitConverterUnits.Speed_FeetPerSecond,
+ GetLocalizedStringName("UnitName_FeetPerSecond"),
+ GetLocalizedStringName("UnitAbbreviation_FeetPerSecond"),
+ 4));
+ speedUnits.PushBack(new OrderedUnit(UnitConverterUnits.Speed_KilometersPerHour,
+ GetLocalizedStringName("UnitName_KilometersPerHour"),
+ GetLocalizedStringName("UnitAbbreviation_KilometersPerHour"),
+ 3,
+ useUSCustomary,
+ useSI,
+ false));
+ speedUnits.PushBack(
+ new OrderedUnit(UnitConverterUnits.Speed_Knot, GetLocalizedStringName("UnitName_Knot"), GetLocalizedStringName("UnitAbbreviation_Knot"), 6));
+ speedUnits.PushBack(
+ new OrderedUnit(UnitConverterUnits.Speed_Mach, GetLocalizedStringName("UnitName_Mach"), GetLocalizedStringName("UnitAbbreviation_Mach"), 7));
+ speedUnits.PushBack(new OrderedUnit(UnitConverterUnits.Speed_MetersPerSecond,
+ GetLocalizedStringName("UnitName_MetersPerSecond"),
+ GetLocalizedStringName("UnitAbbreviation_MetersPerSecond"),
+ 2));
+ speedUnits.PushBack(new OrderedUnit(UnitConverterUnits.Speed_MilesPerHour,
+ GetLocalizedStringName("UnitName_MilesPerHour"),
+ GetLocalizedStringName("UnitAbbreviation_MilesPerHour"),
+ 5,
+ useSI,
+ useUSCustomary,
+ false));
+ speedUnits.PushBack(new OrderedUnit(UnitConverterUnits.Speed_Turtle,
+ GetLocalizedStringName("UnitName_Turtle"),
+ GetLocalizedStringName("UnitAbbreviation_Turtle"),
+ 8,
+ false,
+ false,
+ true));
+ speedUnits.PushBack(new OrderedUnit(
+ UnitConverterUnits.Speed_Horse,
+ GetLocalizedStringName("UnitName_Horse"),
+ GetLocalizedStringName("UnitAbbreviation_Horse"),
+ 9,
+ false,
+ false,
+ true));
+ speedUnits.PushBack(new OrderedUnit(
+ UnitConverterUnits.Speed_Jet,
+ GetLocalizedStringName("UnitName_Jet"),
+ GetLocalizedStringName("UnitAbbreviation_Jet"),
+ 10,
+ false,
+ false,
+ true));
+ unitMap.Add(ViewMode.Speed, speedUnits);
+
+ CalculatorList volumeUnits = new CalculatorList();
+ volumeUnits.PushBack(new OrderedUnit(UnitConverterUnits.Volume_CubicCentimeter,
+ GetLocalizedStringName("UnitName_CubicCentimeter"),
+ GetLocalizedStringName("UnitAbbreviation_CubicCentimeter"),
+ 2));
+ volumeUnits.PushBack(new OrderedUnit(
+ UnitConverterUnits.Volume_CubicFoot,
+ GetLocalizedStringName("UnitName_CubicFoot"),
+ GetLocalizedStringName("UnitAbbreviation_CubicFoot"),
+ 13));
+ volumeUnits.PushBack(new OrderedUnit(
+ UnitConverterUnits.Volume_CubicInch,
+ GetLocalizedStringName("UnitName_CubicInch"),
+ GetLocalizedStringName("UnitAbbreviation_CubicInch"),
+ 12));
+ volumeUnits.PushBack(new OrderedUnit(
+ UnitConverterUnits.Volume_CubicMeter,
+ GetLocalizedStringName("UnitName_CubicMeter"),
+ GetLocalizedStringName("UnitAbbreviation_CubicMeter"),
+ 4));
+ volumeUnits.PushBack(new OrderedUnit(
+ UnitConverterUnits.Volume_CubicYard,
+ GetLocalizedStringName("UnitName_CubicYard"),
+ GetLocalizedStringName("UnitAbbreviation_CubicYard"),
+ 14));
+ volumeUnits.PushBack(
+ new OrderedUnit(UnitConverterUnits.Volume_CupUS, GetLocalizedStringName("UnitName_CupUS"), GetLocalizedStringName("UnitAbbreviation_CupUS"), 8));
+ volumeUnits.PushBack(new OrderedUnit(UnitConverterUnits.Volume_FluidOunceUK,
+ GetLocalizedStringName("UnitName_FluidOunceUK"),
+ GetLocalizedStringName("UnitAbbreviation_FluidOunceUK"),
+ 17));
+ volumeUnits.PushBack(new OrderedUnit(UnitConverterUnits.Volume_FluidOunceUS,
+ GetLocalizedStringName("UnitName_FluidOunceUS"),
+ GetLocalizedStringName("UnitAbbreviation_FluidOunceUS"),
+ 7));
+ volumeUnits.PushBack(new OrderedUnit(
+ UnitConverterUnits.Volume_GallonUK,
+ GetLocalizedStringName("UnitName_GallonUK"),
+ GetLocalizedStringName("UnitAbbreviation_GallonUK"),
+ 20));
+ volumeUnits.PushBack(new OrderedUnit(
+ UnitConverterUnits.Volume_GallonUS,
+ GetLocalizedStringName("UnitName_GallonUS"),
+ GetLocalizedStringName("UnitAbbreviation_GallonUS"),
+ 11));
+ volumeUnits.PushBack(
+ new OrderedUnit(UnitConverterUnits.Volume_Liter, GetLocalizedStringName("UnitName_Liter"), GetLocalizedStringName("UnitAbbreviation_Liter"), 3));
+ volumeUnits.PushBack(new OrderedUnit(UnitConverterUnits.Volume_Milliliter,
+ GetLocalizedStringName("UnitName_Milliliter"),
+ GetLocalizedStringName("UnitAbbreviation_Milliliter"),
+ 1,
+ useUSCustomary,
+ useSI));
+ volumeUnits.PushBack(
+ new OrderedUnit(UnitConverterUnits.Volume_PintUK, GetLocalizedStringName("UnitName_PintUK"), GetLocalizedStringName("UnitAbbreviation_PintUK"), 18));
+ volumeUnits.PushBack(
+ new OrderedUnit(UnitConverterUnits.Volume_PintUS, GetLocalizedStringName("UnitName_PintUS"), GetLocalizedStringName("UnitAbbreviation_PintUS"), 9));
+ volumeUnits.PushBack(new OrderedUnit(UnitConverterUnits.Volume_TablespoonUS,
+ GetLocalizedStringName("UnitName_TablespoonUS"),
+ GetLocalizedStringName("UnitAbbreviation_TablespoonUS"),
+ 6));
+ volumeUnits.PushBack(new OrderedUnit(UnitConverterUnits.Volume_TeaspoonUS,
+ GetLocalizedStringName("UnitName_TeaspoonUS"),
+ GetLocalizedStringName("UnitAbbreviation_TeaspoonUS"),
+ 5,
+ useSI,
+ useUSCustomary && m_currentRegionCode != "GB"));
+ volumeUnits.PushBack(new OrderedUnit(
+ UnitConverterUnits.Volume_QuartUK,
+ GetLocalizedStringName("UnitName_QuartUK"),
+ GetLocalizedStringName("UnitAbbreviation_QuartUK"),
+ 19));
+ volumeUnits.PushBack(new OrderedUnit(
+ UnitConverterUnits.Volume_QuartUS,
+ GetLocalizedStringName("UnitName_QuartUS"),
+ GetLocalizedStringName("UnitAbbreviation_QuartUS"),
+ 10));
+ volumeUnits.PushBack(new OrderedUnit(UnitConverterUnits.Volume_TeaspoonUK,
+ GetLocalizedStringName("UnitName_TeaspoonUK"),
+ GetLocalizedStringName("UnitAbbreviation_TeaspoonUK"),
+ 15,
+ false,
+ useUSCustomary && m_currentRegionCode == "GB"));
+ volumeUnits.PushBack(new OrderedUnit(UnitConverterUnits.Volume_TablespoonUK,
+ GetLocalizedStringName("UnitName_TablespoonUK"),
+ GetLocalizedStringName("UnitAbbreviation_TablespoonUK"),
+ 16));
+ volumeUnits.PushBack(new OrderedUnit(UnitConverterUnits.Volume_CoffeeCup,
+ GetLocalizedStringName("UnitName_CoffeeCup"),
+ GetLocalizedStringName("UnitAbbreviation_CoffeeCup"),
+ 22,
+ false,
+ false,
+ true));
+ volumeUnits.PushBack(new OrderedUnit(UnitConverterUnits.Volume_Bathtub,
+ GetLocalizedStringName("UnitName_Bathtub"),
+ GetLocalizedStringName("UnitAbbreviation_Bathtub"),
+ 23,
+ false,
+ false,
+ true));
+ volumeUnits.PushBack(new OrderedUnit(UnitConverterUnits.Volume_SwimmingPool,
+ GetLocalizedStringName("UnitName_SwimmingPool"),
+ GetLocalizedStringName("UnitAbbreviation_SwimmingPool"),
+ 24,
+ false,
+ false,
+ true));
+ unitMap.Add(ViewMode.Volume, volumeUnits);
+
+ CalculatorList weightUnits = new CalculatorList();
+ weightUnits.PushBack(
+ new OrderedUnit(UnitConverterUnits.Weight_Carat, GetLocalizedStringName("UnitName_Carat"), GetLocalizedStringName("UnitAbbreviation_Carat"), 1));
+ weightUnits.PushBack(new OrderedUnit(
+ UnitConverterUnits.Weight_Centigram,
+ GetLocalizedStringName("UnitName_Centigram"),
+ GetLocalizedStringName("UnitAbbreviation_Centigram"),
+ 3));
+ weightUnits.PushBack(new OrderedUnit(
+ UnitConverterUnits.Weight_Decigram,
+ GetLocalizedStringName("UnitName_Decigram"),
+ GetLocalizedStringName("UnitAbbreviation_Decigram"),
+ 4));
+ weightUnits.PushBack(new OrderedUnit(
+ UnitConverterUnits.Weight_Decagram,
+ GetLocalizedStringName("UnitName_Decagram"),
+ GetLocalizedStringName("UnitAbbreviation_Decagram"),
+ 6));
+ weightUnits.PushBack(
+ new OrderedUnit(UnitConverterUnits.Weight_Gram, GetLocalizedStringName("UnitName_Gram"), GetLocalizedStringName("UnitAbbreviation_Gram"), 5));
+ weightUnits.PushBack(new OrderedUnit(
+ UnitConverterUnits.Weight_Hectogram,
+ GetLocalizedStringName("UnitName_Hectogram"),
+ GetLocalizedStringName("UnitAbbreviation_Hectogram"),
+ 7));
+ weightUnits.PushBack(new OrderedUnit(UnitConverterUnits.Weight_Kilogram,
+ GetLocalizedStringName("UnitName_Kilogram"),
+ GetLocalizedStringName("UnitAbbreviation_Kilogram"),
+ 8,
+ useUSCustomary,
+ useSI));
+ weightUnits.PushBack(new OrderedUnit(
+ UnitConverterUnits.Weight_LongTon,
+ GetLocalizedStringName("UnitName_LongTon"),
+ GetLocalizedStringName("UnitAbbreviation_LongTon"),
+ 14));
+ weightUnits.PushBack(new OrderedUnit(
+ UnitConverterUnits.Weight_Milligram,
+ GetLocalizedStringName("UnitName_Milligram"),
+ GetLocalizedStringName("UnitAbbreviation_Milligram"),
+ 2));
+ weightUnits.PushBack(
+ new OrderedUnit(UnitConverterUnits.Weight_Ounce, GetLocalizedStringName("UnitName_Ounce"), GetLocalizedStringName("UnitAbbreviation_Ounce"), 10));
+ weightUnits.PushBack(new OrderedUnit(UnitConverterUnits.Weight_Pound,
+ GetLocalizedStringName("UnitName_Pound"),
+ GetLocalizedStringName("UnitAbbreviation_Pound"),
+ 11,
+ useSI,
+ useUSCustomary));
+ weightUnits.PushBack(new OrderedUnit(
+ UnitConverterUnits.Weight_ShortTon,
+ GetLocalizedStringName("UnitName_ShortTon"),
+ GetLocalizedStringName("UnitAbbreviation_ShortTon"),
+ 13));
+ weightUnits.PushBack(
+ new OrderedUnit(UnitConverterUnits.Weight_Stone, GetLocalizedStringName("UnitName_Stone"), GetLocalizedStringName("UnitAbbreviation_Stone"), 12));
+ weightUnits.PushBack(
+ new OrderedUnit(UnitConverterUnits.Weight_Tonne, GetLocalizedStringName("UnitName_Tonne"), GetLocalizedStringName("UnitAbbreviation_Tonne"), 9));
+ weightUnits.PushBack(new OrderedUnit(UnitConverterUnits.Weight_Snowflake,
+ GetLocalizedStringName("UnitName_Snowflake"),
+ GetLocalizedStringName("UnitAbbreviation_Snowflake"),
+ 15,
+ false,
+ false,
+ true));
+ weightUnits.PushBack(new OrderedUnit(UnitConverterUnits.Weight_SoccerBall,
+ GetLocalizedStringName("UnitName_SoccerBall"),
+ GetLocalizedStringName("UnitAbbreviation_SoccerBall"),
+ 16,
+ false,
+ false,
+ true));
+ weightUnits.PushBack(new OrderedUnit(UnitConverterUnits.Weight_Elephant,
+ GetLocalizedStringName("UnitName_Elephant"),
+ GetLocalizedStringName("UnitAbbreviation_Elephant"),
+ 17,
+ false,
+ false,
+ true));
+ weightUnits.PushBack(new OrderedUnit(UnitConverterUnits.Weight_Whale,
+ GetLocalizedStringName("UnitName_Whale"),
+ GetLocalizedStringName("UnitAbbreviation_Whale"),
+ 18,
+ false,
+ false,
+ true));
+ unitMap.Add(ViewMode.Weight, weightUnits);
+
+ CalculatorList pressureUnits = new CalculatorList();
+ pressureUnits.PushBack(new OrderedUnit(UnitConverterUnits.Pressure_Atmosphere,
+ GetLocalizedStringName("UnitName_Atmosphere"),
+ GetLocalizedStringName("UnitAbbreviation_Atmosphere"),
+ 1,
+ true,
+ false,
+ false));
+ pressureUnits.PushBack(new OrderedUnit(
+ UnitConverterUnits.Pressure_Bar,
+ GetLocalizedStringName("UnitName_Bar"),
+ GetLocalizedStringName("UnitAbbreviation_Bar"),
+ 2,
+ false,
+ true,
+ false));
+ pressureUnits.PushBack(new OrderedUnit(
+ UnitConverterUnits.Pressure_KiloPascal,
+ GetLocalizedStringName("UnitName_KiloPascal"),
+ GetLocalizedStringName("UnitAbbreviation_KiloPascal"),
+ 3));
+ pressureUnits.PushBack(new OrderedUnit(UnitConverterUnits.Pressure_MillimeterOfMercury,
+ GetLocalizedStringName("UnitName_MillimeterOfMercury "),
+ GetLocalizedStringName("UnitAbbreviation_MillimeterOfMercury "),
+ 4));
+ pressureUnits.PushBack(
+ new OrderedUnit(UnitConverterUnits.Pressure_Pascal, GetLocalizedStringName("UnitName_Pascal"), GetLocalizedStringName("UnitAbbreviation_Pascal"), 5));
+ pressureUnits.PushBack(new OrderedUnit(
+ UnitConverterUnits.Pressure_PSI,
+ GetLocalizedStringName("UnitName_PSI"),
+ GetLocalizedStringName("UnitAbbreviation_PSI"),
+ 6,
+ false,
+ false,
+ false));
+ unitMap.Add(ViewMode.Pressure, pressureUnits);
+
+ CalculatorList angleUnits = new CalculatorList();
+ angleUnits.PushBack(new OrderedUnit(UnitConverterUnits.Angle_Degree,
+ GetLocalizedStringName("UnitName_Degree"),
+ GetLocalizedStringName("UnitAbbreviation_Degree"),
+ 1,
+ true,
+ false,
+ false));
+ angleUnits.PushBack(new OrderedUnit(UnitConverterUnits.Angle_Radian,
+ GetLocalizedStringName("UnitName_Radian"),
+ GetLocalizedStringName("UnitAbbreviation_Radian"),
+ 2,
+ false,
+ true,
+ false));
+ angleUnits.PushBack(
+ new OrderedUnit(UnitConverterUnits.Angle_Gradian, GetLocalizedStringName("UnitName_Gradian"), GetLocalizedStringName("UnitAbbreviation_Gradian"), 3));
+ unitMap.Add(ViewMode.Angle, angleUnits);
+ }
+
+ void GetConversionData(Dictionary> categoryToUnitConversionMap)
+ {
+ /*categoryId, UnitId, factor*/
+ CalculatorList unitDataList = new CalculatorList
+ {
+ new UnitData(ViewMode.Area, UnitConverterUnits.Area_Acre, 4046.8564224),
+ new UnitData(ViewMode.Area, UnitConverterUnits.Area_SquareMeter, 1),
+ new UnitData(ViewMode.Area, UnitConverterUnits.Area_SquareFoot, 0.09290304),
+ new UnitData(ViewMode.Area, UnitConverterUnits.Area_SquareYard, 0.83612736),
+ new UnitData(ViewMode.Area, UnitConverterUnits.Area_SquareMillimeter, 0.000001),
+ new UnitData(ViewMode.Area, UnitConverterUnits.Area_SquareCentimeter, 0.0001),
+ new UnitData(ViewMode.Area, UnitConverterUnits.Area_SquareInch, 0.00064516),
+ new UnitData(ViewMode.Area, UnitConverterUnits.Area_SquareMile, 2589988.110336),
+ new UnitData(ViewMode.Area, UnitConverterUnits.Area_SquareKilometer, 1000000),
+ new UnitData(ViewMode.Area, UnitConverterUnits.Area_Hectare, 10000),
+ new UnitData(ViewMode.Area, UnitConverterUnits.Area_Hand, 0.012516104),
+ new UnitData(ViewMode.Area, UnitConverterUnits.Area_Paper, 0.06032246),
+ new UnitData(ViewMode.Area, UnitConverterUnits.Area_SoccerField, 10869.66),
+ new UnitData(ViewMode.Area, UnitConverterUnits.Area_Castle, 100000),
+ new UnitData(ViewMode.Area, UnitConverterUnits.Area_Pyeong, 400.0 / 121.0),
+
+ new UnitData(ViewMode.Data, UnitConverterUnits.Data_Bit, 0.000000125),
+ new UnitData(ViewMode.Data, UnitConverterUnits.Data_Byte, 0.000001),
+ new UnitData(ViewMode.Data, UnitConverterUnits.Data_Kilobyte, 0.001),
+ new UnitData(ViewMode.Data, UnitConverterUnits.Data_Megabyte, 1),
+ new UnitData(ViewMode.Data, UnitConverterUnits.Data_Gigabyte, 1000),
+ new UnitData(ViewMode.Data, UnitConverterUnits.Data_Terabyte, 1000000),
+ new UnitData(ViewMode.Data, UnitConverterUnits.Data_Petabyte, 1000000000),
+ new UnitData(ViewMode.Data, UnitConverterUnits.Data_Exabytes, 1000000000000),
+ new UnitData(ViewMode.Data, UnitConverterUnits.Data_Zetabytes, 1000000000000000),
+ new UnitData(ViewMode.Data, UnitConverterUnits.Data_Yottabyte, 1000000000000000000),
+ new UnitData(ViewMode.Data, UnitConverterUnits.Data_Kilobit, 0.000125),
+ new UnitData(ViewMode.Data, UnitConverterUnits.Data_Megabit, 0.125),
+ new UnitData(ViewMode.Data, UnitConverterUnits.Data_Gigabit, 125),
+ new UnitData(ViewMode.Data, UnitConverterUnits.Data_Terabit, 125000),
+ new UnitData(ViewMode.Data, UnitConverterUnits.Data_Petabit, 125000000),
+ new UnitData(ViewMode.Data, UnitConverterUnits.Data_Exabits, 125000000000),
+ new UnitData(ViewMode.Data, UnitConverterUnits.Data_Zetabits, 125000000000000),
+ new UnitData(ViewMode.Data, UnitConverterUnits.Data_Yottabit, 125000000000000000),
+ new UnitData(ViewMode.Data, UnitConverterUnits.Data_Gibibits, 134.217728),
+ new UnitData(ViewMode.Data, UnitConverterUnits.Data_Gibibytes, 1073.741824),
+ new UnitData(ViewMode.Data, UnitConverterUnits.Data_Kibibits, 0.000128),
+ new UnitData(ViewMode.Data, UnitConverterUnits.Data_Kibibytes, 0.001024),
+ new UnitData(ViewMode.Data, UnitConverterUnits.Data_Mebibits, 0.131072),
+ new UnitData(ViewMode.Data, UnitConverterUnits.Data_Mebibytes, 1.048576),
+ new UnitData(ViewMode.Data, UnitConverterUnits.Data_Pebibits, 140737488.355328),
+ new UnitData(ViewMode.Data, UnitConverterUnits.Data_Pebibytes, 1125899906.842624),
+ new UnitData(ViewMode.Data, UnitConverterUnits.Data_Tebibits, 137438.953472),
+ new UnitData(ViewMode.Data, UnitConverterUnits.Data_Tebibytes, 1099511.627776),
+ new UnitData(ViewMode.Data, UnitConverterUnits.Data_Exbibits, 144115188075.855872),
+ new UnitData(ViewMode.Data, UnitConverterUnits.Data_Exbibytes, 1152921504606.846976),
+ new UnitData(ViewMode.Data, UnitConverterUnits.Data_Zebibits, 147573952589676.412928),
+ new UnitData(ViewMode.Data, UnitConverterUnits.Data_Zebibytes, 1180591620717411.303424),
+ new UnitData(ViewMode.Data, UnitConverterUnits.Data_Yobibits, 151115727451828646.838272),
+ new UnitData(ViewMode.Data, UnitConverterUnits.Data_Yobibytes, 1208925819614629174.706176),
+ new UnitData(ViewMode.Data, UnitConverterUnits.Data_FloppyDisk, 1.509949),
+ new UnitData(ViewMode.Data, UnitConverterUnits.Data_CD, 734.003200),
+ new UnitData(ViewMode.Data, UnitConverterUnits.Data_DVD, 5046.586573),
+
+ new UnitData(ViewMode.Energy, UnitConverterUnits.Energy_Calorie, 4.184),
+ new UnitData(ViewMode.Energy, UnitConverterUnits.Energy_Kilocalorie, 4184),
+ new UnitData(ViewMode.Energy, UnitConverterUnits.Energy_BritishThermalUnit, 1055.056),
+ new UnitData(ViewMode.Energy, UnitConverterUnits.Energy_Kilojoule, 1000),
+ new UnitData(ViewMode.Energy, UnitConverterUnits.Energy_ElectronVolt, 0.0000000000000000001602176565),
+ new UnitData(ViewMode.Energy, UnitConverterUnits.Energy_Joule, 1),
+ new UnitData(ViewMode.Energy, UnitConverterUnits.Energy_FootPound, 1.3558179483314),
+ new UnitData(ViewMode.Energy, UnitConverterUnits.Energy_Battery, 9000),
+ new UnitData(ViewMode.Energy, UnitConverterUnits.Energy_Banana, 439614),
+ new UnitData(ViewMode.Energy, UnitConverterUnits.Energy_SliceOfCake, 1046700),
+
+ new UnitData(ViewMode.Length, UnitConverterUnits.Length_Inch, 0.0254),
+ new UnitData(ViewMode.Length, UnitConverterUnits.Length_Foot, 0.3048),
+ new UnitData(ViewMode.Length, UnitConverterUnits.Length_Yard, 0.9144),
+ new UnitData(ViewMode.Length, UnitConverterUnits.Length_Mile, 1609.344),
+ new UnitData(ViewMode.Length, UnitConverterUnits.Length_Micron, 0.000001),
+ new UnitData(ViewMode.Length, UnitConverterUnits.Length_Millimeter, 0.001),
+ new UnitData(ViewMode.Length, UnitConverterUnits.Length_Nanometer, 0.000000001),
+ new UnitData(ViewMode.Length, UnitConverterUnits.Length_Centimeter, 0.01),
+ new UnitData(ViewMode.Length, UnitConverterUnits.Length_Meter, 1),
+ new UnitData(ViewMode.Length, UnitConverterUnits.Length_Kilometer, 1000),
+ new UnitData(ViewMode.Length, UnitConverterUnits.Length_NauticalMile, 1852),
+ new UnitData(ViewMode.Length, UnitConverterUnits.Length_Paperclip, 0.035052),
+ new UnitData(ViewMode.Length, UnitConverterUnits.Length_Hand, 0.18669),
+ new UnitData(ViewMode.Length, UnitConverterUnits.Length_JumboJet, 76),
+
+ new UnitData(ViewMode.Power, UnitConverterUnits.Power_BritishThermalUnitPerMinute, 17.58426666666667),
+ new UnitData(ViewMode.Power, UnitConverterUnits.Power_FootPoundPerMinute, 0.0225969658055233),
+ new UnitData(ViewMode.Power, UnitConverterUnits.Power_Watt, 1),
+ new UnitData(ViewMode.Power, UnitConverterUnits.Power_Kilowatt, 1000),
+ new UnitData(ViewMode.Power, UnitConverterUnits.Power_Horsepower, 745.69987158227022),
+ new UnitData(ViewMode.Power, UnitConverterUnits.Power_LightBulb, 60),
+ new UnitData(ViewMode.Power, UnitConverterUnits.Power_Horse, 745.7),
+ new UnitData(ViewMode.Power, UnitConverterUnits.Power_TrainEngine, 2982799.486329081),
+
+ new UnitData(ViewMode.Time, UnitConverterUnits.Time_Day, 86400),
+ new UnitData(ViewMode.Time, UnitConverterUnits.Time_Second, 1),
+ new UnitData(ViewMode.Time, UnitConverterUnits.Time_Week, 604800),
+ new UnitData(ViewMode.Time, UnitConverterUnits.Time_Year, 31557600),
+ new UnitData(ViewMode.Time, UnitConverterUnits.Time_Millisecond, 0.001),
+ new UnitData(ViewMode.Time, UnitConverterUnits.Time_Microsecond, 0.000001),
+ new UnitData(ViewMode.Time, UnitConverterUnits.Time_Minute, 60),
+ new UnitData(ViewMode.Time, UnitConverterUnits.Time_Hour, 3600),
+
+ new UnitData(ViewMode.Volume, UnitConverterUnits.Volume_CupUS, 236.588237),
+ new UnitData(ViewMode.Volume, UnitConverterUnits.Volume_PintUS, 473.176473),
+ new UnitData(ViewMode.Volume, UnitConverterUnits.Volume_PintUK, 568.26125),
+ new UnitData(ViewMode.Volume, UnitConverterUnits.Volume_QuartUS, 946.352946),
+ new UnitData(ViewMode.Volume, UnitConverterUnits.Volume_QuartUK, 1136.5225),
+ new UnitData(ViewMode.Volume, UnitConverterUnits.Volume_GallonUS, 3785.411784),
+ new UnitData(ViewMode.Volume, UnitConverterUnits.Volume_GallonUK, 4546.09),
+ new UnitData(ViewMode.Volume, UnitConverterUnits.Volume_Liter, 1000),
+ new UnitData(ViewMode.Volume, UnitConverterUnits.Volume_TeaspoonUS, 4.92892159375),
+ new UnitData(ViewMode.Volume, UnitConverterUnits.Volume_TablespoonUS, 14.78676478125),
+ new UnitData(ViewMode.Volume, UnitConverterUnits.Volume_CubicCentimeter, 1),
+ new UnitData(ViewMode.Volume, UnitConverterUnits.Volume_CubicYard, 764554.857984),
+ new UnitData(ViewMode.Volume, UnitConverterUnits.Volume_CubicMeter, 1000000),
+ new UnitData(ViewMode.Volume, UnitConverterUnits.Volume_Milliliter, 1),
+ new UnitData(ViewMode.Volume, UnitConverterUnits.Volume_CubicInch, 16.387064),
+ new UnitData(ViewMode.Volume, UnitConverterUnits.Volume_CubicFoot, 28316.846592),
+ new UnitData(ViewMode.Volume, UnitConverterUnits.Volume_FluidOunceUS, 29.5735295625),
+ new UnitData(ViewMode.Volume, UnitConverterUnits.Volume_FluidOunceUK, 28.4130625),
+ new UnitData(ViewMode.Volume, UnitConverterUnits.Volume_TeaspoonUK, 5.91938802083333333333),
+ new UnitData(ViewMode.Volume, UnitConverterUnits.Volume_TablespoonUK, 17.7581640625),
+ new UnitData(ViewMode.Volume, UnitConverterUnits.Volume_CoffeeCup, 236.5882),
+ new UnitData(ViewMode.Volume, UnitConverterUnits.Volume_Bathtub, 378541.2),
+ new UnitData(ViewMode.Volume, UnitConverterUnits.Volume_SwimmingPool, 3750000000),
+
+ new UnitData(ViewMode.Weight, UnitConverterUnits.Weight_Kilogram, 1),
+ new UnitData(ViewMode.Weight, UnitConverterUnits.Weight_Hectogram, 0.1),
+ new UnitData(ViewMode.Weight, UnitConverterUnits.Weight_Decagram, 0.01),
+ new UnitData(ViewMode.Weight, UnitConverterUnits.Weight_Gram, 0.001),
+ new UnitData(ViewMode.Weight, UnitConverterUnits.Weight_Pound, 0.45359237),
+ new UnitData(ViewMode.Weight, UnitConverterUnits.Weight_Ounce, 0.028349523125),
+ new UnitData(ViewMode.Weight, UnitConverterUnits.Weight_Milligram, 0.000001),
+ new UnitData(ViewMode.Weight, UnitConverterUnits.Weight_Centigram, 0.00001),
+ new UnitData(ViewMode.Weight, UnitConverterUnits.Weight_Decigram, 0.0001),
+ new UnitData(ViewMode.Weight, UnitConverterUnits.Weight_LongTon, 1016.0469088),
+ new UnitData(ViewMode.Weight, UnitConverterUnits.Weight_Tonne, 1000),
+ new UnitData(ViewMode.Weight, UnitConverterUnits.Weight_Stone, 6.35029318),
+ new UnitData(ViewMode.Weight, UnitConverterUnits.Weight_Carat, 0.0002),
+ new UnitData(ViewMode.Weight, UnitConverterUnits.Weight_ShortTon, 907.18474),
+ new UnitData(ViewMode.Weight, UnitConverterUnits.Weight_Snowflake, 0.000002),
+ new UnitData(ViewMode.Weight, UnitConverterUnits.Weight_SoccerBall, 0.4325),
+ new UnitData(ViewMode.Weight, UnitConverterUnits.Weight_Elephant, 4000),
+ new UnitData(ViewMode.Weight, UnitConverterUnits.Weight_Whale, 90000),
+
+ new UnitData(ViewMode.Speed, UnitConverterUnits.Speed_CentimetersPerSecond, 1),
+ new UnitData(ViewMode.Speed, UnitConverterUnits.Speed_FeetPerSecond, 30.48),
+ new UnitData(ViewMode.Speed, UnitConverterUnits.Speed_KilometersPerHour, 27.777777777777777777778),
+ new UnitData(ViewMode.Speed, UnitConverterUnits.Speed_Knot, 51.44),
+ new UnitData(ViewMode.Speed, UnitConverterUnits.Speed_Mach, 34030),
+ new UnitData(ViewMode.Speed, UnitConverterUnits.Speed_MetersPerSecond, 100),
+ new UnitData(ViewMode.Speed, UnitConverterUnits.Speed_MilesPerHour, 44.7),
+ new UnitData(ViewMode.Speed, UnitConverterUnits.Speed_Turtle, 8.94),
+ new UnitData(ViewMode.Speed, UnitConverterUnits.Speed_Horse, 2011.5),
+ new UnitData(ViewMode.Speed, UnitConverterUnits.Speed_Jet, 24585),
+
+ new UnitData(ViewMode.Angle, UnitConverterUnits.Angle_Degree, 1),
+ new UnitData(ViewMode.Angle, UnitConverterUnits.Angle_Radian, 57.29577951308233),
+ new UnitData(ViewMode.Angle, UnitConverterUnits.Angle_Gradian, 0.9),
+
+ new UnitData(ViewMode.Pressure, UnitConverterUnits.Pressure_Atmosphere, 1),
+ new UnitData(ViewMode.Pressure, UnitConverterUnits.Pressure_Bar, 0.9869232667160128),
+ new UnitData(ViewMode.Pressure, UnitConverterUnits.Pressure_KiloPascal, 0.0098692326671601),
+ new UnitData(ViewMode.Pressure, UnitConverterUnits.Pressure_MillimeterOfMercury, 0.0013155687145324),
+ new UnitData(ViewMode.Pressure, UnitConverterUnits.Pressure_Pascal, 9.869232667160128e-6),
+ new UnitData(ViewMode.Pressure, UnitConverterUnits.Pressure_PSI, 0.068045961016531)
+ };
+
+ // Populate the hash map and return;
+ foreach (UnitData unitdata in unitDataList)
+ {
+ if (!categoryToUnitConversionMap.TryGetValue(unitdata.categoryId, out var conversionData))
+ {
+ conversionData = new Dictionary {{unitdata.unitId, unitdata.factor}};
+ categoryToUnitConversionMap.Add(unitdata.categoryId, conversionData);
+ }
+ else
+ {
+ conversionData.Add(unitdata.unitId, unitdata.factor);
+ }
+ }
+ }
+
+ string GetLocalizedStringName(String stringId)
+ {
+ return AppResourceProvider.GetInstance().GetResourceString(stringId);
+ }
+
+ void GetExplicitConversionData(Dictionary> unitToUnitConversionList)
+ {
+ /* categoryId, ParentUnitId, UnitId, ratio, offset, offsetfirst*/
+ ExplicitUnitConversionData[] conversionDataList = new[]
+ {
+ new ExplicitUnitConversionData(ViewMode.Temperature, UnitConverterUnits.Temperature_DegreesCelsius, UnitConverterUnits.Temperature_DegreesCelsius, 1, 0),
+ new ExplicitUnitConversionData(ViewMode.Temperature, UnitConverterUnits.Temperature_DegreesCelsius, UnitConverterUnits.Temperature_DegreesFahrenheit, 1.8, 32),
+ new ExplicitUnitConversionData(ViewMode.Temperature, UnitConverterUnits.Temperature_DegreesCelsius, UnitConverterUnits.Temperature_Kelvin, 1, 273.15),
+ new ExplicitUnitConversionData(ViewMode.Temperature,
+ UnitConverterUnits.Temperature_DegreesFahrenheit,
+ UnitConverterUnits.Temperature_DegreesCelsius,
+ 0.55555555555555555555555555555556,
+ -32,
+ CONVERT_WITH_OFFSET_FIRST),
+ new ExplicitUnitConversionData(ViewMode.Temperature, UnitConverterUnits.Temperature_DegreesFahrenheit, UnitConverterUnits.Temperature_DegreesFahrenheit, 1, 0),
+ new ExplicitUnitConversionData(ViewMode.Temperature,
+ UnitConverterUnits.Temperature_DegreesFahrenheit,
+ UnitConverterUnits.Temperature_Kelvin,
+ 0.55555555555555555555555555555556,
+ 459.67,
+ CONVERT_WITH_OFFSET_FIRST),
+ new ExplicitUnitConversionData(ViewMode.Temperature,
+ UnitConverterUnits.Temperature_Kelvin,
+ UnitConverterUnits.Temperature_DegreesCelsius,
+ 1,
+ -273.15,
+ CONVERT_WITH_OFFSET_FIRST),
+ new ExplicitUnitConversionData(ViewMode.Temperature, UnitConverterUnits.Temperature_Kelvin, UnitConverterUnits.Temperature_DegreesFahrenheit, 1.8, -459.67),
+ new ExplicitUnitConversionData(ViewMode.Temperature, UnitConverterUnits.Temperature_Kelvin, UnitConverterUnits.Temperature_Kelvin, 1, 0)
+ };
+
+ // Populate the hash map and return;
+ foreach (ExplicitUnitConversionData data in conversionDataList)
+ {
+ if (!unitToUnitConversionList.TryGetValue(data.parentUnitId, out var conversionData))
+ {
+ conversionData = new Dictionary {{data.unitId, (UCM.ConversionData)(data)}};
+ unitToUnitConversionList.Add(data.parentUnitId, conversionData);
+ }
+ else
+ {
+ conversionData.Add(data.unitId, (UCM.ConversionData)(data));
+ }
+ }
+ }
+ }
+}
diff --git a/src/Calculator.Shared/ViewModels/ApplicationViewModel.cs b/src/Calculator.Shared/ViewModels/ApplicationViewModel.cs
index b2b609d4..ed6d78a8 100644
--- a/src/Calculator.Shared/ViewModels/ApplicationViewModel.cs
+++ b/src/Calculator.Shared/ViewModels/ApplicationViewModel.cs
@@ -21,6 +21,7 @@ using System.Windows.Input;
using System;
using System.Diagnostics;
using System.Collections.ObjectModel;
+using CalculatorApp.DataLoaders;
namespace CalculatorApp.ViewModel
{
@@ -38,10 +39,8 @@ namespace CalculatorApp.ViewModel
private DateCalculatorViewModel m_DateCalcViewModel;
public DateCalculatorViewModel DateCalcViewModel { get => m_DateCalcViewModel; set { m_DateCalcViewModel = value; RaisePropertyChanged("DateCalcViewModel"); } }
-
- // UNO TODO
- //private UnitConverterViewModel m_ConverterViewModel;
- //public UnitConverterViewModel ConverterViewModel { get => m_ConverterViewModel; set { m_ConverterViewModel = value; RaisePropertyChanged("ConverterViewModel"); } }
+ private UnitConverterViewModel m_ConverterViewModel;
+ public UnitConverterViewModel ConverterViewModel { get => m_ConverterViewModel; set { m_ConverterViewModel = value; RaisePropertyChanged("ConverterViewModel"); } }
private CalculatorApp.Common.ViewMode m_PreviousMode;
@@ -62,7 +61,7 @@ namespace CalculatorApp.ViewModel
ViewMode m_mode = ViewMode.None;
- ObservableCollection m_categories;
+ CalculatorObservableCollection m_categories;
public ApplicationViewModel()
{
@@ -87,7 +86,7 @@ namespace CalculatorApp.ViewModel
}
}
- public ObservableCollection Categories
+ public CalculatorObservableCollection Categories
{
get => m_categories;
set
@@ -166,17 +165,16 @@ namespace CalculatorApp.ViewModel
}
else if (NavCategory.IsConverterViewMode(m_mode))
{
- // UNO TODO
- //// TraceLogger.GetInstance().LogConverterModeViewed(m_mode, ApplicationView.GetApplicationViewIdForWindow(CoreWindow.GetForCurrentThread()));
- //if (!m_ConverterViewModel)
- //{
- // var dataLoader = new UnitConverterDataLoader(new GeographicRegion());
- // var currencyDataLoader = new CurrencyDataLoader(new CurrencyHttpClient());
- // m_ConverterViewModel = new UnitConverterViewModel(new UnitConversionManager.UnitConverter(dataLoader, currencyDataLoader));
- //}
+ // TraceLogger.GetInstance().LogConverterModeViewed(m_mode, ApplicationView.GetApplicationViewIdForWindow(CoreWindow.GetForCurrentThread()));
+ if (m_ConverterViewModel == null)
+ {
+ var dataLoader = new UnitConverterDataLoader(new GeographicRegion());
+ var currencyDataLoader = new CurrencyDataLoader(new CurrencyHttpClient());
+ m_ConverterViewModel = new UnitConverterViewModel(new UnitConversionManager.UnitConverter(dataLoader, currencyDataLoader));
+ }
- //m_ConverterViewModel.Mode = m_mode;
- }
+ m_ConverterViewModel.Mode = m_mode;
+ }
var resProvider = AppResourceProvider.GetInstance();
CategoryName = resProvider.GetResourceString(NavCategory.GetNameResourceKey(m_mode));
@@ -193,13 +191,11 @@ namespace CalculatorApp.ViewModel
void OnCopyCommand(object parameter)
{
- // UNO TODO
- //if (NavCategory.IsConverterViewMode(m_mode))
- //{
- // ConverterViewModel.OnCopyCommand(parameter);
- //}
- //else
- if (NavCategory.IsDateCalculatorViewMode(m_mode))
+ if (NavCategory.IsConverterViewMode(m_mode))
+ {
+ ConverterViewModel.OnCopyCommand(parameter);
+ }
+ else if (NavCategory.IsDateCalculatorViewMode(m_mode))
{
DateCalcViewModel.OnCopyCommand(parameter);
}
@@ -211,13 +207,11 @@ namespace CalculatorApp.ViewModel
void OnPasteCommand(object parameter)
{
- // UNO TODO
- //if (NavCategory.IsConverterViewMode(m_mode))
- //{
- // ConverterViewModel.OnPasteCommand(parameter);
- //}
- //else
- if (NavCategory.IsCalculatorViewMode(m_mode))
+ if (NavCategory.IsConverterViewMode(m_mode))
+ {
+ ConverterViewModel.OnPasteCommand(parameter);
+ }
+ else if (NavCategory.IsCalculatorViewMode(m_mode))
{
CalculatorViewModel.OnPasteCommand(parameter);
}
diff --git a/src/Calculator.Shared/ViewModels/HistoryViewModel.cs b/src/Calculator.Shared/ViewModels/HistoryViewModel.cs
index ee9f169a..08694ceb 100644
--- a/src/Calculator.Shared/ViewModels/HistoryViewModel.cs
+++ b/src/Calculator.Shared/ViewModels/HistoryViewModel.cs
@@ -30,8 +30,8 @@ namespace CalculatorApp
public int ItemSize { get => m_ItemSize; set { m_ItemSize = value; RaisePropertyChanged("ItemSize"); } }
- private ObservableCollection m_Items;
- public ObservableCollection Items { get => m_Items; set { m_Items = value; RaisePropertyChanged("Items"); } }
+ private CalculatorObservableCollection m_Items;
+ public CalculatorObservableCollection Items { get => m_Items; set { m_Items = value; RaisePropertyChanged("Items"); } }
private bool m_AreHistoryShortcutsEnabled;
@@ -70,7 +70,7 @@ namespace CalculatorApp
AreHistoryShortcutsEnabled = true;
- Items = new ObservableCollection();
+ Items = new CalculatorObservableCollection();
ItemSize = 0;
}
@@ -97,7 +97,7 @@ namespace CalculatorApp
}
var historyListModel = m_calculatorManager.GetHistoryItems(m_currentMode);
- var historyListVM = new ObservableCollection();
+ var historyListVM = new CalculatorObservableCollection();
var localizer = LocalizationSettings.GetInstance();
if (historyListModel.Count > 0)
{
diff --git a/src/Calculator.Shared/ViewModels/StandardCalculatorViewModel.cs b/src/Calculator.Shared/ViewModels/StandardCalculatorViewModel.cs
index 4b9d9c28..786850a0 100644
--- a/src/Calculator.Shared/ViewModels/StandardCalculatorViewModel.cs
+++ b/src/Calculator.Shared/ViewModels/StandardCalculatorViewModel.cs
@@ -23,6 +23,7 @@ using System.Text;
using System.Linq;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
+using Calculator;
namespace CalculatorApp.ViewModel
{
@@ -86,8 +87,8 @@ namespace CalculatorApp.ViewModel
public string DisplayStringExpression { get => m_DisplayStringExpression; set { m_DisplayStringExpression = value; RaisePropertyChanged("DisplayStringExpression"); } }
- private ObservableCollection m_ExpressionTokens;
- public ObservableCollection ExpressionTokens { get => m_ExpressionTokens; private set { m_ExpressionTokens = value; RaisePropertyChanged("ExpressionTokens"); } }
+ private CalculatorObservableCollection m_ExpressionTokens;
+ public CalculatorObservableCollection ExpressionTokens { get => m_ExpressionTokens; private set { m_ExpressionTokens = value; RaisePropertyChanged("ExpressionTokens"); } }
private string m_DecimalDisplayValue;
@@ -518,7 +519,7 @@ namespace CalculatorApp.ViewModel
m_BinaryDisplayValue = "0";
m_OctalDisplayValue = "0";
m_standardCalculatorManager = new CalculatorManager(ref m_calculatorDisplay, ref m_resourceProvider);
- m_ExpressionTokens = new ObservableCollection();
+ m_ExpressionTokens = new CalculatorObservableCollection();
m_MemorizedNumbers = new List();
m_IsMemoryEmpty = true;
m_IsFToEChecked = false;
@@ -618,7 +619,7 @@ namespace CalculatorApp.ViewModel
if (Utils.IsLastCharacterTarget(displayValue, m_decimalSeparator))
{
// remove the decimal separator, to avoid a long pause between words
- localizedValue = LocalizeDisplayValue(displayValue.Substring(0, displayValue.Length - 1), isError);
+ localizedValue = LocalizeDisplayValue(displayValue.substr(0, displayValue.Length - 1), isError);
// Use a format which has a word in the decimal separator's place
// "The Display is 10 point"
diff --git a/src/Calculator.Shared/ViewModels/UnitConverterViewModel.cs b/src/Calculator.Shared/ViewModels/UnitConverterViewModel.cs
index 23fd72fc..2c7eb3da 100644
--- a/src/Calculator.Shared/ViewModels/UnitConverterViewModel.cs
+++ b/src/Calculator.Shared/ViewModels/UnitConverterViewModel.cs
@@ -1,10 +1,1843 @@
-//using System;
-//using System.Collections.Generic;
-//using System.Text;
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
-//namespace WindowsCalculator.Shared.ViewModels
-//{
-// class UnitConverterViewModel
-// {
-// }
-//}
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Numerics;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Threading;
+using System.Windows.Input;
+using Windows.Foundation.Metadata;
+using Windows.Globalization.NumberFormatting;
+using Windows.Storage;
+using Windows.System.Threading;
+using Windows.UI.Core;
+using Windows.UI.Xaml;
+using CalculationManager;
+using Calculator;
+using CalculatorApp;
+using CalculatorApp.Common;
+using CalculatorApp.Common.Automation;
+using UnitConversionManager;
+using UCM = UnitConversionManager;
+using CategorySelectionInitializer = System.Tuple, UnitConversionManager.Unit, UnitConversionManager.Unit>;
+using UnitToUnitToConversionDataMap = System.Collections.Generic.Dictionary>;
+using CategoryToUnitVectorMap = System.Collections.Generic.Dictionary>;
+
+namespace CalculatorApp.ViewModel
+{
+
+
+ [Windows.UI.Xaml.Data.Bindable]
+ public sealed class Category : INotifyPropertyChanged
+ {
+ internal Category(UnitConversionManager.Category category)
+ {
+ m_original = category;
+
+ }
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ private void RaisePropertyChanged(
+ [System.Runtime.CompilerServices.CallerMemberName]
+ string p = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(p));
+
+ public string Name
+ {
+ get { return m_original.name; }
+ }
+
+ public Windows.UI.Xaml.Visibility NegateVisibility
+ {
+ get { return m_original.supportsNegative ? Windows.UI.Xaml.Visibility.Visible : Windows.UI.Xaml.Visibility.Collapsed; }
+ }
+
+ internal UnitConversionManager.Category GetModelCategory()
+ {
+ return m_original;
+ }
+
+ private UnitConversionManager.Category m_original;
+ };
+
+ [Windows.UI.Xaml.Data.Bindable]
+ public sealed class Unit : INotifyPropertyChanged
+ {
+ internal Unit(UnitConversionManager.Unit unit)
+ {
+ m_original = unit;
+ }
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ private void RaisePropertyChanged(
+ [System.Runtime.CompilerServices.CallerMemberName]
+ string p = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(p));
+
+ public string Name
+ {
+ get { return m_original.name; }
+ }
+
+ public string AccessibleName
+ {
+ get { return m_original.accessibleName; }
+ }
+
+ public string Abbreviation
+ {
+ get { return m_original.abbreviation; }
+ }
+
+ // This method is used to return the desired automation name for default unit in UnitConverter combo box.
+ public override string ToString()
+ {
+ return AccessibleName;
+ }
+
+ internal UnitConversionManager.Unit GetModelUnit()
+ {
+ return m_original;
+ }
+
+ private UnitConversionManager.Unit m_original;
+ }
+
+ [Windows.UI.Xaml.Data.Bindable]
+ public sealed class SupplementaryResult : INotifyPropertyChanged
+ {
+ internal SupplementaryResult(string value, Unit unit)
+ {
+ m_Value = value;
+ m_Unit = unit;
+
+ }
+
+ public bool IsWhimsical()
+ {
+ return m_Unit.GetModelUnit().isWhimsical;
+ }
+
+ public string GetLocalizedAutomationName()
+ {
+ // TODO UNO
+ return "TODO UNO";
+ }
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ private void RaisePropertyChanged(
+ [System.Runtime.CompilerServices.CallerMemberName]
+ string p = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(p));
+
+ private string m_Value;
+
+ public string Value
+ {
+ get => m_Value;
+ private set
+ {
+ m_Value = value;
+ RaisePropertyChanged("Value");
+ }
+ }
+
+
+ private CalculatorApp.ViewModel.Unit m_Unit;
+
+ public CalculatorApp.ViewModel.Unit Unit
+ {
+ get => m_Unit;
+ private set
+ {
+ m_Unit = value;
+ RaisePropertyChanged("Unit");
+ }
+ }
+ }
+
+ public interface IActivatable
+ {
+ bool IsActive { get; set; }
+ };
+
+ public sealed class Activatable : IActivatable
+ where TActivatable : IActivatable
+ {
+ TActivatable m_activatable;
+
+ public Activatable(TActivatable activatable)
+ {
+ m_activatable = activatable;
+ }
+
+ public bool IsActive
+ {
+ get { return m_activatable.IsActive; }
+ set { m_activatable.IsActive = value; }
+ }
+ };
+
+
+ [Windows.UI.Xaml.Data.Bindable]
+ public sealed partial class UnitConverterViewModel : INotifyPropertyChanged
+ {
+
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ private void RaisePropertyChanged(
+ [System.Runtime.CompilerServices.CallerMemberName]
+ string p = null)
+ {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(p));
+ OnPropertyChanged(p);
+ }
+
+ private CalculatorObservableCollection m_Categories;
+
+ public CalculatorObservableCollection Categories
+ {
+ get => m_Categories;
+ private set
+ {
+ m_Categories = value;
+ RaisePropertyChanged("Categories");
+ }
+ }
+
+
+ private Category m_CurrentCategory;
+
+ public Category CurrentCategory
+ {
+ get => m_CurrentCategory;
+ set
+ {
+ m_CurrentCategory = value;
+ RaisePropertyChanged("CurrentCategory");
+ }
+ }
+
+
+ private CalculatorApp.Common.ViewMode m_Mode;
+
+ public CalculatorApp.Common.ViewMode Mode
+ {
+ get => m_Mode;
+ set
+ {
+ m_Mode = value;
+ RaisePropertyChanged("Mode");
+ }
+ }
+
+
+ private CalculatorObservableCollection m_Units;
+
+ public CalculatorObservableCollection Units
+ {
+ get => m_Units;
+ private set
+ {
+ m_Units = value;
+ RaisePropertyChanged("Units");
+ }
+ }
+
+
+ private string m_CurrencySymbol1;
+
+ public string CurrencySymbol1
+ {
+ get => m_CurrencySymbol1;
+ set
+ {
+ m_CurrencySymbol1 = value;
+ RaisePropertyChanged("CurrencySymbol1");
+ }
+ }
+
+
+ private Unit m_Unit1;
+
+ public Unit Unit1
+ {
+ get => m_Unit1;
+ set
+ {
+ m_Unit1 = value;
+ RaisePropertyChanged("Unit1");
+ }
+ }
+
+
+ private string m_Value1;
+
+ public string Value1
+ {
+ get => m_Value1;
+ set
+ {
+ m_Value1 = value;
+ RaisePropertyChanged("Value1");
+ }
+ }
+
+
+ private string m_CurrencySymbol2;
+
+ public string CurrencySymbol2
+ {
+ get => m_CurrencySymbol2;
+ set
+ {
+ m_CurrencySymbol2 = value;
+ RaisePropertyChanged("CurrencySymbol2");
+ }
+ }
+
+
+ private Unit m_Unit2;
+
+ public Unit Unit2
+ {
+ get => m_Unit2;
+ set
+ {
+ m_Unit2 = value;
+ RaisePropertyChanged("Unit2");
+ }
+ }
+
+
+ private string m_Value2;
+
+ public string Value2
+ {
+ get => m_Value2;
+ set
+ {
+ m_Value2 = value;
+ RaisePropertyChanged("Value2");
+ }
+ }
+
+
+ private CalculatorObservableCollection m_SupplementaryResults;
+
+ public CalculatorObservableCollection SupplementaryResults
+ {
+ get => m_SupplementaryResults;
+ set
+ {
+ m_SupplementaryResults = value;
+ RaisePropertyChanged("SupplementaryResults");
+ }
+ }
+
+ internal static string SupplementaryResultsPropertyName => "SupplementaryResults";
+
+ private bool m_Value1Active;
+
+ public bool Value1Active
+ {
+ get => m_Value1Active;
+ set
+ {
+ m_Value1Active = value;
+ RaisePropertyChanged("Value1Active");
+ }
+ }
+
+
+ private bool m_Value2Active;
+
+ public bool Value2Active
+ {
+ get => m_Value2Active;
+ set
+ {
+ m_Value2Active = value;
+ RaisePropertyChanged("Value2Active");
+ }
+ }
+
+
+ private string m_Value1AutomationName;
+
+ public string Value1AutomationName
+ {
+ get => m_Value1AutomationName;
+ set
+ {
+ m_Value1AutomationName = value;
+ RaisePropertyChanged("Value1AutomationName");
+ }
+ }
+
+
+ private string m_Value2AutomationName;
+
+ public string Value2AutomationName
+ {
+ get => m_Value2AutomationName;
+ set
+ {
+ m_Value2AutomationName = value;
+ RaisePropertyChanged("Value2AutomationName");
+ }
+ }
+
+
+ private string m_Unit1AutomationName;
+
+ public string Unit1AutomationName
+ {
+ get => m_Unit1AutomationName;
+ set
+ {
+ m_Unit1AutomationName = value;
+ RaisePropertyChanged("Unit1AutomationName");
+ }
+ }
+
+
+ private string m_Unit2AutomationName;
+
+ public string Unit2AutomationName
+ {
+ get => m_Unit2AutomationName;
+ set
+ {
+ m_Unit2AutomationName = value;
+ RaisePropertyChanged("Unit2AutomationName");
+ }
+ }
+
+
+ private CalculatorApp.Common.Automation.NarratorAnnouncement m_Announcement;
+
+ public CalculatorApp.Common.Automation.NarratorAnnouncement Announcement
+ {
+ get => m_Announcement;
+ set
+ {
+ m_Announcement = value;
+ RaisePropertyChanged("Announcement");
+ }
+ }
+
+
+ private bool m_IsDecimalEnabled;
+
+ public bool IsDecimalEnabled
+ {
+ get => m_IsDecimalEnabled;
+ set
+ {
+ m_IsDecimalEnabled = value;
+ RaisePropertyChanged("IsDecimalEnabled");
+ }
+ }
+
+
+ private bool m_IsDropDownOpen;
+
+ public bool IsDropDownOpen
+ {
+ get => m_IsDropDownOpen;
+ set
+ {
+ m_IsDropDownOpen = value;
+ RaisePropertyChanged("IsDropDownOpen");
+ }
+ }
+
+
+ private bool m_IsDropDownEnabled;
+
+ public bool IsDropDownEnabled
+ {
+ get => m_IsDropDownEnabled;
+ set
+ {
+ m_IsDropDownEnabled = value;
+ RaisePropertyChanged("IsDropDownEnabled");
+ }
+ }
+
+
+ internal const string IsCurrencyLoadingVisiblePropertyName = "IsCurrencyLoadingVisible";
+ private bool m_IsCurrencyLoadingVisible;
+
+ public bool IsCurrencyLoadingVisible
+ {
+ get => m_IsCurrencyLoadingVisible;
+ set
+ {
+ m_IsCurrencyLoadingVisible = value;
+ RaisePropertyChanged("IsCurrencyLoadingVisible");
+ }
+ }
+
+
+ private bool m_IsCurrencyCurrentCategory;
+
+ public bool IsCurrencyCurrentCategory
+ {
+ get => m_IsCurrencyCurrentCategory;
+ set
+ {
+ m_IsCurrencyCurrentCategory = value;
+ RaisePropertyChanged("IsCurrencyCurrentCategory");
+ }
+ }
+
+
+ private string m_CurrencyRatioEquality;
+
+ public string CurrencyRatioEquality
+ {
+ get => m_CurrencyRatioEquality;
+ set
+ {
+ m_CurrencyRatioEquality = value;
+ RaisePropertyChanged("CurrencyRatioEquality");
+ }
+ }
+
+
+ private string m_CurrencyRatioEqualityAutomationName = "TODO UNO";
+
+ public string CurrencyRatioEqualityAutomationName
+ {
+ get => m_CurrencyRatioEqualityAutomationName;
+ set
+ {
+ m_CurrencyRatioEqualityAutomationName = value;
+ RaisePropertyChanged("CurrencyRatioEqualityAutomationName");
+ }
+ }
+
+
+ private string m_CurrencyTimestamp;
+
+ public string CurrencyTimestamp
+ {
+ get => m_CurrencyTimestamp;
+ set
+ {
+ m_CurrencyTimestamp = value;
+ RaisePropertyChanged("CurrencyTimestamp");
+ }
+ }
+
+
+ internal const string NetworkBehaviorPropertyName = "NetworkBehavior";
+ private CalculatorApp.NetworkAccessBehavior m_NetworkBehavior;
+
+ public CalculatorApp.NetworkAccessBehavior NetworkBehavior
+ {
+ get => m_NetworkBehavior;
+ set
+ {
+ m_NetworkBehavior = value;
+ RaisePropertyChanged("NetworkBehavior");
+ }
+ }
+
+
+ internal const string CurrencyDataLoadFailedPropertyName = "CurrencyDataLoadFailed";
+ private bool m_CurrencyDataLoadFailed;
+
+ public bool CurrencyDataLoadFailed
+ {
+ get => m_CurrencyDataLoadFailed;
+ set
+ {
+ m_CurrencyDataLoadFailed = value;
+ RaisePropertyChanged("CurrencyDataLoadFailed");
+ }
+ }
+
+
+ internal const string CurrencyDataIsWeekOldPropertyName = "CurrencyDataIsWeekOld";
+ private bool m_CurrencyDataIsWeekOld;
+
+ public bool CurrencyDataIsWeekOld
+ {
+ get => m_CurrencyDataIsWeekOld;
+ set
+ {
+ m_CurrencyDataIsWeekOld = value;
+ RaisePropertyChanged("CurrencyDataIsWeekOld");
+ }
+ }
+
+
+
+ public Windows.UI.Xaml.Visibility SupplementaryVisibility
+ {
+ get { return SupplementaryResults.Count > 0 ? Windows.UI.Xaml.Visibility.Visible : Windows.UI.Xaml.Visibility.Collapsed; }
+ }
+
+ public Windows.UI.Xaml.Visibility CurrencySymbolVisibility
+ {
+ get
+ {
+ return (CurrencySymbol1.IsEmpty() || CurrencySymbol2.IsEmpty())
+ ? Windows.UI.Xaml.Visibility.Collapsed
+ : Windows.UI.Xaml.Visibility.Visible;
+ }
+ }
+
+ public ICommand CategoryChanged { get; }
+ public ICommand UnitChanged { get; }
+ public ICommand SwitchActive { get; }
+ public ICommand ButtonPressed { get; }
+ public ICommand CopyCommand { get; }
+ public ICommand PasteCommand { get; }
+
+
+ string GetValueFromUnlocalized()
+ {
+ return m_valueFromUnlocalized;
+ }
+
+ string GetValueToUnlocalized()
+ {
+ return m_valueToUnlocalized;
+ }
+
+ UnitConversionManager.IUnitConverter m_model;
+ char m_decimalSeparator;
+
+ enum ConversionParameter
+ {
+ Source,
+ Target
+ }
+
+ ConversionParameter m_value1cp;
+
+ private string ValueFrom
+ {
+ get { return m_value1cp == ConversionParameter.Source ? Value1 : Value2; }
+ set
+ {
+ if (m_value1cp == ConversionParameter.Source) Value1 = value;
+ else Value2 = value;
+ }
+ }
+
+ private Unit UnitFrom
+ {
+ get { return m_value1cp == ConversionParameter.Source ? Unit1 : Unit2; }
+ set
+ {
+ if (m_value1cp == ConversionParameter.Source) Unit1 = value;
+ else Unit2 = value;
+ }
+ }
+
+ private string ValueTo
+ {
+ get { return m_value1cp == ConversionParameter.Target ? Value1 : Value2; }
+ set
+ {
+ if (m_value1cp == ConversionParameter.Target) Value1 = value;
+ else Value2 = value;
+ }
+ }
+
+ Unit UnitTo
+ {
+ get { return m_value1cp == ConversionParameter.Target ? Unit1 : Unit2; }
+ set
+ {
+ if (m_value1cp == ConversionParameter.Target) Unit1 = value;
+ else Unit2 = value;
+ }
+ }
+
+ void SwitchConversionParameters()
+ {
+ m_value1cp = m_value1cp == ConversionParameter.Source ? ConversionParameter.Target : ConversionParameter.Source;
+ }
+
+ bool m_isInputBlocked;
+ Windows.System.Threading.ThreadPoolTimer m_supplementaryResultsTimer;
+ bool m_resettingTimer;
+
+ CalculatorList> m_cachedSuggestedValues;
+
+ //Mutex m_cacheMutex;
+ object m_cacheMutex = new object();
+ Windows.Globalization.NumberFormatting.DecimalFormatter m_decimalFormatter;
+ Windows.Globalization.NumberFormatting.CurrencyFormatter m_currencyFormatter;
+ int m_currencyMaxFractionDigits;
+ string m_valueFromUnlocalized;
+ string m_valueToUnlocalized;
+
+ bool m_relocalizeStringOnSwitch;
+
+ // For Saving the User Preferences only if the Unit converter ViewModel is initialised for the first time
+ bool m_IsFirstTime;
+
+ string m_localizedValueFromFormat;
+ string m_localizedValueFromDecimalFormat;
+ string m_localizedValueToFormat;
+ string m_localizedConversionResultFormat;
+ string m_localizedInputUnitName;
+ string m_localizedOutputUnitName;
+
+ bool m_isValue1Updating;
+ bool m_isValue2Updating;
+ string m_lastAnnouncedFrom;
+ string m_lastAnnouncedTo;
+ string m_lastAnnouncedConversionResult;
+
+ bool m_isCurrencyDataLoaded;
+
+ CalculatorApp.Common.ConversionResultTaskHelper m_conversionResultTaskHelper;
+ }
+
+ internal class UnitConverterVMCallback : UnitConversionManager.IUnitConverterVMCallback
+ {
+ public UnitConverterVMCallback(UnitConverterViewModel viewModel)
+ {
+ m_viewModel = viewModel;
+ }
+
+ public void DisplayCallback(string from, string to)
+ {
+ m_viewModel.UpdateDisplay(from, to);
+ }
+
+ public void SuggestedValueCallback(CalculatorList> suggestedValues)
+ {
+ m_viewModel.UpdateSupplementaryResults(suggestedValues);
+ }
+
+ public void MaxDigitsReached()
+ {
+ m_viewModel.OnMaxDigitsReached();
+ }
+
+ UnitConverterViewModel m_viewModel;
+ }
+
+ internal class ViewModelCurrencyCallback : UnitConversionManager.IViewModelCurrencyCallback
+ {
+ public ViewModelCurrencyCallback(UnitConverterViewModel viewModel)
+ {
+ m_viewModel = viewModel;
+ }
+
+ public void CurrencyDataLoadFinished(bool didLoad)
+ {
+ m_viewModel.OnCurrencyDataLoadFinished(didLoad);
+ }
+
+ public void CurrencySymbolsCallback(string symbol1, string symbol2)
+ {
+ string sym1 = symbol1;
+ string sym2 = symbol2;
+
+ bool value1Active = m_viewModel.Value1Active;
+ m_viewModel.CurrencySymbol1 = value1Active ? sym1 : sym2;
+ m_viewModel.CurrencySymbol2 = value1Active ? sym2 : sym1;
+ }
+
+ public void CurrencyRatiosCallback(string ratioEquality, string accRatioEquality)
+ {
+ m_viewModel.CurrencyRatioEquality = ratioEquality;
+ m_viewModel.CurrencyRatioEqualityAutomationName = accRatioEquality;
+ }
+
+ public void CurrencyTimestampCallback(string timestamp, bool isWeekOld)
+ {
+ m_viewModel.OnCurrencyTimestampUpdated(timestamp, isWeekOld);
+ }
+
+ public void NetworkBehaviorChanged(int newBehavior)
+ {
+ m_viewModel.OnNetworkBehaviorChanged((CalculatorApp.NetworkAccessBehavior)(newBehavior));
+ }
+
+ UnitConverterViewModel m_viewModel;
+ };
+
+ public sealed partial class UnitConverterViewModel
+ {
+ const int EXPECTEDVIEWMODELDATATOKENS = 8;
+
+ // interval is in 100 nanosecond units
+ const uint TIMER_INTERVAL_IN_MS = 10000;
+
+ //#if UNIT_TESTS
+ //#define TIMER_CALLBACK_CONTEXT // CallbackContext.Any ==> UNO cf. _supplementaryResultsTimerContext
+ //#else
+ //#define TIMER_CALLBACK_CONTEXT // CallbackContext.Same ==> UNO cf. _supplementaryResultsTimerContext
+ //#endif
+
+ private static readonly TimeSpan SUPPLEMENTARY_VALUES_INTERVAL = TimeSpan.FromTicks(10 * TIMER_INTERVAL_IN_MS);
+
+ static Unit EMPTY_UNIT = new Unit(UCM.Unit.EMPTY_UNIT);
+
+ private static readonly Func> UNIT_LIST = csi => csi.Item1;
+ private static readonly Func SELECTED_SOURCE_UNIT = csi => csi.Item2;
+ private static readonly Func SELECTED_TARGET_UNIT = csi => csi.Item3;
+
+
+ // x millisecond delay before we consider conversion to be final
+ uint CONVERSION_FINALIZED_DELAY_IN_MS = 1000;
+ static readonly Regex regexTrimSpacesStart = new Regex("^\\s+");
+ static readonly Regex regexTrimSpacesEnd = new Regex("\\s+$");
+
+ const string CurrentCategoryPropertyName = "CurrentCategory";
+ const string Unit1AutomationNamePropertyName = "Unit1AutomationName";
+ const string Unit2AutomationNamePropertyName = "Unit2AutomationName";
+ const string Unit1PropertyName = "Unit1";
+ const string Unit2PropertyName = "Unit2";
+ const string Value1PropertyName = "Value1";
+ const string Value2PropertyName = "Value2";
+ const string Value1ActivePropertyName = "Value1Active";
+ const string Value2ActivePropertyName = "Value2Active";
+ const string Value1AutomationNamePropertyName = "Value1AutomationName";
+ const string Value2AutomationNamePropertyName = "Value2AutomationName";
+ const string CurrencySymbol1PropertyName = "CurrencySymbol1";
+ const string CurrencySymbol2PropertyName = "CurrencySymbol2";
+ const string CurrencySymbolVisibilityPropertyName = "CurrencySymbolVisibility";
+ const string SupplementaryVisibilityPropertyName = "SupplementaryVisibility";
+
+ public static class UnitConverterResourceKeys
+ {
+ public const string ValueFromFormat = "Format_ValueFrom";
+ public const string ValueFromDecimalFormat = "Format_ValueFrom_Decimal";
+ public const string ValueToFormat = "Format_ValueTo";
+ public const string ConversionResultFormat = "Format_ConversionResult";
+ public const string InputUnit_Name = "InputUnit_Name";
+ public const string OutputUnit_Name = "OutputUnit_Name";
+ public const string MaxDigitsReachedFormat = "Format_MaxDigitsReached";
+ public const string UpdatingCurrencyRates = "UpdatingCurrencyRates";
+ public const string CurrencyRatesUpdated = "CurrencyRatesUpdated";
+ public const string CurrencyRatesUpdateFailed = "CurrencyRatesUpdateFailed";
+ }
+
+ public UnitConverterViewModel(IUnitConverter model)
+ {
+ CategoryChanged = new DelegateCommand(OnCategoryChanged);
+ UnitChanged = new DelegateCommand(OnUnitChanged);
+ SwitchActive = new DelegateCommand(OnSwitchActive);
+ ButtonPressed = new DelegateCommand(OnButtonPressed);
+ CopyCommand = new DelegateCommand(OnCopyCommand);
+ PasteCommand = new DelegateCommand(OnPasteCommand);
+
+ m_model = model;
+ m_resettingTimer = false;
+ m_value1cp = ConversionParameter.Source;
+ m_Value1Active = true;
+ m_Value2Active = false;
+ m_Value1 = "0";
+ m_Value2 = "0";
+ m_valueToUnlocalized = "0";
+ m_valueFromUnlocalized = "0";
+ m_relocalizeStringOnSwitch = false;
+ m_Categories = new CalculatorObservableCollection();
+ m_Units = new CalculatorObservableCollection();
+ m_SupplementaryResults = new CalculatorObservableCollection();
+ m_IsDropDownOpen = false;
+ m_IsDropDownEnabled = true;
+ m_IsCurrencyLoadingVisible = false;
+ m_isCurrencyDataLoaded = false;
+ m_lastAnnouncedFrom = "";
+ m_lastAnnouncedTo = "";
+ m_lastAnnouncedConversionResult = "";
+ m_isValue1Updating = false;
+ m_isValue2Updating = false;
+ m_Announcement = null;
+ m_Mode = ViewMode.None;
+ m_CurrencySymbol1 = "";
+ m_CurrencySymbol2 = "";
+ m_IsCurrencyCurrentCategory = false;
+ m_CurrencyRatioEquality = "";
+ m_CurrencyRatioEqualityAutomationName = "";
+ m_isInputBlocked = false;
+ m_CurrencyDataLoadFailed = false;
+
+ m_model.SetViewModelCallback(new UnitConverterVMCallback(this));
+ m_model.SetViewModelCurrencyCallback(new ViewModelCurrencyCallback(this));
+ m_decimalFormatter = LocalizationService.GetRegionalSettingsAwareDecimalFormatter();
+ m_decimalFormatter.FractionDigits = 0;
+ m_decimalFormatter.IsGrouped = true;
+ m_decimalSeparator = LocalizationSettings.GetInstance().GetDecimalSeparator();
+
+ m_currencyFormatter = LocalizationService.GetRegionalSettingsAwareCurrencyFormatter();
+ m_currencyFormatter.IsGrouped = true;
+ m_currencyFormatter.Mode = CurrencyFormatterMode.UseCurrencyCode;
+ m_currencyFormatter.ApplyRoundingForCurrency(RoundingAlgorithm.RoundHalfDown);
+ m_currencyMaxFractionDigits = m_currencyFormatter.FractionDigits;
+
+ var resourceLoader = AppResourceProvider.GetInstance();
+ m_localizedValueFromFormat = resourceLoader.GetResourceString(UnitConverterResourceKeys.ValueFromFormat);
+ m_localizedValueToFormat = resourceLoader.GetResourceString(UnitConverterResourceKeys.ValueToFormat);
+ m_localizedConversionResultFormat = resourceLoader.GetResourceString(UnitConverterResourceKeys.ConversionResultFormat);
+ m_localizedValueFromDecimalFormat = resourceLoader.GetResourceString(UnitConverterResourceKeys.ValueFromDecimalFormat);
+ m_localizedInputUnitName = resourceLoader.GetResourceString(UnitConverterResourceKeys.InputUnit_Name);
+ m_localizedOutputUnitName = resourceLoader.GetResourceString(UnitConverterResourceKeys.OutputUnit_Name);
+
+ Unit1AutomationName = m_localizedInputUnitName;
+ Unit2AutomationName = m_localizedOutputUnitName;
+ IsDecimalEnabled = true;
+
+ m_IsFirstTime = true;
+ m_model.Initialize();
+ PopulateData();
+ }
+
+ void ResetView()
+ {
+ m_model.SendCommand(UCM.Command.Reset);
+ m_IsFirstTime = true;
+ OnCategoryChanged(null);
+ }
+
+ void PopulateData()
+ {
+ InitializeView();
+ }
+
+ void OnCategoryChanged(object parameter)
+ {
+ m_model.SendCommand(UCM.Command.Clear);
+ ResetCategory();
+ }
+
+ void ResetCategory()
+ {
+ UCM.Category currentCategory = CurrentCategory.GetModelCategory();
+ IsCurrencyCurrentCategory = currentCategory.id == NavCategory.Serialize(ViewMode.Currency);
+
+ m_isInputBlocked = false;
+ SetSelectedUnits();
+
+ IsCurrencyLoadingVisible = m_IsCurrencyCurrentCategory && !m_isCurrencyDataLoaded;
+ IsDropDownEnabled = m_Units[0] != EMPTY_UNIT;
+
+ UnitChanged.Execute(null);
+ }
+
+ void SetSelectedUnits()
+ {
+ CategorySelectionInitializer categoryInitializer = m_model.SetCurrentCategory(CurrentCategory.GetModelCategory());
+ BuildUnitList(UNIT_LIST(categoryInitializer));
+
+ UnitFrom = FindUnitInList(SELECTED_SOURCE_UNIT(categoryInitializer));
+ UnitTo = FindUnitInList(SELECTED_TARGET_UNIT(categoryInitializer));
+ }
+
+ void BuildUnitList(CalculatorList modelUnitList)
+ {
+ m_Units.Clear();
+ foreach (UCM.Unit modelUnit in modelUnitList)
+ {
+ if (!modelUnit.isWhimsical)
+ {
+ m_Units.Append(new Unit(modelUnit));
+ }
+ }
+
+ if (m_Units.Count == 0)
+ {
+ m_Units.Append(EMPTY_UNIT);
+ }
+ }
+
+ Unit FindUnitInList(UCM.Unit target)
+ {
+ foreach (Unit vmUnit in m_Units)
+ {
+ UCM.Unit modelUnit = vmUnit.GetModelUnit();
+ if (modelUnit.id == target.id)
+ {
+ return vmUnit;
+ }
+ }
+
+ return EMPTY_UNIT;
+ }
+
+ void OnUnitChanged(object parameter)
+ {
+ if ((m_Unit1 == null) || (m_Unit2 == null))
+ {
+ // Return if both Unit1 & Unit2 are not set
+ return;
+ }
+
+ m_model.SetCurrentUnitTypes(UnitFrom.GetModelUnit(), UnitTo.GetModelUnit());
+ if (m_supplementaryResultsTimer != null)
+ {
+ // End timer to show results immediately
+ m_supplementaryResultsTimer.Cancel();
+ }
+
+ if (!m_IsFirstTime)
+ {
+ SaveUserPreferences();
+ }
+ else
+ {
+ RestoreUserPreferences();
+ m_IsFirstTime = false;
+ }
+ }
+
+ void OnSwitchActive(object unused)
+ {
+ // this can be false if this switch occurs without the user having explicitly updated any strings
+ // (for example, during deserialization). We only want to try this cleanup if there's actually
+ // something to clean up.
+ if (m_relocalizeStringOnSwitch)
+ {
+ // clean up any ill-formed strings that were in progress before the switch
+ ValueFrom = ConvertToLocalizedString(m_valueFromUnlocalized, false);
+ }
+
+ SwitchConversionParameters();
+ // Now deactivate the other
+ if (m_value1cp == ConversionParameter.Source)
+ {
+ Value2Active = false;
+ }
+ else
+ {
+ Value1Active = false;
+ }
+
+ Utils.Swap(ref m_valueFromUnlocalized, ref m_valueToUnlocalized);
+ Utils.Swap(ref m_localizedValueFromFormat, ref m_localizedValueToFormat);
+
+ Utils.Swap(ref m_Unit1AutomationName, ref m_Unit2AutomationName);
+ RaisePropertyChanged(Unit1AutomationNamePropertyName);
+ RaisePropertyChanged(Unit2AutomationNamePropertyName);
+
+ m_isInputBlocked = false;
+ m_model.SwitchActive(m_valueFromUnlocalized);
+ }
+
+ string ConvertToLocalizedString(string stringToLocalize, bool allowPartialStrings)
+ {
+ string result = null;
+
+ if (stringToLocalize.empty())
+ {
+ return result;
+ }
+
+ m_decimalFormatter.IsDecimalPointAlwaysDisplayed = false;
+ m_decimalFormatter.FractionDigits = 0;
+ m_currencyFormatter.IsDecimalPointAlwaysDisplayed = false;
+ m_currencyFormatter.FractionDigits = 0;
+
+ var posOfE = stringToLocalize.find('e');
+ if (posOfE != "".npos())
+ {
+ int posOfSign = posOfE + 1;
+ char signOfE = stringToLocalize.at(posOfSign);
+ string significandStr = stringToLocalize.substr(0, posOfE);
+ string exponentStr = stringToLocalize.substr(posOfSign + 1, stringToLocalize.length() - posOfSign);
+
+ result += ConvertToLocalizedString(significandStr, allowPartialStrings) + "e" + signOfE + ConvertToLocalizedString(exponentStr, allowPartialStrings);
+ }
+ else
+ {
+ // stringToLocalize is in en-US and has the default decimal separator, so this is safe to do.
+ int posOfDecimal = stringToLocalize.find('.');
+
+ bool hasDecimal = "".npos() != posOfDecimal;
+
+ if (hasDecimal)
+ {
+ if (allowPartialStrings)
+ {
+ // allow "in progress" strings, like "3." that occur during the composition of
+ // a final number. Without this, when typing the three characters in "3.2"
+ // you don't see the decimal point when typing it, you only see it once you've finally
+ // typed a post-decimal digit.
+
+ m_decimalFormatter.IsDecimalPointAlwaysDisplayed = true;
+ m_currencyFormatter.IsDecimalPointAlwaysDisplayed = true;
+ }
+
+ // force post-decimal digits so that trailing zeroes entered by the user aren't suddenly cut off.
+ m_decimalFormatter.FractionDigits = (int)(stringToLocalize.length() - (posOfDecimal + 1));
+ m_currencyFormatter.FractionDigits = m_currencyMaxFractionDigits;
+ }
+
+ if (IsCurrencyCurrentCategory)
+ {
+ string currencyResult = m_currencyFormatter.Format(System.Convert.ToDouble(stringToLocalize));
+ string currencyCode = m_currencyFormatter.Currency;
+
+ // CurrencyFormatter always includes LangCode or Symbol. Make it include LangCode
+ // because this includes a non-breaking space. Remove the LangCode.
+ var pos = currencyResult.find(currencyCode);
+ if (pos != "".npos())
+ {
+ currencyResult = currencyResult.Remove(pos, currencyCode.length());
+ currencyResult = regexTrimSpacesStart.Replace(currencyResult, "");
+ currencyResult = regexTrimSpacesEnd.Replace(currencyResult, "");
+ }
+
+ result = currencyResult;
+ }
+ else
+ {
+ // Convert the input string to double using stod
+ // Then use the decimalFormatter to reformat the double to Platform String
+ result = m_decimalFormatter.Format(System.Convert.ToDouble(stringToLocalize));
+ }
+
+ if (hasDecimal)
+ {
+ // Since the output from GetLocaleInfoEx() and DecimalFormatter are differing for decimal string
+ // we are adding the below work-around of editing the string returned by DecimalFormatter
+ // and replacing the decimal separator with the one returned by GetLocaleInfoEx()
+ String formattedSampleString = m_decimalFormatter.Format(System.Convert.ToDouble("1.1"));
+ string formattedSampleWString = formattedSampleString;
+
+ string resultWithDecimal = result;
+ resultWithDecimal = resultWithDecimal.Replace(formattedSampleWString[1], m_decimalSeparator);
+
+ // Copy back the edited string to the result
+ result = resultWithDecimal;
+ }
+ }
+
+ string resultHolder = result;
+ if ((stringToLocalize.front() == '-' && System.Convert.ToDouble(stringToLocalize) == 0) || resultHolder.back() == '-')
+ {
+ if (resultHolder.back() == '-')
+ {
+ result = resultHolder.Remove(resultHolder.size() - 1, 1);
+ }
+
+ result = "-" + result;
+ }
+
+ result = Utils.LRE + result + Utils.PDF;
+ return result;
+ }
+
+ void DisplayPasteError()
+ {
+ String errorMsg = AppResourceProvider.GetInstance().GetCEngineString(EngineStrings.SIDS_DOMAIN); /*SIDS_DOMAIN is for "invalid input"*/
+ Value1 = errorMsg;
+ Value2 = errorMsg;
+ m_relocalizeStringOnSwitch = false;
+ }
+
+ internal void UpdateDisplay(string from, string to)
+ {
+ String fromStr = this.ConvertToLocalizedString(from, true);
+ UpdateInputBlocked(from);
+ String toStr = this.ConvertToLocalizedString(to, true);
+
+ bool updatedValueFrom = ValueFrom != fromStr;
+ bool updatedValueTo = ValueTo != toStr;
+ if (updatedValueFrom)
+ {
+ m_valueFromUnlocalized = from;
+ // once we've updated the unlocalized from string, we'll potentially need to clean it back up when switching between fields
+ // to eliminate dangling decimal points.
+ m_relocalizeStringOnSwitch = true;
+ }
+
+ if (updatedValueTo)
+ {
+ // This is supposed to use trimming logic, but that's highly dependent
+ // on the auto-scaling textbox control which we dont have yet. For now,
+ // not doing anything. It will have to be integrated once that control is
+ // created.
+ m_valueToUnlocalized = to;
+ }
+
+ m_isValue1Updating = m_Value1Active ? updatedValueFrom : updatedValueTo;
+ m_isValue2Updating = m_Value2Active ? updatedValueFrom : updatedValueTo;
+
+ // Setting these properties before setting the member variables above causes
+ // a chain of properties that can result in the wrong result being announced
+ // to Narrator. We need to know which values are updating before setting the
+ // below properties, so that we know when to announce the result.
+ if (updatedValueFrom)
+ {
+ ValueFrom = fromStr;
+ }
+
+ if (updatedValueTo)
+ {
+ ValueTo = toStr;
+ }
+ }
+
+ internal void UpdateSupplementaryResults(CalculatorList> suggestedValues)
+ {
+ lock (m_cacheMutex)
+ {
+ m_cachedSuggestedValues = suggestedValues;
+ }
+
+ // If we're already "ticking", reset the timer
+ if (m_supplementaryResultsTimer != null)
+ {
+ m_resettingTimer = true;
+ m_supplementaryResultsTimer.Cancel();
+ m_resettingTimer = false;
+ }
+
+ // Schedule the timer
+ m_supplementaryResultsTimer = ThreadPoolTimer.CreateTimer(
+ new TimerElapsedHandler(SupplementaryResultsTimerTick),
+ SUPPLEMENTARY_VALUES_INTERVAL,
+ new TimerDestroyedHandler(SupplementaryResultsTimerCancel)); // UNO: TIMER_CALLBACK_CONTEXT: cf. _supplementaryResultsTimerContext
+ }
+
+ public void OnValueActivated(IActivatable control)
+ {
+ control.IsActive = true;
+ }
+
+ void OnButtonPressed(object parameter)
+ {
+ NumbersAndOperatorsEnum numOpEnum = CalculatorButtonPressedEventArgs.GetOperationFromCommandParameter(parameter);
+ UCM.Command command = CommandFromButtonId(numOpEnum);
+
+ // Don't clear the display if combo box is open and escape is pressed
+ if (command == UCM.Command.Clear && IsDropDownOpen)
+ {
+ return;
+ }
+
+ CalculatorList OPERANDS = new CalculatorList
+ {
+ UCM.Command.Zero, UCM.Command.One, UCM.Command.Two, UCM.Command.Three, UCM.Command.Four,
+ UCM.Command.Five, UCM.Command.Six, UCM.Command.Seven, UCM.Command.Eight, UCM.Command.Nine
+ };
+
+ if (OPERANDS.IndexOf(command) != -1)
+ {
+ if (m_isInputBlocked)
+ {
+ return;
+ }
+
+ if (m_IsCurrencyCurrentCategory)
+ {
+ StartConversionResultTimer();
+ }
+ }
+
+ m_model.SendCommand(command);
+ }
+
+ public void OnCopyCommand(object parameter)
+ {
+ // EventWriteClipboardCopy_Start();
+ CopyPasteManager.CopyToClipboard(m_valueFromUnlocalized);
+ // EventWriteClipboardCopy_Stop();
+ }
+
+ public async void OnPasteCommand(object parameter)
+ {
+ // if there's nothing to copy early out
+ if (!CopyPasteManager.HasStringToPaste())
+ {
+ return;
+ }
+
+ // Ensure that the paste happens on the UI thread
+ // EventWriteClipboardPaste_Start();
+ // Any converter ViewMode is fine here.
+ var pastedString = await CopyPasteManager.GetStringToPaste(m_Mode, NavCategory.GetGroupType(m_Mode), default(int), default(int));
+ OnPaste(pastedString, m_Mode);
+ }
+
+ void InitializeView()
+ {
+ CalculatorList categories = m_model.GetCategories();
+ for (uint i = 0; i < categories.Size(); i++)
+ {
+ Category category = new Category(categories[i]);
+ m_Categories.Append(category);
+ }
+
+ RestoreUserPreferences();
+ CurrentCategory = new Category(m_model.GetCurrentCategory());
+ }
+
+ static bool isCategoryChanging = false;
+
+ void OnPropertyChanged(string prop)
+ {
+ if (prop == CurrentCategoryPropertyName)
+ {
+ isCategoryChanging = true;
+ CategoryChanged.Execute(null);
+ isCategoryChanging = false;
+ }
+ else if (prop == Unit1PropertyName || prop == Unit2PropertyName)
+ {
+ // Category changes will handle updating units after they've both been updated.
+ // This event should only be used to update units from explicit user interaction.
+ if (!isCategoryChanging)
+ {
+ UnitChanged.Execute(null);
+ }
+
+ // Get the localized automation name for each CalculationResults field
+ if (prop == Unit1PropertyName)
+ {
+ UpdateValue1AutomationName();
+ }
+ else
+ {
+ UpdateValue2AutomationName();
+ }
+ }
+ else if (prop == Value1PropertyName)
+ {
+ UpdateValue1AutomationName();
+ }
+ else if (prop == Value2PropertyName)
+ {
+ UpdateValue2AutomationName();
+ }
+ else if (prop == Value1ActivePropertyName || prop == Value2ActivePropertyName)
+ {
+ // if one of the values is activated, and as a result both are true, it means
+ // that we're trying to switch.
+ if (Value1Active && Value2Active)
+ {
+ SwitchActive.Execute(null);
+ }
+
+ UpdateValue1AutomationName();
+ UpdateValue2AutomationName();
+ }
+ else if (prop == SupplementaryResultsPropertyName)
+ {
+ RaisePropertyChanged(SupplementaryVisibilityPropertyName);
+ }
+ else if (prop == Value1AutomationNamePropertyName)
+ {
+ m_isValue1Updating = false;
+ if (!m_isValue2Updating)
+ {
+ AnnounceConversionResult();
+ }
+ }
+ else if (prop == Value2AutomationNamePropertyName)
+ {
+ m_isValue2Updating = false;
+ if (!m_isValue1Updating)
+ {
+ AnnounceConversionResult();
+ }
+ }
+ else if (prop == CurrencySymbol1PropertyName || prop == CurrencySymbol2PropertyName)
+ {
+ RaisePropertyChanged(CurrencySymbolVisibilityPropertyName);
+ }
+ }
+
+ String Serialize()
+ {
+ using (var @out = new StringWriter())
+ {
+ const string delimiter = "[;;;]";
+ @out.Write(m_resettingTimer);
+ @out.Write(delimiter);
+ @out.Write((int)(m_value1cp));
+ @out.Write(delimiter);
+ @out.Write(m_Value1Active);
+ @out.Write(delimiter);
+ @out.Write(m_Value2Active);
+ @out.Write(delimiter);
+ @out.Write(m_Value1);
+ @out.Write(delimiter);
+ @out.Write(m_Value2);
+ @out.Write(delimiter);
+ @out.Write(m_valueFromUnlocalized);
+ @out.Write(delimiter);
+ @out.Write(m_valueToUnlocalized);
+ @out.Write(delimiter);
+ @out.Write("[###]");
+ string unitConverterSerializedData = m_model.Serialize();
+
+ if (!unitConverterSerializedData.empty())
+ {
+ @out.Write(m_model.Serialize());
+ @out.Write("[###]");
+ String serializedData = @out.ToString();
+ return serializedData;
+ }
+
+ return null;
+ }
+ }
+
+ void Deserialize(string state)
+ {
+ string serializedData = state;
+ CalculatorList tokens = UCM.UnitConverter.StringToVector(serializedData, "[###]");
+ Debug.Assert(tokens.Size() >= 2);
+ CalculatorList viewModelData = UCM.UnitConverter.StringToVector(tokens[0], "[;;;]");
+ Debug.Assert(viewModelData.Size() == EXPECTEDVIEWMODELDATATOKENS);
+ m_resettingTimer = (viewModelData[0].CompareTo("1") == 0);
+ m_value1cp = (ConversionParameter)System.Convert.ToInt32(viewModelData[1]);
+ m_Value1Active = (viewModelData[2].CompareTo("1") == 0);
+ m_Value2Active = (viewModelData[3].CompareTo("1") == 0);
+ m_Value1 = viewModelData[4];
+ m_Value2 = viewModelData[5];
+ m_valueFromUnlocalized = viewModelData[6];
+ m_valueToUnlocalized = viewModelData[7];
+ string modelData = string.Empty;
+ for (uint i = 1; i < tokens.Size(); i++)
+ {
+ modelData += tokens[i] + "[###]";
+ }
+
+ m_model.DeSerialize(modelData);
+ InitializeView();
+ RaisePropertyChanged(null); // Update since all props have been updated.
+ }
+
+ // Saving User Preferences of Category and Associated-Units across Sessions.
+ void SaveUserPreferences()
+ {
+ if (UnitsAreValid())
+ {
+ ApplicationDataContainer localSettings = ApplicationData.Current.LocalSettings;
+ if (!m_IsCurrencyCurrentCategory)
+ {
+ var userPreferences = m_model.SaveUserPreferences();
+ localSettings.Values["UnitConverterPreferences"] = userPreferences;
+ }
+ else
+ {
+ // Currency preferences shouldn't be saved in the same way as standard converter modes because
+ // the delay loading creates a big mess of issues that are better to avoid.
+ localSettings.Values[CalculatorApp.ViewModel.UnitConverterResourceKeys.CurrencyUnitFromKey] = UnitFrom.Abbreviation;
+ localSettings.Values[CalculatorApp.ViewModel.UnitConverterResourceKeys.CurrencyUnitToKey] = UnitTo.Abbreviation;
+ }
+ }
+ }
+
+ // Restoring User Preferences of Category and Associated-Units.
+ void RestoreUserPreferences()
+ {
+ if (!IsCurrencyCurrentCategory)
+ {
+ ApplicationDataContainer localSettings = ApplicationData.Current.LocalSettings;
+ if (localSettings.Values.ContainsKey("UnitConverterPreferences"))
+ {
+ var userPreferences = (string)localSettings.Values["UnitConverterPreferences"];
+ m_model.RestoreUserPreferences(userPreferences);
+ }
+ }
+ }
+
+ internal void OnCurrencyDataLoadFinished(bool didLoad)
+ {
+ m_isCurrencyDataLoaded = true;
+ CurrencyDataLoadFailed = !didLoad;
+ m_model.ResetCategoriesAndRatios();
+ m_model.Calculate();
+ ResetCategory();
+
+ string key = didLoad ? UnitConverterResourceKeys.CurrencyRatesUpdated : UnitConverterResourceKeys.CurrencyRatesUpdateFailed;
+ String announcement = AppResourceProvider.GetInstance().GetResourceString(key);
+ Announcement = CalculatorAnnouncement.GetUpdateCurrencyRatesAnnouncement(announcement);
+ }
+
+ internal void OnCurrencyTimestampUpdated(string timestamp, bool isWeekOld)
+ {
+ CurrencyDataIsWeekOld = isWeekOld;
+ CurrencyTimestamp = timestamp;
+ }
+
+ public async void RefreshCurrencyRatios()
+ {
+ m_isCurrencyDataLoaded = false;
+ IsCurrencyLoadingVisible = true;
+
+ String announcement = AppResourceProvider.GetInstance().GetResourceString(UnitConverterResourceKeys.UpdatingCurrencyRates);
+ Announcement = CalculatorAnnouncement.GetUpdateCurrencyRatesAnnouncement(announcement);
+
+ var refreshTask = m_model.RefreshCurrencyRatios();
+ var refreshResult = await refreshTask;
+ bool didLoad = refreshResult.Key;
+ string timestamp = refreshResult.Value;
+
+ OnCurrencyTimestampUpdated(timestamp, false /*isWeekOldData*/);
+ OnCurrencyDataLoadFinished(didLoad);
+ }
+
+ internal void OnNetworkBehaviorChanged(NetworkAccessBehavior newBehavior)
+ {
+ CurrencyDataLoadFailed = false;
+ NetworkBehavior = newBehavior;
+ }
+
+ UnitConversionManager.Command CommandFromButtonId(NumbersAndOperatorsEnum button)
+ {
+ UCM.Command command;
+
+ switch (button)
+ {
+ case NumbersAndOperatorsEnum.Zero:
+ command = UCM.Command.Zero;
+ break;
+ case NumbersAndOperatorsEnum.One:
+ command = UCM.Command.One;
+ break;
+ case NumbersAndOperatorsEnum.Two:
+ command = UCM.Command.Two;
+ break;
+ case NumbersAndOperatorsEnum.Three:
+ command = UCM.Command.Three;
+ break;
+ case NumbersAndOperatorsEnum.Four:
+ command = UCM.Command.Four;
+ break;
+ case NumbersAndOperatorsEnum.Five:
+ command = UCM.Command.Five;
+ break;
+ case NumbersAndOperatorsEnum.Six:
+ command = UCM.Command.Six;
+ break;
+ case NumbersAndOperatorsEnum.Seven:
+ command = UCM.Command.Seven;
+ break;
+ case NumbersAndOperatorsEnum.Eight:
+ command = UCM.Command.Eight;
+ break;
+ case NumbersAndOperatorsEnum.Nine:
+ command = UCM.Command.Nine;
+ break;
+ case NumbersAndOperatorsEnum.Decimal:
+ command = UCM.Command.Decimal;
+ break;
+ case NumbersAndOperatorsEnum.Negate:
+ command = UCM.Command.Negate;
+ break;
+ case NumbersAndOperatorsEnum.Backspace:
+ command = UCM.Command.Backspace;
+ break;
+ case NumbersAndOperatorsEnum.Clear:
+ command = UCM.Command.Clear;
+ break;
+ default:
+ command = UCM.Command.None;
+ break;
+ }
+
+ return command;
+ }
+
+ void SupplementaryResultsTimerTick(ThreadPoolTimer timer)
+ {
+ timer.Cancel();
+ }
+
+ // UNO: We cannot specify the resturning context of ThreadPoolTime in C#.
+ // Instead we capture the dispatcher on which this VM has been created, and send back the UI update on it
+ private readonly CoreDispatcher _supplementaryResultsTimerContext = Window.Current.Dispatcher;
+
+ void SupplementaryResultsTimerCancel(ThreadPoolTimer timer)
+ {
+ if (!m_resettingTimer)
+ {
+ _supplementaryResultsTimerContext.RunAsync(CoreDispatcherPriority.Normal, RefreshSupplementaryResults);
+ }
+ }
+
+ void RefreshSupplementaryResults()
+ {
+ lock (m_cacheMutex)
+ {
+ m_SupplementaryResults.Clear();
+
+ CalculatorList whimsicals = new CalculatorList();
+
+ foreach (Tuple suggestedValue in m_cachedSuggestedValues)
+ {
+ SupplementaryResult result = new SupplementaryResult(this.ConvertToLocalizedString(suggestedValue.Item1, false), new Unit(suggestedValue.Item2));
+ if (result.IsWhimsical())
+ {
+ whimsicals.PushBack(result);
+ }
+ else
+ {
+ m_SupplementaryResults.Append(result);
+ }
+ }
+
+ if (whimsicals.Size() > 0)
+ {
+ m_SupplementaryResults.Append(whimsicals[0]);
+ }
+
+ }
+
+ RaisePropertyChanged(SupplementaryResultsPropertyName);
+ // EventWriteConverterSupplementaryResultsUpdated();
+ }
+
+ // When UpdateDisplay is called, the ViewModel will remember the From/To unlocalized display values
+ // This function will announce the conversion result after the ValueTo/ValueFrom automation names update,
+ // only if the new unlocalized display values are different from the last announced values, and if the
+ // values are not both zero.
+ void AnnounceConversionResult()
+ {
+ if ((m_valueFromUnlocalized != m_lastAnnouncedFrom || m_valueToUnlocalized != m_lastAnnouncedTo) && Unit1 != null && Unit2 != null)
+ {
+ m_lastAnnouncedFrom = m_valueFromUnlocalized;
+ m_lastAnnouncedTo = m_valueToUnlocalized;
+
+ Unit unitFrom = Value1Active ? Unit1 : Unit2;
+ Unit unitTo = (unitFrom == Unit1) ? Unit2 : Unit1;
+ m_lastAnnouncedConversionResult = GetLocalizedConversionResultStringFormat(ValueFrom, unitFrom.Name, ValueTo, unitTo.Name);
+
+ Announcement = CalculatorAnnouncement.GetDisplayUpdatedAnnouncement(m_lastAnnouncedConversionResult);
+ }
+ }
+
+ void UpdateInputBlocked(string currencyInput)
+ {
+ // currencyInput is in en-US and has the default decimal separator, so this is safe to do.
+ var posOfDecimal = currencyInput.find('.');
+ m_isInputBlocked = false;
+ if (posOfDecimal != "".npos() && IsCurrencyCurrentCategory)
+ {
+ m_isInputBlocked = (posOfDecimal + (int)(m_currencyMaxFractionDigits) + 1 == currencyInput.length());
+ }
+ }
+
+ NumbersAndOperatorsEnum MapCharacterToButtonId(char ch, bool canSendNegate)
+ {
+ Debug.Assert(NumbersAndOperatorsEnum.Zero < NumbersAndOperatorsEnum.One, "NumbersAndOperatorsEnum order is invalid");
+ Debug.Assert(NumbersAndOperatorsEnum.One < NumbersAndOperatorsEnum.Two, "NumbersAndOperatorsEnum order is invalid");
+ Debug.Assert(NumbersAndOperatorsEnum.Two < NumbersAndOperatorsEnum.Three, "NumbersAndOperatorsEnum order is invalid");
+ Debug.Assert(NumbersAndOperatorsEnum.Three < NumbersAndOperatorsEnum.Four, "NumbersAndOperatorsEnum order is invalid");
+ Debug.Assert(NumbersAndOperatorsEnum.Four < NumbersAndOperatorsEnum.Five, "NumbersAndOperatorsEnum order is invalid");
+ Debug.Assert(NumbersAndOperatorsEnum.Five < NumbersAndOperatorsEnum.Six, "NumbersAndOperatorsEnum order is invalid");
+ Debug.Assert(NumbersAndOperatorsEnum.Six < NumbersAndOperatorsEnum.Seven, "NumbersAndOperatorsEnum order is invalid");
+ Debug.Assert(NumbersAndOperatorsEnum.Seven < NumbersAndOperatorsEnum.Eight, "NumbersAndOperatorsEnum order is invalid");
+ Debug.Assert(NumbersAndOperatorsEnum.Eight < NumbersAndOperatorsEnum.Nine, "NumbersAndOperatorsEnum order is invalid");
+ Debug.Assert(NumbersAndOperatorsEnum.Zero < NumbersAndOperatorsEnum.Nine, "NumbersAndOperatorsEnum order is invalid");
+
+ NumbersAndOperatorsEnum mappedValue = NumbersAndOperatorsEnum.None;
+ canSendNegate = false;
+
+ switch (ch)
+ {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ mappedValue = (int)NumbersAndOperatorsEnum.Zero + (NumbersAndOperatorsEnum)(ch - '0');
+ canSendNegate = true;
+ break;
+
+ case '-':
+ mappedValue = NumbersAndOperatorsEnum.Negate;
+ break;
+
+ default:
+ // Respect the user setting for decimal separator
+ if (ch == m_decimalSeparator)
+ {
+ mappedValue = NumbersAndOperatorsEnum.Decimal;
+ canSendNegate = true;
+ break;
+ }
+
+ break;
+ }
+
+ if (mappedValue == NumbersAndOperatorsEnum.None)
+ {
+ if (LocalizationSettings.GetInstance().IsLocalizedDigit(ch))
+ {
+ mappedValue = (int)NumbersAndOperatorsEnum.Zero
+ + (NumbersAndOperatorsEnum)(ch - LocalizationSettings.GetInstance().GetDigitSymbolFromEnUsDigit('0'));
+ canSendNegate = true;
+ }
+ }
+
+ return mappedValue;
+ }
+
+ public void OnPaste(String stringToPaste, ViewMode mode)
+ {
+ // If pastedString is invalid("NoOp") then display pasteError else process the string
+ if (stringToPaste == CopyPasteManager.PasteErrorString)
+ {
+ this.DisplayPasteError();
+ return;
+ }
+
+ TraceLogger.GetInstance().LogValidInputPasted(mode);
+ bool isFirstLegalChar = true;
+ bool sendNegate = false;
+ string accumulation = "";
+
+ foreach (char it in stringToPaste)
+ {
+ bool canSendNegate = false;
+
+ NumbersAndOperatorsEnum op = MapCharacterToButtonId(it, canSendNegate);
+
+ if (NumbersAndOperatorsEnum.None != op)
+ {
+ if (isFirstLegalChar)
+ {
+ // Send Clear before sending something that will actually apply
+ // to the field.
+ m_model.SendCommand(UCM.Command.Clear);
+ isFirstLegalChar = false;
+
+ // If the very first legal character is a - sign, send negate
+ // after sending the next legal character. Send nothing now, or
+ // it will be ignored.
+ if (NumbersAndOperatorsEnum.Negate == op)
+ {
+ sendNegate = true;
+ }
+ }
+
+ // Negate is only allowed if it's the first legal character, which is handled above.
+ if (NumbersAndOperatorsEnum.Negate != op)
+ {
+ UCM.Command cmd = CommandFromButtonId(op);
+ m_model.SendCommand(cmd);
+
+ if (sendNegate)
+ {
+ if (canSendNegate)
+ {
+ m_model.SendCommand(UCM.Command.Negate);
+ }
+
+ sendNegate = false;
+ }
+ }
+
+ accumulation += it;
+ UpdateInputBlocked(accumulation);
+ if (m_isInputBlocked)
+ {
+ break;
+ }
+ }
+ else
+ {
+ sendNegate = false;
+ }
+ }
+ }
+
+ String GetLocalizedAutomationName(String displayvalue, String unitname, String format)
+ {
+ String valueToLocalize = displayvalue;
+ if (displayvalue == ValueFrom && Utils.IsLastCharacterTarget(m_valueFromUnlocalized, m_decimalSeparator))
+ {
+ // Need to compute a second localized value for the automation
+ // name that does not include the decimal separator.
+ displayvalue = ConvertToLocalizedString(m_valueFromUnlocalized, false /*allowTrailingDecimal*/);
+ format = m_localizedValueFromDecimalFormat;
+ }
+
+ string localizedResult = LocalizationStringUtil.GetLocalizedString(format, displayvalue, unitname);
+ return localizedResult;
+ }
+
+ String GetLocalizedConversionResultStringFormat(
+ String fromValue,
+ String fromUnit,
+ String toValue,
+ String toUnit)
+ {
+ String localizedString = LocalizationStringUtil.GetLocalizedString(m_localizedConversionResultFormat, fromValue, fromUnit, toValue, toUnit);
+ return localizedString;
+ }
+
+ void UpdateValue1AutomationName()
+ {
+ if (Unit1 != null)
+ {
+ Value1AutomationName = GetLocalizedAutomationName(Value1, Unit1.AccessibleName, m_localizedValueFromFormat);
+ }
+ }
+
+ void UpdateValue2AutomationName()
+ {
+ if (Unit2 != null)
+ {
+ Value2AutomationName = GetLocalizedAutomationName(Value2, Unit2.AccessibleName, m_localizedValueToFormat);
+ }
+ }
+
+ internal void OnMaxDigitsReached()
+ {
+ String format = AppResourceProvider.GetInstance().GetResourceString(UnitConverterResourceKeys.MaxDigitsReachedFormat);
+ string announcement = LocalizationStringUtil.GetLocalizedString(format, m_lastAnnouncedConversionResult);
+ Announcement = CalculatorAnnouncement.GetMaxDigitsReachedAnnouncement(announcement);
+ }
+
+ bool UnitsAreValid()
+ {
+ return UnitFrom != null && !UnitFrom.Abbreviation.IsEmpty() && UnitTo != null && !UnitTo.Abbreviation.IsEmpty();
+ }
+
+ void StartConversionResultTimer()
+ {
+ m_conversionResultTaskHelper = new ConversionResultTaskHelper(CONVERSION_FINALIZED_DELAY_IN_MS,
+ () =>
+ {
+ if (UnitsAreValid())
+ {
+ String valueFrom = m_Value1Active ? m_Value1 : m_Value2;
+ String valueTo = m_Value1Active ? m_Value2 : m_Value1;
+ TraceLogger.GetInstance().LogConversionResult(valueFrom, UnitFrom.ToString(), valueTo, UnitTo.ToString());
+ }
+ });
+ }
+
+ String GetLocalizedAutomationName()
+ {
+ // TODO UNO
+ //var format = AppResourceProvider.GetInstance().GetResourceString("SupplementaryUnit_AutomationName");
+ //return new String(LocalizationStringUtil.GetLocalizedString(format, this.Value, this.Unit.Name));
+
+ return string.Empty;
+ }
+ }
+}
diff --git a/src/Calculator.Shared/Views/MainPage.xaml b/src/Calculator.Shared/Views/MainPage.xaml
index 6c04c6f4..0aa64f79 100644
--- a/src/Calculator.Shared/Views/MainPage.xaml
+++ b/src/Calculator.Shared/Views/MainPage.xaml
@@ -25,14 +25,14 @@
-->
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+