Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability for Dev Home to display settings cards from an adaptive card #2488

Merged
merged 13 commits into from
Apr 5, 2024
Merged
27 changes: 27 additions & 0 deletions common/Contracts/IDevHomeActionSetRender.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using AdaptiveCards.Rendering.WinUI3;

namespace DevHome.Common.Contracts;

/// <summary>
/// Represents a renderer for an adaptive card action set that Dev Home can use to invoke actions from within
/// the action set. This is useful for invoking an adaptive card action from an abitraty button within Dev Home's UI.
/// </summary>
public interface IDevHomeActionSetRender : IAdaptiveElementRenderer
bbonaby marked this conversation as resolved.
Show resolved Hide resolved
{
/// <summary>
/// Attempts to validate the user inputs and initiate the action.
/// </summary>
/// <param name="buttonId">The id of the button within the adaptive card Json template</param>
/// <param name="userInputs">The user input from the adaptive card session</param>
/// <returns>True if the users inputs were validated and false otherwise</returns>
public bool TryValidateAndInitiateAction(string buttonId, AdaptiveInputs userInputs);

/// <summary>
/// Initiates the action without validating the user inputs.
/// </summary>
/// <param name="buttonId">The id of the button within the adaptive card Json template</param>
public void InitiateAction(string buttonId);
}
35 changes: 35 additions & 0 deletions common/DevHomeAdaptiveCards/CardInterfaces/IDevHomeSettingsCard.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using AdaptiveCards.ObjectModel.WinUI3;

namespace DevHome.Common.DevHomeAdaptiveCards.CardInterfaces;

/// <summary>
/// Represents a Dev Home settings card that can be rendered through an adaptive card.
/// </summary>
public interface IDevHomeSettingsCard : IAdaptiveCardElement
{
/// <summary>
/// gets or sets the subtitle of the card. The description naming is used because that is what
/// is used in the Windows Community Toolkit settings cards. This is displayed below the header.
/// </summary>
public string Description { get; set; }

/// <summary>
/// gets or sets the header of the card. This is the bolded text at the top of the card.
/// </summary>
public string Header { get; set; }

/// <summary>
/// gets or sets the icon base64 string that represents an image. This is the icon that is
/// displayed to the left of the header.
/// </summary>
public string HeaderIcon { get; set; }

// An element that is not expected to submit the adaptive card
public IDevHomeSettingsCardNonSubmitAction? NonSubmitActionElement { get; set; }

// An element that is expected to submit the adaptive card
public IAdaptiveActionElement? SubmitActionElement { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Threading.Tasks;
using AdaptiveCards.ObjectModel.WinUI3;
using CommunityToolkit.Mvvm.Input;

namespace DevHome.Common.DevHomeAdaptiveCards.CardInterfaces;

/// <summary>
/// Represents an action that can be invoked from a Dev Home settings card that does not submit the card. E.g launches a dialog.
/// </summary>
public interface IDevHomeSettingsCardNonSubmitAction : IAdaptiveCardElement
bbonaby marked this conversation as resolved.
Show resolved Hide resolved
{
/// <summary>
/// Gets the text that is displayed on the action element. E.g button text.
/// </summary>
public string ActionText { get; }

/// <summary>
/// Invokes the action through a relay command
/// </summary>
/// <param name="sender">The UI object that the command originated from</param>
[RelayCommand]
public Task InvokeActionAsync(object sender);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Collections.Generic;
using AdaptiveCards.ObjectModel.WinUI3;
using Windows.Data.Json;

namespace DevHome.Common.DevHomeAdaptiveCards.CardModels;

/// <summary>
/// Represents a content dialog that can be rendered through an adaptive card based on the
/// Json template.
/// </summary>
public class DevHomeContentDialogContent : IAdaptiveCardElement
{
// Specific properties for DevHomeContentDialogContent
public string Title { get; set; } = string.Empty;

// This is the adaptive card that will be shown within
// a content dialogs body.
public JsonObject? ContentDialogInternalAdaptiveCardJson { get; set; }

public string PrimaryButtonText { get; set; } = string.Empty;

public string SecondaryButtonText { get; set; } = string.Empty;

public static string AdaptiveElementType => "DevHome.ContentDialogContent";

// Properties for IAdaptiveCardElement
public string ElementTypeString { get; set; } = AdaptiveElementType;

public JsonObject AdditionalProperties { get; set; } = new();

public ElementType ElementType { get; set; } = ElementType.Custom;

public IAdaptiveCardElement? FallbackContent { get; set; }

public FallbackType FallbackType { get; set; }

public HeightType Height { get; set; } = HeightType.Stretch;

public string Id { get; set; } = string.Empty;

public bool IsVisible { get; set; } = true;

public IList<AdaptiveRequirement> Requirements { get; set; } = new List<AdaptiveRequirement>();

public bool Separator { get; set; }

public Spacing Spacing { get; set; } = Spacing.Default;

public JsonObject? ToJson() => [];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using AdaptiveCards.ObjectModel.WinUI3;
using CommunityToolkit.Mvvm.Input;
using DevHome.Common.DevHomeAdaptiveCards.CardInterfaces;
using DevHome.Common.Views.AdaptiveCardViews;
using Microsoft.UI.Xaml.Controls;
using Windows.Data.Json;

namespace DevHome.Common.DevHomeAdaptiveCards.CardModels;

public partial class DevHomeLaunchContentDialogButton : IDevHomeSettingsCardNonSubmitAction
{
// Specific properties for DevHomeLaunchContentDialogButton
public string ActionText { get; set; } = string.Empty;

public IAdaptiveCardElement? DialogContent { get; set; }

public static string AdaptiveElementType => "DevHome.LaunchContentDialogButton";

// Properties for IAdaptiveActionElement
public string ElementTypeString { get; set; } = AdaptiveElementType;

public JsonObject AdditionalProperties { get; set; } = new();

public ElementType ElementType { get; set; } = ElementType.Custom;

public IAdaptiveCardElement? FallbackContent { get; set; }

public FallbackType FallbackType { get; set; }

public HeightType Height { get; set; } = HeightType.Stretch;

public string Id { get; set; } = string.Empty;

public bool IsVisible { get; set; } = true;

public IList<AdaptiveRequirement> Requirements { get; set; } = new List<AdaptiveRequirement>();

public bool Separator { get; set; }

public Spacing Spacing { get; set; } = Spacing.Default;

public JsonObject? ToJson() => [];

[RelayCommand]
public async Task InvokeActionAsync(object sender)
{
var senderObj = sender as Button;
if (DialogContent is not DevHomeContentDialogContent dialogContent || senderObj == null)
{
return;
}

var dialog = new ContentDialogWithNonInteractiveContent(dialogContent);

dialog.XamlRoot = senderObj.XamlRoot;

await dialog.ShowAsync();
}
}
66 changes: 66 additions & 0 deletions common/DevHomeAdaptiveCards/CardModels/DevHomeSettingsCard.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Collections.Generic;
using System.Text.Json.Serialization;
using AdaptiveCards.ObjectModel.WinUI3;
using DevHome.Common.DevHomeAdaptiveCards.CardInterfaces;
using Microsoft.UI.Xaml.Controls;
using Windows.Data.Json;

namespace DevHome.Common.DevHomeAdaptiveCards.CardModels;

/// <summary>
/// Represents a settings card that can be rendered through an adaptive card based on the ElementTypeString.
/// </summary>
public class DevHomeSettingsCard : IDevHomeSettingsCard
{
// Specific properties for DevHomeSettingsCard
// These properties relate to the Windows Community Toolkit's SettingsCard control.
// We'll allow extensions to provide the data for the SettingsCard control from an Adaptive Card.
// Then we'll render the actual SettingsCard control in the DevHome app.
/// <inheritdoc cref="IDevHomeSettingsCard.Description"/>
public string Description { get; set; } = string.Empty;

/// <inheritdoc cref="IDevHomeSettingsCard.Header"/>
public string Header { get; set; } = string.Empty;

/// <inheritdoc cref="IDevHomeSettingsCard.HeaderIcon"/>
public string HeaderIcon { get; set; } = string.Empty;
bbonaby marked this conversation as resolved.
Show resolved Hide resolved

[JsonIgnore]
public ImageIcon? HeaderIconImage { get; set; }

/// <inheritdoc cref="IDevHomeSettingsCardNonSubmitAction"/>
/// Gets or sets the element that does not submit the card.
public IDevHomeSettingsCardNonSubmitAction? NonSubmitActionElement { get; set; }

public IAdaptiveActionElement? SubmitActionElement { get; set; }

public static string AdaptiveElementType => "DevHome.SettingsCard";

// Properties for IAdaptiveCardElement
public string ElementTypeString { get; set; } = AdaptiveElementType;

public JsonObject AdditionalProperties { get; set; } = new();

public ElementType ElementType { get; set; } = ElementType.Custom;

public IAdaptiveCardElement? FallbackContent { get; set; }

public FallbackType FallbackType { get; set; }

public HeightType Height { get; set; } = HeightType.Stretch;

public string Id { get; set; } = string.Empty;

public bool IsVisible { get; set; } = true;

public IList<AdaptiveRequirement> Requirements { get; set; } = new List<AdaptiveRequirement>();

public bool Separator { get; set; }

public Spacing Spacing { get; set; } = Spacing.Default;

public JsonObject? ToJson() => [];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Collections.Generic;
using AdaptiveCards.ObjectModel.WinUI3;
using Windows.Data.Json;

namespace DevHome.Common.DevHomeAdaptiveCards.CardModels;

public class DevHomeSettingsCardChoiceSet : IAdaptiveCardElement, IAdaptiveInputElement
{
// Specific properties for DevHomeAdaptiveSettingsCardItemsViewChoiceSet
public IList<DevHomeSettingsCard> SettingsCards { get; set; } = [];

public const int UnselectedIndex = -1;

public bool IsSelectionDisabled { get; set; }

public static string AdaptiveElementType => "DevHome.SettingsCardChoiceSet";

public bool IsMultiSelect { get; set; }

public int SelectedValue { get; set; } = UnselectedIndex;

// Specific properties for IAdaptiveInputElement
public ElementType ElementType { get; set; } = ElementType.Custom;

public string ElementTypeString { get; set; } = AdaptiveElementType;

// Specific properties for IAdaptiveCardElement
public string ErrorMessage { get; set; } = string.Empty;

public bool IsRequired { get; set; }

public string Label { get; set; } = string.Empty;

public JsonObject AdditionalProperties { get; set; } = new();

public IAdaptiveCardElement? FallbackContent { get; set; }

public FallbackType FallbackType { get; set; }

public HeightType Height { get; set; } = HeightType.Stretch;

public string Id { get; set; } = string.Empty;

public bool IsVisible { get; set; } = true;

public IList<AdaptiveRequirement> Requirements { get; set; } = new List<AdaptiveRequirement>();

public bool Separator { get; set; }

public Spacing Spacing { get; set; } = Spacing.Default;

public JsonObject? ToJson() => [];
}
58 changes: 58 additions & 0 deletions common/DevHomeAdaptiveCards/InputValues/ItemsViewInputValue.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Globalization;
using AdaptiveCards.ObjectModel.WinUI3;
using AdaptiveCards.Rendering.WinUI3;
using DevHome.Common.Environments.Helpers;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;

namespace DevHome.Common.DevHomeAdaptiveCards.InputValues;

/// <summary>
/// Represents the current value of an ItemsView input element.
/// </summary>
public class ItemsViewInputValue : IAdaptiveInputValue
{
private readonly ItemsView _itemsView;

public ItemsViewInputValue(IAdaptiveInputElement input, ItemsView itemsView)
{
InputElement = input;
_itemsView = itemsView;
}

/// <summary>
/// Gets the current value of the input element. This is the index of the current item.
/// </summary>
public string CurrentValue => _itemsView.CurrentItemIndex.ToString(CultureInfo.InvariantCulture);

public UIElement? ErrorMessage { get; set; }

public IAdaptiveInputElement InputElement { get; set; }

public void SetFocus()
{
_itemsView.Focus(FocusState.Keyboard);
}

// If the items view selection mode isn't None, then the user must select an item.
public bool Validate()
{
if (_itemsView.SelectionMode == ItemsViewSelectionMode.None)
{
return true;
}

if (_itemsView.SelectedItem == null || _itemsView.CurrentItemIndex < 0)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coding guidelines: "Use parentheses to make clauses in an expression apparent".
This is in Windows C++ guidelines and in C# Common code conventions.

{
var errorMessage = StringResourceHelper.GetResource("ItemsViewNonSelectedItemError");
ErrorMessage = new TextBlock();
ErrorMessage.SetValue(TextBlock.TextProperty, errorMessage);
return false;
}

return true;
}
}
Loading
Loading