From f36aac0fc0dbbbd113bd9b35f73a63527d93a85c Mon Sep 17 00:00:00 2001 From: Jeriel Ng Date: Tue, 3 Oct 2023 12:20:17 -0400 Subject: [PATCH] Version 7.0.0 --- BrazeKit.podspec | 6 +- BrazeKitCompat.podspec | 8 +- BrazeLocation.podspec | 8 +- BrazeNotificationService.podspec | 6 +- BrazePushStory.podspec | 6 +- BrazeUI.podspec | 6 +- BrazeUICompat.podspec | 6 +- CHANGELOG.md | 24 ++ .../CardsInfoViewController.m | 2 +- Examples/ObjC/manual-integration-setup.sh | 2 +- .../AppDelegate.swift | 37 +++- .../ContentCardUI-Customization/Readme.swift | 13 +- .../ContentCards-Custom-UI/AppDelegate.swift | 8 +- .../CardsInfoViewController.swift | 8 +- Examples/Swift/manual-integration-setup.sh | 2 +- Package.swift | 16 +- README.md | 2 +- .../AppboyKit/ABKContentCardsController.m | 2 +- .../BrazeKitCompat/include/ABKBannerCard.h | 2 +- ...swift => ContentCardUIImageOnlyCell.swift} | 18 +- .../ContentCardUI/ContentCardMocks.swift | 4 +- .../BrazeUI/ContentCardUI/ContentCardUI.swift | 4 +- .../ContentCardUIViewController.swift | 20 +- Sources/BrazeUI/Dependencies/Align.swift | 33 +-- .../InAppMessageUI/InAppMessageUIError.swift | 4 + .../Views/InAppMessageUIContainerView.swift | 2 +- .../Views/InAppMessageUIModalView.swift | 141 ++---------- .../Views/InAppMessageUISlideupView.swift | 13 +- .../Views/Misc/ModalTextView.swift | 207 ++++++++++++++++++ .../ABKContentCards/AppboyContentCards.h | 2 + 30 files changed, 400 insertions(+), 212 deletions(-) rename Sources/BrazeUI/ContentCardUI/Cells/{ContentCardUIBannerCell.swift => ContentCardUIImageOnlyCell.swift} (80%) create mode 100644 Sources/BrazeUI/InAppMessageUI/Views/Misc/ModalTextView.swift diff --git a/BrazeKit.podspec b/BrazeKit.podspec index 2cf3502362..9ab173ec51 100644 --- a/BrazeKit.podspec +++ b/BrazeKit.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'BrazeKit' - s.version = '6.6.1' + s.version = '7.0.0' s.summary = 'Braze Main SDK library providing support for analytics and push notifications.' s.homepage = 'https://braze.com' @@ -9,8 +9,8 @@ Pod::Spec.new do |s| s.authors = 'Braze, Inc.' s.source = { - :http => 'https://github.com/braze-inc/braze-swift-sdk/releases/download/6.6.1/BrazeKit.zip', - :sha256 => 'a8047d27fcbeb96cd19241e822241e97af36d1bff7afcc62f8bc5264a9fb8432' + :http => 'https://github.com/braze-inc/braze-swift-sdk/releases/download/7.0.0/BrazeKit.zip', + :sha256 => 'ae890a450e889ad57399b36835af98699defc02da2fff9bd1756b8963d545eb5' } s.swift_version = '5.0' diff --git a/BrazeKitCompat.podspec b/BrazeKitCompat.podspec index aae2aac761..42478b0ef5 100644 --- a/BrazeKitCompat.podspec +++ b/BrazeKitCompat.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'BrazeKitCompat' - s.version = '6.6.1' + s.version = '7.0.0' s.summary = 'Compatibility library for users migrating from AppboyKit.' s.homepage = 'https://braze.com' @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.license = { :type => 'Commercial' } s.authors = 'Braze, Inc.' - s.source = { :git => 'https://github.com/braze-inc/braze-swift-sdk.git', :tag => '6.6.1' } + s.source = { :git => 'https://github.com/braze-inc/braze-swift-sdk.git', :tag => '7.0.0' } s.swift_version = '5.0' s.ios.deployment_target = '11.0' @@ -18,8 +18,8 @@ Pod::Spec.new do |s| s.source_files = 'Sources/BrazeKitCompat/**/*.{h,m}' s.public_header_files = 'Sources/BrazeKitCompat/include/*.h' - s.dependency 'BrazeKit', '6.6.1' - s.dependency 'BrazeLocation', '6.6.1' + s.dependency 'BrazeKit', '7.0.0' + s.dependency 'BrazeLocation', '7.0.0' s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } end diff --git a/BrazeLocation.podspec b/BrazeLocation.podspec index 7ec23c4de7..4111030004 100644 --- a/BrazeLocation.podspec +++ b/BrazeLocation.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'BrazeLocation' - s.version = '6.6.1' + s.version = '7.0.0' s.summary = 'Braze location library providing support for location analytics and geofence monitoring.' s.homepage = 'https://braze.com' @@ -9,8 +9,8 @@ Pod::Spec.new do |s| s.authors = 'Braze, Inc.' s.source = { - :http => 'https://github.com/braze-inc/braze-swift-sdk/releases/download/6.6.1/BrazeLocation.zip', - :sha256 => 'b89f1fc5287e90ea9e936c2f83bf2afa6622d2c2c883f32a7cfea6d78d9700e7' + :http => 'https://github.com/braze-inc/braze-swift-sdk/releases/download/7.0.0/BrazeLocation.zip', + :sha256 => 'eb32a3834e0dc09df3c069647b6ac1e1fbce5cb1fed49050325277e12c7773cd' } s.swift_version = '5.0' @@ -21,7 +21,7 @@ Pod::Spec.new do |s| # Depends on BrazeKit because BrazeKit includes the internal _BrazeLocationClient symbols required # for linking against BrazeLocation. - s.dependency 'BrazeKit', '6.6.1' + s.dependency 'BrazeKit', '7.0.0' s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } end diff --git a/BrazeNotificationService.podspec b/BrazeNotificationService.podspec index c827f5a88f..276e5ff558 100644 --- a/BrazeNotificationService.podspec +++ b/BrazeNotificationService.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'BrazeNotificationService' - s.version = '6.6.1' + s.version = '7.0.0' s.summary = 'Braze notification service extension library providing support for Rich Push notifications.' s.homepage = 'https://braze.com' @@ -9,8 +9,8 @@ Pod::Spec.new do |s| s.authors = 'Braze, Inc.' s.source = { - :http => 'https://github.com/braze-inc/braze-swift-sdk/releases/download/6.6.1/BrazeNotificationService.zip', - :sha256 => 'd375317a93be6b4fcd6b56089ec095d9df33e6e8b3ed965db054eaa0eddf4a09' + :http => 'https://github.com/braze-inc/braze-swift-sdk/releases/download/7.0.0/BrazeNotificationService.zip', + :sha256 => 'ebf1ca2501212501853662a3853b498063a3f2b46ae2de072262666c4f731e24' } s.swift_version = '5.0' diff --git a/BrazePushStory.podspec b/BrazePushStory.podspec index 475494fcb0..1b9581a4a5 100644 --- a/BrazePushStory.podspec +++ b/BrazePushStory.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'BrazePushStory' - s.version = '6.6.1' + s.version = '7.0.0' s.summary = 'Braze notification content extension library providing support for Push Stories.' s.homepage = 'https://braze.com' @@ -9,8 +9,8 @@ Pod::Spec.new do |s| s.authors = 'Braze, Inc.' s.source = { - :http => 'https://github.com/braze-inc/braze-swift-sdk/releases/download/6.6.1/BrazePushStory.zip', - :sha256 => 'ee51329c13f235e89fb023baaf1c20a583f0b66c21c1928b52f3e8b9ba7a3497' + :http => 'https://github.com/braze-inc/braze-swift-sdk/releases/download/7.0.0/BrazePushStory.zip', + :sha256 => '26e3ff1284d14a2d2c7c9d02ee08745bc0babd662ed5067b306ff8aa90d4bb3d' } s.swift_version = '5.0' diff --git a/BrazeUI.podspec b/BrazeUI.podspec index f51b0f3067..437f4ac356 100644 --- a/BrazeUI.podspec +++ b/BrazeUI.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'BrazeUI' - s.version = '6.6.1' + s.version = '7.0.0' s.summary = 'Braze-provided user interface library for In-App Messages and Content Cards.' s.homepage = 'https://braze.com' @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.license = { :type => 'Commercial' } s.authors = 'Braze, Inc.' - s.source = { :git => 'https://github.com/braze-inc/braze-swift-sdk.git', :tag => '6.6.1' } + s.source = { :git => 'https://github.com/braze-inc/braze-swift-sdk.git', :tag => '7.0.0' } s.swift_version = '5.0' s.ios.deployment_target = '11.0' @@ -17,7 +17,7 @@ Pod::Spec.new do |s| s.source_files = 'Sources/BrazeUI/**/*.swift' s.resource_bundles = { 'BrazeUI' => ['Sources/BrazeUI/Resources/**/*'] } - s.dependency 'BrazeKit', '6.6.1' + s.dependency 'BrazeKit', '7.0.0' s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } end diff --git a/BrazeUICompat.podspec b/BrazeUICompat.podspec index f872a424a7..3a42ec27fa 100644 --- a/BrazeUICompat.podspec +++ b/BrazeUICompat.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'BrazeUICompat' - s.version = '6.6.1' + s.version = '7.0.0' s.summary = 'Compatibility UI library for users migrating from AppboyUI.' s.homepage = 'https://braze.com' @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.license = { :type => 'Commercial' } s.authors = 'Braze, Inc.' - s.source = { :git => 'https://github.com/braze-inc/braze-swift-sdk.git', :tag => '6.6.1' } + s.source = { :git => 'https://github.com/braze-inc/braze-swift-sdk.git', :tag => '7.0.0' } s.swift_version = '5.0' s.ios.deployment_target = '11.0' @@ -18,7 +18,7 @@ Pod::Spec.new do |s| s.public_header_files = 'Sources/BrazeUICompat/ABK*/**/*.h' s.resource_bundles = { 'BrazeUICompat' => 'Sources/BrazeUICompat/*/Resources/**/*.*' } - s.dependency 'BrazeKitCompat', '6.6.1' + s.dependency 'BrazeKitCompat', '7.0.0' s.dependency 'SDWebImage', '>= 5.8.2', '< 6' s.user_target_xcconfig = { 'CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES' => 'YES' } diff --git a/CHANGELOG.md b/CHANGELOG.md index 15049ae831..f50c75dd6f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,27 @@ +## 7.0.0 + +##### Breaking +- The `useUUIDAsDeviceId` configuration is now enabled by default. + - For more details on the impacts, refer to this [Collecting IDFV - Swift](https://www.braze.com/docs/developer_guide/platform_integration_guides/ios/initial_sdk_setup/other_sdk_customizations/swift_idfv/). +- The `Banner` Content Card type and corresponding UI elements have been renamed to `ImageOnly`. All member methods and properties remain the same. + - `Braze.ContentCard.Banner` → `Braze.ContentCard.ImageOnly` + - `BrazeContentCardUI.BannerCell` → `BrazeContentCardUI.ImageOnlyCell` +- Refactors some text layout logic in BrazeUI into a new `Braze.ModalTextView` class. +- Updates the behavior for Feature Flags methods. + - `FeatureFlags.featureFlag(id:)` now returns `nil` for an ID that does not exist. + - `FeatureFlags.subscribeToUpdates(:)` will trigger the callback when any refresh request completes with a success or failure. + - The callback will also trigger immediately upon initial subscription if previously cached data exists. + +##### Fixed +- Fixes compiler warnings about Swift 6 when compiling `BrazeUI` while using Xcode 15. +- Exposes public imports for `ABKClassicImageContentCardCell.h` and `ABKControlTableViewCell.h` for use in the BrazeUICompat layer. +- Adds additional safeguards around invalid constraint values for `BrazeInAppMessageUI.SlideupView` + +##### Added +- Adds the `enableDarkTheme` property to `BrazeContentCardUI.ViewController.Attributes`. + - Set this field to `false` to prevent the Content Cards feed UI from adopting dark theme styling when the device is in dark mode. + - This field is `true` by default. + ## 6.6.1 ##### Fixed diff --git a/Examples/ObjC/Sources/ContentCards-Custom-UI/CardsInfoViewController.m b/Examples/ObjC/Sources/ContentCards-Custom-UI/CardsInfoViewController.m index c310086793..78ec9ea034 100644 --- a/Examples/ObjC/Sources/ContentCards-Custom-UI/CardsInfoViewController.m +++ b/Examples/ObjC/Sources/ContentCards-Custom-UI/CardsInfoViewController.m @@ -118,7 +118,7 @@ + (Section *)cardSectionFromCard:(BRZContentCardRaw *)card case BRZContentCardRawTypeClassic: [fields addObject:[Field fieldWithName:@"type" value:@"classic"]]; break; - case BRZContentCardRawTypeBanner: + case BRZContentCardRawTypeImageOnly: [fields addObject:[Field fieldWithName:@"type" value:@"banner"]]; break; case BRZContentCardRawTypeCaptionedImage: diff --git a/Examples/ObjC/manual-integration-setup.sh b/Examples/ObjC/manual-integration-setup.sh index 14a42105df..a4c4279aa2 100755 --- a/Examples/ObjC/manual-integration-setup.sh +++ b/Examples/ObjC/manual-integration-setup.sh @@ -20,7 +20,7 @@ if [ ! -f "manual-integration-setup.sh" ]; then fi # Constants -url="https://github.com/braze-inc/braze-swift-sdk/releases/download/6.6.1/braze-swift-sdk-prebuilt.zip" +url="https://github.com/braze-inc/braze-swift-sdk/releases/download/7.0.0/braze-swift-sdk-prebuilt.zip" echo "→" "Cleaning up" rm -rf braze-swift-sdk-prebuilt diff --git a/Examples/Swift/Sources/ContentCardUI-Customization/AppDelegate.swift b/Examples/Swift/Sources/ContentCardUI-Customization/AppDelegate.swift index 0fc6a02548..2f048babf6 100644 --- a/Examples/Swift/Sources/ContentCardUI-Customization/AppDelegate.swift +++ b/Examples/Swift/Sources/ContentCardUI-Customization/AppDelegate.swift @@ -28,13 +28,14 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // MARK: - Customizations -#warning(""" -For demonstration purposes, this example application uses an alternate Content Card view controller initializer. +#warning( + """ + For demonstration purposes, this example application uses an alternate Content Card view controller initializer. -In your implementation, you are expected to use the standard `init(braze:attributes:)` initializer to automatically link the UI to your braze instance. + In your implementation, you are expected to use the standard `init(braze:attributes:)` initializer to automatically link the UI to your braze instance. -See https://braze-inc.github.io/braze-swift-sdk/documentation/brazeui/brazecontentcardui/viewcontroller/init(braze:attributes:) -""") + See https://braze-inc.github.io/braze-swift-sdk/documentation/brazeui/brazecontentcardui/viewcontroller/init(braze:attributes:) + """) extension AppDelegate { @@ -98,7 +99,7 @@ extension AppDelegate { var attributes = BrazeContentCardUI.ViewController.Attributes.defaults // Create two static cards - let headerCard: Braze.ContentCard = .banner( + let headerCard: Braze.ContentCard = .imageOnly( .init( data: .init(viewed: true), image: .mockImage( @@ -106,7 +107,7 @@ extension AppDelegate { drawCorners: false) ) ) - let footerCard: Braze.ContentCard = .banner( + let footerCard: Braze.ContentCard = .imageOnly( .init( data: .init(viewed: true), image: .mockImage( @@ -192,6 +193,26 @@ extension AppDelegate { navigationController.pushViewController(viewController, animated: true) } + static func disableDarkThemeCustomization() { + var attributes = BrazeContentCardUI.ViewController.Attributes.defaults + + attributes.enableDarkTheme = false + + let viewController = BrazeContentCardUI.ViewController( + initialCards: cards, + attributes: attributes + ) + viewController.title = "Dark Theme Disabled" + + // This is necessary to stylize the `title` text, which is set off of + // `viewController`, but is actually situated under `navigationController.navigationBar` + // in the view hierarchy. + if #available(iOS 13.0, *) { + navigationController.navigationBar.overrideUserInterfaceStyle = .light + } + navigationController.pushViewController(viewController, animated: true) + } + } // MARK: - Helpers @@ -200,7 +221,7 @@ private var cards: [Braze.ContentCard] = [ classicPinned, classic, classicImage, - banner, + imageOnly, captionedImage, ] diff --git a/Examples/Swift/Sources/ContentCardUI-Customization/Readme.swift b/Examples/Swift/Sources/ContentCardUI-Customization/Readme.swift index 712d834326..8ea340b63a 100644 --- a/Examples/Swift/Sources/ContentCardUI-Customization/Readme.swift +++ b/Examples/Swift/Sources/ContentCardUI-Customization/Readme.swift @@ -49,6 +49,11 @@ let actions: [(String, String, (ReadmeViewController) -> Void)] = [ "Modify the cards texts before display.", transformCardsCustomization ), + ( + "Disable dark theme", + "Make cards present in light theme even if the device is in dark mode.", + disableDarkThemeCustomization + ), ] // MARK: - Internal @@ -88,8 +93,8 @@ let classicImage: Braze.ContentCard = withContext( ) ) -let banner: Braze.ContentCard = withContext( - .banner( +let imageOnly: Braze.ContentCard = withContext( + .imageOnly( .init( data: .init(clickAction: .url(URL(string: "https://example.com")!, useWebView: true)), image: .mockImage(width: 1200, height: 675, text: "🧁", textSize: 256, drawCorners: false), @@ -142,6 +147,10 @@ func transformCardsCustomization(_ viewController: ReadmeViewController) { AppDelegate.transformCardsCustomization() } +func disableDarkThemeCustomization(_ viewController: ReadmeViewController) { + AppDelegate.disableDarkThemeCustomization() +} + func withContext(_ card: Braze.ContentCard) -> Braze.ContentCard { var card = card diff --git a/Examples/Swift/Sources/ContentCards-Custom-UI/AppDelegate.swift b/Examples/Swift/Sources/ContentCards-Custom-UI/AppDelegate.swift index 7471483a48..b68b1daaa9 100644 --- a/Examples/Swift/Sources/ContentCards-Custom-UI/AppDelegate.swift +++ b/Examples/Swift/Sources/ContentCards-Custom-UI/AppDelegate.swift @@ -50,8 +50,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { switch card { case .classic(let classic): print("classic - title:", classic.title) - case .banner(let banner): - print("banner - image:", banner.image) + case .imageOnly(let imageOnly): + print("imageOnly - image:", imageOnly.image) default: break } @@ -63,8 +63,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { print("classic - title:", title) } - if let image = card.banner?.image { - print("banner - image:", image) + if let image = card.imageOnly?.image { + print("imageOnly - image:", image) } // A wrapper / compatibility representation of the card is accessible via `.json()` diff --git a/Examples/Swift/Sources/ContentCards-Custom-UI/CardsInfoViewController.swift b/Examples/Swift/Sources/ContentCards-Custom-UI/CardsInfoViewController.swift index e9fa710571..9471f63cbd 100644 --- a/Examples/Swift/Sources/ContentCards-Custom-UI/CardsInfoViewController.swift +++ b/Examples/Swift/Sources/ContentCards-Custom-UI/CardsInfoViewController.swift @@ -111,11 +111,11 @@ final class CardsInfoViewController: UITableViewController { Field(name: "description", value: classicImage.description), Field(name: "domain", value: classicImage.domain ?? "nil"), ] - case .banner(let banner): - type.value = "banner" + case .imageOnly(let imageOnly): + type.value = "imageOnly" cardFields += [ - Field(name: "image", value: banner.image), - Field(name: "imageAspectRatio", value: banner.imageAspectRatio ?? "nil"), + Field(name: "image", value: imageOnly.image), + Field(name: "imageAspectRatio", value: imageOnly.imageAspectRatio ?? "nil"), ] case .captionedImage(let captionedImage): type.value = "captionedImage" diff --git a/Examples/Swift/manual-integration-setup.sh b/Examples/Swift/manual-integration-setup.sh index 14a42105df..a4c4279aa2 100755 --- a/Examples/Swift/manual-integration-setup.sh +++ b/Examples/Swift/manual-integration-setup.sh @@ -20,7 +20,7 @@ if [ ! -f "manual-integration-setup.sh" ]; then fi # Constants -url="https://github.com/braze-inc/braze-swift-sdk/releases/download/6.6.1/braze-swift-sdk-prebuilt.zip" +url="https://github.com/braze-inc/braze-swift-sdk/releases/download/7.0.0/braze-swift-sdk-prebuilt.zip" echo "→" "Cleaning up" rm -rf braze-swift-sdk-prebuilt diff --git a/Package.swift b/Package.swift index da8c480aca..17dfe8b21a 100644 --- a/Package.swift +++ b/Package.swift @@ -47,8 +47,8 @@ let package = Package( targets: [ .binaryTarget( name: "BrazeKit", - url: "https://github.com/braze-inc/braze-swift-sdk/releases/download/6.6.1/BrazeKit.zip", - checksum: "a8047d27fcbeb96cd19241e822241e97af36d1bff7afcc62f8bc5264a9fb8432" + url: "https://github.com/braze-inc/braze-swift-sdk/releases/download/7.0.0/BrazeKit.zip", + checksum: "ae890a450e889ad57399b36835af98699defc02da2fff9bd1756b8963d545eb5" ), .target( name: "BrazeKitResources", @@ -65,18 +65,18 @@ let package = Package( ), .binaryTarget( name: "BrazeLocation", - url: "https://github.com/braze-inc/braze-swift-sdk/releases/download/6.6.1/BrazeLocation.zip", - checksum: "b89f1fc5287e90ea9e936c2f83bf2afa6622d2c2c883f32a7cfea6d78d9700e7" + url: "https://github.com/braze-inc/braze-swift-sdk/releases/download/7.0.0/BrazeLocation.zip", + checksum: "eb32a3834e0dc09df3c069647b6ac1e1fbce5cb1fed49050325277e12c7773cd" ), .binaryTarget( name: "BrazeNotificationService", - url: "https://github.com/braze-inc/braze-swift-sdk/releases/download/6.6.1/BrazeNotificationService.zip", - checksum: "d375317a93be6b4fcd6b56089ec095d9df33e6e8b3ed965db054eaa0eddf4a09" + url: "https://github.com/braze-inc/braze-swift-sdk/releases/download/7.0.0/BrazeNotificationService.zip", + checksum: "ebf1ca2501212501853662a3853b498063a3f2b46ae2de072262666c4f731e24" ), .binaryTarget( name: "BrazePushStory", - url: "https://github.com/braze-inc/braze-swift-sdk/releases/download/6.6.1/BrazePushStory.zip", - checksum: "ee51329c13f235e89fb023baaf1c20a583f0b66c21c1928b52f3e8b9ba7a3497" + url: "https://github.com/braze-inc/braze-swift-sdk/releases/download/7.0.0/BrazePushStory.zip", + checksum: "26e3ff1284d14a2d2c7c9d02ee08745bc0babd662ed5067b306ff8aa90d4bb3d" ), .target( name: "BrazeKitCompat", diff --git a/README.md b/README.md index 3467c6346e..1bb9a3c8ad 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@

- Version: 6.6.1 + Version: 7.0.0 -/* +/* * This property is the URL of the card's image. */ @property (copy) NSString *image; diff --git a/Sources/BrazeUI/ContentCardUI/Cells/ContentCardUIBannerCell.swift b/Sources/BrazeUI/ContentCardUI/Cells/ContentCardUIImageOnlyCell.swift similarity index 80% rename from Sources/BrazeUI/ContentCardUI/Cells/ContentCardUIBannerCell.swift rename to Sources/BrazeUI/ContentCardUI/Cells/ContentCardUIImageOnlyCell.swift index 7559f92ea8..17e9b22391 100644 --- a/Sources/BrazeUI/ContentCardUI/Cells/ContentCardUIBannerCell.swift +++ b/Sources/BrazeUI/ContentCardUI/Cells/ContentCardUIImageOnlyCell.swift @@ -3,11 +3,11 @@ import UIKit extension BrazeContentCardUI { - /// The Content Card cell which displays Banner cards. - open class BannerCell: ImageCell { + /// The Content Card cell which displays ImageOnly cards. + open class ImageOnlyCell: ImageCell { /// The type identifier. - public static let identifier = "BrazeContentCardUI.BannerCell" + public static let identifier = "BrazeContentCardUI.ImageOnlyCell" // MARK: - Initialization @@ -39,11 +39,11 @@ extension BrazeContentCardUI { // MARK: - Card Update - /// Updates the cell with the passed banner content card. + /// Updates the cell with the passed imageOnly content card. /// - Parameters: /// - card: The content card to display. /// - imageLoad: The current image load state. - open func set(card: Braze.ContentCard.Banner, imageLoad: AsyncImageView.ImageLoad?) { + open func set(card: Braze.ContentCard.ImageOnly, imageLoad: AsyncImageView.ImageLoad?) { if case .success = imageLoad { } else { // Set the image view aspect ratio using the content card's value only if the image isn't @@ -68,11 +68,11 @@ extension BrazeContentCardUI { import SwiftUI - struct BannerCell_Previews: PreviewProvider { + struct ImageOnlyCell_Previews: PreviewProvider { static let cards: [Braze.ContentCard] = [ - .banner(.mockPinned), - .banner(.mockUnviewed), - .banner(.mockViewed), + .imageOnly(.mockPinned), + .imageOnly(.mockUnviewed), + .imageOnly(.mockViewed), ] static var previews: some View { BrazeContentCardUI.ViewController(initialCards: cards) diff --git a/Sources/BrazeUI/ContentCardUI/ContentCardMocks.swift b/Sources/BrazeUI/ContentCardUI/ContentCardMocks.swift index e369e6e1ef..84e87fd336 100644 --- a/Sources/BrazeUI/ContentCardUI/ContentCardMocks.swift +++ b/Sources/BrazeUI/ContentCardUI/ContentCardMocks.swift @@ -148,9 +148,9 @@ import UIKit } - // MARK: - Banner + // MARK: - ImageOnly - extension Braze.ContentCard.Banner { + extension Braze.ContentCard.ImageOnly { private static let backgroundColor = UIColor(red: 0.38, green: 0.64, blue: 0.74, alpha: 1.00) diff --git a/Sources/BrazeUI/ContentCardUI/ContentCardUI.swift b/Sources/BrazeUI/ContentCardUI/ContentCardUI.swift index 0661e51606..6673638a1d 100644 --- a/Sources/BrazeUI/ContentCardUI/ContentCardUI.swift +++ b/Sources/BrazeUI/ContentCardUI/ContentCardUI.swift @@ -14,8 +14,8 @@ extension Braze.ContentCard { return BrazeContentCardUI.ClassicCell.identifier case .classicImage: return BrazeContentCardUI.ClassicImageCell.identifier - case .banner: - return BrazeContentCardUI.BannerCell.identifier + case .imageOnly: + return BrazeContentCardUI.ImageOnlyCell.identifier case .captionedImage: return BrazeContentCardUI.CaptionedImageCell.identifier case .control: diff --git a/Sources/BrazeUI/ContentCardUI/ContentCardUIViewController.swift b/Sources/BrazeUI/ContentCardUI/ContentCardUIViewController.swift index 9a081aa0f2..9793681035 100644 --- a/Sources/BrazeUI/ContentCardUI/ContentCardUIViewController.swift +++ b/Sources/BrazeUI/ContentCardUI/ContentCardUIViewController.swift @@ -50,7 +50,7 @@ extension BrazeContentCardUI { public var cells: [String: UITableViewCell.Type] = [ ClassicCell.identifier: ClassicCell.self, ClassicImageCell.identifier: ClassicImageCell.self, - BannerCell.identifier: BannerCell.self, + ImageOnlyCell.identifier: ImageOnlyCell.self, CaptionedImageCell.identifier: CaptionedImageCell.self, ControlCell.identifier: ControlCell.self, ] @@ -93,6 +93,9 @@ extension BrazeContentCardUI { /// The empty state message color. public var emptyStateMessageColor: UIColor = .brazeLabel + /// Flag specifying whether content cards should display in dark theme when the device is in dark mode. + public var enableDarkTheme: Bool = true + /// The default attributes. public static let defaults = Self() } @@ -137,6 +140,11 @@ extension BrazeContentCardUI { emptyStateLabel.text = attributes.emptyStateMessage emptyStateLabel.font = attributes.emptyStateMessageFont emptyStateLabel.textColor = attributes.emptyStateMessageColor + + // EnableDarkTheme + if #available(iOS 13, *) { + overrideUserInterfaceStyle = attributes.enableDarkTheme ? .unspecified : .light + } } // MARK: Initialization @@ -319,11 +327,11 @@ extension BrazeContentCardUI { cell.attributes = attributes cell.set(card: classicImage, imageLoad: imageLoad) return cell - case .banner(let banner): - let cell = dequeue(as: BannerCell.self) + case .imageOnly(let imageOnly): + let cell = dequeue(as: ImageOnlyCell.self) cell.contentImageView.retry = retry cell.attributes = attributes - cell.set(card: banner, imageLoad: imageLoad) + cell.set(card: imageOnly, imageLoad: imageLoad) return cell case .captionedImage(let captionedImage): let cell = dequeue(as: CaptionedImageCell.self) @@ -657,7 +665,7 @@ extension BrazeContentCardUI { static let cards: [Braze.ContentCard] = [ .classic(.mockDomain), .classicImage(.mockUnviewed), - .banner(.mockPinned), + .imageOnly(.mockPinned), .captionedImage(.mockShort), ] @@ -673,7 +681,7 @@ extension BrazeContentCardUI { public typealias Cell = BrazeContentCardUI.Cell public typealias ClassicCell = BrazeContentCardUI.ClassicCell public typealias ClassicImageCell = BrazeContentCardUI.ClassicImageCell - public typealias BannerCell = BrazeContentCardUI.BannerCell + public typealias ImageOnlyCell = BrazeContentCardUI.ImageOnlyCell public typealias CaptionedImageCell = BrazeContentCardUI.CaptionedImageCell public typealias ControlCell = BrazeContentCardUI.ControlCell } diff --git a/Sources/BrazeUI/Dependencies/Align.swift b/Sources/BrazeUI/Dependencies/Align.swift index bb1da5d2f7..d1f0cbb065 100644 --- a/Sources/BrazeUI/Dependencies/Align.swift +++ b/Sources/BrazeUI/Dependencies/Align.swift @@ -1,6 +1,9 @@ // The MIT License (MIT) // -// Copyright (c) 2017-2020 Alexander Grebenyuk (github.com/kean). +// Copyright (c) 2017-2022 Alexander Grebenyuk (github.com/kean). + +// Contains the following changes for Xcode 15 support: +// - https://github.com/kean/Align/commit/3bc174c128b6e6de52be95429bd626d265f11ce3 #if os(iOS) || os(tvOS) import UIKit @@ -157,20 +160,20 @@ func * (anchor: Anchor, multiplier: CGFloat) -> Anchor( - _ anchor: Anchor, constant: CGFloat = 0 + @discardableResult func equal( + _ anchor: Anchor, constant: CGFloat = 0 ) -> NSLayoutConstraint { Constraints.add(self, anchor, constant: constant, relation: .equal) } - @discardableResult func greaterThanOrEqual( - _ anchor: Anchor, constant: CGFloat = 0 + @discardableResult func greaterThanOrEqual( + _ anchor: Anchor, constant: CGFloat = 0 ) -> NSLayoutConstraint { Constraints.add(self, anchor, constant: constant, relation: .greaterThanOrEqual) } - @discardableResult func lessThanOrEqual( - _ anchor: Anchor, constant: CGFloat = 0 + @discardableResult func lessThanOrEqual( + _ anchor: Anchor, constant: CGFloat = 0 ) -> NSLayoutConstraint { Constraints.add(self, anchor, constant: constant, relation: .lessThanOrEqual) } @@ -180,20 +183,20 @@ extension Anchor where Type: AnchorType.Alignment { extension Anchor where Type: AnchorType.Dimension { /// Adds a constraint that defines the anchors' attributes as equal to each other. - @discardableResult func equal( - _ anchor: Anchor, constant: CGFloat = 0 + @discardableResult func equal( + _ anchor: Anchor, constant: CGFloat = 0 ) -> NSLayoutConstraint { Constraints.add(self, anchor, constant: constant, relation: .equal) } - @discardableResult func greaterThanOrEqual( - _ anchor: Anchor, constant: CGFloat = 0 + @discardableResult func greaterThanOrEqual( + _ anchor: Anchor, constant: CGFloat = 0 ) -> NSLayoutConstraint { Constraints.add(self, anchor, constant: constant, relation: .greaterThanOrEqual) } - @discardableResult func lessThanOrEqual( - _ anchor: Anchor, constant: CGFloat = 0 + @discardableResult func lessThanOrEqual( + _ anchor: Anchor, constant: CGFloat = 0 ) -> NSLayoutConstraint { Constraints.add(self, anchor, constant: constant, relation: .lessThanOrEqual) } @@ -236,8 +239,8 @@ extension Anchor where Type: AnchorType.Edge { } /// Adds spacing between the current anchors. - @discardableResult func spacing( - _ spacing: CGFloat, to anchor: Anchor, + @discardableResult func spacing( + _ spacing: CGFloat, to anchor: Anchor, relation: NSLayoutConstraint.Relation = .equal ) -> NSLayoutConstraint { let isInverted = diff --git a/Sources/BrazeUI/InAppMessageUI/InAppMessageUIError.swift b/Sources/BrazeUI/InAppMessageUI/InAppMessageUIError.swift index 0fa4a14ef7..df256a11b8 100644 --- a/Sources/BrazeUI/InAppMessageUI/InAppMessageUIError.swift +++ b/Sources/BrazeUI/InAppMessageUI/InAppMessageUIError.swift @@ -17,6 +17,7 @@ extension BrazeInAppMessageUI { case noFontAwesome case noMatchingOrientation case assetsFailure(Braze.ErrorString) + case invalidConstraints case otherMessagePresented(push: Bool) case messageContextInvalid @@ -75,6 +76,9 @@ extension BrazeInAppMessageUI.Error { - description: \(error.localizedDescription) - error: \(error) """ + case .invalidConstraints: + return + "Unable to present message - constraints were invalid or nil." case .otherMessagePresented(let push): return diff --git a/Sources/BrazeUI/InAppMessageUI/Views/InAppMessageUIContainerView.swift b/Sources/BrazeUI/InAppMessageUI/Views/InAppMessageUIContainerView.swift index 92162a7f1d..d6e11266ce 100644 --- a/Sources/BrazeUI/InAppMessageUI/Views/InAppMessageUIContainerView.swift +++ b/Sources/BrazeUI/InAppMessageUI/Views/InAppMessageUIContainerView.swift @@ -64,7 +64,7 @@ extension BrazeInAppMessageUI { let frameEnd = window.screen.coordinateSpace.convert(keyboard.frame, to: superview) let frame = superview.bounds.intersection(frameEnd) bottomConstraint?.constant = -frame.height - + UIView.animate( withDuration: 0.25, delay: 0, diff --git a/Sources/BrazeUI/InAppMessageUI/Views/InAppMessageUIModalView.swift b/Sources/BrazeUI/InAppMessageUI/Views/InAppMessageUIModalView.swift index aa444ff40b..3037800393 100644 --- a/Sources/BrazeUI/InAppMessageUI/Views/InAppMessageUIModalView.swift +++ b/Sources/BrazeUI/InAppMessageUI/Views/InAppMessageUIModalView.swift @@ -125,12 +125,14 @@ extension BrazeInAppMessageUI { bottom: padding.bottom, right: 0 ) - textContainer.layoutMargins = .init( + + textView.layoutMargins = .init( top: 0, left: padding.left, bottom: 0, right: padding.right ) + buttonsContainer?.stack.layoutMargins = .init( top: 0, left: padding.left, @@ -139,12 +141,14 @@ extension BrazeInAppMessageUI { ) // Spacings - textViewStyle.headerMessageSpacing = attributes.labelsSpacing contentView.stack.spacing = attributes.spacing - // Fonts - textViewStyle.header.font = attributes.headerFont - textViewStyle.message.font = attributes.messageFont + // Text view attributes + textView.attributes = .init( + modal: message, + attributes: attributes, + traitCollection: traitCollection + ) // Corner radius shadowView.layer.cornerRadius = attributes.cornerRadius @@ -198,32 +202,11 @@ extension BrazeInAppMessageUI { } }() - public lazy var textView: UITextView = { - let textView = UITextView() - // Config defaults: - textView.backgroundColor = .clear - textView.isEditable = false - textView.isSelectable = false - textView.adjustsFontForContentSizeCategory = true - - // Don't allow scrolling; the textview's parent (a scrollview) will control that. - // This will force the textView to render its full content height, - // and the textViewContainer can then take that as its content and scroll it for us. - // (This hot tip brought to you by https://archive.is/fMUR7 and many other results.) - textView.isScrollEnabled = false - - // Layout defaults: - textView.setContentCompressionResistancePriority(.required, for: .vertical) - textView.setContentHuggingPriority(.required, for: .vertical) - - return textView - }() - - public lazy var textContainer: UIScrollView = { - let container = UIScrollView() - container.addSubview(textView) - return container - }() + public lazy var textView: ModalTextView = ModalTextView( + modal: message, + attributes: attributes, + traitCollection: traitCollection + ) public lazy var buttonsContainer: StackView? = { let container = StackView( @@ -256,7 +239,7 @@ extension BrazeInAppMessageUI { let view = StackView( arrangedSubviews: [ graphicView, - textContainer, + textView, buttonsContainer, ] .compactMap { $0 } @@ -294,18 +277,6 @@ extension BrazeInAppMessageUI { self.gifViewProvider = gifViewProvider self.presented = presented - self.textViewStyle = TextViewStyle( - header: .init( - color: .clear, - font: attributes.headerFont - ), - message: .init( - color: .clear, - font: attributes.messageFont - ), - headerMessageSpacing: attributes.labelsSpacing - ) - super.init(frame: .zero) addSubview(shadowView) @@ -331,8 +302,11 @@ extension BrazeInAppMessageUI { public var theme: Braze.InAppMessage.Theme { message.theme(for: traitCollection) } open func applyTheme() { - textViewStyle.header.color = theme.headerTextColor.uiColor - textViewStyle.message.color = theme.textColor.uiColor + textView.attributes = .init( + modal: message, + attributes: attributes, + traitCollection: traitCollection + ) closeButton.setTitleColor(theme.closeButtonColor.uiColor, for: .normal) contentView.backgroundColor = theme.backgroundColor.uiColor shadowView.alpha = theme.backgroundColor.a @@ -384,19 +358,6 @@ extension BrazeInAppMessageUI { break } - // Anchor the text view's layout using margins from its parent container: - textView.anchors.leading.equal(textContainer.anchors.leadingMargin) - textView.anchors.trailing.equal(textContainer.anchors.trailingMargin) - textView.anchors.top.equal(textContainer.anchors.topMargin) - textView.anchors.bottom.equal(textContainer.anchors.bottomMargin) - textView.anchors.width.equal(textContainer.layoutMarginsGuide.anchors.width) - // Add a not-required priority to allow the text container scrollview to shrink - textView.anchors.height.lessThanOrEqual(textContainer.layoutMarginsGuide.anchors.height) - .priority = - .defaultHigh - let textViewHeightConstraint = textView.anchors.height.equal(textContainer.anchors.height) - textViewHeightConstraint.priority = .defaultHigh - 1 - // Close button closeButton.anchors.height.equal(closeButton.anchors.width) closeButton.anchors.edges.pin(to: contentView.layoutMarginsGuide, alignment: .topTrailing) @@ -509,66 +470,6 @@ extension BrazeInAppMessageUI { dismiss() } - // MARK: - Text view style - - private var textViewStyle: TextViewStyle { - didSet { - if oldValue != textViewStyle { - self.updateTextViewContent() - } - } - } - - private func updateTextViewContent() { - let textViewText = NSMutableAttributedString() - - textViewText.append( - message.header.attributed( - with: [ - NSAttributedString.Key.font: textViewStyle.header.font, - NSAttributedString.Key.foregroundColor: textViewStyle.header.color, - ], - { - $0.lineSpacing = 2 * TextViewLayoutConstants.headerLineSpacingScaleFactor - $0.alignment = message.headerTextAlignment.nsTextAlignment(forTraits: traitCollection) - $0.paragraphSpacing = max( - 0.0, - textViewStyle.headerMessageSpacing - - TextViewLayoutConstants.headerMessageSpacingOffset) - }) - ) - // Users don't add newlines to the end of their header text - // Insert a newline between header and message, but not with the possible extra styling of the header (e.g. font size, etc.) - // The paragraph spacing set above will occur between the header text and this linebreak. - textViewText.append(NSAttributedString(string: "\n")) - - textViewText.append( - message.message.attributed( - with: [ - NSAttributedString.Key.font: textViewStyle.message.font, - NSAttributedString.Key.foregroundColor: textViewStyle.message.color, - ], - { - $0.lineSpacing = 4 * TextViewLayoutConstants.messageLineSpacingScaleFactor - $0.alignment = message.messageTextAlignment.nsTextAlignment(forTraits: traitCollection) - }) - ) - - textView.attributedText = textViewText - } - - private enum TextViewLayoutConstants { - // Manually-tuned values to get us close to our previous StackView+Label appearance. - - // Soak up some vertical space that UITextView leaves above and below its text: - static let textContainerLayoutMargins = UIEdgeInsets(top: -8, left: 0, bottom: -8, right: 0) - // Scale factors for label → textview line spacing: - static let headerLineSpacingScaleFactor = 0.78 - static let messageLineSpacingScaleFactor = 0.47 - // Subtraction offset between header and message: - // (textview/TextKit renders a tiny bit of extra ascender+descender space that we want to eat up) - static let headerMessageSpacingOffset: Double = 1.0 - } } } @@ -630,7 +531,7 @@ extension BrazeInAppMessageUI { ModalView(message: .mockLong, presented: true) .preview(center: .required) - .frame(maxHeight: 375) + .frame(maxHeight: 700) .previewDisplayName("Var. | Long (constrained)") ModalView(message: .mockTallCharacters, presented: true) diff --git a/Sources/BrazeUI/InAppMessageUI/Views/InAppMessageUISlideupView.swift b/Sources/BrazeUI/InAppMessageUI/Views/InAppMessageUISlideupView.swift index cfec241d0f..4b5553cc2c 100644 --- a/Sources/BrazeUI/InAppMessageUI/Views/InAppMessageUISlideupView.swift +++ b/Sources/BrazeUI/InAppMessageUI/Views/InAppMessageUISlideupView.swift @@ -156,8 +156,12 @@ extension BrazeInAppMessageUI { shadowView.shadow = attributes.shadow // Dimensions - maxWidthConstraints.forEach { $0.constant = attributes.maxWidth } - minHeightConstraint.constant = attributes.minHeight + if let minHeightConstraint = minHeightConstraint, + let maxWidthConstraints = maxWidthConstraints + { + maxWidthConstraints.forEach { $0.constant = attributes.maxWidth } + minHeightConstraint.constant = attributes.minHeight + } // Image if case .image = message.graphic { @@ -395,6 +399,11 @@ extension BrazeInAppMessageUI { superview?.layoutIfNeeded() } + if innerYConstraint == nil || outerYConstraint == nil { + logError(BrazeInAppMessageUI.Error.invalidConstraints) + return + } + presented = true UIView.animate( withDuration: message.animateIn ? 0.3 : 0, diff --git a/Sources/BrazeUI/InAppMessageUI/Views/Misc/ModalTextView.swift b/Sources/BrazeUI/InAppMessageUI/Views/Misc/ModalTextView.swift new file mode 100644 index 0000000000..ce586d3889 --- /dev/null +++ b/Sources/BrazeUI/InAppMessageUI/Views/Misc/ModalTextView.swift @@ -0,0 +1,207 @@ +import BrazeKit +import UIKit + +extension BrazeInAppMessageUI { + + // A view displaying modal and full in-app messages' header and message according the Braze's + // specs. + public class ModalTextView: UIScrollView { + + /// The view's header string. + public var header: String { + didSet { updateTextViewContent() } + } + + /// The view's message string. + public var message: String { + didSet { updateTextViewContent() } + } + + /// The view's attributes for customization. + public var attributes: Attributes { + didSet { updateTextViewContent() } + } + + private let textView: UITextView = { + let textView = UITextView() + + // Override defaults + textView.backgroundColor = .clear + textView.isEditable = false + textView.isSelectable = false + textView.adjustsFontForContentSizeCategory = true + + // Don't allow scrolling; the textview's parent (a scrollview) will control that. + // This will force the textView to render its full content height, + // and the textViewContainer can then take that as its content and scroll it for us. + // (This hot tip brought to you by https://archive.is/fMUR7 and many other results.) + textView.isScrollEnabled = false + + // Layout defaults + textView.setContentCompressionResistancePriority(.required, for: .vertical) + textView.setContentHuggingPriority(.required, for: .vertical) + + return textView + }() + + // MARK: - Initialization + + /// Creates and returns a modal text view. + /// - Parameters: + /// - header: The header string. + /// - message: The message string. + /// - attributes: The attributes for customization. + public init(header: String, message: String, attributes: Attributes) { + self.header = header + self.message = message + self.attributes = attributes + + super.init(frame: .zero) + + // Hierarchy + addSubview(textView) + + // Constraints + textView.anchors.top.equal(anchors.topMargin) + textView.anchors.bottom.equal(anchors.bottomMargin) + textView.anchors.edges.pin(to: layoutMarginsGuide, axis: .horizontal) + textView.anchors.height.lessThanOrEqual(layoutMarginsGuide.anchors.height) + .priority = .defaultHigh + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Attributes + + /// The attributes supported by the modal text view. + public struct Attributes { + /// The style for the header. + public var header = TextStyle() + + /// The style for the message. + public var message = TextStyle() + + /// The spacing between the header and message. + public var headerMessageSpacing = 10.0 + } + + /// The style for displaying text in the modal text view. + public struct TextStyle { + /// The text color. + public var color: UIColor = .brazeLabel + + /// The text font. + public var font: UIFont = UIFont() + + /// The text alignment. + public var alignment: NSTextAlignment = .natural + } + + // MARK: - Misc. + + /// Manually-tuned values to get us closer to our visual spec. + private enum Layout { + static let headerLineSpacingScaleFactor = 0.78 + static let messageLineSpacingScaleFactor = 0.47 + static let headerMessageSpacingOffset = 1.0 + } + + private func updateTextViewContent() { + let text = NSMutableAttributedString() + + // Header + text.append( + header.attributed( + with: [ + .font: attributes.header.font, + .foregroundColor: attributes.header.color, + ], + { + $0.lineSpacing = 2 * Layout.headerLineSpacingScaleFactor + $0.alignment = attributes.header.alignment + $0.paragraphSpacing = max( + 0.0, + attributes.headerMessageSpacing - Layout.headerMessageSpacingOffset + ) + }) + ) + + // Users don't add newlines to the end of their header text + // Insert a newline between header and message, but not with the possible extra styling of the header (e.g. font size, etc.) + // The paragraph spacing set above will occur between the header text and this linebreak. + text.append(NSAttributedString(string: "\n")) + + // Message + text.append( + message.attributed( + with: [ + .font: attributes.message.font, + .foregroundColor: attributes.message.color, + ] + ) { + $0.lineSpacing = 4 * Layout.messageLineSpacingScaleFactor + $0.alignment = attributes.message.alignment + } + ) + + textView.attributedText = text + } + + } + +} + +// MARK: - Extensions + +extension BrazeInAppMessageUI.ModalTextView { + + /// Creates and returns a modal text view suitable for modal and full in-app messages. + /// - Parameters: + /// - modal: The modal in-app message. + /// - attributes: The text view attributes. + /// - traitCollection: The current trait collection. + public convenience init( + modal: Braze.InAppMessage.Modal, + attributes: BrazeInAppMessageUI.ModalView.Attributes, + traitCollection: UITraitCollection + ) { + self.init( + header: modal.header, + message: modal.message, + attributes: .init(modal: modal, attributes: attributes, traitCollection: traitCollection) + ) + } + +} + +extension BrazeInAppMessageUI.ModalTextView.Attributes { + + /// Creates and returns a modal text view attributes struct from a modal in-app message. + /// - Parameters: + /// - modal: The modal in-app message. + /// - attributes: The text view attributes. + /// - traitCollection: The current trait collection. + public init( + modal: Braze.InAppMessage.Modal, + attributes: BrazeInAppMessageUI.ModalView.Attributes, + traitCollection: UITraitCollection + ) { + let theme = modal.theme(for: traitCollection) + self.init( + header: .init( + color: theme.headerTextColor.uiColor, + font: attributes.headerFont, + alignment: modal.headerTextAlignment.nsTextAlignment(forTraits: traitCollection) + ), + message: .init( + color: theme.textColor.uiColor, + font: attributes.messageFont, + alignment: modal.messageTextAlignment.nsTextAlignment(forTraits: traitCollection) + ), + headerMessageSpacing: attributes.labelsSpacing + ) + } + +} diff --git a/Sources/BrazeUICompat/ABKContentCards/AppboyContentCards.h b/Sources/BrazeUICompat/ABKContentCards/AppboyContentCards.h index 843ee360e1..f669434b4b 100644 --- a/Sources/BrazeUICompat/ABKContentCards/AppboyContentCards.h +++ b/Sources/BrazeUICompat/ABKContentCards/AppboyContentCards.h @@ -7,3 +7,5 @@ #import "ABKBaseContentCardCell.h" #import "ABKCaptionedImageContentCardCell.h" #import "ABKClassicContentCardCell.h" +#import "ABKClassicImageContentCardCell.h" +#import "ABKControlTableViewCell.h"