├── .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 --------------------------------------------------------------------------------