Merged PR 10741448: [Recall] Snapshot saving and restoring

####What
According to PM [Spec](https://microsoft.sharepoint-df.com/:w:/t/PAXEssentialExperiences421/EcpP5tGRtFdIsRrP84ueRfUBjb6tfayxWtF9ujvJuNx6Dg?e=AeRzVf), saving and restoring Calculator snapshot when required.

The current snapshot supports to:
- Restore the calculator mode.
- Restore the current calculation (display value and expression).
- Restore the history of calculations (either in Standard mode or Scientific mode) shown at the time the snapshot was taken.
- Restore the current calculation error state, if applicable.

####How
- Added `SnapshotHelper` to help save and restore snapshots.
- Besides the existing snapshot information from view models, added an extra field `SnapshotVersion` in `ApplicationSnapshot` for backward compatibility.

#### Note
Unit tests will be added in a separate PR.

Related work items: #50701758
This commit is contained in:
Han Zhang 👾 2024-05-16 06:27:55 +00:00 committed by Tian Liao ☕
commit c4be28fbfc
8 changed files with 584 additions and 15 deletions

View file

@ -477,16 +477,25 @@ namespace CalculationManager
}
}
vector<shared_ptr<HISTORYITEM>> const& CalculatorManager::GetHistoryItems()
vector<shared_ptr<HISTORYITEM>> const& CalculatorManager::GetHistoryItems() const
{
return m_pHistory->GetHistory();
}
vector<shared_ptr<HISTORYITEM>> const& CalculatorManager::GetHistoryItems(_In_ CalculatorMode mode)
vector<shared_ptr<HISTORYITEM>> const& CalculatorManager::GetHistoryItems(_In_ CalculatorMode mode) const
{
return (mode == CalculatorMode::Standard) ? m_pStdHistory->GetHistory() : m_pSciHistory->GetHistory();
}
void CalculatorManager::SetHistoryItems(_In_ std::vector<std::shared_ptr<HISTORYITEM>> const& historyItems)
{
for (auto const& historyItem : historyItems)
{
auto index = m_pHistory->AddItem(historyItem);
OnHistoryItemAdded(index);
}
}
shared_ptr<HISTORYITEM> const& CalculatorManager::GetHistoryItem(_In_ unsigned int uIdx)
{
return m_pHistory->GetHistoryItem(uIdx);

View file

@ -107,8 +107,9 @@ namespace CalculationManager
void UpdateMaxIntDigits();
wchar_t DecimalSeparator();
std::vector<std::shared_ptr<HISTORYITEM>> const& GetHistoryItems();
std::vector<std::shared_ptr<HISTORYITEM>> const& GetHistoryItems(_In_ CalculatorMode mode);
std::vector<std::shared_ptr<HISTORYITEM>> const& GetHistoryItems() const;
std::vector<std::shared_ptr<HISTORYITEM>> const& GetHistoryItems(_In_ CalculatorMode mode) const;
void SetHistoryItems(_In_ std::vector<std::shared_ptr<HISTORYITEM>> const& historyItems);
std::shared_ptr<HISTORYITEM> const& GetHistoryItem(_In_ unsigned int uIdx);
bool RemoveHistoryItem(_In_ unsigned int uIdx);
void ClearHistory();

View file

@ -18,7 +18,6 @@ using namespace CalculatorApp::ViewModel;
using namespace CalculationManager;
using namespace Platform;
using namespace Platform::Collections;
using namespace std;
using namespace Windows::System;
using namespace Windows::Storage;
using namespace Utils;
@ -38,6 +37,439 @@ namespace
{
StringReference CategoriesPropertyName(L"Categories");
StringReference ClearMemoryVisibilityPropertyName(L"ClearMemoryVisibility");
struct SnapshotHelper
{
static constexpr int SnapshotVersion = 0;
static Windows::Data::Json::JsonObject ^ SaveSnapshotToJson(const ApplicationSnapshot& value)
{
auto jsonObject = ref new Windows::Data::Json::JsonObject();
jsonObject->SetNamedValue(L"SnapshotVersion", Windows::Data::Json::JsonValue::CreateNumberValue(value.SnapshotVersion));
jsonObject->SetNamedValue(L"Mode", Windows::Data::Json::JsonValue::CreateNumberValue(value.Mode));
if (value.StandardCalc.has_value())
{
jsonObject->SetNamedValue(L"StandardCalculatorSnapshot", SaveSnapshotToJson(*value.StandardCalc));
}
return jsonObject;
}
static Windows::Data::Json::JsonObject ^ SaveSnapshotToJson(const StandardCalculatorSnapshot& value)
{
auto jsonObject = ref new Windows::Data::Json::JsonObject();
jsonObject->SetNamedValue(L"CalculatorManagerSnapshot", SaveSnapshotToJson(value.CalcManager));
jsonObject->SetNamedValue(L"PrimaryDisplay", SaveSnapshotToJson(value.PrimaryDisplay));
if (value.ExpressionDisplay.has_value())
{
jsonObject->SetNamedValue(L"ExpressionDisplay", SaveSnapshotToJson(*value.ExpressionDisplay));
}
return jsonObject;
}
static Windows::Data::Json::JsonObject ^ SaveSnapshotToJson(const PrimaryDisplaySnapshot& value)
{
auto jsonObject = ref new Windows::Data::Json::JsonObject();
jsonObject->SetNamedValue(L"DisplayValue", Windows::Data::Json::JsonValue::CreateStringValue(value.DisplayValue));
jsonObject->SetNamedValue(L"IsError", Windows::Data::Json::JsonValue::CreateBooleanValue(value.IsError));
return jsonObject;
}
static Windows::Data::Json::JsonObject ^ SaveSnapshotToJson(const ExpressionDisplaySnapshot& value)
{
auto jsonObject = ref new Windows::Data::Json::JsonObject();
auto tokensJsonArray = ref new Windows::Data::Json::JsonArray();
for (const auto& token : value.Tokens)
{
auto tokenJsonArray = ref new Windows::Data::Json::JsonArray();
tokenJsonArray->Append(Windows::Data::Json::JsonValue::CreateStringValue(ref new Platform::String(token.first.c_str())));
tokenJsonArray->Append(Windows::Data::Json::JsonValue::CreateNumberValue(token.second));
tokensJsonArray->Append(tokenJsonArray);
}
jsonObject->SetNamedValue(L"Tokens", tokensJsonArray);
auto commandsJsonArray = ref new Windows::Data::Json::JsonArray();
for (const auto& command : value.Commands)
{
commandsJsonArray->Append(SaveSnapshotToJson(command));
}
jsonObject->SetNamedValue(L"Commands", commandsJsonArray);
return jsonObject;
}
static Windows::Data::Json::JsonObject ^ SaveSnapshotToJson(const CalculatorManagerSnapshot& value)
{
auto jsonObject = ref new Windows::Data::Json::JsonObject();
if (value.HistoryItems.has_value())
{
auto historyJsonArray = ref new Windows::Data::Json::JsonArray();
for (const auto& item : *value.HistoryItems)
{
historyJsonArray->Append(SaveSnapshotToJson(*item));
}
jsonObject->SetNamedValue(L"HistoryItems", historyJsonArray);
}
return jsonObject;
}
static Windows::Data::Json::JsonObject ^ SaveSnapshotToJson(const CalculationManager::HISTORYITEM& value)
{
auto jsonObject = ref new Windows::Data::Json::JsonObject();
jsonObject->SetNamedValue(L"Expression", Windows::Data::Json::JsonValue::CreateStringValue(ref new Platform::String(value.historyItemVector.expression.c_str())));
jsonObject->SetNamedValue(L"Result", Windows::Data::Json::JsonValue::CreateStringValue(ref new Platform::String(value.historyItemVector.result.c_str())));
auto tokensJsonArray = ref new Windows::Data::Json::JsonArray();
for (const auto& token : *value.historyItemVector.spTokens)
{
auto tokenJsonArray = ref new Windows::Data::Json::JsonArray();
tokenJsonArray->Append(Windows::Data::Json::JsonValue::CreateStringValue(ref new Platform::String(token.first.c_str())));
tokenJsonArray->Append(Windows::Data::Json::JsonValue::CreateNumberValue(token.second));
tokensJsonArray->Append(tokenJsonArray);
}
jsonObject->SetNamedValue(L"Tokens", tokensJsonArray);
auto commandsJsonArray = ref new Windows::Data::Json::JsonArray();
for (const auto& command : *value.historyItemVector.spCommands)
{
commandsJsonArray->Append(SaveSnapshotToJson(command));
}
jsonObject->SetNamedValue(L"Commands", commandsJsonArray);
return jsonObject;
}
static Windows::Data::Json::JsonObject ^ SaveSnapshotToJson(const std::shared_ptr<IExpressionCommand>& value)
{
auto jsonObject = ref new Windows::Data::Json::JsonObject();
auto opndCommand = dynamic_cast<COpndCommand*>(value.get());
if (opndCommand != nullptr)
{
jsonObject = SaveSnapshotToJson(*opndCommand);
}
auto unaryCommand = dynamic_cast<CUnaryCommand*>(value.get());
if (unaryCommand != nullptr)
{
jsonObject = SaveSnapshotToJson(*unaryCommand);
}
auto binaryCommand = dynamic_cast<CBinaryCommand*>(value.get());
if (binaryCommand != nullptr)
{
jsonObject = SaveSnapshotToJson(*binaryCommand);
}
auto parenthesesCommand = dynamic_cast<CParentheses*>(value.get());
if (parenthesesCommand != nullptr)
{
jsonObject = SaveSnapshotToJson(*parenthesesCommand);
}
return jsonObject;
}
static Windows::Data::Json::JsonObject ^ SaveSnapshotToJson(const COpndCommand& value)
{
auto jsonObject = ref new Windows::Data::Json::JsonObject();
jsonObject->SetNamedValue(L"CommandType", Windows::Data::Json::JsonValue::CreateNumberValue(static_cast<double>(value.GetCommandType())));
jsonObject->SetNamedValue(L"IsNegative", Windows::Data::Json::JsonValue::CreateBooleanValue(value.IsNegative()));
jsonObject->SetNamedValue(L"IsDecimalPresent", Windows::Data::Json::JsonValue::CreateBooleanValue(value.IsDecimalPresent()));
jsonObject->SetNamedValue(L"IsSciFmt", Windows::Data::Json::JsonValue::CreateBooleanValue(value.IsSciFmt()));
auto commandsJsonArray = ref new Windows::Data::Json::JsonArray();
for (const auto& command : *value.GetCommands())
{
commandsJsonArray->Append(Windows::Data::Json::JsonValue::CreateNumberValue(command));
}
jsonObject->SetNamedValue(L"Commands", commandsJsonArray);
return jsonObject;
}
static Windows::Data::Json::JsonObject ^ SaveSnapshotToJson(const CUnaryCommand& value)
{
auto jsonObject = ref new Windows::Data::Json::JsonObject();
jsonObject->SetNamedValue(L"CommandType", Windows::Data::Json::JsonValue::CreateNumberValue(static_cast<double>(value.GetCommandType())));
auto commandsJsonArray = ref new Windows::Data::Json::JsonArray();
for (const auto& command : *value.GetCommands())
{
commandsJsonArray->Append(Windows::Data::Json::JsonValue::CreateNumberValue(command));
}
jsonObject->SetNamedValue(L"Commands", commandsJsonArray);
return jsonObject;
}
static Windows::Data::Json::JsonObject ^ SaveSnapshotToJson(const CBinaryCommand& value)
{
auto jsonObject = ref new Windows::Data::Json::JsonObject();
jsonObject->SetNamedValue(L"CommandType", Windows::Data::Json::JsonValue::CreateNumberValue(static_cast<double>(value.GetCommandType())));
jsonObject->SetNamedValue(L"Command", Windows::Data::Json::JsonValue::CreateNumberValue(value.GetCommand()));
return jsonObject;
}
static Windows::Data::Json::JsonObject ^ SaveSnapshotToJson(const CParentheses& value)
{
auto jsonObject = ref new Windows::Data::Json::JsonObject();
jsonObject->SetNamedValue(L"CommandType", Windows::Data::Json::JsonValue::CreateNumberValue(static_cast<double>(value.GetCommandType())));
jsonObject->SetNamedValue(L"Command", Windows::Data::Json::JsonValue::CreateNumberValue(value.GetCommand()));
return jsonObject;
}
static void RestoreJsonToSnapshot(Windows::Data::Json::JsonObject ^ jsonObject, std::optional<ApplicationSnapshot>& value)
{
ApplicationSnapshot applicationSnapshot;
applicationSnapshot.SnapshotVersion = static_cast<int>(jsonObject->GetNamedNumber(L"SnapshotVersion"));
if (applicationSnapshot.SnapshotVersion > SnapshotVersion)
{
return;
}
applicationSnapshot.Mode = static_cast<int>(jsonObject->GetNamedNumber(L"Mode"));
if (jsonObject->HasKey(L"StandardCalculatorSnapshot"))
{
std::optional<StandardCalculatorSnapshot> standardCalculatorSnapshot;
RestoreJsonToSnapshot(jsonObject->GetNamedObject(L"StandardCalculatorSnapshot"), standardCalculatorSnapshot);
if (standardCalculatorSnapshot.has_value())
{
applicationSnapshot.StandardCalc = std::move(*standardCalculatorSnapshot);
}
else
{
return;
}
}
value = std::move(applicationSnapshot);
}
static void RestoreJsonToSnapshot(Windows::Data::Json::JsonObject ^ jsonObject, std::optional<StandardCalculatorSnapshot>& value)
{
StandardCalculatorSnapshot standardCalculatorSnapshot;
std::optional<CalculatorManagerSnapshot> calcManagerSnapshot;
RestoreJsonToSnapshot(jsonObject->GetNamedObject(L"CalculatorManagerSnapshot"), calcManagerSnapshot);
if (calcManagerSnapshot.has_value())
{
standardCalculatorSnapshot.CalcManager = std::move(*calcManagerSnapshot);
}
else
{
return;
}
std::optional<PrimaryDisplaySnapshot> primaryDisplaySnapshot;
RestoreJsonToSnapshot(jsonObject->GetNamedObject(L"PrimaryDisplay"), primaryDisplaySnapshot);
if (primaryDisplaySnapshot.has_value())
{
standardCalculatorSnapshot.PrimaryDisplay = std::move(*primaryDisplaySnapshot);
}
else
{
return;
}
if (jsonObject->HasKey(L"ExpressionDisplay"))
{
std::optional<ExpressionDisplaySnapshot> expressionDisplaySnapshot;
RestoreJsonToSnapshot(jsonObject->GetNamedObject(L"ExpressionDisplay"), expressionDisplaySnapshot);
if (expressionDisplaySnapshot.has_value())
{
standardCalculatorSnapshot.ExpressionDisplay = std::move(*expressionDisplaySnapshot);
}
else
{
return;
}
}
value = std::move(standardCalculatorSnapshot);
}
static void RestoreJsonToSnapshot(Windows::Data::Json::JsonObject ^ jsonObject, std::optional<PrimaryDisplaySnapshot>& value)
{
value = PrimaryDisplaySnapshot{ jsonObject->GetNamedString(L"DisplayValue"), jsonObject->GetNamedBoolean(L"IsError") };
}
static void RestoreJsonToSnapshot(Windows::Data::Json::JsonObject ^ jsonObject, std::optional<ExpressionDisplaySnapshot>& value)
{
ExpressionDisplaySnapshot expressionDisplaySnapshot;
expressionDisplaySnapshot.Tokens = RestoreExpressionTokensFromJsonArray(jsonObject->GetNamedArray(L"Tokens"));
if (expressionDisplaySnapshot.Tokens.empty())
{
return;
}
expressionDisplaySnapshot.Commands = RestoreExpressionCommandsFromJsonArray(jsonObject->GetNamedArray(L"Commands"));
if (expressionDisplaySnapshot.Commands.empty())
{
return;
}
value = std::move(expressionDisplaySnapshot);
}
static void RestoreJsonToSnapshot(Windows::Data::Json::JsonObject ^ jsonObject, std::optional<CalculatorManagerSnapshot>& value)
{
CalculatorManagerSnapshot calcManagerSnapshot;
if (jsonObject->HasKey(L"HistoryItems"))
{
std::vector<std::shared_ptr<CalculationManager::HISTORYITEM>> historyItems;
auto historyJsonArray = jsonObject->GetNamedArray(L"HistoryItems");
for (uint32_t i = 0; i < historyJsonArray->Size; ++i)
{
std::optional<CalculationManager::HISTORYITEM> historyItem;
RestoreJsonToSnapshot(historyJsonArray->GetObjectAt(i), historyItem);
if (historyItem.has_value())
{
historyItems.push_back(std::make_shared<CalculationManager::HISTORYITEM>(*historyItem));
}
else
{
return;
}
}
calcManagerSnapshot.HistoryItems = std::move(historyItems);
}
value = std::move(calcManagerSnapshot);
}
static void RestoreJsonToSnapshot(Windows::Data::Json::JsonObject ^ jsonObject, std::optional<CalculationManager::HISTORYITEM>& value)
{
CalculationManager::HISTORYITEM historyItem;
historyItem.historyItemVector.expression = std::wstring(jsonObject->GetNamedString(L"Expression")->Data());
historyItem.historyItemVector.result = std::wstring(jsonObject->GetNamedString(L"Result")->Data());
historyItem.historyItemVector.spTokens =
std::make_shared<std::vector<std::pair<std::wstring, int>>>(RestoreExpressionTokensFromJsonArray(jsonObject->GetNamedArray(L"Tokens")));
if (historyItem.historyItemVector.spTokens->empty())
{
return;
}
historyItem.historyItemVector.spCommands = std::make_shared<std::vector<std::shared_ptr<IExpressionCommand>>>(
RestoreExpressionCommandsFromJsonArray(jsonObject->GetNamedArray(L"Commands")));
if (historyItem.historyItemVector.spCommands->empty())
{
return;
}
value = std::move(historyItem);
}
static void RestoreJsonToSnapshot(Windows::Data::Json::JsonObject ^ jsonObject, std::optional<std::shared_ptr<IExpressionCommand>>& value)
{
auto commandType = static_cast<CalculationManager::CommandType>(jsonObject->GetNamedNumber(L"CommandType"));
switch (commandType)
{
case CalculationManager::CommandType::OperandCommand:
{
std::optional<COpndCommand> opndCommand;
RestoreJsonToSnapshot(jsonObject, opndCommand);
if (opndCommand.has_value())
{
value = std::make_shared<COpndCommand>(*opndCommand);
}
break;
}
case CalculationManager::CommandType::UnaryCommand:
{
std::optional<CUnaryCommand> unaryCommand;
RestoreJsonToSnapshot(jsonObject, unaryCommand);
if (unaryCommand.has_value())
{
value = std::make_shared<CUnaryCommand>(*unaryCommand);
}
break;
}
case CalculationManager::CommandType::BinaryCommand:
{
std::optional<CBinaryCommand> binaryCommand;
RestoreJsonToSnapshot(jsonObject, binaryCommand);
if (binaryCommand.has_value())
{
value = std::make_shared<CBinaryCommand>(*binaryCommand);
}
break;
}
case CalculationManager::CommandType::Parentheses:
{
std::optional<CParentheses> parenthesesCommand;
RestoreJsonToSnapshot(jsonObject, parenthesesCommand);
if (parenthesesCommand.has_value())
{
value = std::make_shared<CParentheses>(*parenthesesCommand);
}
break;
}
default:
throw std::logic_error{ "c8cba597-dfec-447a-bd1c-e78a9ffaad95" };
}
}
static void RestoreJsonToSnapshot(Windows::Data::Json::JsonObject ^ jsonObject, std::optional<COpndCommand>& value)
{
auto isNegative = jsonObject->GetNamedBoolean(L"IsNegative");
auto isDecimalPresent = jsonObject->GetNamedBoolean(L"IsDecimalPresent");
auto isSciFmt = jsonObject->GetNamedBoolean(L"IsSciFmt");
std::vector<int> commands;
auto commandsJsonArray = jsonObject->GetNamedArray(L"Commands");
for (uint32_t i = 0; i < commandsJsonArray->Size; ++i)
{
commands.push_back(static_cast<int>(commandsJsonArray->GetNumberAt(i)));
}
value = COpndCommand(std::make_shared<std::vector<int>>(std::move(commands)), isNegative, isDecimalPresent, isSciFmt);
}
static void RestoreJsonToSnapshot(Windows::Data::Json::JsonObject ^ jsonObject, std::optional<CUnaryCommand>& value)
{
std::vector<int> commands;
auto commandsJsonArray = jsonObject->GetNamedArray(L"Commands");
if (commandsJsonArray->Size == 1)
{
value = CUnaryCommand(static_cast<int>(commandsJsonArray->GetNumberAt(0)));
}
else if (commandsJsonArray->Size == 2)
{
value = CUnaryCommand(static_cast<int>(commandsJsonArray->GetNumberAt(0)), static_cast<int>(commandsJsonArray->GetNumberAt(1)));
}
}
static void RestoreJsonToSnapshot(Windows::Data::Json::JsonObject ^ jsonObject, std::optional<CBinaryCommand>& value)
{
value = CBinaryCommand(static_cast<int>(jsonObject->GetNamedNumber(L"Command")));
}
static void RestoreJsonToSnapshot(Windows::Data::Json::JsonObject ^ jsonObject, std::optional<CParentheses>& value)
{
value = CParentheses(static_cast<int>(jsonObject->GetNamedNumber(L"Command")));
}
static std::vector<std::pair<std::wstring, int>> RestoreExpressionTokensFromJsonArray(Windows::Data::Json::JsonArray ^ jsonArray)
{
std::vector<std::pair<std::wstring, int>> tokens;
for (uint32_t i = 0; i < jsonArray->Size; ++i)
{
auto tokenJsonArray = jsonArray->GetArrayAt(i);
if (tokenJsonArray->Size == 2 && tokenJsonArray->GetAt(0)->ValueType == Windows::Data::Json::JsonValueType::String
&& tokenJsonArray->GetAt(1)->ValueType == Windows::Data::Json::JsonValueType::Number)
{
tokens.emplace_back(std::wstring(tokenJsonArray->GetAt(0)->GetString()->Data()), static_cast<int>(tokenJsonArray->GetAt(1)->GetNumber()));
}
else
{
return {};
}
}
return tokens;
}
static std::vector<std::shared_ptr<IExpressionCommand>> RestoreExpressionCommandsFromJsonArray(Windows::Data::Json::JsonArray ^ jsonArray)
{
std::vector<std::shared_ptr<IExpressionCommand>> commands;
for (uint32_t i = 0; i < jsonArray->Size; ++i)
{
std::optional<std::shared_ptr<IExpressionCommand>> command;
RestoreJsonToSnapshot(jsonArray->GetObjectAt(i), command);
if (command.has_value())
{
commands.push_back(*command);
}
else
{
return {};
}
}
return commands;
}
static bool IsJsonParsingException(Platform::COMException ^ e)
{
return e->HResult == WEB_E_JSON_VALUE_NOT_FOUND || e->HResult == E_ILLEGAL_METHOD_CALL;
}
};
}
ApplicationViewModel::ApplicationViewModel()
@ -151,9 +583,9 @@ void ApplicationViewModel::OnModeChanged()
{
if (!m_ConverterViewModel)
{
auto dataLoader = make_shared<UnitConverterDataLoader>(ref new GeographicRegion());
auto currencyDataLoader = make_shared<CurrencyDataLoader>(make_unique<CurrencyHttpClient>());
m_ConverterViewModel = ref new UnitConverterViewModel(make_shared<UnitConversionManager::UnitConverter>(dataLoader, currencyDataLoader));
auto dataLoader = std::make_shared<UnitConverterDataLoader>(ref new GeographicRegion());
auto currencyDataLoader = std::make_shared<CurrencyDataLoader>(std::make_unique<CurrencyHttpClient>());
m_ConverterViewModel = ref new UnitConverterViewModel(std::make_shared<UnitConversionManager::UnitConverter>(dataLoader, currencyDataLoader));
}
m_ConverterViewModel->Mode = m_mode;
@ -272,3 +704,47 @@ void ApplicationViewModel::SetDisplayNormalAlwaysOnTopOption()
DisplayNormalAlwaysOnTopOption =
m_mode == ViewMode::Standard && ApplicationView::GetForCurrentView()->IsViewModeSupported(ApplicationViewMode::CompactOverlay) && !IsAlwaysOnTop;
}
Windows::Data::Json::JsonObject ^ ApplicationViewModel::SaveApplicationSnapshot()
{
ApplicationSnapshot applicationSnapshot;
applicationSnapshot.SnapshotVersion = SnapshotHelper::SnapshotVersion;
applicationSnapshot.Mode = static_cast<int>(Mode);
if (NavCategory::IsCalculatorViewMode(m_mode) && m_CalculatorViewModel != nullptr)
{
applicationSnapshot.StandardCalc = m_CalculatorViewModel->GetStandardCalculatorSnapshot();
}
return SnapshotHelper::SaveSnapshotToJson(applicationSnapshot);
}
bool ApplicationViewModel::TryRestoreFromSnapshot(Windows::Data::Json::JsonObject ^ jsonObject)
{
std::optional<ApplicationSnapshot> applicationSnapshot;
try
{
SnapshotHelper::RestoreJsonToSnapshot(jsonObject, applicationSnapshot);
}
catch (Platform::COMException ^ e)
{
if (SnapshotHelper::IsJsonParsingException(e))
{
return false;
}
throw;
}
if (applicationSnapshot.has_value())
{
Mode = static_cast<ViewMode>(applicationSnapshot->Mode);
if (applicationSnapshot->StandardCalc.has_value())
{
if (m_CalculatorViewModel == nullptr)
{
m_CalculatorViewModel = ref new StandardCalculatorViewModel();
}
m_CalculatorViewModel->SetStandardCalculatorSnapshot(applicationSnapshot->StandardCalc.value());
}
return true;
}
return false;
}

View file

@ -12,6 +12,13 @@ namespace CalculatorApp
{
namespace ViewModel
{
struct ApplicationSnapshot
{
int SnapshotVersion;
int Mode;
std::optional<StandardCalculatorSnapshot> StandardCalc;
};
[Windows::UI::Xaml::Data::Bindable] public ref class ApplicationViewModel sealed : public Windows::UI::Xaml::Data::INotifyPropertyChanged
{
public:
@ -96,6 +103,9 @@ namespace CalculatorApp
void ToggleAlwaysOnTop(float width, float height);
Windows::Data::Json::JsonObject ^ SaveApplicationSnapshot();
bool TryRestoreFromSnapshot(Windows::Data::Json::JsonObject ^ jsonObject);
private:
bool TryRecoverFromNavigationModeFailure();

View file

@ -1782,3 +1782,34 @@ void StandardCalculatorViewModel::SetBitshiftRadioButtonCheckedAnnouncement(Plat
{
Announcement = CalculatorAnnouncement::GetBitShiftRadioButtonCheckedAnnouncement(announcement);
}
StandardCalculatorSnapshot StandardCalculatorViewModel::GetStandardCalculatorSnapshot() const
{
StandardCalculatorSnapshot snapshot;
auto historyItems = m_standardCalculatorManager.GetHistoryItems();
if (!historyItems.empty())
{
snapshot.CalcManager.HistoryItems = std::move(historyItems);
}
snapshot.PrimaryDisplay = PrimaryDisplaySnapshot{ m_DisplayValue, m_IsInError };
if (!m_tokens->empty() && !m_commands->empty())
{
snapshot.ExpressionDisplay = { *m_tokens, *m_commands };
}
return snapshot;
}
void StandardCalculatorViewModel::SetStandardCalculatorSnapshot(const StandardCalculatorSnapshot& snapshot)
{
if (snapshot.CalcManager.HistoryItems.has_value())
{
m_standardCalculatorManager.SetHistoryItems(snapshot.CalcManager.HistoryItems.value());
}
SetPrimaryDisplay(snapshot.PrimaryDisplay.DisplayValue, snapshot.PrimaryDisplay.IsError);
if (snapshot.ExpressionDisplay.has_value())
{
SetExpressionDisplay(
std::make_shared<std::vector<std::pair<std::wstring, int>>>(snapshot.ExpressionDisplay->Tokens),
std::make_shared<std::vector<std::shared_ptr<IExpressionCommand>>>(snapshot.ExpressionDisplay->Commands));
}
}

View file

@ -33,6 +33,30 @@ namespace CalculatorApp
bool canSendNegate;
};
struct CalculatorManagerSnapshot
{
std::optional<std::vector<std::shared_ptr<CalculationManager::HISTORYITEM>>> HistoryItems;
};
struct PrimaryDisplaySnapshot
{
Platform::String ^ DisplayValue;
bool IsError = false;
};
struct ExpressionDisplaySnapshot
{
std::vector<std::pair<std::wstring, int>> Tokens;
std::vector<std::shared_ptr<IExpressionCommand>> Commands;
};
struct StandardCalculatorSnapshot
{
CalculatorManagerSnapshot CalcManager;
PrimaryDisplaySnapshot PrimaryDisplay;
std::optional<ExpressionDisplaySnapshot> ExpressionDisplay;
};
[Windows::UI::Xaml::Data::Bindable] public ref class StandardCalculatorViewModel sealed : public Windows::UI::Xaml::Data::INotifyPropertyChanged
{
public:
@ -294,6 +318,9 @@ namespace CalculatorApp
{
return m_CurrentAngleType;
}
StandardCalculatorSnapshot GetStandardCalculatorSnapshot() const;
void SetStandardCalculatorSnapshot(const StandardCalculatorSnapshot& state);
private:
void SetMemorizedNumbers(const std::vector<std::wstring>& memorizedNumbers);

View file

@ -45,8 +45,7 @@ namespace CalculatorApp
if (string.IsNullOrEmpty(activity.ActivityId) ||
activity.ActivationUri == null ||
activity.ActivationUri.AbsolutePath != "/snapshot" ||
string.IsNullOrEmpty(activity.ActivationUri.Query) ||
activity.ContentInfo == null)
string.IsNullOrEmpty(activity.ActivationUri.Query))
{
return false;
}

View file

@ -67,9 +67,7 @@ namespace CalculatorApp
var channel = UserActivityChannel.GetDefault();
var activity = await channel.GetOrCreateUserActivityAsync($"{Guid.NewGuid()}");
activity.ActivationUri = new Uri($"ms-calculator:///snapshot?activityId={activity.ActivityId}");
var snapshot = "{}"; // TODO: serialize the current snapshot into a JSON representation string.
activity.ContentInfo = UserActivityContentInfo.FromJson(snapshot);
activity.ContentInfo = UserActivityContentInfo.FromJson(Model.SaveApplicationSnapshot().Stringify());
var resProvider = AppResourceProvider.GetInstance();
activity.VisualElements.DisplayText =
@ -176,9 +174,27 @@ namespace CalculatorApp
}
else
{
if (JsonObject.TryParse(activity.ContentInfo.ToJson(), out var jsonModel))
if (JsonObject.TryParse(activity.ToJson(), out var activityJson))
{
// TODO: try restore the model from jsonModel
try
{
// Work around for bug https://microsoft.visualstudio.com/DefaultCollection/OS/_workitems/edit/48931227 where ContentInfo can't be directly accessed.
var contentJson = activityJson.GetNamedObject("contentInfo");
if (Model.TryRestoreFromSnapshot(contentJson))
{
SelectNavigationItemByModel();
}
else
{
// TODO: show error dialog
return;
}
}
catch (Exception ex)
{
// TODO: show error dialog
return;
}
}
else
{