├── .github
├── CODE_OF_CONDUCT.md
├── ISSUE_TEMPLATE
│ └── bug_report.md
└── workflows
│ ├── ci.yml
│ └── format.yml
├── .gitignore
├── .spi.yml
├── .swift-version
├── .swiftpm
└── xcode
│ ├── package.xcworkspace
│ └── contents.xcworkspacedata
│ └── xcshareddata
│ └── xcschemes
│ ├── AdaptiveCard.xcscheme
│ ├── AdaptiveCardUI-Package-watchOS.xcscheme
│ ├── AdaptiveCardUI-Package.xcscheme
│ └── AdaptiveCardUI.xcscheme
├── Examples
└── AdaptiveCardVisualizer
│ ├── AdaptiveCardVisualizer.xcodeproj
│ ├── project.pbxproj
│ └── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── swiftpm
│ │ └── Package.resolved
│ ├── Cards
│ ├── ActivityUpdate.json
│ ├── FlightDetails.json
│ ├── FlightItinerary.json
│ ├── FlightUpdate.json
│ ├── GitHubRepository.json
│ ├── Photos.json
│ ├── SportingEvent.json
│ ├── StockUpdate.json
│ ├── WeatherCompact.json
│ └── WeatherLarge.json
│ ├── Prism
│ ├── default.css
│ ├── okaidia.css
│ └── prism.js
│ ├── Screenshot.png
│ ├── ScreenshotMac.png
│ ├── Shared
│ ├── AdaptiveCardVisualizerApp.swift
│ ├── Assets.xcassets
│ │ ├── AccentColor.colorset
│ │ │ └── Contents.json
│ │ ├── AppIcon.appiconset
│ │ │ └── Contents.json
│ │ └── Contents.json
│ ├── JSONView+HTMLString.swift
│ ├── RoundedImageStyle.swift
│ ├── SampleCard.swift
│ ├── SampleCardList.swift
│ ├── SampleCardPreview.swift
│ └── SampleCardView.swift
│ ├── iOS
│ ├── Info.plist
│ └── JSONView.swift
│ └── macOS
│ ├── Info.plist
│ ├── JSONView.swift
│ └── macOS.entitlements
├── LICENSE
├── Makefile
├── Package.resolved
├── Package.swift
├── README.md
├── Sources
├── AdaptiveCard
│ ├── Actions
│ │ ├── Action.swift
│ │ ├── ActionProtocol.swift
│ │ ├── ActionStyle.swift
│ │ ├── OpenURLAction.swift
│ │ ├── ShowCardAction.swift
│ │ ├── SubmitAction.swift
│ │ ├── TargetElement.swift
│ │ ├── ToggleVisibilityAction.swift
│ │ └── UnknownAction.swift
│ ├── AdaptiveCard.swift
│ ├── Common
│ │ ├── BackgroundImage.swift
│ │ ├── BlockElementHeight.swift
│ │ ├── ContainerStyle.swift
│ │ ├── FontSize.swift
│ │ ├── FontType.swift
│ │ ├── FontWeight.swift
│ │ ├── HAlignment.swift
│ │ ├── ImageFillMode.swift
│ │ ├── ImageSize.swift
│ │ ├── ImageStyle.swift
│ │ ├── ItemIdentifier.swift
│ │ ├── PixelDimension.swift
│ │ ├── SemanticVersion.swift
│ │ ├── Spacing.swift
│ │ ├── TextColor.swift
│ │ └── VAlignment.swift
│ ├── Containers
│ │ ├── ActionSet.swift
│ │ ├── ColumnSet.swift
│ │ ├── Container.swift
│ │ ├── Fact.swift
│ │ ├── FactSet.swift
│ │ └── ImageSet.swift
│ └── Elements
│ │ ├── CardElement.swift
│ │ ├── CardElementProtocol.swift
│ │ ├── Column.swift
│ │ ├── ColumnSetElement.swift
│ │ ├── ColumnSetElementProtocol.swift
│ │ ├── ColumnWidth.swift
│ │ ├── CustomCardElement.swift
│ │ ├── Fallback.swift
│ │ ├── Image.swift
│ │ ├── RichTextBlock.swift
│ │ ├── TextBlock.swift
│ │ ├── TextRun.swift
│ │ ├── UnknownCardElement.swift
│ │ └── UnknownColumnSetElement.swift
└── AdaptiveCardUI
│ ├── Configuration
│ ├── ActionSetConfiguration.swift
│ ├── AdaptiveCardConfiguration.swift
│ ├── Color+Configuration.swift
│ ├── ContainerConfiguration.swift
│ ├── ContainerStyleConfiguration.swift
│ ├── EnvironmentValues+Configuration.swift
│ ├── FactSetConfiguration.swift
│ ├── FontConfiguration.swift
│ ├── FontTypeConfiguration.swift
│ ├── ImageSizeConfiguration.swift
│ ├── ShowCardConfiguration.swift
│ ├── SpacingConfiguration.swift
│ ├── TextBlockConfiguration.swift
│ ├── TextColorConfiguration.swift
│ ├── TextColorPair.swift
│ └── View+Configuration.swift
│ ├── Internal
│ └── Exports.swift
│ ├── Logic
│ ├── FeatureAdaptation
│ │ ├── Action+FeatureAdaptable.swift
│ │ ├── AdaptiveCard+FeatureAdaptable.swift
│ │ ├── CardElement+FeatureAdaptable.swift
│ │ ├── ColumnSetElement+FeatureAdaptable.swift
│ │ ├── Fallback+FeatureAdaptable.swift
│ │ └── FeatureAdaptable.swift
│ ├── Store
│ │ ├── AdaptiveCard+DuplicateIdentifiers.swift
│ │ ├── AdaptiveCard+ImageURLs.swift
│ │ ├── AdaptiveCardCache.swift
│ │ ├── AdaptiveCardDownloader.swift
│ │ ├── AdaptiveCardStore.swift
│ │ └── ImmediateAdaptiveCardCache.swift
│ └── ToggleVisibility
│ │ ├── AdaptiveCard+Toggleable.swift
│ │ ├── CardElement+Toggleable.swift
│ │ ├── ColumnSetElement+Toggleable.swift
│ │ ├── Image+Toggleable.swift
│ │ └── Toggleable.swift
│ ├── UI
│ ├── ActionSet
│ │ ├── ActionSetView.swift
│ │ ├── ActionView.swift
│ │ ├── CapsuleButtonModifier.swift
│ │ └── CapsuleButtonStyle.swift
│ ├── AdaptiveCard
│ │ ├── AdaptiveCardFeatures.swift
│ │ ├── AdaptiveCardSourceView.swift
│ │ ├── AdaptiveCardView.swift
│ │ ├── ErrorView.swift
│ │ └── PrimitiveCardView.swift
│ ├── BackgroundImage
│ │ ├── BackgroundImageStyle.swift
│ │ ├── BackgroundImageView.swift
│ │ ├── CoverImageView.swift
│ │ ├── RepeatHorizontallyImageView.swift
│ │ └── RepeatVerticallyImageView.swift
│ ├── CardElement
│ │ ├── CardElementList.swift
│ │ ├── CardElementView.swift
│ │ ├── CustomCardElementView.swift
│ │ └── SpacingCardElementView.swift
│ ├── Color
│ │ └── Color+ARGBHex.swift
│ ├── ColumnSet
│ │ ├── ColumnSetView.swift
│ │ ├── ColumnView.swift
│ │ └── SpacingColumnView.swift
│ ├── Container
│ │ ├── ContainerView.swift
│ │ └── EnvironmentValues+ContainerStyle.swift
│ ├── FactSet
│ │ └── FactSetView.swift
│ ├── Helpers
│ │ ├── Alignment+VAlignment.swift
│ │ ├── BlockElementHeight+CGFloat.swift
│ │ ├── CollectSizeModifier.swift
│ │ ├── FlowLayout.swift
│ │ ├── HAlign.swift
│ │ ├── HAlignment+Offset.swift
│ │ ├── Padded.swift
│ │ ├── PixelDimension+CGFloat.swift
│ │ ├── SelectActionModifier.swift
│ │ └── VAlignment+Offset.swift
│ ├── Image
│ │ ├── AutoImageView.swift
│ │ ├── ContentSizeKey.swift
│ │ ├── CustomImageStyle.swift
│ │ ├── FixedSizeImageView.swift
│ │ ├── ImageSetView.swift
│ │ ├── ImageSizeImageView.swift
│ │ ├── ImageStyleView.swift
│ │ ├── ImageView.swift
│ │ └── PrimitiveImageStyle.swift
│ └── Text
│ │ ├── Font.Design+FontType.swift
│ │ ├── Font.Weight+FontWeight.swift
│ │ ├── RichTextBlockView.swift
│ │ ├── Text+Parsing.swift
│ │ ├── TextAlignment+HAlignment.swift
│ │ ├── TextBlockView.swift
│ │ └── TextElement.swift
│ └── en.lproj
│ └── Localizable.strings
└── Tests
├── AdaptiveCardTests
├── ActionTests.swift
├── BackgroundImageTests.swift
├── BlockElementHeightTests.swift
├── CardElementTests.swift
├── ColumnSetElementTests.swift
├── ColumnWidthTests.swift
├── PixelDimensionTests.swift
├── SemanticVersionTests.swift
├── TargetElementTests.swift
└── TextRunTests.swift
└── AdaptiveCardUITests
├── Configuration
├── ActionSetConfigurationTests.swift
├── FontConfigurationTests.swift
├── ImageSizeConfigurationTests.swift
├── ShowCardConfigurationTests.swift
└── SpacingConfigurationTests.swift
├── Logic
├── AdaptiveCardDuplicateIdentifiersTests.swift
├── AdaptiveCardFeatureAdaptableTests.swift
├── AdaptiveCardImageURLsTests.swift
├── AdaptiveCardStoreTests.swift
└── AdaptiveCardToggleVisibilityTests.swift
└── UI
├── ActionRenderingTests.swift
├── AdaptiveCardConfiguration+Test.swift
├── ColorTests.swift
├── ColumnSetRenderingTests.swift
├── ContainerRenderingTests.swift
├── CustomCardElementRenderingTests.swift
├── CustomCardElements
├── RepoLanguage.swift
├── RepoLanguageView.swift
├── StarCount.swift
└── StarCountView.swift
├── FactSetRenderingTests.swift
├── ImageRenderingTests.swift
├── ImageSetRenderingTests.swift
├── RichTextBlockRenderingTests.swift
├── TestHelpers.swift
├── TextBlockRenderingTests.swift
├── TextElementTests.swift
├── __Fixtures__
├── actions.json
├── columnSetBackgroundImage.json
├── columnSetBleed.json
├── columnSetMinHeight.json
├── columnSetStyle.json
├── columnSetVerticalContentAlignment.json
├── columnSetWidth.json
├── containerBackgroundImage.json
├── containerBleed.json
├── containerMinHeight.json
├── containerStyle.json
├── containerVerticalContentAlignment.json
├── customCardElements.json
├── factSet.json
├── image.json
├── imageBackgroundColor.json
├── imageCustomStyle.json
├── imageHorizontalAlignment.json
├── imageSet.json
├── imageSize.json
├── imageWidthHeight.json
├── richTextBlock.json
├── textBlockColor.json
├── textBlockDateAndTime.json
├── textBlockFontType.json
├── textBlockHorizontalAlignment.json
├── textBlockIsSubtle.json
├── textBlockMaxLines.json
├── textBlockSize.json
├── textBlockWeight.json
└── textBlockWrap.json
└── __Snapshots__
├── ActionRenderingTests
├── testHorizontalButtonSpacing.1.png
├── testHorizontalCenterActions.1.png
├── testHorizontalLeftActions.1.png
├── testHorizontalRightActions.1.png
├── testHorizontalStretchActions.1.png
├── testMaxActions.1.png
├── testSpacing.1.png
├── testVerticalButtonSpacing.1.png
├── testVerticalCenterActions.1.png
├── testVerticalLeftActions.1.png
└── testVerticalRightActions.1.png
├── ColumnSetRenderingTests
├── testBackgroundImage.1.png
├── testBleed.1.png
├── testMinHeight.1.png
├── testStyle.1.png
├── testVerticalContentAlignment.1.png
└── testWidth.1.png
├── ContainerRenderingTests
├── testBackgroundImage.1.png
├── testBleed.1.png
├── testMinHeight.1.png
├── testStyle.1.png
└── testVerticalContentAlignment.1.png
├── CustomCardElementRenderingTests
└── testCustomCardElements.1.png
├── FactSetRenderingTests
└── testFactSet.1.png
├── ImageRenderingTests
├── testBackgroundColor.1.png
├── testCustomStyle.1.png
├── testHorizontalAlignment.1.png
├── testImage.1.png
├── testOverridingCustomStyle.1.png
├── testSize.1.png
└── testWidthHeight.1.png
├── ImageSetRenderingTests
└── testImageSet.1.png
├── RichTextBlockRenderingTests
└── testRichTextBlock.1.png
└── TextBlockRenderingTests
├── testColor.1.png
├── testFontType.1.png
├── testHorizontalAlignment.1.png
├── testIsSubtle.1.png
├── testMaxLines.1.png
├── testSize.1.png
├── testWeight.1.png
└── testWrap.1.png
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Enter the Adaptive Card JSON that reproduces the behaviour:
15 |
16 | ```json
17 | {
18 | "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
19 | "type": "AdaptiveCard",
20 | "version": "1.3",
21 | "body": [
22 |
23 | ],
24 | "actions": [
25 |
26 | ]
27 | }
28 | ```
29 |
30 | Or zip up a project that reproduces the behavior and attach it by dragging it here.
31 |
32 | **Expected behavior**
33 | A clear and concise description of what you expected to happen.
34 |
35 | **Screenshots**
36 | If applicable, add screenshots to help explain your problem.
37 |
38 | **Environment**
39 | - Xcode [e.g. 12.0]
40 | - Swift [e.g. 5.3]
41 | - OS (if applicable): [e.g. iOS 14]
42 |
43 | **Additional context**
44 | Add any other context about the problem here.
45 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 | on:
3 | push:
4 | branches:
5 | - main
6 | pull_request:
7 | branches:
8 | - '*'
9 | jobs:
10 | tests:
11 | runs-on: macOS-11.0
12 | steps:
13 | - uses: actions/checkout@v2
14 | - name: Select Xcode 12.3
15 | run: sudo xcode-select -s /Applications/Xcode_12.3.app
16 | - name: Run tests
17 | run: make test
18 |
--------------------------------------------------------------------------------
/.github/workflows/format.yml:
--------------------------------------------------------------------------------
1 | name: SwiftFormat
2 | on:
3 | push:
4 | branches:
5 | - main
6 | jobs:
7 | format:
8 | name: SwiftFormat
9 | runs-on: macOS-latest
10 | steps:
11 | - uses: actions/checkout@v2
12 | - name: Install
13 | run: brew install swiftformat
14 | - name: Format
15 | run: make format
16 | - uses: stefanzweifel/git-auto-commit-action@v4
17 | with:
18 | commit_message: Run swiftformat
19 | branch: 'main'
20 | env:
21 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
22 |
--------------------------------------------------------------------------------
/.spi.yml:
--------------------------------------------------------------------------------
1 | version: 1
2 | builder:
3 | configs:
4 | - platform: watchos
5 | scheme: AdaptiveCardUI-Package-watchOS
6 |
--------------------------------------------------------------------------------
/.swift-version:
--------------------------------------------------------------------------------
1 | 5.3
2 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Examples/AdaptiveCardVisualizer/AdaptiveCardVisualizer.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Examples/AdaptiveCardVisualizer/AdaptiveCardVisualizer.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Examples/AdaptiveCardVisualizer/AdaptiveCardVisualizer.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "object": {
3 | "pins": [
4 | {
5 | "package": "AnyValue",
6 | "repositoryURL": "https://github.com/gonzalezreal/AnyValue",
7 | "state": {
8 | "branch": null,
9 | "revision": "15a662df9f5bb6c7aa45839acb2137da99a22665",
10 | "version": "1.0.0"
11 | }
12 | },
13 | {
14 | "package": "combine-schedulers",
15 | "repositoryURL": "https://github.com/pointfreeco/combine-schedulers",
16 | "state": {
17 | "branch": null,
18 | "revision": "ff42ec9061d864de7982162011321d3df5080c10",
19 | "version": "0.1.2"
20 | }
21 | },
22 | {
23 | "package": "DefaultCodable",
24 | "repositoryURL": "https://github.com/gonzalezreal/DefaultCodable",
25 | "state": {
26 | "branch": null,
27 | "revision": "7417376ad1228c3ac5c2988770c9fe97ca667a57",
28 | "version": "1.2.0"
29 | }
30 | },
31 | {
32 | "package": "NetworkImage",
33 | "repositoryURL": "https://github.com/gonzalezreal/NetworkImage",
34 | "state": {
35 | "branch": null,
36 | "revision": "de0632e94acbebe35d78e2f1670398a18f8ac243",
37 | "version": "1.1.1"
38 | }
39 | },
40 | {
41 | "package": "SnapshotTesting",
42 | "repositoryURL": "https://github.com/pointfreeco/swift-snapshot-testing",
43 | "state": {
44 | "branch": null,
45 | "revision": "c466812aa2e22898f27557e2e780d3aad7a27203",
46 | "version": "1.8.2"
47 | }
48 | }
49 | ]
50 | },
51 | "version": 1
52 | }
53 |
--------------------------------------------------------------------------------
/Examples/AdaptiveCardVisualizer/Cards/Photos.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
3 | "type": "AdaptiveCard",
4 | "version": "1.0",
5 | "body": [
6 | {
7 | "type": "TextBlock",
8 | "text": "Here are some cool photos",
9 | "size": "large"
10 | },
11 | {
12 | "type": "TextBlock",
13 | "text": "from picsum.photos",
14 | "size": "medium",
15 | "weight": "lighter",
16 | "spacing": "none"
17 | },
18 | {
19 | "type": "ImageSet",
20 | "images": [
21 | {
22 | "type": "Image",
23 | "url": "https://picsum.photos/200/200?image=100",
24 | "altText": "White beach panorama"
25 | },
26 | {
27 | "type": "Image",
28 | "url": "https://picsum.photos/300/200?image=200",
29 | "altText": "Cow on a grassy field"
30 | },
31 | {
32 | "type": "Image",
33 | "url": "https://picsum.photos/300/200?image=301",
34 | "altText": "Orange leaves on the sidewalk of a park"
35 | },
36 | {
37 | "type": "Image",
38 | "url": "https://picsum.photos/200/200?image=400",
39 | "altText": "Green leaves"
40 | },
41 | {
42 | "type": "Image",
43 | "url": "https://picsum.photos/300/200?image=500",
44 | "altText": "Top of a sky scrapper"
45 | },
46 | {
47 | "type": "Image",
48 | "url": "https://picsum.photos/200/200?image=600",
49 | "altText": "Foggy forest"
50 | },
51 | {
52 | "type": "Image",
53 | "url": "https://picsum.photos/300/200?image=700",
54 | "altText": "Picure of the blue ocean"
55 | },
56 | {
57 | "type": "Image",
58 | "url": "https://picsum.photos/300/200?image=800",
59 | "altText": "Crowded train station"
60 | },
61 | {
62 | "type": "Image",
63 | "url": "https://picsum.photos/300/200?image=900",
64 | "altText": "Sunset under a dock"
65 | }
66 | ]
67 | }
68 | ]
69 | }
70 |
--------------------------------------------------------------------------------
/Examples/AdaptiveCardVisualizer/Cards/WeatherCompact.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
3 | "type": "AdaptiveCard",
4 | "version": "1.0",
5 | "speak": "The forecast for Seattle January 20 is mostly clear with a High of 51 degrees and Low of 40 degrees",
6 | "body": [
7 | {
8 | "type": "TextBlock",
9 | "text": "Redmond, WA",
10 | "size": "large",
11 | "isSubtle": true
12 | },
13 | {
14 | "type": "TextBlock",
15 | "text": "Mon, Nov 4, 2019 6:21 PM",
16 | "spacing": "none"
17 | },
18 | {
19 | "type": "ColumnSet",
20 | "columns": [
21 | {
22 | "type": "Column",
23 | "width": "auto",
24 | "items": [
25 | {
26 | "type": "Image",
27 | "url": "https://messagecardplayground.azurewebsites.net/assets/Mostly%20Cloudy-Square.png",
28 | "size": "small",
29 | "altText": "Mostly cloudy weather"
30 | }
31 | ]
32 | },
33 | {
34 | "type": "Column",
35 | "width": "auto",
36 | "items": [
37 | {
38 | "type": "TextBlock",
39 | "text": "46",
40 | "size": "extraLarge",
41 | "spacing": "none"
42 | }
43 | ]
44 | },
45 | {
46 | "type": "Column",
47 | "width": "stretch",
48 | "items": [
49 | {
50 | "type": "TextBlock",
51 | "text": "°F",
52 | "weight": "bolder",
53 | "spacing": "small"
54 | }
55 | ]
56 | },
57 | {
58 | "type": "Column",
59 | "width": "stretch",
60 | "items": [
61 | {
62 | "type": "TextBlock",
63 | "text": "Hi 50",
64 | "horizontalAlignment": "left"
65 | },
66 | {
67 | "type": "TextBlock",
68 | "text": "Lo 41",
69 | "horizontalAlignment": "left",
70 | "spacing": "none"
71 | }
72 | ]
73 | }
74 | ]
75 | }
76 | ]
77 | }
78 |
--------------------------------------------------------------------------------
/Examples/AdaptiveCardVisualizer/Screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gonzalezreal/AdaptiveCardUI/4ac1a3c0c14cb951fb530712de7e4ae832573bf3/Examples/AdaptiveCardVisualizer/Screenshot.png
--------------------------------------------------------------------------------
/Examples/AdaptiveCardVisualizer/ScreenshotMac.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gonzalezreal/AdaptiveCardUI/4ac1a3c0c14cb951fb530712de7e4ae832573bf3/Examples/AdaptiveCardVisualizer/ScreenshotMac.png
--------------------------------------------------------------------------------
/Examples/AdaptiveCardVisualizer/Shared/AdaptiveCardVisualizerApp.swift:
--------------------------------------------------------------------------------
1 | import AdaptiveCardUI
2 | import SwiftUI
3 |
4 | @main
5 | struct AdaptiveCardVisualizerApp: App {
6 | var body: some Scene {
7 | WindowGroup {
8 | NavigationView {
9 | SampleCardList(sampleCards: SampleCard.all)
10 | }
11 | .onAppear {
12 | // Register custom elements
13 | CardElement.register(StarCount.self)
14 | CardElement.register(RepoLanguage.self)
15 | }
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Examples/AdaptiveCardVisualizer/Shared/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Examples/AdaptiveCardVisualizer/Shared/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Examples/AdaptiveCardVisualizer/Shared/JSONView+HTMLString.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | extension JSONView {
4 | private enum Constants {
5 | static let htmlFormat = """
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | %@
16 |
17 |
18 |
19 |
20 | """
21 | }
22 |
23 | func htmlString(with json: String, colorScheme: ColorScheme) -> String {
24 | let css: String
25 |
26 | switch colorScheme {
27 | case .dark:
28 | css = "okaidia.css"
29 | default:
30 | css = "default.css"
31 | }
32 |
33 | return String(format: Constants.htmlFormat, css, json)
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Examples/AdaptiveCardVisualizer/Shared/RoundedImageStyle.swift:
--------------------------------------------------------------------------------
1 | import AdaptiveCardUI
2 | import SwiftUI
3 |
4 | struct RoundedImageStyle: CustomImageStyle {
5 | func makeBody(content: Content) -> some View {
6 | content.clipShape(RoundedRectangle(cornerRadius: 4))
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/Examples/AdaptiveCardVisualizer/Shared/SampleCardList.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | struct SampleCardList: View {
4 | @State private var selection: SampleCard?
5 |
6 | let sampleCards: [SampleCard]
7 |
8 | var body: some View {
9 | List(selection: $selection) {
10 | ForEach(sampleCards) { sampleCard in
11 | NavigationLink(
12 | destination: SampleCardView(sampleCard: sampleCard),
13 | tag: sampleCard,
14 | selection: $selection
15 | ) {
16 | Text(sampleCard.title)
17 | }
18 | .tag(sampleCard)
19 | }
20 | }
21 | .navigationTitle("Adaptive Cards")
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Examples/AdaptiveCardVisualizer/Shared/SampleCardPreview.swift:
--------------------------------------------------------------------------------
1 | import AdaptiveCardUI
2 | import SwiftUI
3 |
4 | struct SampleCardPreview: View {
5 | private enum Constants {
6 | static let cardCornerRadius: CGFloat = 4
7 | static let cardBorderColor = Color.primary.opacity(0.25)
8 | static let cardBorderWidth: CGFloat = 0.5
9 | static let maxCardWidth: CGFloat = 400
10 | }
11 |
12 | let resourceName: String
13 |
14 | var body: some View {
15 | if let url = Bundle.main.url(forResource: resourceName, withExtension: nil) {
16 | AdaptiveCardView(url: url)
17 | .clipShape(RoundedRectangle(cornerRadius: Constants.cardCornerRadius))
18 | .overlay(
19 | RoundedRectangle(cornerRadius: Constants.cardCornerRadius)
20 | .strokeBorder(Constants.cardBorderColor, lineWidth: Constants.cardBorderWidth)
21 | )
22 | .frame(maxWidth: Constants.maxCardWidth)
23 | .padding()
24 | .actionSetConfiguration(ActionSetConfiguration(actionsOrientation: .horizontal))
25 | .customCardElement { StarCountView($0) }
26 | .customCardElement { RepoLanguageView($0) }
27 | .onImageStyle(.default, apply: RoundedImageStyle())
28 | .buttonStyle(CapsuleButtonStyle())
29 | .animation(.default)
30 | }
31 | }
32 | }
33 |
34 | struct SampleCardPreview_Previews: PreviewProvider {
35 | static var previews: some View {
36 | SampleCardPreview(resourceName: SampleCard.activityUpdate.resourceName)
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Examples/AdaptiveCardVisualizer/Shared/SampleCardView.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | struct SampleCardView: View {
4 | private enum Segment {
5 | case preview, json
6 | }
7 |
8 | @State private var segment: Segment = .preview
9 |
10 | let sampleCard: SampleCard
11 |
12 | var body: some View {
13 | #if os(iOS)
14 | _body.navigationBarTitleDisplayMode(.inline)
15 | #else
16 | _body
17 | #endif
18 | }
19 |
20 | private var _body: some View {
21 | Group {
22 | switch segment {
23 | case .preview:
24 | ScrollView {
25 | SampleCardPreview(resourceName: sampleCard.resourceName)
26 | }
27 | case .json:
28 | JSONView(resourceName: sampleCard.resourceName)
29 | }
30 | }
31 | .toolbar {
32 | Picker(selection: $segment, label: EmptyView()) {
33 | Text("Preview").tag(Segment.preview)
34 | Text("JSON").tag(Segment.json)
35 | }
36 | .pickerStyle(SegmentedPickerStyle())
37 | }
38 | .navigationTitle(sampleCard.title)
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Examples/AdaptiveCardVisualizer/iOS/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UIApplicationSceneManifest
24 |
25 | UIApplicationSupportsMultipleScenes
26 |
27 |
28 | UIApplicationSupportsIndirectInputEvents
29 |
30 | UILaunchScreen
31 |
32 | UIRequiredDeviceCapabilities
33 |
34 | armv7
35 |
36 | UISupportedInterfaceOrientations
37 |
38 | UIInterfaceOrientationPortrait
39 | UIInterfaceOrientationLandscapeLeft
40 | UIInterfaceOrientationLandscapeRight
41 |
42 | UISupportedInterfaceOrientations~ipad
43 |
44 | UIInterfaceOrientationPortrait
45 | UIInterfaceOrientationPortraitUpsideDown
46 | UIInterfaceOrientationLandscapeLeft
47 | UIInterfaceOrientationLandscapeRight
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/Examples/AdaptiveCardVisualizer/iOS/JSONView.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import WebKit
3 |
4 | struct JSONView: UIViewRepresentable {
5 | let resourceName: String
6 |
7 | func makeUIView(context _: Context) -> WKWebView {
8 | let uiView = WKWebView()
9 | uiView.isOpaque = false
10 | uiView.backgroundColor = .clear
11 |
12 | return uiView
13 | }
14 |
15 | func updateUIView(_ uiView: WKWebView, context: Context) {
16 | guard let url = Bundle.main.url(forResource: resourceName, withExtension: nil),
17 | let data = try? Data(contentsOf: url),
18 | let json = String(data: data, encoding: .utf8)
19 | else {
20 | return
21 | }
22 |
23 | uiView.loadHTMLString(
24 | htmlString(with: json, colorScheme: context.environment.colorScheme),
25 | baseURL: Bundle.main.resourceURL
26 | )
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Examples/AdaptiveCardVisualizer/macOS/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIconFile
10 |
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleVersion
22 | 1
23 | LSMinimumSystemVersion
24 | $(MACOSX_DEPLOYMENT_TARGET)
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Examples/AdaptiveCardVisualizer/macOS/JSONView.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import WebKit
3 |
4 | struct JSONView: NSViewRepresentable {
5 | let resourceName: String
6 |
7 | func makeNSView(context _: Context) -> WKWebView {
8 | WKWebView()
9 | }
10 |
11 | func updateNSView(_ nsView: WKWebView, context: Context) {
12 | guard let url = Bundle.main.url(forResource: resourceName, withExtension: nil),
13 | let data = try? Data(contentsOf: url),
14 | let json = String(data: data, encoding: .utf8)
15 | else {
16 | return
17 | }
18 |
19 | nsView.loadHTMLString(
20 | htmlString(with: json, colorScheme: context.environment.colorScheme),
21 | baseURL: Bundle.main.resourceURL
22 | )
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Examples/AdaptiveCardVisualizer/macOS/macOS.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.files.user-selected.read-only
8 |
9 | com.apple.security.network.client
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2020 Guillermo Gonzalez
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | DESTINATION_MACOS = platform=macOS
2 | DESTINATION_IOS = platform=iOS Simulator,name=iPhone 8
3 | DESTINATION_IOS_SNAPSHOT = platform=iOS Simulator,OS=14.0,name=iPhone 8
4 | DESTINATION_TVOS = platform=tvOS Simulator,name=Apple TV
5 | DESTINATION_WATCHOS = generic/platform=watchOS
6 |
7 | default: test
8 |
9 | test: test-macos test-ios test-tvos watchos
10 |
11 | test-macos:
12 | xcodebuild test \
13 | -scheme AdaptiveCardUI-Package \
14 | -destination '$(DESTINATION_MACOS)'
15 |
16 | test-ios:
17 | xcodebuild test \
18 | -scheme AdaptiveCardUI-Package \
19 | -destination '$(DESTINATION_IOS)'
20 |
21 | snapshot-test-ios:
22 | xcodebuild test \
23 | -scheme AdaptiveCardUI-Package \
24 | -destination '$(DESTINATION_IOS_SNAPSHOT)' \
25 | 'OTHER_SWIFT_FLAGS=-D SNAPSHOT_TESTS'
26 |
27 | test-tvos:
28 | xcodebuild test \
29 | -scheme AdaptiveCardUI-Package \
30 | -destination '$(DESTINATION_TVOS)'
31 |
32 | watchos:
33 | xcodebuild \
34 | -scheme AdaptiveCardUI-Package-watchOS \
35 | -destination '$(DESTINATION_WATCHOS)'
36 |
37 | format:
38 | swiftformat .
39 |
40 | .PHONY: format
41 |
--------------------------------------------------------------------------------
/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "object": {
3 | "pins": [
4 | {
5 | "package": "AnyValue",
6 | "repositoryURL": "https://github.com/gonzalezreal/AnyValue",
7 | "state": {
8 | "branch": null,
9 | "revision": "15a662df9f5bb6c7aa45839acb2137da99a22665",
10 | "version": "1.0.0"
11 | }
12 | },
13 | {
14 | "package": "combine-schedulers",
15 | "repositoryURL": "https://github.com/pointfreeco/combine-schedulers",
16 | "state": {
17 | "branch": null,
18 | "revision": "ff42ec9061d864de7982162011321d3df5080c10",
19 | "version": "0.1.2"
20 | }
21 | },
22 | {
23 | "package": "DefaultCodable",
24 | "repositoryURL": "https://github.com/gonzalezreal/DefaultCodable",
25 | "state": {
26 | "branch": null,
27 | "revision": "7417376ad1228c3ac5c2988770c9fe97ca667a57",
28 | "version": "1.2.0"
29 | }
30 | },
31 | {
32 | "package": "NetworkImage",
33 | "repositoryURL": "https://github.com/gonzalezreal/NetworkImage",
34 | "state": {
35 | "branch": null,
36 | "revision": "de0632e94acbebe35d78e2f1670398a18f8ac243",
37 | "version": "1.1.1"
38 | }
39 | },
40 | {
41 | "package": "SnapshotTesting",
42 | "repositoryURL": "https://github.com/pointfreeco/swift-snapshot-testing",
43 | "state": {
44 | "branch": null,
45 | "revision": "c466812aa2e22898f27557e2e780d3aad7a27203",
46 | "version": "1.8.2"
47 | }
48 | }
49 | ]
50 | },
51 | "version": 1
52 | }
53 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.3
2 |
3 | import PackageDescription
4 |
5 | let package = Package(
6 | name: "AdaptiveCardUI",
7 | defaultLocalization: "en",
8 | platforms: [
9 | .macOS(.v10_12),
10 | .iOS(.v11),
11 | .tvOS(.v11),
12 | .watchOS(.v3),
13 | ],
14 | products: [
15 | .library(
16 | name: "AdaptiveCard",
17 | targets: ["AdaptiveCard"]
18 | ),
19 | .library(
20 | name: "AdaptiveCardUI",
21 | targets: ["AdaptiveCardUI"]
22 | ),
23 | ],
24 | dependencies: [
25 | .package(url: "https://github.com/gonzalezreal/DefaultCodable", from: "1.2.0"),
26 | .package(url: "https://github.com/gonzalezreal/AnyValue", from: "1.0.0"),
27 | .package(url: "https://github.com/gonzalezreal/NetworkImage", from: "1.1.1"),
28 | .package(url: "https://github.com/pointfreeco/combine-schedulers", from: "0.1.2"),
29 | .package(name: "SnapshotTesting", url: "https://github.com/pointfreeco/swift-snapshot-testing", from: "1.8.1"),
30 | ],
31 | targets: [
32 | .target(
33 | name: "AdaptiveCard",
34 | dependencies: [
35 | "DefaultCodable",
36 | "AnyValue",
37 | ]
38 | ),
39 | .testTarget(
40 | name: "AdaptiveCardTests",
41 | dependencies: [
42 | "AdaptiveCard",
43 | ]
44 | ),
45 | .target(
46 | name: "AdaptiveCardUI",
47 | dependencies: [
48 | "AdaptiveCard",
49 | "NetworkImage",
50 | .product(name: "CombineSchedulers", package: "combine-schedulers"),
51 | ]
52 | ),
53 | .testTarget(
54 | name: "AdaptiveCardUITests",
55 | dependencies: [
56 | "AdaptiveCardUI",
57 | "SnapshotTesting",
58 | ],
59 | exclude: ["UI/__Fixtures__", "UI/__Snapshots__"]
60 | ),
61 | ]
62 | )
63 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCard/Actions/ActionProtocol.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | /// A type that represents a card action.
4 | public protocol ActionProtocol {
5 | /// Label for button or link that represents this action.
6 | var title: String { get }
7 |
8 | /// Optional icon to be shown on the action in conjunction with the title.
9 | var iconURL: URL? { get }
10 |
11 | /// Controls the style of an Action, which influences how the action is displayed, spoken, etc.
12 | var style: ActionStyle { get }
13 |
14 | /// Describes what to do when an unknown action is encountered or the requires of this can’t be met.
15 | var fallback: Fallback { get }
16 |
17 | /// A series of key/value pairs indicating features that the action requires with corresponding minimum version.
18 | /// When a feature is missing or of insufficient version, fallback is triggered.
19 | var requires: [String: SemanticVersion] { get }
20 | }
21 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCard/Actions/ActionStyle.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | /// Controls the style of an Action, which influences how the action is displayed, spoken, etc.
4 | public struct ActionStyle: Codable, Hashable, RawRepresentable {
5 | public private(set) var rawValue: String
6 |
7 | public init(rawValue: String) {
8 | self.rawValue = rawValue
9 | }
10 | }
11 |
12 | extension ActionStyle: Equatable {
13 | public static func == (lhs: ActionStyle, rhs: ActionStyle) -> Bool {
14 | lhs.rawValue.caseInsensitiveCompare(rhs.rawValue) == .orderedSame
15 | }
16 | }
17 |
18 | public extension ActionStyle {
19 | static let `default` = ActionStyle(rawValue: "default")
20 | static let positive = ActionStyle(rawValue: "positive")
21 | static let destructive = ActionStyle(rawValue: "destructive")
22 | }
23 |
24 | extension ActionStyle: CaseIterable {
25 | public static var allCases: [ActionStyle] = [.default, .positive, .destructive]
26 | }
27 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCard/Actions/OpenURLAction.swift:
--------------------------------------------------------------------------------
1 | import DefaultCodable
2 | import Foundation
3 |
4 | /// When invoked, show the given url either by launching it in an external web browser or showing within an embedded web browser.
5 | public struct OpenURLAction: ActionProtocol, Codable, Equatable {
6 | /// The URL to open.
7 | public var url: URL
8 |
9 | /// Label for button or link that represents this action.
10 | @Default public var title: String
11 |
12 | /// Optional icon to be shown on the action in conjunction with the title.
13 | public var iconURL: URL?
14 |
15 | /// Controls the style of an Action, which influences how the action is displayed, spoken, etc.
16 | @Default public var style: ActionStyle
17 |
18 | /// Describes what to do when an unknown action is encountered or the requires of this can’t be met.
19 | @Default public var fallback: Fallback
20 |
21 | /// A series of key/value pairs indicating features that the action requires with corresponding minimum version.
22 | /// When a feature is missing or of insufficient version, fallback is triggered.
23 | @Default public var requires: [String: SemanticVersion]
24 |
25 | public init(
26 | url: URL,
27 | title: String = "",
28 | iconURL: URL? = nil,
29 | style: ActionStyle = .default,
30 | fallback: Fallback = .none,
31 | requires: [String: SemanticVersion] = [:]
32 | ) {
33 | self.url = url
34 | self.title = title
35 | self.iconURL = iconURL
36 | self.style = style
37 | self.fallback = fallback
38 | self.requires = requires
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCard/Actions/ShowCardAction.swift:
--------------------------------------------------------------------------------
1 | import DefaultCodable
2 | import Foundation
3 |
4 | /// Defines an AdaptiveCard which is shown to the user when the button or link is clicked.
5 | public struct ShowCardAction: ActionProtocol, Codable, Equatable {
6 | /// The Adaptive Card to show.
7 | public var card: AdaptiveCard
8 |
9 | /// Label for button or link that represents this action.
10 | @Default public var title: String
11 |
12 | /// Optional icon to be shown on the action in conjunction with the title.
13 | public var iconURL: URL?
14 |
15 | /// Controls the style of an Action, which influences how the action is displayed, spoken, etc.
16 | @Default public var style: ActionStyle
17 |
18 | /// Describes what to do when an unknown action is encountered or the requires of this can’t be met.
19 | @Default public var fallback: Fallback
20 |
21 | /// A series of key/value pairs indicating features that the action requires with corresponding minimum version.
22 | /// When a feature is missing or of insufficient version, fallback is triggered.
23 | @Default public var requires: [String: SemanticVersion]
24 |
25 | public init(
26 | card: AdaptiveCard,
27 | title: String = "",
28 | iconURL: URL? = nil,
29 | style: ActionStyle = .default,
30 | fallback: Fallback = .none,
31 | requires: [String: SemanticVersion] = [:]
32 | ) {
33 | self.card = card
34 | self.title = title
35 | self.iconURL = iconURL
36 | self.style = style
37 | self.fallback = fallback
38 | self.requires = requires
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCard/Actions/SubmitAction.swift:
--------------------------------------------------------------------------------
1 | import AnyValue
2 | import DefaultCodable
3 | import Foundation
4 |
5 | /// Gathers input fields, merges with optional data field, and sends an event to the client.
6 | /// It is up to the client to determine how this data is processed.
7 | public struct SubmitAction: ActionProtocol, Codable, Equatable {
8 | /// Initial data that input fields will be combined with.
9 | @Default public var data: [String: AnyValue]
10 |
11 | /// Label for button or link that represents this action.
12 | @Default public var title: String
13 |
14 | /// Optional icon to be shown on the action in conjunction with the title.
15 | public var iconURL: URL?
16 |
17 | /// Controls the style of an Action, which influences how the action is displayed, spoken, etc.
18 | @Default public var style: ActionStyle
19 |
20 | /// Describes what to do when an unknown action is encountered or the requires of this can’t be met.
21 | @Default public var fallback: Fallback
22 |
23 | /// A series of key/value pairs indicating features that the action requires with corresponding minimum version.
24 | /// When a feature is missing or of insufficient version, fallback is triggered.
25 | @Default public var requires: [String: SemanticVersion]
26 |
27 | public init(
28 | data: [String: AnyValue] = [:],
29 | title: String = "",
30 | iconURL: URL? = nil,
31 | style: ActionStyle = .default,
32 | fallback: Fallback = .none,
33 | requires: [String: SemanticVersion] = [:]
34 | ) {
35 | self.data = data
36 | self.title = title
37 | self.iconURL = iconURL
38 | self.style = style
39 | self.fallback = fallback
40 | self.requires = requires
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCard/Actions/TargetElement.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | /// Represents an entry for `Action.ToggleVisibility`'s targetElements property.
4 | public struct TargetElement: Codable, Equatable {
5 | /// Element ID of element to toggle.
6 | public var elementId: String
7 |
8 | /// If `true`, always show target element. If `false`, always hide target element. If `nil`, toggle target element''s visibility.
9 | public var isVisible: Bool?
10 |
11 | public init(elementId: String, isVisible: Bool? = nil) {
12 | self.elementId = elementId
13 | self.isVisible = isVisible
14 | }
15 |
16 | private enum CodingKeys: String, CodingKey {
17 | case elementId, isVisible
18 | }
19 |
20 | public init(from decoder: Decoder) throws {
21 | let singleValueContainer = try decoder.singleValueContainer()
22 |
23 | if let elementId = try? singleValueContainer.decode(String.self) {
24 | self.init(elementId: elementId)
25 | } else {
26 | let container = try decoder.container(keyedBy: CodingKeys.self)
27 | try self.init(
28 | elementId: container.decode(String.self, forKey: .elementId),
29 | isVisible: container.decodeIfPresent(Bool.self, forKey: .isVisible)
30 | )
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCard/Actions/ToggleVisibilityAction.swift:
--------------------------------------------------------------------------------
1 | import DefaultCodable
2 | import Foundation
3 |
4 | /// An action that toggles the visibility of associated card elements.
5 | public struct ToggleVisibilityAction: ActionProtocol, Codable, Equatable {
6 | /// The array of TargetElements.
7 | @Default public var targetElements: [TargetElement]
8 |
9 | /// Label for button or link that represents this action.
10 | @Default public var title: String
11 |
12 | /// Optional icon to be shown on the action in conjunction with the title.
13 | public var iconURL: URL?
14 |
15 | /// Controls the style of an Action, which influences how the action is displayed, spoken, etc.
16 | @Default public var style: ActionStyle
17 |
18 | /// Describes what to do when an unknown action is encountered or the requires of this can’t be met.
19 | @Default public var fallback: Fallback
20 |
21 | /// A series of key/value pairs indicating features that the action requires with corresponding minimum version.
22 | /// When a feature is missing or of insufficient version, fallback is triggered.
23 | @Default public var requires: [String: SemanticVersion]
24 |
25 | public init(
26 | targetElements: [TargetElement],
27 | title: String = "",
28 | iconURL: URL? = nil,
29 | style: ActionStyle = .default,
30 | fallback: Fallback = .none,
31 | requires: [String: SemanticVersion] = [:]
32 | ) {
33 | self.targetElements = targetElements
34 | self.title = title
35 | self.iconURL = iconURL
36 | self.style = style
37 | self.fallback = fallback
38 | self.requires = requires
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCard/Actions/UnknownAction.swift:
--------------------------------------------------------------------------------
1 | import DefaultCodable
2 | import Foundation
3 |
4 | /// Represents an unknown action. Unknown actions are discarded or replaced by their `fallback`, in case one is provided.
5 | public struct UnknownAction: ActionProtocol, Codable, Equatable {
6 | /// Label for button or link that represents this action.
7 | @Default public var title: String
8 |
9 | /// Optional icon to be shown on the action in conjunction with the title.
10 | public var iconURL: URL?
11 |
12 | /// Controls the style of an Action, which influences how the action is displayed, spoken, etc.
13 | @Default public var style: ActionStyle
14 |
15 | /// Describes what to do when an unknown action is encountered or the requires of this can’t be met.
16 | @Default public var fallback: Fallback
17 |
18 | /// A series of key/value pairs indicating features that the action requires with corresponding minimum version.
19 | /// When a feature is missing or of insufficient version, fallback is triggered.
20 | @Default public var requires: [String: SemanticVersion]
21 |
22 | public init(
23 | title: String = "",
24 | iconURL: URL? = nil,
25 | style: ActionStyle = .default,
26 | fallback: Fallback = .none,
27 | requires: [String: SemanticVersion] = [:]
28 | ) {
29 | self.title = title
30 | self.iconURL = iconURL
31 | self.style = style
32 | self.fallback = fallback
33 | self.requires = requires
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCard/Common/BlockElementHeight.swift:
--------------------------------------------------------------------------------
1 | import DefaultCodable
2 | import Foundation
3 |
4 | public enum BlockElementHeight: Codable, Equatable {
5 | /// The height of the element will be determined by the height of its contents.
6 | case auto
7 |
8 | /// The element will stretch its height to the available remaining height of the parent container.
9 | case stretch
10 |
11 | /// The fixed height for the element.
12 | case pixels(Int)
13 |
14 | public init(from decoder: Decoder) throws {
15 | let container = try decoder.singleValueContainer()
16 | let value = try container.decode(String.self).lowercased()
17 |
18 | switch value {
19 | case "auto":
20 | self = .auto
21 | case "stretch":
22 | self = .stretch
23 | default:
24 | let pixelDimension = try PixelDimension(from: decoder)
25 | self = .pixels(pixelDimension.value)
26 | }
27 | }
28 |
29 | public func encode(to encoder: Encoder) throws {
30 | var container = encoder.singleValueContainer()
31 |
32 | switch self {
33 | case .auto:
34 | try container.encode("auto")
35 | case .stretch:
36 | try container.encode("stretch")
37 | case let .pixels(value):
38 | try container.encode(PixelDimension(value: value))
39 | }
40 | }
41 | }
42 |
43 | extension BlockElementHeight: DefaultValueProvider {
44 | public static var `default` = BlockElementHeight.auto
45 | }
46 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCard/Common/ContainerStyle.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | /// The style for a container element.
4 | public struct ContainerStyle: Codable, Hashable, RawRepresentable {
5 | public private(set) var rawValue: String
6 |
7 | public init(rawValue: String) {
8 | self.rawValue = rawValue
9 | }
10 | }
11 |
12 | extension ContainerStyle: Equatable {
13 | public static func == (lhs: ContainerStyle, rhs: ContainerStyle) -> Bool {
14 | lhs.rawValue.caseInsensitiveCompare(rhs.rawValue) == .orderedSame
15 | }
16 | }
17 |
18 | public extension ContainerStyle {
19 | static let `default` = ContainerStyle(rawValue: "default")
20 | static let emphasis = ContainerStyle(rawValue: "emphasis")
21 | static let good = ContainerStyle(rawValue: "good")
22 | static let attention = ContainerStyle(rawValue: "attention")
23 | static let warning = ContainerStyle(rawValue: "warning")
24 | static let accent = ContainerStyle(rawValue: "accent")
25 | }
26 |
27 | extension ContainerStyle: CaseIterable {
28 | public static var allCases: [ContainerStyle] = [
29 | .default, .emphasis, .good,
30 | .attention, .warning, .accent,
31 | ]
32 | }
33 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCard/Common/FontSize.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | /// Controls the relative size of text elements.
4 | public struct FontSize: Codable, Hashable, RawRepresentable {
5 | public private(set) var rawValue: String
6 |
7 | public init(rawValue: String) {
8 | self.rawValue = rawValue
9 | }
10 | }
11 |
12 | extension FontSize: Equatable {
13 | public static func == (lhs: FontSize, rhs: FontSize) -> Bool {
14 | lhs.rawValue.caseInsensitiveCompare(rhs.rawValue) == .orderedSame
15 | }
16 | }
17 |
18 | public extension FontSize {
19 | static let `default` = FontSize(rawValue: "default")
20 | static let small = FontSize(rawValue: "small")
21 | static let medium = FontSize(rawValue: "medium")
22 | static let large = FontSize(rawValue: "large")
23 | static let extraLarge = FontSize(rawValue: "extraLarge")
24 | }
25 |
26 | extension FontSize: CaseIterable {
27 | public static var allCases: [FontSize] = [
28 | .default, .small, .medium, .large, .extraLarge,
29 | ]
30 | }
31 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCard/Common/FontType.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | /// The font type of text elements.
4 | public struct FontType: Codable, Hashable, RawRepresentable {
5 | public private(set) var rawValue: String
6 |
7 | public init(rawValue: String) {
8 | self.rawValue = rawValue
9 | }
10 | }
11 |
12 | extension FontType: Equatable {
13 | public static func == (lhs: FontType, rhs: FontType) -> Bool {
14 | lhs.rawValue.caseInsensitiveCompare(rhs.rawValue) == .orderedSame
15 | }
16 | }
17 |
18 | public extension FontType {
19 | static let `default` = FontType(rawValue: "default")
20 | static let monospace = FontType(rawValue: "monospace")
21 | }
22 |
23 | extension FontType: CaseIterable {
24 | public static var allCases: [FontType] = [.default, .monospace]
25 | }
26 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCard/Common/FontWeight.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | /// Controls the weight of text elements.
4 | public struct FontWeight: Codable, Hashable, RawRepresentable {
5 | public private(set) var rawValue: String
6 |
7 | public init(rawValue: String) {
8 | self.rawValue = rawValue
9 | }
10 | }
11 |
12 | extension FontWeight: Equatable {
13 | public static func == (lhs: FontWeight, rhs: FontWeight) -> Bool {
14 | lhs.rawValue.caseInsensitiveCompare(rhs.rawValue) == .orderedSame
15 | }
16 | }
17 |
18 | public extension FontWeight {
19 | static let `default` = FontWeight(rawValue: "default")
20 | static let light = FontWeight(rawValue: "lighter")
21 | static let bold = FontWeight(rawValue: "bolder")
22 | }
23 |
24 | extension FontWeight: CaseIterable {
25 | public static var allCases: [FontWeight] = [.default, .light, .bold]
26 | }
27 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCard/Common/HAlignment.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | /// Controls how content is horizontally positioned within its container.
4 | public struct HAlignment: Codable, Hashable, RawRepresentable {
5 | public private(set) var rawValue: String
6 |
7 | public init(rawValue: String) {
8 | self.rawValue = rawValue
9 | }
10 | }
11 |
12 | extension HAlignment: Equatable {
13 | public static func == (lhs: HAlignment, rhs: HAlignment) -> Bool {
14 | lhs.rawValue.caseInsensitiveCompare(rhs.rawValue) == .orderedSame
15 | }
16 | }
17 |
18 | public extension HAlignment {
19 | static let left = HAlignment(rawValue: "left")
20 | static let center = HAlignment(rawValue: "center")
21 | static let right = HAlignment(rawValue: "right")
22 | }
23 |
24 | extension HAlignment: CaseIterable {
25 | public static var allCases: [HAlignment] = [.left, .center, .right]
26 | }
27 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCard/Common/ImageFillMode.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | public struct ImageFillMode: Codable, Hashable, RawRepresentable {
4 | public private(set) var rawValue: String
5 |
6 | public init(rawValue: String) {
7 | self.rawValue = rawValue
8 | }
9 | }
10 |
11 | extension ImageFillMode: Equatable {
12 | public static func == (lhs: ImageFillMode, rhs: ImageFillMode) -> Bool {
13 | lhs.rawValue.caseInsensitiveCompare(rhs.rawValue) == .orderedSame
14 | }
15 | }
16 |
17 | public extension ImageFillMode {
18 | static let cover = ImageFillMode(rawValue: "cover")
19 | static let repeatHorizontally = ImageFillMode(rawValue: "repeatHorizontally")
20 | static let repeatVertically = ImageFillMode(rawValue: "repeatVertically")
21 | static let `repeat` = ImageFillMode(rawValue: "repeat")
22 | }
23 |
24 | extension ImageFillMode: CaseIterable {
25 | public static var allCases: [ImageFillMode] = [
26 | .cover, .repeatHorizontally, .repeatVertically, .repeat,
27 | ]
28 | }
29 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCard/Common/ImageSize.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | /// Controls the approximate size of the image. The physical dimensions will vary per host.
4 | public struct ImageSize: Codable, Hashable, RawRepresentable {
5 | public private(set) var rawValue: String
6 |
7 | public init(rawValue: String) {
8 | self.rawValue = rawValue
9 | }
10 | }
11 |
12 | extension ImageSize: Equatable {
13 | public static func == (lhs: ImageSize, rhs: ImageSize) -> Bool {
14 | lhs.rawValue.caseInsensitiveCompare(rhs.rawValue) == .orderedSame
15 | }
16 | }
17 |
18 | public extension ImageSize {
19 | static let auto = ImageSize(rawValue: "auto")
20 | static let stretch = ImageSize(rawValue: "stretch")
21 | static let small = ImageSize(rawValue: "small")
22 | static let medium = ImageSize(rawValue: "medium")
23 | static let large = ImageSize(rawValue: "large")
24 | }
25 |
26 | extension ImageSize: CaseIterable {
27 | public static var allCases: [ImageSize] = [
28 | .auto, .stretch, .small, .medium, .large,
29 | ]
30 | }
31 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCard/Common/ImageStyle.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | public struct ImageStyle: Codable, Hashable, RawRepresentable {
4 | public private(set) var rawValue: String
5 |
6 | public init(rawValue: String) {
7 | self.rawValue = rawValue
8 | }
9 | }
10 |
11 | extension ImageStyle: Equatable {
12 | public static func == (lhs: ImageStyle, rhs: ImageStyle) -> Bool {
13 | lhs.rawValue.caseInsensitiveCompare(rhs.rawValue) == .orderedSame
14 | }
15 | }
16 |
17 | public extension ImageStyle {
18 | static let `default` = ImageStyle(rawValue: "default")
19 | static let person = ImageStyle(rawValue: "person")
20 | }
21 |
22 | extension ImageStyle: CaseIterable {
23 | public static var allCases: [ImageStyle] = [.default, .person]
24 | }
25 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCard/Common/ItemIdentifier.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | @propertyWrapper
4 | public struct ItemIdentifier: Codable, Equatable {
5 | public var wrappedValue: String
6 |
7 | public init(wrappedValue: String) {
8 | self.wrappedValue = wrappedValue
9 | }
10 |
11 | public init(from decoder: Decoder) throws {
12 | let container = try decoder.singleValueContainer()
13 |
14 | if container.decodeNil() {
15 | wrappedValue = UUID().uuidString
16 | } else {
17 | wrappedValue = try container.decode(String.self)
18 | }
19 | }
20 | }
21 |
22 | public extension KeyedDecodingContainer {
23 | func decode(_: ItemIdentifier.Type, forKey key: Key) throws -> ItemIdentifier {
24 | if let value = try decodeIfPresent(ItemIdentifier.self, forKey: key) {
25 | return value
26 | } else {
27 | return ItemIdentifier(wrappedValue: UUID().uuidString)
28 | }
29 | }
30 | }
31 |
32 | public extension KeyedEncodingContainer {
33 | mutating func encode(_ value: ItemIdentifier, forKey key: Key) throws {
34 | guard !value.wrappedValue.isEmpty else { return }
35 | try encode(value.wrappedValue, forKey: key)
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCard/Common/PixelDimension.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | public struct PixelDimension: Codable, Equatable {
4 | public var value: Int
5 |
6 | public init(value: Int) {
7 | self.value = value
8 | }
9 |
10 | public init(from decoder: Decoder) throws {
11 | let container = try decoder.singleValueContainer()
12 | let stringValue = try container.decode(String.self)
13 |
14 | let scanner = Scanner(string: stringValue)
15 | var value = 0
16 |
17 | guard scanner.scanInt(&value) else {
18 | throw DecodingError.dataCorruptedError(
19 | in: container,
20 | debugDescription: "Invalid pixel dimension: \(stringValue)"
21 | )
22 | }
23 |
24 | self.value = value
25 | }
26 |
27 | public func encode(to encoder: Encoder) throws {
28 | var container = encoder.singleValueContainer()
29 | try container.encode("\(value)px")
30 | }
31 | }
32 |
33 | extension PixelDimension: ExpressibleByIntegerLiteral {
34 | public init(integerLiteral value: Int) {
35 | self.init(value: value)
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCard/Common/Spacing.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | /// Specifies how much spacing. Hosts pick the exact pixel amounts for each of these.
4 | public struct Spacing: Codable, Hashable, RawRepresentable {
5 | public private(set) var rawValue: String
6 |
7 | public init(rawValue: String) {
8 | self.rawValue = rawValue
9 | }
10 | }
11 |
12 | extension Spacing: Equatable {
13 | public static func == (lhs: Spacing, rhs: Spacing) -> Bool {
14 | lhs.rawValue.caseInsensitiveCompare(rhs.rawValue) == .orderedSame
15 | }
16 | }
17 |
18 | public extension Spacing {
19 | static let `default` = Spacing(rawValue: "default")
20 | static let none = Spacing(rawValue: "none")
21 | static let small = Spacing(rawValue: "small")
22 | static let medium = Spacing(rawValue: "medium")
23 | static let large = Spacing(rawValue: "large")
24 | static let extraLarge = Spacing(rawValue: "extraLarge")
25 | static let padding = Spacing(rawValue: "padding")
26 | }
27 |
28 | extension Spacing: CaseIterable {
29 | public static var allCases: [Spacing] = [
30 | .default, .none, .small, .medium, .large, .extraLarge, .padding,
31 | ]
32 | }
33 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCard/Common/TextColor.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | public struct TextColor: Codable, Hashable, RawRepresentable {
4 | public private(set) var rawValue: String
5 |
6 | public init(rawValue: String) {
7 | self.rawValue = rawValue
8 | }
9 | }
10 |
11 | extension TextColor: Equatable {
12 | public static func == (lhs: TextColor, rhs: TextColor) -> Bool {
13 | lhs.rawValue.caseInsensitiveCompare(rhs.rawValue) == .orderedSame
14 | }
15 | }
16 |
17 | public extension TextColor {
18 | static let `default` = TextColor(rawValue: "default")
19 | static let dark = TextColor(rawValue: "dark")
20 | static let light = TextColor(rawValue: "light")
21 | static let accent = TextColor(rawValue: "accent")
22 | static let good = TextColor(rawValue: "good")
23 | static let warning = TextColor(rawValue: "warning")
24 | static let attention = TextColor(rawValue: "attention")
25 | }
26 |
27 | extension TextColor: CaseIterable {
28 | public static var allCases: [TextColor] = [
29 | .default, .dark, .light, .accent, .good, .warning, .attention,
30 | ]
31 | }
32 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCard/Common/VAlignment.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | public struct VAlignment: Codable, Hashable, RawRepresentable {
4 | public private(set) var rawValue: String
5 |
6 | public init(rawValue: String) {
7 | self.rawValue = rawValue
8 | }
9 | }
10 |
11 | extension VAlignment: Equatable {
12 | public static func == (lhs: VAlignment, rhs: VAlignment) -> Bool {
13 | lhs.rawValue.caseInsensitiveCompare(rhs.rawValue) == .orderedSame
14 | }
15 | }
16 |
17 | public extension VAlignment {
18 | static let top = VAlignment(rawValue: "top")
19 | static let center = VAlignment(rawValue: "center")
20 | static let bottom = VAlignment(rawValue: "bottom")
21 | }
22 |
23 | extension VAlignment: CaseIterable {
24 | public static var allCases: [VAlignment] = [.top, .center, .bottom]
25 | }
26 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCard/Containers/ActionSet.swift:
--------------------------------------------------------------------------------
1 | import DefaultCodable
2 | import Foundation
3 |
4 | /// Displays a set of actions.
5 | public struct ActionSet: CardElementProtocol, Codable, Equatable {
6 | /// A unique identifier associated with the item.
7 | @ItemIdentifier public var id: String
8 |
9 | /// If `false`, this item will be removed from the visual tree.
10 | @Default public var isVisible: Bool
11 |
12 | /// When `true`, draw a separating line at the top of the element.
13 | @Default public var separator: Bool
14 |
15 | /// Controls the amount of spacing between this element and the preceding element.
16 | @Default public var spacing: Spacing
17 |
18 | /// Describes what to do when an unknown element is encountered or the requires of this or any children can’t be met.
19 | @Default public var fallback: Fallback
20 |
21 | /// A series of key/value pairs indicating features that the item requires with corresponding minimum version.
22 | /// When a feature is missing or of insufficient version, fallback is triggered.
23 | @Default public var requires: [String: SemanticVersion]
24 |
25 | /// The array of `Action` elements to show.
26 | public var actions: [Action]
27 |
28 | public init(
29 | id: String = "",
30 | isVisible: Bool = true,
31 | separator: Bool = false,
32 | spacing: Spacing = .default,
33 | fallback: Fallback = .none,
34 | requires: [String: SemanticVersion] = [:],
35 | actions: [Action]
36 | ) {
37 | self.id = id
38 | self.isVisible = isVisible
39 | self.separator = separator
40 | self.spacing = spacing
41 | self.fallback = fallback
42 | self.requires = requires
43 | self.actions = actions
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCard/Containers/Fact.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | /// Describes a fact in a `FactSet` as a key / value pair.
4 | public struct Fact: Codable, Equatable {
5 | /// The title of the fact.
6 | public var title: String
7 |
8 | /// The value of the fact.
9 | public var value: String
10 |
11 | public init(title: String, value: String) {
12 | self.title = title
13 | self.value = value
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCard/Containers/FactSet.swift:
--------------------------------------------------------------------------------
1 | import DefaultCodable
2 | import Foundation
3 |
4 | /// The `FactSet` element displays a series of facts
5 | /// (i.e. name / value pairs) in a tabular form.
6 | public struct FactSet: CardElementProtocol, Codable, Equatable {
7 | /// A unique identifier associated with the item.
8 | @ItemIdentifier public var id: String
9 |
10 | /// If `false`, this item will be removed from the visual tree.
11 | @Default public var isVisible: Bool
12 |
13 | /// When `true`, draw a separating line at the top of the element.
14 | @Default public var separator: Bool
15 |
16 | /// Controls the amount of spacing between this element and the preceding element.
17 | @Default public var spacing: Spacing
18 |
19 | /// Describes what to do when an unknown element is encountered or the requires of this or any children can’t be met.
20 | @Default public var fallback: Fallback
21 |
22 | /// A series of key/value pairs indicating features that the item requires with corresponding minimum version.
23 | /// When a feature is missing or of insufficient version, fallback is triggered.
24 | @Default public var requires: [String: SemanticVersion]
25 |
26 | public var facts: [Fact]
27 |
28 | public init(
29 | id: String = "",
30 | isVisible: Bool = true,
31 | separator: Bool = false,
32 | spacing: Spacing = .default,
33 | fallback: Fallback = .none,
34 | requires: [String: SemanticVersion] = [:],
35 | facts: [Fact]
36 | ) {
37 | self.id = id
38 | self.isVisible = isVisible
39 | self.separator = separator
40 | self.spacing = spacing
41 | self.fallback = fallback
42 | self.requires = requires
43 | self.facts = facts
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCard/Elements/CardElementProtocol.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | /// A type that represents a card element.
4 | public protocol CardElementProtocol {
5 | /// A unique identifier associated with the item.
6 | var id: String { get }
7 |
8 | /// If `false`, this item will be removed from the visual tree.
9 | var isVisible: Bool { get set }
10 |
11 | /// When `true`, draw a separating line at the top of the element.
12 | var separator: Bool { get }
13 |
14 | /// Controls the amount of spacing between this element and the preceding element.
15 | var spacing: Spacing { get }
16 |
17 | /// Describes what to do when an unknown element is encountered or the requires of this or any children can’t be met.
18 | var fallback: Fallback { get }
19 |
20 | /// A series of key/value pairs indicating features that the item requires with corresponding minimum version.
21 | /// When a feature is missing or of insufficient version, fallback is triggered.
22 | var requires: [String: SemanticVersion] { get }
23 | }
24 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCard/Elements/ColumnSetElement.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | /// An element inside a column set.
4 | @dynamicMemberLookup
5 | public indirect enum ColumnSetElement {
6 | /// A column element.
7 | case column(Column)
8 |
9 | /// An unknown column set element.
10 | case unknown(UnknownColumnSetElement)
11 | }
12 |
13 | extension ColumnSetElement: Equatable {}
14 |
15 | extension ColumnSetElement: Codable {
16 | private enum CodingKeys: String, CodingKey {
17 | case type
18 | }
19 |
20 | public init(from decoder: Decoder) throws {
21 | let container = try decoder.container(keyedBy: CodingKeys.self)
22 | let type = try container.decode(String.self, forKey: .type)
23 |
24 | switch type {
25 | case String(describing: Column.self):
26 | self = .column(try Column(from: decoder))
27 | default:
28 | self = .unknown(try UnknownColumnSetElement(from: decoder))
29 | }
30 | }
31 |
32 | public func encode(to encoder: Encoder) throws {
33 | var container = encoder.container(keyedBy: CodingKeys.self)
34 |
35 | switch self {
36 | case let .column(element):
37 | try container.encode(String(describing: Column.self), forKey: .type)
38 | try element.encode(to: encoder)
39 | case .unknown:
40 | break // do nothing
41 | }
42 | }
43 | }
44 |
45 | public extension ColumnSetElement {
46 | subscript(dynamicMember keyPath: KeyPath) -> T {
47 | switch self {
48 | case let .column(element):
49 | return element[keyPath: keyPath]
50 | case let .unknown(element):
51 | return element[keyPath: keyPath]
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCard/Elements/ColumnSetElementProtocol.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | /// A type that reperesents a column set element.
4 | public protocol ColumnSetElementProtocol {
5 | /// A unique identifier associated with the item.
6 | var id: String { get }
7 |
8 | /// If `false`, this item will be removed from the visual tree.
9 | var isVisible: Bool { get set }
10 |
11 | /// When `true`, draw a separating line at the top of the element.
12 | var separator: Bool { get }
13 |
14 | /// Controls the amount of spacing between this element and the preceding element.
15 | var spacing: Spacing { get }
16 |
17 | /// Describes what to do when an unknown element is encountered or the requires of this or any children can’t be met.
18 | var fallback: Fallback { get }
19 |
20 | /// A series of key/value pairs indicating features that the item requires with corresponding minimum version.
21 | /// When a feature is missing or of insufficient version, fallback is triggered.
22 | var requires: [String: SemanticVersion] { get }
23 | }
24 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCard/Elements/ColumnWidth.swift:
--------------------------------------------------------------------------------
1 | import DefaultCodable
2 | import Foundation
3 |
4 | public enum ColumnWidth: Codable, Equatable {
5 | case auto
6 | case stretch
7 | case weight(Double)
8 | case pixels(Int)
9 |
10 | public init(from decoder: Decoder) throws {
11 | let container = try decoder.singleValueContainer()
12 |
13 | if let weight = try? container.decode(Double.self) {
14 | self = .weight(weight)
15 | } else {
16 | let value = try container.decode(String.self)
17 |
18 | if let weight = Double(value) {
19 | self = .weight(weight)
20 | } else {
21 | switch value {
22 | case "auto":
23 | self = .auto
24 | case "stretch":
25 | self = .stretch
26 | default:
27 | let pixelDimension = try PixelDimension(from: decoder)
28 | self = .pixels(pixelDimension.value)
29 | }
30 | }
31 | }
32 | }
33 |
34 | public func encode(to encoder: Encoder) throws {
35 | var container = encoder.singleValueContainer()
36 |
37 | switch self {
38 | case .auto:
39 | try container.encode("auto")
40 | case .stretch:
41 | try container.encode("stretch")
42 | case let .weight(value):
43 | try container.encode(value)
44 | case let .pixels(value):
45 | try container.encode(PixelDimension(value: value))
46 | }
47 | }
48 | }
49 |
50 | extension ColumnWidth: DefaultValueProvider {
51 | public static var `default` = ColumnWidth.stretch
52 | }
53 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCard/Elements/CustomCardElement.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | /// A type that represents a custom card element.
4 | public protocol CustomCardElement: CardElementProtocol {
5 | /// The type name of this custom card element.
6 | static var typeName: String { get }
7 | }
8 |
9 | public extension CustomCardElement {
10 | static var typeName: String { String(describing: self) }
11 | }
12 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCard/Elements/Fallback.swift:
--------------------------------------------------------------------------------
1 | import DefaultCodable
2 | import Foundation
3 |
4 | /// Describes what to do when an unknown item is encountered or the requires of this or any children can't be met.
5 | public enum Fallback {
6 | case none
7 | case drop
8 | case element(Element)
9 | }
10 |
11 | extension Fallback: Equatable where Element: Equatable {}
12 |
13 | private enum Constants {
14 | static let drop = "drop"
15 | }
16 |
17 | extension Fallback: Codable where Element: Codable {
18 | public init(from decoder: Decoder) throws {
19 | let container = try decoder.singleValueContainer()
20 |
21 | if let fallbackOption = try? container.decode(String.self),
22 | fallbackOption.caseInsensitiveCompare(Constants.drop) == .orderedSame
23 | {
24 | self = .drop
25 | } else if let element = try? container.decode(Element.self) {
26 | self = .element(element)
27 | } else {
28 | self = .none
29 | }
30 | }
31 |
32 | public func encode(to encoder: Encoder) throws {
33 | var container = encoder.singleValueContainer()
34 |
35 | switch self {
36 | case .none:
37 | try container.encodeNil()
38 | case .drop:
39 | try container.encode(Constants.drop)
40 | case let .element(element):
41 | try container.encode(element)
42 | }
43 | }
44 | }
45 |
46 | public extension Fallback where Element: Equatable & Codable {
47 | enum None: DefaultValueProvider {
48 | public static var `default`: Fallback { .none }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCard/Elements/RichTextBlock.swift:
--------------------------------------------------------------------------------
1 | import DefaultCodable
2 | import Foundation
3 |
4 | /// Defines an array of inlines, allowing for inline text formatting.
5 | public struct RichTextBlock: CardElementProtocol, Codable, Equatable {
6 | /// A unique identifier associated with the item.
7 | @ItemIdentifier public var id: String
8 |
9 | /// If `false`, this item will be removed from the visual tree.
10 | @Default public var isVisible: Bool
11 |
12 | /// When `true`, draw a separating line at the top of the element.
13 | @Default public var separator: Bool
14 |
15 | /// Controls the amount of spacing between this element and the preceding element.
16 | @Default public var spacing: Spacing
17 |
18 | /// Describes what to do when an unknown element is encountered or the requires of this or any children can’t be met.
19 | @Default public var fallback: Fallback
20 |
21 | /// A series of key/value pairs indicating features that the item requires with corresponding minimum version.
22 | /// When a feature is missing or of insufficient version, fallback is triggered.
23 | @Default public var requires: [String: SemanticVersion]
24 |
25 | /// The array of inlines.
26 | public var inlines: [TextRun]
27 |
28 | /// Controls the horizontal text alignment.
29 | @Default public var horizontalAlignment: HAlignment
30 |
31 | public init(
32 | id: String = "",
33 | isVisible: Bool = true,
34 | separator: Bool = false,
35 | spacing: Spacing = .default,
36 | fallback: Fallback = .none,
37 | requires: [String: SemanticVersion] = [:],
38 | inlines: [TextRun],
39 | horizontalAlignment: HAlignment = .left
40 | ) {
41 | self.id = id
42 | self.isVisible = isVisible
43 | self.separator = separator
44 | self.spacing = spacing
45 | self.fallback = fallback
46 | self.requires = requires
47 | self.inlines = inlines
48 | self.horizontalAlignment = horizontalAlignment
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCard/Elements/UnknownCardElement.swift:
--------------------------------------------------------------------------------
1 | import DefaultCodable
2 | import Foundation
3 |
4 | /// An unknown card element. Unknown card elements are discarded or replaced
5 | /// by their `fallback`, in case one is provided.
6 | public struct UnknownCardElement: CardElementProtocol, Codable, Equatable {
7 | /// A unique identifier associated with the item.
8 | @ItemIdentifier public var id: String
9 |
10 | /// If `false`, this item will be removed from the visual tree.
11 | @Default public var isVisible: Bool
12 |
13 | /// When `true`, draw a separating line at the top of the element.
14 | @Default public var separator: Bool
15 |
16 | /// Controls the amount of spacing between this element and the preceding element.
17 | @Default public var spacing: Spacing
18 |
19 | /// Describes what to do when an unknown element is encountered or the requires of this or any children can’t be met.
20 | @Default public var fallback: Fallback
21 |
22 | /// A series of key/value pairs indicating features that the item requires with corresponding minimum version.
23 | /// When a feature is missing or of insufficient version, fallback is triggered.
24 | @Default public var requires: [String: SemanticVersion]
25 |
26 | public init(
27 | id: String = "",
28 | isVisible: Bool = true,
29 | separator: Bool = false,
30 | spacing: Spacing = .default,
31 | fallback: Fallback = .none,
32 | requires: [String: SemanticVersion] = [:]
33 | ) {
34 | self.id = id
35 | self.isVisible = isVisible
36 | self.separator = separator
37 | self.spacing = spacing
38 | self.fallback = fallback
39 | self.requires = requires
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCard/Elements/UnknownColumnSetElement.swift:
--------------------------------------------------------------------------------
1 | import DefaultCodable
2 | import Foundation
3 |
4 | /// An unknown column set element. Unknown column set elements are discarded or replaced by their `fallback`,
5 | /// in case one is provided.
6 | public struct UnknownColumnSetElement: ColumnSetElementProtocol, Codable, Equatable {
7 | /// A unique identifier associated with the item.
8 | @ItemIdentifier public var id: String
9 |
10 | /// If `false`, this item will be removed from the visual tree.
11 | @Default public var isVisible: Bool
12 |
13 | /// When `true`, draw a separating line at the top of the element.
14 | @Default public var separator: Bool
15 |
16 | /// Controls the amount of spacing between this element and the preceding element.
17 | @Default public var spacing: Spacing
18 |
19 | /// Describes what to do when an unknown element is encountered or the requires of this or any children can’t be met.
20 | @Default public var fallback: Fallback
21 |
22 | /// A series of key/value pairs indicating features that the item requires with corresponding minimum version.
23 | /// When a feature is missing or of insufficient version, fallback is triggered.
24 | @Default public var requires: [String: SemanticVersion]
25 |
26 | public init(
27 | id: String = "",
28 | isVisible: Bool = true,
29 | separator: Bool = false,
30 | spacing: Spacing = .default,
31 | fallback: Fallback = .none,
32 | requires: [String: SemanticVersion] = [:]
33 | ) {
34 | self.id = id
35 | self.isVisible = isVisible
36 | self.separator = separator
37 | self.spacing = spacing
38 | self.fallback = fallback
39 | self.requires = requires
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCardUI/Configuration/Color+Configuration.swift:
--------------------------------------------------------------------------------
1 | #if canImport(SwiftUI)
2 |
3 | import SwiftUI
4 |
5 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
6 | public extension Color {
7 | var subtle: Color { opacity(0.8) }
8 |
9 | static var defaultBackground: Color {
10 | #if os(macOS)
11 | return Color(.controlBackgroundColor)
12 | #elseif os(iOS)
13 | return Color(.systemBackground)
14 | #elseif os(tvOS)
15 | // FIXME: Find an appropriate default background color for tvOS
16 | return Color.clear
17 | #elseif os(watchOS)
18 | // FIXME: Find an appropriate default background color for watchOS
19 | return Color.black
20 | #endif
21 | }
22 |
23 | static var emphasisBackground: Color {
24 | #if os(macOS)
25 | return Color(.windowBackgroundColor)
26 | #elseif os(iOS)
27 | return Color(.secondarySystemBackground)
28 | #elseif os(tvOS)
29 | // FIXME: Find an appropriate emphasis background color for tvOS
30 | return Color.clear
31 | #elseif os(watchOS)
32 | // FIXME: Find an appropriate emphasis background color for watchOS
33 | return Color(.darkGray)
34 | #endif
35 | }
36 | }
37 |
38 | #endif
39 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCardUI/Configuration/ContainerConfiguration.swift:
--------------------------------------------------------------------------------
1 | #if canImport(SwiftUI)
2 |
3 | import SwiftUI
4 |
5 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
6 | public struct ContainerConfiguration: Equatable {
7 | /// The background color to use for this container.
8 | public let backgroundColor: Color
9 |
10 | /// The text colors to use for this container.
11 | public let textColors: TextColorConfiguration
12 |
13 | public init(backgroundColor: Color, textColors: TextColorConfiguration) {
14 | self.backgroundColor = backgroundColor
15 | self.textColors = textColors
16 | }
17 | }
18 |
19 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
20 | public extension ContainerConfiguration {
21 | static let `default` = ContainerConfiguration(
22 | backgroundColor: .defaultBackground,
23 | textColors: .default
24 | )
25 |
26 | static let emphasis = ContainerConfiguration(
27 | backgroundColor: .emphasisBackground,
28 | textColors: .default
29 | )
30 |
31 | static let accent = ContainerConfiguration(
32 | backgroundColor: .accentColor,
33 | textColors: .default
34 | )
35 | }
36 |
37 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
38 | extension ContainerConfiguration: Decodable {
39 | private enum CodingKeys: String, CodingKey {
40 | case backgroundColor, foregroundColors
41 | }
42 |
43 | public init(from decoder: Decoder) throws {
44 | let container = try decoder.container(keyedBy: CodingKeys.self)
45 |
46 | backgroundColor = (try Color(argbHex: container.decodeIfPresent(String.self, forKey: .backgroundColor))) ?? .defaultBackground
47 | textColors = try container.decodeIfPresent(TextColorConfiguration.self, forKey: .foregroundColors) ?? .default
48 | }
49 | }
50 |
51 | #endif
52 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCardUI/Configuration/FactSetConfiguration.swift:
--------------------------------------------------------------------------------
1 | #if canImport(SwiftUI)
2 |
3 | import AdaptiveCard
4 | import SwiftUI
5 |
6 | /// A set of values that control the appearance of fact sets inside adaptive cards.
7 | ///
8 | /// To set the fact set configuration for a view hierarchy, use the `factSetConfiguration(_:)`
9 | /// modifier and specify an instance of this type.
10 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
11 | public struct FactSetConfiguration: Equatable {
12 | /// Text configuration to use for titles in fact sets.
13 | public let title: TextBlockConfiguration
14 |
15 | /// Text configuration to use for values in fact sets.
16 | public let value: TextBlockConfiguration
17 |
18 | /// Spacing between titles and values.
19 | public var spacing: CGFloat
20 |
21 | public init(
22 | title: TextBlockConfiguration,
23 | value: TextBlockConfiguration,
24 | spacing: CGFloat
25 | ) {
26 | self.title = title
27 | self.value = value
28 | self.spacing = spacing
29 | }
30 | }
31 |
32 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
33 | public extension FactSetConfiguration {
34 | static let `default` = FactSetConfiguration(
35 | title: TextBlockConfiguration(weight: .bold, maxWidth: 132),
36 | value: .default,
37 | spacing: 8
38 | )
39 | }
40 |
41 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
42 | extension FactSetConfiguration: Decodable {
43 | private enum CodingKeys: String, CodingKey {
44 | case title, value, spacing
45 | }
46 |
47 | public init(from decoder: Decoder) throws {
48 | let container = try decoder.container(keyedBy: CodingKeys.self)
49 |
50 | title = try container.decodeIfPresent(TextBlockConfiguration.self, forKey: .title) ?? Self.default.title
51 | value = try container.decodeIfPresent(TextBlockConfiguration.self, forKey: .value) ?? Self.default.value
52 | spacing = try container.decodeIfPresent(CGFloat.self, forKey: .spacing) ?? Self.default.spacing
53 | }
54 | }
55 |
56 | #endif
57 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCardUI/Configuration/FontTypeConfiguration.swift:
--------------------------------------------------------------------------------
1 | #if canImport(SwiftUI)
2 |
3 | import AdaptiveCard
4 | import SwiftUI
5 |
6 | /// A font type configuration specifies the different fonts for each font type.
7 | ///
8 | /// To set the font type configuration for a view hierarchy, use the `fontTypeConfiguration(_:)`
9 | /// modifier and specify an instance of this type.
10 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
11 | public struct FontTypeConfiguration: Equatable {
12 | /// The font configuration for the default font type.
13 | public let `default`: FontConfiguration
14 |
15 | /// The font configuration for the monospace font type.
16 | public let monospace: FontConfiguration
17 |
18 | public init(default: FontConfiguration, monospace: FontConfiguration) {
19 | self.default = `default`
20 | self.monospace = monospace
21 | }
22 |
23 | public subscript(fontType: FontType) -> FontConfiguration {
24 | switch fontType {
25 | case .monospace:
26 | return monospace
27 | default:
28 | return `default`
29 | }
30 | }
31 | }
32 |
33 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
34 | public extension FontTypeConfiguration {
35 | static let `default` = FontTypeConfiguration(
36 | default: .default,
37 | monospace: .monospace
38 | )
39 | }
40 |
41 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
42 | extension FontTypeConfiguration: Decodable {
43 | private enum CodingKeys: String, CodingKey {
44 | case `default`, monospace
45 | }
46 |
47 | public init(from decoder: Decoder) throws {
48 | let container = try decoder.container(keyedBy: CodingKeys.self)
49 |
50 | `default` = try container.decodeIfPresent(FontConfiguration.self, forKey: .default) ?? .default
51 | monospace = try container.decodeIfPresent(FontConfiguration.self, forKey: .monospace) ?? .monospace
52 | }
53 | }
54 |
55 | #endif
56 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCardUI/Configuration/ShowCardConfiguration.swift:
--------------------------------------------------------------------------------
1 | #if canImport(SwiftUI)
2 |
3 | import AdaptiveCard
4 | import SwiftUI
5 |
6 | /// Controls behavior and styling of `showCard` actions.
7 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
8 | public struct ShowCardConfiguration: Equatable {
9 | /// Indicates the initial container style for the card.
10 | public let style: ContainerStyle
11 |
12 | /// Amount of margin to use when displaying the card.
13 | public let inlineTopMargin: CGFloat
14 |
15 | public init(style: ContainerStyle, inlineTopMargin: CGFloat) {
16 | self.style = style
17 | self.inlineTopMargin = inlineTopMargin
18 | }
19 | }
20 |
21 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
22 | public extension ShowCardConfiguration {
23 | static let `default` = ShowCardConfiguration(
24 | style: .emphasis,
25 | inlineTopMargin: 8
26 | )
27 | }
28 |
29 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
30 | extension ShowCardConfiguration: Decodable {
31 | private enum CodingKeys: String, CodingKey {
32 | case style, inlineTopMargin
33 | }
34 |
35 | public init(from decoder: Decoder) throws {
36 | let container = try decoder.container(keyedBy: CodingKeys.self)
37 |
38 | style = try container.decodeIfPresent(ContainerStyle.self, forKey: .style) ?? Self.default.style
39 | inlineTopMargin = try container.decodeIfPresent(CGFloat.self, forKey: .inlineTopMargin) ?? Self.default.inlineTopMargin
40 | }
41 | }
42 |
43 | #endif
44 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCardUI/Configuration/TextColorPair.swift:
--------------------------------------------------------------------------------
1 | #if canImport(SwiftUI)
2 |
3 | import SwiftUI
4 |
5 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
6 | public struct TextColorPair: Equatable {
7 | /// Color to use when displaying default text.
8 | public let `default`: Color
9 |
10 | /// Color to use when displaying subtle text.
11 | public let subtle: Color
12 |
13 | public init(default: Color, subtle: Color) {
14 | self.default = `default`
15 | self.subtle = subtle
16 | }
17 | }
18 |
19 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
20 | public extension TextColorPair {
21 | static let `default` = TextColorPair(default: .primary, subtle: .secondary)
22 |
23 | static var dark: TextColorPair {
24 | #if os(macOS) || os(tvOS) || os(watchOS)
25 | return TextColorPair(default: .black, subtle: Color.black.subtle)
26 | #else
27 | return TextColorPair(default: Color(.darkText), subtle: Color(.darkText).subtle)
28 | #endif
29 | }
30 |
31 | static let light = TextColorPair(default: .white, subtle: Color.white.subtle)
32 |
33 | static let accent = TextColorPair(default: .accentColor, subtle: Color.accentColor.subtle)
34 |
35 | static let good = TextColorPair(default: .green, subtle: Color.green.subtle)
36 |
37 | static let warning = TextColorPair(default: .yellow, subtle: Color.yellow.subtle)
38 |
39 | static let attention = TextColorPair(default: .red, subtle: Color.red.subtle)
40 | }
41 |
42 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
43 | extension TextColorPair: Decodable {
44 | private enum CodingKeys: String, CodingKey {
45 | case `default`, subtle
46 | }
47 |
48 | public init(from decoder: Decoder) throws {
49 | let container = try decoder.container(keyedBy: CodingKeys.self)
50 |
51 | `default` = (try Color(argbHex: container.decodeIfPresent(String.self, forKey: .default))) ?? .primary
52 | subtle = (try Color(argbHex: container.decodeIfPresent(String.self, forKey: .subtle))) ?? .secondary
53 | }
54 | }
55 |
56 | #endif
57 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCardUI/Internal/Exports.swift:
--------------------------------------------------------------------------------
1 | @_exported import AdaptiveCard
2 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCardUI/Logic/FeatureAdaptation/Action+FeatureAdaptable.swift:
--------------------------------------------------------------------------------
1 | // swiftformat:options --self insert
2 |
3 | import AdaptiveCard
4 | import Foundation
5 |
6 | extension Action: FeatureAdaptable {
7 | func adaptingToFeatures(_ features: [String: SemanticVersion], shouldFallback: inout Bool) -> Action? {
8 | guard features.satisfies(self.requires) else {
9 | return self.fallback.adaptingToFeatures(features, shouldFallback: &shouldFallback)
10 | }
11 |
12 | switch self {
13 | case .openURL, .submit, .toggleVisibility:
14 | return self
15 | case var .showCard(action):
16 | action.card = action.card.adaptingToFeatures(features)
17 | return .showCard(action)
18 | case .unknown:
19 | return self.fallback.adaptingToFeatures(features, shouldFallback: &shouldFallback)
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCardUI/Logic/FeatureAdaptation/AdaptiveCard+FeatureAdaptable.swift:
--------------------------------------------------------------------------------
1 | import AdaptiveCard
2 | import Foundation
3 |
4 | extension AdaptiveCard {
5 | func adaptingToFeatures(_ features: [String: SemanticVersion]) -> AdaptiveCard {
6 | var result = self
7 | var shouldFallback = false
8 |
9 | result.body = body.compactMap { element in
10 | element.adaptingToFeatures(features, shouldFallback: &shouldFallback)
11 | }
12 | result.actions = actions.compactMap { action in
13 | action.adaptingToFeatures(features, shouldFallback: &shouldFallback)
14 | }
15 |
16 | return result
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCardUI/Logic/FeatureAdaptation/ColumnSetElement+FeatureAdaptable.swift:
--------------------------------------------------------------------------------
1 | // swiftformat:options --self insert
2 |
3 | import AdaptiveCard
4 | import Foundation
5 |
6 | extension ColumnSetElement: FeatureAdaptable {
7 | func adaptingToFeatures(_ features: [String: SemanticVersion], shouldFallback: inout Bool) -> ColumnSetElement? {
8 | guard features.satisfies(self.requires) else {
9 | return self.fallback.adaptingToFeatures(features, shouldFallback: &shouldFallback)
10 | }
11 |
12 | switch self {
13 | case var .column(column):
14 | var elementShouldFallback = false
15 | column.items = column.items.compactMap { item in
16 | item.adaptingToFeatures(features, shouldFallback: &elementShouldFallback)
17 | }
18 | if elementShouldFallback {
19 | return column.fallback.adaptingToFeatures(
20 | features,
21 | shouldFallback: &shouldFallback
22 | ) ?? .column(column)
23 | } else {
24 | return .column(column)
25 | }
26 | case .unknown:
27 | return self.fallback.adaptingToFeatures(features, shouldFallback: &shouldFallback)
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCardUI/Logic/FeatureAdaptation/Fallback+FeatureAdaptable.swift:
--------------------------------------------------------------------------------
1 | import AdaptiveCard
2 | import Foundation
3 |
4 | extension Fallback where Element: FeatureAdaptable {
5 | func adaptingToFeatures(_ features: [String: SemanticVersion], shouldFallback: inout Bool) -> Element? {
6 | switch self {
7 | case .none:
8 | shouldFallback = true
9 | return nil
10 | case .drop:
11 | return nil
12 | case let .element(element):
13 | return element.adaptingToFeatures(features, shouldFallback: &shouldFallback)
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCardUI/Logic/FeatureAdaptation/FeatureAdaptable.swift:
--------------------------------------------------------------------------------
1 | import AdaptiveCard
2 | import Foundation
3 |
4 | protocol FeatureAdaptable {
5 | func adaptingToFeatures(_ features: [String: SemanticVersion], shouldFallback: inout Bool) -> Self?
6 | }
7 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCardUI/Logic/Store/AdaptiveCard+DuplicateIdentifiers.swift:
--------------------------------------------------------------------------------
1 | import AdaptiveCard
2 | import Foundation
3 |
4 | extension AdaptiveCard {
5 | var duplicateIdentifiers: Set {
6 | Set(
7 | Dictionary(grouping: identifiers, by: { $0 })
8 | .filter { $1.count > 1 }
9 | .keys
10 | )
11 | }
12 | }
13 |
14 | private extension AdaptiveCard {
15 | var identifiers: [String] {
16 | body.flatMap(\.identifiers) + actions.flatMap(\.identifiers)
17 | }
18 | }
19 |
20 | private extension CardElement {
21 | var identifiers: [String] {
22 | switch self {
23 | case .textBlock, .image, .richTextBlock, .factSet, .custom:
24 | return [id]
25 | case let .actionSet(actionSet):
26 | return [actionSet.id] + actionSet.actions.flatMap(\.identifiers)
27 | case let .container(container):
28 | return [container.id] + container.items.flatMap(\.identifiers)
29 | case let .columnSet(columnSet):
30 | return [columnSet.id] + columnSet.columns.flatMap(\.identifiers)
31 | case let .imageSet(imageSet):
32 | return [imageSet.id] + imageSet.images.map(\.id)
33 | case .unknown:
34 | return []
35 | }
36 | }
37 | }
38 |
39 | private extension Action {
40 | var identifiers: [String] {
41 | switch self {
42 | case let .showCard(action):
43 | return action.card.identifiers
44 | case .openURL, .submit, .toggleVisibility, .unknown:
45 | return []
46 | }
47 | }
48 | }
49 |
50 | private extension ColumnSetElement {
51 | var identifiers: [String] {
52 | switch self {
53 | case let .column(column):
54 | return [column.id] + column.items.flatMap(\.identifiers)
55 | case .unknown:
56 | return []
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCardUI/Logic/Store/AdaptiveCard+ImageURLs.swift:
--------------------------------------------------------------------------------
1 | import AdaptiveCard
2 | import Foundation
3 |
4 | extension AdaptiveCard {
5 | var imageURLs: Set {
6 | Set(_imageURLs)
7 | }
8 | }
9 |
10 | private extension AdaptiveCard {
11 | var _imageURLs: [URL] {
12 | body.flatMap(\.imageURLs) +
13 | actions.flatMap(\.imageURLs) +
14 | [backgroundImage?.url].compactMap { $0 }
15 | }
16 | }
17 |
18 | private extension CardElement {
19 | var imageURLs: [URL] {
20 | switch self {
21 | case .textBlock, .richTextBlock, .factSet, .custom, .unknown:
22 | return []
23 | case let .image(image):
24 | return [image.url]
25 | case let .actionSet(actionSet):
26 | return actionSet.actions.flatMap(\.imageURLs)
27 | case let .container(container):
28 | return container.items.flatMap(\.imageURLs) +
29 | [container.backgroundImage?.url].compactMap { $0 }
30 | case let .columnSet(columnSet):
31 | return columnSet.columns.flatMap(\.imageURLs)
32 | case let .imageSet(imageSet):
33 | return imageSet.images.map(\.url)
34 | }
35 | }
36 | }
37 |
38 | private extension Action {
39 | var imageURLs: [URL] {
40 | switch self {
41 | case let .showCard(action):
42 | return [action.iconURL].compactMap { $0 } + action.card._imageURLs
43 | case let .openURL(action):
44 | return [action.iconURL].compactMap { $0 }
45 | case let .submit(action):
46 | return [action.iconURL].compactMap { $0 }
47 | case let .toggleVisibility(action):
48 | return [action.iconURL].compactMap { $0 }
49 | case .unknown:
50 | return []
51 | }
52 | }
53 | }
54 |
55 | private extension ColumnSetElement {
56 | var imageURLs: [URL] {
57 | switch self {
58 | case let .column(column):
59 | return column.items.flatMap(\.imageURLs) +
60 | [column.backgroundImage?.url].compactMap { $0 }
61 | case .unknown:
62 | return []
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCardUI/Logic/Store/AdaptiveCardCache.swift:
--------------------------------------------------------------------------------
1 | import AdaptiveCard
2 | import Foundation
3 |
4 | protocol AdaptiveCardCache: AnyObject {
5 | func adaptiveCard(for url: URL) -> AdaptiveCard?
6 | func setAdaptiveCard(_ adaptiveCard: AdaptiveCard, for url: URL)
7 | }
8 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCardUI/Logic/Store/AdaptiveCardDownloader.swift:
--------------------------------------------------------------------------------
1 | #if canImport(Combine)
2 |
3 | import AdaptiveCard
4 | import Combine
5 | import Foundation
6 |
7 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
8 | final class AdaptiveCardDownloader {
9 | private let session: URLSession
10 | private let adaptiveCardCache: AdaptiveCardCache
11 |
12 | static let shared = AdaptiveCardDownloader(
13 | session: URLSession(configuration: .default),
14 | adaptiveCardCache: ImmediateAdaptiveCardCache()
15 | )
16 |
17 | init(session: URLSession, adaptiveCardCache: AdaptiveCardCache) {
18 | self.session = session
19 | self.adaptiveCardCache = adaptiveCardCache
20 | }
21 |
22 | func adaptiveCard(for url: URL) -> AnyPublisher {
23 | if let adaptiveCard = adaptiveCardCache.adaptiveCard(for: url) {
24 | return Just(adaptiveCard)
25 | .setFailureType(to: Error.self)
26 | .eraseToAnyPublisher()
27 | } else {
28 | return session.dataTaskPublisher(for: url)
29 | .map(\.data)
30 | .decode(type: AdaptiveCard.self, decoder: JSONDecoder())
31 | .handleEvents(receiveOutput: { [adaptiveCardCache] adaptiveCard in
32 | adaptiveCardCache.setAdaptiveCard(adaptiveCard, for: url)
33 | })
34 | .eraseToAnyPublisher()
35 | }
36 | }
37 | }
38 |
39 | #endif
40 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCardUI/Logic/Store/ImmediateAdaptiveCardCache.swift:
--------------------------------------------------------------------------------
1 | import AdaptiveCard
2 | import Foundation
3 |
4 | final class ImmediateAdaptiveCardCache: AdaptiveCardCache {
5 | private class AdaptiveCardBox {
6 | let value: AdaptiveCard
7 |
8 | init(value: AdaptiveCard) {
9 | self.value = value
10 | }
11 | }
12 |
13 | private let cache = NSCache()
14 |
15 | func adaptiveCard(for url: URL) -> AdaptiveCard? {
16 | cache.object(forKey: url as NSURL)?.value
17 | }
18 |
19 | func setAdaptiveCard(_ adaptiveCard: AdaptiveCard, for url: URL) {
20 | cache.setObject(AdaptiveCardBox(value: adaptiveCard), forKey: url as NSURL)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCardUI/Logic/ToggleVisibility/AdaptiveCard+Toggleable.swift:
--------------------------------------------------------------------------------
1 | import AdaptiveCard
2 | import Foundation
3 |
4 | extension AdaptiveCard {
5 | mutating func toggleVisibility(of targetElements: [TargetElement]) {
6 | for target in targetElements {
7 | body.toggleVisibility(of: target)
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCardUI/Logic/ToggleVisibility/ColumnSetElement+Toggleable.swift:
--------------------------------------------------------------------------------
1 | import AdaptiveCard
2 | import Foundation
3 |
4 | extension ColumnSetElement: Toggleable {
5 | mutating func toggleVisibility(of target: TargetElement) -> Bool {
6 | guard case var .column(column) = self else {
7 | return false
8 | }
9 |
10 | var visibilityChanged = false
11 |
12 | if column.id == target.elementId {
13 | if let isVisible = target.isVisible {
14 | column.isVisible = isVisible
15 | } else {
16 | column.isVisible.toggle()
17 | }
18 | visibilityChanged = true
19 | } else {
20 | visibilityChanged = column.items.toggleVisibility(of: target)
21 | }
22 |
23 | if visibilityChanged {
24 | self = .column(column)
25 | }
26 |
27 | return visibilityChanged
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCardUI/Logic/ToggleVisibility/Image+Toggleable.swift:
--------------------------------------------------------------------------------
1 | import AdaptiveCard
2 | import Foundation
3 |
4 | extension Image: Toggleable {
5 | mutating func toggleVisibility(of target: TargetElement) -> Bool {
6 | guard id == target.elementId else { return false }
7 |
8 | if let isVisible = target.isVisible {
9 | self.isVisible = isVisible
10 | } else {
11 | isVisible.toggle()
12 | }
13 |
14 | return true
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCardUI/Logic/ToggleVisibility/Toggleable.swift:
--------------------------------------------------------------------------------
1 | import AdaptiveCard
2 | import Foundation
3 |
4 | protocol Toggleable {
5 | mutating func toggleVisibility(of target: TargetElement) -> Bool
6 | }
7 |
8 | extension Array where Element: Toggleable {
9 | @discardableResult mutating func toggleVisibility(of target: TargetElement) -> Bool {
10 | for index in indices {
11 | if self[index].toggleVisibility(of: target) {
12 | return true
13 | }
14 | }
15 |
16 | return false
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCardUI/UI/ActionSet/ActionView.swift:
--------------------------------------------------------------------------------
1 | #if canImport(SwiftUI)
2 |
3 | import AdaptiveCard
4 | import SwiftUI
5 |
6 | @available(macOS 11.0, iOS 14.0, tvOS 14.0, watchOS 7.0, *)
7 | struct ActionView: View {
8 | @Environment(\.openURL) private var openURL
9 | @EnvironmentObject private var store: AdaptiveCardStore
10 |
11 | @Binding private var selection: Int?
12 |
13 | private let action: Action
14 | private let tag: Int
15 |
16 | init(_ action: Action, tag: Int, selection: Binding) {
17 | self.action = action
18 | self.tag = tag
19 | _selection = selection
20 | }
21 |
22 | var body: some View {
23 | Button(action.title) {
24 | switch action {
25 | case let .openURL(openURLAction):
26 | openURL(openURLAction.url)
27 | case .showCard:
28 | withAnimation {
29 | if selection != tag {
30 | selection = tag
31 | } else {
32 | selection = nil
33 | }
34 | }
35 | case .submit:
36 | // TODO: implement
37 | break
38 | case let .toggleVisibility(toggleVisibilityAction):
39 | store.send(.toggleVisibility(toggleVisibilityAction.targetElements))
40 | case .unknown:
41 | // Do nothing
42 | break
43 | }
44 | }
45 | }
46 | }
47 |
48 | #endif
49 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCardUI/UI/ActionSet/CapsuleButtonModifier.swift:
--------------------------------------------------------------------------------
1 | #if canImport(SwiftUI)
2 |
3 | import SwiftUI
4 |
5 | @available(macOS 11.0, iOS 14.0, tvOS 14.0, watchOS 7.0, *)
6 | struct CapsuleButtonModifier: ViewModifier {
7 | private enum Defaults {
8 | static let contentInsets = EdgeInsets(top: 8, leading: 12, bottom: 8, trailing: 12)
9 | static let minHeight: CGFloat = 30
10 | static let pressedOpacity = 0.8
11 | }
12 |
13 | @Environment(\.actionSetConfiguration) private var actionSetConfiguration
14 |
15 | private let isPressed: Bool
16 |
17 | init(isPressed: Bool) {
18 | self.isPressed = isPressed
19 | }
20 |
21 | func body(content: Content) -> some View {
22 | content
23 | .textCase(.uppercase)
24 | .font(Font.caption.weight(.semibold))
25 | .foregroundColor(.white)
26 | .padding(Defaults.contentInsets)
27 | .frame(
28 | maxWidth: actionSetConfiguration.actionAlignment == .stretch ? .infinity : nil,
29 | minHeight: Defaults.minHeight
30 | )
31 | .background(
32 | Capsule().foregroundColor(.accentColor)
33 | )
34 | .opacity(isPressed ? Defaults.pressedOpacity : 1)
35 | }
36 | }
37 |
38 | #endif
39 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCardUI/UI/ActionSet/CapsuleButtonStyle.swift:
--------------------------------------------------------------------------------
1 | #if canImport(SwiftUI)
2 |
3 | import SwiftUI
4 |
5 | @available(macOS 11.0, iOS 14.0, tvOS 14.0, watchOS 7.0, *)
6 | public struct CapsuleButtonStyle: ButtonStyle {
7 | public init() {}
8 |
9 | public func makeBody(configuration: Configuration) -> some View {
10 | configuration.label
11 | .modifier(CapsuleButtonModifier(isPressed: configuration.isPressed))
12 | }
13 | }
14 |
15 | #endif
16 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCardUI/UI/AdaptiveCard/AdaptiveCardFeatures.swift:
--------------------------------------------------------------------------------
1 | #if canImport(SwiftUI)
2 |
3 | import AdaptiveCard
4 | import SwiftUI
5 |
6 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
7 | public extension View {
8 | /// Sets the supported features for adaptive cards within this view.
9 | func adaptiveCardFeatures(_ features: [String: SemanticVersion]) -> some View {
10 | environment(\.adaptiveCardFeatures, features)
11 | }
12 | }
13 |
14 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
15 | public extension EnvironmentValues {
16 | /// The adaptive card supported features of this environment.
17 | var adaptiveCardFeatures: [String: SemanticVersion] {
18 | get { self[AdaptiveCardFeaturesKey.self] }
19 | set {
20 | self[AdaptiveCardFeaturesKey.self] = newValue
21 | .merging(AdaptiveCardFeaturesKey.defaultValue) { _, b in b }
22 | }
23 | }
24 | }
25 |
26 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
27 | private struct AdaptiveCardFeaturesKey: EnvironmentKey {
28 | enum Constants {
29 | static let adaptiveCardFeature = "adaptiveCards"
30 | }
31 |
32 | static let defaultValue: [String: SemanticVersion] = [
33 | Constants.adaptiveCardFeature: adaptiveCardVersion,
34 | ]
35 | }
36 |
37 | #endif
38 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCardUI/UI/AdaptiveCard/AdaptiveCardSourceView.swift:
--------------------------------------------------------------------------------
1 | #if canImport(SwiftUI)
2 |
3 | import AdaptiveCard
4 | import Combine
5 | import SwiftUI
6 |
7 | enum AdaptiveCardSource {
8 | case url(URL)
9 | case value(AdaptiveCard)
10 | }
11 |
12 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
13 | extension AdaptiveCardStore: ObservableObject {}
14 |
15 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
16 | extension AdaptiveCardStore {
17 | convenience init(source: AdaptiveCardSource, features: [String: SemanticVersion]) {
18 | self.init(environment: Environment(features: features))
19 |
20 | switch source {
21 | case let .url(url):
22 | send(.didSetURL(url))
23 | case let .value(value):
24 | send(.didLoadAdaptiveCard(value))
25 | }
26 | }
27 | }
28 |
29 | @available(macOS 11.0, iOS 14.0, tvOS 14.0, watchOS 7.0, *)
30 | struct AdaptiveCardSourceView: View {
31 | @StateObject private var store: AdaptiveCardStore
32 |
33 | init(source: AdaptiveCardSource, features: [String: SemanticVersion]) {
34 | _store = StateObject(
35 | wrappedValue: AdaptiveCardStore(
36 | source: source,
37 | features: features
38 | )
39 | )
40 | }
41 |
42 | var body: some View {
43 | switch store.state {
44 | case .notRequested, .loading:
45 | EmptyView()
46 | case let .adaptiveCard(adaptiveCard):
47 | PrimitiveCardView(adaptiveCard)
48 | .environmentObject(store)
49 | case let .fallbackText(text):
50 | ErrorView(text: text)
51 | case .failed:
52 | ErrorView(text: NSLocalizedString("Error.render", bundle: .module, comment: ""))
53 | }
54 | }
55 | }
56 |
57 | #endif
58 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCardUI/UI/AdaptiveCard/ErrorView.swift:
--------------------------------------------------------------------------------
1 | #if canImport(SwiftUI)
2 |
3 | import SwiftUI
4 |
5 | @available(macOS 11.0, iOS 14.0, tvOS 14.0, watchOS 7.0, *)
6 | struct ErrorView: View {
7 | @Environment(\.locale) private var locale
8 | @Environment(\.fontTypeConfiguration) private var fontTypeConfiguration
9 | @Environment(\.spacingConfiguration) private var spacingConfiguration
10 | @Environment(\.containerStyleConfiguration) private var containerStyleConfiguration
11 | @Environment(\.imageSizeConfiguration) private var imageSizeConfiguration
12 |
13 | private let text: String
14 |
15 | init(text: String) {
16 | self.text = text
17 | }
18 |
19 | var body: some View {
20 | VStack(spacing: spacingConfiguration.medium) {
21 | SwiftUI.Image(systemName: "rectangle.badge.xmark")
22 | .resizable()
23 | .aspectRatio(contentMode: .fit)
24 | .frame(width: imageSizeConfiguration.small)
25 | .foregroundColor(containerStyleConfiguration.emphasis.textColors.default.subtle)
26 | Text(parsing: text, locale: locale)
27 | .font(fontTypeConfiguration.default.default)
28 | .foregroundColor(
29 | containerStyleConfiguration.emphasis.textColors.default.subtle
30 | )
31 | .multilineTextAlignment(.center)
32 | .fixedSize(horizontal: false, vertical: true)
33 | }
34 | .padding(spacingConfiguration.padding)
35 | .background(containerStyleConfiguration.emphasis.backgroundColor)
36 | }
37 | }
38 |
39 | #endif
40 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCardUI/UI/BackgroundImage/BackgroundImageStyle.swift:
--------------------------------------------------------------------------------
1 | #if canImport(SwiftUI)
2 |
3 | import AdaptiveCard
4 | import NetworkImage
5 | import SwiftUI
6 |
7 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
8 | struct BackgroundImageStyle: NetworkImageStyle {
9 | private let fillMode: ImageFillMode
10 | private let horizontalAlignment: HAlignment
11 | private let verticalAlignment: VAlignment
12 |
13 | init(
14 | fillMode: ImageFillMode,
15 | horizontalAlignment: HAlignment,
16 | verticalAlignment: VAlignment
17 | ) {
18 | self.fillMode = fillMode
19 | self.horizontalAlignment = horizontalAlignment
20 | self.verticalAlignment = verticalAlignment
21 | }
22 |
23 | func makeBody(state: NetworkImageState) -> some View {
24 | switch state {
25 | case .loading, .failed:
26 | EmptyView()
27 | case let .image(image, size):
28 | switch fillMode {
29 | case .repeat:
30 | image.resizable(resizingMode: .tile)
31 | case .repeatHorizontally:
32 | RepeatHorizontallyImageView(
33 | image: image,
34 | size: size,
35 | verticalAlignment: verticalAlignment
36 | )
37 | case .repeatVertically:
38 | RepeatVerticallyImageView(
39 | image: image,
40 | size: size,
41 | horizontalAlignment: horizontalAlignment
42 | )
43 | default:
44 | CoverImageView(
45 | image: image,
46 | aspectRatio: size.width / size.height,
47 | horizontalAlignment: horizontalAlignment,
48 | verticalAlignment: verticalAlignment
49 | )
50 | }
51 | }
52 | }
53 | }
54 |
55 | #endif
56 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCardUI/UI/BackgroundImage/BackgroundImageView.swift:
--------------------------------------------------------------------------------
1 | #if canImport(SwiftUI)
2 |
3 | import AdaptiveCard
4 | import NetworkImage
5 | import SwiftUI
6 |
7 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
8 | struct BackgroundImageView: View {
9 | private let backgroundImage: BackgroundImage?
10 | private let content: Content
11 |
12 | init(_ backgroundImage: BackgroundImage?, for content: Content) {
13 | self.backgroundImage = backgroundImage
14 | self.content = content
15 | }
16 |
17 | var body: some View {
18 | if let backgroundImage = self.backgroundImage {
19 | content
20 | .background(NetworkImage(url: backgroundImage.url))
21 | .networkImageStyle(
22 | BackgroundImageStyle(
23 | fillMode: backgroundImage.fillMode,
24 | horizontalAlignment: backgroundImage.horizontalAlignment,
25 | verticalAlignment: backgroundImage.verticalAlignment
26 | )
27 | )
28 | } else {
29 | content
30 | }
31 | }
32 | }
33 |
34 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
35 | extension View {
36 | func backgroundImage(_ backgroundImage: BackgroundImage?) -> some View {
37 | BackgroundImageView(backgroundImage, for: self)
38 | }
39 | }
40 |
41 | #endif
42 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCardUI/UI/BackgroundImage/RepeatHorizontallyImageView.swift:
--------------------------------------------------------------------------------
1 | #if canImport(SwiftUI)
2 |
3 | import AdaptiveCard
4 | import SwiftUI
5 |
6 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
7 | struct RepeatHorizontallyImageView: View {
8 | private let image: SwiftUI.Image
9 | private let size: CGSize
10 | private let verticalAlignment: VAlignment
11 |
12 | init(
13 | image: SwiftUI.Image,
14 | size: CGSize,
15 | verticalAlignment: VAlignment
16 | ) {
17 | self.image = image
18 | self.size = size
19 | self.verticalAlignment = verticalAlignment
20 | }
21 |
22 | var body: some View {
23 | GeometryReader { proxy in
24 | makeBody(for: proxy.size)
25 | }
26 | .clipped()
27 | }
28 |
29 | func makeBody(for proposedSize: CGSize) -> some View {
30 | let count = Int(ceil(proposedSize.width / size.width))
31 | let offset = verticalAlignment.offset(for: proposedSize, contentSize: size)
32 |
33 | return HStack(spacing: 0) {
34 | ForEach(1 ... count, id: \.self) { _ in
35 | image
36 | }
37 | }
38 | .offset(offset)
39 | }
40 | }
41 |
42 | #endif
43 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCardUI/UI/BackgroundImage/RepeatVerticallyImageView.swift:
--------------------------------------------------------------------------------
1 | #if canImport(SwiftUI)
2 |
3 | import AdaptiveCard
4 | import SwiftUI
5 |
6 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
7 | struct RepeatVerticallyImageView: View {
8 | private let image: SwiftUI.Image
9 | private let size: CGSize
10 | private let horizontalAlignment: HAlignment
11 |
12 | init(
13 | image: SwiftUI.Image,
14 | size: CGSize,
15 | horizontalAlignment: HAlignment
16 | ) {
17 | self.image = image
18 | self.size = size
19 | self.horizontalAlignment = horizontalAlignment
20 | }
21 |
22 | var body: some View {
23 | GeometryReader { proxy in
24 | makeBody(for: proxy.size)
25 | }
26 | .clipped()
27 | }
28 |
29 | func makeBody(for proposedSize: CGSize) -> some View {
30 | let count = Int(ceil(proposedSize.height / size.height))
31 | let offset = horizontalAlignment.offset(for: proposedSize, contentSize: size)
32 |
33 | return VStack(spacing: 0) {
34 | ForEach(1 ... count, id: \.self) { _ in
35 | image
36 | }
37 | }
38 | .offset(offset)
39 | }
40 | }
41 |
42 | #endif
43 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCardUI/UI/CardElement/CardElementView.swift:
--------------------------------------------------------------------------------
1 | #if canImport(SwiftUI)
2 |
3 | import AdaptiveCard
4 | import SwiftUI
5 |
6 | @available(macOS 11.0, iOS 14.0, tvOS 14.0, watchOS 7.0, *)
7 | struct CardElementView: View {
8 | private let cardElement: CardElement
9 |
10 | init(_ cardElement: CardElement) {
11 | self.cardElement = cardElement
12 | }
13 |
14 | var body: some View {
15 | switch cardElement {
16 | case let .textBlock(textBlock):
17 | TextBlockView(textBlock)
18 | case let .image(imageModel):
19 | ImageView(imageModel)
20 | case let .richTextBlock(richTextBlock):
21 | RichTextBlockView(richTextBlock)
22 | case let .actionSet(actionSet):
23 | ActionSetView(actionSet)
24 | case let .container(container):
25 | ContainerView(container)
26 | case let .columnSet(columnSet):
27 | ColumnSetView(columnSet)
28 | case let .factSet(factSet):
29 | FactSetView(factSet)
30 | case let .imageSet(imageSet):
31 | ImageSetView(imageSet)
32 | case let .custom(customCardElement):
33 | CustomCardElementView(customCardElement)
34 | default:
35 | EmptyView()
36 | }
37 | }
38 | }
39 |
40 | #endif
41 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCardUI/UI/CardElement/CustomCardElementView.swift:
--------------------------------------------------------------------------------
1 | #if canImport(SwiftUI)
2 |
3 | import AdaptiveCard
4 | import SwiftUI
5 |
6 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
7 | struct CustomCardElementView: View {
8 | @Environment(\.customCardElementViewFactories) private var customCardElementViewFactories
9 |
10 | private let customCardElement: CustomCardElement
11 |
12 | init(_ customCardElement: CustomCardElement) {
13 | self.customCardElement = customCardElement
14 | }
15 |
16 | var body: some View {
17 | if let viewFactory = customCardElementViewFactories[type(of: customCardElement).typeName] {
18 | viewFactory(customCardElement)
19 | }
20 | }
21 | }
22 |
23 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
24 | public extension View {
25 | /// Sets the view that renders a custom card element within this view.
26 | func customCardElement(
27 | _: Element.Type = Element.self,
28 | content: @escaping (Element) -> Content
29 | ) -> some View where Element: CustomCardElement, Content: View {
30 | environment(
31 | \.customCardElementViewFactories,
32 | [Element.typeName: { AnyView(content($0 as! Element)) }]
33 | )
34 | }
35 | }
36 |
37 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
38 | private extension EnvironmentValues {
39 | var customCardElementViewFactories: [String: CustomCardElementViewFactory] {
40 | get { self[CustomCardElementViewFactoriesKey.self] }
41 | set {
42 | self[CustomCardElementViewFactoriesKey.self]
43 | .merge(newValue) { _, b in b }
44 | }
45 | }
46 | }
47 |
48 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
49 | private typealias CustomCardElementViewFactory = (CustomCardElement) -> AnyView
50 |
51 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
52 | private struct CustomCardElementViewFactoriesKey: EnvironmentKey {
53 | static let defaultValue: [String: CustomCardElementViewFactory] = [:]
54 | }
55 |
56 | #endif
57 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCardUI/UI/CardElement/SpacingCardElementView.swift:
--------------------------------------------------------------------------------
1 | #if canImport(SwiftUI)
2 |
3 | import AdaptiveCard
4 | import SwiftUI
5 |
6 | @available(macOS 11.0, iOS 14.0, tvOS 14.0, watchOS 7.0, *)
7 | struct SpacingCardElementView: View {
8 | @Environment(\.spacingConfiguration) private var spacingConfiguration
9 |
10 | private let cardElement: CardElement
11 |
12 | init(_ cardElement: CardElement) {
13 | self.cardElement = cardElement
14 | }
15 |
16 | var body: some View {
17 | VStack(spacing: 0) {
18 | if cardElement.separator {
19 | Divider()
20 | .padding(.vertical, spacingConfiguration[cardElement.spacing] * 0.5)
21 | CardElementView(cardElement)
22 | } else {
23 | CardElementView(cardElement)
24 | .padding(.top, spacingConfiguration[cardElement.spacing])
25 | }
26 | }
27 | }
28 | }
29 |
30 | #endif
31 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCardUI/UI/Color/Color+ARGBHex.swift:
--------------------------------------------------------------------------------
1 | #if canImport(SwiftUI)
2 | import SwiftUI
3 |
4 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
5 | public extension Color {
6 | /// Creates a color from an RGB or an ARGB hexadecimal string.
7 | init?(argbHex: String?) {
8 | guard let argbHex = argbHex else {
9 | return nil
10 | }
11 |
12 | let scanner = Scanner(string: argbHex)
13 | var value: UInt64 = 0
14 |
15 | guard scanner.scanString("#") != nil else { return nil }
16 | guard scanner.scanHexInt64(&value) else { return nil }
17 |
18 | let a, r, g, b: Double
19 |
20 | switch argbHex.count {
21 | case 7: a = 1
22 | case 9: a = Double((value & 0xFF00_0000) >> 24) / 255.0
23 | default: return nil
24 | }
25 |
26 | r = Double((value & 0x00FF_0000) >> 16) / 255.0
27 | g = Double((value & 0x0000_FF00) >> 8) / 255.0
28 | b = Double(value & 0x0000_00FF) / 255.0
29 |
30 | self.init(red: r, green: g, blue: b, opacity: a)
31 | }
32 | }
33 | #endif
34 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCardUI/UI/ColumnSet/SpacingColumnView.swift:
--------------------------------------------------------------------------------
1 | #if canImport(SwiftUI)
2 |
3 | import AdaptiveCard
4 | import SwiftUI
5 |
6 | @available(macOS 11.0, iOS 14.0, tvOS 14.0, watchOS 7.0, *)
7 | struct SpacingColumnView: View {
8 | @Environment(\.spacingConfiguration) private var spacingConfiguration
9 |
10 | private let column: Column
11 | private let minWidth: CGFloat?
12 | private let height: CGFloat?
13 |
14 | init(column: Column, minWidth: CGFloat?, height: CGFloat?) {
15 | self.column = column
16 | self.minWidth = minWidth
17 | self.height = height
18 | }
19 |
20 | var body: some View {
21 | HStack(spacing: 0) {
22 | if column.separator {
23 | Divider()
24 | .padding(.horizontal, spacingConfiguration[column.spacing] * 0.5)
25 | ColumnView(column: column, minWidth: minWidth, height: height)
26 | } else {
27 | ColumnView(column: column, minWidth: minWidth, height: height)
28 | .padding(.leading, spacingConfiguration[column.spacing])
29 | }
30 | }
31 | }
32 | }
33 |
34 | #endif
35 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCardUI/UI/Container/ContainerView.swift:
--------------------------------------------------------------------------------
1 | #if canImport(SwiftUI)
2 |
3 | import AdaptiveCard
4 | import SwiftUI
5 |
6 | @available(macOS 11.0, iOS 14.0, tvOS 14.0, watchOS 7.0, *)
7 | struct ContainerView: View {
8 | @Environment(\.containerStyleConfiguration) private var containerStyleConfiguration
9 | @Environment(\.spacingConfiguration) private var spacingConfiguration
10 | @Environment(\.containerStyle) private var parentContainerStyle
11 |
12 | private let container: Container
13 |
14 | private var padding: CGFloat {
15 | if container.backgroundImage != nil {
16 | return spacingConfiguration.padding
17 | } else {
18 | switch (container.style, parentContainerStyle) {
19 | case (.none, _), (ContainerStyle.default, ContainerStyle.default):
20 | return 0
21 | case (.some, _):
22 | return spacingConfiguration.padding
23 | }
24 | }
25 | }
26 |
27 | private var backgroundColor: Color? {
28 | container.style.flatMap {
29 | containerStyleConfiguration[$0].backgroundColor
30 | }
31 | }
32 |
33 | init(_ container: Container) {
34 | self.container = container
35 | }
36 |
37 | var body: some View {
38 | CardElementList(container.items, padding: padding)
39 | .containerStyle(container.style)
40 | .frame(
41 | minHeight: container.minHeight?.cgFloatValue,
42 | alignment: Alignment(container.verticalContentAlignment)
43 | )
44 | .backgroundImage(container.backgroundImage)
45 | .background(backgroundColor)
46 | .selectAction(container.selectAction)
47 | }
48 | }
49 |
50 | #endif
51 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCardUI/UI/Container/EnvironmentValues+ContainerStyle.swift:
--------------------------------------------------------------------------------
1 | #if canImport(SwiftUI)
2 |
3 | import AdaptiveCard
4 | import SwiftUI
5 |
6 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
7 | public extension EnvironmentValues {
8 | var containerStyle: ContainerStyle {
9 | get { self[ContainerStyleKey.self] }
10 | set { self[ContainerStyleKey.self] = newValue }
11 | }
12 | }
13 |
14 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
15 | internal extension View {
16 | @ViewBuilder func containerStyle(_ containerStyle: ContainerStyle?) -> some View {
17 | if let containerStyle = containerStyle {
18 | environment(\.containerStyle, containerStyle)
19 | } else {
20 | self
21 | }
22 | }
23 | }
24 |
25 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
26 | private struct ContainerStyleKey: EnvironmentKey {
27 | static let defaultValue: ContainerStyle = .default
28 | }
29 |
30 | #endif
31 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCardUI/UI/Helpers/Alignment+VAlignment.swift:
--------------------------------------------------------------------------------
1 | #if canImport(SwiftUI)
2 |
3 | import AdaptiveCard
4 | import SwiftUI
5 |
6 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
7 | extension Alignment {
8 | init(_ verticalAlignment: VAlignment) {
9 | switch verticalAlignment {
10 | case .center:
11 | self = .center
12 | case .bottom:
13 | self = .bottom
14 | default:
15 | self = .top
16 | }
17 | }
18 | }
19 |
20 | #endif
21 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCardUI/UI/Helpers/BlockElementHeight+CGFloat.swift:
--------------------------------------------------------------------------------
1 | #if canImport(SwiftUI)
2 |
3 | import AdaptiveCard
4 | import SwiftUI
5 |
6 | extension BlockElementHeight {
7 | var cgFloatValue: CGFloat? {
8 | switch self {
9 | case .auto, .stretch:
10 | return nil
11 | case let .pixels(value):
12 | return CGFloat(value)
13 | }
14 | }
15 | }
16 |
17 | #endif
18 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCardUI/UI/Helpers/CollectSizeModifier.swift:
--------------------------------------------------------------------------------
1 | #if canImport(SwiftUI)
2 |
3 | import SwiftUI
4 |
5 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
6 | private struct CollectSizeModifier: ViewModifier {
7 | private let tag: Tag
8 |
9 | init(_ tag: Tag) {
10 | self.tag = tag
11 | }
12 |
13 | func body(content: Content) -> some View {
14 | content.background(
15 | GeometryReader { proxy in
16 | Color.clear.preference(
17 | key: CollectSizePreference.self,
18 | value: [tag: proxy.size]
19 | )
20 | }
21 | )
22 | }
23 | }
24 |
25 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
26 | private struct CollectSizePreference: PreferenceKey {
27 | static var defaultValue: [Tag: CGSize] {
28 | [:]
29 | }
30 |
31 | static func reduce(
32 | value: inout [Tag: CGSize],
33 | nextValue: () -> [Tag: CGSize]
34 | ) {
35 | value.merge(nextValue(), uniquingKeysWith: { $1 })
36 | }
37 | }
38 |
39 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
40 | extension View {
41 | func collectSize(tag: Tag) -> some View {
42 | modifier(CollectSizeModifier(tag))
43 | }
44 |
45 | func onCollectedSizesChange(perform action: @escaping ([Tag: CGSize]) -> Void) -> some View {
46 | onPreferenceChange(CollectSizePreference.self, perform: action)
47 | }
48 | }
49 |
50 | #endif
51 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCardUI/UI/Helpers/FlowLayout.swift:
--------------------------------------------------------------------------------
1 | #if canImport(SwiftUI)
2 |
3 | import SwiftUI
4 |
5 | // Adapted from https://gist.github.com/chriseidhof/3c6ea3fb2102052d1898d8ea27fbee07
6 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
7 | struct FlowLayout {
8 | private let proposedSize: CGSize
9 | private let horizontalSpacing: CGFloat
10 | private let verticalSpacing: CGFloat
11 |
12 | private var position = CGPoint.zero
13 | private var lineHeight: CGFloat = 0
14 |
15 | var size: CGSize {
16 | CGSize(width: proposedSize.width, height: position.y + lineHeight)
17 | }
18 |
19 | init(proposedSize: CGSize, horizontalSpacing: CGFloat, verticalSpacing: CGFloat) {
20 | self.proposedSize = proposedSize
21 | self.horizontalSpacing = horizontalSpacing
22 | self.verticalSpacing = verticalSpacing
23 | }
24 |
25 | mutating func addElementWithSize(_ size: CGSize) -> CGRect {
26 | if position.x + size.width > proposedSize.width {
27 | position.x = 0
28 | position.y += lineHeight + verticalSpacing
29 | lineHeight = 0
30 | }
31 |
32 | let result = CGRect(origin: position, size: size)
33 |
34 | lineHeight = max(lineHeight, size.height)
35 | position.x += size.width + horizontalSpacing
36 |
37 | return result
38 | }
39 | }
40 |
41 | #endif
42 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCardUI/UI/Helpers/HAlign.swift:
--------------------------------------------------------------------------------
1 | #if canImport(SwiftUI)
2 |
3 | import AdaptiveCard
4 | import SwiftUI
5 |
6 | /// A view that aligns its content horizontally.
7 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
8 | public struct HAlign: View {
9 | private let horizontalAlignment: HAlignment?
10 | private let content: Content
11 |
12 | public init(
13 | _ horizontalAlignment: HAlignment?,
14 | @ViewBuilder content: () -> Content
15 | ) {
16 | self.horizontalAlignment = horizontalAlignment
17 | self.content = content()
18 | }
19 |
20 | public var body: some View {
21 | HStack(spacing: 0) {
22 | switch horizontalAlignment {
23 | case HAlignment.left:
24 | content
25 | Spacer()
26 | case HAlignment.center:
27 | Spacer()
28 | content
29 | Spacer()
30 | case HAlignment.right:
31 | Spacer()
32 | content
33 | default:
34 | content
35 | }
36 | }
37 | }
38 | }
39 |
40 | #endif
41 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCardUI/UI/Helpers/HAlignment+Offset.swift:
--------------------------------------------------------------------------------
1 | #if canImport(SwiftUI)
2 |
3 | import AdaptiveCard
4 | import SwiftUI
5 |
6 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
7 | extension HAlignment {
8 | func offset(for proposedSize: CGSize, contentSize: CGSize) -> CGSize {
9 | switch self {
10 | case .center:
11 | return CGSize(width: (proposedSize.width - contentSize.width) * 0.5, height: 0)
12 | case .right:
13 | return CGSize(width: proposedSize.width - contentSize.width, height: 0)
14 | default:
15 | return .zero
16 | }
17 | }
18 | }
19 |
20 | #endif
21 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCardUI/UI/Helpers/Padded.swift:
--------------------------------------------------------------------------------
1 | #if canImport(SwiftUI)
2 |
3 | import SwiftUI
4 |
5 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
6 | struct Padded- {
7 | var item: Item
8 | var edges: Edge.Set
9 | var length: CGFloat
10 | }
11 |
12 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
13 | extension Padded: Identifiable where Item: Identifiable {
14 | var id: Item.ID {
15 | item.id
16 | }
17 | }
18 |
19 | #endif
20 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCardUI/UI/Helpers/PixelDimension+CGFloat.swift:
--------------------------------------------------------------------------------
1 | #if canImport(SwiftUI)
2 |
3 | import AdaptiveCard
4 | import SwiftUI
5 |
6 | extension PixelDimension {
7 | var cgFloatValue: CGFloat {
8 | CGFloat(value)
9 | }
10 | }
11 |
12 | #endif
13 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCardUI/UI/Helpers/SelectActionModifier.swift:
--------------------------------------------------------------------------------
1 | #if canImport(SwiftUI)
2 |
3 | import AdaptiveCard
4 | import SwiftUI
5 |
6 | @available(macOS 11.0, iOS 14.0, tvOS 14.0, watchOS 7.0, *)
7 | private struct SelectActionModifier: ViewModifier {
8 | @Environment(\.openURL) private var openURL
9 | @EnvironmentObject private var store: AdaptiveCardStore
10 |
11 | private let action: Action?
12 |
13 | init(_ action: Action?) {
14 | self.action = action
15 | }
16 |
17 | @ViewBuilder func body(content: Content) -> some View {
18 | if let action = self.action {
19 | Button(
20 | action: {
21 | perform(action)
22 | },
23 | label: {
24 | content
25 | }
26 | )
27 | .buttonStyle(PlainButtonStyle())
28 | } else {
29 | content
30 | }
31 | }
32 |
33 | private func perform(_ action: Action) {
34 | switch action {
35 | case let .openURL(openURLAction):
36 | openURL(openURLAction.url)
37 | case .showCard:
38 | // Not supported in select actions
39 | break
40 | case .submit:
41 | // TODO: implement
42 | break
43 | case let .toggleVisibility(toggleVisibilityAction):
44 | store.send(.toggleVisibility(toggleVisibilityAction.targetElements))
45 | case .unknown:
46 | // Do nothing
47 | break
48 | }
49 | }
50 | }
51 |
52 | @available(macOS 11.0, iOS 14.0, tvOS 14.0, watchOS 7.0, *)
53 | extension View {
54 | func selectAction(_ action: Action?) -> some View {
55 | modifier(SelectActionModifier(action))
56 | }
57 | }
58 |
59 | #endif
60 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCardUI/UI/Helpers/VAlignment+Offset.swift:
--------------------------------------------------------------------------------
1 | #if canImport(SwiftUI)
2 |
3 | import AdaptiveCard
4 | import SwiftUI
5 |
6 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
7 | extension VAlignment {
8 | func offset(for proposedSize: CGSize, contentSize: CGSize) -> CGSize {
9 | switch self {
10 | case .center:
11 | return CGSize(width: 0, height: (proposedSize.height - contentSize.height) * 0.5)
12 | case .bottom:
13 | return CGSize(width: 0, height: proposedSize.height - contentSize.height)
14 | default:
15 | return .zero
16 | }
17 | }
18 | }
19 |
20 | #endif
21 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCardUI/UI/Image/ContentSizeKey.swift:
--------------------------------------------------------------------------------
1 | #if canImport(SwiftUI)
2 |
3 | import SwiftUI
4 |
5 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
6 | struct ContentSizeKey: PreferenceKey {
7 | static let defaultValue: CGSize? = nil
8 |
9 | static func reduce(value: inout CGSize?, nextValue: () -> CGSize?) {
10 | value = value ?? nextValue()
11 | }
12 | }
13 |
14 | #endif
15 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCardUI/UI/Image/CustomImageStyle.swift:
--------------------------------------------------------------------------------
1 | #if canImport(SwiftUI)
2 |
3 | import AdaptiveCard
4 | import SwiftUI
5 |
6 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
7 | public protocol CustomImageStyle {
8 | associatedtype Body: View
9 |
10 | func makeBody(content: Content) -> Body
11 |
12 | typealias Content = AnyView
13 | }
14 |
15 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
16 | struct AnyCustomImageStyle: CustomImageStyle {
17 | private let _makeBody: (Content) -> AnyView
18 |
19 | init(_ customImageStyle: T) where T: CustomImageStyle {
20 | _makeBody = {
21 | AnyView(customImageStyle.makeBody(content: $0))
22 | }
23 | }
24 |
25 | func makeBody(content: Content) -> AnyView {
26 | _makeBody(content)
27 | }
28 | }
29 |
30 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
31 | public extension View {
32 | func onImageStyle(_ imageStyle: ImageStyle, apply customImageStyle: T) -> some View where T: CustomImageStyle {
33 | environment(
34 | \.customImageStyles,
35 | [imageStyle: AnyCustomImageStyle(customImageStyle)]
36 | )
37 | }
38 | }
39 |
40 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
41 | extension EnvironmentValues {
42 | var customImageStyles: [ImageStyle: AnyCustomImageStyle] {
43 | get { self[CustomImageStylesKey.self] }
44 | set {
45 | self[CustomImageStylesKey.self]
46 | .merge(newValue) { _, b in b }
47 | }
48 | }
49 | }
50 |
51 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
52 | private struct CustomImageStylesKey: EnvironmentKey {
53 | static let defaultValue: [ImageStyle: AnyCustomImageStyle] = [:]
54 | }
55 |
56 | #endif
57 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCardUI/UI/Image/ImageSizeImageView.swift:
--------------------------------------------------------------------------------
1 | #if canImport(SwiftUI)
2 |
3 | import AdaptiveCard
4 | import SwiftUI
5 |
6 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
7 | struct ImageSizeImageView: View {
8 | @Environment(\.imageSizeConfiguration) private var imageSizeConfiguration
9 |
10 | private let image: SwiftUI.Image
11 | private let originalSize: CGSize
12 | private let size: ImageSize
13 | private let horizontalAlignment: HAlignment
14 | private let backgroundColor: Color?
15 | private let style: ImageStyle
16 |
17 | init(
18 | image: SwiftUI.Image,
19 | originalSize: CGSize,
20 | size: ImageSize,
21 | horizontalAlignment: HAlignment,
22 | backgroundColor: Color?,
23 | style: ImageStyle
24 | ) {
25 | self.image = image
26 | self.originalSize = originalSize
27 | self.size = size
28 | self.horizontalAlignment = horizontalAlignment
29 | self.backgroundColor = backgroundColor
30 | self.style = style
31 | }
32 |
33 | var body: some View {
34 | switch size {
35 | case .stretch, .small, .medium, .large:
36 | FixedSizeImageView(
37 | image: image,
38 | aspectRatio: originalSize.width / originalSize.height,
39 | width: imageSizeConfiguration[size], // `nil` will stretch to the proposed width
40 | height: nil,
41 | horizontalAlignment: horizontalAlignment,
42 | backgroundColor: backgroundColor,
43 | style: style
44 | )
45 | default:
46 | AutoImageView(
47 | image: image,
48 | size: originalSize,
49 | horizontalAlignment: horizontalAlignment,
50 | backgroundColor: backgroundColor,
51 | style: style
52 | )
53 | }
54 | }
55 | }
56 |
57 | #endif
58 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCardUI/UI/Image/ImageStyleView.swift:
--------------------------------------------------------------------------------
1 | #if canImport(SwiftUI)
2 |
3 | import AdaptiveCard
4 | import SwiftUI
5 |
6 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
7 | private struct ImageStyleView: View {
8 | @Environment(\.customImageStyles) private var customImageStyles
9 |
10 | private let content: Content
11 | private let imageStyle: ImageStyle
12 |
13 | init(content: Content, imageStyle: ImageStyle) {
14 | self.content = content
15 | self.imageStyle = imageStyle
16 | }
17 |
18 | var body: some View {
19 | if let customImageStyle = customImageStyles[imageStyle] {
20 | customImageStyle.makeBody(content: AnyView(content))
21 | } else {
22 | switch imageStyle {
23 | case .person:
24 | content.clipShape(Circle())
25 | default:
26 | content
27 | }
28 | }
29 | }
30 | }
31 |
32 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
33 | extension View {
34 | func imageStyle(_ imageStyle: ImageStyle) -> some View {
35 | ImageStyleView(content: self, imageStyle: imageStyle)
36 | }
37 | }
38 |
39 | #endif
40 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCardUI/UI/Image/ImageView.swift:
--------------------------------------------------------------------------------
1 | #if canImport(SwiftUI)
2 |
3 | import struct AdaptiveCard.Image
4 | import NetworkImage
5 | import SwiftUI
6 |
7 | @available(macOS 11.0, iOS 14.0, tvOS 14.0, watchOS 7.0, *)
8 | struct ImageView: View {
9 | private let image: Image
10 |
11 | init(_ image: Image) {
12 | self.image = image
13 | }
14 |
15 | var body: some View {
16 | NetworkImage(url: image.url)
17 | .selectAction(image.selectAction)
18 | .networkImageStyle(
19 | PrimitiveImageStyle(
20 | width: image.width?.cgFloatValue,
21 | height: image.height.cgFloatValue,
22 | size: image.size,
23 | horizontalAlignment: image.horizontalAlignment,
24 | style: image.style,
25 | backgroundColor: Color(argbHex: image.backgroundColor)
26 | )
27 | )
28 | }
29 | }
30 |
31 | #endif
32 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCardUI/UI/Image/PrimitiveImageStyle.swift:
--------------------------------------------------------------------------------
1 | #if canImport(SwiftUI)
2 |
3 | import AdaptiveCard
4 | import NetworkImage
5 | import SwiftUI
6 |
7 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
8 | struct PrimitiveImageStyle: NetworkImageStyle {
9 | private let width: CGFloat?
10 | private let height: CGFloat?
11 | private let size: ImageSize
12 | private let horizontalAlignment: HAlignment
13 | private let style: ImageStyle
14 | private let backgroundColor: Color?
15 |
16 | init(
17 | width: CGFloat?,
18 | height: CGFloat?,
19 | size: ImageSize,
20 | horizontalAlignment: HAlignment,
21 | style: ImageStyle,
22 | backgroundColor: Color?
23 | ) {
24 | self.width = width
25 | self.height = height
26 | self.size = size
27 | self.horizontalAlignment = horizontalAlignment
28 | self.style = style
29 | self.backgroundColor = backgroundColor
30 | }
31 |
32 | func makeBody(state: NetworkImageState) -> some View {
33 | switch state {
34 | case .loading, .failed:
35 | EmptyView()
36 | case let .image(image, size):
37 | if width != nil || height != nil {
38 | FixedSizeImageView(
39 | image: image,
40 | aspectRatio: size.width / size.height,
41 | width: width,
42 | height: height,
43 | horizontalAlignment: horizontalAlignment,
44 | backgroundColor: backgroundColor,
45 | style: style
46 | )
47 | } else {
48 | ImageSizeImageView(
49 | image: image,
50 | originalSize: size,
51 | size: self.size,
52 | horizontalAlignment: horizontalAlignment,
53 | backgroundColor: backgroundColor,
54 | style: style
55 | )
56 | }
57 | }
58 | }
59 | }
60 |
61 | #endif
62 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCardUI/UI/Text/Font.Design+FontType.swift:
--------------------------------------------------------------------------------
1 | #if canImport(SwiftUI)
2 |
3 | import AdaptiveCard
4 | import SwiftUI
5 |
6 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *)
7 | extension Font.Design {
8 | init(_ fontType: FontType) {
9 | switch fontType {
10 | case .monospace:
11 | self = .monospaced
12 | default:
13 | self = .default
14 | }
15 | }
16 | }
17 |
18 | #endif
19 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCardUI/UI/Text/Font.Weight+FontWeight.swift:
--------------------------------------------------------------------------------
1 | #if canImport(SwiftUI)
2 |
3 | import AdaptiveCard
4 | import SwiftUI
5 |
6 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
7 | extension Font.Weight {
8 | init(_ fontWeight: FontWeight) {
9 | switch fontWeight {
10 | case .light:
11 | self = .light
12 | case .bold:
13 | self = .bold
14 | default:
15 | self = .regular
16 | }
17 | }
18 | }
19 |
20 | #endif
21 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCardUI/UI/Text/RichTextBlockView.swift:
--------------------------------------------------------------------------------
1 | #if canImport(SwiftUI)
2 |
3 | import AdaptiveCard
4 | import SwiftUI
5 |
6 | @available(macOS 11.0, iOS 14.0, tvOS 14.0, watchOS 7.0, *)
7 | struct RichTextBlockView: View {
8 | @Environment(\.locale) private var locale
9 | @Environment(\.fontTypeConfiguration) private var fontTypeConfiguration
10 | @Environment(\.containerStyleConfiguration) private var containerStyleConfiguration
11 | @Environment(\.containerStyle) private var containerStyle
12 |
13 | private let richTextBlock: RichTextBlock
14 |
15 | init(_ richTextBlock: RichTextBlock) {
16 | self.richTextBlock = richTextBlock
17 | }
18 |
19 | var body: some View {
20 | HAlign(richTextBlock.horizontalAlignment) {
21 | richTextBlock.inlines.map {
22 | text(for: $0)
23 | }
24 | .joined()
25 | .multilineTextAlignment(TextAlignment(richTextBlock.horizontalAlignment))
26 | .fixedSize(horizontal: false, vertical: true)
27 | }
28 | }
29 |
30 | private func text(for textRun: TextRun) -> Text {
31 | let result = Text(parsing: textRun.text, locale: locale)
32 | .font(fontTypeConfiguration[textRun.fontType][textRun.size])
33 | .fontWeight(Font.Weight(textRun.weight))
34 | .foregroundColor(
35 | containerStyleConfiguration[containerStyle].textColors[textRun.color, textRun.isSubtle]
36 | )
37 | .strikethrough(textRun.strikethrough)
38 |
39 | return textRun.italic ? result.italic() : result
40 | }
41 | }
42 |
43 | #endif
44 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCardUI/UI/Text/Text+Parsing.swift:
--------------------------------------------------------------------------------
1 | #if canImport(SwiftUI)
2 |
3 | import SwiftUI
4 |
5 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
6 | extension Text {
7 | init(parsing input: String, locale: Locale? = nil) {
8 | let formatter = DateFormatter()
9 | formatter.locale = locale
10 |
11 | self = TextElement.parse(input).map { element -> Text in
12 | switch element {
13 | case let .plain(value):
14 | return Text(value)
15 | case let .date(date, style):
16 | switch style {
17 | case .compact:
18 | formatter.dateStyle = .short
19 | formatter.timeStyle = .none
20 | case .short:
21 | formatter.dateStyle = .long
22 | formatter.timeStyle = .none
23 | case .long:
24 | formatter.dateStyle = .full
25 | formatter.timeStyle = .none
26 | }
27 | return Text(formatter.string(from: date))
28 | case let .time(date):
29 | formatter.dateStyle = .none
30 | formatter.timeStyle = .short
31 | return Text(formatter.string(from: date))
32 | }
33 | }
34 | .joined()
35 | }
36 | }
37 |
38 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
39 | extension Array where Element == Text {
40 | func joined() -> Text {
41 | var result = Text("")
42 | for element in self {
43 | result = result + element
44 | }
45 | return result
46 | }
47 | }
48 |
49 | #endif
50 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCardUI/UI/Text/TextAlignment+HAlignment.swift:
--------------------------------------------------------------------------------
1 | #if canImport(SwiftUI)
2 |
3 | import AdaptiveCard
4 | import SwiftUI
5 |
6 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
7 | extension TextAlignment {
8 | init(_ horizontalAlignment: HAlignment) {
9 | switch horizontalAlignment {
10 | case .center:
11 | self = .center
12 | case .right:
13 | self = .trailing
14 | default:
15 | self = .leading
16 | }
17 | }
18 | }
19 |
20 | #endif
21 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCardUI/UI/Text/TextBlockView.swift:
--------------------------------------------------------------------------------
1 | #if canImport(SwiftUI)
2 |
3 | import AdaptiveCard
4 | import SwiftUI
5 |
6 | @available(macOS 11.0, iOS 14.0, tvOS 14.0, watchOS 7.0, *)
7 | struct TextBlockView: View {
8 | @Environment(\.locale) private var locale
9 | @Environment(\.fontTypeConfiguration) private var fontTypeConfiguration
10 | @Environment(\.containerStyleConfiguration) private var containerStyleConfiguration
11 | @Environment(\.containerStyle) private var containerStyle
12 |
13 | private let textBlock: TextBlock
14 |
15 | init(_ textBlock: TextBlock) {
16 | self.textBlock = textBlock
17 | }
18 |
19 | var body: some View {
20 | HAlign(textBlock.horizontalAlignment) {
21 | Text(parsing: textBlock.text, locale: locale)
22 | .font(fontTypeConfiguration[textBlock.fontType][textBlock.size])
23 | .fontWeight(Font.Weight(textBlock.weight))
24 | .foregroundColor(
25 | containerStyleConfiguration[containerStyle].textColors[textBlock.color, textBlock.isSubtle]
26 | )
27 | .multilineTextAlignment(TextAlignment(textBlock.horizontalAlignment))
28 | .lineLimit(textBlock.wrap ? textBlock.maxLines : 1)
29 | .fixedSize(horizontal: false, vertical: true)
30 | }
31 | }
32 | }
33 |
34 | #endif
35 |
--------------------------------------------------------------------------------
/Sources/AdaptiveCardUI/en.lproj/Localizable.strings:
--------------------------------------------------------------------------------
1 | "Error.render" = "The card could not be rendered";
2 |
--------------------------------------------------------------------------------
/Tests/AdaptiveCardTests/PixelDimensionTests.swift:
--------------------------------------------------------------------------------
1 | import AdaptiveCard
2 | import XCTest
3 |
4 | final class PixelDimensionTests: XCTestCase {
5 | private struct Model: Codable, Equatable {
6 | var height: PixelDimension
7 | }
8 |
9 | func testPixelDimensionDecodeReturnsPixelDimension() throws {
10 | // given
11 | let json = """
12 | {
13 | "height": "50px"
14 | }
15 | """.data(using: .utf8)!
16 | let expected = Model(height: 50)
17 |
18 | // when
19 | let result = try JSONDecoder().decode(Model.self, from: json)
20 |
21 | // then
22 | XCTAssertEqual(expected, result)
23 | }
24 |
25 | func testInvalidPixelDimensionDecodeThrows() {
26 | // given
27 | let json = """
28 | {
29 | "height": "foo"
30 | }
31 | """.data(using: .utf8)!
32 |
33 | // then
34 | XCTAssertThrowsError(try JSONDecoder().decode(Model.self, from: json))
35 | }
36 |
37 | func testPixelDimensionEncodeReturnsPixelDimensionJSON() throws {
38 | // given
39 | let model = Model(height: 75)
40 | let expected = """
41 | {
42 | "height" : "75px"
43 | }
44 | """.data(using: .utf8)!
45 | let encoder = JSONEncoder()
46 | encoder.outputFormatting = [.prettyPrinted]
47 |
48 | // when
49 | let result = try encoder.encode(model)
50 |
51 | // then
52 | XCTAssertEqual(expected, result)
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/Tests/AdaptiveCardTests/TargetElementTests.swift:
--------------------------------------------------------------------------------
1 | import AdaptiveCard
2 | import XCTest
3 |
4 | final class TargetElementTests: XCTestCase {
5 | func testStringDecodeReturnsTargetElement() throws {
6 | // given
7 | let json = "[\"textToToggle\"]".data(using: .utf8)!
8 | let expected = [TargetElement(elementId: "textToToggle")]
9 |
10 | // when
11 | let result = try JSONDecoder().decode([TargetElement].self, from: json)
12 |
13 | // then
14 | XCTAssertEqual(expected, result)
15 | }
16 |
17 | func testObjectDecodeReturnsTargetElement() throws {
18 | // given
19 | let json = """
20 | {
21 | "elementId": "textToToggle",
22 | "isVisible": true
23 | }
24 | """.data(using: .utf8)!
25 | let expected = TargetElement(elementId: "textToToggle", isVisible: true)
26 |
27 | // when
28 | let result = try JSONDecoder().decode(TargetElement.self, from: json)
29 |
30 | // then
31 | XCTAssertEqual(expected, result)
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/Configuration/ActionSetConfigurationTests.swift:
--------------------------------------------------------------------------------
1 | #if canImport(SwiftUI)
2 |
3 | import SwiftUI
4 | import XCTest
5 |
6 | import AdaptiveCardUI
7 |
8 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
9 | final class ActionSetConfigurationTests: XCTestCase {
10 | func testEmptyJSONObjectDecodeReturnsDefault() throws {
11 | // given
12 | let json = "{}".data(using: .utf8)!
13 | let expected = ActionSetConfiguration.default
14 |
15 | // when
16 | let result = try JSONDecoder().decode(ActionSetConfiguration.self, from: json)
17 |
18 | // then
19 | XCTAssertEqual(expected, result)
20 | }
21 |
22 | func testConfigJSONDecodeReturnsValidConfig() throws {
23 | // given
24 | let json = """
25 | {
26 | "actionsOrientation": "horizontal",
27 | "actionAlignment": "center",
28 | "buttonSpacing": 20,
29 | "maxActions": 100,
30 | "spacing": "extraLarge",
31 | "showCard": {
32 | "inlineTopMargin": 10,
33 | "style": "default"
34 | }
35 | }
36 | """.data(using: .utf8)!
37 |
38 | let expected = ActionSetConfiguration(
39 | actionsOrientation: .horizontal,
40 | actionAlignment: .center,
41 | buttonSpacing: 20,
42 | spacing: .extraLarge,
43 | maxActions: 100,
44 | showCard: ShowCardConfiguration(
45 | style: .default,
46 | inlineTopMargin: 10
47 | )
48 | )
49 |
50 | // when
51 | let result = try JSONDecoder().decode(ActionSetConfiguration.self, from: json)
52 |
53 | // then
54 | XCTAssertEqual(expected, result)
55 | }
56 | }
57 |
58 | #endif
59 |
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/Configuration/ImageSizeConfigurationTests.swift:
--------------------------------------------------------------------------------
1 | #if canImport(SwiftUI)
2 |
3 | import SwiftUI
4 | import XCTest
5 |
6 | import AdaptiveCardUI
7 |
8 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
9 | final class ImageSizeConfigurationTests: XCTestCase {
10 | func testAnyConfigSubscriptEqualsProperty() {
11 | // given
12 | let anyConfig = ImageSizeConfiguration.default
13 |
14 | // then
15 | XCTAssertEqual(anyConfig.small, anyConfig[.small])
16 | XCTAssertEqual(anyConfig.medium, anyConfig[.medium])
17 | XCTAssertEqual(anyConfig.large, anyConfig[.large])
18 | XCTAssertNil(anyConfig[.auto])
19 | XCTAssertNil(anyConfig[.stretch])
20 | }
21 |
22 | func testEmptyJSONObjectDecodeReturnsDefault() throws {
23 | // given
24 | let json = "{}".data(using: .utf8)!
25 | let expected = ImageSizeConfiguration.default
26 |
27 | // when
28 | let result = try JSONDecoder().decode(ImageSizeConfiguration.self, from: json)
29 |
30 | // then
31 | XCTAssertEqual(expected, result)
32 | }
33 |
34 | func testConfigJSONDecodeReturnsValidConfig() throws {
35 | // given
36 | let json = """
37 | {
38 | "small": 100,
39 | "medium": 200,
40 | "large": 300,
41 | }
42 | """.data(using: .utf8)!
43 |
44 | let expected = ImageSizeConfiguration(
45 | small: 100,
46 | medium: 200,
47 | large: 300
48 | )
49 |
50 | // when
51 | let result = try JSONDecoder().decode(ImageSizeConfiguration.self, from: json)
52 |
53 | // then
54 | XCTAssertEqual(expected, result)
55 | }
56 | }
57 |
58 | #endif
59 |
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/Configuration/ShowCardConfigurationTests.swift:
--------------------------------------------------------------------------------
1 | #if canImport(SwiftUI)
2 |
3 | import SwiftUI
4 | import XCTest
5 |
6 | import AdaptiveCardUI
7 |
8 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
9 | final class ShowCardConfigurationTests: XCTestCase {
10 | func testEmptyJSONObjectDecodeReturnsDefault() throws {
11 | // given
12 | let json = "{}".data(using: .utf8)!
13 | let expected = ShowCardConfiguration.default
14 |
15 | // when
16 | let result = try JSONDecoder().decode(ShowCardConfiguration.self, from: json)
17 |
18 | // then
19 | XCTAssertEqual(expected, result)
20 | }
21 |
22 | func testConfigJSONDecodeReturnsValidConfig() throws {
23 | // given
24 | let json = """
25 | {
26 | "inlineTopMargin": 20,
27 | "style": "default"
28 | }
29 | """.data(using: .utf8)!
30 |
31 | let expected = ShowCardConfiguration(
32 | style: .default,
33 | inlineTopMargin: 20
34 | )
35 |
36 | // when
37 | let result = try JSONDecoder().decode(ShowCardConfiguration.self, from: json)
38 |
39 | // then
40 | XCTAssertEqual(expected, result)
41 | }
42 | }
43 |
44 | #endif
45 |
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/Configuration/SpacingConfigurationTests.swift:
--------------------------------------------------------------------------------
1 | #if canImport(SwiftUI)
2 |
3 | import SwiftUI
4 | import XCTest
5 |
6 | import AdaptiveCardUI
7 |
8 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
9 | final class SpacingConfigurationTests: XCTestCase {
10 | func testAnyConfigSubscriptEqualsProperty() {
11 | // given
12 | let anyConfig = SpacingConfiguration.default
13 |
14 | // then
15 | XCTAssertEqual(anyConfig.small, anyConfig[.small])
16 | XCTAssertEqual(anyConfig.default, anyConfig[.default])
17 | XCTAssertEqual(anyConfig.medium, anyConfig[.medium])
18 | XCTAssertEqual(anyConfig.large, anyConfig[.large])
19 | XCTAssertEqual(anyConfig.extraLarge, anyConfig[.extraLarge])
20 | XCTAssertEqual(anyConfig.padding, anyConfig[.padding])
21 | }
22 |
23 | func testEmptyJSONObjectDecodeReturnsDefault() throws {
24 | // given
25 | let json = "{}".data(using: .utf8)!
26 | let expected = SpacingConfiguration.default
27 |
28 | // when
29 | let result = try JSONDecoder().decode(SpacingConfiguration.self, from: json)
30 |
31 | // then
32 | XCTAssertEqual(expected, result)
33 | }
34 |
35 | func testConfigJSONDecodeReturnsValidConfig() throws {
36 | // given
37 | let json = """
38 | {
39 | "small": 4,
40 | "default": 9,
41 | "medium": 21,
42 | "large": 31,
43 | "extraLarge": 41,
44 | "padding": 11,
45 | }
46 | """.data(using: .utf8)!
47 | let expected = SpacingConfiguration(
48 | default: 9,
49 | small: 4,
50 | medium: 21,
51 | large: 31,
52 | extraLarge: 41,
53 | padding: 11
54 | )
55 |
56 | // when
57 | let result = try JSONDecoder().decode(SpacingConfiguration.self, from: json)
58 |
59 | // then
60 | XCTAssertEqual(expected, result)
61 | }
62 | }
63 |
64 | #endif
65 |
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/ColorTests.swift:
--------------------------------------------------------------------------------
1 | #if canImport(SwiftUI)
2 |
3 | import SwiftUI
4 | import XCTest
5 |
6 | @testable import AdaptiveCardUI
7 |
8 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
9 | final class ColorTests: XCTestCase {
10 | func testARGBHexInitSuccess() {
11 | // given
12 | let hex = "#AABBCCDD"
13 | let expected = Color(red: 0xBB / 255, green: 0xCC / 255, blue: 0xDD / 255, opacity: 0xAA / 255)
14 |
15 | // when
16 | let result = Color(argbHex: hex)
17 |
18 | // then
19 | XCTAssertEqual(expected, result)
20 | }
21 |
22 | func testRGBHexInitSuccess() {
23 | // given
24 | let hex = "#AABBCC"
25 | let expected = Color(red: 0xAA / 255, green: 0xBB / 255, blue: 0xCC / 255, opacity: 1)
26 |
27 | // when
28 | let result = Color(argbHex: hex)
29 |
30 | // then
31 | XCTAssertEqual(expected, result)
32 | }
33 |
34 | func testValueWithoutHashInitFails() {
35 | // given
36 | let hex = "AABBCCDD"
37 |
38 | // when
39 | let result = Color(argbHex: hex)
40 |
41 | // then
42 | XCTAssertNil(result)
43 | }
44 |
45 | func testTooShortValueInitFails() {
46 | // given
47 | let hex = "#AABBC"
48 |
49 | // when
50 | let result = Color(argbHex: hex)
51 |
52 | // then
53 | XCTAssertNil(result)
54 | }
55 |
56 | func testTooLongValueInitFails() {
57 | // given
58 | let hex = "#AABBCCDDF"
59 |
60 | // when
61 | let result = Color(argbHex: hex)
62 |
63 | // then
64 | XCTAssertNil(result)
65 | }
66 |
67 | func testNilValueInitFails() {
68 | // given
69 | let hex: String? = nil
70 |
71 | // when
72 | let result = Color(argbHex: hex)
73 |
74 | // then
75 | XCTAssertNil(result)
76 | }
77 | }
78 |
79 | #endif
80 |
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/CustomCardElementRenderingTests.swift:
--------------------------------------------------------------------------------
1 | #if SNAPSHOT_TESTS
2 |
3 | import SnapshotTesting
4 | import SwiftUI
5 | import XCTest
6 |
7 | import AdaptiveCardUI
8 |
9 | @available(iOS 14.0, *)
10 | final class CustomCardElementRenderingTests: XCTestCase {
11 | override class func setUp() {
12 | super.setUp()
13 | CardElement.register(RepoLanguage.self)
14 | CardElement.register(StarCount.self)
15 | }
16 |
17 | func testCustomCardElements() {
18 | let view = AdaptiveCardView(url: fixtureURL("customCardElements.json"))
19 | .animation(nil)
20 | .customCardElement { RepoLanguageView($0) }
21 | .customCardElement { StarCountView($0) }
22 | .adaptiveCardConfiguration(.test)
23 | .environment(\.locale, Locale(identifier: "en_US"))
24 |
25 | let vc = UIHostingController(rootView: view)
26 | vc.view.frame = CGRect(x: 0, y: 0, width: 300, height: 150)
27 |
28 | assertSnapshot(matching: vc, as: .wait(for: 0.25, on: .image))
29 | }
30 | }
31 |
32 | #endif
33 |
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/CustomCardElements/RepoLanguage.swift:
--------------------------------------------------------------------------------
1 | import AdaptiveCardUI
2 | import DefaultCodable
3 | import Foundation
4 |
5 | struct RepoLanguage: CustomCardElement, Codable, Equatable {
6 | // MARK: - CustomCardElement
7 |
8 | @ItemIdentifier var id: String
9 |
10 | @Default var isVisible: Bool
11 |
12 | @Default var separator: Bool
13 |
14 | @Default var spacing: Spacing
15 |
16 | @Default var fallback: Fallback
17 |
18 | @Default var requires: [String: SemanticVersion]
19 |
20 | // MARK: - RepoLanguage
21 |
22 | @Default var horizontalAlignment: HAlignment
23 |
24 | var language: String
25 |
26 | var color: String
27 | }
28 |
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/CustomCardElements/RepoLanguageView.swift:
--------------------------------------------------------------------------------
1 | #if canImport(SwiftUI)
2 |
3 | import AdaptiveCardUI
4 | import SwiftUI
5 |
6 | @available(macOS 11.0, iOS 14.0, tvOS 14.0, watchOS 7.0, *)
7 | struct RepoLanguageView: View {
8 | @Environment(\.fontTypeConfiguration) private var fontTypeConfiguration
9 | @Environment(\.containerStyleConfiguration) private var containerStyleConfiguration
10 | @Environment(\.containerStyle) private var containerStyle
11 |
12 | private let repoLanguage: RepoLanguage
13 |
14 | init(_ repoLanguage: RepoLanguage) {
15 | self.repoLanguage = repoLanguage
16 | }
17 |
18 | var body: some View {
19 | HAlign(repoLanguage.horizontalAlignment) {
20 | Label {
21 | Text(repoLanguage.language)
22 | .foregroundColor(
23 | containerStyleConfiguration[containerStyle].textColors.default.default
24 | )
25 | } icon: {
26 | Image(systemName: "circle.fill")
27 | .imageScale(.small)
28 | .foregroundColor(Color(argbHex: repoLanguage.color))
29 | }
30 | .font(fontTypeConfiguration.default.default)
31 | }
32 | }
33 | }
34 |
35 | #endif
36 |
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/CustomCardElements/StarCount.swift:
--------------------------------------------------------------------------------
1 | import AdaptiveCardUI
2 | import DefaultCodable
3 | import Foundation
4 |
5 | struct StarCount: CustomCardElement, Codable, Equatable {
6 | // MARK: - CustomCardElement
7 |
8 | @ItemIdentifier var id: String
9 |
10 | @Default var isVisible: Bool
11 |
12 | @Default var separator: Bool
13 |
14 | @Default var spacing: Spacing
15 |
16 | @Default var fallback: Fallback
17 |
18 | @Default var requires: [String: SemanticVersion]
19 |
20 | // MARK: - StartCount
21 |
22 | @Default var horizontalAlignment: HAlignment
23 |
24 | var value: Int
25 | }
26 |
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/CustomCardElements/StarCountView.swift:
--------------------------------------------------------------------------------
1 | #if canImport(SwiftUI)
2 |
3 | import AdaptiveCardUI
4 | import SwiftUI
5 |
6 | @available(macOS 11.0, iOS 14.0, tvOS 14.0, watchOS 7.0, *)
7 | struct StarCountView: View {
8 | @Environment(\.locale) private var locale
9 | @Environment(\.fontTypeConfiguration) private var fontTypeConfiguration
10 | @Environment(\.containerStyleConfiguration) private var containerStyleConfiguration
11 | @Environment(\.containerStyle) private var containerStyle
12 |
13 | private let starCount: StarCount
14 |
15 | init(_ starCount: StarCount) {
16 | self.starCount = starCount
17 | }
18 |
19 | var body: some View {
20 | HAlign(starCount.horizontalAlignment) {
21 | Label {
22 | Text(formattedValue ?? "0")
23 | } icon: {
24 | Image(systemName: "star")
25 | .imageScale(.small)
26 | }
27 | .font(fontTypeConfiguration.default.default)
28 | .foregroundColor(
29 | containerStyleConfiguration[containerStyle].textColors.default.default
30 | )
31 | }
32 | }
33 |
34 | private var formattedValue: String? {
35 | let formatter = NumberFormatter()
36 | formatter.locale = locale
37 | formatter.numberStyle = .decimal
38 |
39 | return formatter.string(for: starCount.value)
40 | }
41 | }
42 |
43 | #endif
44 |
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/FactSetRenderingTests.swift:
--------------------------------------------------------------------------------
1 | #if SNAPSHOT_TESTS
2 |
3 | import SnapshotTesting
4 | import SwiftUI
5 | import XCTest
6 |
7 | import AdaptiveCardUI
8 |
9 | @available(iOS 14.0, *)
10 | final class FactSetRenderingTests: XCTestCase {
11 | func testFactSet() {
12 | let view = AdaptiveCardView(url: fixtureURL("factSet.json"))
13 | .animation(nil)
14 | .adaptiveCardConfiguration(.test)
15 |
16 | let vc = UIHostingController(rootView: view)
17 | vc.view.frame = CGRect(x: 0, y: 0, width: 400, height: 200)
18 |
19 | assertSnapshot(matching: vc, as: .wait(for: 0.25, on: .image))
20 | }
21 | }
22 |
23 | #endif
24 |
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/ImageSetRenderingTests.swift:
--------------------------------------------------------------------------------
1 | #if SNAPSHOT_TESTS
2 |
3 | import SnapshotTesting
4 | import SwiftUI
5 | import XCTest
6 |
7 | import AdaptiveCardUI
8 |
9 | @available(iOS 14.0, *)
10 | final class ImageSetRenderingTests: XCTestCase {
11 | func testImageSet() {
12 | let view = AdaptiveCardView(url: fixtureURL("imageSet.json"))
13 | .animation(nil)
14 | .adaptiveCardConfiguration(.test)
15 |
16 | let vc = UIHostingController(rootView: view)
17 | vc.view.frame = CGRect(x: 0, y: 0, width: 300, height: 260)
18 |
19 | assertSnapshot(matching: vc, as: .wait(for: 0.25, on: .image))
20 | }
21 | }
22 |
23 | #endif
24 |
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/RichTextBlockRenderingTests.swift:
--------------------------------------------------------------------------------
1 | #if SNAPSHOT_TESTS
2 |
3 | import SnapshotTesting
4 | import SwiftUI
5 | import XCTest
6 |
7 | import AdaptiveCardUI
8 |
9 | @available(iOS 14.0, *)
10 | final class RichTextBlockRenderingTests: XCTestCase {
11 | func testRichTextBlock() {
12 | let view = AdaptiveCardView(url: fixtureURL("richTextBlock.json"))
13 | .animation(nil)
14 | .adaptiveCardConfiguration(.test)
15 |
16 | let vc = UIHostingController(rootView: view)
17 | vc.view.frame = CGRect(x: 0, y: 0, width: 375, height: 300)
18 |
19 | assertSnapshot(matching: vc, as: .wait(for: 0.25, on: .image))
20 | }
21 | }
22 |
23 | #endif
24 |
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/TestHelpers.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | func fixtureURL(_ fileName: String, file: StaticString = #file) -> URL {
4 | URL(fileURLWithPath: "\(file)", isDirectory: false)
5 | .deletingLastPathComponent()
6 | .appendingPathComponent("__Fixtures__")
7 | .appendingPathComponent(fileName)
8 | }
9 |
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/__Fixtures__/actions.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "AdaptiveCard",
3 | "version": "1.2",
4 | "body": [
5 | {
6 | "type": "TextBlock",
7 | "text": "Test toggle buttons..."
8 | },
9 | {
10 | "type": "TextBlock",
11 | "text": "Here's the thing",
12 | "id": "thingToToggle",
13 | "isVisible": false
14 | }
15 | ],
16 | "actions": [
17 | {
18 | "type": "Action.ToggleVisibility",
19 | "title": "Toggle",
20 | "targetElements": [
21 | "thingToToggle"
22 | ]
23 | },
24 | {
25 | "type": "Action.ToggleVisibility",
26 | "title": "Show",
27 | "targetElements": [
28 | {
29 | "elementId": "thingToToggle",
30 | "isVisible": true
31 | }
32 | ]
33 | },
34 | {
35 | "type": "Action.ToggleVisibility",
36 | "title": "Hide",
37 | "targetElements": [
38 | {
39 | "elementId": "thingToToggle",
40 | "isVisible": false
41 | }
42 | ]
43 | }
44 | ]
45 | }
46 |
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/__Fixtures__/columnSetMinHeight.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
3 | "type": "AdaptiveCard",
4 | "version": "1.3",
5 | "body": [
6 | {
7 | "type": "ColumnSet",
8 | "style": "emphasis",
9 | "minHeight": "25px",
10 | "columns": [
11 | {
12 | "type": "Column",
13 | "style": "default",
14 | "minHeight": "100px",
15 | "items": [
16 | {
17 | "type": "TextBlock",
18 | "wrap": true,
19 | "text": "Column minHeight > ColumnSet minHeight"
20 | }
21 | ],
22 | "width": "stretch"
23 | }
24 | ]
25 | },
26 | {
27 | "type": "ColumnSet",
28 | "style": "emphasis",
29 | "minHeight": "100px",
30 | "columns": [
31 | {
32 | "type": "Column",
33 | "style": "default",
34 | "minHeight": "25px",
35 | "items": [
36 | {
37 | "type": "TextBlock",
38 | "wrap": true,
39 | "text": "Column minHeight < ColumnSet minHeight"
40 | }
41 | ],
42 | "width": "stretch"
43 | }
44 | ]
45 | }
46 | ]
47 | }
48 |
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/__Fixtures__/columnSetStyle.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
3 | "type": "AdaptiveCard",
4 | "version": "1.3",
5 | "body": [
6 | {
7 | "type": "ColumnSet",
8 | "columns": [
9 | {
10 | "type": "Column",
11 | "items": [
12 | {
13 | "type": "TextBlock",
14 | "text": "No Style"
15 | }
16 | ]
17 | },
18 | {
19 | "type": "Column",
20 | "style": "default",
21 | "items": [
22 | {
23 | "type": "TextBlock",
24 | "text": "Default Style"
25 | }
26 | ]
27 | },
28 | {
29 | "type": "Column",
30 | "style": "emphasis",
31 | "items": [
32 | {
33 | "type": "TextBlock",
34 | "text": "Emphasis Style"
35 | },
36 | {
37 | "type": "Container",
38 | "items": [
39 | {
40 | "type": "TextBlock",
41 | "text": "Container no style"
42 | }
43 | ]
44 | },
45 | {
46 | "type": "Container",
47 | "style": "default",
48 | "items": [
49 | {
50 | "type": "TextBlock",
51 | "text": "Container default style"
52 | }
53 | ]
54 | },
55 | {
56 | "type": "Container",
57 | "style": "emphasis",
58 | "items": [
59 | {
60 | "type": "TextBlock",
61 | "text": "Container emphasis style"
62 | }
63 | ]
64 | }
65 | ]
66 | }
67 | ]
68 | }
69 | ]
70 | }
71 |
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/__Fixtures__/columnSetVerticalContentAlignment.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
3 | "type": "AdaptiveCard",
4 | "version": "1.3",
5 | "body": [
6 | {
7 | "type": "ColumnSet",
8 | "columns": [
9 | {
10 | "type": "Column",
11 | "width": "stretch",
12 | "style": "emphasis",
13 | "items": [
14 | {
15 | "type": "TextBlock",
16 | "text": "Vertical"
17 | },
18 | {
19 | "type": "TextBlock",
20 | "text": "Content"
21 | },
22 | {
23 | "type": "TextBlock",
24 | "text": "Alignment"
25 | }
26 | ]
27 | },
28 | {
29 | "type": "Column",
30 | "width": "stretch",
31 | "style": "emphasis",
32 | "items": [
33 | {
34 | "type": "TextBlock",
35 | "text": "Top"
36 | }
37 | ]
38 | },
39 | {
40 | "type": "Column",
41 | "width": "stretch",
42 | "style": "emphasis",
43 | "verticalContentAlignment": "center",
44 | "items": [
45 | {
46 | "type": "TextBlock",
47 | "text": "Center"
48 | }
49 | ]
50 | },
51 | {
52 | "type": "Column",
53 | "width": "stretch",
54 | "style": "emphasis",
55 | "verticalContentAlignment": "bottom",
56 | "items": [
57 | {
58 | "type": "TextBlock",
59 | "text": "Bottom"
60 | }
61 | ]
62 | }
63 | ]
64 | }
65 | ]
66 | }
67 |
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/__Fixtures__/containerBleed.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
3 | "type": "AdaptiveCard",
4 | "version": "1.2",
5 | "body": [
6 | {
7 | "type": "Container",
8 | "style": "emphasis",
9 | "bleed": true,
10 | "items": [
11 | {
12 | "type": "TextBlock",
13 | "text": "style: emphasis, bleed: true"
14 | },
15 | {
16 | "type": "Container",
17 | "backgroundImage": {
18 | "url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8DwHwAFBQIAX8jx0gAAAABJRU5ErkJggg=="
19 | },
20 | "items": [
21 | {
22 | "type": "TextBlock",
23 | "text": "background image"
24 | }
25 | ]
26 | },
27 | {
28 | "type": "Container",
29 | "backgroundImage": {
30 | "url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPj/HwADBwIAMCbHYQAAAABJRU5ErkJggg=="
31 | },
32 | "bleed": true,
33 | "items": [
34 | {
35 | "type": "TextBlock",
36 | "text": "bleed: true, background image",
37 | "color": "light"
38 | }
39 | ]
40 | },
41 | {
42 | "type": "Container",
43 | "items": [
44 | {
45 | "type": "TextBlock",
46 | "text": "style from parent"
47 | },
48 | {
49 | "type": "Container",
50 | "style": "default",
51 | "items": [
52 | {
53 | "type": "TextBlock",
54 | "text": "style: default"
55 | }
56 | ]
57 | }
58 | ]
59 | }
60 | ]
61 | },
62 | {
63 | "type": "Container",
64 | "style": "emphasis",
65 | "items": [
66 | {
67 | "type": "TextBlock",
68 | "text": "style: emphasis"
69 | }
70 | ]
71 | }
72 | ]
73 | }
74 |
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/__Fixtures__/containerMinHeight.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
3 | "type": "AdaptiveCard",
4 | "version": "1.2",
5 | "body": [
6 | {
7 | "type": "Container",
8 | "minHeight": "100px",
9 | "style": "emphasis",
10 | "items": [
11 | {
12 | "type": "TextBlock",
13 | "wrap": true,
14 | "text": "This textblock is inside a container with a min height of 100px"
15 | }
16 | ]
17 | }
18 | ]
19 | }
20 |
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/__Fixtures__/containerVerticalContentAlignment.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
3 | "type": "AdaptiveCard",
4 | "version": "1.1",
5 | "body": [
6 | {
7 | "type": "Container",
8 | "minHeight": "88px",
9 | "style": "emphasis",
10 | "items": [
11 | {
12 | "type": "TextBlock",
13 | "text": "verticalContentAlignment: not specified"
14 | }
15 | ]
16 | },
17 | {
18 | "type": "Container",
19 | "minHeight": "88px",
20 | "style": "emphasis",
21 | "verticalContentAlignment": "top",
22 | "items": [
23 | {
24 | "type": "TextBlock",
25 | "text": "verticalContentAlignment: top"
26 | }
27 | ]
28 | },
29 | {
30 | "type": "Container",
31 | "minHeight": "88px",
32 | "style": "emphasis",
33 | "verticalContentAlignment": "center",
34 | "items": [
35 | {
36 | "type": "TextBlock",
37 | "text": "verticalContentAlignment: center"
38 | }
39 | ]
40 | },
41 | {
42 | "type": "Container",
43 | "minHeight": "88px",
44 | "style": "emphasis",
45 | "verticalContentAlignment": "bottom",
46 | "items": [
47 | {
48 | "type": "TextBlock",
49 | "text": "verticalContentAlignment: bottom"
50 | }
51 | ]
52 | }
53 | ]
54 | }
55 |
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/__Fixtures__/customCardElements.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
3 | "type": "AdaptiveCard",
4 | "version": "1.3",
5 | "body": [
6 | {
7 | "type": "TextBlock",
8 | "text": "Swift",
9 | "isSubtle": true,
10 | "size": "medium"
11 | },
12 | {
13 | "type": "TextBlock",
14 | "text": "Apple",
15 | "weight": "bolder",
16 | "size": "medium",
17 | "spacing": "none"
18 | },
19 | {
20 | "type": "TextBlock",
21 | "text": "The Swift Programming Language"
22 | },
23 | {
24 | "type": "ColumnSet",
25 | "spacing": "small",
26 | "columns": [
27 | {
28 | "type": "Column",
29 | "width": "auto",
30 | "items": [
31 | {
32 | "type": "StarCount",
33 | "value": 53500
34 | }
35 | ]
36 | },
37 | {
38 | "type": "Column",
39 | "items": [
40 | {
41 | "type": "RepoLanguage",
42 | "language": "C++",
43 | "color": "#f34b7d"
44 | }
45 | ]
46 | }
47 | ]
48 | }
49 | ]
50 | }
51 |
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/__Fixtures__/factSet.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
3 | "type": "AdaptiveCard",
4 | "version": "1.2",
5 | "body": [
6 | {
7 | "type": "FactSet",
8 | "facts": [
9 | {
10 | "title": "1 Lorem ipsum dolor sit amet",
11 | "value": "1 Donec ac velit in leo egestas fermentum."
12 | },
13 | {
14 | "title": "2 Mauris",
15 | "value": "2 Sed vel velit sit amet"
16 | },
17 | {
18 | "title": "3 Etiam vitae mi eget mi lobortis",
19 | "value": "3 Nullam mattis orci"
20 | },
21 | {
22 | "title": "4 Praesent",
23 | "value": "4 Sed scelerisque dolor at elit suscipit, eget venenatis orci placerat."
24 | }
25 | ]
26 | }
27 | ]
28 | }
29 |
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/__Fixtures__/image.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
3 | "type": "AdaptiveCard",
4 | "version": "1.0",
5 | "body": [
6 | {
7 | "type": "Image",
8 | "id": "AnyImage",
9 | "url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAABGdBTUEAALGPC/xhBQAAADhlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAAqACAAQAAAABAAAAKKADAAQAAAABAAAAKAAAAABZLkSWAAABdElEQVRYCc2TCw6CMBBEEbwXeDNv5kE4C4mUMKTAtt0fFBMziu3um5fYNI6vcRyH8HYc2bw9h03T9F3nDV5zW69Bq7l+ntd7WnQDjOw18WerABfAyB543Cy6AFLGqGegl6QZkLCH/S4WzYA5U7nf0KKUJsCMPew1WzQBcgxxzqANlWpAhj3sM1lUA0rMSM6iFVIFKLCHPWqLKkCNEc2d0E4MqLBnsigG1JoIlJq7IkCDPbVFEaDGAMiQ0hlsQAd7YBT9o9mA0uagoVIyiwXoaA+8bIssQEljEJSSO7MIeIE9sLMsFgG5TbFVkpzZWcAL7aFH0WIWkNMQm7RZ2pEEvMEeOmUtJgFLzTDdI3O7SMAb7aFf0iIJmGuEid6Z2nkCrGAPXUmLJ8BUE0y5MqndO8CK9tD7ZHEHSDXAzbvyyLABPsAeHOwsboBHcpyukTHLAvgge/CxWVwAY2KcqJ1gegV785dfbSBqf9d1nxak1IHazwLbH4DVAnPf+eoYAAAAAElFTkSuQmCC",
10 | }
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/__Fixtures__/imageBackgroundColor.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
3 | "type": "AdaptiveCard",
4 | "version": "1.0",
5 | "body": [
6 | {
7 | "type": "Image",
8 | "url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAABGdBTUEAALGPC/xhBQAAADhlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAAqACAAQAAAABAAAAKKADAAQAAAABAAAAKAAAAABZLkSWAAABdElEQVRYCc2TCw6CMBBEEbwXeDNv5kE4C4mUMKTAtt0fFBMziu3um5fYNI6vcRyH8HYc2bw9h03T9F3nDV5zW69Bq7l+ntd7WnQDjOw18WerABfAyB543Cy6AFLGqGegl6QZkLCH/S4WzYA5U7nf0KKUJsCMPew1WzQBcgxxzqANlWpAhj3sM1lUA0rMSM6iFVIFKLCHPWqLKkCNEc2d0E4MqLBnsigG1JoIlJq7IkCDPbVFEaDGAMiQ0hlsQAd7YBT9o9mA0uagoVIyiwXoaA+8bIssQEljEJSSO7MIeIE9sLMsFgG5TbFVkpzZWcAL7aFH0WIWkNMQm7RZ2pEEvMEeOmUtJgFLzTDdI3O7SMAb7aFf0iIJmGuEid6Z2nkCrGAPXUmLJ8BUE0y5MqndO8CK9tD7ZHEHSDXAzbvyyLABPsAeHOwsboBHcpyukTHLAvgge/CxWVwAY2KcqJ1gegV785dfbSBqf9d1nxak1IHazwLbH4DVAnPf+eoYAAAAAElFTkSuQmCC",
9 | "backgroundColor": "#FF0000FF",
10 | },
11 | {
12 | "type": "Image",
13 | "url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAABGdBTUEAALGPC/xhBQAAADhlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAAqACAAQAAAABAAAAKKADAAQAAAABAAAAKAAAAABZLkSWAAABdElEQVRYCc2TCw6CMBBEEbwXeDNv5kE4C4mUMKTAtt0fFBMziu3um5fYNI6vcRyH8HYc2bw9h03T9F3nDV5zW69Bq7l+ntd7WnQDjOw18WerABfAyB543Cy6AFLGqGegl6QZkLCH/S4WzYA5U7nf0KKUJsCMPew1WzQBcgxxzqANlWpAhj3sM1lUA0rMSM6iFVIFKLCHPWqLKkCNEc2d0E4MqLBnsigG1JoIlJq7IkCDPbVFEaDGAMiQ0hlsQAd7YBT9o9mA0uagoVIyiwXoaA+8bIssQEljEJSSO7MIeIE9sLMsFgG5TbFVkpzZWcAL7aFH0WIWkNMQm7RZ2pEEvMEeOmUtJgFLzTDdI3O7SMAb7aFf0iIJmGuEid6Z2nkCrGAPXUmLJ8BUE0y5MqndO8CK9tD7ZHEHSDXAzbvyyLABPsAeHOwsboBHcpyukTHLAvgge/CxWVwAY2KcqJ1gegV785dfbSBqf9d1nxak1IHazwLbH4DVAnPf+eoYAAAAAElFTkSuQmCC",
14 | "style": "person",
15 | "backgroundColor": "#FFFF0000"
16 | }
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/__Fixtures__/imageCustomStyle.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
3 | "type": "AdaptiveCard",
4 | "version": "1.0",
5 | "body": [
6 | {
7 | "type": "Image",
8 | "url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAABGdBTUEAALGPC/xhBQAAADhlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAAqACAAQAAAABAAAAKKADAAQAAAABAAAAKAAAAABZLkSWAAABdElEQVRYCc2TCw6CMBBEEbwXeDNv5kE4C4mUMKTAtt0fFBMziu3um5fYNI6vcRyH8HYc2bw9h03T9F3nDV5zW69Bq7l+ntd7WnQDjOw18WerABfAyB543Cy6AFLGqGegl6QZkLCH/S4WzYA5U7nf0KKUJsCMPew1WzQBcgxxzqANlWpAhj3sM1lUA0rMSM6iFVIFKLCHPWqLKkCNEc2d0E4MqLBnsigG1JoIlJq7IkCDPbVFEaDGAMiQ0hlsQAd7YBT9o9mA0uagoVIyiwXoaA+8bIssQEljEJSSO7MIeIE9sLMsFgG5TbFVkpzZWcAL7aFH0WIWkNMQm7RZ2pEEvMEeOmUtJgFLzTDdI3O7SMAb7aFf0iIJmGuEid6Z2nkCrGAPXUmLJ8BUE0y5MqndO8CK9tD7ZHEHSDXAzbvyyLABPsAeHOwsboBHcpyukTHLAvgge/CxWVwAY2KcqJ1gegV785dfbSBqf9d1nxak1IHazwLbH4DVAnPf+eoYAAAAAElFTkSuQmCC",
9 | "backgroundColor": "#FF0000FF"
10 | },
11 | {
12 | "type": "Image",
13 | "url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAABGdBTUEAALGPC/xhBQAAADhlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAAqACAAQAAAABAAAAKKADAAQAAAABAAAAKAAAAABZLkSWAAABdElEQVRYCc2TCw6CMBBEEbwXeDNv5kE4C4mUMKTAtt0fFBMziu3um5fYNI6vcRyH8HYc2bw9h03T9F3nDV5zW69Bq7l+ntd7WnQDjOw18WerABfAyB543Cy6AFLGqGegl6QZkLCH/S4WzYA5U7nf0KKUJsCMPew1WzQBcgxxzqANlWpAhj3sM1lUA0rMSM6iFVIFKLCHPWqLKkCNEc2d0E4MqLBnsigG1JoIlJq7IkCDPbVFEaDGAMiQ0hlsQAd7YBT9o9mA0uagoVIyiwXoaA+8bIssQEljEJSSO7MIeIE9sLMsFgG5TbFVkpzZWcAL7aFH0WIWkNMQm7RZ2pEEvMEeOmUtJgFLzTDdI3O7SMAb7aFf0iIJmGuEid6Z2nkCrGAPXUmLJ8BUE0y5MqndO8CK9tD7ZHEHSDXAzbvyyLABPsAeHOwsboBHcpyukTHLAvgge/CxWVwAY2KcqJ1gegV785dfbSBqf9d1nxak1IHazwLbH4DVAnPf+eoYAAAAAElFTkSuQmCC",
14 | "style": "rounded",
15 | "backgroundColor": "#FFFF0000"
16 | }
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/__Fixtures__/imageHorizontalAlignment.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
3 | "type": "AdaptiveCard",
4 | "version": "1.0",
5 | "body": [
6 | {
7 | "type": "Image",
8 | "url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+L/xPwAFaAKx7ybCAwAAAABJRU5ErkJggg==",
9 | "size": "small",
10 | "horizontalAlignment": "left"
11 | },
12 | {
13 | "type": "Image",
14 | "url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+L/xPwAFaAKx7ybCAwAAAABJRU5ErkJggg==",
15 | "size": "small",
16 | "horizontalAlignment": "center"
17 | },
18 | {
19 | "type": "Image",
20 | "url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+L/xPwAFaAKx7ybCAwAAAABJRU5ErkJggg==",
21 | "size": "small",
22 | "horizontalAlignment": "right"
23 | }
24 | ]
25 | }
26 |
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/__Fixtures__/imageSize.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
3 | "type": "AdaptiveCard",
4 | "version": "1.0",
5 | "body": [
6 | {
7 | "type": "Image",
8 | "url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAABGdBTUEAALGPC/xhBQAAADhlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAAqACAAQAAAABAAAAKKADAAQAAAABAAAAKAAAAABZLkSWAAABdElEQVRYCc2TCw6CMBBEEbwXeDNv5kE4C4mUMKTAtt0fFBMziu3um5fYNI6vcRyH8HYc2bw9h03T9F3nDV5zW69Bq7l+ntd7WnQDjOw18WerABfAyB543Cy6AFLGqGegl6QZkLCH/S4WzYA5U7nf0KKUJsCMPew1WzQBcgxxzqANlWpAhj3sM1lUA0rMSM6iFVIFKLCHPWqLKkCNEc2d0E4MqLBnsigG1JoIlJq7IkCDPbVFEaDGAMiQ0hlsQAd7YBT9o9mA0uagoVIyiwXoaA+8bIssQEljEJSSO7MIeIE9sLMsFgG5TbFVkpzZWcAL7aFH0WIWkNMQm7RZ2pEEvMEeOmUtJgFLzTDdI3O7SMAb7aFf0iIJmGuEid6Z2nkCrGAPXUmLJ8BUE0y5MqndO8CK9tD7ZHEHSDXAzbvyyLABPsAeHOwsboBHcpyukTHLAvgge/CxWVwAY2KcqJ1gegV785dfbSBqf9d1nxak1IHazwLbH4DVAnPf+eoYAAAAAElFTkSuQmCC",
9 | "size": "auto"
10 | },
11 | {
12 | "type": "Image",
13 | "url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+L/xPwAFaAKx7ybCAwAAAABJRU5ErkJggg==",
14 | "size": "stretch"
15 | },
16 | {
17 | "type": "Image",
18 | "url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+L/xPwAFaAKx7ybCAwAAAABJRU5ErkJggg==",
19 | "size": "small"
20 | },
21 | {
22 | "type": "Image",
23 | "url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+L/xPwAFaAKx7ybCAwAAAABJRU5ErkJggg==",
24 | "size": "medium"
25 | },
26 | {
27 | "type": "Image",
28 | "url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+L/xPwAFaAKx7ybCAwAAAABJRU5ErkJggg==",
29 | "size": "large"
30 | }
31 | ]
32 | }
33 |
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/__Fixtures__/imageWidthHeight.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
3 | "type": "AdaptiveCard",
4 | "version": "1.0",
5 | "body": [
6 | {
7 | "type": "Image",
8 | "url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+L/xPwAFaAKx7ybCAwAAAABJRU5ErkJggg==",
9 | "width": "20px"
10 | },
11 | {
12 | "type": "Image",
13 | "url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+L/xPwAFaAKx7ybCAwAAAABJRU5ErkJggg==",
14 | "height": "60px"
15 | },
16 | {
17 | "type": "Image",
18 | "url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+L/xPwAFaAKx7ybCAwAAAABJRU5ErkJggg==",
19 | "width": "60px",
20 | "height": "20px"
21 | }
22 | ]
23 | }
24 |
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/__Fixtures__/textBlockColor.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
3 | "type": "AdaptiveCard",
4 | "version": "1.0",
5 | "body": [
6 | {
7 | "type": "TextBlock",
8 | "text": "color: default",
9 | "color": "default"
10 | },
11 | {
12 | "type": "TextBlock",
13 | "text": "color: accent",
14 | "color": "accent"
15 | },
16 | {
17 | "type": "TextBlock",
18 | "text": "color: good",
19 | "color": "good"
20 | },
21 | {
22 | "type": "TextBlock",
23 | "text": "color: warning",
24 | "color": "warning"
25 | },
26 | {
27 | "type": "TextBlock",
28 | "text": "color: attention",
29 | "color": "attention"
30 | },
31 | {
32 | "type": "TextBlock",
33 | "text": "color: light",
34 | "color": "light"
35 | },
36 | {
37 | "type": "TextBlock",
38 | "text": "color: dark",
39 | "color": "dark"
40 | }
41 | ]
42 | }
43 |
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/__Fixtures__/textBlockFontType.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
3 | "type": "AdaptiveCard",
4 | "version": "1.2",
5 | "body": [
6 | {
7 | "type": "TextBlock",
8 | "text": "Font type not set."
9 | },
10 | {
11 | "type": "TextBlock",
12 | "text": "Font type set to default.",
13 | "fontType": "default"
14 | },
15 | {
16 | "type": "TextBlock",
17 | "text": "Font type set to monospace.",
18 | "fontType": "monospace"
19 | }
20 | ]
21 | }
22 |
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/__Fixtures__/textBlockHorizontalAlignment.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
3 | "type": "AdaptiveCard",
4 | "version": "1.0",
5 | "body": [
6 | {
7 | "type": "TextBlock",
8 | "text": "horizontalAlignment:left",
9 | "horizontalAlignment": "left"
10 | },
11 | {
12 | "type": "TextBlock",
13 | "text": "horizontalAlignment:center",
14 | "horizontalAlignment": "center"
15 | },
16 | {
17 | "type": "TextBlock",
18 | "text": "horizontalAlignment:right",
19 | "horizontalAlignment": "right"
20 | }
21 | ]
22 | }
23 |
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/__Fixtures__/textBlockIsSubtle.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
3 | "type": "AdaptiveCard",
4 | "version": "1.0",
5 | "body": [
6 | {
7 | "type": "TextBlock",
8 | "text": "isSubtle:false",
9 | "isSubtle": false
10 | },
11 | {
12 | "type": "TextBlock",
13 | "text": "isSubtle:true",
14 | "isSubtle": true
15 | }
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/__Fixtures__/textBlockMaxLines.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
3 | "type": "AdaptiveCard",
4 | "version": "1.0",
5 | "body": [
6 | {
7 | "type": "TextBlock",
8 | "text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
9 | "wrap": true,
10 | "maxLines": 1
11 | },
12 | {
13 | "type": "TextBlock",
14 | "text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.",
15 | "wrap": true,
16 | "maxLines": 2,
17 | "separator": true
18 | }
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/__Fixtures__/textBlockSize.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
3 | "type": "AdaptiveCard",
4 | "version": "1.0",
5 | "body": [
6 | {
7 | "type": "TextBlock",
8 | "text": "size:default"
9 | },
10 | {
11 | "type": "TextBlock",
12 | "text": "size:small",
13 | "size": "small"
14 | },
15 | {
16 | "type": "TextBlock",
17 | "text": "size:default",
18 | "size": "default"
19 | },
20 | {
21 | "type": "TextBlock",
22 | "text": "size:medium",
23 | "size": "medium"
24 | },
25 | {
26 | "type": "TextBlock",
27 | "text": "size:large",
28 | "size": "large"
29 | },
30 | {
31 | "type": "TextBlock",
32 | "text": "size:extraLarge",
33 | "size": "extraLarge"
34 | }
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/__Fixtures__/textBlockWeight.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
3 | "type": "AdaptiveCard",
4 | "version": "1.0",
5 | "body": [
6 | {
7 | "type": "TextBlock",
8 | "text": "weight: lighter",
9 | "weight": "lighter"
10 | },
11 | {
12 | "type": "TextBlock",
13 | "text": "weight: default",
14 | "weight": "default"
15 | },
16 | {
17 | "type": "TextBlock",
18 | "text": "weight: bolder",
19 | "weight": "bolder"
20 | }
21 | ]
22 | }
23 |
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/__Fixtures__/textBlockWrap.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
3 | "type": "AdaptiveCard",
4 | "version": "1.0",
5 | "body": [
6 | {
7 | "type": "TextBlock",
8 | "text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat",
9 | "wrap": false
10 | },
11 | {
12 | "type": "TextBlock",
13 | "text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
14 | "wrap": true,
15 | "separator": true,
16 | "spacing": "medium"
17 | }
18 | ]
19 | }
20 |
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/__Snapshots__/ActionRenderingTests/testHorizontalButtonSpacing.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gonzalezreal/AdaptiveCardUI/4ac1a3c0c14cb951fb530712de7e4ae832573bf3/Tests/AdaptiveCardUITests/UI/__Snapshots__/ActionRenderingTests/testHorizontalButtonSpacing.1.png
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/__Snapshots__/ActionRenderingTests/testHorizontalCenterActions.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gonzalezreal/AdaptiveCardUI/4ac1a3c0c14cb951fb530712de7e4ae832573bf3/Tests/AdaptiveCardUITests/UI/__Snapshots__/ActionRenderingTests/testHorizontalCenterActions.1.png
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/__Snapshots__/ActionRenderingTests/testHorizontalLeftActions.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gonzalezreal/AdaptiveCardUI/4ac1a3c0c14cb951fb530712de7e4ae832573bf3/Tests/AdaptiveCardUITests/UI/__Snapshots__/ActionRenderingTests/testHorizontalLeftActions.1.png
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/__Snapshots__/ActionRenderingTests/testHorizontalRightActions.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gonzalezreal/AdaptiveCardUI/4ac1a3c0c14cb951fb530712de7e4ae832573bf3/Tests/AdaptiveCardUITests/UI/__Snapshots__/ActionRenderingTests/testHorizontalRightActions.1.png
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/__Snapshots__/ActionRenderingTests/testHorizontalStretchActions.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gonzalezreal/AdaptiveCardUI/4ac1a3c0c14cb951fb530712de7e4ae832573bf3/Tests/AdaptiveCardUITests/UI/__Snapshots__/ActionRenderingTests/testHorizontalStretchActions.1.png
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/__Snapshots__/ActionRenderingTests/testMaxActions.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gonzalezreal/AdaptiveCardUI/4ac1a3c0c14cb951fb530712de7e4ae832573bf3/Tests/AdaptiveCardUITests/UI/__Snapshots__/ActionRenderingTests/testMaxActions.1.png
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/__Snapshots__/ActionRenderingTests/testSpacing.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gonzalezreal/AdaptiveCardUI/4ac1a3c0c14cb951fb530712de7e4ae832573bf3/Tests/AdaptiveCardUITests/UI/__Snapshots__/ActionRenderingTests/testSpacing.1.png
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/__Snapshots__/ActionRenderingTests/testVerticalButtonSpacing.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gonzalezreal/AdaptiveCardUI/4ac1a3c0c14cb951fb530712de7e4ae832573bf3/Tests/AdaptiveCardUITests/UI/__Snapshots__/ActionRenderingTests/testVerticalButtonSpacing.1.png
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/__Snapshots__/ActionRenderingTests/testVerticalCenterActions.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gonzalezreal/AdaptiveCardUI/4ac1a3c0c14cb951fb530712de7e4ae832573bf3/Tests/AdaptiveCardUITests/UI/__Snapshots__/ActionRenderingTests/testVerticalCenterActions.1.png
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/__Snapshots__/ActionRenderingTests/testVerticalLeftActions.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gonzalezreal/AdaptiveCardUI/4ac1a3c0c14cb951fb530712de7e4ae832573bf3/Tests/AdaptiveCardUITests/UI/__Snapshots__/ActionRenderingTests/testVerticalLeftActions.1.png
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/__Snapshots__/ActionRenderingTests/testVerticalRightActions.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gonzalezreal/AdaptiveCardUI/4ac1a3c0c14cb951fb530712de7e4ae832573bf3/Tests/AdaptiveCardUITests/UI/__Snapshots__/ActionRenderingTests/testVerticalRightActions.1.png
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/__Snapshots__/ColumnSetRenderingTests/testBackgroundImage.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gonzalezreal/AdaptiveCardUI/4ac1a3c0c14cb951fb530712de7e4ae832573bf3/Tests/AdaptiveCardUITests/UI/__Snapshots__/ColumnSetRenderingTests/testBackgroundImage.1.png
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/__Snapshots__/ColumnSetRenderingTests/testBleed.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gonzalezreal/AdaptiveCardUI/4ac1a3c0c14cb951fb530712de7e4ae832573bf3/Tests/AdaptiveCardUITests/UI/__Snapshots__/ColumnSetRenderingTests/testBleed.1.png
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/__Snapshots__/ColumnSetRenderingTests/testMinHeight.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gonzalezreal/AdaptiveCardUI/4ac1a3c0c14cb951fb530712de7e4ae832573bf3/Tests/AdaptiveCardUITests/UI/__Snapshots__/ColumnSetRenderingTests/testMinHeight.1.png
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/__Snapshots__/ColumnSetRenderingTests/testStyle.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gonzalezreal/AdaptiveCardUI/4ac1a3c0c14cb951fb530712de7e4ae832573bf3/Tests/AdaptiveCardUITests/UI/__Snapshots__/ColumnSetRenderingTests/testStyle.1.png
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/__Snapshots__/ColumnSetRenderingTests/testVerticalContentAlignment.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gonzalezreal/AdaptiveCardUI/4ac1a3c0c14cb951fb530712de7e4ae832573bf3/Tests/AdaptiveCardUITests/UI/__Snapshots__/ColumnSetRenderingTests/testVerticalContentAlignment.1.png
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/__Snapshots__/ColumnSetRenderingTests/testWidth.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gonzalezreal/AdaptiveCardUI/4ac1a3c0c14cb951fb530712de7e4ae832573bf3/Tests/AdaptiveCardUITests/UI/__Snapshots__/ColumnSetRenderingTests/testWidth.1.png
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/__Snapshots__/ContainerRenderingTests/testBackgroundImage.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gonzalezreal/AdaptiveCardUI/4ac1a3c0c14cb951fb530712de7e4ae832573bf3/Tests/AdaptiveCardUITests/UI/__Snapshots__/ContainerRenderingTests/testBackgroundImage.1.png
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/__Snapshots__/ContainerRenderingTests/testBleed.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gonzalezreal/AdaptiveCardUI/4ac1a3c0c14cb951fb530712de7e4ae832573bf3/Tests/AdaptiveCardUITests/UI/__Snapshots__/ContainerRenderingTests/testBleed.1.png
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/__Snapshots__/ContainerRenderingTests/testMinHeight.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gonzalezreal/AdaptiveCardUI/4ac1a3c0c14cb951fb530712de7e4ae832573bf3/Tests/AdaptiveCardUITests/UI/__Snapshots__/ContainerRenderingTests/testMinHeight.1.png
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/__Snapshots__/ContainerRenderingTests/testStyle.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gonzalezreal/AdaptiveCardUI/4ac1a3c0c14cb951fb530712de7e4ae832573bf3/Tests/AdaptiveCardUITests/UI/__Snapshots__/ContainerRenderingTests/testStyle.1.png
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/__Snapshots__/ContainerRenderingTests/testVerticalContentAlignment.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gonzalezreal/AdaptiveCardUI/4ac1a3c0c14cb951fb530712de7e4ae832573bf3/Tests/AdaptiveCardUITests/UI/__Snapshots__/ContainerRenderingTests/testVerticalContentAlignment.1.png
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/__Snapshots__/CustomCardElementRenderingTests/testCustomCardElements.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gonzalezreal/AdaptiveCardUI/4ac1a3c0c14cb951fb530712de7e4ae832573bf3/Tests/AdaptiveCardUITests/UI/__Snapshots__/CustomCardElementRenderingTests/testCustomCardElements.1.png
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/__Snapshots__/FactSetRenderingTests/testFactSet.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gonzalezreal/AdaptiveCardUI/4ac1a3c0c14cb951fb530712de7e4ae832573bf3/Tests/AdaptiveCardUITests/UI/__Snapshots__/FactSetRenderingTests/testFactSet.1.png
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/__Snapshots__/ImageRenderingTests/testBackgroundColor.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gonzalezreal/AdaptiveCardUI/4ac1a3c0c14cb951fb530712de7e4ae832573bf3/Tests/AdaptiveCardUITests/UI/__Snapshots__/ImageRenderingTests/testBackgroundColor.1.png
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/__Snapshots__/ImageRenderingTests/testCustomStyle.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gonzalezreal/AdaptiveCardUI/4ac1a3c0c14cb951fb530712de7e4ae832573bf3/Tests/AdaptiveCardUITests/UI/__Snapshots__/ImageRenderingTests/testCustomStyle.1.png
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/__Snapshots__/ImageRenderingTests/testHorizontalAlignment.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gonzalezreal/AdaptiveCardUI/4ac1a3c0c14cb951fb530712de7e4ae832573bf3/Tests/AdaptiveCardUITests/UI/__Snapshots__/ImageRenderingTests/testHorizontalAlignment.1.png
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/__Snapshots__/ImageRenderingTests/testImage.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gonzalezreal/AdaptiveCardUI/4ac1a3c0c14cb951fb530712de7e4ae832573bf3/Tests/AdaptiveCardUITests/UI/__Snapshots__/ImageRenderingTests/testImage.1.png
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/__Snapshots__/ImageRenderingTests/testOverridingCustomStyle.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gonzalezreal/AdaptiveCardUI/4ac1a3c0c14cb951fb530712de7e4ae832573bf3/Tests/AdaptiveCardUITests/UI/__Snapshots__/ImageRenderingTests/testOverridingCustomStyle.1.png
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/__Snapshots__/ImageRenderingTests/testSize.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gonzalezreal/AdaptiveCardUI/4ac1a3c0c14cb951fb530712de7e4ae832573bf3/Tests/AdaptiveCardUITests/UI/__Snapshots__/ImageRenderingTests/testSize.1.png
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/__Snapshots__/ImageRenderingTests/testWidthHeight.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gonzalezreal/AdaptiveCardUI/4ac1a3c0c14cb951fb530712de7e4ae832573bf3/Tests/AdaptiveCardUITests/UI/__Snapshots__/ImageRenderingTests/testWidthHeight.1.png
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/__Snapshots__/ImageSetRenderingTests/testImageSet.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gonzalezreal/AdaptiveCardUI/4ac1a3c0c14cb951fb530712de7e4ae832573bf3/Tests/AdaptiveCardUITests/UI/__Snapshots__/ImageSetRenderingTests/testImageSet.1.png
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/__Snapshots__/RichTextBlockRenderingTests/testRichTextBlock.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gonzalezreal/AdaptiveCardUI/4ac1a3c0c14cb951fb530712de7e4ae832573bf3/Tests/AdaptiveCardUITests/UI/__Snapshots__/RichTextBlockRenderingTests/testRichTextBlock.1.png
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/__Snapshots__/TextBlockRenderingTests/testColor.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gonzalezreal/AdaptiveCardUI/4ac1a3c0c14cb951fb530712de7e4ae832573bf3/Tests/AdaptiveCardUITests/UI/__Snapshots__/TextBlockRenderingTests/testColor.1.png
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/__Snapshots__/TextBlockRenderingTests/testFontType.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gonzalezreal/AdaptiveCardUI/4ac1a3c0c14cb951fb530712de7e4ae832573bf3/Tests/AdaptiveCardUITests/UI/__Snapshots__/TextBlockRenderingTests/testFontType.1.png
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/__Snapshots__/TextBlockRenderingTests/testHorizontalAlignment.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gonzalezreal/AdaptiveCardUI/4ac1a3c0c14cb951fb530712de7e4ae832573bf3/Tests/AdaptiveCardUITests/UI/__Snapshots__/TextBlockRenderingTests/testHorizontalAlignment.1.png
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/__Snapshots__/TextBlockRenderingTests/testIsSubtle.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gonzalezreal/AdaptiveCardUI/4ac1a3c0c14cb951fb530712de7e4ae832573bf3/Tests/AdaptiveCardUITests/UI/__Snapshots__/TextBlockRenderingTests/testIsSubtle.1.png
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/__Snapshots__/TextBlockRenderingTests/testMaxLines.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gonzalezreal/AdaptiveCardUI/4ac1a3c0c14cb951fb530712de7e4ae832573bf3/Tests/AdaptiveCardUITests/UI/__Snapshots__/TextBlockRenderingTests/testMaxLines.1.png
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/__Snapshots__/TextBlockRenderingTests/testSize.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gonzalezreal/AdaptiveCardUI/4ac1a3c0c14cb951fb530712de7e4ae832573bf3/Tests/AdaptiveCardUITests/UI/__Snapshots__/TextBlockRenderingTests/testSize.1.png
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/__Snapshots__/TextBlockRenderingTests/testWeight.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gonzalezreal/AdaptiveCardUI/4ac1a3c0c14cb951fb530712de7e4ae832573bf3/Tests/AdaptiveCardUITests/UI/__Snapshots__/TextBlockRenderingTests/testWeight.1.png
--------------------------------------------------------------------------------
/Tests/AdaptiveCardUITests/UI/__Snapshots__/TextBlockRenderingTests/testWrap.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gonzalezreal/AdaptiveCardUI/4ac1a3c0c14cb951fb530712de7e4ae832573bf3/Tests/AdaptiveCardUITests/UI/__Snapshots__/TextBlockRenderingTests/testWrap.1.png
--------------------------------------------------------------------------------