From e2fd25fe945038a696c78425e8baf490cfd4e178 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Mon, 21 Oct 2019 12:28:30 -0700 Subject: [PATCH 01/19] [UWP] Add accessible names to all action buttons (#3528) Fixes #3527 --- source/uwp/Renderer/lib/ActionHelpers.cpp | 1020 +++++++++++++++++++++ 1 file changed, 1020 insertions(+) create mode 100644 source/uwp/Renderer/lib/ActionHelpers.cpp diff --git a/source/uwp/Renderer/lib/ActionHelpers.cpp b/source/uwp/Renderer/lib/ActionHelpers.cpp new file mode 100644 index 0000000000..630144f6d6 --- /dev/null +++ b/source/uwp/Renderer/lib/ActionHelpers.cpp @@ -0,0 +1,1020 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +#include "pch.h" + +#include "ActionHelpers.h" +#include "AdaptiveImage.h" +#include "AdaptiveRenderArgs.h" +#include "AdaptiveShowCardActionRenderer.h" + +using namespace Microsoft::WRL; +using namespace Microsoft::WRL::Wrappers; +using namespace ABI::AdaptiveNamespace; +using namespace ABI::Windows::Foundation; +using namespace ABI::Windows::Foundation::Collections; +using namespace ABI::Windows::UI::Xaml; +using namespace ABI::Windows::UI::Xaml::Automation; +using namespace ABI::Windows::UI::Xaml::Controls; +using namespace ABI::Windows::UI::Xaml::Controls::Primitives; +using namespace ABI::Windows::UI::Xaml::Input; +using namespace ABI::Windows::UI::Xaml::Media; + +namespace AdaptiveNamespace::ActionHelpers +{ + HRESULT GetButtonMargin(_In_ IAdaptiveActionsConfig* actionsConfig, Thickness& buttonMargin) noexcept + { + buttonMargin = {0, 0, 0, 0}; + UINT32 buttonSpacing; + RETURN_IF_FAILED(actionsConfig->get_ButtonSpacing(&buttonSpacing)); + + ABI::AdaptiveNamespace::ActionsOrientation actionsOrientation; + RETURN_IF_FAILED(actionsConfig->get_ActionsOrientation(&actionsOrientation)); + + if (actionsOrientation == ABI::AdaptiveNamespace::ActionsOrientation::Horizontal) + { + buttonMargin.Left = buttonMargin.Right = buttonSpacing / 2; + } + else + { + buttonMargin.Top = buttonMargin.Bottom = buttonSpacing / 2; + } + + return S_OK; + } + + void ArrangeButtonContent(_In_ IAdaptiveActionElement* action, + _In_ IAdaptiveActionsConfig* actionsConfig, + _In_ IAdaptiveRenderContext* renderContext, + ABI::AdaptiveNamespace::ContainerStyle containerStyle, + _In_ ABI::AdaptiveNamespace::IAdaptiveHostConfig* hostConfig, + bool allActionsHaveIcons, + _In_ IButton* button) + { + HString title; + THROW_IF_FAILED(action->get_Title(title.GetAddressOf())); + + HString iconUrl; + THROW_IF_FAILED(action->get_IconUrl(iconUrl.GetAddressOf())); + + ComPtr localButton(button); + ComPtr automationProperties; + THROW_IF_FAILED(GetActivationFactory( + HStringReference(RuntimeClass_Windows_UI_Xaml_Automation_AutomationProperties).Get(), &automationProperties)); + ComPtr buttonAsDependencyObject; + THROW_IF_FAILED(localButton.As(&buttonAsDependencyObject)); + THROW_IF_FAILED(automationProperties->SetName(buttonAsDependencyObject.Get(), title.Get())); + + // Check if the button has an iconUrl + if (iconUrl != nullptr) + { + // Get icon configs + ABI::AdaptiveNamespace::IconPlacement iconPlacement; + UINT32 iconSize; + + THROW_IF_FAILED(actionsConfig->get_IconPlacement(&iconPlacement)); + THROW_IF_FAILED(actionsConfig->get_IconSize(&iconSize)); + + // Define the alignment for the button contents + ComPtr buttonContentsStackPanel = + XamlHelpers::CreateXamlClass(HStringReference(RuntimeClass_Windows_UI_Xaml_Controls_StackPanel)); + + // Create image and add it to the button + ComPtr adaptiveImage; + THROW_IF_FAILED(MakeAndInitialize(&adaptiveImage)); + + THROW_IF_FAILED(adaptiveImage->put_Url(iconUrl.Get())); + THROW_IF_FAILED(adaptiveImage->put_HorizontalAlignment(ABI::AdaptiveNamespace::HAlignment::Center)); + + ComPtr adaptiveCardElement; + THROW_IF_FAILED(adaptiveImage.As(&adaptiveCardElement)); + ComPtr childRenderArgs; + THROW_IF_FAILED( + MakeAndInitialize(&childRenderArgs, containerStyle, buttonContentsStackPanel.Get(), nullptr)); + + ComPtr elementRenderers; + THROW_IF_FAILED(renderContext->get_ElementRenderers(&elementRenderers)); + + ComPtr buttonIcon; + ComPtr elementRenderer; + THROW_IF_FAILED(elementRenderers->Get(HStringReference(L"Image").Get(), &elementRenderer)); + if (elementRenderer != nullptr) + { + elementRenderer->Render(adaptiveCardElement.Get(), renderContext, childRenderArgs.Get(), &buttonIcon); + if (buttonIcon == nullptr) + { + XamlHelpers::SetContent(localButton.Get(), title.Get()); + return; + } + } + + // Create title text block + ComPtr buttonText = + XamlHelpers::CreateXamlClass(HStringReference(RuntimeClass_Windows_UI_Xaml_Controls_TextBlock)); + THROW_IF_FAILED(buttonText->put_Text(title.Get())); + THROW_IF_FAILED(buttonText->put_TextAlignment(TextAlignment::TextAlignment_Center)); + + // Handle different arrangements inside button + ComPtr buttonIconAsFrameworkElement; + THROW_IF_FAILED(buttonIcon.As(&buttonIconAsFrameworkElement)); + ComPtr separator; + if (iconPlacement == ABI::AdaptiveNamespace::IconPlacement::AboveTitle && allActionsHaveIcons) + { + THROW_IF_FAILED(buttonContentsStackPanel->put_Orientation(Orientation::Orientation_Vertical)); + + // Set icon height to iconSize (aspect ratio is automatically maintained) + THROW_IF_FAILED(buttonIconAsFrameworkElement->put_Height(iconSize)); + } + else + { + THROW_IF_FAILED(buttonContentsStackPanel->put_Orientation(Orientation::Orientation_Horizontal)); + + // Add event to the image to resize itself when the textblock is rendered + ComPtr buttonIconAsImage; + THROW_IF_FAILED(buttonIcon.As(&buttonIconAsImage)); + + EventRegistrationToken eventToken; + THROW_IF_FAILED(buttonIconAsImage->add_ImageOpened( + Callback([buttonIconAsFrameworkElement, + buttonText](IInspectable* /*sender*/, IRoutedEventArgs * /*args*/) -> HRESULT { + ComPtr buttonTextAsFrameworkElement; + RETURN_IF_FAILED(buttonText.As(&buttonTextAsFrameworkElement)); + + return SetMatchingHeight(buttonIconAsFrameworkElement.Get(), buttonTextAsFrameworkElement.Get()); + }).Get(), + &eventToken)); + + // Only add spacing when the icon must be located at the left of the title + UINT spacingSize; + THROW_IF_FAILED(GetSpacingSizeFromSpacing(hostConfig, ABI::AdaptiveNamespace::Spacing::Default, &spacingSize)); + + ABI::Windows::UI::Color color = {0}; + separator = XamlHelpers::CreateSeparator(renderContext, spacingSize, spacingSize, color, false); + } + + ComPtr buttonContentsPanel; + THROW_IF_FAILED(buttonContentsStackPanel.As(&buttonContentsPanel)); + + // Add image to stack panel + XamlHelpers::AppendXamlElementToPanel(buttonIcon.Get(), buttonContentsPanel.Get()); + + // Add separator to stack panel + if (separator != nullptr) + { + XamlHelpers::AppendXamlElementToPanel(separator.Get(), buttonContentsPanel.Get()); + } + + // Add text to stack panel + XamlHelpers::AppendXamlElementToPanel(buttonText.Get(), buttonContentsPanel.Get()); + + // Finally, put the stack panel inside the final button + ComPtr buttonContentControl; + THROW_IF_FAILED(localButton.As(&buttonContentControl)); + THROW_IF_FAILED(buttonContentControl->put_Content(buttonContentsPanel.Get())); + } + else + { + XamlHelpers::SetContent(localButton.Get(), title.Get()); + } + } + + HRESULT HandleActionStyling(_In_ IAdaptiveActionElement* adaptiveActionElement, + _In_ IFrameworkElement* buttonFrameworkElement, + _In_ IAdaptiveRenderContext* renderContext) + { + HString actionSentiment; + RETURN_IF_FAILED(adaptiveActionElement->get_Style(actionSentiment.GetAddressOf())); + + INT32 isSentimentPositive{}, isSentimentDestructive{}, isSentimentDefault{}; + + ComPtr resourceDictionary; + RETURN_IF_FAILED(renderContext->get_OverrideStyles(&resourceDictionary)); + ComPtr styleToApply; + + ComPtr contextImpl = + PeekInnards(renderContext); + + if ((SUCCEEDED(WindowsCompareStringOrdinal(actionSentiment.Get(), HStringReference(L"default").Get(), &isSentimentDefault)) && + (isSentimentDefault == 0)) || + WindowsIsStringEmpty(actionSentiment.Get())) + { + RETURN_IF_FAILED(XamlHelpers::SetStyleFromResourceDictionary(renderContext, L"Adaptive.Action", buttonFrameworkElement)); + } + else if (SUCCEEDED(WindowsCompareStringOrdinal(actionSentiment.Get(), HStringReference(L"positive").Get(), &isSentimentPositive)) && + (isSentimentPositive == 0)) + { + if (SUCCEEDED(XamlHelpers::TryGetResourceFromResourceDictionaries(resourceDictionary.Get(), + L"Adaptive.Action.Positive", + &styleToApply))) + { + RETURN_IF_FAILED(buttonFrameworkElement->put_Style(styleToApply.Get())); + } + else + { + // By default, set the action background color to accent color + ComPtr actionSentimentDictionary = contextImpl->GetDefaultActionSentimentDictionary(); + + if (SUCCEEDED(XamlHelpers::TryGetResourceFromResourceDictionaries(actionSentimentDictionary.Get(), + L"PositiveActionDefaultStyle", + styleToApply.GetAddressOf()))) + { + RETURN_IF_FAILED(buttonFrameworkElement->put_Style(styleToApply.Get())); + } + } + } + else if (SUCCEEDED(WindowsCompareStringOrdinal(actionSentiment.Get(), HStringReference(L"destructive").Get(), &isSentimentDestructive)) && + (isSentimentDestructive == 0)) + { + if (SUCCEEDED(XamlHelpers::TryGetResourceFromResourceDictionaries(resourceDictionary.Get(), + L"Adaptive.Action.Destructive", + &styleToApply))) + { + RETURN_IF_FAILED(buttonFrameworkElement->put_Style(styleToApply.Get())); + } + else + { + // By default, set the action text color to attention color + ComPtr actionSentimentDictionary = contextImpl->GetDefaultActionSentimentDictionary(); + + if (SUCCEEDED(XamlHelpers::TryGetResourceFromResourceDictionaries(actionSentimentDictionary.Get(), + L"DestructiveActionDefaultStyle", + styleToApply.GetAddressOf()))) + { + RETURN_IF_FAILED(buttonFrameworkElement->put_Style(styleToApply.Get())); + } + } + } + else + { + HString actionSentimentStyle; + RETURN_IF_FAILED(WindowsConcatString(HStringReference(L"Adaptive.Action.").Get(), + actionSentiment.Get(), + actionSentimentStyle.GetAddressOf())); + RETURN_IF_FAILED(XamlHelpers::SetStyleFromResourceDictionary( + renderContext, StringToWstring(HStringToUTF8(actionSentimentStyle.Get())), buttonFrameworkElement)); + } + return S_OK; + } + + HRESULT SetMatchingHeight(_In_ IFrameworkElement* elementToChange, _In_ IFrameworkElement* elementToMatch) + { + DOUBLE actualHeight; + RETURN_IF_FAILED(elementToMatch->get_ActualHeight(&actualHeight)); + + ComPtr localElement(elementToChange); + RETURN_IF_FAILED(localElement->put_Height(actualHeight)); + + ComPtr frameworkElementAsUIElement; + RETURN_IF_FAILED(localElement.As(&frameworkElementAsUIElement)); + RETURN_IF_FAILED(frameworkElementAsUIElement->put_Visibility(Visibility::Visibility_Visible)); + return S_OK; + } + + HRESULT BuildAction(_In_ IAdaptiveActionElement* adaptiveActionElement, + _In_ IAdaptiveRenderContext* renderContext, + _In_ IAdaptiveRenderArgs* renderArgs, + _Outptr_ IUIElement** actionControl) + { + // Render a button for the action + ComPtr action(adaptiveActionElement); + ComPtr button = + XamlHelpers::CreateXamlClass(HStringReference(RuntimeClass_Windows_UI_Xaml_Controls_Button)); + + ComPtr buttonFrameworkElement; + RETURN_IF_FAILED(button.As(&buttonFrameworkElement)); + + ComPtr hostConfig; + RETURN_IF_FAILED(renderContext->get_HostConfig(&hostConfig)); + ComPtr actionsConfig; + RETURN_IF_FAILED(hostConfig->get_Actions(actionsConfig.GetAddressOf())); + + Thickness buttonMargin; + RETURN_IF_FAILED(GetButtonMargin(actionsConfig.Get(), buttonMargin)); + RETURN_IF_FAILED(buttonFrameworkElement->put_Margin(buttonMargin)); + + ABI::AdaptiveNamespace::ActionsOrientation actionsOrientation; + RETURN_IF_FAILED(actionsConfig->get_ActionsOrientation(&actionsOrientation)); + + ABI::AdaptiveNamespace::ActionAlignment actionAlignment; + RETURN_IF_FAILED(actionsConfig->get_ActionAlignment(&actionAlignment)); + + if (actionsOrientation == ABI::AdaptiveNamespace::ActionsOrientation::Horizontal) + { + // For horizontal alignment, we always use stretch + RETURN_IF_FAILED(buttonFrameworkElement->put_HorizontalAlignment( + ABI::Windows::UI::Xaml::HorizontalAlignment::HorizontalAlignment_Stretch)); + } + else + { + switch (actionAlignment) + { + case ABI::AdaptiveNamespace::ActionAlignment::Center: + RETURN_IF_FAILED(buttonFrameworkElement->put_HorizontalAlignment(ABI::Windows::UI::Xaml::HorizontalAlignment_Center)); + break; + case ABI::AdaptiveNamespace::ActionAlignment::Left: + RETURN_IF_FAILED(buttonFrameworkElement->put_HorizontalAlignment(ABI::Windows::UI::Xaml::HorizontalAlignment_Left)); + break; + case ABI::AdaptiveNamespace::ActionAlignment::Right: + RETURN_IF_FAILED(buttonFrameworkElement->put_HorizontalAlignment(ABI::Windows::UI::Xaml::HorizontalAlignment_Right)); + break; + case ABI::AdaptiveNamespace::ActionAlignment::Stretch: + RETURN_IF_FAILED(buttonFrameworkElement->put_HorizontalAlignment(ABI::Windows::UI::Xaml::HorizontalAlignment_Stretch)); + break; + } + } + + ABI::AdaptiveNamespace::ContainerStyle containerStyle; + RETURN_IF_FAILED(renderArgs->get_ContainerStyle(&containerStyle)); + + boolean allowAboveTitleIconPlacement; + RETURN_IF_FAILED(renderArgs->get_AllowAboveTitleIconPlacement(&allowAboveTitleIconPlacement)); + + ArrangeButtonContent(action.Get(), + actionsConfig.Get(), + renderContext, + containerStyle, + hostConfig.Get(), + allowAboveTitleIconPlacement, + button.Get()); + + ABI::AdaptiveNamespace::ActionType actionType; + RETURN_IF_FAILED(action->get_ActionType(&actionType)); + + ComPtr showCardActionConfig; + RETURN_IF_FAILED(actionsConfig->get_ShowCard(&showCardActionConfig)); + ABI::AdaptiveNamespace::ActionMode showCardActionMode; + RETURN_IF_FAILED(showCardActionConfig->get_ActionMode(&showCardActionMode)); + std::shared_ptr>> allShowCards = std::make_shared>>(); + + // Add click handler which calls IAdaptiveActionInvoker::SendActionEvent + ComPtr buttonBase; + RETURN_IF_FAILED(button.As(&buttonBase)); + + ComPtr actionInvoker; + RETURN_IF_FAILED(renderContext->get_ActionInvoker(&actionInvoker)); + EventRegistrationToken clickToken; + RETURN_IF_FAILED(buttonBase->add_Click(Callback([action, actionInvoker](IInspectable* /*sender*/, IRoutedEventArgs * + /*args*/) -> HRESULT { + return actionInvoker->SendActionEvent(action.Get()); + }).Get(), + &clickToken)); + + RETURN_IF_FAILED(HandleActionStyling(adaptiveActionElement, buttonFrameworkElement.Get(), renderContext)); + + ComPtr buttonAsUIElement; + RETURN_IF_FAILED(button.As(&buttonAsUIElement)); + *actionControl = buttonAsUIElement.Detach(); + return S_OK; + } + + bool WarnForInlineShowCard(_In_ IAdaptiveRenderContext* renderContext, _In_ IAdaptiveActionElement* action, const std::wstring& warning) + { + if (action != nullptr) + { + ABI::AdaptiveNamespace::ActionType actionType; + THROW_IF_FAILED(action->get_ActionType(&actionType)); + + if (actionType == ABI::AdaptiveNamespace::ActionType::ShowCard) + { + THROW_IF_FAILED(renderContext->AddWarning(ABI::AdaptiveNamespace::WarningStatusCode::UnsupportedValue, + HStringReference(warning.c_str()).Get())); + return true; + } + } + + return false; + } + + static HRESULT HandleKeydownForInlineAction(_In_ IKeyRoutedEventArgs* args, + _In_ IAdaptiveActionInvoker* actionInvoker, + _In_ IAdaptiveActionElement* inlineAction) + { + ABI::Windows::System::VirtualKey key; + RETURN_IF_FAILED(args->get_Key(&key)); + + if (key == ABI::Windows::System::VirtualKey::VirtualKey_Enter) + { + ComPtr coreWindowStatics; + RETURN_IF_FAILED(GetActivationFactory(HStringReference(RuntimeClass_Windows_UI_Core_CoreWindow).Get(), &coreWindowStatics)); + + ComPtr coreWindow; + RETURN_IF_FAILED(coreWindowStatics->GetForCurrentThread(&coreWindow)); + + ABI::Windows::UI::Core::CoreVirtualKeyStates shiftKeyState; + RETURN_IF_FAILED(coreWindow->GetKeyState(ABI::Windows::System::VirtualKey_Shift, &shiftKeyState)); + + ABI::Windows::UI::Core::CoreVirtualKeyStates ctrlKeyState; + RETURN_IF_FAILED(coreWindow->GetKeyState(ABI::Windows::System::VirtualKey_Control, &ctrlKeyState)); + + if (shiftKeyState == ABI::Windows::UI::Core::CoreVirtualKeyStates_None && + ctrlKeyState == ABI::Windows::UI::Core::CoreVirtualKeyStates_None) + { + RETURN_IF_FAILED(actionInvoker->SendActionEvent(inlineAction)); + RETURN_IF_FAILED(args->put_Handled(true)); + } + } + + return S_OK; + } + + void HandleInlineAction(_In_ IAdaptiveRenderContext* renderContext, + _In_ IAdaptiveRenderArgs* renderArgs, + _In_ ITextBox* textBox, + _In_ IAdaptiveActionElement* inlineAction, + _COM_Outptr_ IUIElement** textBoxWithInlineAction) + { + ComPtr localTextBox(textBox); + ComPtr localInlineAction(inlineAction); + + ABI::AdaptiveNamespace::ActionType actionType; + THROW_IF_FAILED(localInlineAction->get_ActionType(&actionType)); + + ComPtr hostConfig; + THROW_IF_FAILED(renderContext->get_HostConfig(&hostConfig)); + + // Inline ShowCards are not supported for inline actions + if (WarnForInlineShowCard(renderContext, localInlineAction.Get(), L"Inline ShowCard not supported for InlineAction")) + { + THROW_IF_FAILED(localTextBox.CopyTo(textBoxWithInlineAction)); + return; + } + + // Create a grid to hold the text box and the action button + ComPtr gridStatics; + THROW_IF_FAILED(GetActivationFactory(HStringReference(RuntimeClass_Windows_UI_Xaml_Controls_Grid).Get(), &gridStatics)); + + ComPtr xamlGrid = + XamlHelpers::CreateXamlClass(HStringReference(RuntimeClass_Windows_UI_Xaml_Controls_Grid)); + ComPtr> columnDefinitions; + THROW_IF_FAILED(xamlGrid->get_ColumnDefinitions(&columnDefinitions)); + ComPtr gridAsPanel; + THROW_IF_FAILED(xamlGrid.As(&gridAsPanel)); + + // Create the first column and add the text box to it + ComPtr textBoxColumnDefinition = XamlHelpers::CreateXamlClass( + HStringReference(RuntimeClass_Windows_UI_Xaml_Controls_ColumnDefinition)); + THROW_IF_FAILED(textBoxColumnDefinition->put_Width({1, GridUnitType::GridUnitType_Star})); + THROW_IF_FAILED(columnDefinitions->Append(textBoxColumnDefinition.Get())); + + ComPtr textBoxAsFrameworkElement; + THROW_IF_FAILED(localTextBox.As(&textBoxAsFrameworkElement)); + + THROW_IF_FAILED(gridStatics->SetColumn(textBoxAsFrameworkElement.Get(), 0)); + XamlHelpers::AppendXamlElementToPanel(textBox, gridAsPanel.Get()); + + // Create a separator column + ComPtr separatorColumnDefinition = XamlHelpers::CreateXamlClass( + HStringReference(RuntimeClass_Windows_UI_Xaml_Controls_ColumnDefinition)); + THROW_IF_FAILED(separatorColumnDefinition->put_Width({1.0, GridUnitType::GridUnitType_Auto})); + THROW_IF_FAILED(columnDefinitions->Append(separatorColumnDefinition.Get())); + + UINT spacingSize; + THROW_IF_FAILED(GetSpacingSizeFromSpacing(hostConfig.Get(), ABI::AdaptiveNamespace::Spacing::Default, &spacingSize)); + + auto separator = XamlHelpers::CreateSeparator(renderContext, spacingSize, 0, {0}, false); + + ComPtr separatorAsFrameworkElement; + THROW_IF_FAILED(separator.As(&separatorAsFrameworkElement)); + + THROW_IF_FAILED(gridStatics->SetColumn(separatorAsFrameworkElement.Get(), 1)); + XamlHelpers::AppendXamlElementToPanel(separator.Get(), gridAsPanel.Get()); + + // Create a column for the button + ComPtr inlineActionColumnDefinition = XamlHelpers::CreateXamlClass( + HStringReference(RuntimeClass_Windows_UI_Xaml_Controls_ColumnDefinition)); + THROW_IF_FAILED(inlineActionColumnDefinition->put_Width({0, GridUnitType::GridUnitType_Auto})); + THROW_IF_FAILED(columnDefinitions->Append(inlineActionColumnDefinition.Get())); + + // Create a text box with the action title. This will be the tool tip if there's an icon + // or the content of the button otherwise + ComPtr titleTextBlock = + XamlHelpers::CreateXamlClass(HStringReference(RuntimeClass_Windows_UI_Xaml_Controls_TextBlock)); + HString title; + THROW_IF_FAILED(localInlineAction->get_Title(title.GetAddressOf())); + THROW_IF_FAILED(titleTextBlock->put_Text(title.Get())); + + HString iconUrl; + THROW_IF_FAILED(localInlineAction->get_IconUrl(iconUrl.GetAddressOf())); + ComPtr actionUIElement; + if (iconUrl != nullptr) + { + // Render the icon using the adaptive image renderer + ComPtr elementRenderers; + THROW_IF_FAILED(renderContext->get_ElementRenderers(&elementRenderers)); + ComPtr imageRenderer; + THROW_IF_FAILED(elementRenderers->Get(HStringReference(L"Image").Get(), &imageRenderer)); + + ComPtr adaptiveImage; + THROW_IF_FAILED(MakeAndInitialize(&adaptiveImage)); + + THROW_IF_FAILED(adaptiveImage->put_Url(iconUrl.Get())); + + ComPtr adaptiveImageAsElement; + THROW_IF_FAILED(adaptiveImage.As(&adaptiveImageAsElement)); + + THROW_IF_FAILED(imageRenderer->Render(adaptiveImageAsElement.Get(), renderContext, renderArgs, &actionUIElement)); + + // Add the tool tip + ComPtr toolTip = + XamlHelpers::CreateXamlClass(HStringReference(RuntimeClass_Windows_UI_Xaml_Controls_ToolTip)); + ComPtr toolTipAsContentControl; + THROW_IF_FAILED(toolTip.As(&toolTipAsContentControl)); + THROW_IF_FAILED(toolTipAsContentControl->put_Content(titleTextBlock.Get())); + + ComPtr toolTipService; + THROW_IF_FAILED(GetActivationFactory(HStringReference(RuntimeClass_Windows_UI_Xaml_Controls_ToolTipService).Get(), + &toolTipService)); + + ComPtr actionAsDependencyObject; + THROW_IF_FAILED(actionUIElement.As(&actionAsDependencyObject)); + + THROW_IF_FAILED(toolTipService->SetToolTip(actionAsDependencyObject.Get(), toolTip.Get())); + } + else + { + // If there's no icon, just use the title text. Put it centered in a grid so it is + // centered relative to the text box. + ComPtr textBlockAsFrameworkElement; + THROW_IF_FAILED(titleTextBlock.As(&textBlockAsFrameworkElement)); + THROW_IF_FAILED(textBlockAsFrameworkElement->put_VerticalAlignment(ABI::Windows::UI::Xaml::VerticalAlignment_Center)); + + ComPtr titleGrid = + XamlHelpers::CreateXamlClass(HStringReference(RuntimeClass_Windows_UI_Xaml_Controls_Grid)); + ComPtr panel; + THROW_IF_FAILED(titleGrid.As(&panel)); + XamlHelpers::AppendXamlElementToPanel(titleTextBlock.Get(), panel.Get()); + + THROW_IF_FAILED(panel.As(&actionUIElement)); + } + + // Make the action the same size as the text box + EventRegistrationToken eventToken; + THROW_IF_FAILED(textBoxAsFrameworkElement->add_Loaded( + Callback([actionUIElement, textBoxAsFrameworkElement](IInspectable* /*sender*/, IRoutedEventArgs * + /*args*/) -> HRESULT { + ComPtr actionFrameworkElement; + RETURN_IF_FAILED(actionUIElement.As(&actionFrameworkElement)); + + return ActionHelpers::SetMatchingHeight(actionFrameworkElement.Get(), textBoxAsFrameworkElement.Get()); + }).Get(), + &eventToken)); + + // Wrap the action in a button + ComPtr touchTargetUIElement; + WrapInTouchTarget(nullptr, actionUIElement.Get(), localInlineAction.Get(), renderContext, false, L"Adaptive.Input.Text.InlineAction", &touchTargetUIElement); + + ComPtr touchTargetFrameworkElement; + THROW_IF_FAILED(touchTargetUIElement.As(&touchTargetFrameworkElement)); + + // Align to bottom so the icon stays with the bottom of the text box as it grows in the multiline case + THROW_IF_FAILED(touchTargetFrameworkElement->put_VerticalAlignment(ABI::Windows::UI::Xaml::VerticalAlignment_Bottom)); + + // Add the action to the column + THROW_IF_FAILED(gridStatics->SetColumn(touchTargetFrameworkElement.Get(), 2)); + XamlHelpers::AppendXamlElementToPanel(touchTargetFrameworkElement.Get(), gridAsPanel.Get()); + + // If this isn't a multiline input, enter should invoke the action + ComPtr actionInvoker; + THROW_IF_FAILED(renderContext->get_ActionInvoker(&actionInvoker)); + + boolean isMultiLine; + THROW_IF_FAILED(textBox->get_AcceptsReturn(&isMultiLine)); + + if (!isMultiLine) + { + ComPtr textBoxAsUIElement; + THROW_IF_FAILED(localTextBox.As(&textBoxAsUIElement)); + + EventRegistrationToken keyDownEventToken; + THROW_IF_FAILED(textBoxAsUIElement->add_KeyDown( + Callback([actionInvoker, localInlineAction](IInspectable* /*sender*/, IKeyRoutedEventArgs* args) -> HRESULT { + return HandleKeydownForInlineAction(args, actionInvoker.Get(), localInlineAction.Get()); + }).Get(), + &keyDownEventToken)); + } + + THROW_IF_FAILED(xamlGrid.CopyTo(textBoxWithInlineAction)); + } + + void WrapInTouchTarget(_In_ IAdaptiveCardElement* adaptiveCardElement, + _In_ IUIElement* elementToWrap, + _In_ IAdaptiveActionElement* action, + _In_ IAdaptiveRenderContext* renderContext, + bool fullWidth, + const std::wstring& style, + _COM_Outptr_ IUIElement** finalElement) + { + ComPtr hostConfig; + THROW_IF_FAILED(renderContext->get_HostConfig(&hostConfig)); + + if (ActionHelpers::WarnForInlineShowCard(renderContext, action, L"Inline ShowCard not supported for SelectAction")) + { + // Was inline show card, so don't wrap the element and just return + ComPtr localElementToWrap(elementToWrap); + localElementToWrap.CopyTo(finalElement); + return; + } + + ComPtr button = + XamlHelpers::CreateXamlClass(HStringReference(RuntimeClass_Windows_UI_Xaml_Controls_Button)); + + ComPtr buttonAsContentControl; + THROW_IF_FAILED(button.As(&buttonAsContentControl)); + THROW_IF_FAILED(buttonAsContentControl->put_Content(elementToWrap)); + + ComPtr spacingConfig; + THROW_IF_FAILED(hostConfig->get_Spacing(&spacingConfig)); + + UINT32 cardPadding = 0; + if (fullWidth) + { + THROW_IF_FAILED(spacingConfig->get_Padding(&cardPadding)); + } + + ComPtr buttonAsFrameworkElement; + THROW_IF_FAILED(button.As(&buttonAsFrameworkElement)); + + // We want the hit target to equally split the vertical space above and below the current item. + // However, all we know is the spacing of the current item, which only applies to the spacing above. + // We don't know what the spacing of the NEXT element will be, so we can't calculate the correct spacing + // below. For now, we'll simply assume the bottom spacing is the same as the top. NOTE: Only apply spacings + // (padding, margin) for adaptive card elements to avoid adding spacings to card-level selectAction. + if (adaptiveCardElement != nullptr) + { + ABI::AdaptiveNamespace::Spacing elementSpacing; + THROW_IF_FAILED(adaptiveCardElement->get_Spacing(&elementSpacing)); + UINT spacingSize; + THROW_IF_FAILED(GetSpacingSizeFromSpacing(hostConfig.Get(), elementSpacing, &spacingSize)); + double topBottomPadding = spacingSize / 2.0; + + // For button padding, we apply the cardPadding and topBottomPadding (and then we negate these in the margin) + ComPtr buttonAsControl; + THROW_IF_FAILED(button.As(&buttonAsControl)); + THROW_IF_FAILED(buttonAsControl->put_Padding({(double)cardPadding, topBottomPadding, (double)cardPadding, topBottomPadding})); + + double negativeCardMargin = cardPadding * -1.0; + double negativeTopBottomMargin = topBottomPadding * -1.0; + + THROW_IF_FAILED(buttonAsFrameworkElement->put_Margin( + {negativeCardMargin, negativeTopBottomMargin, negativeCardMargin, negativeTopBottomMargin})); + } + + // Style the hit target button + THROW_IF_FAILED( + XamlHelpers::SetStyleFromResourceDictionary(renderContext, style.c_str(), buttonAsFrameworkElement.Get())); + + if (action != nullptr) + { + // If we have an action, use the title for the AutomationProperties.Name + HString title; + THROW_IF_FAILED(action->get_Title(title.GetAddressOf())); + + ComPtr buttonAsDependencyObject; + THROW_IF_FAILED(button.As(&buttonAsDependencyObject)); + + ComPtr automationPropertiesStatics; + THROW_IF_FAILED( + GetActivationFactory(HStringReference(RuntimeClass_Windows_UI_Xaml_Automation_AutomationProperties).Get(), + &automationPropertiesStatics)); + + THROW_IF_FAILED(automationPropertiesStatics->SetName(buttonAsDependencyObject.Get(), title.Get())); + + WireButtonClickToAction(button.Get(), action, renderContext); + } + + THROW_IF_FAILED(button.CopyTo(finalElement)); + } + + void WireButtonClickToAction(_In_ IButton* button, _In_ IAdaptiveActionElement* action, _In_ IAdaptiveRenderContext* renderContext) + { + // Note that this method currently doesn't support inline show card actions, it + // assumes the caller won't call this method if inline show card is specified. + ComPtr localButton(button); + ComPtr actionInvoker; + THROW_IF_FAILED(renderContext->get_ActionInvoker(&actionInvoker)); + ComPtr strongAction(action); + + // Add click handler + ComPtr buttonBase; + THROW_IF_FAILED(localButton.As(&buttonBase)); + + EventRegistrationToken clickToken; + THROW_IF_FAILED(buttonBase->add_Click(Callback([strongAction, actionInvoker](IInspectable* /*sender*/, IRoutedEventArgs * + /*args*/) -> HRESULT { + THROW_IF_FAILED(actionInvoker->SendActionEvent(strongAction.Get())); + return S_OK; + }).Get(), + &clickToken)); + } + + void HandleSelectAction(_In_ IAdaptiveCardElement* adaptiveCardElement, + _In_ IAdaptiveActionElement* selectAction, + _In_ IAdaptiveRenderContext* renderContext, + _In_ IUIElement* uiElement, + bool supportsInteractivity, + bool fullWidthTouchTarget, + _COM_Outptr_ IUIElement** outUiElement) + { + if (selectAction != nullptr && supportsInteractivity) + { + WrapInTouchTarget(adaptiveCardElement, uiElement, selectAction, renderContext, fullWidthTouchTarget, L"Adaptive.SelectAction", outUiElement); + } + else + { + if (selectAction != nullptr) + { + renderContext->AddWarning(ABI::AdaptiveNamespace::WarningStatusCode::InteractivityNotSupported, + HStringReference(L"SelectAction present, but Interactivity is not supported").Get()); + } + + ComPtr localUiElement(uiElement); + THROW_IF_FAILED(localUiElement.CopyTo(outUiElement)); + } + } + + HRESULT BuildActions(_In_ IAdaptiveCard* adaptiveCard, + _In_ IVector* children, + _In_ IPanel* bodyPanel, + bool insertSeparator, + _In_ IAdaptiveRenderContext* renderContext, + _In_ ABI::AdaptiveNamespace::IAdaptiveRenderArgs* renderArgs) + { + ComPtr hostConfig; + RETURN_IF_FAILED(renderContext->get_HostConfig(&hostConfig)); + ComPtr actionsConfig; + RETURN_IF_FAILED(hostConfig->get_Actions(actionsConfig.GetAddressOf())); + + // Create a separator between the body and the actions + if (insertSeparator) + { + ABI::AdaptiveNamespace::Spacing spacing; + RETURN_IF_FAILED(actionsConfig->get_Spacing(&spacing)); + + UINT spacingSize; + RETURN_IF_FAILED(GetSpacingSizeFromSpacing(hostConfig.Get(), spacing, &spacingSize)); + + ABI::Windows::UI::Color color = {0}; + auto separator = XamlHelpers::CreateSeparator(renderContext, spacingSize, 0, color); + XamlHelpers::AppendXamlElementToPanel(separator.Get(), bodyPanel); + } + + ComPtr actionSetControl; + RETURN_IF_FAILED(BuildActionSetHelper(adaptiveCard, nullptr, children, renderContext, renderArgs, &actionSetControl)); + + XamlHelpers::AppendXamlElementToPanel(actionSetControl.Get(), bodyPanel); + return S_OK; + } + + HRESULT BuildActionSetHelper(_In_opt_ ABI::AdaptiveNamespace::IAdaptiveCard* adaptiveCard, + _In_opt_ IAdaptiveActionSet* adaptiveActionSet, + _In_ IVector* children, + _In_ IAdaptiveRenderContext* renderContext, + _In_ IAdaptiveRenderArgs* renderArgs, + _Outptr_ IUIElement** actionSetControl) + { + ComPtr hostConfig; + RETURN_IF_FAILED(renderContext->get_HostConfig(&hostConfig)); + ComPtr actionsConfig; + RETURN_IF_FAILED(hostConfig->get_Actions(actionsConfig.GetAddressOf())); + + ABI::AdaptiveNamespace::ActionAlignment actionAlignment; + RETURN_IF_FAILED(actionsConfig->get_ActionAlignment(&actionAlignment)); + + ABI::AdaptiveNamespace::ActionsOrientation actionsOrientation; + RETURN_IF_FAILED(actionsConfig->get_ActionsOrientation(&actionsOrientation)); + + // Declare the panel that will host the buttons + ComPtr actionsPanel; + ComPtr> columnDefinitions; + + if (actionAlignment == ABI::AdaptiveNamespace::ActionAlignment::Stretch && + actionsOrientation == ABI::AdaptiveNamespace::ActionsOrientation::Horizontal) + { + // If stretch alignment and orientation is horizontal, we use a grid with equal column widths to achieve + // stretch behavior. For vertical orientation, we'll still just use a stack panel since the concept of + // stretching buttons height isn't really valid, especially when the height of cards are typically dynamic. + ComPtr actionsGrid = + XamlHelpers::CreateXamlClass(HStringReference(RuntimeClass_Windows_UI_Xaml_Controls_Grid)); + RETURN_IF_FAILED(actionsGrid->get_ColumnDefinitions(&columnDefinitions)); + RETURN_IF_FAILED(actionsGrid.As(&actionsPanel)); + } + else + { + // Create a stack panel for the action buttons + ComPtr actionStackPanel = + XamlHelpers::CreateXamlClass(HStringReference(RuntimeClass_Windows_UI_Xaml_Controls_StackPanel)); + + auto uiOrientation = (actionsOrientation == ABI::AdaptiveNamespace::ActionsOrientation::Horizontal) ? + Orientation::Orientation_Horizontal : + Orientation::Orientation_Vertical; + + RETURN_IF_FAILED(actionStackPanel->put_Orientation(uiOrientation)); + + ComPtr actionsFrameworkElement; + RETURN_IF_FAILED(actionStackPanel.As(&actionsFrameworkElement)); + + switch (actionAlignment) + { + case ABI::AdaptiveNamespace::ActionAlignment::Center: + RETURN_IF_FAILED(actionsFrameworkElement->put_HorizontalAlignment(ABI::Windows::UI::Xaml::HorizontalAlignment_Center)); + break; + case ABI::AdaptiveNamespace::ActionAlignment::Left: + RETURN_IF_FAILED(actionsFrameworkElement->put_HorizontalAlignment(ABI::Windows::UI::Xaml::HorizontalAlignment_Left)); + break; + case ABI::AdaptiveNamespace::ActionAlignment::Right: + RETURN_IF_FAILED(actionsFrameworkElement->put_HorizontalAlignment(ABI::Windows::UI::Xaml::HorizontalAlignment_Right)); + break; + case ABI::AdaptiveNamespace::ActionAlignment::Stretch: + RETURN_IF_FAILED(actionsFrameworkElement->put_HorizontalAlignment(ABI::Windows::UI::Xaml::HorizontalAlignment_Stretch)); + break; + } + + // Add the action buttons to the stack panel + RETURN_IF_FAILED(actionStackPanel.As(&actionsPanel)); + } + + Thickness buttonMargin; + RETURN_IF_FAILED(ActionHelpers::GetButtonMargin(actionsConfig.Get(), buttonMargin)); + if (actionsOrientation == ABI::AdaptiveNamespace::ActionsOrientation::Horizontal) + { + // Negate the spacing on the sides so the left and right buttons are flush on the side. + // We do NOT remove the margin from the individual button itself, since that would cause + // the equal columns stretch behavior to not have equal columns (since the first and last + // button would be narrower without the same margins as its peers). + ComPtr actionsPanelAsFrameworkElement; + RETURN_IF_FAILED(actionsPanel.As(&actionsPanelAsFrameworkElement)); + RETURN_IF_FAILED(actionsPanelAsFrameworkElement->put_Margin({buttonMargin.Left * -1, 0, buttonMargin.Right * -1, 0})); + } + else + { + // Negate the spacing on the top and bottom so the first and last buttons don't have extra padding + ComPtr actionsPanelAsFrameworkElement; + RETURN_IF_FAILED(actionsPanel.As(&actionsPanelAsFrameworkElement)); + RETURN_IF_FAILED(actionsPanelAsFrameworkElement->put_Margin({0, buttonMargin.Top * -1, 0, buttonMargin.Bottom * -1})); + } + + UINT32 maxActions; + RETURN_IF_FAILED(actionsConfig->get_MaxActions(&maxActions)); + + bool allActionsHaveIcons{true}; + XamlHelpers::IterateOverVector(children, [&](IAdaptiveActionElement* child) { + HString iconUrl; + RETURN_IF_FAILED(child->get_IconUrl(iconUrl.GetAddressOf())); + + if (WindowsIsStringEmpty(iconUrl.Get())) + { + allActionsHaveIcons = false; + } + return S_OK; + }); + + UINT currentAction = 0; + + RETURN_IF_FAILED(renderArgs->put_AllowAboveTitleIconPlacement(allActionsHaveIcons)); + + std::shared_ptr>> allShowCards = std::make_shared>>(); + ComPtr showCardsStackPanel = + XamlHelpers::CreateXamlClass(HStringReference(RuntimeClass_Windows_UI_Xaml_Controls_StackPanel)); + ComPtr gridStatics; + RETURN_IF_FAILED(GetActivationFactory(HStringReference(RuntimeClass_Windows_UI_Xaml_Controls_Grid).Get(), &gridStatics)); + XamlHelpers::IterateOverVector(children, [&](IAdaptiveActionElement* child) { + if (currentAction < maxActions) + { + // Render each action using the registered renderer + ComPtr action(child); + ComPtr actionRegistration; + RETURN_IF_FAILED(renderContext->get_ActionRenderers(&actionRegistration)); + + ComPtr renderer; + while (!renderer) + { + HString actionTypeString; + RETURN_IF_FAILED(action->get_ActionTypeString(actionTypeString.GetAddressOf())); + RETURN_IF_FAILED(actionRegistration->Get(actionTypeString.Get(), &renderer)); + if (!renderer) + { + ABI::AdaptiveNamespace::FallbackType actionFallbackType; + action->get_FallbackType(&actionFallbackType); + switch (actionFallbackType) + { + case ABI::AdaptiveNamespace::FallbackType::Drop: + { + RETURN_IF_FAILED(XamlHelpers::WarnForFallbackDrop(renderContext, actionTypeString.Get())); + return S_OK; + } + + case ABI::AdaptiveNamespace::FallbackType::Content: + { + ComPtr actionFallback; + RETURN_IF_FAILED(action->get_FallbackContent(&actionFallback)); + + HString fallbackTypeString; + RETURN_IF_FAILED(actionFallback->get_ActionTypeString(fallbackTypeString.GetAddressOf())); + RETURN_IF_FAILED(XamlHelpers::WarnForFallbackContentElement(renderContext, + actionTypeString.Get(), + fallbackTypeString.Get())); + + action = actionFallback; + break; + } + + case ABI::AdaptiveNamespace::FallbackType::None: + default: + return E_FAIL; + } + } + } + + ComPtr actionControl; + RETURN_IF_FAILED(renderer->Render(action.Get(), renderContext, renderArgs, &actionControl)); + + XamlHelpers::AppendXamlElementToPanel(actionControl.Get(), actionsPanel.Get()); + + ABI::AdaptiveNamespace::ActionType actionType; + RETURN_IF_FAILED(action->get_ActionType(&actionType)); + + // Build inline show cards if needed + if (actionType == ABI::AdaptiveNamespace::ActionType_ShowCard) + { + ComPtr uiShowCard; + + ComPtr showCardActionConfig; + RETURN_IF_FAILED(actionsConfig->get_ShowCard(&showCardActionConfig)); + + ABI::AdaptiveNamespace::ActionMode showCardActionMode; + RETURN_IF_FAILED(showCardActionConfig->get_ActionMode(&showCardActionMode)); + + if (showCardActionMode == ABI::AdaptiveNamespace::ActionMode::Inline) + { + ComPtr showCardAction; + RETURN_IF_FAILED(action.As(&showCardAction)); + + ComPtr showCard; + RETURN_IF_FAILED(showCardAction->get_Card(&showCard)); + + RETURN_IF_FAILED(AdaptiveShowCardActionRenderer::BuildShowCard( + showCard.Get(), renderContext, renderArgs, (adaptiveActionSet == nullptr), uiShowCard.GetAddressOf())); + + ComPtr showCardsPanel; + RETURN_IF_FAILED(showCardsStackPanel.As(&showCardsPanel)); + XamlHelpers::AppendXamlElementToPanel(uiShowCard.Get(), showCardsPanel.Get()); + + if (adaptiveActionSet) + { + RETURN_IF_FAILED( + renderContext->AddInlineShowCard(adaptiveActionSet, showCardAction.Get(), uiShowCard.Get())); + } + else + { + ComPtr contextImpl = + PeekInnards(renderContext); + + RETURN_IF_FAILED( + contextImpl->AddInlineShowCard(adaptiveCard, showCardAction.Get(), uiShowCard.Get())); + } + } + } + + if (columnDefinitions != nullptr) + { + // If using the equal width columns, we'll add a column and assign the column + ComPtr columnDefinition = XamlHelpers::CreateXamlClass( + HStringReference(RuntimeClass_Windows_UI_Xaml_Controls_ColumnDefinition)); + RETURN_IF_FAILED(columnDefinition->put_Width({1.0, GridUnitType::GridUnitType_Star})); + RETURN_IF_FAILED(columnDefinitions->Append(columnDefinition.Get())); + + ComPtr actionFrameworkElement; + THROW_IF_FAILED(actionControl.As(&actionFrameworkElement)); + THROW_IF_FAILED(gridStatics->SetColumn(actionFrameworkElement.Get(), currentAction)); + } + } + else + { + renderContext->AddWarning(ABI::AdaptiveNamespace::WarningStatusCode::MaxActionsExceeded, + HStringReference(L"Some actions were not rendered due to exceeding the maximum number of actions allowed") + .Get()); + } + currentAction++; + return S_OK; + }); + + // Reset icon placement value + RETURN_IF_FAILED(renderArgs->put_AllowAboveTitleIconPlacement(false)); + + ComPtr actionsPanelAsFrameworkElement; + RETURN_IF_FAILED(actionsPanel.As(&actionsPanelAsFrameworkElement)); + RETURN_IF_FAILED(XamlHelpers::SetStyleFromResourceDictionary(renderContext, + L"Adaptive.Actions", + actionsPanelAsFrameworkElement.Get())); + + ComPtr actionSet = + XamlHelpers::CreateXamlClass(HStringReference(RuntimeClass_Windows_UI_Xaml_Controls_StackPanel)); + ComPtr actionSetAsPanel; + actionSet.As(&actionSetAsPanel); + + // Add buttons and show cards to panel + XamlHelpers::AppendXamlElementToPanel(actionsPanel.Get(), actionSetAsPanel.Get()); + XamlHelpers::AppendXamlElementToPanel(showCardsStackPanel.Get(), actionSetAsPanel.Get()); + + return actionSetAsPanel.CopyTo(actionSetControl); + } +} From ddcc7af871b4c7a7f74df0ca1cef698affed25cd Mon Sep 17 00:00:00 2001 From: Joseph Woo Date: Mon, 21 Oct 2019 17:51:29 -0700 Subject: [PATCH 02/19] [.NET] Updated obsolete messages (#3529) --- .../Library/AdaptiveCards/Rendering/AdaptiveHostConfig.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/dotnet/Library/AdaptiveCards/Rendering/AdaptiveHostConfig.cs b/source/dotnet/Library/AdaptiveCards/Rendering/AdaptiveHostConfig.cs index c2f2019db7..e869513426 100644 --- a/source/dotnet/Library/AdaptiveCards/Rendering/AdaptiveHostConfig.cs +++ b/source/dotnet/Library/AdaptiveCards/Rendering/AdaptiveHostConfig.cs @@ -27,15 +27,15 @@ public class AdaptiveHostConfig : AdaptiveConfigBase public FactSetConfig FactSet { get; set; } = new FactSetConfig(); [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] - [Obsolete("AdaptiveHostConfig.FontFamily has been deprecated. Use AdaptiveHostConfig.FontStyles.Default.FontFamily", false)] + [Obsolete("AdaptiveHostConfig.FontFamily has been deprecated. Use AdaptiveHostConfig.FontTypes.Default.FontFamily", false)] public string FontFamily { get; set; } [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] - [Obsolete("AdaptiveHostConfig.FontSizes has been deprecated. Use AdaptiveHostConfig.FontStyles.Default.FontSizes", false)] + [Obsolete("AdaptiveHostConfig.FontSizes has been deprecated. Use AdaptiveHostConfig.FontTypes.Default.FontSizes", false)] public FontSizesConfig FontSizes { get; set; } = new FontSizesConfig(); [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] - [Obsolete("AdaptiveHostConfig.FontWeights has been deprecated. Use AdaptiveHostConfig.FontStyles.Default.FontWeights", false)] + [Obsolete("AdaptiveHostConfig.FontWeights has been deprecated. Use AdaptiveHostConfig.FontTypes.Default.FontWeights", false)] public FontWeightsConfig FontWeights { get; set; } = new FontWeightsConfig(); [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] From 19057e0a721970f841b8cb48e4fe610b2fec6a50 Mon Sep 17 00:00:00 2001 From: almedina-ms <35784165+almedina-ms@users.noreply.github.com> Date: Mon, 23 Sep 2019 15:36:29 -0700 Subject: [PATCH 03/19] Add validation for properly rendered columns (#3471) --- .../AdaptiveColumnSetRenderer.cs | 114 +++++++++--------- 1 file changed, 59 insertions(+), 55 deletions(-) diff --git a/source/dotnet/Library/AdaptiveCards.Rendering.Wpf/AdaptiveColumnSetRenderer.cs b/source/dotnet/Library/AdaptiveCards.Rendering.Wpf/AdaptiveColumnSetRenderer.cs index 23ed73d101..a5e41b20dc 100644 --- a/source/dotnet/Library/AdaptiveCards.Rendering.Wpf/AdaptiveColumnSetRenderer.cs +++ b/source/dotnet/Library/AdaptiveCards.Rendering.Wpf/AdaptiveColumnSetRenderer.cs @@ -60,83 +60,87 @@ public static FrameworkElement Render(AdaptiveColumnSet columnSet, AdaptiveRende FrameworkElement uiContainer = context.Render(column); - TagContent tag = null; - - // Add vertical Separator - if (uiColumnSet.ColumnDefinitions.Count > 0 && (column.Separator || column.Spacing != AdaptiveSpacing.None)) + // If the column couldn't be rendered and the fallback is 'drop' + if (uiContainer != null) { - var uiSep = new Grid(); - uiSep.Style = context.GetStyle($"Adaptive.VerticalSeparator"); + TagContent tag = null; - uiSep.VerticalAlignment = VerticalAlignment.Stretch; + // Add vertical Separator + if (uiColumnSet.ColumnDefinitions.Count > 0 && (column.Separator || column.Spacing != AdaptiveSpacing.None)) + { + var uiSep = new Grid(); + uiSep.Style = context.GetStyle($"Adaptive.VerticalSeparator"); - int spacing = context.Config.GetSpacing(column.Spacing); - uiSep.Margin = new Thickness(spacing / 2.0, 0, spacing / 2.0, 0); + uiSep.VerticalAlignment = VerticalAlignment.Stretch; - uiSep.Width = context.Config.Separator.LineThickness; - if (column.Separator && context.Config.Separator.LineColor != null) - { - uiSep.Background = context.GetColorBrush(context.Config.Separator.LineColor); - } + int spacing = context.Config.GetSpacing(column.Spacing); + uiSep.Margin = new Thickness(spacing / 2.0, 0, spacing / 2.0, 0); - tag = new TagContent(uiSep, uiColumnSet); + uiSep.Width = context.Config.Separator.LineThickness; + if (column.Separator && context.Config.Separator.LineColor != null) + { + uiSep.Background = context.GetColorBrush(context.Config.Separator.LineColor); + } - uiColumnSet.ColumnDefinitions.Add(new ColumnDefinition() { Width = GridLength.Auto }); - Grid.SetColumn(uiSep, uiColumnSet.ColumnDefinitions.Count - 1); - uiColumnSet.Children.Add(uiSep); - } - else - { - tag = new TagContent(null, uiColumnSet); - } + tag = new TagContent(uiSep, uiColumnSet); + + uiColumnSet.ColumnDefinitions.Add(new ColumnDefinition() { Width = GridLength.Auto }); + Grid.SetColumn(uiSep, uiColumnSet.ColumnDefinitions.Count - 1); + uiColumnSet.Children.Add(uiSep); + } + else + { + tag = new TagContent(null, uiColumnSet); + } - // do some sizing magic using the magic GridUnitType.Star - var width = column.Width?.ToLower(); - if (string.IsNullOrEmpty(width)) + // do some sizing magic using the magic GridUnitType.Star + var width = column.Width?.ToLower(); + if (string.IsNullOrEmpty(width)) #pragma warning disable CS0618 // Type or member is obsolete - width = column.Size?.ToLower(); + width = column.Size?.ToLower(); #pragma warning restore CS0618 // Type or member is obsolete - ColumnDefinition columnDefinition = null; + ColumnDefinition columnDefinition = null; - if (width == null || width == AdaptiveColumnWidth.Stretch.ToLower()) - { - columnDefinition = new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star) }; - } - else if (width == AdaptiveColumnWidth.Auto.ToLower()) - { - columnDefinition = new ColumnDefinition() { Width = GridLength.Auto }; - } - else - { - if (double.TryParse(width, out double val) && val >= 0) + if (width == null || width == AdaptiveColumnWidth.Stretch.ToLower()) { - // Weighted proportion (number only) - columnDefinition = new ColumnDefinition() { Width = new GridLength(val, GridUnitType.Star) }; + columnDefinition = new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star) }; } - else if (width.EndsWith("px") && double.TryParse(width.Substring(0, width.Length - 2), out double pxVal) && pxVal >= 0) + else if (width == AdaptiveColumnWidth.Auto.ToLower()) { - // Exact pixel (number followed by "px") - columnDefinition = new ColumnDefinition() { Width = new GridLength((int)pxVal, GridUnitType.Pixel) }; + columnDefinition = new ColumnDefinition() { Width = GridLength.Auto }; } else { - columnDefinition = new ColumnDefinition() { Width = GridLength.Auto }; + if (double.TryParse(width, out double val) && val >= 0) + { + // Weighted proportion (number only) + columnDefinition = new ColumnDefinition() { Width = new GridLength(val, GridUnitType.Star) }; + } + else if (width.EndsWith("px") && double.TryParse(width.Substring(0, width.Length - 2), out double pxVal) && pxVal >= 0) + { + // Exact pixel (number followed by "px") + columnDefinition = new ColumnDefinition() { Width = new GridLength((int)pxVal, GridUnitType.Pixel) }; + } + else + { + columnDefinition = new ColumnDefinition() { Width = GridLength.Auto }; + } } - } - // Store the column definition in the tag so we can toggle the visibility later - tag.ColumnDefinition = columnDefinition; - tag.ViewIndex = uiColumnSet.ColumnDefinitions.Count; + // Store the column definition in the tag so we can toggle the visibility later + tag.ColumnDefinition = columnDefinition; + tag.ViewIndex = uiColumnSet.ColumnDefinitions.Count; - uiColumnSet.ColumnDefinitions.Add(columnDefinition); + uiColumnSet.ColumnDefinitions.Add(columnDefinition); - uiContainer.Tag = tag; - - Grid.SetColumn(uiContainer, uiColumnSet.ColumnDefinitions.Count - 1); - uiColumnSet.Children.Add(uiContainer); + uiContainer.Tag = tag; - context.SetVisibility(uiContainer, column.IsVisible, tag); + Grid.SetColumn(uiContainer, uiColumnSet.ColumnDefinitions.Count - 1); + uiColumnSet.Children.Add(uiContainer); + + context.SetVisibility(uiContainer, column.IsVisible, tag); + } } context.ResetSeparatorVisibilityInsideContainer(uiColumnSet); From db6fab676e104432bc86e2fb70e126adb005835c Mon Sep 17 00:00:00 2001 From: Joseph Woo Date: Wed, 30 Oct 2019 13:45:34 -0700 Subject: [PATCH 04/19] [.NET] Parse Context Update (#3548) * made parse context not static; need to fix the internal id being null when showcard is parsed * adding missing file * add parse context for property parsing * work in progress * fixes concurrency issue during parsing * minor fix * updated PR per CR comments --- .../Library/AdaptiveCards/AdaptiveCard.cs | 2 +- .../AdaptiveCards/AdaptiveCardConverter.cs | 13 +- .../AdaptiveFallbackConverter.cs | 4 +- .../AdaptiveCards/AdaptiveInlinesConverter.cs | 18 +- .../AdaptiveTypedBaseElementConverter.cs | 24 ++ .../AdaptiveCards/AdaptiveTypedElement.cs | 2 +- .../AdaptiveTypedElementConverter.cs | 3 +- .../IgnoreEmptyItemsConverter.cs | 2 +- .../Library/AdaptiveCards/ParseContext.cs | 30 ++- .../WarningLoggingContractResolver.cs | 14 +- .../AdaptiveCardApiTests.cs | 243 +++++++++++++++++- 11 files changed, 327 insertions(+), 28 deletions(-) create mode 100644 source/dotnet/Library/AdaptiveCards/AdaptiveTypedBaseElementConverter.cs diff --git a/source/dotnet/Library/AdaptiveCards/AdaptiveCard.cs b/source/dotnet/Library/AdaptiveCards/AdaptiveCard.cs index 85575b749c..aec8c240cb 100644 --- a/source/dotnet/Library/AdaptiveCards/AdaptiveCard.cs +++ b/source/dotnet/Library/AdaptiveCards/AdaptiveCard.cs @@ -238,7 +238,7 @@ public static AdaptiveCardParseResult FromJson(string json) { parseResult.Card = JsonConvert.DeserializeObject(json, new JsonSerializerSettings { - ContractResolver = new WarningLoggingContractResolver(parseResult), + ContractResolver = new WarningLoggingContractResolver(parseResult, new ParseContext()), Converters = { new StrictIntConverter() } }); } diff --git a/source/dotnet/Library/AdaptiveCards/AdaptiveCardConverter.cs b/source/dotnet/Library/AdaptiveCards/AdaptiveCardConverter.cs index e8654655f8..dc11d6dfc2 100644 --- a/source/dotnet/Library/AdaptiveCards/AdaptiveCardConverter.cs +++ b/source/dotnet/Library/AdaptiveCards/AdaptiveCardConverter.cs @@ -9,7 +9,7 @@ namespace AdaptiveCards { - public class AdaptiveCardConverter : JsonConverter, ILogWarnings + public class AdaptiveCardConverter : AdaptiveTypedBaseElementConverter, ILogWarnings { public List Warnings { get; set; } = new List(); @@ -62,9 +62,6 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist if (reader.Depth == 0) { - // Needed for ID collision detection after fallback was introduced - ParseContext.Initialize(); - ValidateJsonVersion(ref jObject); if (new AdaptiveSchemaVersion(jObject.Value("version")) > AdaptiveCard.KnownSchemaVersion) @@ -72,6 +69,14 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist return MakeFallbackTextCard(jObject); } } + + /// this is needed when client calls JsonConvert.Deserializer method, we need this contract resolver, + /// so we can pass ParseContext + if (!(serializer.ContractResolver is WarningLoggingContractResolver)) + { + serializer.ContractResolver = new WarningLoggingContractResolver(new AdaptiveCardParseResult(), new ParseContext()); + } + var typedElementConverter = serializer.ContractResolver.ResolveContract(typeof(AdaptiveTypedElement)).Converter; var card = (AdaptiveCard)typedElementConverter.ReadJson(jObject.CreateReader(), objectType, existingValue, serializer); diff --git a/source/dotnet/Library/AdaptiveCards/AdaptiveFallbackConverter.cs b/source/dotnet/Library/AdaptiveCards/AdaptiveFallbackConverter.cs index 6b991c874f..3696699890 100644 --- a/source/dotnet/Library/AdaptiveCards/AdaptiveFallbackConverter.cs +++ b/source/dotnet/Library/AdaptiveCards/AdaptiveFallbackConverter.cs @@ -5,7 +5,7 @@ namespace AdaptiveCards { - public class AdaptiveFallbackConverter : JsonConverter, ILogWarnings + public class AdaptiveFallbackConverter : AdaptiveTypedBaseElementConverter, ILogWarnings { public List Warnings { get; set; } = new List(); @@ -103,7 +103,7 @@ public override bool CanConvert(Type objectType) return result; } - public static AdaptiveFallbackElement ParseFallback(JToken fallbackJSON, JsonSerializer serializer, string objectId, AdaptiveInternalID internalId) + public AdaptiveFallbackElement ParseFallback(JToken fallbackJSON, JsonSerializer serializer, string objectId, AdaptiveInternalID internalId) { // Handle fallback as a string ("drop") if (fallbackJSON.Type == JTokenType.String) diff --git a/source/dotnet/Library/AdaptiveCards/AdaptiveInlinesConverter.cs b/source/dotnet/Library/AdaptiveCards/AdaptiveInlinesConverter.cs index 903ec61cc5..13cc2f2d72 100644 --- a/source/dotnet/Library/AdaptiveCards/AdaptiveInlinesConverter.cs +++ b/source/dotnet/Library/AdaptiveCards/AdaptiveInlinesConverter.cs @@ -8,7 +8,7 @@ namespace AdaptiveCards { - class AdaptiveInlinesConverter : JsonConverter + class AdaptiveInlinesConverter : AdaptiveTypedBaseElementConverter { public override bool CanRead => true; @@ -35,7 +35,21 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist else { JObject jobj = (JObject)obj; - arrayList.Add((AdaptiveInline)jobj.ToObject(typeof(AdaptiveTextRun))); + if (jobj.Value("type") != AdaptiveTextRun.TypeName) + { + throw new AdaptiveSerializationException($"Property 'type' must be '{AdaptiveTextRun.TypeName}'"); + } + + if(ParseContext == null) { + ParseContext = new ParseContext(); + } + + var adaptiveInline = JsonConvert.DeserializeObject(jobj.ToString(), new JsonSerializerSettings + { + ContractResolver = new WarningLoggingContractResolver(new AdaptiveCardParseResult(), ParseContext), + Converters = { new StrictIntConverter() } + }); + arrayList.Add(adaptiveInline); } } return arrayList; diff --git a/source/dotnet/Library/AdaptiveCards/AdaptiveTypedBaseElementConverter.cs b/source/dotnet/Library/AdaptiveCards/AdaptiveTypedBaseElementConverter.cs new file mode 100644 index 0000000000..87e074eb58 --- /dev/null +++ b/source/dotnet/Library/AdaptiveCards/AdaptiveTypedBaseElementConverter.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AdaptiveCards +{ + public abstract class AdaptiveTypedBaseElementConverter : JsonConverter + { + protected ParseContext parseContext; + public ParseContext ParseContext + { + get => parseContext; + set + { + parseContext = value; + } + } + } +} diff --git a/source/dotnet/Library/AdaptiveCards/AdaptiveTypedElement.cs b/source/dotnet/Library/AdaptiveCards/AdaptiveTypedElement.cs index c97f06bae2..c457726b41 100644 --- a/source/dotnet/Library/AdaptiveCards/AdaptiveTypedElement.cs +++ b/source/dotnet/Library/AdaptiveCards/AdaptiveTypedElement.cs @@ -46,7 +46,7 @@ public abstract class AdaptiveTypedElement public AdaptiveFallbackElement Fallback { get; set; } [JsonIgnore] - public AdaptiveInternalID InternalID { get; } + public AdaptiveInternalID InternalID { get; set; } /// /// A unique ID associated with the element. For Inputs the ID will be used as the key for Action.Submit response diff --git a/source/dotnet/Library/AdaptiveCards/AdaptiveTypedElementConverter.cs b/source/dotnet/Library/AdaptiveCards/AdaptiveTypedElementConverter.cs index 707c66eafc..c656cdd4c7 100644 --- a/source/dotnet/Library/AdaptiveCards/AdaptiveTypedElementConverter.cs +++ b/source/dotnet/Library/AdaptiveCards/AdaptiveTypedElementConverter.cs @@ -12,7 +12,7 @@ namespace AdaptiveCards /// /// This handles using type field to instantiate strongly typed object on deserialization /// - public class AdaptiveTypedElementConverter : JsonConverter, ILogWarnings + public class AdaptiveTypedElementConverter : AdaptiveTypedBaseElementConverter, ILogWarnings { public List Warnings { get; set; } = new List(); @@ -98,6 +98,7 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist try { serializer.Populate(jObject.CreateReader(), result); + result.InternalID = internalID; } catch (JsonSerializationException) { } diff --git a/source/dotnet/Library/AdaptiveCards/IgnoreEmptyItemsConverter.cs b/source/dotnet/Library/AdaptiveCards/IgnoreEmptyItemsConverter.cs index bbee84585f..724e327af6 100644 --- a/source/dotnet/Library/AdaptiveCards/IgnoreEmptyItemsConverter.cs +++ b/source/dotnet/Library/AdaptiveCards/IgnoreEmptyItemsConverter.cs @@ -9,7 +9,7 @@ namespace AdaptiveCards { - public class IgnoreEmptyItemsConverter : JsonConverter + public class IgnoreEmptyItemsConverter : AdaptiveTypedBaseElementConverter { public override bool CanConvert(Type objectType) { diff --git a/source/dotnet/Library/AdaptiveCards/ParseContext.cs b/source/dotnet/Library/AdaptiveCards/ParseContext.cs index 9fe0256ea2..444213896b 100644 --- a/source/dotnet/Library/AdaptiveCards/ParseContext.cs +++ b/source/dotnet/Library/AdaptiveCards/ParseContext.cs @@ -5,24 +5,18 @@ namespace AdaptiveCards { - static class ParseContext + public class ParseContext { - public static void Initialize() - { - elementIds = new Dictionary>(); - idStack = new Stack>(); - } - public enum ContextType { Element, Action }; - public static ContextType Type { get; set; } + public ContextType Type { get; set; } - private static IDictionary> elementIds = new Dictionary>(); + private IDictionary> elementIds = new Dictionary>(); - private static Stack> idStack = new Stack>(); + private Stack> idStack = new Stack>(); // Push the provided state on to our ID stack - public static void PushElement(string idJsonProperty, AdaptiveInternalID internalId) + public void PushElement(string idJsonProperty, AdaptiveInternalID internalId) { if (internalId.Equals(AdaptiveInternalID.Invalid)) { @@ -31,8 +25,18 @@ public static void PushElement(string idJsonProperty, AdaptiveInternalID interna idStack.Push(new Tuple(idJsonProperty, internalId, AdaptiveFallbackConverter.IsInFallback)); } + public AdaptiveInternalID PeekElement() + { + if (idStack.Count == 0) + { + // internal id in dot net needs to be revisited tracked via issue 3386 + return new AdaptiveInternalID(); + } + return idStack.Peek().Item2; + } + // Pop the last id off our stack and perform validation - public static void PopElement() + public void PopElement() { // about to pop an element off the stack. perform collision list maintenance and detection. var idsToPop = idStack.Peek(); @@ -111,7 +115,7 @@ public static void PopElement() // Walk stack looking for first element to be marked fallback (which isn't the ID we're supposed to skip), then // return its internal ID. If none, return an invalid ID. (see comment above) - public static AdaptiveInternalID GetNearestFallbackID(AdaptiveInternalID skipID) + public AdaptiveInternalID GetNearestFallbackID(AdaptiveInternalID skipID) { foreach (var curElement in idStack) { diff --git a/source/dotnet/Library/AdaptiveCards/WarningLoggingContractResolver.cs b/source/dotnet/Library/AdaptiveCards/WarningLoggingContractResolver.cs index ce6d90cd71..1eda59fa2a 100644 --- a/source/dotnet/Library/AdaptiveCards/WarningLoggingContractResolver.cs +++ b/source/dotnet/Library/AdaptiveCards/WarningLoggingContractResolver.cs @@ -13,16 +13,23 @@ namespace AdaptiveCards internal class WarningLoggingContractResolver : DefaultContractResolver { private readonly AdaptiveCardParseResult _parseResult; + private ParseContext _parseContext; - public WarningLoggingContractResolver(AdaptiveCardParseResult parseResult) + public WarningLoggingContractResolver(AdaptiveCardParseResult parseResult, ParseContext parseContext) { _parseResult = parseResult; + _parseContext = parseContext; } protected override JsonConverter ResolveContractConverter(Type type) { var converter = base.ResolveContractConverter(type); + if (converter is AdaptiveTypedBaseElementConverter converterWithContext) + { + converterWithContext.ParseContext = _parseContext; + } + if (converter is ILogWarnings logWarnings) { logWarnings.Warnings = _parseResult.Warnings; @@ -46,6 +53,11 @@ protected override JsonProperty CreateProperty(MemberInfo member, MemberSerializ converter.Warnings = _parseResult.Warnings; } + if (property?.Converter is AdaptiveTypedBaseElementConverter converterWithContext) + { + converterWithContext.ParseContext = _parseContext; + } + if (property?.MemberConverter is ILogWarnings memberConverter) { memberConverter.Warnings = _parseResult.Warnings; diff --git a/source/dotnet/Test/AdaptiveCards.Test/AdaptiveCardApiTests.cs b/source/dotnet/Test/AdaptiveCards.Test/AdaptiveCardApiTests.cs index dd39e2d02e..4c78a06dc7 100644 --- a/source/dotnet/Test/AdaptiveCards.Test/AdaptiveCardApiTests.cs +++ b/source/dotnet/Test/AdaptiveCards.Test/AdaptiveCardApiTests.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; using System.Collections; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Newtonsoft.Json; +using System.Threading.Tasks; namespace AdaptiveCards.Test { @@ -1155,6 +1155,245 @@ public void TestObjectModelMinHeight() }"; var outputJson = card.ToJson(); Assert.AreEqual(outputJson, expectedJson); -} + } + + [TestMethod] + public void TestImplicitImageType() + { + // Images set to type "Image" or with type unset should parse correctly within an image set + var imageTypeSetOrEmpty = + @"{ + ""type"": ""AdaptiveCard"", + ""version"": ""1.2"", + ""body"": [ + { + ""type"": ""ImageSet"", + ""images"": [ + { + ""type"": ""Image"", + ""url"": ""http://adaptivecards.io/content/cats/1.png"" + }, + { + ""url"": ""http://adaptivecards.io/content/cats/1.png"" + } + ] + } + ] + }"; + + // Images set to a bogus type should not parse correctly + var imageTypeInvalid = + @"{ + ""type"": ""AdaptiveCard"", + ""version"": ""1.2"", + ""body"": [ + { + ""type"": ""ImageSet"", + ""images"": [ + { + ""type"": ""Elephant"", + ""url"": ""http://adaptivecards.io/content/cats/1.png"" + } + ] + } + ] + }"; + + var result = AdaptiveCard.FromJson(imageTypeSetOrEmpty); + Assert.IsNotNull(result.Card); + Assert.AreEqual(2, (result.Card.Body[0] as AdaptiveImageSet).Images.Count); + + var ex = Assert.ThrowsException(() => + { + AdaptiveCard.FromJson(imageTypeInvalid); + }); + + StringAssert.Contains(ex.Message, "The value \"AdaptiveCards.AdaptiveUnknownElement\" is not of type \"AdaptiveCards.AdaptiveImage\" and cannot be used in this generic collection."); + } + + [TestMethod] + public void TestParsingRichTextBlockWithInvalidInlineType() + { + // card with invalid inline type + var invalidCard = + @"{ + ""$schema"": ""http://adaptivecards.io/schemas/adaptive-card.json"", + ""type"": ""AdaptiveCard"", + ""version"": ""1.2"", + ""body"": [ + { + ""type"": ""RichTextBlock"", + ""inlines"": [ + { + ""type"": ""TextRun"", + ""text"": ""You did exactly what you had to do. You considered all your options, you tried every alternative and then you made the hard choice. The Enterprise computer system is controlled by three primary main processor cores, cross-linked with a redundant melacortz ramistat, fourteen kiloquad interface modules. I think you've let your personal feelings cloud your judgement. Flair is what marks the difference between artistry and mere competence. They were just sucked into space. We have a saboteur aboard. When has justice ever been as simple as a rule book? Your shields were failing, sir. Travel time to the nearest starbase? Sorry, Data. I'm afraid I still don't understand, sir. How long can two people talk about nothing? Wait a minute - you've been declared dead. You can't give orders around here. You're going to be an interesting companion, Mr. Data. Our neural pathways have become accustomed to your sensory input patterns. Fear is the true enemy, the only enemy. The Federation's gone; the Borg is everywhere! Computer, lights up!"" + }, + { + ""type"": ""who cares???"", + ""text"": ""You did exactly what you had to do. You considered all your options, you tried every alternative and then you made the hard choice. The Enterprise computer system is controlled by three primary main processor cores, cross-linked with a redundant melacortz ramistat, fourteen kiloquad interface modules. I think you've let your personal feelings cloud your judgement. Flair is what marks the difference between artistry and mere competence. They were just sucked into space. We have a saboteur aboard. When has justice ever been as simple as a rule book? Your shields were failing, sir. Travel time to the nearest starbase? Sorry, Data. I'm afraid I still don't understand, sir. How long can two people talk about nothing? Wait a minute - you've been declared dead. You can't give orders around here. You're going to be an interesting companion, Mr. Data. Our neural pathways have become accustomed to your sensory input patterns. Fear is the true enemy, the only enemy. The Federation's gone; the Borg is everywhere! Computer, lights up!"" + } + ] + } + ] + }"; + + var ex = Assert.ThrowsException(() => + { + AdaptiveCard.FromJson(invalidCard); + }); + + + StringAssert.Contains(ex.Message, "Property 'type' must be 'TextRun'"); + } + void RenderCardTask(string payload) + { + AdaptiveCardParseResult parseResult = AdaptiveCard.FromJson(payload); + if (parseResult.Warnings.Count != 0) + { + throw new Exception("parse failed"); + } + } + + [TestMethod] + public void TestConcurrentParsing() + { + // card with invalid inline type + var adaptiveCard = +@"{ + ""$schema"": ""http://adaptivecards.io/schemas/adaptive-card.json"", + ""type"": ""AdaptiveCard"", + ""version"": ""1.0"", + ""body"": [ + { + ""type"": ""TextBlock"", + ""text"": ""Publish Adaptive Card schema"", + ""weight"": ""bolder"", + ""size"": ""medium"" + }, + { + ""type"": ""ColumnSet"", + ""columns"": [ + { + ""type"": ""Column"", + ""width"": ""auto"", + ""items"": [ + { + ""type"": ""Image"", + ""url"": ""https://pbs.twimg.com/profile_images/3647943215/d7f12830b3c17a5a9e4afcc370e3a37e_400x400.jpeg"", + ""size"": ""small"", + ""style"": ""person"" + } + ] + }, + { + ""type"": ""Column"", + ""width"": ""stretch"", + ""items"": [ + { + ""type"": ""TextBlock"", + ""text"": ""Matt Hidinger"", + ""weight"": ""bolder"", + ""wrap"": true + }, + { + ""type"": ""TextBlock"", + ""spacing"": ""none"", + ""text"": ""Created {{DATE(2017-02-14T06:08:39Z, SHORT)}}"", + ""isSubtle"": true, + ""wrap"": true + } + ] + } + ] + }, + { + ""type"": ""TextBlock"", + ""text"": ""Now that we have defined the main rules and features of the format, we need to produce a schema and publish it to GitHub. The schema will be the starting point of our reference documentation."", + ""wrap"": true + }, + { + ""type"": ""FactSet"", + ""facts"": [ + { + ""title"": ""Board:"", + ""value"": ""Adaptive Card"" + }, + { + ""title"": ""List:"", + ""value"": ""Backlog"" + }, + { + ""title"": ""Assigned to:"", + ""value"": ""Matt Hidinger"" + }, + { + ""title"": ""Due date:"", + ""value"": ""Not set"" + } + ] + } + ], + ""actions"": [ + { + ""type"": ""Action.ShowCard"", + ""title"": ""Set due date"", + ""card"": { + ""type"": ""AdaptiveCard"", + ""body"": [ + { + ""type"": ""Input.Date"", + ""id"": ""dueDate"" + } + ], + ""actions"": [ + { + ""type"": ""Action.Submit"", + ""title"": ""OK"" + } + ] + } + }, + { + ""type"": ""Action.ShowCard"", + ""title"": ""Comment"", + ""card"": { + ""type"": ""AdaptiveCard"", + ""body"": [ + { + ""type"": ""Input.Text"", + ""id"": ""comment"", + ""isMultiline"": true, + ""placeholder"": ""Enter your comment"" + } + ], + ""actions"": [ + { + ""type"": ""Action.Submit"", + ""title"": ""OK"" + } + ] + } + } + ] +}"; + List tasks = new List(); + for (var i = 0; i < 10; i++) + { + var payload = adaptiveCard; + var task = Task.Run(() => { RenderCardTask(payload); }); + tasks.Add(task); + } + + try + { + Task.WaitAll(tasks.ToArray()); + } + catch + { + // it's perfectly fine card. if there is exception, it is due to concurreny + // as it's the only variable. + Assert.Fail(); + } + } } } From 834c22727e7b448996cb2215a20b2cc4c85b151d Mon Sep 17 00:00:00 2001 From: Joseph Woo Date: Fri, 1 Nov 2019 11:36:23 -0700 Subject: [PATCH 05/19] .Net Concurrency (#3556) * addressed the review comments * review comments fixes --- .../AdaptiveFallbackConverter.cs | 2 ++ .../AdaptiveCards/AdaptiveInlinesConverter.cs | 16 ++++++-------- .../AdaptiveTypedBaseElementConverter.cs | 21 +++++++------------ .../Library/AdaptiveCards/ParseContext.cs | 12 ++--------- .../AdaptiveCardApiTests.cs | 3 ++- 5 files changed, 19 insertions(+), 35 deletions(-) diff --git a/source/dotnet/Library/AdaptiveCards/AdaptiveFallbackConverter.cs b/source/dotnet/Library/AdaptiveCards/AdaptiveFallbackConverter.cs index 3696699890..c79b3898c0 100644 --- a/source/dotnet/Library/AdaptiveCards/AdaptiveFallbackConverter.cs +++ b/source/dotnet/Library/AdaptiveCards/AdaptiveFallbackConverter.cs @@ -1,3 +1,5 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; diff --git a/source/dotnet/Library/AdaptiveCards/AdaptiveInlinesConverter.cs b/source/dotnet/Library/AdaptiveCards/AdaptiveInlinesConverter.cs index 13cc2f2d72..6bc0730b05 100644 --- a/source/dotnet/Library/AdaptiveCards/AdaptiveInlinesConverter.cs +++ b/source/dotnet/Library/AdaptiveCards/AdaptiveInlinesConverter.cs @@ -24,6 +24,11 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist var array = JArray.Load(reader); List list = array.ToObject>(); List arrayList = new List(); + var serializerSettigns = new JsonSerializerSettings + { + ContractResolver = new WarningLoggingContractResolver(new AdaptiveCardParseResult(), ParseContext), + Converters = { new StrictIntConverter() } + }; // We only support text runs for now, which can be specified as either a string or an object foreach (object obj in list) @@ -40,16 +45,7 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist throw new AdaptiveSerializationException($"Property 'type' must be '{AdaptiveTextRun.TypeName}'"); } - if(ParseContext == null) { - ParseContext = new ParseContext(); - } - - var adaptiveInline = JsonConvert.DeserializeObject(jobj.ToString(), new JsonSerializerSettings - { - ContractResolver = new WarningLoggingContractResolver(new AdaptiveCardParseResult(), ParseContext), - Converters = { new StrictIntConverter() } - }); - arrayList.Add(adaptiveInline); + arrayList.Add(JsonConvert.DeserializeObject(jobj.ToString(), serializerSettigns)); } } return arrayList; diff --git a/source/dotnet/Library/AdaptiveCards/AdaptiveTypedBaseElementConverter.cs b/source/dotnet/Library/AdaptiveCards/AdaptiveTypedBaseElementConverter.cs index 87e074eb58..75ba06fedc 100644 --- a/source/dotnet/Library/AdaptiveCards/AdaptiveTypedBaseElementConverter.cs +++ b/source/dotnet/Library/AdaptiveCards/AdaptiveTypedBaseElementConverter.cs @@ -1,24 +1,17 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace AdaptiveCards { + /// + /// JsonConverters that deserialize to Adpative Elements and require + /// ParseContext must inherit this Class + /// ParseContext provides id generation, id collision detections, and other useful + /// services during deserialization + /// public abstract class AdaptiveTypedBaseElementConverter : JsonConverter { - protected ParseContext parseContext; - public ParseContext ParseContext - { - get => parseContext; - set - { - parseContext = value; - } - } + public ParseContext ParseContext { get; set; } = new ParseContext(); } } diff --git a/source/dotnet/Library/AdaptiveCards/ParseContext.cs b/source/dotnet/Library/AdaptiveCards/ParseContext.cs index 444213896b..0419f173f1 100644 --- a/source/dotnet/Library/AdaptiveCards/ParseContext.cs +++ b/source/dotnet/Library/AdaptiveCards/ParseContext.cs @@ -1,3 +1,5 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. using System; using System.Collections.Generic; using System.Linq; @@ -25,16 +27,6 @@ public void PushElement(string idJsonProperty, AdaptiveInternalID internalId) idStack.Push(new Tuple(idJsonProperty, internalId, AdaptiveFallbackConverter.IsInFallback)); } - public AdaptiveInternalID PeekElement() - { - if (idStack.Count == 0) - { - // internal id in dot net needs to be revisited tracked via issue 3386 - return new AdaptiveInternalID(); - } - return idStack.Peek().Item2; - } - // Pop the last id off our stack and perform validation public void PopElement() { diff --git a/source/dotnet/Test/AdaptiveCards.Test/AdaptiveCardApiTests.cs b/source/dotnet/Test/AdaptiveCards.Test/AdaptiveCardApiTests.cs index 4c78a06dc7..72f019fd14 100644 --- a/source/dotnet/Test/AdaptiveCards.Test/AdaptiveCardApiTests.cs +++ b/source/dotnet/Test/AdaptiveCards.Test/AdaptiveCardApiTests.cs @@ -1245,6 +1245,7 @@ public void TestParsingRichTextBlockWithInvalidInlineType() StringAssert.Contains(ex.Message, "Property 'type' must be 'TextRun'"); } + void RenderCardTask(string payload) { AdaptiveCardParseResult parseResult = AdaptiveCard.FromJson(payload); @@ -1392,7 +1393,7 @@ public void TestConcurrentParsing() { // it's perfectly fine card. if there is exception, it is due to concurreny // as it's the only variable. - Assert.Fail(); + Assert.Fail("Unexpected failure parsing a valid AdaptiveCard"); } } } From 0e499916b8e94354ab340c2f7053805efc538f60 Mon Sep 17 00:00:00 2001 From: nesalang Date: Fri, 1 Nov 2019 12:51:02 -0700 Subject: [PATCH 06/19] [.NET] Added missing pieces from previous commit --- source/dotnet/Library/AdaptiveCards/AdaptiveImage.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/source/dotnet/Library/AdaptiveCards/AdaptiveImage.cs b/source/dotnet/Library/AdaptiveCards/AdaptiveImage.cs index e182d511db..7dbb9fa3a7 100644 --- a/source/dotnet/Library/AdaptiveCards/AdaptiveImage.cs +++ b/source/dotnet/Library/AdaptiveCards/AdaptiveImage.cs @@ -35,6 +35,7 @@ public AdaptiveImage(Uri url) #if !NETSTANDARD1_3 [XmlIgnore] #endif + [JsonProperty(Required = Required.Default)] public override string Type { get; set; } = TypeName; /// From dc5c4c506625d76cd94382141d7ff6532cab5f22 Mon Sep 17 00:00:00 2001 From: Joseph Woo Date: Thu, 24 Oct 2019 16:17:27 -0700 Subject: [PATCH 07/19] =?UTF-8?q?[iOS]=20Added=20Custom=20Parsing=20and=20?= =?UTF-8?q?allowed=20signal=20to=20be=20passed=20to=20host=20app=20from?= =?UTF-8?q?=E2=80=A6=20(#3526)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Added Custom Parsing and allowed signal to be passed to host app from custom action in SelectAction * updated with CR fixes * fixed for CR comments * addresssing CR comments --- .../ADCIOSVisualizer/CustomActionNewType.mm | 47 +-- .../ADCIOSVisualizer/ViewController.m | 7 +- .../ADCIOSVisualizerTests.mm | 267 +++++++++++------- .../AdaptiveCards/ACOBaseActionElement.h | 2 +- .../AdaptiveCards/ACRAggregateTarget.h | 7 +- .../AdaptiveCards/ACRAggregateTarget.mm | 13 +- .../AdaptiveCards/ACRCustomActionRenderer.mm | 45 ++- .../AdaptiveCards/ACRTargetBuilderDirector.mm | 106 ++++--- .../AdaptiveCards/AdaptiveCards/UtiliOS.h | 15 +- .../AdaptiveCards/AdaptiveCards/UtiliOS.mm | 205 ++++++++++++-- 10 files changed, 466 insertions(+), 248 deletions(-) diff --git a/source/ios/AdaptiveCards/ADCIOSVisualizer/ADCIOSVisualizer/CustomActionNewType.mm b/source/ios/AdaptiveCards/ADCIOSVisualizer/ADCIOSVisualizer/CustomActionNewType.mm index 960d6c933b..0edff1606d 100644 --- a/source/ios/AdaptiveCards/ADCIOSVisualizer/ADCIOSVisualizer/CustomActionNewType.mm +++ b/source/ios/AdaptiveCards/ADCIOSVisualizer/ADCIOSVisualizer/CustomActionNewType.mm @@ -7,13 +7,14 @@ #import "CustomActionNewType.h" #import +#import #import @implementation CustomActionNewType -- (ACOBaseActionElement *)deserialize:(NSData *)json parseContext:(ACOParseContext* )parseContext; +- (ACOBaseActionElement *)deserialize:(NSData *)json parseContext:(ACOParseContext *)parseContext; { - if(json) { + if (json) { NSDictionary *data = [NSJSONSerialization JSONObjectWithData:json options:NSJSONReadingMutableLeaves error:nil]; NSNumber *red = data[@"Red"]; @@ -31,7 +32,7 @@ - (ACOBaseActionElement *)deserialize:(NSData *)json parseContext:(ACOParseConte newTypeAction.color = [UIColor colorWithRed:red.doubleValue / 255.0 green:green.doubleValue / 255.0 blue:blue.doubleValue / 255.0 alpha:1.0]; newTypeAction.cornerradius = cornerRadius.integerValue; newTypeAction.alertMessage = data[@"alertMessage"]; - + newTypeAction.type = ACRUnknownAction; return newTypeAction; @@ -41,40 +42,7 @@ - (ACOBaseActionElement *)deserialize:(NSData *)json parseContext:(ACOParseConte @end -@interface AlertTarget:NSObject - -@property(weak) ACRView *rootView; -@property CustomActionNewType *action; - -- (instancetype)init:(ACRView *)rootView action:(CustomActionNewType *)action; - -- (IBAction)send:(UIButton *)sender; - -@end - -@implementation AlertTarget - -- (instancetype)init:(ACRView *)rootView action:(CustomActionNewType *)action -{ - self = [super init]; - if(self) { - self.rootView = rootView; - self.action = action; - } - return self; -} - -- (IBAction)send:(UIButton *)sender -{ - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"successfully rendered new button type" message:_action.alertMessage preferredStyle:UIAlertControllerStyleAlert]; - [alertController addAction:[UIAlertAction actionWithTitle:@"Dismiss" style:UIAlertActionStyleDefault handler:nil]]; - _action.alertController = alertController; - [_rootView.acrActionDelegate didFetchUserResponses:nil action:_action]; -} - -@end - -@implementation CustomActionNewTypeRenderer:ACRBaseActionElementRenderer +@implementation CustomActionNewTypeRenderer : ACRBaseActionElementRenderer + (CustomActionNewTypeRenderer *)getInstance { @@ -88,14 +56,15 @@ - (UIButton *)renderButton:(ACRView *)rootView baseActionElement:(ACOBaseActionElement *)acoElem hostConfig:(ACOHostConfig *)acoConfig { - NSBundle* bundle = [NSBundle bundleWithIdentifier:@"MSFT.AdaptiveCards"]; + NSBundle *bundle = [NSBundle bundleWithIdentifier:@"MSFT.AdaptiveCards"]; ACRButton *button = [bundle loadNibNamed:@"ACRButton" owner:rootView options:nil][0]; [button setTitle:acoElem.title forState:UIControlStateNormal]; CustomActionNewType *newType = (CustomActionNewType *)acoElem; button.backgroundColor = newType.color; button.layer.cornerRadius = newType.cornerradius; - AlertTarget *target = [[AlertTarget alloc] init:rootView action:newType]; + // ACRAggregateTarget relays signal (event) back to host app via ACRActionDelegate + ACRAggregateTarget *target = [[ACRAggregateTarget alloc] initWithActionElement:newType rootView:rootView]; [button addTarget:target action:@selector(send:) forControlEvents:UIControlEventTouchUpInside]; [superview addTarget:target]; diff --git a/source/ios/AdaptiveCards/ADCIOSVisualizer/ADCIOSVisualizer/ViewController.m b/source/ios/AdaptiveCards/ADCIOSVisualizer/ADCIOSVisualizer/ViewController.m index 4e17620b5e..5d3b544897 100644 --- a/source/ios/AdaptiveCards/ADCIOSVisualizer/ADCIOSVisualizer/ViewController.m +++ b/source/ios/AdaptiveCards/ADCIOSVisualizer/ADCIOSVisualizer/ViewController.m @@ -374,9 +374,10 @@ - (void)didFetchUserResponses:(ACOAdaptiveCard *)card action:(ACOBaseActionEleme } else if (action.type == ACRUnknownAction) { if ([action isKindOfClass:[CustomActionNewType class]]) { CustomActionNewType *newType = (CustomActionNewType *)action; - if (newType.alertController) { - [self presentViewController:newType.alertController animated:YES completion:nil]; - } + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"successfully rendered new button type" message:newType.alertMessage preferredStyle:UIAlertControllerStyleAlert]; + [alertController addAction:[UIAlertAction actionWithTitle:@"Dismiss" style:UIAlertActionStyleDefault handler:nil]]; + newType.alertController = alertController; + [self presentViewController:alertController animated:YES completion:nil]; } } } diff --git a/source/ios/AdaptiveCards/ADCIOSVisualizer/ADCIOSVisualizerTests/ADCIOSVisualizerTests.mm b/source/ios/AdaptiveCards/ADCIOSVisualizer/ADCIOSVisualizerTests/ADCIOSVisualizerTests.mm index b93f77b2d6..3ba53af736 100644 --- a/source/ios/AdaptiveCards/ADCIOSVisualizer/ADCIOSVisualizerTests/ADCIOSVisualizerTests.mm +++ b/source/ios/AdaptiveCards/ADCIOSVisualizer/ADCIOSVisualizerTests/ADCIOSVisualizerTests.mm @@ -6,36 +6,37 @@ // Copyright © 2017 Microsoft. All rights reserved. // -#import -#import -#import #import "AdaptiveCards/ACORemoteResourceInformationPrivate.h" +#import "AdaptiveCards/ACRShowCardTarget.h" #import "AdaptiveCards/ACRViewPrivate.h" #import "AdaptiveCards/ShowCardAction.h" #import "AdaptiveCards/TextBlock.h" -#import "AdaptiveCards/ACRShowCardTarget.h" #import "AdaptiveCards/UtiliOS.h" +#import "CustomActionNewType.h" +#import +#import +#import @interface ADCIOSVisualizerTests : XCTestCase @end -@implementation ADCIOSVisualizerTests -{ +@implementation ADCIOSVisualizerTests { NSBundle *_mainBundle; NSString *_defaultHostConfigFile; ACOHostConfig *_defaultHostConfig; } -- (void)setUp { +- (void)setUp +{ [super setUp]; _mainBundle = [NSBundle mainBundle]; _defaultHostConfigFile = [NSString stringWithContentsOfFile:[_mainBundle pathForResource:@"sample" ofType:@"json"] - encoding:NSUTF8StringEncoding - error:nil]; - if(_defaultHostConfigFile){ + encoding:NSUTF8StringEncoding + error:nil]; + if (_defaultHostConfigFile) { ACOHostConfigParseResult *hostconfigParseResult = [ACOHostConfig fromJson:_defaultHostConfigFile resourceResolvers:nil]; - if(hostconfigParseResult.isValid){ + if (hostconfigParseResult.isValid) { _defaultHostConfig = hostconfigParseResult.config; } } @@ -46,7 +47,7 @@ - (void)setUp { - (NSArray *)prepCards:(NSArray *)fileNames { NSMutableArray *cards = [[NSMutableArray alloc] init]; - for(NSString *fileName in fileNames){ + for (NSString *fileName in fileNames) { NSString *payload = [NSString stringWithContentsOfFile:[_mainBundle pathForResource:fileName ofType:@"json"] encoding:NSUTF8StringEncoding error:nil]; ACOAdaptiveCardParseResult *cardParseResult = [ACOAdaptiveCard fromJson:payload]; XCTAssertTrue(cardParseResult.isValid); @@ -55,15 +56,20 @@ - (void)setUp { return cards; } -- (void)tearDown { +- (void)tearDown +{ // Put teardown code here. This method is called after the invocation of each test method in the class. _mainBundle = nil; _defaultHostConfigFile = nil; _defaultHostConfig = nil; + [[ACRRegistration getInstance] setBaseCardElementRenderer:nil cardElementType:ACRCardElementType::ACRTextBlock]; + [[ACRRegistration getInstance] setBaseCardElementRenderer:nil cardElementType:ACRCardElementType::ACRRichTextBlock]; + [[ACRRegistration getInstance] setBaseCardElementRenderer:nil cardElementType:ACRCardElementType::ACRFactSet]; [super tearDown]; } -- (void)testRemoteResouceInformation { +- (void)testRemoteResouceInformation +{ // This is an example of a functional test case. // Use XCTAssert and related functions to verify your tests produce the correct results. @@ -79,13 +85,14 @@ - (void)testRemoteResouceInformation { @"http://contososcubademo.azurewebsites.net/assets/tofu.jpg" ]; unsigned int index = 0; - for(ACORemoteResourceInformation *info in remoteInformation){ + for (ACORemoteResourceInformation *info in remoteInformation) { XCTAssertTrue([[testStrings objectAtIndex:index++] isEqualToString:info.url.absoluteString]); XCTAssertTrue([@"image" isEqualToString:info.mimeType]); } } -- (void)testRelativeURLInformation { +- (void)testRelativeURLInformation +{ NSString *payload = [NSString stringWithContentsOfFile:[_mainBundle pathForResource:@"Image.ImageBaseUrl" ofType:@"json"] encoding:NSUTF8StringEncoding error:nil]; ACOAdaptiveCardParseResult *cardParseResult = [ACOAdaptiveCard fromJson:payload]; XCTAssertTrue(cardParseResult.isValid); @@ -93,7 +100,8 @@ - (void)testRelativeURLInformation { XCTAssertTrue([_defaultHostConfig.baseURL.absoluteString isEqualToString:@"https://pbs.twimg.com/profile_images/3647943215/"]); } -- (void)testACRTextView { +- (void)testACRTextView +{ NSString *payload = [NSString stringWithContentsOfFile:[_mainBundle pathForResource:@"Feedback" ofType:@"json"] encoding:NSUTF8StringEncoding error:nil]; ACOAdaptiveCardParseResult *cardParseResult = [ACOAdaptiveCard fromJson:payload]; XCTAssertTrue(cardParseResult && cardParseResult.isValid); @@ -104,24 +112,46 @@ - (void)testACRTextView { XCTAssertTrue([acrTextView.text length] == 0); } -- (void)testBuildingShowCardTarget { +// this test ensure that extending text render doesn't crash +// in use case where custom renderer uses default text renderer +- (void)testExtendingTextRenderersDoesNotCrash +{ + ACRRegistration *registration = [ACRRegistration getInstance]; + [registration setBaseCardElementRenderer:[ACRTextBlockRenderer getInstance] cardElementType:ACRCardElementType::ACRTextBlock]; + [registration setBaseCardElementRenderer:[ACRRichTextBlockRenderer getInstance] cardElementType:ACRCardElementType::ACRRichTextBlock]; + [registration setBaseCardElementRenderer:[ACRFactSetRenderer getInstance] cardElementType:ACRCardElementType::ACRFactSet]; + + // TextBlock.Maxlines is used for testing when text block renderer is overriden + // RichTextBlock tests for RichTextBlock renderer extension + // ActivityUpdate tests TextBlock & FactSet extension combinations + NSArray *payloadNames = @[ @"TextBlock.MaxLines", @"RichTextBlock", @"ActivityUpdate" ]; + + NSArray *cards = [self prepCards:payloadNames]; + for (ACOAdaptiveCard *card in cards) { + ACRRenderResult *renderResult = [ACRRenderer render:card config:self->_defaultHostConfig widthConstraint:320.0]; + XCTAssertNotNil(renderResult.view); + } +} + +- (void)testBuildingShowCardTarget +{ NSString *payload = [NSString stringWithContentsOfFile:[_mainBundle pathForResource:@"Feedback" ofType:@"json"] encoding:NSUTF8StringEncoding error:nil]; ACOAdaptiveCardParseResult *cardParseResult = [ACOAdaptiveCard fromJson:payload]; XCTAssertTrue(cardParseResult && cardParseResult.isValid); ACRRenderResult *renderResult = [ACRRenderer render:cardParseResult.card config:_defaultHostConfig widthConstraint:335]; - + ACRView *testView = renderResult.view; std::shared_ptr textblock = std::make_shared(); textblock->SetText("happy testing"); - + std::shared_ptr innerCard = std::make_shared(); innerCard->GetBody().push_back(textblock); - + std::shared_ptr action = std::make_shared(); action->SetCard(innerCard); NSObject *target; UIButton *button = [UIButton buttonWithType:UIButtonType::UIButtonTypeSystem]; - + XCTAssert(ACRRenderingStatus::ACRFailed == buildTarget([testView getSelectActionsTargetBuilderDirector], action, &target)); XCTAssert(ACRRenderingStatus::ACRFailed == buildTarget([testView getQuickReplyTargetBuilderDirector], action, &target)); @@ -130,20 +160,21 @@ - (void)testBuildingShowCardTarget { XCTAssert(ACRRenderingStatus::ACRFailed == buildTarget([testView getActionsTargetBuilderDirector], action, &target)); XCTAssert(ACRRenderingStatus::ACROk == buildTargetForButton([testView getActionsTargetBuilderDirector], action, button, &target)); - + XCTAssertNotNil(target); - + XCTAssertTrue([target respondsToSelector:@selector(createShowCard:superview:)]); } -- (void)testChoiceSetInputCanGatherDefaultValues { +- (void)testChoiceSetInputCanGatherDefaultValues +{ NSString *payload = [NSString stringWithContentsOfFile:[_mainBundle pathForResource:@"Input.ChoiceSet" ofType:@"json"] encoding:NSUTF8StringEncoding error:nil]; NSDictionary *expectedValue = @{ @"myColor" : @"1", @"myColor3" : @"1,3", @"myColor2" : @"1", @"myColor4" : @"1" - }; + }; NSData *expectedData = [NSJSONSerialization dataWithJSONObject:expectedValue options:NSJSONWritingPrettyPrinted error:nil]; NSString *expectedString = [[NSString alloc] initWithData:expectedData encoding:NSUTF8StringEncoding]; ACOAdaptiveCardParseResult *cardParseResult = [ACOAdaptiveCard fromJson:payload]; @@ -155,255 +186,283 @@ - (void)testChoiceSetInputCanGatherDefaultValues { XCTAssertTrue([str isEqualToString:expectedString]); } -- (void)testPerformanceOnComplicatedCards { +- (void)testPerformanceOnComplicatedCards +{ // This is an example of a performance test case. - NSArray *payloadNames = @[@"Restaurant", @"FoodOrder", @"ActivityUpdate"]; + NSArray *payloadNames = @[ @"Restaurant", @"FoodOrder", @"ActivityUpdate" ]; NSArray *cards = [self prepCards:payloadNames]; [self measureBlock:^{ // Put the code you want to measure the time of here. - for(ACOAdaptiveCard *card in cards) { + for (ACOAdaptiveCard *card in cards) { [ACRRenderer render:card config:self->_defaultHostConfig widthConstraint:320.0]; } }]; } -- (void)testPerformanceOnRestaurant { +- (void)testPerformanceOnRestaurant +{ // This is an example of a performance test case. - NSArray *payloadNames = @[@"Restaurant"]; + NSArray *payloadNames = @[ @"Restaurant" ]; NSArray *cards = [self prepCards:payloadNames]; [self measureBlock:^{ // Put the code you want to measure the time of here. - for(ACOAdaptiveCard *card in cards) { + for (ACOAdaptiveCard *card in cards) { [ACRRenderer render:card config:self->_defaultHostConfig widthConstraint:320.0]; } }]; } -- (void)testPerformanceOnFoodOrder { +- (void)testPerformanceOnFoodOrder +{ // This is an example of a performance test case. - NSArray *payloadNames = @[@"FoodOrder"]; + NSArray *payloadNames = @[ @"FoodOrder" ]; NSArray *cards = [self prepCards:payloadNames]; [self measureBlock:^{ // Put the code you want to measure the time of here. - for(ACOAdaptiveCard *card in cards) { + for (ACOAdaptiveCard *card in cards) { [ACRRenderer render:card config:self->_defaultHostConfig widthConstraint:320.0]; } }]; } -- (void)testPerformanceOnActivityUpdate { +- (void)testPerformanceOnActivityUpdate +{ // This is an example of a performance test case. - NSArray *payloadNames = @[@"ActivityUpdate"]; + NSArray *payloadNames = @[ @"ActivityUpdate" ]; NSArray *cards = [self prepCards:payloadNames]; [self measureBlock:^{ // Put the code you want to measure the time of here. - for(ACOAdaptiveCard *card in cards) { + for (ACOAdaptiveCard *card in cards) { [ACRRenderer render:card config:self->_defaultHostConfig widthConstraint:320.0]; } }]; } -- (void)testPerformanceOnSimpleCardsTextBlock { +- (void)testPerformanceOnSimpleCardsTextBlock +{ // This is an example of a performance test case. - NSArray *payloadNames = @[@"TextBlock.MaxLines", @"TextBlock.Wrap", @"TextBlock.HorizontalAlignment"]; + NSArray *payloadNames = @[ @"TextBlock.MaxLines", @"TextBlock.Wrap", @"TextBlock.HorizontalAlignment" ]; NSArray *cards = [self prepCards:payloadNames]; [self measureBlock:^{ // Put the code you want to measure the time of here. - for(ACOAdaptiveCard *card in cards) { + for (ACOAdaptiveCard *card in cards) { [ACRRenderer render:card config:self->_defaultHostConfig widthConstraint:320.0]; } }]; } -- (void)testPerformanceOnSimpleCardsTextBlockColor { +- (void)testPerformanceOnSimpleCardsTextBlockColor +{ // This is an example of a performance test case. - NSArray *payloadNames = @[@"TextBlock.Color"]; + NSArray *payloadNames = @[ @"TextBlock.Color" ]; NSArray *cards = [self prepCards:payloadNames]; [self measureBlock:^{ // Put the code you want to measure the time of here. - for(ACOAdaptiveCard *card in cards) { + for (ACOAdaptiveCard *card in cards) { [ACRRenderer render:card config:self->_defaultHostConfig widthConstraint:320.0]; } }]; } -- (void)testPerformanceOnSimpleCardsTextBlockDateTimeFormatting{ +- (void)testPerformanceOnSimpleCardsTextBlockDateTimeFormatting +{ // This is an example of a performance test case. - NSArray *payloadNames = @[@"TextBlock.DateTimeFormatting"]; + NSArray *payloadNames = @[ @"TextBlock.DateTimeFormatting" ]; NSArray *cards = [self prepCards:payloadNames]; [self measureBlock:^{ // Put the code you want to measure the time of here. - for(ACOAdaptiveCard *card in cards) { + for (ACOAdaptiveCard *card in cards) { [ACRRenderer render:card config:self->_defaultHostConfig widthConstraint:320.0]; } }]; } -- (void)testPerformanceOnSimpleCardsTextBlockHorizontalAlignment{ +- (void)testPerformanceOnSimpleCardsTextBlockHorizontalAlignment +{ // This is an example of a performance test case. - NSArray *payloadNames = @[@"TextBlock.HorizontalAlignment"]; + NSArray *payloadNames = @[ @"TextBlock.HorizontalAlignment" ]; NSArray *cards = [self prepCards:payloadNames]; [self measureBlock:^{ // Put the code you want to measure the time of here. - for(ACOAdaptiveCard *card in cards) { + for (ACOAdaptiveCard *card in cards) { [ACRRenderer render:card config:self->_defaultHostConfig widthConstraint:320.0]; } }]; } -- (void)testPerformanceOnSimpleCardsTextBlockSubtle{ +- (void)testPerformanceOnSimpleCardsTextBlockSubtle +{ // This is an example of a performance test case. - NSArray *payloadNames = @[@"TextBlock.IsSubtle"]; + NSArray *payloadNames = @[ @"TextBlock.IsSubtle" ]; NSArray *cards = [self prepCards:payloadNames]; [self measureBlock:^{ // Put the code you want to measure the time of here. - for(ACOAdaptiveCard *card in cards) { + for (ACOAdaptiveCard *card in cards) { [ACRRenderer render:card config:self->_defaultHostConfig widthConstraint:320.0]; } }]; } -- (void)testPerformanceOnSimpleCardsTextBlockLists{ +- (void)testPerformanceOnSimpleCardsTextBlockLists +{ // This is an example of a performance test case. - NSArray *payloadNames = @[@"TextBlock.Lists"]; + NSArray *payloadNames = @[ @"TextBlock.Lists" ]; NSArray *cards = [self prepCards:payloadNames]; [self measureBlock:^{ // Put the code you want to measure the time of here. - for(ACOAdaptiveCard *card in cards) { + for (ACOAdaptiveCard *card in cards) { [ACRRenderer render:card config:self->_defaultHostConfig widthConstraint:320.0]; } }]; } -- (void)testPerformanceOnSimpleCardsTextBlockMarkDown{ +- (void)testPerformanceOnSimpleCardsTextBlockMarkDown +{ // This is an example of a performance test case. - NSArray *payloadNames = @[@"TextBlock.Markdown"]; + NSArray *payloadNames = @[ @"TextBlock.Markdown" ]; NSArray *cards = [self prepCards:payloadNames]; [self measureBlock:^{ // Put the code you want to measure the time of here. - for(ACOAdaptiveCard *card in cards) { + for (ACOAdaptiveCard *card in cards) { [ACRRenderer render:card config:self->_defaultHostConfig widthConstraint:320.0]; } }]; } -- (void)testPerformanceOnSimpleCardsTextBlockMaxLines{ +- (void)testPerformanceOnSimpleCardsTextBlockMaxLines +{ // This is an example of a performance test case. - NSArray *payloadNames = @[@"TextBlock.MaxLines"]; + NSArray *payloadNames = @[ @"TextBlock.MaxLines" ]; NSArray *cards = [self prepCards:payloadNames]; [self measureBlock:^{ // Put the code you want to measure the time of here. - for(ACOAdaptiveCard *card in cards) { + for (ACOAdaptiveCard *card in cards) { [ACRRenderer render:card config:self->_defaultHostConfig widthConstraint:320.0]; } }]; } -- (void)testPerformanceOnSimpleCardsTextBlockSize{ +- (void)testPerformanceOnSimpleCardsTextBlockSize +{ // This is an example of a performance test case. - NSArray *payloadNames = @[@"TextBlock.Size"]; + NSArray *payloadNames = @[ @"TextBlock.Size" ]; NSArray *cards = [self prepCards:payloadNames]; [self measureBlock:^{ // Put the code you want to measure the time of here. - for(ACOAdaptiveCard *card in cards) { + for (ACOAdaptiveCard *card in cards) { [ACRRenderer render:card config:self->_defaultHostConfig widthConstraint:320.0]; } }]; } -- (void)testPerformanceOnSimpleCardsTextBlockSpacing{ +- (void)testPerformanceOnSimpleCardsTextBlockSpacing +{ // This is an example of a performance test case. - NSArray *payloadNames = @[@"TextBlock.Spacing"]; + NSArray *payloadNames = @[ @"TextBlock.Spacing" ]; NSArray *cards = [self prepCards:payloadNames]; [self measureBlock:^{ // Put the code you want to measure the time of here. - for(ACOAdaptiveCard *card in cards) { + for (ACOAdaptiveCard *card in cards) { [ACRRenderer render:card config:self->_defaultHostConfig widthConstraint:320.0]; } }]; } -- (void)testPerformanceOnSimpleCardsTextBlockWeight{ +- (void)testPerformanceOnSimpleCardsTextBlockWeight +{ // This is an example of a performance test case. - NSArray *payloadNames = @[@"TextBlock.Weight"]; + NSArray *payloadNames = @[ @"TextBlock.Weight" ]; NSArray *cards = [self prepCards:payloadNames]; [self measureBlock:^{ // Put the code you want to measure the time of here. - for(ACOAdaptiveCard *card in cards) { + for (ACOAdaptiveCard *card in cards) { [ACRRenderer render:card config:self->_defaultHostConfig widthConstraint:320.0]; } }]; } -- (void)testPerformanceOnSimpleCardsTextBlockWrap{ +- (void)testPerformanceOnSimpleCardsTextBlockWrap +{ // This is an example of a performance test case. - NSArray *payloadNames = @[@"TextBlock.Wrap"]; + NSArray *payloadNames = @[ @"TextBlock.Wrap" ]; NSArray *cards = [self prepCards:payloadNames]; [self measureBlock:^{ // Put the code you want to measure the time of here. - for(ACOAdaptiveCard *card in cards) { + for (ACOAdaptiveCard *card in cards) { [ACRRenderer render:card config:self->_defaultHostConfig widthConstraint:320.0]; } }]; } -- (void)testPerformanceOnSimpleCardsImage{ +- (void)testPerformanceOnSimpleCardsImage +{ // This is an example of a performance test case. - NSArray *payloadNames = @[@"Image.Size", @"Image.Spacing", @"Image.Style", @"Image"]; + NSArray *payloadNames = @[ @"Image.Size", @"Image.Spacing", @"Image.Style", @"Image" ]; NSArray *cards = [self prepCards:payloadNames]; [self measureBlock:^{ // Put the code you want to measure the time of here. - for(ACOAdaptiveCard *card in cards) { + for (ACOAdaptiveCard *card in cards) { [ACRRenderer render:card config:self->_defaultHostConfig widthConstraint:320.0]; } }]; } -- (void)testPerformanceOnSimpleCardsColumnSet{ +- (void)testPerformanceOnSimpleCardsColumnSet +{ // This is an example of a performance test case. - NSArray *payloadNames = @[@"ColumnSet.Spacing", @"ColumnSet"]; + NSArray *payloadNames = @[ @"ColumnSet.Spacing", @"ColumnSet" ]; NSArray *cards = [self prepCards:payloadNames]; [self measureBlock:^{ // Put the code you want to measure the time of here. - for(ACOAdaptiveCard *card in cards) { + for (ACOAdaptiveCard *card in cards) { [ACRRenderer render:card config:self->_defaultHostConfig widthConstraint:320.0]; } }]; } -- (void)testPerformanceOnSimpleCardsColumn{ +- (void)testPerformanceOnSimpleCardsColumn +{ // This is an example of a performance test case. - NSArray *payloadNames = @[@"Column.Size.Ratio", @"Column.Spacing", @"Column.Width.Ratio", @"Column.Width", @"Column"]; + NSArray *payloadNames = @[ @"Column.Size.Ratio", @"Column.Spacing", @"Column.Width.Ratio", @"Column.Width", @"Column" ]; NSArray *cards = [self prepCards:payloadNames]; [self measureBlock:^{ // Put the code you want to measure the time of here. - for(ACOAdaptiveCard *card in cards) { + for (ACOAdaptiveCard *card in cards) { [ACRRenderer render:card config:self->_defaultHostConfig widthConstraint:320.0]; } }]; } -- (void)testSharedEnumsCompatabilityTest{ - XCTAssertTrue(static_cast(AdaptiveCards::CardElementType::ActionSet) == ACRActionSet); - XCTAssertTrue(static_cast(AdaptiveCards::CardElementType::AdaptiveCard) == ACRAdaptiveCard); - XCTAssertTrue(static_cast(AdaptiveCards::CardElementType::ChoiceInput) == ACRChoiceInput); - XCTAssertTrue(static_cast(AdaptiveCards::CardElementType::ChoiceSetInput) == ACRChoiceSetInput); - XCTAssertTrue(static_cast(AdaptiveCards::CardElementType::Column) == ACRColumn); - XCTAssertTrue(static_cast(AdaptiveCards::CardElementType::ColumnSet) == ACRColumnSet); - XCTAssertTrue(static_cast(AdaptiveCards::CardElementType::Container) == ACRContainer); - XCTAssertTrue(static_cast(AdaptiveCards::CardElementType::Custom) == ACRCustom); - XCTAssertTrue(static_cast(AdaptiveCards::CardElementType::DateInput) == ACRDateInput); - XCTAssertTrue(static_cast(AdaptiveCards::CardElementType::Fact) == ACRFact); - XCTAssertTrue(static_cast(AdaptiveCards::CardElementType::FactSet) == ACRFactSet); - XCTAssertTrue(static_cast(AdaptiveCards::CardElementType::Image) == ACRImage); - XCTAssertTrue(static_cast(AdaptiveCards::CardElementType::ImageSet) == ACRImageSet); - XCTAssertTrue(static_cast(AdaptiveCards::CardElementType::Media) == ACRMedia); - XCTAssertTrue(static_cast(AdaptiveCards::CardElementType::NumberInput) == ACRNumberInput); - XCTAssertTrue(static_cast(AdaptiveCards::CardElementType::TextBlock) == ACRTextBlock); - XCTAssertTrue(static_cast(AdaptiveCards::CardElementType::TextInput) == ACRTextInput); - XCTAssertTrue(static_cast(AdaptiveCards::CardElementType::TimeInput) == ACRTimeInput); - XCTAssertTrue(static_cast(AdaptiveCards::CardElementType::ToggleInput) == ACRToggleInput); - XCTAssertTrue(static_cast(AdaptiveCards::CardElementType::Unknown) == ACRUnknown); +- (void)testSharedEnumsCompatabilityWithiOSSDKEnums +{ + // The below Enums from shared model's numeric values should be in sync with + // iOS SDK's enum. + XCTAssertTrue(static_cast(AdaptiveCards::CardElementType::ActionSet) == ACRActionSet); + XCTAssertTrue(static_cast(AdaptiveCards::CardElementType::AdaptiveCard) == ACRAdaptiveCard); + XCTAssertTrue(static_cast(AdaptiveCards::CardElementType::ChoiceInput) == ACRChoiceInput); + XCTAssertTrue(static_cast(AdaptiveCards::CardElementType::ChoiceSetInput) == ACRChoiceSetInput); + XCTAssertTrue(static_cast(AdaptiveCards::CardElementType::Column) == ACRColumn); + XCTAssertTrue(static_cast(AdaptiveCards::CardElementType::ColumnSet) == ACRColumnSet); + XCTAssertTrue(static_cast(AdaptiveCards::CardElementType::Container) == ACRContainer); + XCTAssertTrue(static_cast(AdaptiveCards::CardElementType::Custom) == ACRCustom); + XCTAssertTrue(static_cast(AdaptiveCards::CardElementType::DateInput) == ACRDateInput); + XCTAssertTrue(static_cast(AdaptiveCards::CardElementType::Fact) == ACRFact); + XCTAssertTrue(static_cast(AdaptiveCards::CardElementType::FactSet) == ACRFactSet); + XCTAssertTrue(static_cast(AdaptiveCards::CardElementType::Image) == ACRImage); + XCTAssertTrue(static_cast(AdaptiveCards::CardElementType::ImageSet) == ACRImageSet); + XCTAssertTrue(static_cast(AdaptiveCards::CardElementType::Media) == ACRMedia); + XCTAssertTrue(static_cast(AdaptiveCards::CardElementType::NumberInput) == ACRNumberInput); + XCTAssertTrue(static_cast(AdaptiveCards::CardElementType::TextBlock) == ACRTextBlock); + XCTAssertTrue(static_cast(AdaptiveCards::CardElementType::TextInput) == ACRTextInput); + XCTAssertTrue(static_cast(AdaptiveCards::CardElementType::TimeInput) == ACRTimeInput); + XCTAssertTrue(static_cast(AdaptiveCards::CardElementType::ToggleInput) == ACRToggleInput); + XCTAssertTrue(static_cast(AdaptiveCards::CardElementType::Unknown) == ACRUnknown); + XCTAssertTrue(static_cast(AdaptiveCards::CardElementType::RichTextBlock) == ACRRichTextBlock); + XCTAssertTrue(static_cast(AdaptiveCards::ActionType::ShowCard) == ACRShowCard); + XCTAssertTrue(static_cast(AdaptiveCards::ActionType::Submit) == ACRSubmit); + XCTAssertTrue(static_cast(AdaptiveCards::ActionType::OpenUrl) == ACROpenUrl); + XCTAssertTrue(static_cast(AdaptiveCards::ActionType::ToggleVisibility) == ACRToggleVisibility); + XCTAssertTrue(static_cast(AdaptiveCards::ActionType::UnknownAction) == ACRUnknownAction); } @end diff --git a/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACOBaseActionElement.h b/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACOBaseActionElement.h index 7c1818d8c2..2020d3e677 100644 --- a/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACOBaseActionElement.h +++ b/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACOBaseActionElement.h @@ -17,7 +17,7 @@ typedef NS_ENUM(NSInteger, ACRActionType) { ACRSubmit, ACROpenUrl, ACRToggleVisibility, - ACRUnknownAction, + ACRUnknownAction = 6, }; typedef NS_ENUM(NSInteger, ACRIconPlacement) { diff --git a/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRAggregateTarget.h b/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRAggregateTarget.h index 1af4a19ce1..b39670587a 100644 --- a/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRAggregateTarget.h +++ b/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRAggregateTarget.h @@ -5,14 +5,13 @@ // Copyright © 2018 Microsoft. All rights reserved. // -#import #import "ACRIContentHoldingView.h" -#import "SharedAdaptiveCard.h" -#import "HostConfig.h" #import "ACRLongPressGestureRecognizerEventHandler.h" #import "ACRView.h" +#import -@interface ACRAggregateTarget:NSObject +// AggregateTraget is used to relay the signal back to host +@interface ACRAggregateTarget : NSObject - (instancetype)initWithActionElement:(ACOBaseActionElement *)actionElement rootView:(ACRView *)rootView; diff --git a/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRAggregateTarget.mm b/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRAggregateTarget.mm index e373a340f5..4fbcd77b31 100644 --- a/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRAggregateTarget.mm +++ b/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRAggregateTarget.mm @@ -5,25 +5,24 @@ // Copyright © 2018 Microsoft. All rights reserved. // -#import #import "ACRAggregateTarget.h" +#import "ACOBaseActionElementPrivate.h" #import "ACRContentHoldingUIView.h" #import "ACRIBaseInputHandler.h" #import "ACRView.h" #import "ACRViewController.h" -#import "ACOBaseActionElementPrivate.h" +#import -@implementation ACRAggregateTarget -{ +@implementation ACRAggregateTarget { ACOBaseActionElement *_actionElement; __weak ACRView *_view; } -- (instancetype)initWithActionElement:(ACOBaseActionElement *)actionElement rootView:(ACRView*)rootView; +- (instancetype)initWithActionElement:(ACOBaseActionElement *)actionElement rootView:(ACRView *)rootView; { self = [super init]; - if(self) { - _actionElement = [[ACOBaseActionElement alloc] initWithBaseActionElement:[actionElement element]]; + if (self) { + _actionElement = actionElement; _view = rootView; } return self; diff --git a/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRCustomActionRenderer.mm b/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRCustomActionRenderer.mm index 0ab4b1e661..249ca13a98 100644 --- a/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRCustomActionRenderer.mm +++ b/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRCustomActionRenderer.mm @@ -6,14 +6,17 @@ // #import "ACRCustomActionRenderer.h" -#import "UnknownAction.h" -#import "SharedAdaptiveCard.h" #import "ACOBaseActionElementPrivate.h" -#import "ACRContentHoldingUIView.h" #import "ACOHostConfigPrivate.h" -#import "UtiliOS.h" +#import "ACRContentHoldingUIView.h" #import "ACRRegistration.h" +#import "SharedAdaptiveCard.h" +#import "UnknownAction.h" +#import "UtiliOS.h" +// this is an entry point to custom parsing and rendering +// it will call a registered custom parser to deserialize, then the deserialized object is rendered by calling +// the appropriate custom renderer @implementation ACRCustomActionRenderer + (ACRCustomActionRenderer *)getInstance @@ -27,32 +30,24 @@ + (ACRActionType)elemType return ACRUnknownAction; } -- (UIButton* )renderButton:(ACRView *)view +- (UIButton *)renderButton:(ACRView *)view inputs:(NSMutableArray *)inputs superview:(UIView *)superview baseActionElement:(ACOBaseActionElement *)acoElem hostConfig:(ACOHostConfig *)acoConfig; { - std::shared_ptr customAction = std::dynamic_pointer_cast([acoElem element]); - - ACRRegistration *reg = [ACRRegistration getInstance]; - if(reg) { - NSString *type = [NSString stringWithCString:customAction->GetElementTypeString().c_str() encoding:NSUTF8StringEncoding]; - NSObject *parser = [reg getCustomActionElementParser:type]; - if (!parser) { - @throw [ACOFallbackException fallbackException]; - } - Json::Value blob = customAction->GetAdditionalProperties(); - Json::FastWriter fastWriter; - NSString *jsonString = [[NSString alloc] initWithCString:fastWriter.write(blob).c_str() encoding:NSUTF8StringEncoding]; - if(jsonString.length > 0){ - NSData *jsonPayload = [jsonString dataUsingEncoding:NSUTF8StringEncoding]; - ACOParseContext *context = [reg getParseContext]; - ACOBaseActionElement *actionElement = [parser deserialize:jsonPayload parseContext:context]; - ACRBaseActionElementRenderer *renderer = [reg getActionRenderer:[NSNumber numberWithLong:type.hash]];; - if(renderer) { - return [renderer renderButton:view inputs:inputs superview:superview baseActionElement:actionElement hostConfig:acoConfig]; - } + std::shared_ptr unknownAction = std::dynamic_pointer_cast([acoElem element]); + // we get back a deserialized action object by calling a custom parser registered via host + ACOBaseActionElement *customAction = deserializeUnknownActionToCustomAction(unknownAction); + if (customAction) { + ACRRegistration *reg = [ACRRegistration getInstance]; + NSString *type = [NSString stringWithCString:unknownAction->GetElementTypeString().c_str() encoding:NSUTF8StringEncoding]; + + ACRBaseActionElementRenderer *renderer = [reg getActionRenderer:[NSNumber numberWithLong:type.hash]]; + + if (renderer) { + // render a button by calling custom renderer + return [renderer renderButton:view inputs:inputs superview:superview baseActionElement:customAction hostConfig:acoConfig]; } } return nil; diff --git a/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRTargetBuilderDirector.mm b/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRTargetBuilderDirector.mm index e84bb292ee..81f3fb3c3f 100644 --- a/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRTargetBuilderDirector.mm +++ b/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRTargetBuilderDirector.mm @@ -34,6 +34,7 @@ #import "ACRErrors.h" #import "ACRShowCardTarget.h" #import "ACRToggleVisibilityTarget.h" +#import "UtiliOS.h" // protocol all TargetBuild should implement @protocol ACRITargetBuilder @@ -55,20 +56,29 @@ + (ACRTargetBuilder *)getInstance; @implementation ACRTargetBuilder -+ (ACRTargetBuilder *)getInstance { ++ (ACRTargetBuilder *)getInstance +{ static ACRTargetBuilder *singletonInstance = [[self alloc] init]; return singletonInstance; } - (NSObject *)build:(std::shared_ptr const &)action - director:(ACRTargetBuilderDirector *)director { + director:(ACRTargetBuilderDirector *)director +{ return nil; } - (NSObject *)build:(std::shared_ptr const &)action director:(ACRTargetBuilderDirector *)director - ForButton:(UIButton *)button { - return nil; + ForButton:(UIButton *)button +{ + NSObject *target = [self build:action director:director]; + if (target) { + [button addTarget:target + action:@selector(send:) + forControlEvents:UIControlEventTouchUpInside]; + } + return target; } @end @@ -79,30 +89,20 @@ @interface ACRAggregateTargetBuilder : ACRTargetBuilder @implementation ACRAggregateTargetBuilder -+ (ACRAggregateTargetBuilder *)getInstance { ++ (ACRAggregateTargetBuilder *)getInstance +{ static ACRAggregateTargetBuilder *singletonInstance = [[self alloc] init]; return singletonInstance; } - (NSObject *)build:(std::shared_ptr const &)action - director:(ACRTargetBuilderDirector *)director { + director:(ACRTargetBuilderDirector *)director +{ ACOBaseActionElement *acoElem = [[ACOBaseActionElement alloc] initWithBaseActionElement:action]; return [[ACRAggregateTarget alloc] initWithActionElement:acoElem rootView:director.rootView]; } -- (NSObject *)build:(std::shared_ptr const &)action - director:(ACRTargetBuilderDirector *)director - ForButton:(UIButton *)button { - NSObject *target = [self build:action director:director]; - if (target) { - [button addTarget:target - action:@selector(send:) - forControlEvents:UIControlEventTouchUpInside]; - } - return target; -} - @end @interface ACRShowCardTargetBuilder : ACRTargetBuilder @@ -110,20 +110,16 @@ @interface ACRShowCardTargetBuilder : ACRTargetBuilder @implementation ACRShowCardTargetBuilder -+ (ACRShowCardTargetBuilder *)getInstance { ++ (ACRShowCardTargetBuilder *)getInstance +{ static ACRShowCardTargetBuilder *singletonInstance = [[self alloc] init]; return singletonInstance; } -// currently not needed -- (NSObject *)build:(std::shared_ptr const &)action - director:(ACRTargetBuilderDirector *)director { - return nil; -} - - (NSObject *)build:(std::shared_ptr const &)action director:(ACRTargetBuilderDirector *)director - ForButton:(UIButton *)button { + ForButton:(UIButton *)button +{ std::shared_ptr showCardAction = std::dynamic_pointer_cast(action); @@ -148,13 +144,15 @@ @interface ACRToggleVisibilityTargetBuilder : ACRTargetBuilder @implementation ACRToggleVisibilityTargetBuilder -+ (ACRToggleVisibilityTargetBuilder *)getInstance { ++ (ACRToggleVisibilityTargetBuilder *)getInstance +{ static ACRToggleVisibilityTargetBuilder *singletonInstance = [[self alloc] init]; return singletonInstance; } - (NSObject *)build:(std::shared_ptr const &)action - director:(ACRTargetBuilderDirector *)director { + director:(ACRTargetBuilderDirector *)director +{ std::shared_ptr toggleVisibilityAction = std::dynamic_pointer_cast(action); @@ -168,7 +166,8 @@ - (NSObject *)build:(std::shared_ptr const &)action - (NSObject *)build:(std::shared_ptr const &)action director:(ACRTargetBuilderDirector *)director - ForButton:(UIButton *)button { + ForButton:(UIButton *)button +{ NSObject *target = [self build:action director:director]; if (target) { [button addTarget:target @@ -180,18 +179,46 @@ - (NSObject *)build:(std::shared_ptr const &)action @end +// build target for unknown actions +@interface ACRUnknownActionTargetBuilder : ACRTargetBuilder +@end + +@implementation ACRUnknownActionTargetBuilder + ++ (ACRUnknownActionTargetBuilder *)getInstance +{ + static ACRUnknownActionTargetBuilder *singletonInstance = [[self alloc] init]; + return singletonInstance; +} + +- (NSObject *)build:(std::shared_ptr const &)action + director:(ACRTargetBuilderDirector *)director +{ + std::shared_ptr unknownAction = std::dynamic_pointer_cast(action); + if (unknownAction) { + ACOBaseActionElement *customAction = deserializeUnknownActionToCustomAction(unknownAction); + return [[ACRAggregateTarget alloc] initWithActionElement:customAction rootView:director.rootView]; + } + + return nil; +} + +@end + @implementation ACRTargetBuilderDirector { NSDictionary *_builders; } -- (instancetype)init { +- (instancetype)init +{ self = [self init:nil capability:ACRAction adaptiveHostConfig:nil]; return self; } - (instancetype)init:(ACRView *)rootView capability:(ACRTargetCapability)capability - adaptiveHostConfig:(ACOHostConfig *)adaptiveHostConfig { + adaptiveHostConfig:(ACOHostConfig *)adaptiveHostConfig +{ self = [super init]; if (self) { self.rootView = rootView; @@ -200,6 +227,7 @@ - (instancetype)init:(ACRView *)rootView NSNumber *submit = [NSNumber numberWithInt:static_cast(ActionType::Submit)]; NSNumber *showcard = [NSNumber numberWithInt:static_cast(ActionType::ShowCard)]; NSNumber *toggle = [NSNumber numberWithInt:static_cast(ActionType::ToggleVisibility)]; + NSNumber *unknown = [NSNumber numberWithInt:static_cast(ActionType::UnknownAction)]; // target capability lists supported events and corresponding target builders switch (capability) { @@ -215,7 +243,8 @@ - (instancetype)init:(ACRView *)rootView _builders = @{ openUrl : [ACRAggregateTargetBuilder getInstance], submit : [ACRAggregateTargetBuilder getInstance], - toggle : [ACRToggleVisibilityTargetBuilder getInstance] + toggle : [ACRToggleVisibilityTargetBuilder getInstance], + unknown : [ACRUnknownActionTargetBuilder getInstance] }; break; case ACRQuickReply: @@ -230,7 +259,8 @@ - (instancetype)init:(ACRView *)rootView return self; } -- (NSObject *)build:(std::shared_ptr const &)action { +- (NSObject *)build:(std::shared_ptr const &)action +{ if ([self checkAgainstMyCapability:action]) { // check fail, stop and return return nil; @@ -241,7 +271,8 @@ - (NSObject *)build:(std::shared_ptr const &)action { } - (NSObject *)build:(std::shared_ptr const &)action - forButton:(UIButton *)button { + forButton:(UIButton *)button +{ if (ACRRenderingStatus::ACROk == [self checkAgainstMyCapability:action]) { ACRTargetBuilder *builder = [self getBuilder:action]; return [builder build:action director:self ForButton:button]; @@ -249,17 +280,18 @@ - (NSObject *)build:(std::shared_ptr const &)action return nil; } -- (ACRRenderingStatus)checkAgainstMyCapability:(std::shared_ptr const &)action { +- (ACRRenderingStatus)checkAgainstMyCapability:(std::shared_ptr const &)action +{ return ([_builders objectForKey:[NSNumber numberWithInt:static_cast(action->GetElementType())]]) ? ACRRenderingStatus::ACROk : ACRRenderingStatus::ACRUnsupported; } -- (ACRTargetBuilder *)getBuilder:(std::shared_ptr const &)action { +- (ACRTargetBuilder *)getBuilder:(std::shared_ptr const &)action +{ NSNumber *key = [NSNumber numberWithInt:static_cast(action->GetElementType())]; return _builders[key]; } @end - diff --git a/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/UtiliOS.h b/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/UtiliOS.h index 3fa989ec2a..bb51ef27fe 100644 --- a/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/UtiliOS.h +++ b/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/UtiliOS.h @@ -4,13 +4,16 @@ // Copyfight © 2019 Microsoft. All rights reserved. // -#import #import "ACRErrors.h" #import "ACRSeparator.h" #import "ACRViewPrivate.h" #import "BaseCardElement.h" #import "CollectionTypeElement.h" -#import "TextElementProperties.h" +#import "RichTextElementProperties.h" +#import "TextBlock.h" +#import "TextRun.h" +#import "UnknownAction.h" +#import using namespace AdaptiveCards; @@ -55,3 +58,11 @@ ACRRenderingStatus buildTargetForButton(ACRTargetBuilderDirector *director, ACRRenderingStatus buildTarget(ACRTargetBuilderDirector *director, std::shared_ptr const &action, NSObject **target); + +void TextBlockToRichTextElementProperties(const std::shared_ptr &textBlock, RichTextElementProperties &textProp); + +void TextRunToRichTextElementProperties(const std::shared_ptr &textRun, RichTextElementProperties &textProp); + +void buildIntermediateResultForText(ACRView *rootView, ACOHostConfig *hostConfig, RichTextElementProperties const &textProperties, NSString *elementId); + +ACOBaseActionElement *deserializeUnknownActionToCustomAction(const std::shared_ptr action); diff --git a/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/UtiliOS.mm b/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/UtiliOS.mm index ca881e86dd..c02da2da06 100644 --- a/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/UtiliOS.mm +++ b/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/UtiliOS.mm @@ -18,10 +18,14 @@ #import "BackgroundImage.h" #import "BaseActionElement.h" #import "Enums.h" +#import "MarkDownParser.h" +#import "RichTextElementProperties.h" +#import "TextRun.h" using namespace AdaptiveCards; -void configVisibility(UIView *view, std::shared_ptr const &visibilityInfo) { +void configVisibility(UIView *view, std::shared_ptr const &visibilityInfo) +{ view.hidden = !(visibilityInfo->GetIsVisible()); NSString *hashkey = [NSString stringWithCString:visibilityInfo->GetId().c_str() encoding:NSUTF8StringEncoding]; @@ -29,7 +33,8 @@ void configVisibility(UIView *view, std::shared_ptr const &visi } void configSeparatorVisibility(ACRSeparator *view, - std::shared_ptr const &visibilityInfo) { + std::shared_ptr const &visibilityInfo) +{ view.hidden = !(visibilityInfo->GetIsVisible()); NSMutableString *hashkey = [NSMutableString stringWithCString:visibilityInfo->GetId().c_str() encoding:NSUTF8StringEncoding]; @@ -38,7 +43,8 @@ void configSeparatorVisibility(ACRSeparator *view, } void renderBackgroundImage(const std::shared_ptr backgroundImage, - UIView *containerView, ACRView *rootView) { + UIView *containerView, ACRView *rootView) +{ if (backgroundImage == nullptr || backgroundImage->GetUrl().empty()) { return; } @@ -87,7 +93,8 @@ void renderBackgroundImage(const std::shared_ptr } void renderBackgroundImage(const BackgroundImage *backgroundImageProperties, UIImageView *imageView, - UIImage *image) { + UIImage *image) +{ if (backgroundImageProperties->GetFillMode() == ImageFillMode::Repeat || backgroundImageProperties->GetFillMode() == ImageFillMode::RepeatHorizontally || backgroundImageProperties->GetFillMode() == ImageFillMode::RepeatVertically) { @@ -98,7 +105,8 @@ void renderBackgroundImage(const BackgroundImage *backgroundImageProperties, UII } void applyBackgroundImageConstraints(const BackgroundImage *backgroundImageProperties, - UIImageView *imageView, UIImage *image) { + UIImageView *imageView, UIImage *image) +{ if (backgroundImageProperties == nullptr || imageView == nullptr || image == nullptr) { return; } @@ -276,8 +284,8 @@ void applyBackgroundImageConstraints(const BackgroundImage *backgroundImagePrope } else if (superView.frame.size.height > imageView.frame.size.height) { [imageView.heightAnchor constraintEqualToAnchor:superView.heightAnchor].active = YES; - } else { // if background image is bigger than the superview; let it retain its - // dimensions + } else { // if background image is bigger than the superview; let it retain its + // dimensions imageView.translatesAutoresizingMaskIntoConstraints = YES; } @@ -305,7 +313,8 @@ void applyBackgroundImageConstraints(const BackgroundImage *backgroundImagePrope } void configBleed(ACRView *rootView, std::shared_ptr const &elem, - ACRContentStackView *container, ACOHostConfig *acoConfig) { + ACRContentStackView *container, ACOHostConfig *acoConfig) +{ std::shared_ptr collection = std::dynamic_pointer_cast(elem); if (collection) { @@ -368,27 +377,29 @@ void configBleed(ACRView *rootView, std::shared_ptr const &elem ObserverActionBlock generateBackgroundImageObserverAction( std::shared_ptr backgroundImageProperties, ACRView *observer, - std::shared_ptr const &context) { + std::shared_ptr const &context) +{ return ^(NSObject *imageResourceResolver, NSString *key, std::shared_ptr const &elem, NSURL *url, ACRView *rootView) { - UIImageView *view = [imageResourceResolver resolveImageViewResource:url]; - if (view) { - [view addObserver:observer - forKeyPath:@"image" - options:NSKeyValueObservingOptionNew - context:backgroundImageProperties.get()]; - - // store the image view and column for easy retrieval in ACRView::observeValueForKeyPath - [rootView setImageView:key view:view]; - [rootView setImageContext:key context:context]; - } + UIImageView *view = [imageResourceResolver resolveImageViewResource:url]; + if (view) { + [view addObserver:observer + forKeyPath:@"image" + options:NSKeyValueObservingOptionNew + context:backgroundImageProperties.get()]; + + // store the image view and column for easy retrieval in ACRView::observeValueForKeyPath + [rootView setImageView:key view:view]; + [rootView setImageContext:key context:context]; + } }; } void handleFallbackException(ACOFallbackException *exception, UIView *view, ACRView *rootView, NSMutableArray *inputs, std::shared_ptr const &givenElem, - ACOHostConfig *config) { + ACOHostConfig *config) +{ std::shared_ptr fallbackBaseElement = nullptr; std::shared_ptr elem = givenElem; bool bCanFallbackToAncestor = elem->CanFallbackToAncestor(); @@ -443,7 +454,8 @@ void handleFallbackException(ACOFallbackException *exception, UIView *view) { + UIView *view) +{ if (elemType == CardElementType::Container || elemType == CardElementType::Column || elemType == CardElementType::ColumnSet) { [view removeLastViewFromArrangedSubview]; @@ -454,7 +466,8 @@ void handleActionFallbackException(ACOFallbackException *exception, UIView *view, ACRView *rootView, NSMutableArray *inputs, ACOBaseActionElement *acoElem, ACOHostConfig *config, - UIView *actionSet) { + UIView *actionSet) +{ std::shared_ptr fallbackBaseElement = nullptr; std::shared_ptr elem = acoElem.element; bool bCanFallbackToAncestor = elem->CanFallbackToAncestor(); @@ -504,7 +517,8 @@ void handleActionFallbackException(ACOFallbackException *exception, } } -UIFontDescriptor *getItalicFontDescriptor(UIFontDescriptor *descriptor, bool isItalic) { +UIFontDescriptor *getItalicFontDescriptor(UIFontDescriptor *descriptor, bool isItalic) +{ if (isItalic && descriptor) { return [descriptor fontDescriptorWithSymbolicTraits:UIFontDescriptorTraitItalic]; } @@ -514,14 +528,153 @@ void handleActionFallbackException(ACOFallbackException *exception, ACRRenderingStatus buildTargetForButton(ACRTargetBuilderDirector *director, std::shared_ptr const &action, - UIButton *button, NSObject **target) { + UIButton *button, NSObject **target) +{ *target = [director build:action forButton:button]; return *target ? ACRRenderingStatus::ACROk : ACRRenderingStatus::ACRFailed; } ACRRenderingStatus buildTarget(ACRTargetBuilderDirector *director, std::shared_ptr const &action, - NSObject **target) { + NSObject **target) +{ *target = [director build:action]; return *target ? ACRRenderingStatus::ACROk : ACRRenderingStatus::ACRFailed; } + +void buildIntermediateResultForText(ACRView *rootView, ACOHostConfig *hostConfig, RichTextElementProperties const &textProperties, NSString *elementId) +{ + std::shared_ptr markDownParser = std::make_shared([ACOHostConfig getLocalizedDate:textProperties.GetText() language:textProperties.GetLanguage()]); + + // MarkDownParser transforms text with MarkDown to a html string + NSString *parsedString = [NSString stringWithCString:markDownParser->TransformToHtml().c_str() encoding:NSUTF8StringEncoding]; + NSDictionary *data = nil; + + // use Apple's html rendering only if the string has markdowns + if (markDownParser->HasHtmlTags() || markDownParser->IsEscaped()) { + NSString *fontFamilyName = nil; + + if (![hostConfig getFontFamily:textProperties.GetFontType()]) { + if (textProperties.GetFontType() == FontType::Monospace) { + fontFamilyName = @"'Courier New'"; + } else { + fontFamilyName = @"'-apple-system', 'San Francisco'"; + } + } else { + fontFamilyName = [hostConfig getFontFamily:textProperties.GetFontType()]; + } + + NSString *font_style = textProperties.GetItalic() ? @"italic" : @"normal"; + // Font and text size are applied as CSS style by appending it to the html string + parsedString = [parsedString stringByAppendingString:[NSString stringWithFormat:@"", + fontFamilyName, + [hostConfig getTextBlockTextSize:textProperties.GetFontType() + textSize:textProperties.GetTextSize()], + [hostConfig getTextBlockFontWeight:textProperties.GetFontType() + textWeight:textProperties.GetTextWeight()], + font_style]]; + + NSData *htmlData = [parsedString dataUsingEncoding:NSUTF16StringEncoding]; + NSDictionary *options = @{NSDocumentTypeDocumentAttribute : NSHTMLTextDocumentType}; + data = @{@"html" : htmlData, @"options" : options}; + } else { + int fontweight = [hostConfig getTextBlockFontWeight:textProperties.GetFontType() + textWeight:textProperties.GetTextWeight()]; + // sanity check, 400 is the normal font; + if (fontweight <= 0 || fontweight > 900) { + fontweight = 400; + } + UIFont *font = nil; + fontweight -= 100; + fontweight /= 100; + + if (![hostConfig getFontFamily:textProperties.GetFontType()]) { + const NSArray *fontweights = @[ @(UIFontWeightUltraLight), @(UIFontWeightThin), @(UIFontWeightLight), @(UIFontWeightRegular), @(UIFontWeightMedium), + @(UIFontWeightSemibold), @(UIFontWeightBold), @(UIFontWeightHeavy), @(UIFontWeightBlack) ]; + const CGFloat size = [hostConfig getTextBlockTextSize:textProperties.GetFontType() textSize:textProperties.GetTextSize()]; + if (textProperties.GetFontType() == FontType::Monospace) { + const NSArray *fontweights = @[ @"UltraLight", @"Thin", @"Light", @"Regular", + @"Medium", @"Semibold", @"Bold", @"Heavy", @"Black" ]; + UIFontDescriptor *descriptor = [UIFontDescriptor fontDescriptorWithFontAttributes:@{UIFontDescriptorFamilyAttribute : @"Courier New", + UIFontDescriptorFaceAttribute : fontweights[fontweight]}]; + descriptor = getItalicFontDescriptor(descriptor, textProperties.GetItalic()); + + font = [UIFont fontWithDescriptor:descriptor size:[hostConfig getTextBlockTextSize:textProperties.GetFontType() textSize:textProperties.GetTextSize()]]; + } else { + font = [UIFont systemFontOfSize:size weight:[fontweights[fontweight] floatValue]]; + + if (textProperties.GetItalic()) { + font = [UIFont fontWithDescriptor: + getItalicFontDescriptor(font.fontDescriptor, textProperties.GetItalic()) + size:size]; + } + } + } else { + // font weight as string since font weight as double doesn't work + // normailze fontweight for indexing + const NSArray *fontweights = @[ @"UltraLight", @"Thin", @"Light", @"Regular", + @"Medium", @"Semibold", @"Bold", @"Heavy", @"Black" ]; + UIFontDescriptor *descriptor = [UIFontDescriptor fontDescriptorWithFontAttributes: + @{UIFontDescriptorFamilyAttribute : [hostConfig getFontFamily:textProperties.GetFontType()], + UIFontDescriptorFaceAttribute : fontweights[fontweight]}]; + + descriptor = getItalicFontDescriptor(descriptor, textProperties.GetItalic()); + + font = [UIFont fontWithDescriptor:descriptor size:[hostConfig getTextBlockTextSize:textProperties.GetFontType() textSize:textProperties.GetTextSize()]]; + } + + NSDictionary *attributeDictionary = @{NSFontAttributeName : font}; + data = @{@"nonhtml" : parsedString, @"descriptor" : attributeDictionary}; + } + + if (elementId) { + [rootView enqueueIntermediateTextProcessingResult:data + elementId:elementId]; + } +} + +void TextBlockToRichTextElementProperties(const std::shared_ptr &textBlock, RichTextElementProperties &textProp) +{ + textProp.SetText(textBlock->GetText()); + textProp.SetTextSize(textBlock->GetTextSize()); + textProp.SetTextWeight(textBlock->GetTextWeight()); + textProp.SetFontType(textBlock->GetFontType()); + textProp.SetTextColor(textBlock->GetTextColor()); + textProp.SetIsSubtle(textBlock->GetIsSubtle()); + textProp.SetLanguage(textBlock->GetLanguage()); +} + +void TextRunToRichTextElementProperties(const std::shared_ptr &textRun, RichTextElementProperties &textProp) +{ + textProp.SetText(textRun->GetText()); + textProp.SetTextSize(textRun->GetTextSize()); + textProp.SetTextWeight(textRun->GetTextWeight()); + textProp.SetFontType(textRun->GetFontType()); + textProp.SetTextColor(textRun->GetTextColor()); + textProp.SetIsSubtle(textRun->GetIsSubtle()); + textProp.SetLanguage(textRun->GetLanguage()); + textProp.SetItalic(textRun->GetItalic()); + textProp.SetStrikethrough(textRun->GetStrikethrough()); +} + +ACOBaseActionElement *deserializeUnknownActionToCustomAction(const std::shared_ptr unknownAction) +{ + ACRRegistration *reg = [ACRRegistration getInstance]; + ACOBaseActionElement *customAction = nil; + if (reg) { + NSString *type = [NSString stringWithCString:unknownAction->GetElementTypeString().c_str() encoding:NSUTF8StringEncoding]; + NSObject *parser = [reg getCustomActionElementParser:type]; + if (!parser) { + @throw [ACOFallbackException fallbackException]; + } + Json::Value blob = unknownAction->GetAdditionalProperties(); + Json::FastWriter fastWriter; + NSString *jsonString = [[NSString alloc] initWithCString:fastWriter.write(blob).c_str() encoding:NSUTF8StringEncoding]; + if (jsonString.length > 0) { + NSData *jsonPayload = [jsonString dataUsingEncoding:NSUTF8StringEncoding]; + ACOParseContext *context = [reg getParseContext]; + customAction = [parser deserialize:jsonPayload parseContext:context]; + } + } + return customAction; +} From 984c5c76d46aab5ae94cf592aa0e933db742e968 Mon Sep 17 00:00:00 2001 From: Joseph Woo Date: Fri, 25 Oct 2019 09:54:26 -0700 Subject: [PATCH 08/19] [iOS][Sample App]Updated custom image renderer to handle toggle visibility (#3514) * [iOS][Sample App]Updated custom image renderer to handle toggle visiblity * toggle visibilty delegate will call action delegate to notify view frame changes --- .../ADCIOSVisualizer/CustomImageRenderer.mm | 3 +++ .../ADCIOSVisualizer/ADCIOSVisualizer/ViewController.m | 4 ++++ .../AdaptiveCards/ACRToggleVisibilityTarget.mm | 6 ++++-- .../ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/UtiliOS.h | 2 ++ 4 files changed, 13 insertions(+), 2 deletions(-) diff --git a/source/ios/AdaptiveCards/ADCIOSVisualizer/ADCIOSVisualizer/CustomImageRenderer.mm b/source/ios/AdaptiveCards/ADCIOSVisualizer/ADCIOSVisualizer/CustomImageRenderer.mm index 296bc853f3..54bd34adcf 100644 --- a/source/ios/AdaptiveCards/ADCIOSVisualizer/ADCIOSVisualizer/CustomImageRenderer.mm +++ b/source/ios/AdaptiveCards/ADCIOSVisualizer/ADCIOSVisualizer/CustomImageRenderer.mm @@ -18,6 +18,7 @@ #import #import #import +#import @implementation CustomImageRenderer @@ -170,6 +171,8 @@ - (UIView *)render:(UIView *)viewGroup hostConfig:acoConfig]; view.translatesAutoresizingMaskIntoConstraints = NO; wrappingview.translatesAutoresizingMaskIntoConstraints = NO; + configVisibility(wrappingview, elem); + return wrappingview; } diff --git a/source/ios/AdaptiveCards/ADCIOSVisualizer/ADCIOSVisualizer/ViewController.m b/source/ios/AdaptiveCards/ADCIOSVisualizer/ADCIOSVisualizer/ViewController.m index 5d3b544897..a901b41a4f 100644 --- a/source/ios/AdaptiveCards/ADCIOSVisualizer/ADCIOSVisualizer/ViewController.m +++ b/source/ios/AdaptiveCards/ADCIOSVisualizer/ADCIOSVisualizer/ViewController.m @@ -379,6 +379,10 @@ - (void)didFetchUserResponses:(ACOAdaptiveCard *)card action:(ACOBaseActionEleme newType.alertController = alertController; [self presentViewController:alertController animated:YES completion:nil]; } + } else if (action.type == ACRToggleVisibility) { + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Toggle Visibilty" message:nil preferredStyle:UIAlertControllerStyleAlert]; + [alertController addAction:[UIAlertAction actionWithTitle:@"Dismiss" style:UIAlertActionStyleDefault handler:nil]]; + [self presentViewController:alertController animated:YES completion:nil]; } } diff --git a/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRToggleVisibilityTarget.mm b/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRToggleVisibilityTarget.mm index a80f485dc0..d360f23468 100644 --- a/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRToggleVisibilityTarget.mm +++ b/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRToggleVisibilityTarget.mm @@ -17,7 +17,7 @@ @implementation ACRToggleVisibilityTarget { ACOHostConfig *_config; __weak ACRView *_rootView; - std::unique_ptr _action; + std::shared_ptr _action; } - (instancetype)initWithActionElement:(std::shared_ptr const &)actionElement @@ -29,7 +29,7 @@ - (instancetype)initWithActionElement:(std::shared_ptr(*(actionElement.get())); + _action = std::make_shared(*(actionElement.get())); } return self; } @@ -64,6 +64,8 @@ - (void)doSelectAction } } } + + [_rootView.acrActionDelegate didFetchUserResponses:[_rootView card] action:[[ACOBaseActionElement alloc] initWithBaseActionElement:_action]]; } @end diff --git a/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/UtiliOS.h b/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/UtiliOS.h index bb51ef27fe..1f8a955609 100644 --- a/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/UtiliOS.h +++ b/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/UtiliOS.h @@ -17,6 +17,8 @@ using namespace AdaptiveCards; +// configures tag and initial visibility of the given view. Toggle visibility action +// will access the view by the tag to change the visibility. void configVisibility(UIView *view, std::shared_ptr const &visibilityInfo); void configSeparatorVisibility(ACRSeparator *view, From 1ab18d8e2d5d413fef9d0a9a84a84ffc309190e8 Mon Sep 17 00:00:00 2001 From: RebeccaAnne Date: Wed, 9 Oct 2019 09:19:24 -0700 Subject: [PATCH 09/19] [UWP] Fix circular reference in ImageOpened lambda for Auto sized image handling (#3507) --- source/uwp/Renderer/lib/XamlBuilder.cpp | 164 ++++++++++++++---------- 1 file changed, 97 insertions(+), 67 deletions(-) diff --git a/source/uwp/Renderer/lib/XamlBuilder.cpp b/source/uwp/Renderer/lib/XamlBuilder.cpp index 19d4d1da3e..5d83b88c1f 100644 --- a/source/uwp/Renderer/lib/XamlBuilder.cpp +++ b/source/uwp/Renderer/lib/XamlBuilder.cpp @@ -245,7 +245,8 @@ namespace AdaptiveNamespace return S_OK; } - HRESULT XamlBuilder::AddListener(_In_ IXamlBuilderListener* listener) noexcept try + HRESULT XamlBuilder::AddListener(_In_ IXamlBuilderListener* listener) noexcept + try { if (m_listeners.find(listener) == m_listeners.end()) { @@ -259,7 +260,8 @@ namespace AdaptiveNamespace } CATCH_RETURN; - HRESULT XamlBuilder::RemoveListener(_In_ IXamlBuilderListener* listener) noexcept try + HRESULT XamlBuilder::RemoveListener(_In_ IXamlBuilderListener* listener) noexcept + try { if (m_listeners.find(listener) != m_listeners.end()) { @@ -922,7 +924,8 @@ namespace AdaptiveNamespace static inline HRESULT WarnForFallbackContentElement(_In_ ABI::AdaptiveNamespace::IAdaptiveRenderContext* renderContext, _In_ HSTRING parentElementType, - _In_ HSTRING fallbackElementType) try + _In_ HSTRING fallbackElementType) + try { std::string warning = "Performing fallback for element of type \""; warning.append(HStringToUTF8(parentElementType)); @@ -934,8 +937,8 @@ namespace AdaptiveNamespace } CATCH_RETURN; - static inline HRESULT WarnForFallbackDrop(_In_ ABI::AdaptiveNamespace::IAdaptiveRenderContext* renderContext, - _In_ HSTRING elementType) try + static inline HRESULT WarnForFallbackDrop(_In_ ABI::AdaptiveNamespace::IAdaptiveRenderContext* renderContext, _In_ HSTRING elementType) + try { std::string warning = "Dropping element of type \""; warning.append(HStringToUTF8(elementType)); @@ -1322,8 +1325,7 @@ namespace AdaptiveNamespace RETURN_IF_FAILED(buttonText.As(&buttonTextAsFrameworkElement)); return SetMatchingHeight(buttonIconAsFrameworkElement.Get(), buttonTextAsFrameworkElement.Get()); - }) - .Get(), + }).Get(), &eventToken)); // Only add spacing when the icon must be located at the left of the title @@ -1906,11 +1908,10 @@ namespace AdaptiveNamespace ComPtr actionInvoker; RETURN_IF_FAILED(renderContext->get_ActionInvoker(&actionInvoker)); EventRegistrationToken clickToken; - RETURN_IF_FAILED(buttonBase->add_Click(Callback( - [action, actionInvoker](IInspectable* /*sender*/, IRoutedEventArgs * /*args*/) -> HRESULT { - return actionInvoker->SendActionEvent(action.Get()); - }) - .Get(), + RETURN_IF_FAILED(buttonBase->add_Click(Callback([action, actionInvoker](IInspectable* /*sender*/, IRoutedEventArgs * + /*args*/) -> HRESULT { + return actionInvoker->SendActionEvent(action.Get()); + }).Get(), &clickToken)); RETURN_IF_FAILED(HandleActionStyling(adaptiveActionElement, buttonFrameworkElement.Get(), renderContext)); @@ -2313,17 +2314,36 @@ namespace AdaptiveNamespace THROW_IF_FAILED(ellipseAsUIElement->put_Visibility(Visibility::Visibility_Collapsed)); // Handle ImageOpened event so we can check the imageSource's size to determine if it fits in its parent EventRegistrationToken eventToken; + ComPtr localParentElement(parentElement); + + // Take weak references to the ellipse and parent to avoid circular references between this lambda and + // its parents (Parent->Ellipse->ImageBrush->Lambda->(Parent and Ellipse)) + WeakRef weakParent; + THROW_IF_FAILED(localParentElement.AsWeak(&weakParent)); + + WeakRef weakEllipse; + THROW_IF_FAILED(ellipseAsUIElement.AsWeak(&weakEllipse)); THROW_IF_FAILED(brushAsImageBrush->add_ImageOpened( - Callback([ellipseAsUIElement, isVisible](IInspectable* /*sender*/, IRoutedEventArgs * /*args*/) -> HRESULT { - // Don't set the AutoImageSize on the ellipse as it makes the ellipse grow bigger than - // what it would be otherwise, just set the visibility when we get the image + Callback([weakEllipse, imageSourceAsBitmap, weakParent, isVisible]( + IInspectable* /*sender*/, IRoutedEventArgs * /*args*/) -> HRESULT { if (isVisible) { - RETURN_IF_FAILED(ellipseAsUIElement->put_Visibility(Visibility::Visibility_Visible)); + ComPtr lambdaEllipseAsFrameworkElement; + RETURN_IF_FAILED(weakEllipse.As(&lambdaEllipseAsFrameworkElement)); + + ComPtr lambdaParentElement; + RETURN_IF_FAILED(weakParent.As(&lambdaParentElement)); + + if (lambdaEllipseAsFrameworkElement && lambdaParentElement) + { + RETURN_IF_FAILED(SetAutoImageSize(lambdaEllipseAsFrameworkElement.Get(), + lambdaParentElement.Get(), + imageSourceAsBitmap.Get(), + isVisible)); + } } return S_OK; - }) - .Get(), + }).Get(), &eventToken)); } } @@ -2352,18 +2372,34 @@ namespace AdaptiveNamespace THROW_IF_FAILED(imageAsUIElement->put_Visibility(Visibility::Visibility_Collapsed)); // Handle ImageOpened event so we can check the imageSource's size to determine if it fits in its parent - ComPtr strongParentElement(parentElement); + ComPtr localParentElement(parentElement); + + // Take weak references to the image and parent to avoid circular references between this lambda and + // its parents (Parent->Image->Lambda->(Parent and Image)) + WeakRef weakParent; + THROW_IF_FAILED(localParentElement.AsWeak(&weakParent)); + + WeakRef weakImage; + THROW_IF_FAILED(imageAsFrameworkElement.AsWeak(&weakImage)); EventRegistrationToken eventToken; THROW_IF_FAILED(xamlImage->add_ImageOpened( - Callback( - [imageAsFrameworkElement, strongParentElement, imageSourceAsBitmap, isVisible](IInspectable* /*sender*/, IRoutedEventArgs * - /*args*/) -> HRESULT { - return SetAutoImageSize(imageAsFrameworkElement.Get(), - strongParentElement.Get(), - imageSourceAsBitmap.Get(), - isVisible); - }) - .Get(), + Callback([weakImage, weakParent, imageSourceAsBitmap, isVisible](IInspectable* /*sender*/, IRoutedEventArgs * + /*args*/) -> HRESULT { + ComPtr lambdaImageAsFrameworkElement; + RETURN_IF_FAILED(weakImage.As(&lambdaImageAsFrameworkElement)); + + ComPtr lambdaParentElement; + RETURN_IF_FAILED(weakParent.As(&lambdaParentElement)); + + if (lambdaImageAsFrameworkElement && lambdaParentElement) + { + RETURN_IF_FAILED(SetAutoImageSize(lambdaImageAsFrameworkElement.Get(), + lambdaParentElement.Get(), + imageSourceAsBitmap.Get(), + isVisible)); + } + return S_OK; + }).Get(), &eventToken)); } else @@ -3917,14 +3953,13 @@ namespace AdaptiveNamespace // Make the action the same size as the text box EventRegistrationToken eventToken; THROW_IF_FAILED(textBoxAsFrameworkElement->add_Loaded( - Callback( - [actionUIElement, textBoxAsFrameworkElement](IInspectable* /*sender*/, IRoutedEventArgs * /*args*/) -> HRESULT { - ComPtr actionFrameworkElement; - RETURN_IF_FAILED(actionUIElement.As(&actionFrameworkElement)); + Callback([actionUIElement, textBoxAsFrameworkElement](IInspectable* /*sender*/, IRoutedEventArgs * + /*args*/) -> HRESULT { + ComPtr actionFrameworkElement; + RETURN_IF_FAILED(actionUIElement.As(&actionFrameworkElement)); - return SetMatchingHeight(actionFrameworkElement.Get(), textBoxAsFrameworkElement.Get()); - }) - .Get(), + return SetMatchingHeight(actionFrameworkElement.Get(), textBoxAsFrameworkElement.Get()); + }).Get(), &eventToken)); // Wrap the action in a button @@ -3957,8 +3992,7 @@ namespace AdaptiveNamespace THROW_IF_FAILED(textBoxAsUIElement->add_KeyDown( Callback([actionInvoker, localInlineAction](IInspectable* /*sender*/, IKeyRoutedEventArgs* args) -> HRESULT { return HandleKeydownForInlineAction(args, actionInvoker.Get(), localInlineAction.Get()); - }) - .Get(), + }).Get(), &keyDownEventToken)); } @@ -4289,28 +4323,26 @@ namespace AdaptiveNamespace EventRegistrationToken clickToken; RETURN_IF_FAILED(touchTargetAsButtonBase->add_Click( - Callback( - [touchTargetUIElement, lambdaRenderContext, adaptiveMedia, mediaElement, mediaSourceUrl, lambdaMimeType, mediaInvoker]( - IInspectable* /*sender*/, IRoutedEventArgs * /*args*/) -> HRESULT { - // Take ownership of the passed in HSTRING - HString localMimeType; - localMimeType.Attach(lambdaMimeType); - - // Turn off the button to prevent extra clicks - ComPtr buttonAsControl; - touchTargetUIElement.As(&buttonAsControl); - RETURN_IF_FAILED(buttonAsControl->put_IsEnabled(false)); - - // Handle the click - return HandleMediaClick(lambdaRenderContext.Get(), - adaptiveMedia.Get(), - mediaElement.Get(), - touchTargetUIElement.Get(), - mediaSourceUrl.Get(), - lambdaMimeType, - mediaInvoker.Get()); - }) - .Get(), + Callback([touchTargetUIElement, lambdaRenderContext, adaptiveMedia, mediaElement, mediaSourceUrl, lambdaMimeType, mediaInvoker]( + IInspectable* /*sender*/, IRoutedEventArgs * /*args*/) -> HRESULT { + // Take ownership of the passed in HSTRING + HString localMimeType; + localMimeType.Attach(lambdaMimeType); + + // Turn off the button to prevent extra clicks + ComPtr buttonAsControl; + touchTargetUIElement.As(&buttonAsControl); + RETURN_IF_FAILED(buttonAsControl->put_IsEnabled(false)); + + // Handle the click + return HandleMediaClick(lambdaRenderContext.Get(), + adaptiveMedia.Get(), + mediaElement.Get(), + touchTargetUIElement.Get(), + mediaSourceUrl.Get(), + lambdaMimeType, + mediaInvoker.Get()); + }).Get(), &clickToken)); RETURN_IF_FAILED(mediaPanelAsUIElement.CopyTo(mediaControl)); @@ -4451,12 +4483,11 @@ namespace AdaptiveNamespace THROW_IF_FAILED(localButton.As(&buttonBase)); EventRegistrationToken clickToken; - THROW_IF_FAILED(buttonBase->add_Click(Callback( - [strongAction, actionInvoker](IInspectable* /*sender*/, IRoutedEventArgs * /*args*/) -> HRESULT { - THROW_IF_FAILED(actionInvoker->SendActionEvent(strongAction.Get())); - return S_OK; - }) - .Get(), + THROW_IF_FAILED(buttonBase->add_Click(Callback([strongAction, actionInvoker](IInspectable* /*sender*/, IRoutedEventArgs * + /*args*/) -> HRESULT { + THROW_IF_FAILED(actionInvoker->SendActionEvent(strongAction.Get())); + return S_OK; + }).Get(), &clickToken)); } @@ -4471,8 +4502,7 @@ namespace AdaptiveNamespace // Add Tap handler that sets the event as handled so that it doesn't propagate to the parent containers. return uiElement->add_Tapped(Callback([](IInspectable* /*sender*/, ITappedRoutedEventArgs* args) -> HRESULT { return args->put_Handled(TRUE); - }) - .Get(), + }).Get(), &clickToken); } From 32fe1480af1ccb471519cc0be213a39fb1ab8e34 Mon Sep 17 00:00:00 2001 From: RebeccaAnne Date: Fri, 27 Sep 2019 15:21:30 -0700 Subject: [PATCH 10/19] [UWP] Fix cirucular reference in element tag (#3496) --- source/uwp/Renderer/lib/ElementTagContent.cpp | 5 ++++- source/uwp/Renderer/lib/ElementTagContent.h | 2 +- source/uwp/Renderer/lib/XamlBuilder.cpp | 5 ++++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/source/uwp/Renderer/lib/ElementTagContent.cpp b/source/uwp/Renderer/lib/ElementTagContent.cpp index a2ff6569a4..ed1151acf0 100644 --- a/source/uwp/Renderer/lib/ElementTagContent.cpp +++ b/source/uwp/Renderer/lib/ElementTagContent.cpp @@ -4,6 +4,7 @@ #include "pch.h" #include "ElementTagContent.h" +using namespace Microsoft::WRL; using namespace ABI::AdaptiveNamespace; using namespace ABI::Windows::UI::Xaml; using namespace ABI::Windows::UI::Xaml::Controls; @@ -15,8 +16,10 @@ namespace AdaptiveNamespace _In_ IUIElement* separator, _In_ IColumnDefinition* columnDefinition) { + ComPtr localParentPanel(parentPanel); + RETURN_IF_FAILED(localParentPanel.AsWeak(&m_parentPanel)); + m_columnDefinition = columnDefinition; - m_parentPanel = parentPanel; m_separator = separator; m_cardElement = cardElement; return S_OK; diff --git a/source/uwp/Renderer/lib/ElementTagContent.h b/source/uwp/Renderer/lib/ElementTagContent.h index 1f44b40941..4df9c7cec6 100644 --- a/source/uwp/Renderer/lib/ElementTagContent.h +++ b/source/uwp/Renderer/lib/ElementTagContent.h @@ -32,6 +32,6 @@ namespace AdaptiveNamespace Microsoft::WRL::ComPtr m_cardElement; Microsoft::WRL::ComPtr m_columnDefinition; Microsoft::WRL::ComPtr m_separator; - Microsoft::WRL::ComPtr m_parentPanel; + Microsoft::WRL::WeakRef m_parentPanel; }; } diff --git a/source/uwp/Renderer/lib/XamlBuilder.cpp b/source/uwp/Renderer/lib/XamlBuilder.cpp index 5d83b88c1f..f96304819a 100644 --- a/source/uwp/Renderer/lib/XamlBuilder.cpp +++ b/source/uwp/Renderer/lib/XamlBuilder.cpp @@ -1501,7 +1501,10 @@ namespace AdaptiveNamespace for (auto parentPanel : parentPanels) { - SetSeparatorVisibility(parentPanel); + if (parentPanel) + { + SetSeparatorVisibility(parentPanel); + } } return S_OK; From e656484727be7e14f23bf60fbb86aab50727ab1e Mon Sep 17 00:00:00 2001 From: nesalang Date: Fri, 1 Nov 2019 16:54:41 -0700 Subject: [PATCH 11/19] updated version info --- source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/Info.plist | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/Info.plist b/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/Info.plist index d7fb95495f..a86e98a982 100644 --- a/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/Info.plist +++ b/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 1.2.0 + 1.2.4 CFBundleVersion $(CURRENT_PROJECT_VERSION) NSPrincipalClass From 4de189b3b8bf611df1592b35b5a7654e3cfe1575 Mon Sep 17 00:00:00 2001 From: shalinijoshi19 Date: Fri, 1 Nov 2019 14:49:57 -0700 Subject: [PATCH 12/19] Release: Updating custom.props to 1.2.4 --- custom.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom.props b/custom.props index 3df7262f7f..cd63409182 100644 --- a/custom.props +++ b/custom.props @@ -4,7 +4,7 @@ 1 2 - 1.2.3 + 1.2.4 AdaptiveCards From a0d240c5ea5f0ce99919813392f975bbdb2a0b58 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Fri, 1 Nov 2019 13:43:46 -0700 Subject: [PATCH 13/19] [UWP] Use custom button to show OpenUrl actions as links (#3555) --- .../uwp/Renderer/AdaptiveCardRenderer.vcxproj | 5 +++- .../AdaptiveCardRenderer.vcxproj.filters | 3 ++ source/uwp/Renderer/lib/ActionHelpers.cpp | 30 ++++++++++++++----- source/uwp/Renderer/lib/LinkButton.cpp | 29 ++++++++++++++++++ source/uwp/Renderer/lib/LinkButton.h | 22 ++++++++++++++ source/uwp/Renderer/lib/pch.h | 11 +++++++ 6 files changed, 91 insertions(+), 9 deletions(-) create mode 100644 source/uwp/Renderer/lib/LinkButton.cpp create mode 100644 source/uwp/Renderer/lib/LinkButton.h diff --git a/source/uwp/Renderer/AdaptiveCardRenderer.vcxproj b/source/uwp/Renderer/AdaptiveCardRenderer.vcxproj index c97c4bd384..1f59752059 100644 --- a/source/uwp/Renderer/AdaptiveCardRenderer.vcxproj +++ b/source/uwp/Renderer/AdaptiveCardRenderer.vcxproj @@ -132,6 +132,8 @@ + + @@ -201,6 +203,7 @@ + @@ -372,4 +375,4 @@ - \ No newline at end of file + diff --git a/source/uwp/Renderer/AdaptiveCardRenderer.vcxproj.filters b/source/uwp/Renderer/AdaptiveCardRenderer.vcxproj.filters index 1e49cabe39..5f04d3b759 100644 --- a/source/uwp/Renderer/AdaptiveCardRenderer.vcxproj.filters +++ b/source/uwp/Renderer/AdaptiveCardRenderer.vcxproj.filters @@ -111,6 +111,7 @@ + @@ -229,6 +230,8 @@ + + diff --git a/source/uwp/Renderer/lib/ActionHelpers.cpp b/source/uwp/Renderer/lib/ActionHelpers.cpp index 630144f6d6..3e473d6cc5 100644 --- a/source/uwp/Renderer/lib/ActionHelpers.cpp +++ b/source/uwp/Renderer/lib/ActionHelpers.cpp @@ -6,6 +6,7 @@ #include "AdaptiveImage.h" #include "AdaptiveRenderArgs.h" #include "AdaptiveShowCardActionRenderer.h" +#include "LinkButton.h" using namespace Microsoft::WRL; using namespace Microsoft::WRL::Wrappers; @@ -58,8 +59,9 @@ namespace AdaptiveNamespace::ActionHelpers ComPtr localButton(button); ComPtr automationProperties; - THROW_IF_FAILED(GetActivationFactory( - HStringReference(RuntimeClass_Windows_UI_Xaml_Automation_AutomationProperties).Get(), &automationProperties)); + THROW_IF_FAILED( + GetActivationFactory(HStringReference(RuntimeClass_Windows_UI_Xaml_Automation_AutomationProperties).Get(), + &automationProperties)); ComPtr buttonAsDependencyObject; THROW_IF_FAILED(localButton.As(&buttonAsDependencyObject)); THROW_IF_FAILED(automationProperties->SetName(buttonAsDependencyObject.Get(), title.Get())); @@ -274,10 +276,25 @@ namespace AdaptiveNamespace::ActionHelpers _In_ IAdaptiveRenderArgs* renderArgs, _Outptr_ IUIElement** actionControl) { - // Render a button for the action + // determine what type of action we're building ComPtr action(adaptiveActionElement); - ComPtr button = - XamlHelpers::CreateXamlClass(HStringReference(RuntimeClass_Windows_UI_Xaml_Controls_Button)); + ABI::AdaptiveNamespace::ActionType actionType; + RETURN_IF_FAILED(action->get_ActionType(&actionType)); + + // now construct an appropriate button for the action type + ComPtr button; + if (actionType == ABI::AdaptiveNamespace::ActionType_OpenUrl) + { + // OpenUrl buttons should appear as links for accessibility purposes, so we use our custom LinkButton. + auto linkButton = winrt::make(); + button = linkButton.as().detach(); + } + + if (!button) + { + // Either non-OpenUrl action or instantiating LinkButton failed. Use standard button. + button = XamlHelpers::CreateXamlClass(HStringReference(RuntimeClass_Windows_UI_Xaml_Controls_Button)); + } ComPtr buttonFrameworkElement; RETURN_IF_FAILED(button.As(&buttonFrameworkElement)); @@ -336,9 +353,6 @@ namespace AdaptiveNamespace::ActionHelpers allowAboveTitleIconPlacement, button.Get()); - ABI::AdaptiveNamespace::ActionType actionType; - RETURN_IF_FAILED(action->get_ActionType(&actionType)); - ComPtr showCardActionConfig; RETURN_IF_FAILED(actionsConfig->get_ShowCard(&showCardActionConfig)); ABI::AdaptiveNamespace::ActionMode showCardActionMode; diff --git a/source/uwp/Renderer/lib/LinkButton.cpp b/source/uwp/Renderer/lib/LinkButton.cpp new file mode 100644 index 0000000000..033903d2dc --- /dev/null +++ b/source/uwp/Renderer/lib/LinkButton.cpp @@ -0,0 +1,29 @@ +// Copyright (C) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +#include "pch.h" +#include "LinkButton.h" + +namespace AdaptiveNamespace +{ + winrt::Windows::UI::Xaml::Automation::Peers::AutomationPeer LinkButton::OnCreateAutomationPeer() + { + // instead of the standard ButtonAutomationPeer, use our custom peer + return winrt::make(*this); + } + + LinkButtonAutomationPeer::LinkButtonAutomationPeer(LinkButton& linkButton) : + winrt::Windows::UI::Xaml::Automation::Peers::ButtonAutomationPeerT( + linkButton.operator winrt::Windows::UI::Xaml::Controls::Button()) + { + } + + winrt::Windows::UI::Xaml::Automation::Peers::AutomationControlType LinkButtonAutomationPeer::GetAutomationControlType() const + { + return winrt::Windows::UI::Xaml::Automation::Peers::AutomationControlType::Hyperlink; + } + + winrt::Windows::UI::Xaml::Automation::Peers::AutomationControlType LinkButtonAutomationPeer::GetAutomationControlTypeCore() const + { + return winrt::Windows::UI::Xaml::Automation::Peers::AutomationControlType::Hyperlink; + } +} diff --git a/source/uwp/Renderer/lib/LinkButton.h b/source/uwp/Renderer/lib/LinkButton.h new file mode 100644 index 0000000000..f5f876e65a --- /dev/null +++ b/source/uwp/Renderer/lib/LinkButton.h @@ -0,0 +1,22 @@ +// Copyright (C) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +#pragma once + +namespace AdaptiveNamespace +{ + // LinkButton is a templated button that exists strictly to behave as a button but appear as a link for + // accessibility purposes. + struct LinkButton : public winrt::Windows::UI::Xaml::Controls::ButtonT + { + winrt::Windows::UI::Xaml::Automation::Peers::AutomationPeer OnCreateAutomationPeer(); + }; + + struct LinkButtonAutomationPeer + : public winrt::Windows::UI::Xaml::Automation::Peers::ButtonAutomationPeerT + { + LinkButtonAutomationPeer(LinkButton& linkButton); + + winrt::Windows::UI::Xaml::Automation::Peers::AutomationControlType GetAutomationControlType() const; + winrt::Windows::UI::Xaml::Automation::Peers::AutomationControlType GetAutomationControlTypeCore() const; + }; +} diff --git a/source/uwp/Renderer/lib/pch.h b/source/uwp/Renderer/lib/pch.h index c0e14dfa44..4af4d18426 100644 --- a/source/uwp/Renderer/lib/pch.h +++ b/source/uwp/Renderer/lib/pch.h @@ -43,3 +43,14 @@ #define FACILITY_ADAPTIVECARDS 0xADA #define ERRORBASE_ADAPTIVECARDS 0x1000 #define E_PERFORM_FALLBACK MAKE_HRESULT(1, FACILITY_ADAPTIVECARDS, ERRORBASE_ADAPTIVECARDS) + +#include +#include +#include +#include + +#include +#include +#include +#include +#include From 526f902bb39c6e6ac37d266820ba06d594c6a3ea Mon Sep 17 00:00:00 2001 From: Joseph Woo Date: Tue, 1 Oct 2019 15:56:46 -0700 Subject: [PATCH 14/19] =?UTF-8?q?[iOS]=20allows=20default=20text=20rendere?= =?UTF-8?q?rs=20to=20be=20used=20in=20their=20custom=20versio=E2=80=A6=20(?= =?UTF-8?q?#3498)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [iOS] allows default text renderers to be used in their custom versions of renderer * adding missing files * fixed project file removal by tools (xcode, git, and etc) * CR comments feedback reflection * CR comments fix --- .../project.pbxproj | 4 + .../TestFiles/RichTextBlock.json | 106 +++++ .../ADCIOSVisualizer/TestFiles/fileslist | 11 - .../AdaptiveCards/AdaptiveCards/ACFramework.h | 1 + .../AdaptiveCards/ACRFactSetRenderer.mm | 37 +- .../AdaptiveCards/ACRRichTextBlockRenderer.mm | 173 +++++-- .../AdaptiveCards/ACRTextBlockRenderer.mm | 69 ++- .../AdaptiveCards/AdaptiveCards/ACRView.mm | 448 +++++++----------- .../AdaptiveCards/ACRViewPrivate.h | 2 + 9 files changed, 485 insertions(+), 366 deletions(-) create mode 100644 source/ios/AdaptiveCards/ADCIOSVisualizer/TestFiles/RichTextBlock.json delete mode 100644 source/ios/AdaptiveCards/ADCIOSVisualizer/TestFiles/fileslist diff --git a/source/ios/AdaptiveCards/ADCIOSVisualizer/ADCIOSVisualizer.xcodeproj/project.pbxproj b/source/ios/AdaptiveCards/ADCIOSVisualizer/ADCIOSVisualizer.xcodeproj/project.pbxproj index fae15cadfb..90f3576a3e 100644 --- a/source/ios/AdaptiveCards/ADCIOSVisualizer/ADCIOSVisualizer.xcodeproj/project.pbxproj +++ b/source/ios/AdaptiveCards/ADCIOSVisualizer/ADCIOSVisualizer.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 30860BC220C9B5C9009F9D99 /* (null) in Resources */ = {isa = PBXBuildFile; }; 30860BC420C9B5C9009F9D99 /* (null) in Resources */ = {isa = PBXBuildFile; }; + 6B055764233EC53D000EE24A /* RichTextBlock.json in Resources */ = {isa = PBXBuildFile; fileRef = 6B055763233EC53D000EE24A /* RichTextBlock.json */; }; 6B14FC5D2113BC2200A11CC5 /* (null) in Resources */ = {isa = PBXBuildFile; }; 6B1509FE22FC98AE00046B3A /* samples in Resources */ = {isa = PBXBuildFile; fileRef = 6B1509FD22FC98AE00046B3A /* samples */; }; 6B150A0022FCE49600046B3A /* AdaptiveFileBrowserSource.mm in Sources */ = {isa = PBXBuildFile; fileRef = 6B1509FF22FCE49600046B3A /* AdaptiveFileBrowserSource.mm */; }; @@ -102,6 +103,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 6B055763233EC53D000EE24A /* RichTextBlock.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = RichTextBlock.json; sourceTree = ""; }; 6B1509FD22FC98AE00046B3A /* samples */ = {isa = PBXFileReference; lastKnownFileType = folder; name = samples; path = ../../../../samples; sourceTree = ""; }; 6B1509FF22FCE49600046B3A /* AdaptiveFileBrowserSource.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = AdaptiveFileBrowserSource.mm; sourceTree = ""; }; 6B150A0122FCE4BA00046B3A /* AdaptiveFileBrowserSource.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AdaptiveFileBrowserSource.h; sourceTree = ""; }; @@ -236,6 +238,7 @@ 6B150A2D230354F100046B3A /* Image.Style.json */, 6B150A0A23034F2800046B3A /* Input.ChoiceSet.json */, 6B150A0C23034F2800046B3A /* Restaurant.json */, + 6B055763233EC53D000EE24A /* RichTextBlock.json */, 6B150A15230353AA00046B3A /* TextBlock.Color.json */, 6B150A16230353AB00046B3A /* TextBlock.DateTimeFormatting.json */, 6B150A1C230353AB00046B3A /* TextBlock.HorizontalAlignment.json */, @@ -489,6 +492,7 @@ 6B150A3B230354F200046B3A /* Column.Spacing.json in Resources */, 6B150A38230354F200046B3A /* Image.Style.json in Resources */, F423C0851EE1FB6100905679 /* Assets.xcassets in Resources */, + 6B055764233EC53D000EE24A /* RichTextBlock.json in Resources */, 6B150A1023034F2800046B3A /* Input.ChoiceSet.json in Resources */, 6B150A22230353AB00046B3A /* TextBlock.Markdown.json in Resources */, 6B150A0623033D6C00046B3A /* filebrowserHostConfig.json in Resources */, diff --git a/source/ios/AdaptiveCards/ADCIOSVisualizer/TestFiles/RichTextBlock.json b/source/ios/AdaptiveCards/ADCIOSVisualizer/TestFiles/RichTextBlock.json new file mode 100644 index 0000000000..053e1c6cc7 --- /dev/null +++ b/source/ios/AdaptiveCards/ADCIOSVisualizer/TestFiles/RichTextBlock.json @@ -0,0 +1,106 @@ +{ + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "type": "AdaptiveCard", + "version": "1.2", + "body": [ + { + "type": "RichTextBlock", + "inlines": [ + "This is the first inline. ", + { + "type": "TextRun", + "text": "We support colors, ", + "color": "good" + }, + { + "type": "TextRun", + "text": "both regular and subtle. ", + "isSubtle": true + }, + { + "type": "TextRun", + "text": "Text ", + "size": "small" + }, + { + "type": "TextRun", + "text": "of ", + "size": "medium" + }, + { + "type": "TextRun", + "text": "all ", + "size": "large" + }, + { + "type": "TextRun", + "text": "sizes! ", + "size": "extraLarge" + }, + { + "type": "TextRun", + "text": "Light weight text. ", + "weight": "lighter" + }, + { + "type": "TextRun", + "text": "Bold weight text. ", + "weight": "bolder" + }, + { + "type": "TextRun", + "text": "Highlights. ", + "highlight": true + }, + { + "type": "TextRun", + "text": "Italics. ", + "italic": true + }, + { + "type": "TextRun", + "text": "Strikethrough. ", + "strikethrough": true + }, + { + "type": "TextRun", + "text": "Monospace too!", + "fontType": "monospace" + } + ] + }, + { + "type": "RichTextBlock", + "inlines": [ + { + "type": "TextRun", + "text": "Date-Time parsing: {{DATE(2017-02-14T06:08:39Z,LONG)}} {{TIME(2017-02-14T06:08:39Z)}}" + } + ] + }, + { + "type": "RichTextBlock", + "horizontalAlignment": "center", + "inlines": [ + { + "type": "TextRun", + "text": "Rich text blocks also support center alignment. Lorem ipsum dolor Lorem ipsum dolor Lorem ipsum dolor Lorem ipsum dolor Lorem ipsum dolor " + } + ] + }, + { + "type": "RichTextBlock", + "horizontalAlignment": "right", + "inlines": [ + { + "type": "TextRun", + "text": "Rich text blocks also support right alignment. Lorem ipsum dolor Lorem ipsum dolor Lorem ipsum dolor Lorem ipsum dolor Lorem ipsum dolor " + } + ] + }, + { + "type": "RichTextBlock", + "inlines": [] + } + ] +} diff --git a/source/ios/AdaptiveCards/ADCIOSVisualizer/TestFiles/fileslist b/source/ios/AdaptiveCards/ADCIOSVisualizer/TestFiles/fileslist deleted file mode 100644 index ce1718e4c0..0000000000 --- a/source/ios/AdaptiveCards/ADCIOSVisualizer/TestFiles/fileslist +++ /dev/null @@ -1,11 +0,0 @@ -"Image.Size.json" -"Image.Spacing.json" -"Image.Style.json" -"Image.json" -"ColumnSet.Spacing.json" -"ColumnSet.json" -"Column.Size.Ratio.json" -"Column.Spacing.json" -"Column.Width.Ratio.json" -"Column.Width.json" -"Column.json" diff --git a/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACFramework.h b/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACFramework.h index 72460aaa28..1b9c0d0799 100644 --- a/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACFramework.h +++ b/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACFramework.h @@ -51,6 +51,7 @@ FOUNDATION_EXPORT const unsigned char AdaptiveCarsFrameworkVersionString[]; #import #import #import +#import #import #import #import diff --git a/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRFactSetRenderer.mm b/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRFactSetRenderer.mm index cea30a1000..9f621c2b66 100644 --- a/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRFactSetRenderer.mm +++ b/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRFactSetRenderer.mm @@ -4,16 +4,17 @@ // // Copyright © 2017 Microsoft. All rights reserved. // -#import "ACRTextBlockRenderer.h" -#import "ACRContentHoldingUIView.h" #import "ACRFactSetRenderer.h" -#import "ACRSeparator.h" +#import "ACOBaseCardElementPrivate.h" +#import "ACOHostConfigPrivate.h" #import "ACRColumnSetView.h" +#import "ACRContentHoldingUIView.h" +#import "ACRRegistration.h" +#import "ACRSeparator.h" +#import "ACRTextBlockRenderer.h" +#import "ACRUILabel.h" #import "Fact.h" #import "FactSet.h" -#import "ACOHostConfigPrivate.h" -#import "ACOBaseCardElementPrivate.h" -#import "ACRUILabel.h" #import "UtiliOS.h" @implementation ACRFactSetRenderer @@ -123,17 +124,25 @@ - (UIView *)render:(UIView *)viewGroup [factSetWrapperView adjustHuggingForLastElement]; - for(auto fact :fctSet->GetFacts()) - { + BOOL isOverridden = [[ACRRegistration getInstance] isElementRendererOverridden:ACRCardElementType::ACRFactSet]; + + for (auto fact : fctSet->GetFacts()) { NSString *title = [NSString stringWithCString:fact->GetTitle().c_str() encoding:NSUTF8StringEncoding]; + NSString *titleElemId = [key stringByAppendingString:[[NSNumber numberWithInt:rowFactId++] stringValue]]; + if (isOverridden == YES) { + RichTextElementProperties titleTextProp{config->GetFactSet().title, fact->GetTitle(), fact->GetLanguage()}; + buildIntermediateResultForText(rootView, acoConfig, titleTextProp, titleElemId); + } + ACRUILabel *titleLab = [ACRFactSetRenderer buildLabel:title superview:viewGroup hostConfig:acoConfig textConfig:config->GetFactSet().title containerStyle:style - elementId:[key stringByAppendingString:[[NSNumber numberWithInt:rowFactId++] stringValue]] + elementId:titleElemId rootView:rootView element:elem]; + [titleLab setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical]; [titleLab setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal]; [titleLab setContentHuggingPriority:UILayoutPriorityDefaultHigh forAxis:UILayoutConstraintAxisHorizontal]; @@ -143,15 +152,23 @@ - (UIView *)render:(UIView *)viewGroup constraintForTitleLab.active = YES; constraintForTitleLab.priority = UILayoutPriorityRequired; } + NSString *value = [NSString stringWithCString:fact->GetValue().c_str() encoding:NSUTF8StringEncoding]; + NSString *valElemId = [key stringByAppendingString:[[NSNumber numberWithInt:rowFactId++] stringValue]]; + if (isOverridden == YES) { + RichTextElementProperties valueTextProp{config->GetFactSet().value, fact->GetValue(), fact->GetLanguage()}; + buildIntermediateResultForText(rootView, acoConfig, valueTextProp, valElemId); + } + ACRUILabel *valueLab = [ACRFactSetRenderer buildLabel:value superview:viewGroup hostConfig:acoConfig textConfig:config->GetFactSet().value containerStyle:style - elementId:[key stringByAppendingString:[[NSNumber numberWithInt:rowFactId++] stringValue]] + elementId:valElemId rootView:rootView element:elem]; + [valueLab setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical]; [titleStack addArrangedSubview:titleLab]; [valueStack addArrangedSubview:valueLab]; diff --git a/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRRichTextBlockRenderer.mm b/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRRichTextBlockRenderer.mm index ac286fdc5e..9c208a7c3a 100644 --- a/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRRichTextBlockRenderer.mm +++ b/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRRichTextBlockRenderer.mm @@ -6,21 +6,22 @@ // #import "ACRRichTextBlockRenderer.h" -#import "ACRContentHoldingUIView.h" -#import "RichTextBlock.h" -#import "TextRun.h" -#import "ACRAggregateTarget.h" -#import "HostConfig.h" -#import "MarkDownParser.h" -#import "ACRView.h" -#import "ACOHostConfigPrivate.h" -#import "ACOBaseCardElementPrivate.h" #import "ACOBaseActionElementPrivate.h" +#import "ACOBaseCardElementPrivate.h" +#import "ACOHostConfigPrivate.h" +#import "ACRAggregateTarget.h" +#import "ACRContentHoldingUIView.h" +#import "ACRLongPressGestureRecognizerFactory.h" +#import "ACRRegistration.h" #import "ACRUILabel.h" +#import "ACRView.h" #import "DateTimePreparsedToken.h" #import "DateTimePreparser.h" +#import "HostConfig.h" +#import "MarkDownParser.h" +#import "RichTextBlock.h" +#import "TextRun.h" #import "UtiliOS.h" -#import "ACRLongPressGestureRecognizerFactory.h" @implementation ACRRichTextBlockRenderer @@ -44,7 +45,8 @@ - (UIView *)render:(UIView *)viewGroup std::shared_ptr config = [acoConfig getHostConfig]; std::shared_ptr elem = [acoElem element]; std::shared_ptr rTxtBlck = std::dynamic_pointer_cast(elem); - ACRUILabel *lab = [[ACRUILabel alloc] initWithFrame:CGRectMake(0,0,viewGroup.frame.size.width, 0)]; + ACRUILabel *lab = + [[ACRUILabel alloc] initWithFrame:CGRectMake(0, 0, viewGroup.frame.size.width, 0)]; lab.backgroundColor = [UIColor clearColor]; lab.style = [viewGroup style]; lab.editable = NO; @@ -52,20 +54,29 @@ - (UIView *)render:(UIView *)viewGroup lab.textContainerInset = UIEdgeInsetsZero; lab.layoutManager.usesFontLeading = false; + BOOL isOverriden = [[ACRRegistration getInstance] isElementRendererOverridden:ACRCardElementType::ACRRichTextBlock]; NSMutableAttributedString *content = [[NSMutableAttributedString alloc] init]; - if(rootView){ + if (rootView) { NSMutableDictionary *textMap = [rootView getTextMap]; - for(const auto &inlineText : rTxtBlck->GetInlines()) { + for (const auto &inlineText : rTxtBlck->GetInlines()) { std::shared_ptr textRun = std::static_pointer_cast(inlineText); if (textRun) { - NSNumber *number = [NSNumber numberWithUnsignedLongLong:(unsigned long long)textRun.get()]; + NSNumber *number = + [NSNumber numberWithUnsignedLongLong:(unsigned long long)textRun.get()]; NSString *key = [number stringValue]; - NSDictionary* data = textMap[key]; NSData *htmlData = nil; NSDictionary *options = nil; NSDictionary *descriptor = nil; NSString *text = nil; + + if (isOverriden) { + RichTextElementProperties textProp; + TextRunToRichTextElementProperties(textRun, textProp); + buildIntermediateResultForText(rootView, acoConfig, textProp, key); + } + + NSDictionary *data = textMap[key]; if (data) { htmlData = data[@"html"]; options = data[@"options"]; @@ -75,50 +86,85 @@ - (UIView *)render:(UIView *)viewGroup NSMutableAttributedString *textRunContent = nil; // Initializing NSMutableAttributedString for HTML rendering is very slow - if(htmlData) { - textRunContent = [[NSMutableAttributedString alloc] initWithData:htmlData options:options documentAttributes:nil error:nil]; + if (htmlData) { + textRunContent = [[NSMutableAttributedString alloc] initWithData:htmlData + options:options + documentAttributes:nil + error:nil]; lab.selectable = YES; lab.dataDetectorTypes = UIDataDetectorTypeLink; lab.userInteractionEnabled = YES; } else { - textRunContent = [[NSMutableAttributedString alloc] initWithString:text attributes:descriptor]; + textRunContent = [[NSMutableAttributedString alloc] initWithString:text + attributes:descriptor]; // text is preprocessed by markdown parser, and will wrapped by

// lines below remove the p tags [textRunContent deleteCharactersInRange:NSMakeRange(0, 3)]; - [textRunContent deleteCharactersInRange:NSMakeRange([textRunContent length] -4, 4)]; + [textRunContent + deleteCharactersInRange:NSMakeRange([textRunContent length] - 4, 4)]; } // Set paragraph style such as line break mode and alignment NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init]; - paragraphStyle.alignment = [ACOHostConfig getTextBlockAlignment:rTxtBlck->GetHorizontalAlignment()]; + paragraphStyle.alignment = + [ACOHostConfig getTextBlockAlignment:rTxtBlck->GetHorizontalAlignment()]; // Obtain text color to apply to the attributed string ACRContainerStyle style = lab.style; - auto foregroundColor = [acoConfig getTextBlockColor:style textColor:textRun->GetTextColor() subtleOption:textRun->GetIsSubtle()]; + auto foregroundColor = [acoConfig getTextBlockColor:style + textColor:textRun->GetTextColor() + subtleOption:textRun->GetIsSubtle()]; // Config and add Select Action std::shared_ptr baseAction = textRun->GetSelectAction(); - if(baseAction) { - NSObject* target; - if (ACRRenderingStatus::ACROk == buildTarget([rootView getSelectActionsTargetBuilderDirector], baseAction, &target)) { - [textRunContent addAttribute:@"SelectAction" value:target range:NSMakeRange(0, textRunContent.length - 1)]; - [ACRLongPressGestureRecognizerFactory addTapGestureRecognizerToUITextView:lab target:(NSObject *)target rootView:rootView hostConfig:acoConfig]; + if (baseAction) { + NSObject *target; + if (ACRRenderingStatus::ACROk == + buildTarget([rootView getSelectActionsTargetBuilderDirector], baseAction, + &target)) { + NSRange selectActionRange = NSMakeRange(0, textRunContent.length - 1); + + [textRunContent addAttribute:@"SelectAction" + value:target + range:selectActionRange]; + [ACRLongPressGestureRecognizerFactory + addTapGestureRecognizerToUITextView:lab + target:(NSObject + *)target + rootView:rootView + hostConfig:acoConfig]; + + [textRunContent addAttribute:NSUnderlineStyleAttributeName + value:[NSNumber numberWithInt:NSUnderlineStyleSingle] + range:selectActionRange]; + [textRunContent addAttribute:NSUnderlineColorAttributeName + value:foregroundColor + range:selectActionRange]; } } // apply hightlight to textrun - if(textRun->GetHighlight()) { + if (textRun->GetHighlight()) { UIColor *highlightColor = [acoConfig getHighlightColor:style foregroundColor:textRun->GetTextColor() subtleOption:textRun->GetIsSubtle()]; - [textRunContent addAttribute:NSBackgroundColorAttributeName value:highlightColor range:NSMakeRange(0,textRunContent.length)]; + [textRunContent addAttribute:NSBackgroundColorAttributeName + value:highlightColor + range:NSMakeRange(0, textRunContent.length)]; } - - if(textRun->GetStrikethrough()) { - [textRunContent addAttribute:NSStrikethroughStyleAttributeName value:[NSNumber numberWithInteger:NSUnderlineStyleSingle] range:NSMakeRange(0, textRunContent.length)]; + + if (textRun->GetStrikethrough()) { + [textRunContent addAttribute:NSStrikethroughStyleAttributeName + value:[NSNumber numberWithInteger:NSUnderlineStyleSingle] + range:NSMakeRange(0, textRunContent.length)]; } - // Add paragraph style, text color, text weight as attributes to a NSMutableAttributedString, content. - [textRunContent addAttributes:@{NSParagraphStyleAttributeName:paragraphStyle, NSForegroundColorAttributeName:foregroundColor,} range:NSMakeRange(0, textRunContent.length - 1)]; + // Add paragraph style, text color, text weight as attributes to a + // NSMutableAttributedString, content. + [textRunContent addAttributes:@{ + NSParagraphStyleAttributeName : paragraphStyle, + NSForegroundColorAttributeName : foregroundColor, + } + range:NSMakeRange(0, textRunContent.length - 1)]; [content appendAttributedString:textRunContent]; } @@ -129,7 +175,8 @@ - (UIView *)render:(UIView *)viewGroup lab.attributedText = content; lab.area = lab.frame.size.width * lab.frame.size.height; - ACRContentHoldingUIView *wrappingview = [[ACRContentHoldingUIView alloc] initWithFrame:lab.frame]; + ACRContentHoldingUIView *wrappingview = + [[ACRContentHoldingUIView alloc] initWithFrame:lab.frame]; wrappingview.translatesAutoresizingMaskIntoConstraints = NO; lab.translatesAutoresizingMaskIntoConstraints = NO; @@ -137,29 +184,63 @@ - (UIView *)render:(UIView *)viewGroup [wrappingview addSubview:lab]; NSLayoutAttribute horizontalAlignment = NSLayoutAttributeLeading; - if(rTxtBlck->GetHorizontalAlignment() == HorizontalAlignment::Right) { + if (rTxtBlck->GetHorizontalAlignment() == HorizontalAlignment::Right) { horizontalAlignment = NSLayoutAttributeTrailing; } else if (rTxtBlck->GetHorizontalAlignment() == HorizontalAlignment::Center) { horizontalAlignment = NSLayoutAttributeCenterX; } - [NSLayoutConstraint constraintWithItem:lab attribute:horizontalAlignment relatedBy:NSLayoutRelationEqual toItem:wrappingview attribute:horizontalAlignment multiplier:1.0 constant:0].active = YES; - [NSLayoutConstraint constraintWithItem:lab attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:wrappingview attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0].active = YES; - [NSLayoutConstraint constraintWithItem:lab attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:wrappingview attribute:NSLayoutAttributeTop multiplier:1.0 constant:0].active = YES; + [NSLayoutConstraint constraintWithItem:lab + attribute:horizontalAlignment + relatedBy:NSLayoutRelationEqual + toItem:wrappingview + attribute:horizontalAlignment + multiplier:1.0 + constant:0] + .active = YES; + [NSLayoutConstraint constraintWithItem:lab + attribute:NSLayoutAttributeBottom + relatedBy:NSLayoutRelationEqual + toItem:wrappingview + attribute:NSLayoutAttributeBottom + multiplier:1.0 + constant:0] + .active = YES; + [NSLayoutConstraint constraintWithItem:lab + attribute:NSLayoutAttributeTop + relatedBy:NSLayoutRelationEqual + toItem:wrappingview + attribute:NSLayoutAttributeTop + multiplier:1.0 + constant:0] + .active = YES; lab.textContainer.maximumNumberOfLines = 0; - if(rTxtBlck->GetHeight() == HeightType::Auto){ - [wrappingview setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical]; - [wrappingview setContentHuggingPriority:UILayoutPriorityDefaultHigh forAxis:UILayoutConstraintAxisVertical]; + if (rTxtBlck->GetHeight() == HeightType::Auto) { + [wrappingview setContentCompressionResistancePriority:UILayoutPriorityRequired + forAxis:UILayoutConstraintAxisVertical]; + [wrappingview setContentHuggingPriority:UILayoutPriorityDefaultHigh + forAxis:UILayoutConstraintAxisVertical]; } else { - [wrappingview setContentHuggingPriority:UILayoutPriorityDefaultLow forAxis:UILayoutConstraintAxisVertical]; - [wrappingview setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical]; + [wrappingview setContentHuggingPriority:UILayoutPriorityDefaultLow + forAxis:UILayoutConstraintAxisVertical]; + [wrappingview setContentCompressionResistancePriority:UILayoutPriorityRequired + forAxis:UILayoutConstraintAxisVertical]; } - [NSLayoutConstraint constraintWithItem:wrappingview attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:lab attribute:NSLayoutAttributeWidth multiplier:1.0 constant:0].active = YES; - [lab setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal]; - [wrappingview setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal]; + [NSLayoutConstraint constraintWithItem:wrappingview + attribute:NSLayoutAttributeWidth + relatedBy:NSLayoutRelationGreaterThanOrEqual + toItem:lab + attribute:NSLayoutAttributeWidth + multiplier:1.0 + constant:0] + .active = YES; + [lab setContentCompressionResistancePriority:UILayoutPriorityRequired + forAxis:UILayoutConstraintAxisHorizontal]; + [wrappingview setContentCompressionResistancePriority:UILayoutPriorityRequired + forAxis:UILayoutConstraintAxisHorizontal]; configVisibility(wrappingview, elem); diff --git a/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRTextBlockRenderer.mm b/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRTextBlockRenderer.mm index ddfe3b30e6..3f9093a3af 100644 --- a/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRTextBlockRenderer.mm +++ b/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRTextBlockRenderer.mm @@ -6,16 +6,17 @@ // #import "ACRTextBlockRenderer.h" -#import "ACRContentHoldingUIView.h" -#import "TextBlock.h" -#import "HostConfig.h" -#import "MarkDownParser.h" -#import "ACRView.h" -#import "ACOHostConfigPrivate.h" #import "ACOBaseCardElementPrivate.h" +#import "ACOHostConfigPrivate.h" +#import "ACRContentHoldingUIView.h" +#import "ACRRegistration.h" #import "ACRUILabel.h" +#import "ACRView.h" #import "DateTimePreparsedToken.h" #import "DateTimePreparser.h" +#import "HostConfig.h" +#import "MarkDownParser.h" +#import "TextBlock.h" #import "UtiliOS.h" @implementation ACRTextBlockRenderer @@ -32,34 +33,46 @@ + (ACRCardElementType)elemType } - (UIView *)render:(UIView *)viewGroup - rootView:(ACRView *)rootView - inputs:(NSMutableArray *)inputs - baseCardElement:(ACOBaseCardElement *)acoElem - hostConfig:(ACOHostConfig *)acoConfig; + rootView:(ACRView *)rootView + inputs:(NSMutableArray *)inputs + baseCardElement:(ACOBaseCardElement *)acoElem + hostConfig:(ACOHostConfig *)acoConfig; { std::shared_ptr config = [acoConfig getHostConfig]; std::shared_ptr elem = [acoElem element]; std::shared_ptr txtBlck = std::dynamic_pointer_cast(elem); - ACRUILabel *lab = [[ACRUILabel alloc] initWithFrame:CGRectMake(0,0,viewGroup.frame.size.width, 0)]; + ACRUILabel *lab = [[ACRUILabel alloc] initWithFrame:CGRectMake(0, 0, viewGroup.frame.size.width, 0)]; lab.backgroundColor = [UIColor clearColor]; lab.style = [viewGroup style]; NSMutableAttributedString *content = nil; - if(rootView){ + if (rootView) { NSMutableDictionary *textMap = [rootView getTextMap]; NSNumber *number = [NSNumber numberWithUnsignedLongLong:(unsigned long long)txtBlck.get()]; NSString *key = [number stringValue]; - NSDictionary* data = textMap[key]; - NSData *htmlData = data[@"html"]; - NSDictionary *options = data[@"options"]; - NSDictionary *descriptor = data[@"descriptor"]; - NSString *text = data[@"nonhtml"]; + NSDictionary *data = nil; + NSData *htmlData = nil; + NSDictionary *options = nil; + NSDictionary *descriptor = nil; + NSString *text = nil; + + if ([[ACRRegistration getInstance] isElementRendererOverridden:ACRCardElementType::ACRTextBlock] == YES) { + RichTextElementProperties textProp; + TextBlockToRichTextElementProperties(txtBlck, textProp); + buildIntermediateResultForText(rootView, acoConfig, textProp, key); + } + + data = textMap[key]; + htmlData = data[@"html"]; + options = data[@"options"]; + descriptor = data[@"descriptor"]; + text = data[@"nonhtml"]; // Initializing NSMutableAttributedString for HTML rendering is very slow - if(htmlData) { + if (htmlData) { content = [[NSMutableAttributedString alloc] initWithData:htmlData options:options documentAttributes:nil error:nil]; // Drop newline char - [content deleteCharactersInRange:NSMakeRange([content length] -1, 1)]; + [content deleteCharactersInRange:NSMakeRange([content length] - 1, 1)]; lab.selectable = YES; lab.dataDetectorTypes = UIDataDetectorTypeLink; lab.userInteractionEnabled = YES; @@ -67,7 +80,7 @@ - (UIView *)render:(UIView *)viewGroup // if html rendering is skipped, remove p tags from both ends (

,

) content = [[NSMutableAttributedString alloc] initWithString:text attributes:descriptor]; [content deleteCharactersInRange:NSMakeRange(0, 3)]; - [content deleteCharactersInRange:NSMakeRange([content length] -4, 4)]; + [content deleteCharactersInRange:NSMakeRange([content length] - 4, 4)]; } lab.editable = NO; lab.textContainer.lineFragmentPadding = 0; @@ -83,8 +96,12 @@ - (UIView *)render:(UIView *)viewGroup auto foregroundColor = [acoConfig getTextBlockColor:style textColor:txtBlck->GetTextColor() subtleOption:txtBlck->GetIsSubtle()]; // Add paragraph style, text color, text weight as attributes to a NSMutableAttributedString, content. - [content addAttributes:@{NSParagraphStyleAttributeName:paragraphStyle, NSForegroundColorAttributeName:foregroundColor,} range:NSMakeRange(0, content.length)]; - + [content addAttributes:@{ + NSParagraphStyleAttributeName : paragraphStyle, + NSForegroundColorAttributeName : foregroundColor, + } + range:NSMakeRange(0, content.length)]; + lab.textContainer.lineBreakMode = NSLineBreakByTruncatingTail; lab.attributedText = content; } @@ -99,11 +116,11 @@ - (UIView *)render:(UIView *)viewGroup [wrappingview addSubview:lab]; lab.textContainer.maximumNumberOfLines = int(txtBlck->GetMaxLines()); - if(!lab.textContainer.maximumNumberOfLines && !txtBlck->GetWrap()){ + if (!lab.textContainer.maximumNumberOfLines && !txtBlck->GetWrap()) { lab.textContainer.maximumNumberOfLines = 1; } - if(txtBlck->GetHeight() == HeightType::Auto){ + if (txtBlck->GetHeight() == HeightType::Auto) { [wrappingview setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical]; [wrappingview setContentHuggingPriority:UILayoutPriorityDefaultHigh forAxis:UILayoutConstraintAxisVertical]; } else { @@ -137,14 +154,14 @@ - (UIView *)render:(UIView *)viewGroup [rightGuide.trailingAnchor constraintEqualToAnchor:wrappingview.trailingAnchor].active = YES; [lab.leadingAnchor constraintEqualToAnchor:wrappingview.leadingAnchor].active = YES; } - + if (adaptiveAlignment == HorizontalAlignment::Center) { [lab.centerXAnchor constraintEqualToAnchor:wrappingview.centerXAnchor].active = YES; } [wrappingview.heightAnchor constraintEqualToAnchor:lab.heightAnchor].active = YES; [wrappingview.widthAnchor constraintGreaterThanOrEqualToAnchor:lab.widthAnchor].active = YES; - + [lab.centerYAnchor constraintEqualToAnchor:wrappingview.centerYAnchor].active = YES; configVisibility(wrappingview, elem); diff --git a/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRView.mm b/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRView.mm index ed8c81e600..2d4af35918 100644 --- a/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRView.mm +++ b/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRView.mm @@ -5,44 +5,43 @@ // Copyright © 2018 Microsoft. All rights reserved. // -#import "ACRViewPrivate.h" -#import "ACRContentHoldingUIView.h" +#import "ACOAdaptiveCardPrivate.h" +#import "ACOBaseCardElementPrivate.h" #import "ACOHostConfigPrivate.h" +#import "ACRButton.h" +#import "ACRContentHoldingUIView.h" #import "ACRIBaseCardElementRenderer.h" -#import "ACOBaseCardElementPrivate.h" -#import "ACOAdaptiveCardPrivate.h" -#import "SharedAdaptiveCard.h" -#import "ACRRendererPrivate.h" +#import "ACRImageRenderer.h" #import "ACRRegistration.h" -#import -#import "Container.h" -#import "ColumnSet.h" +#import "ACRRendererPrivate.h" +#import "ACRTextBlockRenderer.h" +#import "ACRUIImageView.h" +#import "ACRUILabel.h" +#import "ACRViewPrivate.h" +#import "AdaptiveBase64Util.h" +#import "BackgroundImage.h" #import "Column.h" -#import "Fact.h" +#import "ColumnSet.h" +#import "Container.h" #import "Enums.h" +#import "Fact.h" +#import "FactSet.h" +#import "ImageSet.h" +#import "MarkDownParser.h" #import "Media.h" -#import "TextInput.h" -#import "ACRImageRenderer.h" -#import "TextBlock.h" -#import "TextRun.h" #import "RichTextBlock.h" -#import "ACRTextBlockRenderer.h" -#import "MarkDownParser.h" -#import "ImageSet.h" -#import "ACRUILabel.h" -#import "ACRUIImageView.h" -#import "FactSet.h" #import "RichTextElementProperties.h" -#import "AdaptiveBase64Util.h" -#import "ACRButton.h" -#import "BackgroundImage.h" +#import "SharedAdaptiveCard.h" +#import "TextBlock.h" +#import "TextInput.h" +#import "TextRun.h" #import "UtiliOS.h" +#import using namespace AdaptiveCards; -typedef UIImage* (^ImageLoadBlock)(NSURL *url); +typedef UIImage * (^ImageLoadBlock)(NSURL *url); -@implementation ACRView -{ +@implementation ACRView { ACOAdaptiveCard *_adaptiveCard; ACOHostConfig *_hostConfig; NSMutableDictionary *_imageViewMap; @@ -56,7 +55,7 @@ @implementation ACRView NSMutableDictionary *_imageContextMap; NSMutableDictionary *_imageViewContextMap; NSMutableSet *_setOfRemovedObservers; - NSMutableDictionary *_paddingMap; + NSMutableDictionary *_paddingMap; ACRTargetBuilderDirector *_actionsTargetBuilderDirector; ACRTargetBuilderDirector *_selectActionsTargetBuilderDirector; ACRTargetBuilderDirector *_quickReplyTargetBuilderDirector; @@ -65,7 +64,7 @@ @implementation ACRView - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; - if(self){ + if (self) { std::shared_ptr cHostConfig = std::make_shared(); _hostConfig = [[ACOHostConfig alloc] initWithConfig:cHostConfig]; _imageViewMap = [[NSMutableDictionary alloc] init]; @@ -98,7 +97,7 @@ - (instancetype)init:(ACOAdaptiveCard *)card _selectActionsTargetBuilderDirector = [[ACRTargetBuilderDirector alloc] init:self capability:ACRSelectAction adaptiveHostConfig:_hostConfig]; _quickReplyTargetBuilderDirector = [[ACRTargetBuilderDirector alloc] init:self capability:ACRQuickReply adaptiveHostConfig:_hostConfig]; } - unsigned int padding = [_hostConfig getHostConfig]->GetSpacing().paddingSpacing; + unsigned int padding = [_hostConfig getHostConfig] -> GetSpacing().paddingSpacing; [self removeConstraints:self.constraints]; if (padding) { [self applyPadding:padding priority:1000]; @@ -122,18 +121,18 @@ - (UIView *)render { NSMutableArray *inputs = [[NSMutableArray alloc] init]; - if(self.frame.size.width){ + if (self.frame.size.width) { [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:self.frame.size.width].active = YES; } UIView *newView = [ACRRenderer renderWithAdaptiveCards:[_adaptiveCard card] inputs:inputs context:self containingView:self hostconfig:_hostConfig]; - ContainerStyle style = ([_hostConfig getHostConfig]->GetAdaptiveCard().allowCustomStyle)? [_adaptiveCard card]->GetStyle(): ContainerStyle::Default; + ContainerStyle style = ([_hostConfig getHostConfig] -> GetAdaptiveCard().allowCustomStyle) ? [_adaptiveCard card] -> GetStyle() : ContainerStyle::Default; newView.backgroundColor = [_hostConfig getBackgroundColorForContainerStyle: - [ACOHostConfig getPlatformContainerStyle:style]]; + [ACOHostConfig getPlatformContainerStyle:style]]; - renderBackgroundImage([_adaptiveCard card]->GetBackgroundImage(), newView, self); + renderBackgroundImage([_adaptiveCard card] -> GetBackgroundImage(), newView, self); [self callDidLoadElementsIfNeeded]; return newView; @@ -148,27 +147,18 @@ - (void)waitForAsyncTasksToFinish - (void)callDidLoadElementsIfNeeded { // Call back app with didLoadElements - if ([[self acrActionDelegate] respondsToSelector:@selector(didLoadElements)] && !_numberOfSubscribers) - { + if ([[self acrActionDelegate] respondsToSelector:@selector(didLoadElements)] && !_numberOfSubscribers) { [[self acrActionDelegate] didLoadElements]; } } - (void)processBaseCardElement:(std::shared_ptr const &)elem { - switch (elem->GetElementType()) - { - case CardElementType::TextBlock: - { + switch (elem->GetElementType()) { + case CardElementType::TextBlock: { std::shared_ptr textBlockElement = std::static_pointer_cast(elem); RichTextElementProperties textProp; - textProp.SetText(textBlockElement->GetText()); - textProp.SetTextSize(textBlockElement->GetTextSize()); - textProp.SetTextWeight(textBlockElement->GetTextWeight()); - textProp.SetFontType(textBlockElement->GetFontType()); - textProp.SetTextColor(textBlockElement->GetTextColor()); - textProp.SetIsSubtle(textBlockElement->GetIsSubtle()); - textProp.SetLanguage(textBlockElement->GetLanguage()); + TextBlockToRichTextElementProperties(textBlockElement, textProp); /// tag a base card element with unique key NSNumber *number = [NSNumber numberWithUnsignedLongLong:(unsigned long long)textBlockElement.get()]; @@ -176,22 +166,13 @@ - (void)processBaseCardElement:(std::shared_ptr const &)elem [self processTextConcurrently:textProp elementId:key]; break; } - case CardElementType::RichTextBlock: - { + case CardElementType::RichTextBlock: { std::shared_ptr rTxtBlkElement = std::static_pointer_cast(elem); for (const auto &inlineText : rTxtBlkElement->GetInlines()) { std::shared_ptr textRun = std::static_pointer_cast(inlineText); - if(textRun) { + if (textRun) { RichTextElementProperties textProp; - textProp.SetText(textRun->GetText()); - textProp.SetTextSize(textRun->GetTextSize()); - textProp.SetTextWeight(textRun->GetTextWeight()); - textProp.SetFontType(textRun->GetFontType()); - textProp.SetTextColor(textRun->GetTextColor()); - textProp.SetIsSubtle(textRun->GetIsSubtle()); - textProp.SetLanguage(textRun->GetLanguage()); - textProp.SetItalic(textRun->GetItalic()); - textProp.SetStrikethrough(textRun->GetStrikethrough()); + TextRunToRichTextElementProperties(textRun, textProp); NSNumber *number = [NSNumber numberWithUnsignedLongLong:(unsigned long long)textRun.get()]; NSString *key = [number stringValue]; [self processTextConcurrently:textProp elementId:key]; @@ -199,34 +180,33 @@ - (void)processBaseCardElement:(std::shared_ptr const &)elem } break; } - case CardElementType::FactSet: - { + case CardElementType::FactSet: { [self tagBaseCardElement:elem]; std::shared_ptr factSet = std::dynamic_pointer_cast(elem); NSString *key = [NSString stringWithCString:elem->GetId().c_str() encoding:[NSString defaultCStringEncoding]]; key = [key stringByAppendingString:@"*"]; int rowFactId = 0; - for(auto fact : factSet->GetFacts()) { + for (auto fact : factSet->GetFacts()) { - RichTextElementProperties titleTextProp{[_hostConfig getHostConfig]->GetFactSet().title, fact->GetTitle(), fact->GetLanguage()}; + RichTextElementProperties titleTextProp{[_hostConfig getHostConfig] -> GetFactSet().title, fact->GetTitle(), fact->GetLanguage()}; [self processTextConcurrently:titleTextProp elementId:[key stringByAppendingString:[[NSNumber numberWithInt:rowFactId++] stringValue]]]; - RichTextElementProperties valueTextProp{[_hostConfig getHostConfig]->GetFactSet().value, fact->GetValue(), fact->GetLanguage()}; + RichTextElementProperties valueTextProp{[_hostConfig getHostConfig] -> GetFactSet().value, fact->GetValue(), fact->GetLanguage()}; [self processTextConcurrently:valueTextProp elementId:[key stringByAppendingString:[[NSNumber numberWithInt:rowFactId++] stringValue]]]; } break; } - case CardElementType::Image: - { + case CardElementType::Image: { ObserverActionBlock observerAction = - ^(NSObject* imageResourceResolver, NSString* key, std::shared_ptr const &elem, NSURL* url, ACRView *rootView) { + ^(NSObject *imageResourceResolver, NSString *key, std::shared_ptr const &elem, NSURL *url, ACRView *rootView) { UIImageView *view = [imageResourceResolver resolveImageViewResource:url]; - if(view) { - [view addObserver:self forKeyPath:@"image" + if (view) { + [view addObserver:self + forKeyPath:@"image" options:NSKeyValueObservingOptionNew context:elem.get()]; @@ -234,147 +214,144 @@ - (void)processBaseCardElement:(std::shared_ptr const &)elem [rootView setImageView:key view:view]; [rootView setImageContext:key context:elem]; } - }; + }; [self loadImageAccordingToResourceResolverIF:elem key:nil observerAction:observerAction]; break; } - case CardElementType::ImageSet: - { - std::shared_ptrimgSetElem = std::static_pointer_cast(elem); - for(auto img :imgSetElem->GetImages()) { // loops through images in image set + case CardElementType::ImageSet: { + std::shared_ptr imgSetElem = std::static_pointer_cast(elem); + for (auto img : imgSetElem->GetImages()) { // loops through images in image set std::shared_ptr baseImgElem = std::static_pointer_cast(img); img->SetImageSize(imgSetElem->GetImageSize()); ObserverActionBlock observerAction = - ^(NSObject* imageResourceResolver, NSString* key, std::shared_ptr const &elem, NSURL* url, ACRView *rootView) { - UIImageView *view = [imageResourceResolver resolveImageViewResource:url]; - if(view) { - [view addObserver:self forKeyPath:@"image" - options:NSKeyValueObservingOptionNew - context:elem.get()]; - - // store the image view and image set element for easy retrieval in ACRView::observeValueForKeyPath - [rootView setImageView:key view:view]; - [rootView setImageContext:key context:elem]; - } - }; + ^(NSObject *imageResourceResolver, NSString *key, std::shared_ptr const &elem, NSURL *url, ACRView *rootView) { + UIImageView *view = [imageResourceResolver resolveImageViewResource:url]; + if (view) { + [view addObserver:self + forKeyPath:@"image" + options:NSKeyValueObservingOptionNew + context:elem.get()]; + + // store the image view and image set element for easy retrieval in ACRView::observeValueForKeyPath + [rootView setImageView:key view:view]; + [rootView setImageContext:key context:elem]; + } + }; [self loadImageAccordingToResourceResolverIF:baseImgElem key:nil observerAction:observerAction]; - } break; } - case CardElementType::Media: - { + case CardElementType::Media: { std::shared_ptr mediaElem = std::static_pointer_cast(elem); - std::string poster = mediaElem->GetPoster(); - if(poster.empty()) { - poster = [_hostConfig getHostConfig]->GetMedia().defaultPoster; + std::string poster = mediaElem->GetPoster(); + if (poster.empty()) { + poster = [_hostConfig getHostConfig] -> GetMedia().defaultPoster; } - if(!poster.empty()) { + if (!poster.empty()) { ObserverActionBlock observerAction = - ^(NSObject* imageResourceResolver, NSString* key, std::shared_ptr const &imgElem, NSURL* url, ACRView* rootView) { - UIImageView *view = [imageResourceResolver resolveImageViewResource:url]; - ACRContentHoldingUIView *contentholdingview = [[ACRContentHoldingUIView alloc] initWithFrame:view.frame]; - if(view) { - [contentholdingview addSubview:view]; - contentholdingview.isMediaType = YES; - [view addObserver:self forKeyPath:@"image" - options:NSKeyValueObservingOptionNew - context:elem.get()]; - - // store the image view and media element for easy retrieval in ACRView::observeValueForKeyPath - [rootView setImageView:key view:contentholdingview]; - [rootView setImageContext:key context:elem]; - } - }; + ^(NSObject *imageResourceResolver, NSString *key, std::shared_ptr const &imgElem, NSURL *url, ACRView *rootView) { + UIImageView *view = [imageResourceResolver resolveImageViewResource:url]; + ACRContentHoldingUIView *contentholdingview = [[ACRContentHoldingUIView alloc] initWithFrame:view.frame]; + if (view) { + [contentholdingview addSubview:view]; + contentholdingview.isMediaType = YES; + [view addObserver:self + forKeyPath:@"image" + options:NSKeyValueObservingOptionNew + context:elem.get()]; + + // store the image view and media element for easy retrieval in ACRView::observeValueForKeyPath + [rootView setImageView:key view:contentholdingview]; + [rootView setImageContext:key context:elem]; + } + }; [self loadImageAccordingToResourceResolverIF:elem key:nil observerAction:observerAction]; } - if (![_hostConfig getHostConfig]->GetMedia().playButton.empty()) { + if (![_hostConfig getHostConfig] -> GetMedia().playButton.empty()) { ObserverActionBlock observerAction = - ^(NSObject* imageResourceResolver, NSString* key, std::shared_ptr const &elem, NSURL* url, ACRView* rootView) { - UIImageView *view = [imageResourceResolver resolveImageViewResource:url]; - if(view) { - [view addObserver:rootView forKeyPath:@"image" - options:NSKeyValueObservingOptionNew - context:nil]; - // store the image view for easy retrieval in ACRView::observeValueForKeyPath - [rootView setImageView:key view:view]; - } - }; + ^(NSObject *imageResourceResolver, NSString *key, std::shared_ptr const &elem, NSURL *url, ACRView *rootView) { + UIImageView *view = [imageResourceResolver resolveImageViewResource:url]; + if (view) { + [view addObserver:rootView + forKeyPath:@"image" + options:NSKeyValueObservingOptionNew + context:nil]; + // store the image view for easy retrieval in ACRView::observeValueForKeyPath + [rootView setImageView:key view:view]; + } + }; NSNumber *number = [NSNumber numberWithUnsignedLongLong:(unsigned long long)elem.get()]; - NSString *key = [NSString stringWithFormat:@"%@_%@", [number stringValue], @"playIcon" ]; + NSString *key = [NSString stringWithFormat:@"%@_%@", [number stringValue], @"playIcon"]; - [self loadImageAccordingToResourceResolverIFFromString:[_hostConfig getHostConfig]->GetMedia().playButton key:key observerAction:observerAction]; + [self loadImageAccordingToResourceResolverIFFromString:[_hostConfig getHostConfig] -> GetMedia().playButton key:key observerAction:observerAction]; } break; } - case CardElementType::TextInput: - { + case CardElementType::TextInput: { std::shared_ptr textInput = std::static_pointer_cast(elem); std::shared_ptr action = textInput->GetInlineAction(); - if(action != nullptr && !action->GetIconUrl().empty()) { + if (action != nullptr && !action->GetIconUrl().empty()) { ObserverActionBlockForBaseAction observerAction = - ^(NSObject* imageResourceResolver, NSString* key, std::shared_ptr const &elem, NSURL* url, ACRView *rootView) { - UIImageView *view = [imageResourceResolver resolveImageViewResource:url]; - if(view) { - [view addObserver:self forKeyPath:@"image" - options:NSKeyValueObservingOptionNew - context:elem.get()]; - - // store the image view for easy retrieval in ACRView::observeValueForKeyPath - [rootView setImageView:key view:view]; - } - }; + ^(NSObject *imageResourceResolver, NSString *key, std::shared_ptr const &elem, NSURL *url, ACRView *rootView) { + UIImageView *view = [imageResourceResolver resolveImageViewResource:url]; + if (view) { + [view addObserver:self + forKeyPath:@"image" + options:NSKeyValueObservingOptionNew + context:elem.get()]; + + // store the image view for easy retrieval in ACRView::observeValueForKeyPath + [rootView setImageView:key view:view]; + } + }; [self loadImageAccordingToResourceResolverIFForBaseAction:action key:nil observerAction:observerAction]; } break; } // continue on search - case CardElementType::Container: - { + case CardElementType::Container: { std::shared_ptr container = std::static_pointer_cast(elem); auto backgroundImageProperties = container->GetBackgroundImage(); - if((backgroundImageProperties != nullptr) && !(backgroundImageProperties->GetUrl().empty())) { + if ((backgroundImageProperties != nullptr) && !(backgroundImageProperties->GetUrl().empty())) { ObserverActionBlock observerAction = generateBackgroundImageObserverAction(backgroundImageProperties, self, container); [self loadBackgroundImageAccordingToResourceResolverIF:backgroundImageProperties key:nil observerAction:observerAction]; } std::vector> &new_body = container->GetItems(); - [self addTasksToConcurrentQueue: new_body]; + [self addTasksToConcurrentQueue:new_body]; break; } // continue on search - case CardElementType::ColumnSet: - { + case CardElementType::ColumnSet: { std::shared_ptr columSet = std::static_pointer_cast(elem); std::vector> &columns = columSet->GetColumns(); // ColumnSet is vector of Column, instead of vector of BaseCardElement - for(auto const &column : columns) { // update serial number that is used for generating unique key for image_map - [self processBaseCardElement: column]; + for (auto const &column : columns) { // update serial number that is used for generating unique key for image_map + [self processBaseCardElement:column]; } break; } - case CardElementType::Column: - { + case CardElementType::Column: { std::shared_ptr column = std::static_pointer_cast(elem); // Handle background image (if necessary) auto backgroundImageProperties = column->GetBackgroundImage(); - if((backgroundImageProperties != nullptr) && !(backgroundImageProperties->GetUrl().empty())) { + if ((backgroundImageProperties != nullptr) && !(backgroundImageProperties->GetUrl().empty())) { ObserverActionBlock observerAction = generateBackgroundImageObserverAction(backgroundImageProperties, self, column); [self loadBackgroundImageAccordingToResourceResolverIF:backgroundImageProperties key:nil observerAction:observerAction]; } - + // add column fallbacks to async task queue [self processFallback:column]; - [self addTasksToConcurrentQueue: column->GetItems()]; + [self addTasksToConcurrentQueue:column->GetItems()]; } } } @@ -384,9 +361,8 @@ - (void)addTasksToConcurrentQueue:(std::vector> { ACRRegistration *rendererRegistration = [ACRRegistration getInstance]; - for (auto &elem : body) - { - if ([rendererRegistration isElementRendererOverridden:(ACRCardElementType) elem->GetElementType()] == YES) { + for (auto &elem : body) { + if ([rendererRegistration isElementRendererOverridden:(ACRCardElementType)elem->GetElementType()] == YES) { continue; } @@ -398,18 +374,19 @@ - (void)addTasksToConcurrentQueue:(std::vector> // Walk through the actions found and process them concurrently - (void)loadImagesForActionsAndCheckIfAllActionsHaveIconImages:(std::vector> const &)actions hostconfig:(ACOHostConfig *)hostConfig; { - for(auto &action : actions){ - if(!action->GetIconUrl().empty()) { + for (auto &action : actions) { + if (!action->GetIconUrl().empty()) { ObserverActionBlockForBaseAction observerAction = - ^(NSObject* imageResourceResolver, NSString* key, std::shared_ptr const &elem, NSURL* url, ACRView *rootView) { - UIImageView *view = [imageResourceResolver resolveImageViewResource:url]; - if(view) { - [view addObserver:self forKeyPath:@"image" - options:NSKeyValueObservingOptionNew - context:elem.get()]; - [rootView setImageView:key view:view]; - } - }; + ^(NSObject *imageResourceResolver, NSString *key, std::shared_ptr const &elem, NSURL *url, ACRView *rootView) { + UIImageView *view = [imageResourceResolver resolveImageViewResource:url]; + if (view) { + [view addObserver:self + forKeyPath:@"image" + options:NSKeyValueObservingOptionNew + context:elem.get()]; + [rootView setImageView:key view:view]; + } + }; [self loadImageAccordingToResourceResolverIFForBaseAction:action key:nil observerAction:observerAction]; } else { hostConfig.allActionsHaveIcons = NO; @@ -423,99 +400,22 @@ - (void)processTextConcurrently:(RichTextElementProperties const &)textPropertie RichTextElementProperties textProp = std::move(textProperties); /// dispatch to concurrent queue dispatch_group_async(_async_tasks_group, _global_queue, - ^{ - std::shared_ptr markDownParser = std::make_shared([ACOHostConfig getLocalizedDate:textProp.GetText() language:textProp.GetLanguage()]); - - // MarkDownParser transforms text with MarkDown to a html string - NSString* parsedString = [NSString stringWithCString:markDownParser->TransformToHtml().c_str() encoding:NSUTF8StringEncoding]; - NSDictionary *data = nil; - - // use Apple's html rendering only if the string has markdowns - if(markDownParser->HasHtmlTags() || markDownParser->IsEscaped()) { - NSString *fontFamilyName = nil; - - if(![self->_hostConfig getFontFamily:textProp.GetFontType()]){ - if(textProp.GetFontType() == FontType::Monospace){ - fontFamilyName = @"'Courier New'"; - } else{ - fontFamilyName = @"'-apple-system', 'San Francisco'"; - } - } else { - fontFamilyName = [self->_hostConfig getFontFamily:textProp.GetFontType()]; - } - - NSString *font_style = textProp.GetItalic() ? @"italic" : @"normal"; - // Font and text size are applied as CSS style by appending it to the html string - parsedString = [parsedString stringByAppendingString:[NSString stringWithFormat:@"", - fontFamilyName, - [self->_hostConfig getTextBlockTextSize:textProp.GetFontType() - textSize:textProp.GetTextSize()], - [self->_hostConfig getTextBlockFontWeight:textProp.GetFontType() - textWeight:textProp.GetTextWeight()], - font_style]]; - - NSData *htmlData = [parsedString dataUsingEncoding:NSUTF16StringEncoding]; - NSDictionary *options = @{NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType}; - data = @{@"html" : htmlData, @"options" : options}; - } else { - int fontweight = [self->_hostConfig getTextBlockFontWeight:textProp.GetFontType() - textWeight:textProp.GetTextWeight()]; - // sanity check, 400 is the normal font; - if(fontweight <= 0 || fontweight > 900){ - fontweight = 400; - } - UIFont *font = nil; - fontweight -= 100; - fontweight /= 100; - - if (![self->_hostConfig getFontFamily:textProp.GetFontType()]){ - const NSArray *fontweights = @[@(UIFontWeightUltraLight), @(UIFontWeightThin), @(UIFontWeightLight), @(UIFontWeightRegular), @(UIFontWeightMedium), - @(UIFontWeightSemibold), @(UIFontWeightBold), @(UIFontWeightHeavy), @(UIFontWeightBlack)]; - const CGFloat size = [self->_hostConfig getTextBlockTextSize:textProp.GetFontType() textSize:textProp.GetTextSize()]; - if (textProp.GetFontType() == FontType::Monospace) { - const NSArray *fontweights = @[ @"UltraLight", @"Thin", @"Light", @"Regular", - @"Medium", @"Semibold", @"Bold", @"Heavy", @"Black" ]; - UIFontDescriptor *descriptor = [UIFontDescriptor fontDescriptorWithFontAttributes:@{UIFontDescriptorFamilyAttribute: @"Courier New", - UIFontDescriptorFaceAttribute:fontweights[fontweight]}]; - descriptor = getItalicFontDescriptor(descriptor, textProp.GetItalic()); - - font = [UIFont fontWithDescriptor:descriptor size:[self->_hostConfig getTextBlockTextSize:textProp.GetFontType() textSize:textProp.GetTextSize()]]; - } else { - font = [UIFont systemFontOfSize:size weight:[fontweights[fontweight] floatValue]]; - - if (textProp.GetItalic()) { - font = [UIFont fontWithDescriptor: - getItalicFontDescriptor(font.fontDescriptor, textProp.GetItalic()) - size:size]; - } - } - } else { - // font weight as string since font weight as double doesn't work - // normailze fontweight for indexing - const NSArray *fontweights = @[ @"UltraLight", @"Thin", @"Light", @"Regular", - @"Medium", @"Semibold", @"Bold", @"Heavy", @"Black" ]; - UIFontDescriptor *descriptor = [UIFontDescriptor fontDescriptorWithFontAttributes: - @{UIFontDescriptorFamilyAttribute: [self->_hostConfig getFontFamily:textProp.GetFontType()], - UIFontDescriptorFaceAttribute:fontweights[fontweight]}]; - - descriptor = getItalicFontDescriptor(descriptor, textProp.GetItalic()); - - font = [UIFont fontWithDescriptor:descriptor size:[self->_hostConfig getTextBlockTextSize:textProp.GetFontType() textSize:textProp.GetTextSize()]]; - } - - NSDictionary *attributeDictionary = @{NSFontAttributeName:font}; - data = @{@"nonhtml" : parsedString, @"descriptor" : attributeDictionary}; - } + ^{ + buildIntermediateResultForText(self, self->_hostConfig, textProp, elementId); + }); +} - if(elementId) { - dispatch_sync(self->_serial_text_queue, ^{self->_textMap[elementId] = data; }); - } - }); +- (void)enqueueIntermediateTextProcessingResult:(NSDictionary *)data + elementId:(NSString *)elementId +{ + dispatch_sync(_serial_text_queue, ^{ + self->_textMap[elementId] = data; + }); } - (void)loadImage:(std::string const &)urlStr { - if(urlStr.empty()){ + if (urlStr.empty()) { return; } @@ -523,17 +423,17 @@ - (void)loadImage:(std::string const &)urlStr encoding:[NSString defaultCStringEncoding]]; NSURL *url = [NSURL URLWithString:nSUrlStr]; // if url is relative, try again with adding base url from host config - if([url.relativePath isEqualToString:nSUrlStr]) { + if ([url.relativePath isEqualToString:nSUrlStr]) { url = [NSURL URLWithString:nSUrlStr relativeToURL:_hostConfig.baseURL]; } NSObject *imageResourceResolver = [_hostConfig getResourceResolverForScheme:[url scheme]]; ImageLoadBlock imageloadblock = nil; - if(!imageResourceResolver || ![imageResourceResolver respondsToSelector:@selector(resolveImageResource:)]) { - imageloadblock = ^(NSURL *url){ + if (!imageResourceResolver || ![imageResourceResolver respondsToSelector:@selector(resolveImageResource:)]) { + imageloadblock = ^(NSURL *url) { // download image UIImage *img = nil; - if([url.scheme isEqualToString: @"data"]) { + if ([url.scheme isEqualToString:@"data"]) { NSString *absoluteUri = url.absoluteString; std::string dataUri = AdaptiveCards::AdaptiveBase64Util::ExtractDataFromUri(std::string([absoluteUri UTF8String])); std::vector decodedDataUri = AdaptiveCards::AdaptiveBase64Util::Decode(dataUri); @@ -547,17 +447,18 @@ - (void)loadImage:(std::string const &)urlStr } dispatch_group_async(_async_tasks_group, _global_queue, - ^{ - UIImage *img = nil; - if(imageloadblock) { - img = imageloadblock(url); - } else if(imageResourceResolver){ - img = [imageResourceResolver resolveImageResource:url]; - } + ^{ + UIImage *img = nil; + if (imageloadblock) { + img = imageloadblock(url); + } else if (imageResourceResolver) { + img = [imageResourceResolver resolveImageResource:url]; + } - dispatch_sync(self->_serial_queue, ^{self->_imageViewMap[nSUrlStr] = img;}); - } - ); + dispatch_sync(self->_serial_queue, ^{ + self->_imageViewMap[nSUrlStr] = img; + }); + }); } // add postfix to existing BaseCardElement ID to be used as key @@ -636,7 +537,7 @@ - (void)observeValueForKeyPath:(NSString *)path ofObject:(id)object change:(NSDi } else { // handle background image for adaptive card that uses resource resolver UIImageView *imageView = (UIImageView *)object; - auto backgroundImage = [_adaptiveCard card]->GetBackgroundImage(); + auto backgroundImage = [_adaptiveCard card] -> GetBackgroundImage(); // remove observer early in case background image must be changed to handle mode = repeat [self removeObserver:self forKeyPath:path onObject:object]; @@ -666,7 +567,7 @@ - (void)loadBackgroundImageAccordingToResourceResolverIF:(std::shared_ptrGetUrl().c_str() encoding:[NSString defaultCStringEncoding]]; - if(!key) { + if (!key) { key = [number stringValue]; } @@ -674,7 +575,8 @@ - (void)loadBackgroundImageAccordingToResourceResolverIF:(std::shared_ptr imgElem = std::make_shared(); imgElem->SetUrl(url); @@ -687,7 +589,8 @@ - (void)loadImageAccordingToResourceResolverIFFromString:(std::string const &)ur } - (void)loadImageAccordingToResourceResolverIF:(std::shared_ptr const &)elem - key:(NSString *)key observerAction:(ObserverActionBlock)observerAction + key:(NSString *)key + observerAction:(ObserverActionBlock)observerAction { NSNumber *number = nil; NSString *nSUrlStr = nil; @@ -725,7 +628,8 @@ - (void)loadImage:(NSString *)nSUrlStr key:(NSString *)key context:(std::shared_ } - (void)loadImageAccordingToResourceResolverIFForBaseAction:(std::shared_ptr const &)elem - key:(NSString *)key observerAction:(ObserverActionBlockForBaseAction)observerAction + key:(NSString *)key + observerAction:(ObserverActionBlockForBaseAction)observerAction { NSNumber *number = nil; NSString *nSUrlStr = nil; @@ -734,7 +638,7 @@ - (void)loadImageAccordingToResourceResolverIFForBaseAction:(std::shared_ptrGetIconUrl().c_str() encoding:[NSString defaultCStringEncoding]]; - if(!key) { + if (!key) { key = [number stringValue]; } @@ -751,8 +655,7 @@ - (void)loadImageAccordingToResourceResolverIFForBaseAction:(std::shared_ptr - (ACRTargetBuilderDirector *)getQuickReplyTargetBuilderDirector; +- (void)enqueueIntermediateTextProcessingResult:(NSDictionary *)data + elementId:(NSString *)elementId; @end From 605f1c5d3c0cc19db3b63c79cc647b7d33aec94b Mon Sep 17 00:00:00 2001 From: "Paul Campbell (DEP)" Date: Mon, 4 Nov 2019 12:15:29 -0800 Subject: [PATCH 15/19] [UWP] Update PerfApp test signing cert --- source/uwp/PerfApp/PerfApp.vcxproj | 1 + source/uwp/PerfApp/PerfApp_TemporaryKey.pfx | Bin 2520 -> 2520 bytes 2 files changed, 1 insertion(+) diff --git a/source/uwp/PerfApp/PerfApp.vcxproj b/source/uwp/PerfApp/PerfApp.vcxproj index 0b1a07cd62..ac7bbd3146 100644 --- a/source/uwp/PerfApp/PerfApp.vcxproj +++ b/source/uwp/PerfApp/PerfApp.vcxproj @@ -124,6 +124,7 @@ PerfApp_TemporaryKey.pfx + 4BC45B550883E1E2D5459F3C11F16C53211F9630 diff --git a/source/uwp/PerfApp/PerfApp_TemporaryKey.pfx b/source/uwp/PerfApp/PerfApp_TemporaryKey.pfx index 106669bd9273facff1a06114ab7d7bf2ba261333..e25fe9ca4daab1198a5d2e756a0fb6ba0acb7c6a 100644 GIT binary patch delta 2267 zcmV<12qgE|6W9}wXn%Ucg4dU>g0cbv2haq91lU*0AufNh3O>;xJDa?2OVwcqskhdkfVM6=I+%n}_d%In1Nj)OVcBL;gc zVy^D)9hlXLxEbSDK>l6uO~LB(Pe!;1fkDVHx-X2Qf7j(6JAbY3ep(%emjt)KD4y8I z?|`p`Z8{gP6vB*bsN3)|*R$ldKgLd$&324al0(`;(Yh$((K@<)=`un`+aIE`mMhD$ zYR~FUk0@HEyW-axG~%`^sE~8&WJGIU*=BMGuOJ|PrDfw75QN|jtrmbU{0#_34bT2N zrQ+tH$?5}zpMQU$@GpwJ6SLvJyc7gnM|bw$D=dl$0wwtB#J9~MU4{cd9$WqUCQw<&!LB8! zbc>cK0E}?^DH`|x9C}9Zf<`jTQ`PG%Vt!DfbhvrAuLVqq!v&_sF;#op4@`{UK7h_a zdjU|*et*EaH`g1X-Vnl5M%bDX2@I`MswgfRF(w2$vNNnw;{;tIU8m(qxC2dH@HHgK z{Jq11rk*Wk2|%BJ%5;PG7jRwzqpQs`Zpx-@VWZiub@8h505H&zDBx1sNMFu1gVRgb z7nEQL>FpQ|mJ_*`M+dVhgp~Mq>vf(*lfK{g)_qNAx|ME!I+|`+3JglII>?dhg_Gg{HjI2DWe7f62dr^YPHDme__ppHM92 zR{zF3S1fO}J9^Lu-*Ebd;>T|^B$OZ@-$M*9w&=`qX82yzhGQKADEJLSY7fyt1XKavX0K5VK$OwXXKsjv^jwk6K1I`!r$bSvC zD_o7Gf%6iI&N8;kp>Q`peE>-&z@NI9Y+N&{jn%hqE&KrZBv-9{YNCy|d^~q*0T5+| zOwKsrEpFknMSO=)GAU_lE`>kTR@~1`4&2~N5WHA9Y)sm}Woa-l^5SEcrWCW8p+(ID zmIQvG>#c$91q=ng?>Rr8cAN;6lz%3I89Ert!>F4nWC3)HE`nm;Nv>wE#!)}vp!6|@ z)9yh_4OFvXeIN_c5|(jP#*?IhksWE`^xjM;W^>#&LNQU%!9 zsWzdpO};-Cs0&I}zpCPM62^wv`}X~yCxx7jDW`_3&^2q9meA#0l?)Wl-haePwrFt; zX9zGV*Z7*NS}oQaIcS_#oj+%yoV{SogIzWyS4!12ZyTb&P%hMD2Ywa?CpJg9(mcSz zVK5-X!$Y!S;x)DFO2SuRT$_5%nVO33)&RLXW$3Z#X}JGhieT*o5AoW6dW>OaKjN=h zRxJz@|DoACM%9Vn=vutxmw!Q?^Jmw~%@BegL}zmOVP?boz`V}z%+_AAq50hQ}T$}^7cMzpvYFmukZF2IxDeG z?Qxeu=-K9aIGr3S3Um2^9_+$Ir+pZYq#vqsKWUIBqg59D(mlMQAh*soI4F_C ziF|K-kvd%h_pzXRS>muWfdH?I7U(NcM|PGU*%{}UHXs|27=Knsz);==sqTZqgzSsS z1~s+~;?QhV&W*-JG#>byc@Wlb%OA6gqhl~JuW`>U4@?dF2QfpXE;_?H%JL8k3^BY% zo6et39;5vd8`w~lUJhq}^>|EzhCbE@@R5@KLs3D?*O>avpfj)qTz5ltloFg)agp5d zXpTNBS%F%nM}Nl{IJbZrWZ7_@#%&6jJI}(;o)6>;XYL7wJMnT519VkQfnBw&5Y_3V zLQv>t^9!ERU~ z=w~({hOJlciFiJ-_9`ATaG_tFqs5CKKyEJDkH(#n!V7}ulBSTIQT~|2_r$*;W_pPh zJ%0?3{nxpwhkvHb(_cepowy%D0$hRi9JcCy)SECYukKKGpAgW z7HZM1zF*k+7foE{w~3Ojuo|pz+L?5G<}O>QVSgs~e%(rF)h7sSN*-mLby*#*04VZ^ zmrm1%Y{V6DGTZ*`vl-}4D$>)kTWMcxP^FbTb4p+^sPDn}H>7ZqD!QQUlb0nZh%Oh*{-C-7<Yk)qx^R<&y4yEH!5APi_Wlp#5W?c3mA-- zd%oR?B5{%=u^+h`ccstbpL4$EB1gBf0e^K?w=NfTl&NIN)#8LU->Wx;YED*Niwr>i zG-GOQEt2EjX0Q_WnC>J!ft!v8z1XKQj!JRjZk92P$^YdftFg3yE49GxKlRF<5@0;F z-Sb;WAWUv~>zrZmAl4`pl>l3hL#@3}caxF5((*av6}hBohB}&R+$qgKI)sR;SbvDn zYU}je)tDX=?Tfo1_E;NL!4)J2FcezVN$nQF0}bymsXMy8B`mrM-NdQtb>z1xYU6v^}E>UnOH4OQ6>%W zHesNXJgqoHZBt@WZeq>1mo+K)a(~bakMyVAsEV0vL0=*xpi!X~2YTB&LNQUni|3O&&wVTKO-BGj|7HC|O`0}M?x?vt?G4!R7MdqLc-K?D;lgseJ zjmLCQfIuBsX3*Mr{K2H5QGceR>jx7mWF7B)7RTUxnpSZAF#%I$erIp9jERkUTB<~3ijuSuXGbZO zuH?gGwSN>kqkjc)^PcVpsAn_F z`posbo>uxWH4b7g2Sh#==JtrlpsBh z$~iL@7Ybn>i$_|eCC-BF@&4i(me`K@nZ`fn1pTg9@l%?mUOc!jFZ4y%c?Vhh3 zxH6?^h4hecLCuAC(ZHJi0{-4D&@arbQrh9BnI>yf*bb##7IEjiqkFydalSRbOjVYs z5p_a@ZL<%VqJLWdry_eG)Aw@W{ls3CBzMFIlQr?%4{RIb)_)Ioa9dxkL52DOF=`;r zaR#_Dzl?KVmPgDujNym=Me60C57jD3A+Nk0wQ0&HFE2i@COhBvgiCY^1=F37SniQ3 z$2nWAh`At*KmiL6T?N1CJ~IAI)o}A05hU?+PT_@0gynYyGuqALFZG2igkS@bIqvx- zVWwp3mXPFQ4uA7BcI`gG_2*}3+}vQ_b&zSO(e8sMt!Omma5`I@(~wH3NBx>MJB708 z4kyXi6Fq~;0MJs;h(;m(dbHV;-h>Ia^TuURFgq|GFb4(&D-Ht!8Uz#*!gz8uX6drX pWwf{x9i#5-f1w5h6q?2#GYA>wZYxq+>jCpT*jo_rCIkWk2hd%DG~oaM From 6fccd7de9676255c7de437a12f04e470680d8c2b Mon Sep 17 00:00:00 2001 From: "Paul Campbell (DEP)" Date: Mon, 4 Nov 2019 13:12:06 -0800 Subject: [PATCH 16/19] [UWP] Move accessibility changes from ActionHelpers to XamlBuilder --- .../uwp/Renderer/AdaptiveCardRenderer.vcxproj | 3 +- .../AdaptiveCardRenderer.vcxproj.filters | 1 - source/uwp/Renderer/lib/ActionHelpers.cpp | 1034 ----------------- source/uwp/Renderer/lib/XamlBuilder.cpp | 30 +- 4 files changed, 26 insertions(+), 1042 deletions(-) delete mode 100644 source/uwp/Renderer/lib/ActionHelpers.cpp diff --git a/source/uwp/Renderer/AdaptiveCardRenderer.vcxproj b/source/uwp/Renderer/AdaptiveCardRenderer.vcxproj index 1f59752059..29c5c531fc 100644 --- a/source/uwp/Renderer/AdaptiveCardRenderer.vcxproj +++ b/source/uwp/Renderer/AdaptiveCardRenderer.vcxproj @@ -133,7 +133,6 @@ - @@ -375,4 +374,4 @@
- + \ No newline at end of file diff --git a/source/uwp/Renderer/AdaptiveCardRenderer.vcxproj.filters b/source/uwp/Renderer/AdaptiveCardRenderer.vcxproj.filters index 5f04d3b759..acea9703b0 100644 --- a/source/uwp/Renderer/AdaptiveCardRenderer.vcxproj.filters +++ b/source/uwp/Renderer/AdaptiveCardRenderer.vcxproj.filters @@ -230,7 +230,6 @@ - diff --git a/source/uwp/Renderer/lib/ActionHelpers.cpp b/source/uwp/Renderer/lib/ActionHelpers.cpp deleted file mode 100644 index 3e473d6cc5..0000000000 --- a/source/uwp/Renderer/lib/ActionHelpers.cpp +++ /dev/null @@ -1,1034 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -#include "pch.h" - -#include "ActionHelpers.h" -#include "AdaptiveImage.h" -#include "AdaptiveRenderArgs.h" -#include "AdaptiveShowCardActionRenderer.h" -#include "LinkButton.h" - -using namespace Microsoft::WRL; -using namespace Microsoft::WRL::Wrappers; -using namespace ABI::AdaptiveNamespace; -using namespace ABI::Windows::Foundation; -using namespace ABI::Windows::Foundation::Collections; -using namespace ABI::Windows::UI::Xaml; -using namespace ABI::Windows::UI::Xaml::Automation; -using namespace ABI::Windows::UI::Xaml::Controls; -using namespace ABI::Windows::UI::Xaml::Controls::Primitives; -using namespace ABI::Windows::UI::Xaml::Input; -using namespace ABI::Windows::UI::Xaml::Media; - -namespace AdaptiveNamespace::ActionHelpers -{ - HRESULT GetButtonMargin(_In_ IAdaptiveActionsConfig* actionsConfig, Thickness& buttonMargin) noexcept - { - buttonMargin = {0, 0, 0, 0}; - UINT32 buttonSpacing; - RETURN_IF_FAILED(actionsConfig->get_ButtonSpacing(&buttonSpacing)); - - ABI::AdaptiveNamespace::ActionsOrientation actionsOrientation; - RETURN_IF_FAILED(actionsConfig->get_ActionsOrientation(&actionsOrientation)); - - if (actionsOrientation == ABI::AdaptiveNamespace::ActionsOrientation::Horizontal) - { - buttonMargin.Left = buttonMargin.Right = buttonSpacing / 2; - } - else - { - buttonMargin.Top = buttonMargin.Bottom = buttonSpacing / 2; - } - - return S_OK; - } - - void ArrangeButtonContent(_In_ IAdaptiveActionElement* action, - _In_ IAdaptiveActionsConfig* actionsConfig, - _In_ IAdaptiveRenderContext* renderContext, - ABI::AdaptiveNamespace::ContainerStyle containerStyle, - _In_ ABI::AdaptiveNamespace::IAdaptiveHostConfig* hostConfig, - bool allActionsHaveIcons, - _In_ IButton* button) - { - HString title; - THROW_IF_FAILED(action->get_Title(title.GetAddressOf())); - - HString iconUrl; - THROW_IF_FAILED(action->get_IconUrl(iconUrl.GetAddressOf())); - - ComPtr localButton(button); - ComPtr automationProperties; - THROW_IF_FAILED( - GetActivationFactory(HStringReference(RuntimeClass_Windows_UI_Xaml_Automation_AutomationProperties).Get(), - &automationProperties)); - ComPtr buttonAsDependencyObject; - THROW_IF_FAILED(localButton.As(&buttonAsDependencyObject)); - THROW_IF_FAILED(automationProperties->SetName(buttonAsDependencyObject.Get(), title.Get())); - - // Check if the button has an iconUrl - if (iconUrl != nullptr) - { - // Get icon configs - ABI::AdaptiveNamespace::IconPlacement iconPlacement; - UINT32 iconSize; - - THROW_IF_FAILED(actionsConfig->get_IconPlacement(&iconPlacement)); - THROW_IF_FAILED(actionsConfig->get_IconSize(&iconSize)); - - // Define the alignment for the button contents - ComPtr buttonContentsStackPanel = - XamlHelpers::CreateXamlClass(HStringReference(RuntimeClass_Windows_UI_Xaml_Controls_StackPanel)); - - // Create image and add it to the button - ComPtr adaptiveImage; - THROW_IF_FAILED(MakeAndInitialize(&adaptiveImage)); - - THROW_IF_FAILED(adaptiveImage->put_Url(iconUrl.Get())); - THROW_IF_FAILED(adaptiveImage->put_HorizontalAlignment(ABI::AdaptiveNamespace::HAlignment::Center)); - - ComPtr adaptiveCardElement; - THROW_IF_FAILED(adaptiveImage.As(&adaptiveCardElement)); - ComPtr childRenderArgs; - THROW_IF_FAILED( - MakeAndInitialize(&childRenderArgs, containerStyle, buttonContentsStackPanel.Get(), nullptr)); - - ComPtr elementRenderers; - THROW_IF_FAILED(renderContext->get_ElementRenderers(&elementRenderers)); - - ComPtr buttonIcon; - ComPtr elementRenderer; - THROW_IF_FAILED(elementRenderers->Get(HStringReference(L"Image").Get(), &elementRenderer)); - if (elementRenderer != nullptr) - { - elementRenderer->Render(adaptiveCardElement.Get(), renderContext, childRenderArgs.Get(), &buttonIcon); - if (buttonIcon == nullptr) - { - XamlHelpers::SetContent(localButton.Get(), title.Get()); - return; - } - } - - // Create title text block - ComPtr buttonText = - XamlHelpers::CreateXamlClass(HStringReference(RuntimeClass_Windows_UI_Xaml_Controls_TextBlock)); - THROW_IF_FAILED(buttonText->put_Text(title.Get())); - THROW_IF_FAILED(buttonText->put_TextAlignment(TextAlignment::TextAlignment_Center)); - - // Handle different arrangements inside button - ComPtr buttonIconAsFrameworkElement; - THROW_IF_FAILED(buttonIcon.As(&buttonIconAsFrameworkElement)); - ComPtr separator; - if (iconPlacement == ABI::AdaptiveNamespace::IconPlacement::AboveTitle && allActionsHaveIcons) - { - THROW_IF_FAILED(buttonContentsStackPanel->put_Orientation(Orientation::Orientation_Vertical)); - - // Set icon height to iconSize (aspect ratio is automatically maintained) - THROW_IF_FAILED(buttonIconAsFrameworkElement->put_Height(iconSize)); - } - else - { - THROW_IF_FAILED(buttonContentsStackPanel->put_Orientation(Orientation::Orientation_Horizontal)); - - // Add event to the image to resize itself when the textblock is rendered - ComPtr buttonIconAsImage; - THROW_IF_FAILED(buttonIcon.As(&buttonIconAsImage)); - - EventRegistrationToken eventToken; - THROW_IF_FAILED(buttonIconAsImage->add_ImageOpened( - Callback([buttonIconAsFrameworkElement, - buttonText](IInspectable* /*sender*/, IRoutedEventArgs * /*args*/) -> HRESULT { - ComPtr buttonTextAsFrameworkElement; - RETURN_IF_FAILED(buttonText.As(&buttonTextAsFrameworkElement)); - - return SetMatchingHeight(buttonIconAsFrameworkElement.Get(), buttonTextAsFrameworkElement.Get()); - }).Get(), - &eventToken)); - - // Only add spacing when the icon must be located at the left of the title - UINT spacingSize; - THROW_IF_FAILED(GetSpacingSizeFromSpacing(hostConfig, ABI::AdaptiveNamespace::Spacing::Default, &spacingSize)); - - ABI::Windows::UI::Color color = {0}; - separator = XamlHelpers::CreateSeparator(renderContext, spacingSize, spacingSize, color, false); - } - - ComPtr buttonContentsPanel; - THROW_IF_FAILED(buttonContentsStackPanel.As(&buttonContentsPanel)); - - // Add image to stack panel - XamlHelpers::AppendXamlElementToPanel(buttonIcon.Get(), buttonContentsPanel.Get()); - - // Add separator to stack panel - if (separator != nullptr) - { - XamlHelpers::AppendXamlElementToPanel(separator.Get(), buttonContentsPanel.Get()); - } - - // Add text to stack panel - XamlHelpers::AppendXamlElementToPanel(buttonText.Get(), buttonContentsPanel.Get()); - - // Finally, put the stack panel inside the final button - ComPtr buttonContentControl; - THROW_IF_FAILED(localButton.As(&buttonContentControl)); - THROW_IF_FAILED(buttonContentControl->put_Content(buttonContentsPanel.Get())); - } - else - { - XamlHelpers::SetContent(localButton.Get(), title.Get()); - } - } - - HRESULT HandleActionStyling(_In_ IAdaptiveActionElement* adaptiveActionElement, - _In_ IFrameworkElement* buttonFrameworkElement, - _In_ IAdaptiveRenderContext* renderContext) - { - HString actionSentiment; - RETURN_IF_FAILED(adaptiveActionElement->get_Style(actionSentiment.GetAddressOf())); - - INT32 isSentimentPositive{}, isSentimentDestructive{}, isSentimentDefault{}; - - ComPtr resourceDictionary; - RETURN_IF_FAILED(renderContext->get_OverrideStyles(&resourceDictionary)); - ComPtr styleToApply; - - ComPtr contextImpl = - PeekInnards(renderContext); - - if ((SUCCEEDED(WindowsCompareStringOrdinal(actionSentiment.Get(), HStringReference(L"default").Get(), &isSentimentDefault)) && - (isSentimentDefault == 0)) || - WindowsIsStringEmpty(actionSentiment.Get())) - { - RETURN_IF_FAILED(XamlHelpers::SetStyleFromResourceDictionary(renderContext, L"Adaptive.Action", buttonFrameworkElement)); - } - else if (SUCCEEDED(WindowsCompareStringOrdinal(actionSentiment.Get(), HStringReference(L"positive").Get(), &isSentimentPositive)) && - (isSentimentPositive == 0)) - { - if (SUCCEEDED(XamlHelpers::TryGetResourceFromResourceDictionaries(resourceDictionary.Get(), - L"Adaptive.Action.Positive", - &styleToApply))) - { - RETURN_IF_FAILED(buttonFrameworkElement->put_Style(styleToApply.Get())); - } - else - { - // By default, set the action background color to accent color - ComPtr actionSentimentDictionary = contextImpl->GetDefaultActionSentimentDictionary(); - - if (SUCCEEDED(XamlHelpers::TryGetResourceFromResourceDictionaries(actionSentimentDictionary.Get(), - L"PositiveActionDefaultStyle", - styleToApply.GetAddressOf()))) - { - RETURN_IF_FAILED(buttonFrameworkElement->put_Style(styleToApply.Get())); - } - } - } - else if (SUCCEEDED(WindowsCompareStringOrdinal(actionSentiment.Get(), HStringReference(L"destructive").Get(), &isSentimentDestructive)) && - (isSentimentDestructive == 0)) - { - if (SUCCEEDED(XamlHelpers::TryGetResourceFromResourceDictionaries(resourceDictionary.Get(), - L"Adaptive.Action.Destructive", - &styleToApply))) - { - RETURN_IF_FAILED(buttonFrameworkElement->put_Style(styleToApply.Get())); - } - else - { - // By default, set the action text color to attention color - ComPtr actionSentimentDictionary = contextImpl->GetDefaultActionSentimentDictionary(); - - if (SUCCEEDED(XamlHelpers::TryGetResourceFromResourceDictionaries(actionSentimentDictionary.Get(), - L"DestructiveActionDefaultStyle", - styleToApply.GetAddressOf()))) - { - RETURN_IF_FAILED(buttonFrameworkElement->put_Style(styleToApply.Get())); - } - } - } - else - { - HString actionSentimentStyle; - RETURN_IF_FAILED(WindowsConcatString(HStringReference(L"Adaptive.Action.").Get(), - actionSentiment.Get(), - actionSentimentStyle.GetAddressOf())); - RETURN_IF_FAILED(XamlHelpers::SetStyleFromResourceDictionary( - renderContext, StringToWstring(HStringToUTF8(actionSentimentStyle.Get())), buttonFrameworkElement)); - } - return S_OK; - } - - HRESULT SetMatchingHeight(_In_ IFrameworkElement* elementToChange, _In_ IFrameworkElement* elementToMatch) - { - DOUBLE actualHeight; - RETURN_IF_FAILED(elementToMatch->get_ActualHeight(&actualHeight)); - - ComPtr localElement(elementToChange); - RETURN_IF_FAILED(localElement->put_Height(actualHeight)); - - ComPtr frameworkElementAsUIElement; - RETURN_IF_FAILED(localElement.As(&frameworkElementAsUIElement)); - RETURN_IF_FAILED(frameworkElementAsUIElement->put_Visibility(Visibility::Visibility_Visible)); - return S_OK; - } - - HRESULT BuildAction(_In_ IAdaptiveActionElement* adaptiveActionElement, - _In_ IAdaptiveRenderContext* renderContext, - _In_ IAdaptiveRenderArgs* renderArgs, - _Outptr_ IUIElement** actionControl) - { - // determine what type of action we're building - ComPtr action(adaptiveActionElement); - ABI::AdaptiveNamespace::ActionType actionType; - RETURN_IF_FAILED(action->get_ActionType(&actionType)); - - // now construct an appropriate button for the action type - ComPtr button; - if (actionType == ABI::AdaptiveNamespace::ActionType_OpenUrl) - { - // OpenUrl buttons should appear as links for accessibility purposes, so we use our custom LinkButton. - auto linkButton = winrt::make(); - button = linkButton.as().detach(); - } - - if (!button) - { - // Either non-OpenUrl action or instantiating LinkButton failed. Use standard button. - button = XamlHelpers::CreateXamlClass(HStringReference(RuntimeClass_Windows_UI_Xaml_Controls_Button)); - } - - ComPtr buttonFrameworkElement; - RETURN_IF_FAILED(button.As(&buttonFrameworkElement)); - - ComPtr hostConfig; - RETURN_IF_FAILED(renderContext->get_HostConfig(&hostConfig)); - ComPtr actionsConfig; - RETURN_IF_FAILED(hostConfig->get_Actions(actionsConfig.GetAddressOf())); - - Thickness buttonMargin; - RETURN_IF_FAILED(GetButtonMargin(actionsConfig.Get(), buttonMargin)); - RETURN_IF_FAILED(buttonFrameworkElement->put_Margin(buttonMargin)); - - ABI::AdaptiveNamespace::ActionsOrientation actionsOrientation; - RETURN_IF_FAILED(actionsConfig->get_ActionsOrientation(&actionsOrientation)); - - ABI::AdaptiveNamespace::ActionAlignment actionAlignment; - RETURN_IF_FAILED(actionsConfig->get_ActionAlignment(&actionAlignment)); - - if (actionsOrientation == ABI::AdaptiveNamespace::ActionsOrientation::Horizontal) - { - // For horizontal alignment, we always use stretch - RETURN_IF_FAILED(buttonFrameworkElement->put_HorizontalAlignment( - ABI::Windows::UI::Xaml::HorizontalAlignment::HorizontalAlignment_Stretch)); - } - else - { - switch (actionAlignment) - { - case ABI::AdaptiveNamespace::ActionAlignment::Center: - RETURN_IF_FAILED(buttonFrameworkElement->put_HorizontalAlignment(ABI::Windows::UI::Xaml::HorizontalAlignment_Center)); - break; - case ABI::AdaptiveNamespace::ActionAlignment::Left: - RETURN_IF_FAILED(buttonFrameworkElement->put_HorizontalAlignment(ABI::Windows::UI::Xaml::HorizontalAlignment_Left)); - break; - case ABI::AdaptiveNamespace::ActionAlignment::Right: - RETURN_IF_FAILED(buttonFrameworkElement->put_HorizontalAlignment(ABI::Windows::UI::Xaml::HorizontalAlignment_Right)); - break; - case ABI::AdaptiveNamespace::ActionAlignment::Stretch: - RETURN_IF_FAILED(buttonFrameworkElement->put_HorizontalAlignment(ABI::Windows::UI::Xaml::HorizontalAlignment_Stretch)); - break; - } - } - - ABI::AdaptiveNamespace::ContainerStyle containerStyle; - RETURN_IF_FAILED(renderArgs->get_ContainerStyle(&containerStyle)); - - boolean allowAboveTitleIconPlacement; - RETURN_IF_FAILED(renderArgs->get_AllowAboveTitleIconPlacement(&allowAboveTitleIconPlacement)); - - ArrangeButtonContent(action.Get(), - actionsConfig.Get(), - renderContext, - containerStyle, - hostConfig.Get(), - allowAboveTitleIconPlacement, - button.Get()); - - ComPtr showCardActionConfig; - RETURN_IF_FAILED(actionsConfig->get_ShowCard(&showCardActionConfig)); - ABI::AdaptiveNamespace::ActionMode showCardActionMode; - RETURN_IF_FAILED(showCardActionConfig->get_ActionMode(&showCardActionMode)); - std::shared_ptr>> allShowCards = std::make_shared>>(); - - // Add click handler which calls IAdaptiveActionInvoker::SendActionEvent - ComPtr buttonBase; - RETURN_IF_FAILED(button.As(&buttonBase)); - - ComPtr actionInvoker; - RETURN_IF_FAILED(renderContext->get_ActionInvoker(&actionInvoker)); - EventRegistrationToken clickToken; - RETURN_IF_FAILED(buttonBase->add_Click(Callback([action, actionInvoker](IInspectable* /*sender*/, IRoutedEventArgs * - /*args*/) -> HRESULT { - return actionInvoker->SendActionEvent(action.Get()); - }).Get(), - &clickToken)); - - RETURN_IF_FAILED(HandleActionStyling(adaptiveActionElement, buttonFrameworkElement.Get(), renderContext)); - - ComPtr buttonAsUIElement; - RETURN_IF_FAILED(button.As(&buttonAsUIElement)); - *actionControl = buttonAsUIElement.Detach(); - return S_OK; - } - - bool WarnForInlineShowCard(_In_ IAdaptiveRenderContext* renderContext, _In_ IAdaptiveActionElement* action, const std::wstring& warning) - { - if (action != nullptr) - { - ABI::AdaptiveNamespace::ActionType actionType; - THROW_IF_FAILED(action->get_ActionType(&actionType)); - - if (actionType == ABI::AdaptiveNamespace::ActionType::ShowCard) - { - THROW_IF_FAILED(renderContext->AddWarning(ABI::AdaptiveNamespace::WarningStatusCode::UnsupportedValue, - HStringReference(warning.c_str()).Get())); - return true; - } - } - - return false; - } - - static HRESULT HandleKeydownForInlineAction(_In_ IKeyRoutedEventArgs* args, - _In_ IAdaptiveActionInvoker* actionInvoker, - _In_ IAdaptiveActionElement* inlineAction) - { - ABI::Windows::System::VirtualKey key; - RETURN_IF_FAILED(args->get_Key(&key)); - - if (key == ABI::Windows::System::VirtualKey::VirtualKey_Enter) - { - ComPtr coreWindowStatics; - RETURN_IF_FAILED(GetActivationFactory(HStringReference(RuntimeClass_Windows_UI_Core_CoreWindow).Get(), &coreWindowStatics)); - - ComPtr coreWindow; - RETURN_IF_FAILED(coreWindowStatics->GetForCurrentThread(&coreWindow)); - - ABI::Windows::UI::Core::CoreVirtualKeyStates shiftKeyState; - RETURN_IF_FAILED(coreWindow->GetKeyState(ABI::Windows::System::VirtualKey_Shift, &shiftKeyState)); - - ABI::Windows::UI::Core::CoreVirtualKeyStates ctrlKeyState; - RETURN_IF_FAILED(coreWindow->GetKeyState(ABI::Windows::System::VirtualKey_Control, &ctrlKeyState)); - - if (shiftKeyState == ABI::Windows::UI::Core::CoreVirtualKeyStates_None && - ctrlKeyState == ABI::Windows::UI::Core::CoreVirtualKeyStates_None) - { - RETURN_IF_FAILED(actionInvoker->SendActionEvent(inlineAction)); - RETURN_IF_FAILED(args->put_Handled(true)); - } - } - - return S_OK; - } - - void HandleInlineAction(_In_ IAdaptiveRenderContext* renderContext, - _In_ IAdaptiveRenderArgs* renderArgs, - _In_ ITextBox* textBox, - _In_ IAdaptiveActionElement* inlineAction, - _COM_Outptr_ IUIElement** textBoxWithInlineAction) - { - ComPtr localTextBox(textBox); - ComPtr localInlineAction(inlineAction); - - ABI::AdaptiveNamespace::ActionType actionType; - THROW_IF_FAILED(localInlineAction->get_ActionType(&actionType)); - - ComPtr hostConfig; - THROW_IF_FAILED(renderContext->get_HostConfig(&hostConfig)); - - // Inline ShowCards are not supported for inline actions - if (WarnForInlineShowCard(renderContext, localInlineAction.Get(), L"Inline ShowCard not supported for InlineAction")) - { - THROW_IF_FAILED(localTextBox.CopyTo(textBoxWithInlineAction)); - return; - } - - // Create a grid to hold the text box and the action button - ComPtr gridStatics; - THROW_IF_FAILED(GetActivationFactory(HStringReference(RuntimeClass_Windows_UI_Xaml_Controls_Grid).Get(), &gridStatics)); - - ComPtr xamlGrid = - XamlHelpers::CreateXamlClass(HStringReference(RuntimeClass_Windows_UI_Xaml_Controls_Grid)); - ComPtr> columnDefinitions; - THROW_IF_FAILED(xamlGrid->get_ColumnDefinitions(&columnDefinitions)); - ComPtr gridAsPanel; - THROW_IF_FAILED(xamlGrid.As(&gridAsPanel)); - - // Create the first column and add the text box to it - ComPtr textBoxColumnDefinition = XamlHelpers::CreateXamlClass( - HStringReference(RuntimeClass_Windows_UI_Xaml_Controls_ColumnDefinition)); - THROW_IF_FAILED(textBoxColumnDefinition->put_Width({1, GridUnitType::GridUnitType_Star})); - THROW_IF_FAILED(columnDefinitions->Append(textBoxColumnDefinition.Get())); - - ComPtr textBoxAsFrameworkElement; - THROW_IF_FAILED(localTextBox.As(&textBoxAsFrameworkElement)); - - THROW_IF_FAILED(gridStatics->SetColumn(textBoxAsFrameworkElement.Get(), 0)); - XamlHelpers::AppendXamlElementToPanel(textBox, gridAsPanel.Get()); - - // Create a separator column - ComPtr separatorColumnDefinition = XamlHelpers::CreateXamlClass( - HStringReference(RuntimeClass_Windows_UI_Xaml_Controls_ColumnDefinition)); - THROW_IF_FAILED(separatorColumnDefinition->put_Width({1.0, GridUnitType::GridUnitType_Auto})); - THROW_IF_FAILED(columnDefinitions->Append(separatorColumnDefinition.Get())); - - UINT spacingSize; - THROW_IF_FAILED(GetSpacingSizeFromSpacing(hostConfig.Get(), ABI::AdaptiveNamespace::Spacing::Default, &spacingSize)); - - auto separator = XamlHelpers::CreateSeparator(renderContext, spacingSize, 0, {0}, false); - - ComPtr separatorAsFrameworkElement; - THROW_IF_FAILED(separator.As(&separatorAsFrameworkElement)); - - THROW_IF_FAILED(gridStatics->SetColumn(separatorAsFrameworkElement.Get(), 1)); - XamlHelpers::AppendXamlElementToPanel(separator.Get(), gridAsPanel.Get()); - - // Create a column for the button - ComPtr inlineActionColumnDefinition = XamlHelpers::CreateXamlClass( - HStringReference(RuntimeClass_Windows_UI_Xaml_Controls_ColumnDefinition)); - THROW_IF_FAILED(inlineActionColumnDefinition->put_Width({0, GridUnitType::GridUnitType_Auto})); - THROW_IF_FAILED(columnDefinitions->Append(inlineActionColumnDefinition.Get())); - - // Create a text box with the action title. This will be the tool tip if there's an icon - // or the content of the button otherwise - ComPtr titleTextBlock = - XamlHelpers::CreateXamlClass(HStringReference(RuntimeClass_Windows_UI_Xaml_Controls_TextBlock)); - HString title; - THROW_IF_FAILED(localInlineAction->get_Title(title.GetAddressOf())); - THROW_IF_FAILED(titleTextBlock->put_Text(title.Get())); - - HString iconUrl; - THROW_IF_FAILED(localInlineAction->get_IconUrl(iconUrl.GetAddressOf())); - ComPtr actionUIElement; - if (iconUrl != nullptr) - { - // Render the icon using the adaptive image renderer - ComPtr elementRenderers; - THROW_IF_FAILED(renderContext->get_ElementRenderers(&elementRenderers)); - ComPtr imageRenderer; - THROW_IF_FAILED(elementRenderers->Get(HStringReference(L"Image").Get(), &imageRenderer)); - - ComPtr adaptiveImage; - THROW_IF_FAILED(MakeAndInitialize(&adaptiveImage)); - - THROW_IF_FAILED(adaptiveImage->put_Url(iconUrl.Get())); - - ComPtr adaptiveImageAsElement; - THROW_IF_FAILED(adaptiveImage.As(&adaptiveImageAsElement)); - - THROW_IF_FAILED(imageRenderer->Render(adaptiveImageAsElement.Get(), renderContext, renderArgs, &actionUIElement)); - - // Add the tool tip - ComPtr toolTip = - XamlHelpers::CreateXamlClass(HStringReference(RuntimeClass_Windows_UI_Xaml_Controls_ToolTip)); - ComPtr toolTipAsContentControl; - THROW_IF_FAILED(toolTip.As(&toolTipAsContentControl)); - THROW_IF_FAILED(toolTipAsContentControl->put_Content(titleTextBlock.Get())); - - ComPtr toolTipService; - THROW_IF_FAILED(GetActivationFactory(HStringReference(RuntimeClass_Windows_UI_Xaml_Controls_ToolTipService).Get(), - &toolTipService)); - - ComPtr actionAsDependencyObject; - THROW_IF_FAILED(actionUIElement.As(&actionAsDependencyObject)); - - THROW_IF_FAILED(toolTipService->SetToolTip(actionAsDependencyObject.Get(), toolTip.Get())); - } - else - { - // If there's no icon, just use the title text. Put it centered in a grid so it is - // centered relative to the text box. - ComPtr textBlockAsFrameworkElement; - THROW_IF_FAILED(titleTextBlock.As(&textBlockAsFrameworkElement)); - THROW_IF_FAILED(textBlockAsFrameworkElement->put_VerticalAlignment(ABI::Windows::UI::Xaml::VerticalAlignment_Center)); - - ComPtr titleGrid = - XamlHelpers::CreateXamlClass(HStringReference(RuntimeClass_Windows_UI_Xaml_Controls_Grid)); - ComPtr panel; - THROW_IF_FAILED(titleGrid.As(&panel)); - XamlHelpers::AppendXamlElementToPanel(titleTextBlock.Get(), panel.Get()); - - THROW_IF_FAILED(panel.As(&actionUIElement)); - } - - // Make the action the same size as the text box - EventRegistrationToken eventToken; - THROW_IF_FAILED(textBoxAsFrameworkElement->add_Loaded( - Callback([actionUIElement, textBoxAsFrameworkElement](IInspectable* /*sender*/, IRoutedEventArgs * - /*args*/) -> HRESULT { - ComPtr actionFrameworkElement; - RETURN_IF_FAILED(actionUIElement.As(&actionFrameworkElement)); - - return ActionHelpers::SetMatchingHeight(actionFrameworkElement.Get(), textBoxAsFrameworkElement.Get()); - }).Get(), - &eventToken)); - - // Wrap the action in a button - ComPtr touchTargetUIElement; - WrapInTouchTarget(nullptr, actionUIElement.Get(), localInlineAction.Get(), renderContext, false, L"Adaptive.Input.Text.InlineAction", &touchTargetUIElement); - - ComPtr touchTargetFrameworkElement; - THROW_IF_FAILED(touchTargetUIElement.As(&touchTargetFrameworkElement)); - - // Align to bottom so the icon stays with the bottom of the text box as it grows in the multiline case - THROW_IF_FAILED(touchTargetFrameworkElement->put_VerticalAlignment(ABI::Windows::UI::Xaml::VerticalAlignment_Bottom)); - - // Add the action to the column - THROW_IF_FAILED(gridStatics->SetColumn(touchTargetFrameworkElement.Get(), 2)); - XamlHelpers::AppendXamlElementToPanel(touchTargetFrameworkElement.Get(), gridAsPanel.Get()); - - // If this isn't a multiline input, enter should invoke the action - ComPtr actionInvoker; - THROW_IF_FAILED(renderContext->get_ActionInvoker(&actionInvoker)); - - boolean isMultiLine; - THROW_IF_FAILED(textBox->get_AcceptsReturn(&isMultiLine)); - - if (!isMultiLine) - { - ComPtr textBoxAsUIElement; - THROW_IF_FAILED(localTextBox.As(&textBoxAsUIElement)); - - EventRegistrationToken keyDownEventToken; - THROW_IF_FAILED(textBoxAsUIElement->add_KeyDown( - Callback([actionInvoker, localInlineAction](IInspectable* /*sender*/, IKeyRoutedEventArgs* args) -> HRESULT { - return HandleKeydownForInlineAction(args, actionInvoker.Get(), localInlineAction.Get()); - }).Get(), - &keyDownEventToken)); - } - - THROW_IF_FAILED(xamlGrid.CopyTo(textBoxWithInlineAction)); - } - - void WrapInTouchTarget(_In_ IAdaptiveCardElement* adaptiveCardElement, - _In_ IUIElement* elementToWrap, - _In_ IAdaptiveActionElement* action, - _In_ IAdaptiveRenderContext* renderContext, - bool fullWidth, - const std::wstring& style, - _COM_Outptr_ IUIElement** finalElement) - { - ComPtr hostConfig; - THROW_IF_FAILED(renderContext->get_HostConfig(&hostConfig)); - - if (ActionHelpers::WarnForInlineShowCard(renderContext, action, L"Inline ShowCard not supported for SelectAction")) - { - // Was inline show card, so don't wrap the element and just return - ComPtr localElementToWrap(elementToWrap); - localElementToWrap.CopyTo(finalElement); - return; - } - - ComPtr button = - XamlHelpers::CreateXamlClass(HStringReference(RuntimeClass_Windows_UI_Xaml_Controls_Button)); - - ComPtr buttonAsContentControl; - THROW_IF_FAILED(button.As(&buttonAsContentControl)); - THROW_IF_FAILED(buttonAsContentControl->put_Content(elementToWrap)); - - ComPtr spacingConfig; - THROW_IF_FAILED(hostConfig->get_Spacing(&spacingConfig)); - - UINT32 cardPadding = 0; - if (fullWidth) - { - THROW_IF_FAILED(spacingConfig->get_Padding(&cardPadding)); - } - - ComPtr buttonAsFrameworkElement; - THROW_IF_FAILED(button.As(&buttonAsFrameworkElement)); - - // We want the hit target to equally split the vertical space above and below the current item. - // However, all we know is the spacing of the current item, which only applies to the spacing above. - // We don't know what the spacing of the NEXT element will be, so we can't calculate the correct spacing - // below. For now, we'll simply assume the bottom spacing is the same as the top. NOTE: Only apply spacings - // (padding, margin) for adaptive card elements to avoid adding spacings to card-level selectAction. - if (adaptiveCardElement != nullptr) - { - ABI::AdaptiveNamespace::Spacing elementSpacing; - THROW_IF_FAILED(adaptiveCardElement->get_Spacing(&elementSpacing)); - UINT spacingSize; - THROW_IF_FAILED(GetSpacingSizeFromSpacing(hostConfig.Get(), elementSpacing, &spacingSize)); - double topBottomPadding = spacingSize / 2.0; - - // For button padding, we apply the cardPadding and topBottomPadding (and then we negate these in the margin) - ComPtr buttonAsControl; - THROW_IF_FAILED(button.As(&buttonAsControl)); - THROW_IF_FAILED(buttonAsControl->put_Padding({(double)cardPadding, topBottomPadding, (double)cardPadding, topBottomPadding})); - - double negativeCardMargin = cardPadding * -1.0; - double negativeTopBottomMargin = topBottomPadding * -1.0; - - THROW_IF_FAILED(buttonAsFrameworkElement->put_Margin( - {negativeCardMargin, negativeTopBottomMargin, negativeCardMargin, negativeTopBottomMargin})); - } - - // Style the hit target button - THROW_IF_FAILED( - XamlHelpers::SetStyleFromResourceDictionary(renderContext, style.c_str(), buttonAsFrameworkElement.Get())); - - if (action != nullptr) - { - // If we have an action, use the title for the AutomationProperties.Name - HString title; - THROW_IF_FAILED(action->get_Title(title.GetAddressOf())); - - ComPtr buttonAsDependencyObject; - THROW_IF_FAILED(button.As(&buttonAsDependencyObject)); - - ComPtr automationPropertiesStatics; - THROW_IF_FAILED( - GetActivationFactory(HStringReference(RuntimeClass_Windows_UI_Xaml_Automation_AutomationProperties).Get(), - &automationPropertiesStatics)); - - THROW_IF_FAILED(automationPropertiesStatics->SetName(buttonAsDependencyObject.Get(), title.Get())); - - WireButtonClickToAction(button.Get(), action, renderContext); - } - - THROW_IF_FAILED(button.CopyTo(finalElement)); - } - - void WireButtonClickToAction(_In_ IButton* button, _In_ IAdaptiveActionElement* action, _In_ IAdaptiveRenderContext* renderContext) - { - // Note that this method currently doesn't support inline show card actions, it - // assumes the caller won't call this method if inline show card is specified. - ComPtr localButton(button); - ComPtr actionInvoker; - THROW_IF_FAILED(renderContext->get_ActionInvoker(&actionInvoker)); - ComPtr strongAction(action); - - // Add click handler - ComPtr buttonBase; - THROW_IF_FAILED(localButton.As(&buttonBase)); - - EventRegistrationToken clickToken; - THROW_IF_FAILED(buttonBase->add_Click(Callback([strongAction, actionInvoker](IInspectable* /*sender*/, IRoutedEventArgs * - /*args*/) -> HRESULT { - THROW_IF_FAILED(actionInvoker->SendActionEvent(strongAction.Get())); - return S_OK; - }).Get(), - &clickToken)); - } - - void HandleSelectAction(_In_ IAdaptiveCardElement* adaptiveCardElement, - _In_ IAdaptiveActionElement* selectAction, - _In_ IAdaptiveRenderContext* renderContext, - _In_ IUIElement* uiElement, - bool supportsInteractivity, - bool fullWidthTouchTarget, - _COM_Outptr_ IUIElement** outUiElement) - { - if (selectAction != nullptr && supportsInteractivity) - { - WrapInTouchTarget(adaptiveCardElement, uiElement, selectAction, renderContext, fullWidthTouchTarget, L"Adaptive.SelectAction", outUiElement); - } - else - { - if (selectAction != nullptr) - { - renderContext->AddWarning(ABI::AdaptiveNamespace::WarningStatusCode::InteractivityNotSupported, - HStringReference(L"SelectAction present, but Interactivity is not supported").Get()); - } - - ComPtr localUiElement(uiElement); - THROW_IF_FAILED(localUiElement.CopyTo(outUiElement)); - } - } - - HRESULT BuildActions(_In_ IAdaptiveCard* adaptiveCard, - _In_ IVector* children, - _In_ IPanel* bodyPanel, - bool insertSeparator, - _In_ IAdaptiveRenderContext* renderContext, - _In_ ABI::AdaptiveNamespace::IAdaptiveRenderArgs* renderArgs) - { - ComPtr hostConfig; - RETURN_IF_FAILED(renderContext->get_HostConfig(&hostConfig)); - ComPtr actionsConfig; - RETURN_IF_FAILED(hostConfig->get_Actions(actionsConfig.GetAddressOf())); - - // Create a separator between the body and the actions - if (insertSeparator) - { - ABI::AdaptiveNamespace::Spacing spacing; - RETURN_IF_FAILED(actionsConfig->get_Spacing(&spacing)); - - UINT spacingSize; - RETURN_IF_FAILED(GetSpacingSizeFromSpacing(hostConfig.Get(), spacing, &spacingSize)); - - ABI::Windows::UI::Color color = {0}; - auto separator = XamlHelpers::CreateSeparator(renderContext, spacingSize, 0, color); - XamlHelpers::AppendXamlElementToPanel(separator.Get(), bodyPanel); - } - - ComPtr actionSetControl; - RETURN_IF_FAILED(BuildActionSetHelper(adaptiveCard, nullptr, children, renderContext, renderArgs, &actionSetControl)); - - XamlHelpers::AppendXamlElementToPanel(actionSetControl.Get(), bodyPanel); - return S_OK; - } - - HRESULT BuildActionSetHelper(_In_opt_ ABI::AdaptiveNamespace::IAdaptiveCard* adaptiveCard, - _In_opt_ IAdaptiveActionSet* adaptiveActionSet, - _In_ IVector* children, - _In_ IAdaptiveRenderContext* renderContext, - _In_ IAdaptiveRenderArgs* renderArgs, - _Outptr_ IUIElement** actionSetControl) - { - ComPtr hostConfig; - RETURN_IF_FAILED(renderContext->get_HostConfig(&hostConfig)); - ComPtr actionsConfig; - RETURN_IF_FAILED(hostConfig->get_Actions(actionsConfig.GetAddressOf())); - - ABI::AdaptiveNamespace::ActionAlignment actionAlignment; - RETURN_IF_FAILED(actionsConfig->get_ActionAlignment(&actionAlignment)); - - ABI::AdaptiveNamespace::ActionsOrientation actionsOrientation; - RETURN_IF_FAILED(actionsConfig->get_ActionsOrientation(&actionsOrientation)); - - // Declare the panel that will host the buttons - ComPtr actionsPanel; - ComPtr> columnDefinitions; - - if (actionAlignment == ABI::AdaptiveNamespace::ActionAlignment::Stretch && - actionsOrientation == ABI::AdaptiveNamespace::ActionsOrientation::Horizontal) - { - // If stretch alignment and orientation is horizontal, we use a grid with equal column widths to achieve - // stretch behavior. For vertical orientation, we'll still just use a stack panel since the concept of - // stretching buttons height isn't really valid, especially when the height of cards are typically dynamic. - ComPtr actionsGrid = - XamlHelpers::CreateXamlClass(HStringReference(RuntimeClass_Windows_UI_Xaml_Controls_Grid)); - RETURN_IF_FAILED(actionsGrid->get_ColumnDefinitions(&columnDefinitions)); - RETURN_IF_FAILED(actionsGrid.As(&actionsPanel)); - } - else - { - // Create a stack panel for the action buttons - ComPtr actionStackPanel = - XamlHelpers::CreateXamlClass(HStringReference(RuntimeClass_Windows_UI_Xaml_Controls_StackPanel)); - - auto uiOrientation = (actionsOrientation == ABI::AdaptiveNamespace::ActionsOrientation::Horizontal) ? - Orientation::Orientation_Horizontal : - Orientation::Orientation_Vertical; - - RETURN_IF_FAILED(actionStackPanel->put_Orientation(uiOrientation)); - - ComPtr actionsFrameworkElement; - RETURN_IF_FAILED(actionStackPanel.As(&actionsFrameworkElement)); - - switch (actionAlignment) - { - case ABI::AdaptiveNamespace::ActionAlignment::Center: - RETURN_IF_FAILED(actionsFrameworkElement->put_HorizontalAlignment(ABI::Windows::UI::Xaml::HorizontalAlignment_Center)); - break; - case ABI::AdaptiveNamespace::ActionAlignment::Left: - RETURN_IF_FAILED(actionsFrameworkElement->put_HorizontalAlignment(ABI::Windows::UI::Xaml::HorizontalAlignment_Left)); - break; - case ABI::AdaptiveNamespace::ActionAlignment::Right: - RETURN_IF_FAILED(actionsFrameworkElement->put_HorizontalAlignment(ABI::Windows::UI::Xaml::HorizontalAlignment_Right)); - break; - case ABI::AdaptiveNamespace::ActionAlignment::Stretch: - RETURN_IF_FAILED(actionsFrameworkElement->put_HorizontalAlignment(ABI::Windows::UI::Xaml::HorizontalAlignment_Stretch)); - break; - } - - // Add the action buttons to the stack panel - RETURN_IF_FAILED(actionStackPanel.As(&actionsPanel)); - } - - Thickness buttonMargin; - RETURN_IF_FAILED(ActionHelpers::GetButtonMargin(actionsConfig.Get(), buttonMargin)); - if (actionsOrientation == ABI::AdaptiveNamespace::ActionsOrientation::Horizontal) - { - // Negate the spacing on the sides so the left and right buttons are flush on the side. - // We do NOT remove the margin from the individual button itself, since that would cause - // the equal columns stretch behavior to not have equal columns (since the first and last - // button would be narrower without the same margins as its peers). - ComPtr actionsPanelAsFrameworkElement; - RETURN_IF_FAILED(actionsPanel.As(&actionsPanelAsFrameworkElement)); - RETURN_IF_FAILED(actionsPanelAsFrameworkElement->put_Margin({buttonMargin.Left * -1, 0, buttonMargin.Right * -1, 0})); - } - else - { - // Negate the spacing on the top and bottom so the first and last buttons don't have extra padding - ComPtr actionsPanelAsFrameworkElement; - RETURN_IF_FAILED(actionsPanel.As(&actionsPanelAsFrameworkElement)); - RETURN_IF_FAILED(actionsPanelAsFrameworkElement->put_Margin({0, buttonMargin.Top * -1, 0, buttonMargin.Bottom * -1})); - } - - UINT32 maxActions; - RETURN_IF_FAILED(actionsConfig->get_MaxActions(&maxActions)); - - bool allActionsHaveIcons{true}; - XamlHelpers::IterateOverVector(children, [&](IAdaptiveActionElement* child) { - HString iconUrl; - RETURN_IF_FAILED(child->get_IconUrl(iconUrl.GetAddressOf())); - - if (WindowsIsStringEmpty(iconUrl.Get())) - { - allActionsHaveIcons = false; - } - return S_OK; - }); - - UINT currentAction = 0; - - RETURN_IF_FAILED(renderArgs->put_AllowAboveTitleIconPlacement(allActionsHaveIcons)); - - std::shared_ptr>> allShowCards = std::make_shared>>(); - ComPtr showCardsStackPanel = - XamlHelpers::CreateXamlClass(HStringReference(RuntimeClass_Windows_UI_Xaml_Controls_StackPanel)); - ComPtr gridStatics; - RETURN_IF_FAILED(GetActivationFactory(HStringReference(RuntimeClass_Windows_UI_Xaml_Controls_Grid).Get(), &gridStatics)); - XamlHelpers::IterateOverVector(children, [&](IAdaptiveActionElement* child) { - if (currentAction < maxActions) - { - // Render each action using the registered renderer - ComPtr action(child); - ComPtr actionRegistration; - RETURN_IF_FAILED(renderContext->get_ActionRenderers(&actionRegistration)); - - ComPtr renderer; - while (!renderer) - { - HString actionTypeString; - RETURN_IF_FAILED(action->get_ActionTypeString(actionTypeString.GetAddressOf())); - RETURN_IF_FAILED(actionRegistration->Get(actionTypeString.Get(), &renderer)); - if (!renderer) - { - ABI::AdaptiveNamespace::FallbackType actionFallbackType; - action->get_FallbackType(&actionFallbackType); - switch (actionFallbackType) - { - case ABI::AdaptiveNamespace::FallbackType::Drop: - { - RETURN_IF_FAILED(XamlHelpers::WarnForFallbackDrop(renderContext, actionTypeString.Get())); - return S_OK; - } - - case ABI::AdaptiveNamespace::FallbackType::Content: - { - ComPtr actionFallback; - RETURN_IF_FAILED(action->get_FallbackContent(&actionFallback)); - - HString fallbackTypeString; - RETURN_IF_FAILED(actionFallback->get_ActionTypeString(fallbackTypeString.GetAddressOf())); - RETURN_IF_FAILED(XamlHelpers::WarnForFallbackContentElement(renderContext, - actionTypeString.Get(), - fallbackTypeString.Get())); - - action = actionFallback; - break; - } - - case ABI::AdaptiveNamespace::FallbackType::None: - default: - return E_FAIL; - } - } - } - - ComPtr actionControl; - RETURN_IF_FAILED(renderer->Render(action.Get(), renderContext, renderArgs, &actionControl)); - - XamlHelpers::AppendXamlElementToPanel(actionControl.Get(), actionsPanel.Get()); - - ABI::AdaptiveNamespace::ActionType actionType; - RETURN_IF_FAILED(action->get_ActionType(&actionType)); - - // Build inline show cards if needed - if (actionType == ABI::AdaptiveNamespace::ActionType_ShowCard) - { - ComPtr uiShowCard; - - ComPtr showCardActionConfig; - RETURN_IF_FAILED(actionsConfig->get_ShowCard(&showCardActionConfig)); - - ABI::AdaptiveNamespace::ActionMode showCardActionMode; - RETURN_IF_FAILED(showCardActionConfig->get_ActionMode(&showCardActionMode)); - - if (showCardActionMode == ABI::AdaptiveNamespace::ActionMode::Inline) - { - ComPtr showCardAction; - RETURN_IF_FAILED(action.As(&showCardAction)); - - ComPtr showCard; - RETURN_IF_FAILED(showCardAction->get_Card(&showCard)); - - RETURN_IF_FAILED(AdaptiveShowCardActionRenderer::BuildShowCard( - showCard.Get(), renderContext, renderArgs, (adaptiveActionSet == nullptr), uiShowCard.GetAddressOf())); - - ComPtr showCardsPanel; - RETURN_IF_FAILED(showCardsStackPanel.As(&showCardsPanel)); - XamlHelpers::AppendXamlElementToPanel(uiShowCard.Get(), showCardsPanel.Get()); - - if (adaptiveActionSet) - { - RETURN_IF_FAILED( - renderContext->AddInlineShowCard(adaptiveActionSet, showCardAction.Get(), uiShowCard.Get())); - } - else - { - ComPtr contextImpl = - PeekInnards(renderContext); - - RETURN_IF_FAILED( - contextImpl->AddInlineShowCard(adaptiveCard, showCardAction.Get(), uiShowCard.Get())); - } - } - } - - if (columnDefinitions != nullptr) - { - // If using the equal width columns, we'll add a column and assign the column - ComPtr columnDefinition = XamlHelpers::CreateXamlClass( - HStringReference(RuntimeClass_Windows_UI_Xaml_Controls_ColumnDefinition)); - RETURN_IF_FAILED(columnDefinition->put_Width({1.0, GridUnitType::GridUnitType_Star})); - RETURN_IF_FAILED(columnDefinitions->Append(columnDefinition.Get())); - - ComPtr actionFrameworkElement; - THROW_IF_FAILED(actionControl.As(&actionFrameworkElement)); - THROW_IF_FAILED(gridStatics->SetColumn(actionFrameworkElement.Get(), currentAction)); - } - } - else - { - renderContext->AddWarning(ABI::AdaptiveNamespace::WarningStatusCode::MaxActionsExceeded, - HStringReference(L"Some actions were not rendered due to exceeding the maximum number of actions allowed") - .Get()); - } - currentAction++; - return S_OK; - }); - - // Reset icon placement value - RETURN_IF_FAILED(renderArgs->put_AllowAboveTitleIconPlacement(false)); - - ComPtr actionsPanelAsFrameworkElement; - RETURN_IF_FAILED(actionsPanel.As(&actionsPanelAsFrameworkElement)); - RETURN_IF_FAILED(XamlHelpers::SetStyleFromResourceDictionary(renderContext, - L"Adaptive.Actions", - actionsPanelAsFrameworkElement.Get())); - - ComPtr actionSet = - XamlHelpers::CreateXamlClass(HStringReference(RuntimeClass_Windows_UI_Xaml_Controls_StackPanel)); - ComPtr actionSetAsPanel; - actionSet.As(&actionSetAsPanel); - - // Add buttons and show cards to panel - XamlHelpers::AppendXamlElementToPanel(actionsPanel.Get(), actionSetAsPanel.Get()); - XamlHelpers::AppendXamlElementToPanel(showCardsStackPanel.Get(), actionSetAsPanel.Get()); - - return actionSetAsPanel.CopyTo(actionSetControl); - } -} diff --git a/source/uwp/Renderer/lib/XamlBuilder.cpp b/source/uwp/Renderer/lib/XamlBuilder.cpp index f96304819a..a61f375e0f 100644 --- a/source/uwp/Renderer/lib/XamlBuilder.cpp +++ b/source/uwp/Renderer/lib/XamlBuilder.cpp @@ -32,6 +32,7 @@ #include #include "XamlBuilder.h" #include "XamlHelpers.h" +#include "LinkButton.h" using namespace Microsoft::WRL; using namespace Microsoft::WRL::Wrappers; @@ -1248,6 +1249,13 @@ namespace AdaptiveNamespace THROW_IF_FAILED(action->get_IconUrl(&iconUrl)); ComPtr localButton(button); + ComPtr automationProperties; + THROW_IF_FAILED( + GetActivationFactory(HStringReference(RuntimeClass_Windows_UI_Xaml_Automation_AutomationProperties).Get(), + &automationProperties)); + ComPtr buttonAsDependencyObject; + THROW_IF_FAILED(localButton.As(&buttonAsDependencyObject)); + THROW_IF_FAILED(automationProperties->SetName(buttonAsDependencyObject.Get(), title.Get())); // Check if the button has an iconUrl if (iconUrl != nullptr) @@ -1836,8 +1844,23 @@ namespace AdaptiveNamespace { // Render a button for the action ComPtr action(adaptiveActionElement); - ComPtr button = - XamlHelpers::CreateXamlClass(HStringReference(RuntimeClass_Windows_UI_Xaml_Controls_Button)); + ABI::AdaptiveNamespace::ActionType actionType; + RETURN_IF_FAILED(action->get_ActionType(&actionType)); + + // now construct an appropriate button for the action type + ComPtr button; + if (actionType == ABI::AdaptiveNamespace::ActionType_OpenUrl) + { + // OpenUrl buttons should appear as links for accessibility purposes, so we use our custom LinkButton. + auto linkButton = winrt::make(); + button = linkButton.as().detach(); + } + + if (!button) + { + // Either non-OpenUrl action or instantiating LinkButton failed. Use standard button. + button = XamlHelpers::CreateXamlClass(HStringReference(RuntimeClass_Windows_UI_Xaml_Controls_Button)); + } ComPtr buttonFrameworkElement; RETURN_IF_FAILED(button.As(&buttonFrameworkElement)); @@ -1895,9 +1918,6 @@ namespace AdaptiveNamespace allowAboveTitleIconPlacement, button.Get()); - ABI::AdaptiveNamespace::ActionType actionType; - RETURN_IF_FAILED(action->get_ActionType(&actionType)); - ComPtr showCardActionConfig; RETURN_IF_FAILED(actionsConfig->get_ShowCard(&showCardActionConfig)); ABI::AdaptiveNamespace::ActionMode showCardActionMode; From 51b9389033820612a3738abd865ed2cb268a07e4 Mon Sep 17 00:00:00 2001 From: shalinijoshi19 Date: Mon, 4 Nov 2019 13:37:47 -0800 Subject: [PATCH 17/19] Revert "fix xml serialization (#3455)" This reverts commit 3ca37b1b2f263a94050ca1bcc3a1bc25827d7fab which introduced an unexpected breaking change in the patch-only 1.2 branch release train with 1.2.3 --- .../AdaptiveCards/AdaptiveActionSet.cs | 1 - .../Library/AdaptiveCards/AdaptiveCard.cs | 28 ++++-- .../AdaptiveCollectionElement.cs | 16 +--- .../AdaptiveCards/AdaptiveContainer.cs | 2 - .../Library/AdaptiveCards/AdaptiveElement.cs | 27 +++++- .../Library/AdaptiveCards/AdaptiveHeight.cs | 74 +++++++--------- .../Library/AdaptiveCards/AdaptiveInline.cs | 37 -------- .../AdaptiveCards/AdaptiveInlinesConverter.cs | 13 ++- .../AdaptiveCards/AdaptiveRichTextBlock.cs | 2 +- .../AdaptiveCards/AdaptiveTargetElement.cs | 14 +-- .../AdaptiveCards/AdaptiveTextInput.cs | 1 - .../Library/AdaptiveCards/AdaptiveTextRun.cs | 6 +- .../AdaptiveToggleVisibilityAction.cs | 2 +- .../AdaptiveCards/AdaptiveTypedElement.cs | 12 +-- .../Library/AdaptiveCards/IAdaptiveInline.cs | 8 ++ .../AdaptiveCards/SerializableDictionary.cs | 85 ------------------- .../XmlSerializationTests.cs | 38 +++------ 17 files changed, 122 insertions(+), 244 deletions(-) delete mode 100644 source/dotnet/Library/AdaptiveCards/AdaptiveInline.cs create mode 100644 source/dotnet/Library/AdaptiveCards/IAdaptiveInline.cs delete mode 100644 source/dotnet/Library/AdaptiveCards/SerializableDictionary.cs diff --git a/source/dotnet/Library/AdaptiveCards/AdaptiveActionSet.cs b/source/dotnet/Library/AdaptiveCards/AdaptiveActionSet.cs index 53527f826b..967ecc76ff 100644 --- a/source/dotnet/Library/AdaptiveCards/AdaptiveActionSet.cs +++ b/source/dotnet/Library/AdaptiveCards/AdaptiveActionSet.cs @@ -35,7 +35,6 @@ public class AdaptiveActionSet : AdaptiveElement [XmlElement(typeof(AdaptiveShowCardAction))] [XmlElement(typeof(AdaptiveSubmitAction))] [XmlElement(typeof(AdaptiveToggleVisibilityAction))] - [XmlElement(typeof(AdaptiveUnknownAction))] #endif public List Actions { get; set; } = new List(); } diff --git a/source/dotnet/Library/AdaptiveCards/AdaptiveCard.cs b/source/dotnet/Library/AdaptiveCards/AdaptiveCard.cs index aec8c240cb..28eedbe0f4 100644 --- a/source/dotnet/Library/AdaptiveCards/AdaptiveCard.cs +++ b/source/dotnet/Library/AdaptiveCards/AdaptiveCard.cs @@ -123,9 +123,10 @@ public AdaptiveCard() : this(new AdaptiveSchemaVersion(1, 0)) { } [JsonConverter(typeof(StringSizeWithUnitConverter), true)] [JsonProperty(Order = -4, DefaultValueHandling = DefaultValueHandling.Ignore)] #if !NETSTANDARD1_3 - [XmlElement] + [XmlElement(typeof(AdaptiveHeight))] #endif - public AdaptiveHeight Height { get; set; } = new AdaptiveHeight(AdaptiveHeightType.Auto); + [DefaultValue(typeof(AdaptiveHeight), "auto")] + public AdaptiveHeight Height { get; set; } /// /// Explicit card minimum height in pixels @@ -145,7 +146,6 @@ public AdaptiveCard() : this(new AdaptiveSchemaVersion(1, 0)) { } [JsonConverter(typeof(IgnoreEmptyItemsConverter))] #if !NETSTANDARD1_3 [XmlElement(typeof(AdaptiveTextBlock))] - [XmlElement(typeof(AdaptiveRichTextBlock))] [XmlElement(typeof(AdaptiveImage))] [XmlElement(typeof(AdaptiveContainer))] [XmlElement(typeof(AdaptiveColumnSet))] @@ -159,7 +159,6 @@ public AdaptiveCard() : this(new AdaptiveSchemaVersion(1, 0)) { } [XmlElement(typeof(AdaptiveChoiceSetInput))] [XmlElement(typeof(AdaptiveMedia))] [XmlElement(typeof(AdaptiveActionSet))] - [XmlElement(typeof(AdaptiveUnknownElement))] #endif public List Body { get; set; } = new List(); @@ -175,7 +174,6 @@ public AdaptiveCard() : this(new AdaptiveSchemaVersion(1, 0)) { } [XmlElement(typeof(AdaptiveShowCardAction))] [XmlElement(typeof(AdaptiveSubmitAction))] [XmlElement(typeof(AdaptiveToggleVisibilityAction))] - [XmlElement(typeof(AdaptiveUnknownAction))] #endif public List Actions { get; set; } = new List(); @@ -215,7 +213,25 @@ public bool ShouldSerializeJsonSchema() [DefaultValue(null)] public AdaptiveAction SelectAction { get; set; } - public bool ShouldSerializeHeight() => this.Height?.ShouldSerializeAdaptiveHeight() == true; + public bool ShouldSerializeHeight() + { + if (Height == AdaptiveHeight.Auto) + { + return false; + } + if (Height.HeightType == AdaptiveHeightType.Pixel) + { + if (!Height.Unit.HasValue) + { + return false; + } + if (Height.Unit.Value == 0) + { + return false; + } + } + return true; + } /// /// Callback that will be invoked should a null or empty version string is encountered. The callback may return an alternate version to use for parsing. diff --git a/source/dotnet/Library/AdaptiveCards/AdaptiveCollectionElement.cs b/source/dotnet/Library/AdaptiveCards/AdaptiveCollectionElement.cs index 19623b11d4..4dbfc3e89b 100644 --- a/source/dotnet/Library/AdaptiveCards/AdaptiveCollectionElement.cs +++ b/source/dotnet/Library/AdaptiveCards/AdaptiveCollectionElement.cs @@ -16,32 +16,24 @@ namespace AdaptiveCards /// public abstract class AdaptiveCollectionElement : AdaptiveElement { + /// /// The style in which the image is displayed. /// [JsonConverter(typeof(IgnoreNullEnumConverter), true)] [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] #if !NETSTANDARD1_3 - [XmlIgnore] + [XmlElement] #endif [DefaultValue(null)] public AdaptiveContainerStyle? Style { get; set; } -#if !NETSTANDARD1_3 - // Xml Serializer doesn't handle nullable value types, but this trick allows us to serialize only if non-null - [JsonIgnore] - [XmlAttribute("Style")] - [EditorBrowsable(EditorBrowsableState.Never)] - public AdaptiveContainerStyle StyleXml { get { return (Style.HasValue) ? Style.Value : AdaptiveContainerStyle.Default; } set { Style = value; } } - public bool ShouldSerializeStyleXml() => this.Style.HasValue; -#endif - /// /// The content alignment for the element inside the container. /// [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] #if !NETSTANDARD1_3 - [XmlAttribute] + [XmlElement] #endif [DefaultValue(typeof(AdaptiveVerticalContentAlignment), "top")] public AdaptiveVerticalContentAlignment VerticalContentAlignment { get; set; } @@ -61,7 +53,7 @@ public abstract class AdaptiveCollectionElement : AdaptiveElement /// [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] #if !NETSTANDARD1_3 - [XmlAttribute] + [XmlElement] #endif [DefaultValue(false)] public bool Bleed { get; set; } diff --git a/source/dotnet/Library/AdaptiveCards/AdaptiveContainer.cs b/source/dotnet/Library/AdaptiveCards/AdaptiveContainer.cs index 707fcad84d..5bf75e2ae1 100644 --- a/source/dotnet/Library/AdaptiveCards/AdaptiveContainer.cs +++ b/source/dotnet/Library/AdaptiveCards/AdaptiveContainer.cs @@ -34,7 +34,6 @@ public class AdaptiveContainer : AdaptiveCollectionElement [JsonConverter(typeof(IgnoreEmptyItemsConverter))] #if !NETSTANDARD1_3 [XmlElement(typeof(AdaptiveTextBlock))] - [XmlElement(typeof(AdaptiveRichTextBlock))] [XmlElement(typeof(AdaptiveImage))] [XmlElement(typeof(AdaptiveContainer))] [XmlElement(typeof(AdaptiveColumnSet))] @@ -48,7 +47,6 @@ public class AdaptiveContainer : AdaptiveCollectionElement [XmlElement(typeof(AdaptiveToggleInput))] [XmlElement(typeof(AdaptiveMedia))] [XmlElement(typeof(AdaptiveActionSet))] - [XmlElement(typeof(AdaptiveUnknownElement))] #endif public List Items { get; set; } = new List(); diff --git a/source/dotnet/Library/AdaptiveCards/AdaptiveElement.cs b/source/dotnet/Library/AdaptiveCards/AdaptiveElement.cs index 6c8c35a8f3..29c302236a 100644 --- a/source/dotnet/Library/AdaptiveCards/AdaptiveElement.cs +++ b/source/dotnet/Library/AdaptiveCards/AdaptiveElement.cs @@ -37,17 +37,36 @@ public abstract class AdaptiveElement : AdaptiveTypedElement [Obsolete("CardElement.Speak has been deprecated. Use AdaptiveCard.Speak", false)] public string Speak { get; set; } + public bool ShouldSerializeHeight() + { + if (Height == AdaptiveHeight.Auto) + { + return false; + } + if (Height.HeightType == AdaptiveHeightType.Pixel) + { + if (!Height.Unit.HasValue) + { + return false; + } + if (Height.Unit.Value == 0) + { + return false; + } + } + return true; + } + /// /// The amount of space the element should be separated from the previous element. Default value is . /// [JsonConverter(typeof(StringSizeWithUnitConverter), true)] [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] #if !NETSTANDARD1_3 - [XmlElement] + [XmlElement(typeof(AdaptiveHeight))] #endif - public AdaptiveHeight Height { get; set; } = new AdaptiveHeight(AdaptiveHeightType.Auto); - - public bool ShouldSerializeHeight() => this.Height?.ShouldSerializeAdaptiveHeight() == true; + [DefaultValue(typeof(AdaptiveHeight), "auto")] + public AdaptiveHeight Height { get; set; } /// /// Indicates whether the element should be visible when the card has been rendered. diff --git a/source/dotnet/Library/AdaptiveCards/AdaptiveHeight.cs b/source/dotnet/Library/AdaptiveCards/AdaptiveHeight.cs index 0a4f6fe77b..6e74f1cf34 100644 --- a/source/dotnet/Library/AdaptiveCards/AdaptiveHeight.cs +++ b/source/dotnet/Library/AdaptiveCards/AdaptiveHeight.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. using Newtonsoft.Json; -using System; -using System.Xml.Serialization; namespace AdaptiveCards { @@ -30,48 +28,30 @@ public enum AdaptiveHeightType } - public class AdaptiveHeight : IEquatable + public struct AdaptiveHeight { public static AdaptiveHeight Auto { get; } = new AdaptiveHeight(AdaptiveHeightType.Auto); public static AdaptiveHeight Stretch { get; } = new AdaptiveHeight(AdaptiveHeightType.Stretch); - public AdaptiveHeight() - { - } + public AdaptiveHeightType _heightType; + public AdaptiveHeightType HeightType { get { return _heightType; } set { } } + + public uint? _unit; + public uint? Unit { get { return _unit; } set { } } public AdaptiveHeight(uint px) { - HeightType = AdaptiveHeightType.Pixel; - this.Unit = px; + _heightType = AdaptiveHeightType.Pixel; + _unit = px; } public AdaptiveHeight(AdaptiveHeightType heightType) { - HeightType = heightType; - Unit = null; + _heightType = heightType; + _unit = null; } - - [JsonProperty("heightType")] -#if !NETSTANDARD1_3 - [XmlAttribute] -#endif - public AdaptiveHeightType HeightType { get; set; } - - [JsonProperty("unit")] -#if !NETSTANDARD1_3 - [XmlIgnore] -#endif - public uint? Unit { get; set; } - -#if !NETSTANDARD1_3 - [XmlAttribute("Unit")] - [JsonIgnore] - public uint UnitXml { get { return Unit.HasValue ? Unit.Value : 0; } set { Unit = value; } } - public bool ShouldSerializeUnitXml() => Unit.HasValue; -#endif - public bool IsPixel() { return HeightType == AdaptiveHeightType.Pixel; @@ -83,8 +63,7 @@ public bool ShouldSerializeAdaptiveHeight() { return false; } - - if (HeightType == AdaptiveHeightType.Pixel) + if( HeightType == AdaptiveHeightType.Pixel ) { if (!Unit.HasValue) { @@ -100,13 +79,23 @@ public bool ShouldSerializeAdaptiveHeight() public static bool operator ==(AdaptiveHeight ah1, AdaptiveHeight ah2) { - return ah1.Equals(ah2); + if (ah1 != null && ah2 != null) + { + return ah1.Equals(ah2); + } + + if (ah1 == null && ah2 == null) + { + return true; + } + return false; } public static bool operator !=(AdaptiveHeight ah1, AdaptiveHeight ah2) { - return !ah1.Equals(ah2); + return !(ah1 == ah2); } + public override int GetHashCode() { if (!Unit.HasValue) @@ -118,18 +107,17 @@ public override int GetHashCode() public override bool Equals(object obj) { - return this.Equals(obj as AdaptiveHeight); - } - - public Boolean Equals(AdaptiveHeight other) - { - if (this.HeightType == other.HeightType) + if (obj is AdaptiveHeight) { - if (this.HeightType == AdaptiveHeightType.Pixel) + AdaptiveHeight ah = (AdaptiveHeight)obj; + if (HeightType == ah.HeightType) { - return this.Unit == other.Unit; + if (HeightType == AdaptiveHeightType.Pixel) + { + return Unit == ah.Unit; + } + return true; } - return true; } return false; } diff --git a/source/dotnet/Library/AdaptiveCards/AdaptiveInline.cs b/source/dotnet/Library/AdaptiveCards/AdaptiveInline.cs deleted file mode 100644 index 0ec76c6b7c..0000000000 --- a/source/dotnet/Library/AdaptiveCards/AdaptiveInline.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -using Newtonsoft.Json; -using Newtonsoft.Json.Serialization; -using System; -using System.Collections.Generic; -using System.Xml.Serialization; - -namespace AdaptiveCards -{ - [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))] - public abstract class AdaptiveInline - { - /// - /// The type name of the inline - /// - [JsonProperty(Order = -10, Required = Required.Always, DefaultValueHandling = DefaultValueHandling.Include)] -#if !NETSTANDARD1_3 - // don't serialize type with xml, because we use element name or attribute for type - [XmlIgnore] -#endif - public abstract string Type { get; set; } - - /// - /// Additional properties not found on the default schema - /// - [JsonExtensionData] -#if NETSTANDARD1_3 - public IDictionary AdditionalProperties { get; set; } = new Dictionary(StringComparer.OrdinalIgnoreCase); -#else - // Dictionary<> is not supported with XmlSerialization because Dictionary is not serializable, SerializableDictionary<> is - [XmlElement] - public SerializableDictionary AdditionalProperties { get; set; } = new SerializableDictionary(StringComparer.OrdinalIgnoreCase); - public bool ShouldSerializeAdditionalProperties() => this.AdditionalProperties.Count > 0; -#endif - } -} diff --git a/source/dotnet/Library/AdaptiveCards/AdaptiveInlinesConverter.cs b/source/dotnet/Library/AdaptiveCards/AdaptiveInlinesConverter.cs index 6bc0730b05..80351b5a1f 100644 --- a/source/dotnet/Library/AdaptiveCards/AdaptiveInlinesConverter.cs +++ b/source/dotnet/Library/AdaptiveCards/AdaptiveInlinesConverter.cs @@ -30,6 +30,7 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist Converters = { new StrictIntConverter() } }; + // We only support text runs for now, which can be specified as either a string or an object foreach (object obj in list) { @@ -45,7 +46,17 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist throw new AdaptiveSerializationException($"Property 'type' must be '{AdaptiveTextRun.TypeName}'"); } - arrayList.Add(JsonConvert.DeserializeObject(jobj.ToString(), serializerSettigns)); + if (ParseContext == null) + { + ParseContext = new ParseContext(); + } + + var adaptiveInline = JsonConvert.DeserializeObject(jobj.ToString(), new JsonSerializerSettings + { + ContractResolver = new WarningLoggingContractResolver(new AdaptiveCardParseResult(), ParseContext), + Converters = { new StrictIntConverter() } + }); + arrayList.Add(adaptiveInline); } } return arrayList; diff --git a/source/dotnet/Library/AdaptiveCards/AdaptiveRichTextBlock.cs b/source/dotnet/Library/AdaptiveCards/AdaptiveRichTextBlock.cs index ae7521fc2a..89e640f259 100644 --- a/source/dotnet/Library/AdaptiveCards/AdaptiveRichTextBlock.cs +++ b/source/dotnet/Library/AdaptiveCards/AdaptiveRichTextBlock.cs @@ -39,6 +39,6 @@ public AdaptiveRichTextBlock() #if !NETSTANDARD1_3 [XmlElement(typeof(AdaptiveTextRun))] #endif - public List Inlines { get; set; } = new List(); + public List Inlines { get; set; } = new List(); } } diff --git a/source/dotnet/Library/AdaptiveCards/AdaptiveTargetElement.cs b/source/dotnet/Library/AdaptiveCards/AdaptiveTargetElement.cs index 1ac0b4d06b..e1e6c960ea 100644 --- a/source/dotnet/Library/AdaptiveCards/AdaptiveTargetElement.cs +++ b/source/dotnet/Library/AdaptiveCards/AdaptiveTargetElement.cs @@ -6,7 +6,6 @@ using Newtonsoft.Json.Serialization; using System; using System.Collections.Generic; -using System.ComponentModel; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -34,12 +33,12 @@ public AdaptiveTargetElement(string elementId, bool isVisible) /// Identifier of element to change visibility. /// #if !NETSTANDARD1_3 - [XmlAttribute] + [XmlIgnore] #endif public string ElementId { get; set; } /// - /// Value to change visibility to. + /// Value to change visibility to. /// [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] #if !NETSTANDARD1_3 @@ -47,15 +46,6 @@ public AdaptiveTargetElement(string elementId, bool isVisible) #endif public bool? IsVisible { get; set; } = null; -#if !NETSTANDARD1_3 - // Xml Serializer doesn't handle nullable value types, but this trick allows us to serialize only if non-null - [JsonIgnore] - [XmlAttribute("IsVisible")] - [EditorBrowsable(EditorBrowsableState.Never)] - public bool IsVisibleXml { get { return IsVisible.HasValue ? IsVisible.Value : true; } set { IsVisible = value; } } - public bool ShouldSerializeIsVisibleXml() => this.IsVisible.HasValue; -#endif - /// /// Implicit conversion from to . /// diff --git a/source/dotnet/Library/AdaptiveCards/AdaptiveTextInput.cs b/source/dotnet/Library/AdaptiveCards/AdaptiveTextInput.cs index 0fadac94fe..0ce2b9f48e 100644 --- a/source/dotnet/Library/AdaptiveCards/AdaptiveTextInput.cs +++ b/source/dotnet/Library/AdaptiveCards/AdaptiveTextInput.cs @@ -80,7 +80,6 @@ public class AdaptiveTextInput : AdaptiveInput [XmlElement(typeof(AdaptiveShowCardAction))] [XmlElement(typeof(AdaptiveSubmitAction))] [XmlElement(typeof(AdaptiveToggleVisibilityAction))] - [XmlElement(typeof(AdaptiveUnknownAction))] #endif public AdaptiveAction InlineAction { get; set; } diff --git a/source/dotnet/Library/AdaptiveCards/AdaptiveTextRun.cs b/source/dotnet/Library/AdaptiveCards/AdaptiveTextRun.cs index 23b17817a8..f922ba677e 100644 --- a/source/dotnet/Library/AdaptiveCards/AdaptiveTextRun.cs +++ b/source/dotnet/Library/AdaptiveCards/AdaptiveTextRun.cs @@ -9,13 +9,13 @@ namespace AdaptiveCards { [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))] #if !NETSTANDARD1_3 - [XmlType(TypeName = AdaptiveTextRun.TypeName)] + [XmlType(TypeName = AdaptiveTextBlock.TypeName)] #endif - public class AdaptiveTextRun : AdaptiveInline, IAdaptiveTextElement + public class AdaptiveTextRun : IAdaptiveTextElement, IAdaptiveInline { public const string TypeName = "TextRun"; - public override string Type { get; set; } = TypeName; + public string Type { get; set; } = TypeName; public AdaptiveTextRun() { diff --git a/source/dotnet/Library/AdaptiveCards/AdaptiveToggleVisibilityAction.cs b/source/dotnet/Library/AdaptiveCards/AdaptiveToggleVisibilityAction.cs index 6c5dae3b6b..941ecb414e 100644 --- a/source/dotnet/Library/AdaptiveCards/AdaptiveToggleVisibilityAction.cs +++ b/source/dotnet/Library/AdaptiveCards/AdaptiveToggleVisibilityAction.cs @@ -34,7 +34,7 @@ public class AdaptiveToggleVisibilityAction : AdaptiveAction [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] [JsonConverter(typeof(ToggleElementsConverter))] #if !NETSTANDARD1_3 - [XmlElement] + [XmlIgnore] #endif public List TargetElements { get; set; } = new List(); } diff --git a/source/dotnet/Library/AdaptiveCards/AdaptiveTypedElement.cs b/source/dotnet/Library/AdaptiveCards/AdaptiveTypedElement.cs index c457726b41..a18af8fc45 100644 --- a/source/dotnet/Library/AdaptiveCards/AdaptiveTypedElement.cs +++ b/source/dotnet/Library/AdaptiveCards/AdaptiveTypedElement.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.ComponentModel; -using System.Runtime.Serialization; using System.Xml.Serialization; namespace AdaptiveCards @@ -19,7 +18,6 @@ public abstract class AdaptiveTypedElement ///
[JsonProperty(Order = -10, Required = Required.Always, DefaultValueHandling = DefaultValueHandling.Include)] #if !NETSTANDARD1_3 - // don't serialize type with xml, because we use element name or attribute for type [XmlIgnore] #endif public abstract string Type { get; set; } @@ -28,14 +26,10 @@ public abstract class AdaptiveTypedElement /// Additional properties not found on the default schema /// [JsonExtensionData] -#if NETSTANDARD1_3 - public IDictionary AdditionalProperties { get; set; } = new Dictionary(StringComparer.OrdinalIgnoreCase); -#else - // Dictionary<> is not supported with XmlSerialization because Dictionary is not serializable, SerializableDictionary<> is - [XmlElement] - public SerializableDictionary AdditionalProperties { get; set; } = new SerializableDictionary(StringComparer.OrdinalIgnoreCase); - public bool ShouldSerializeAdditionalProperties() => this.AdditionalProperties.Count > 0; +#if !NETSTANDARD1_3 + [XmlIgnore] #endif + public IDictionary AdditionalProperties { get; set; } = new Dictionary(StringComparer.OrdinalIgnoreCase); [JsonConverter(typeof(AdaptiveFallbackConverter))] [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] diff --git a/source/dotnet/Library/AdaptiveCards/IAdaptiveInline.cs b/source/dotnet/Library/AdaptiveCards/IAdaptiveInline.cs new file mode 100644 index 0000000000..e0ab7c7b67 --- /dev/null +++ b/source/dotnet/Library/AdaptiveCards/IAdaptiveInline.cs @@ -0,0 +1,8 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +namespace AdaptiveCards +{ + public interface IAdaptiveInline + { + } +} diff --git a/source/dotnet/Library/AdaptiveCards/SerializableDictionary.cs b/source/dotnet/Library/AdaptiveCards/SerializableDictionary.cs deleted file mode 100644 index c782f3236f..0000000000 --- a/source/dotnet/Library/AdaptiveCards/SerializableDictionary.cs +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -using System; -using System.Collections; -using System.Collections.Generic; -using System.Xml.Serialization; - -namespace AdaptiveCards -{ -#if !NETSTANDARD1_3 - [XmlRoot("dictionary")] - public class SerializableDictionary : Dictionary, IXmlSerializable - { - public SerializableDictionary() - : base() - { - } - - public SerializableDictionary(IEqualityComparer comparer) - : base(comparer) - { - } - - #region IXmlSerializable Members - public System.Xml.Schema.XmlSchema GetSchema() - { - return null; - } - - public void ReadXml(System.Xml.XmlReader reader) - { - XmlSerializer keySerializer = new XmlSerializer(typeof(TKey)); - XmlSerializer valueSerializer = new XmlSerializer(typeof(TValue)); - - bool wasEmpty = reader.IsEmptyElement; - reader.Read(); - - if (wasEmpty) - return; - - while (reader.NodeType != System.Xml.XmlNodeType.EndElement) - { - reader.ReadStartElement("item"); - - reader.ReadStartElement("key"); - TKey key = (TKey)keySerializer.Deserialize(reader); - reader.ReadEndElement(); - - reader.ReadStartElement("value"); - TValue value = (TValue)valueSerializer.Deserialize(reader); - reader.ReadEndElement(); - - this.Add(key, value); - - reader.ReadEndElement(); - reader.MoveToContent(); - } - reader.ReadEndElement(); - } - - public void WriteXml(System.Xml.XmlWriter writer) - { - XmlSerializer keySerializer = new XmlSerializer(typeof(TKey)); - XmlSerializer valueSerializer = new XmlSerializer(typeof(TValue)); - - foreach (TKey key in this.Keys) - { - writer.WriteStartElement("item"); - - writer.WriteStartElement("key"); - keySerializer.Serialize(writer, key); - writer.WriteEndElement(); - - writer.WriteStartElement("value"); - TValue value = this[key]; - valueSerializer.Serialize(writer, value); - writer.WriteEndElement(); - - writer.WriteEndElement(); - } - } - #endregion - } -#endif -} diff --git a/source/dotnet/Test/AdaptiveCards.Test/XmlSerializationTests.cs b/source/dotnet/Test/AdaptiveCards.Test/XmlSerializationTests.cs index cbad8c6074..eef7450794 100644 --- a/source/dotnet/Test/AdaptiveCards.Test/XmlSerializationTests.cs +++ b/source/dotnet/Test/AdaptiveCards.Test/XmlSerializationTests.cs @@ -21,7 +21,7 @@ namespace AdaptiveCards.Test public class XmlSerializationTests { [TestMethod] - public void VerifySerializationForAllScenarioFiles() + public void SerializeAllScenarios() { CompareLogic compareLogic = new CompareLogic(new ComparisonConfig() { @@ -34,34 +34,20 @@ public void VerifySerializationForAllScenarioFiles() }); XmlSerializer serializer = new XmlSerializer(typeof(AdaptiveCard)); - foreach (var version in Directory.EnumerateDirectories(@"..\..\..\..\..\..\..\samples\", "v*")) + foreach (var file in Directory.EnumerateFiles(@"..\..\..\..\..\..\..\samples\v1.0\Scenarios")) { - var folder = Path.Combine($"{version}\\scenarios"); - if (Directory.Exists(folder)) + string json = File.ReadAllText(file); + var card = JsonConvert.DeserializeObject(json, new JsonSerializerSettings { - foreach (var file in Directory.EnumerateFiles(folder, "*.json")) - { - string json = File.ReadAllText(file); - var card = JsonConvert.DeserializeObject(json, new JsonSerializerSettings - { - Converters = { new StrictIntConverter() } - }); + Converters = { new StrictIntConverter() } + }); + StringBuilder sb = new StringBuilder(); + serializer.Serialize(new StringWriter(sb), card); + string xml = sb.ToString(); + var card2 = (AdaptiveCard)serializer.Deserialize(new StringReader(xml)); - // test XML serialization round-trips - StringBuilder sb = new StringBuilder(); - serializer.Serialize(new StringWriter(sb), card); - string xml = sb.ToString(); - var card2 = (AdaptiveCard)serializer.Deserialize(new StringReader(xml)); - - var result = compareLogic.Compare(card, card2); - Assert.IsTrue(result.AreEqual, $"XML serialization different: {Path.GetFullPath(file)}: {result.DifferencesString}"); - - // test JSON serialization round-trips - var card3 = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(card)); - result = compareLogic.Compare(card, card3); - Assert.IsTrue(result.AreEqual, $"JSON Serialization different: {Path.GetFullPath(file)}: {result.DifferencesString}"); - } - } + var result = compareLogic.Compare(card, card2); + Assert.IsTrue(result.AreEqual, result.DifferencesString); } } } From 88016066165c5b2ed621bef277437a24185fc314 Mon Sep 17 00:00:00 2001 From: shalinijoshi19 Date: Mon, 4 Nov 2019 13:41:02 -0800 Subject: [PATCH 18/19] Bad merge with the last revert --- .../dotnet/Library/AdaptiveCards/AdaptiveInlinesConverter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/dotnet/Library/AdaptiveCards/AdaptiveInlinesConverter.cs b/source/dotnet/Library/AdaptiveCards/AdaptiveInlinesConverter.cs index 80351b5a1f..85867c6260 100644 --- a/source/dotnet/Library/AdaptiveCards/AdaptiveInlinesConverter.cs +++ b/source/dotnet/Library/AdaptiveCards/AdaptiveInlinesConverter.cs @@ -16,14 +16,14 @@ class AdaptiveInlinesConverter : AdaptiveTypedBaseElementConverter public override bool CanConvert(Type objectType) { - return typeof(List).GetTypeInfo().IsAssignableFrom(objectType.GetTypeInfo()); + return typeof(List).GetTypeInfo().IsAssignableFrom(objectType.GetTypeInfo()); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { var array = JArray.Load(reader); List list = array.ToObject>(); - List arrayList = new List(); + List arrayList = new List(); var serializerSettigns = new JsonSerializerSettings { ContractResolver = new WarningLoggingContractResolver(new AdaptiveCardParseResult(), ParseContext), From 69a16d169cf41698a049f2db636b160b2768ac68 Mon Sep 17 00:00:00 2001 From: nesalang Date: Mon, 4 Nov 2019 14:24:49 -0800 Subject: [PATCH 19/19] fixed bad merge --- .../AdaptiveCards/AdaptiveInlinesConverter.cs | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/source/dotnet/Library/AdaptiveCards/AdaptiveInlinesConverter.cs b/source/dotnet/Library/AdaptiveCards/AdaptiveInlinesConverter.cs index 85867c6260..fcaf54a145 100644 --- a/source/dotnet/Library/AdaptiveCards/AdaptiveInlinesConverter.cs +++ b/source/dotnet/Library/AdaptiveCards/AdaptiveInlinesConverter.cs @@ -30,7 +30,6 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist Converters = { new StrictIntConverter() } }; - // We only support text runs for now, which can be specified as either a string or an object foreach (object obj in list) { @@ -46,17 +45,7 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist throw new AdaptiveSerializationException($"Property 'type' must be '{AdaptiveTextRun.TypeName}'"); } - if (ParseContext == null) - { - ParseContext = new ParseContext(); - } - - var adaptiveInline = JsonConvert.DeserializeObject(jobj.ToString(), new JsonSerializerSettings - { - ContractResolver = new WarningLoggingContractResolver(new AdaptiveCardParseResult(), ParseContext), - Converters = { new StrictIntConverter() } - }); - arrayList.Add(adaptiveInline); + arrayList.Add(JsonConvert.DeserializeObject(jobj.ToString(), serializerSettigns)); } } return arrayList;