Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Display custom FIAMs with SwiftUI #7496

Merged
merged 50 commits into from
Mar 16, 2021
Merged
Show file tree
Hide file tree
Changes from 48 commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
b4f9eee
Revert to previous nil check on experiment JSON
christibbs Jan 7, 2021
fb58268
Merge branch 'master' of github.com:firebase/firebase-ios-sdk
christibbs Jan 19, 2021
7edab22
Merge branch 'master' of github.com:firebase/firebase-ios-sdk
christibbs Feb 1, 2021
7cbcd4c
Merge branch 'master' of github.com:firebase/firebase-ios-sdk
christibbs Feb 2, 2021
ba96a72
Merge branch 'master' of github.com:firebase/firebase-ios-sdk
christibbs Feb 9, 2021
0baba0b
Add Swift extensions podspec
christibbs Feb 9, 2021
b910ab2
Add viewmodifier for displaying custom in-app messages w/ SwiftUI
christibbs Feb 9, 2021
bb24ec6
Add Multiplatform project to integration test SwiftUI pod
christibbs Feb 9, 2021
b2e184a
We don't support macOS yet, comment that out from the Podfile
christibbs Feb 9, 2021
f088b1e
Fix variable renaming bug
christibbs Feb 9, 2021
2eb423d
View modifier implementation that works, but design is questionable
christibbs Feb 10, 2021
2b13a7c
Super basic integration
christibbs Feb 10, 2021
518a2bf
Split view modifiers based on in-app message subtype
christibbs Feb 11, 2021
b58a326
Fix versioning in podspec
christibbs Feb 11, 2021
c6d6f26
Configure Firebase in test app
christibbs Feb 11, 2021
10d5873
Remove trailing whitespace
christibbs Feb 11, 2021
291a688
Clean up SDK fetching code, sample app
christibbs Feb 11, 2021
8d722a7
Updates to sample code, fully functional modal message now
christibbs Feb 11, 2021
f06d744
Update implementing app example
christibbs Feb 12, 2021
dd358fc
Better code readability for view modifiers
christibbs Feb 16, 2021
ac8f9a2
Add unit tests for DelegateBridge
christibbs Feb 16, 2021
450b7cc
Add test target to pod spec
christibbs Feb 16, 2021
040261e
Add copyright notices
christibbs Feb 16, 2021
08a92d4
Remove whitespace from pod spec
christibbs Feb 16, 2021
dfa743f
Add auto-complete documentation for in-app message extensions
christibbs Feb 16, 2021
3e90c8f
Lowered min target
christibbs Feb 26, 2021
d4d4b0d
Update Package.swift
christibbs Feb 26, 2021
f908781
First pass at CI updates
christibbs Feb 26, 2021
fc4a98c
@available(13) for SwiftUI structs
christibbs Feb 26, 2021
cfd1bd7
Use singleton DelegateBridge
christibbs Feb 28, 2021
a181556
Update inappmessaging.yml
christibbs Mar 1, 2021
08f73ed
Run scripts/style.sh
christibbs Mar 1, 2021
97b5eb4
Update build.sh
christibbs Mar 1, 2021
f78e347
Modify build.sh
christibbs Mar 1, 2021
e9e6c9e
Update CHANGELOG, try something new with GHA configuration
christibbs Mar 2, 2021
7eb4734
Let's try this for GHA
christibbs Mar 2, 2021
75587e4
Fully qualify FIAM message enums
christibbs Mar 2, 2021
34ba6ac
Remove redundant type declaration
christibbs Mar 2, 2021
0eec2bb
Let's try direct instantiation of messages in unit tests
christibbs Mar 2, 2021
c50317a
Test CI build works without actual test cases
christibbs Mar 2, 2021
eee202b
Let's try this, making init available.
christibbs Mar 3, 2021
5f56642
Try exposing initializer
christibbs Mar 3, 2021
b6685d8
Expose simpler initializer for in-app message superclass
christibbs Mar 3, 2021
7f894c9
Fix up UI test app initializers
christibbs Mar 3, 2021
c4095f7
Update CHANGELOG, GHA workflows
christibbs Mar 4, 2021
955b0eb
Cleaner GHA test flow
christibbs Mar 5, 2021
99b4a7f
Clean up custom in-app message view modifier implementation
christibbs Mar 9, 2021
fae7aa2
Configuration cleanup
christibbs Mar 9, 2021
c5fcc43
Add some tests to ensure FIAM SwiftUI API compiles
christibbs Mar 15, 2021
5d8b009
Add SpecsDev to Podfile of integration testing app. Make @State var p…
christibbs Mar 16, 2021
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions .github/workflows/inappmessaging.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,15 @@ jobs:
if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request'

runs-on: macOS-latest
strategy:
matrix:
podspec: [FirebaseInAppMessaging.podspec, FirebaseInAppMessagingSwift.podspec]
steps:
- uses: actions/checkout@v2
- name: Setup Bundler
run: scripts/setup_bundler.sh
- name: FirebaseInAppMessaging
run: scripts/third_party/travis/retry.sh scripts/pod_lib_lint.rb FirebaseInAppMessaging.podspec
run: scripts/third_party/travis/retry.sh scripts/pod_lib_lint.rb ${{ matrix.podspec}}

tests:
# Don't run on private repo unless it is a PR.
Expand All @@ -38,9 +41,9 @@ jobs:
- name: Setup Bundler
run: scripts/setup_bundler.sh
- name: Prereqs
run: scripts/install_prereqs.sh InAppMessaging ${{ matrix.platform }} xcodebuild
run: scripts/install_prereqs.sh InAppMessaging ${{ matrix.platform }} xcodebuild
- name: Build and test
run: scripts/third_party/travis/retry.sh scripts/build.sh InAppMessaging ${{ matrix.platform }} xcodebuild
run: scripts/third_party/travis/retry.sh scripts/build.sh InAppMessaging ${{ matrix.platform }} xcodebuild

spm:
# Don't run on private repo unless it is a PR.
Expand Down
3 changes: 3 additions & 0 deletions FirebaseInAppMessaging/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# 2021-3 -- v.7.9.0
- [added] Added support for building custom in-app messages with SwiftUI (#7496).

# 2021-2 -- v.7.7.0
- [fixed] Fixed accessibility experience for in-app messages (#7445).
- [fixed] Fixed conversion tracking for in-app messages with a conversion event but not a button / action URL (#7306).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,13 @@ NS_SWIFT_NAME(InAppMessagingDisplayMessage)
/// Unavailable.
- (instancetype)init NS_UNAVAILABLE;

/// Exposed for unit testing only. Don't instantiate this in your app directly.
- (instancetype)initWithMessageID:(NSString *)messageID
christibbs marked this conversation as resolved.
Show resolved Hide resolved
campaignName:(NSString *)campaignName
renderAsTestMessage:(BOOL)renderAsTestMessage
messageType:(FIRInAppMessagingDisplayMessageType)messageType
triggerType:(FIRInAppMessagingDisplayTriggerType)triggerType;

@end

NS_SWIFT_NAME(InAppMessagingCardDisplay)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import FirebaseInAppMessaging
import SwiftUI

// MARK: Image-only messages.

@available(iOS 13, tvOS 13, *)
struct ImageOnlyInAppMessageDisplayViewModifier<DisplayMessage: View>: ViewModifier {
var closure: (InAppMessagingImageOnlyDisplay, InAppMessagingDisplayDelegate) -> DisplayMessage
@ObservedObject var delegateBridge = DelegateBridge.shared

func body(content: Content) -> some View {
return content.overlay(overlayView())
}

@ViewBuilder
func overlayView() -> some View {
if let (message, delegate) = delegateBridge.inAppMessageData,
let imageOnlyMessage = message as? InAppMessagingImageOnlyDisplay {
closure(imageOnlyMessage, delegate)
.onAppear { delegate.impressionDetected?(for: imageOnlyMessage) }
} else {
EmptyView()
}
}
}

@available(iOS 13, tvOS 13, *)
public extension View {
/// Overrides the default display of an image only in-app message as defined on the Firebase console.
func imageOnlyInAppMessage<Content: View>(closure: @escaping (InAppMessagingImageOnlyDisplay,
InAppMessagingDisplayDelegate)
-> Content)
-> some View {
modifier(ImageOnlyInAppMessageDisplayViewModifier(closure: closure))
}
}

// MARK: Banner messages.

@available(iOS 13, tvOS 13, *)
struct BannerInAppMessageDisplayViewModifier<DisplayMessage: View>: ViewModifier {
var closure: (InAppMessagingBannerDisplay, InAppMessagingDisplayDelegate) -> DisplayMessage
@ObservedObject var delegateBridge = DelegateBridge.shared

func body(content: Content) -> some View {
return content.overlay(overlayView())
}

@ViewBuilder
func overlayView() -> some View {
if let (message, delegate) = delegateBridge.inAppMessageData,
let bannerMessage = message as? InAppMessagingBannerDisplay {
closure(bannerMessage, delegate).onAppear { delegate.impressionDetected?(for: bannerMessage) }
} else {
EmptyView()
}
}
}

@available(iOS 13, tvOS 13, *)
public extension View {
/// Overrides the default display of a banner in-app message as defined on the Firebase console.
func bannerInAppMessage<Content: View>(closure: @escaping (InAppMessagingBannerDisplay,
InAppMessagingDisplayDelegate)
-> Content)
-> some View {
modifier(BannerInAppMessageDisplayViewModifier(closure: closure))
christibbs marked this conversation as resolved.
Show resolved Hide resolved
}
}

// MARK: Modal messages.

@available(iOS 13, tvOS 13, *)
struct ModalInAppMessageDisplayViewModifier<DisplayMessage: View>: ViewModifier {
var closure: (InAppMessagingModalDisplay, InAppMessagingDisplayDelegate) -> DisplayMessage
@ObservedObject var delegateBridge = DelegateBridge.shared

func body(content: Content) -> some View {
return content.overlay(overlayView())
}

@ViewBuilder
func overlayView() -> some View {
if let (message, delegate) = delegateBridge.inAppMessageData,
let modalMessage = message as? InAppMessagingModalDisplay {
closure(modalMessage, delegate).onAppear { delegate.impressionDetected?(for: modalMessage) }
} else {
EmptyView()
}
}
}

@available(iOS 13, tvOS 13, *)
public extension View {
/// Overrides the default display of a modal in-app message as defined on the Firebase console.
func modalInAppMessage<Content: View>(closure: @escaping (InAppMessagingModalDisplay,
InAppMessagingDisplayDelegate)
-> Content)
-> some View {
modifier(ModalInAppMessageDisplayViewModifier(closure: closure))
}
}

// MARK: Card messages.

@available(iOS 13, tvOS 13, *)
struct CardInAppMessageDisplayViewModifier<DisplayMessage: View>: ViewModifier {
var closure: (InAppMessagingCardDisplay, InAppMessagingDisplayDelegate) -> DisplayMessage
@ObservedObject var delegateBridge = DelegateBridge.shared

func body(content: Content) -> some View {
return content.overlay(overlayView())
}

@ViewBuilder
func overlayView() -> some View {
if let (message, delegate) = delegateBridge.inAppMessageData,
let cardMessage = message as? InAppMessagingCardDisplay {
closure(cardMessage, delegate).onAppear { delegate.impressionDetected?(for: cardMessage) }
} else {
EmptyView()
}
}
}

@available(iOS 13, tvOS 13, *)
public extension View {
/// Overrides the default display of a card in-app message as defined on the Firebase console.
func cardInAppMessage<Content: View>(closure: @escaping (InAppMessagingCardDisplay,
InAppMessagingDisplayDelegate)
-> Content)
-> some View {
modifier(CardInAppMessageDisplayViewModifier(closure: closure))
christibbs marked this conversation as resolved.
Show resolved Hide resolved
}
}

// MARK: Bridge to Firebase In-App Messaging SDK.

/**
* A singleton that acts as the bridge between view modifiers for displaying custom in-app messages and the
* in-app message fetch/display/interaction flow.
*/
@available(iOS 13, tvOS 13, *)
class DelegateBridge: NSObject, InAppMessagingDisplay, InAppMessagingDisplayDelegate,
christibbs marked this conversation as resolved.
Show resolved Hide resolved
ObservableObject {
@Published var inAppMessageData: (InAppMessagingDisplayMessage,
InAppMessagingDisplayDelegate)? = nil

static let shared = DelegateBridge()

override init() {
super.init()
InAppMessaging.inAppMessaging().messageDisplayComponent = self
InAppMessaging.inAppMessaging().delegate = self
}

func displayMessage(_ messageForDisplay: InAppMessagingDisplayMessage,
displayDelegate: InAppMessagingDisplayDelegate) {
DispatchQueue.main.async {
self.inAppMessageData = (messageForDisplay, displayDelegate)
}
}

func messageClicked(_ inAppMessage: InAppMessagingDisplayMessage,
with action: InAppMessagingAction) {
DispatchQueue.main.async {
self.inAppMessageData = nil
}
}

func messageDismissed(_ inAppMessage: InAppMessagingDisplayMessage,
dismissType: FIRInAppMessagingDismissType) {
DispatchQueue.main.async {
self.inAppMessageData = nil
}
}
}
Loading