mirror of
https://github.com/firebase/firebase-ios-sdk
synced 2026-11-10 22:45:59 +05:45
782 lines
35 KiB
Objective-C
782 lines
35 KiB
Objective-C
/*
|
|
* Copyright 2017 Google
|
|
*
|
|
* 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 <TargetConditionals.h>
|
|
#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_VISION
|
|
|
|
#import <UIKit/UIKit.h>
|
|
#import "FirebaseCore/Extension/FirebaseCoreInternal.h"
|
|
|
|
#import "FirebaseInAppMessaging/Sources/FIRCore+InAppMessaging.h"
|
|
#import "FirebaseInAppMessaging/Sources/Private/Data/FIRIAMMessageContentData.h"
|
|
#import "FirebaseInAppMessaging/Sources/Private/Data/FIRIAMMessageDefinition.h"
|
|
#import "FirebaseInAppMessaging/Sources/Private/Flows/FIRIAMActivityLogger.h"
|
|
#import "FirebaseInAppMessaging/Sources/Private/Flows/FIRIAMDisplayExecutor.h"
|
|
#import "FirebaseInAppMessaging/Sources/Private/Util/UIApplication+FIRForegroundWindowScene.h"
|
|
#import "FirebaseInAppMessaging/Sources/Public/FirebaseInAppMessaging/FIRInAppMessaging.h"
|
|
#import "FirebaseInAppMessaging/Sources/RenderingObjects/FIRInAppMessagingRenderingPrivate.h"
|
|
#import "FirebaseInAppMessaging/Sources/Runtime/FIRIAMSDKRuntimeErrorCodes.h"
|
|
|
|
#import "FirebaseABTesting/Sources/Private/FirebaseABTestingInternal.h"
|
|
|
|
@implementation FIRIAMDisplaySetting
|
|
@end
|
|
|
|
@interface FIRIAMDisplayExecutor () <FIRInAppMessagingDisplayDelegate>
|
|
@property(nonatomic) id<FIRIAMTimeFetcher> timeFetcher;
|
|
|
|
// YES if a message is being rendered at this time
|
|
@property(nonatomic) BOOL isMsgBeingDisplayed;
|
|
@property(nonatomic) NSTimeInterval lastDisplayTime;
|
|
@property(nonatomic, nonnull, readonly) FIRInAppMessaging *inAppMessaging;
|
|
@property(nonatomic, nonnull, readonly) FIRIAMDisplaySetting *setting;
|
|
@property(nonatomic, nonnull, readonly) FIRIAMMessageClientCache *messageCache;
|
|
@property(nonatomic, nonnull, readonly) id<FIRIAMBookKeeper> displayBookKeeper;
|
|
@property(nonatomic) BOOL impressionRecorded;
|
|
@property(nonatomic, nonnull, readonly) id<FIRIAMAnalyticsEventLogger> analyticsEventLogger;
|
|
@property(nonatomic, nonnull, readonly) FIRIAMActionURLFollower *actionURLFollower;
|
|
// Used for displaying the test on device message error alert.
|
|
@property(nonatomic, strong) UIWindow *alertWindow;
|
|
@end
|
|
|
|
@implementation FIRIAMDisplayExecutor {
|
|
FIRIAMMessageDefinition *_currentMsgBeingDisplayed;
|
|
}
|
|
|
|
+ (NSString *)logStringForNilMessageDisplayComponent {
|
|
#if TARGET_OS_IOS
|
|
return @"Message display component is not present yet. No display should happen.";
|
|
#else // TARGET_OS_TV
|
|
return @"There is no default UI for tvOS. You must implement a messageDisplayComponent and set "
|
|
@"it on the InAppMessaging singleton. See "
|
|
@"https://firebase.google.com/docs/in-app-messaging/"
|
|
@"customize-messages#create_your_own_message_display_library.";
|
|
#endif
|
|
}
|
|
|
|
#pragma mark - FIRInAppMessagingDisplayDelegate methods
|
|
- (void)messageClicked:(FIRInAppMessagingDisplayMessage *)inAppMessage
|
|
withAction:(FIRInAppMessagingAction *)action {
|
|
// Call through to app-side delegate.
|
|
__weak id<FIRInAppMessagingDisplayDelegate> appSideDelegate = self.inAppMessaging.delegate;
|
|
if ([appSideDelegate respondsToSelector:@selector(messageClicked:withAction:)]) {
|
|
[appSideDelegate messageClicked:inAppMessage withAction:action];
|
|
}
|
|
|
|
self.isMsgBeingDisplayed = NO;
|
|
if (!_currentMsgBeingDisplayed.renderData.messageID) {
|
|
FIRLogWarning(kFIRLoggerInAppMessaging, @"I-IAM400030",
|
|
@"messageClicked called but "
|
|
"there is no current message ID.");
|
|
return;
|
|
}
|
|
|
|
if (_currentMsgBeingDisplayed.isTestMessage) {
|
|
FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400031",
|
|
@"A test message clicked. Do test event impression/click analytics logging");
|
|
|
|
[self.analyticsEventLogger
|
|
logAnalyticsEventForType:FIRIAMAnalyticsEventTestMessageImpression
|
|
forCampaignID:_currentMsgBeingDisplayed.renderData.messageID
|
|
withCampaignName:_currentMsgBeingDisplayed.renderData.name
|
|
eventTimeInMs:nil
|
|
completion:^(BOOL success) {
|
|
FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400036",
|
|
@"Logging analytics event for url following %@",
|
|
success ? @"succeeded" : @"failed");
|
|
}];
|
|
|
|
[self.analyticsEventLogger
|
|
logAnalyticsEventForType:FIRIAMAnalyticsEventTestMessageClick
|
|
forCampaignID:_currentMsgBeingDisplayed.renderData.messageID
|
|
withCampaignName:_currentMsgBeingDisplayed.renderData.name
|
|
eventTimeInMs:nil
|
|
completion:^(BOOL success) {
|
|
FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400039",
|
|
@"Logging analytics event for url following %@",
|
|
success ? @"succeeded" : @"failed");
|
|
}];
|
|
} else {
|
|
// Logging the impression
|
|
[self recordValidImpression:_currentMsgBeingDisplayed.renderData.messageID
|
|
withMessageName:_currentMsgBeingDisplayed.renderData.name];
|
|
|
|
if (action.actionURL) {
|
|
[self.analyticsEventLogger
|
|
logAnalyticsEventForType:FIRIAMAnalyticsEventActionURLFollow
|
|
forCampaignID:_currentMsgBeingDisplayed.renderData.messageID
|
|
withCampaignName:_currentMsgBeingDisplayed.renderData.name
|
|
eventTimeInMs:nil
|
|
completion:^(BOOL success) {
|
|
FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400032",
|
|
@"Logging analytics event for url following %@",
|
|
success ? @"succeeded" : @"failed");
|
|
}];
|
|
|
|
// Also start tracking conversions.
|
|
[self.analyticsEventLogger
|
|
logConversionTrackingEventForCampaignID:_currentMsgBeingDisplayed.renderData.messageID];
|
|
}
|
|
}
|
|
|
|
NSURL *actionURL = action.actionURL;
|
|
|
|
if (!actionURL) {
|
|
FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400033",
|
|
@"messageClicked called but "
|
|
"there is no action url specified in the message data.");
|
|
// it's equivalent to closing the message with no further action
|
|
return;
|
|
} else {
|
|
FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400037", @"Following action url %@",
|
|
actionURL.absoluteString);
|
|
@try {
|
|
[self.actionURLFollower
|
|
followActionURL:actionURL
|
|
withCompletionBlock:^(BOOL success) {
|
|
FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400034",
|
|
@"Seeing %@ from following action URL", success ? @"success" : @"error");
|
|
}];
|
|
} @catch (NSException *e) {
|
|
FIRLogWarning(kFIRLoggerInAppMessaging, @"I-IAM400035",
|
|
@"Exception encountered in following "
|
|
"action url (%@): %@ ",
|
|
actionURL, e.description);
|
|
@throw;
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)messageDismissed:(FIRInAppMessagingDisplayMessage *)inAppMessage
|
|
dismissType:(FIRInAppMessagingDismissType)dismissType {
|
|
// Call through to app-side delegate.
|
|
__weak id<FIRInAppMessagingDisplayDelegate> appSideDelegate = self.inAppMessaging.delegate;
|
|
if ([appSideDelegate respondsToSelector:@selector(messageDismissed:dismissType:)]) {
|
|
[appSideDelegate messageDismissed:inAppMessage dismissType:dismissType];
|
|
}
|
|
|
|
self.isMsgBeingDisplayed = NO;
|
|
if (!_currentMsgBeingDisplayed.renderData.messageID) {
|
|
FIRLogWarning(kFIRLoggerInAppMessaging, @"I-IAM400014",
|
|
@"messageDismissedWithType called but "
|
|
"there is no current message ID.");
|
|
return;
|
|
}
|
|
|
|
if (_currentMsgBeingDisplayed.isTestMessage) {
|
|
FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400020",
|
|
@"A test message dismissed. Record the impression event.");
|
|
[self.analyticsEventLogger
|
|
logAnalyticsEventForType:FIRIAMAnalyticsEventTestMessageImpression
|
|
forCampaignID:_currentMsgBeingDisplayed.renderData.messageID
|
|
withCampaignName:_currentMsgBeingDisplayed.renderData.name
|
|
eventTimeInMs:nil
|
|
completion:^(BOOL success) {
|
|
FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400038",
|
|
@"Logging analytics event for url following %@",
|
|
success ? @"succeeded" : @"failed");
|
|
}];
|
|
|
|
return;
|
|
}
|
|
|
|
// Logging the impression
|
|
[self recordValidImpression:_currentMsgBeingDisplayed.renderData.messageID
|
|
withMessageName:_currentMsgBeingDisplayed.renderData.name];
|
|
|
|
FIRIAMAnalyticsLogEventType logEventType = dismissType == FIRInAppMessagingDismissTypeAuto
|
|
? FIRIAMAnalyticsEventMessageDismissAuto
|
|
: FIRIAMAnalyticsEventMessageDismissClick;
|
|
|
|
[self.analyticsEventLogger
|
|
logAnalyticsEventForType:logEventType
|
|
forCampaignID:_currentMsgBeingDisplayed.renderData.messageID
|
|
withCampaignName:_currentMsgBeingDisplayed.renderData.name
|
|
eventTimeInMs:nil
|
|
completion:^(BOOL success) {
|
|
FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400004",
|
|
@"Logging analytics event for message dismiss %@",
|
|
success ? @"succeeded" : @"failed");
|
|
}];
|
|
}
|
|
|
|
- (void)impressionDetectedForMessage:(FIRInAppMessagingDisplayMessage *)inAppMessage {
|
|
__weak id<FIRInAppMessagingDisplayDelegate> appSideDelegate = self.inAppMessaging.delegate;
|
|
if ([appSideDelegate respondsToSelector:@selector(impressionDetectedForMessage:)]) {
|
|
[appSideDelegate impressionDetectedForMessage:inAppMessage];
|
|
}
|
|
|
|
if (!_currentMsgBeingDisplayed.renderData.messageID) {
|
|
FIRLogWarning(kFIRLoggerInAppMessaging, @"I-IAM400022",
|
|
@"impressionDetected called but "
|
|
"there is no current message ID.");
|
|
return;
|
|
}
|
|
|
|
// If this is an experimental FIAM, activate the experiment.
|
|
if (inAppMessage.campaignInfo.experimentPayload) {
|
|
[[FIRExperimentController sharedInstance]
|
|
activateExperiment:inAppMessage.campaignInfo.experimentPayload
|
|
forServiceOrigin:@"fiam"];
|
|
}
|
|
|
|
if (!_currentMsgBeingDisplayed.isTestMessage) {
|
|
// Displayed long enough to be a valid impression.
|
|
[self recordValidImpression:_currentMsgBeingDisplayed.renderData.messageID
|
|
withMessageName:_currentMsgBeingDisplayed.renderData.name];
|
|
|
|
if ([self shouldTrackConversionsOnImpressionForCurrentInAppMessage:_currentMsgBeingDisplayed]) {
|
|
[self.analyticsEventLogger
|
|
logConversionTrackingEventForCampaignID:_currentMsgBeingDisplayed.renderData.messageID];
|
|
}
|
|
} else {
|
|
FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400011",
|
|
@"A test message. Record the test message impression event.");
|
|
return;
|
|
}
|
|
}
|
|
|
|
- (BOOL)shouldTrackConversionsOnImpressionForCurrentInAppMessage:
|
|
(FIRIAMMessageDefinition *)inAppMessage {
|
|
// If the message has no action URL, an impression is enough to start tracking conversions.
|
|
id<FIRIAMMessageContentData> contentData = inAppMessage.renderData.contentData;
|
|
return contentData.actionURL == nil && contentData.secondaryActionURL == nil;
|
|
}
|
|
|
|
- (void)displayErrorForMessage:(FIRInAppMessagingDisplayMessage *)inAppMessage
|
|
error:(NSError *)error {
|
|
__weak id<FIRInAppMessagingDisplayDelegate> appSideDelegate = self.inAppMessaging.delegate;
|
|
if ([appSideDelegate respondsToSelector:@selector(displayErrorForMessage:error:)]) {
|
|
[appSideDelegate displayErrorForMessage:inAppMessage error:error];
|
|
}
|
|
|
|
self.isMsgBeingDisplayed = NO;
|
|
|
|
if (!_currentMsgBeingDisplayed.renderData.messageID) {
|
|
FIRLogWarning(kFIRLoggerInAppMessaging, @"I-IAM400017",
|
|
@"displayErrorEncountered called but "
|
|
"there is no current message ID.");
|
|
return;
|
|
}
|
|
|
|
NSString *messageID = _currentMsgBeingDisplayed.renderData.messageID;
|
|
|
|
FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400009",
|
|
@"Display ran into error for message %@: %@", messageID, error);
|
|
|
|
if (_currentMsgBeingDisplayed.isTestMessage) {
|
|
[self displayMessageLoadError:error];
|
|
FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400012",
|
|
@"A test message. No analytics tracking "
|
|
"from image data loading failure");
|
|
return;
|
|
}
|
|
|
|
// we remove the message from the client side cache so that it won't be retried until next time
|
|
// it's fetched again from server.
|
|
[self.messageCache removeMessageWithId:messageID];
|
|
NSString *messageName = _currentMsgBeingDisplayed.renderData.name;
|
|
|
|
if ([error.domain isEqualToString:NSURLErrorDomain]) {
|
|
[self.analyticsEventLogger
|
|
logAnalyticsEventForType:FIRIAMAnalyticsEventImageFetchError
|
|
forCampaignID:messageID
|
|
withCampaignName:messageName
|
|
eventTimeInMs:nil
|
|
completion:^(BOOL success) {
|
|
FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400010",
|
|
@"Logging analytics event for image fetch error %@",
|
|
success ? @"succeeded" : @"failed");
|
|
}];
|
|
} else if (error.code == FIRIAMSDKRuntimeErrorNonImageMimetypeFromImageURL) {
|
|
[self.analyticsEventLogger
|
|
logAnalyticsEventForType:FIRIAMAnalyticsEventImageFormatUnsupported
|
|
forCampaignID:messageID
|
|
withCampaignName:messageName
|
|
eventTimeInMs:nil
|
|
completion:^(BOOL success) {
|
|
FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400013",
|
|
@"Logging analytics event for image format error %@",
|
|
success ? @"succeeded" : @"failed");
|
|
}];
|
|
}
|
|
}
|
|
|
|
- (void)recordValidImpression:(NSString *)messageID withMessageName:(NSString *)messageName {
|
|
if (!self.impressionRecorded) {
|
|
[self.displayBookKeeper
|
|
recordNewImpressionForMessage:messageID
|
|
withStartTimestampInSeconds:[self.timeFetcher currentTimestampInSeconds]];
|
|
self.impressionRecorded = YES;
|
|
[self.messageCache removeMessageWithId:messageID];
|
|
// Log an impression analytics event as well.
|
|
[self.analyticsEventLogger
|
|
logAnalyticsEventForType:FIRIAMAnalyticsEventMessageImpression
|
|
forCampaignID:messageID
|
|
withCampaignName:messageName
|
|
eventTimeInMs:nil
|
|
completion:^(BOOL success) {
|
|
FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400007",
|
|
@"Logging analytics event for impression %@",
|
|
success ? @"succeeded" : @"failed");
|
|
}];
|
|
}
|
|
}
|
|
|
|
- (void)displayMessageLoadError:(NSError *)error {
|
|
NSString *errorMsg = error.userInfo[NSLocalizedDescriptionKey]
|
|
? error.userInfo[NSLocalizedDescriptionKey]
|
|
: NSLocalizedString(@"Message loading failed", nil);
|
|
UIAlertController *alert = [UIAlertController
|
|
alertControllerWithTitle:@"Firebase InAppMessaging fail to load a test message"
|
|
message:errorMsg
|
|
preferredStyle:UIAlertControllerStyleAlert];
|
|
|
|
UIAlertAction *defaultAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"OK", nil)
|
|
style:UIAlertActionStyleDefault
|
|
handler:^(UIAlertAction *action) {
|
|
self.alertWindow.hidden = NO;
|
|
self.alertWindow = nil;
|
|
}];
|
|
|
|
[alert addAction:defaultAction];
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
if (@available(iOS 13.0, tvOS 13.0, *)) {
|
|
UIWindowScene *foregroundedScene =
|
|
[[UIApplication sharedApplication] fir_foregroundWindowScene];
|
|
|
|
if (foregroundedScene == nil) {
|
|
return;
|
|
}
|
|
self.alertWindow = [[UIWindow alloc] initWithWindowScene:foregroundedScene];
|
|
#if TARGET_OS_IOS || TARGET_OS_TV
|
|
} else {
|
|
self.alertWindow = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
|
|
#endif
|
|
}
|
|
UIViewController *alertViewController = [[UIViewController alloc] init];
|
|
self.alertWindow.rootViewController = alertViewController;
|
|
self.alertWindow.hidden = NO;
|
|
[alertViewController presentViewController:alert animated:YES completion:nil];
|
|
});
|
|
}
|
|
|
|
- (instancetype)initWithInAppMessaging:(FIRInAppMessaging *)inAppMessaging
|
|
setting:(FIRIAMDisplaySetting *)setting
|
|
messageCache:(FIRIAMMessageClientCache *)cache
|
|
timeFetcher:(id<FIRIAMTimeFetcher>)timeFetcher
|
|
bookKeeper:(id<FIRIAMBookKeeper>)displayBookKeeper
|
|
actionURLFollower:(FIRIAMActionURLFollower *)actionURLFollower
|
|
activityLogger:(FIRIAMActivityLogger *)activityLogger
|
|
analyticsEventLogger:(id<FIRIAMAnalyticsEventLogger>)analyticsEventLogger {
|
|
if (self = [super init]) {
|
|
_inAppMessaging = inAppMessaging;
|
|
_timeFetcher = timeFetcher;
|
|
_lastDisplayTime = displayBookKeeper.lastDisplayTime;
|
|
_setting = setting;
|
|
_messageCache = cache;
|
|
_displayBookKeeper = displayBookKeeper;
|
|
_isMsgBeingDisplayed = NO;
|
|
_analyticsEventLogger = analyticsEventLogger;
|
|
_actionURLFollower = actionURLFollower;
|
|
_suppressMessageDisplay = NO; // always allow message display on startup
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)checkAndDisplayNextContextualMessageForAnalyticsEvent:(NSString *)eventName {
|
|
// synchronizing on self so that we won't potentially enter the render flow from two
|
|
// threads: example like showing analytics triggered message and a regular app open
|
|
// triggered message
|
|
@synchronized(self) {
|
|
if (self.suppressMessageDisplay) {
|
|
FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400015",
|
|
@"Message display is being suppressed. No contextual message rendering.");
|
|
return;
|
|
}
|
|
|
|
if (!self.messageDisplayComponent) {
|
|
FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400026", @"%@",
|
|
[[self class] logStringForNilMessageDisplayComponent]);
|
|
return;
|
|
}
|
|
|
|
if (self.isMsgBeingDisplayed) {
|
|
FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400008",
|
|
@"An in-app message display is in progress, do not check analytics event "
|
|
"based message for now.");
|
|
|
|
return;
|
|
}
|
|
|
|
// Pop up next analytics event based message to be displayed
|
|
FIRIAMMessageDefinition *nextAnalyticsBasedMessage =
|
|
[self.messageCache nextOnFirebaseAnalyticEventDisplayMsg:eventName];
|
|
|
|
if (nextAnalyticsBasedMessage) {
|
|
[self displayForMessage:nextAnalyticsBasedMessage
|
|
triggerType:FIRInAppMessagingDisplayTriggerTypeOnAnalyticsEvent];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (FIRInAppMessagingCardDisplay *)
|
|
cardDisplayMessageWithMessageDefinition:(FIRIAMMessageDefinition *)definition
|
|
portraitImageData:(FIRInAppMessagingImageData *)portraitImageData
|
|
landscapeImageData:
|
|
(nullable FIRInAppMessagingImageData *)landscapeImageData
|
|
triggerType:(FIRInAppMessagingDisplayTriggerType)triggerType {
|
|
// For easier reference in this method.
|
|
FIRIAMMessageRenderData *renderData = definition.renderData;
|
|
|
|
NSString *title = renderData.contentData.titleText;
|
|
NSString *body = renderData.contentData.bodyText;
|
|
|
|
// Action button data is never nil for a card message.
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
|
FIRInAppMessagingActionButton *primaryActionButton = [[FIRInAppMessagingActionButton alloc]
|
|
initWithButtonText:renderData.contentData.actionButtonText
|
|
buttonTextColor:renderData.renderingEffectSettings.btnTextColor
|
|
backgroundColor:renderData.renderingEffectSettings.btnBGColor];
|
|
|
|
#pragma clang diagnostic pop
|
|
|
|
FIRInAppMessagingActionButton *secondaryActionButton = nil;
|
|
if (definition.renderData.contentData.secondaryActionButtonText) {
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
|
secondaryActionButton = [[FIRInAppMessagingActionButton alloc]
|
|
initWithButtonText:renderData.contentData.secondaryActionButtonText
|
|
buttonTextColor:renderData.renderingEffectSettings.secondaryActionBtnTextColor
|
|
backgroundColor:renderData.renderingEffectSettings.btnBGColor];
|
|
#pragma clang diagnostic pop
|
|
}
|
|
|
|
FIRInAppMessagingCardDisplay *cardMessage = [[FIRInAppMessagingCardDisplay alloc]
|
|
initWithMessageID:renderData.messageID
|
|
campaignName:renderData.name
|
|
experimentPayload:definition.experimentPayload
|
|
renderAsTestMessage:definition.isTestMessage
|
|
triggerType:triggerType
|
|
titleText:title
|
|
bodyText:body
|
|
textColor:renderData.renderingEffectSettings.textColor
|
|
portraitImageData:portraitImageData
|
|
landscapeImageData:landscapeImageData
|
|
backgroundColor:renderData.renderingEffectSettings.displayBGColor
|
|
primaryActionButton:primaryActionButton
|
|
secondaryActionButton:secondaryActionButton
|
|
primaryActionURL:definition.renderData.contentData.actionURL
|
|
secondaryActionURL:definition.renderData.contentData.secondaryActionURL
|
|
appData:definition.appData];
|
|
return cardMessage;
|
|
}
|
|
|
|
- (FIRInAppMessagingBannerDisplay *)
|
|
bannerDisplayMessageWithMessageDefinition:(FIRIAMMessageDefinition *)definition
|
|
imageData:(FIRInAppMessagingImageData *)imageData
|
|
triggerType:(FIRInAppMessagingDisplayTriggerType)triggerType {
|
|
NSString *title = definition.renderData.contentData.titleText;
|
|
NSString *body = definition.renderData.contentData.bodyText;
|
|
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
|
FIRInAppMessagingBannerDisplay *bannerMessage = [[FIRInAppMessagingBannerDisplay alloc]
|
|
initWithMessageID:definition.renderData.messageID
|
|
campaignName:definition.renderData.name
|
|
experimentPayload:definition.experimentPayload
|
|
renderAsTestMessage:definition.isTestMessage
|
|
triggerType:triggerType
|
|
titleText:title
|
|
bodyText:body
|
|
textColor:definition.renderData.renderingEffectSettings.textColor
|
|
backgroundColor:definition.renderData.renderingEffectSettings.displayBGColor
|
|
imageData:imageData
|
|
actionURL:definition.renderData.contentData.actionURL
|
|
appData:definition.appData];
|
|
#pragma clang diagnostic pop
|
|
|
|
return bannerMessage;
|
|
}
|
|
|
|
- (FIRInAppMessagingImageOnlyDisplay *)
|
|
imageOnlyDisplayMessageWithMessageDefinition:(FIRIAMMessageDefinition *)definition
|
|
imageData:(FIRInAppMessagingImageData *)imageData
|
|
triggerType:(FIRInAppMessagingDisplayTriggerType)triggerType {
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
|
FIRInAppMessagingImageOnlyDisplay *imageOnlyMessage = [[FIRInAppMessagingImageOnlyDisplay alloc]
|
|
initWithMessageID:definition.renderData.messageID
|
|
campaignName:definition.renderData.name
|
|
experimentPayload:definition.experimentPayload
|
|
renderAsTestMessage:definition.isTestMessage
|
|
triggerType:triggerType
|
|
imageData:imageData
|
|
actionURL:definition.renderData.contentData.actionURL
|
|
appData:definition.appData];
|
|
#pragma clang diagnostic pop
|
|
|
|
return imageOnlyMessage;
|
|
}
|
|
|
|
- (FIRInAppMessagingModalDisplay *)
|
|
modalDisplayMessageWithMessageDefinition:(FIRIAMMessageDefinition *)definition
|
|
imageData:(FIRInAppMessagingImageData *)imageData
|
|
triggerType:(FIRInAppMessagingDisplayTriggerType)triggerType {
|
|
// For easier reference in this method.
|
|
FIRIAMMessageRenderData *renderData = definition.renderData;
|
|
|
|
NSString *title = renderData.contentData.titleText;
|
|
NSString *body = renderData.contentData.bodyText;
|
|
|
|
FIRInAppMessagingActionButton *actionButton = nil;
|
|
|
|
if (definition.renderData.contentData.actionButtonText) {
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
|
actionButton = [[FIRInAppMessagingActionButton alloc]
|
|
initWithButtonText:renderData.contentData.actionButtonText
|
|
buttonTextColor:renderData.renderingEffectSettings.btnTextColor
|
|
backgroundColor:renderData.renderingEffectSettings.btnBGColor];
|
|
#pragma clang diagnostic pop
|
|
}
|
|
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
|
FIRInAppMessagingModalDisplay *modalViewMessage = [[FIRInAppMessagingModalDisplay alloc]
|
|
initWithMessageID:definition.renderData.messageID
|
|
campaignName:definition.renderData.name
|
|
experimentPayload:definition.experimentPayload
|
|
renderAsTestMessage:definition.isTestMessage
|
|
triggerType:triggerType
|
|
titleText:title
|
|
bodyText:body
|
|
textColor:renderData.renderingEffectSettings.textColor
|
|
backgroundColor:renderData.renderingEffectSettings.displayBGColor
|
|
imageData:imageData
|
|
actionButton:actionButton
|
|
actionURL:definition.renderData.contentData.actionURL
|
|
appData:definition.appData];
|
|
#pragma clang diagnostic pop
|
|
|
|
return modalViewMessage;
|
|
}
|
|
|
|
- (FIRInAppMessagingDisplayMessage *)
|
|
displayMessageWithMessageDefinition:(FIRIAMMessageDefinition *)definition
|
|
imageData:(FIRInAppMessagingImageData *)imageData
|
|
landscapeImageData:(nullable FIRInAppMessagingImageData *)landscapeImageData
|
|
triggerType:(FIRInAppMessagingDisplayTriggerType)triggerType {
|
|
switch (definition.renderData.renderingEffectSettings.viewMode) {
|
|
case FIRIAMRenderAsCardView:
|
|
if (imageData == nil) {
|
|
// Image data should never nil for a valid card message.
|
|
return nil;
|
|
}
|
|
return [self cardDisplayMessageWithMessageDefinition:definition
|
|
portraitImageData:imageData
|
|
landscapeImageData:landscapeImageData
|
|
triggerType:triggerType];
|
|
case FIRIAMRenderAsBannerView:
|
|
return [self bannerDisplayMessageWithMessageDefinition:definition
|
|
imageData:imageData
|
|
triggerType:triggerType];
|
|
case FIRIAMRenderAsModalView:
|
|
return [self modalDisplayMessageWithMessageDefinition:definition
|
|
imageData:imageData
|
|
triggerType:triggerType];
|
|
case FIRIAMRenderAsImageOnlyView:
|
|
return [self imageOnlyDisplayMessageWithMessageDefinition:definition
|
|
imageData:imageData
|
|
triggerType:triggerType];
|
|
default:
|
|
return nil;
|
|
}
|
|
}
|
|
|
|
- (void)displayForMessage:(FIRIAMMessageDefinition *)message
|
|
triggerType:(FIRInAppMessagingDisplayTriggerType)triggerType {
|
|
_currentMsgBeingDisplayed = message;
|
|
self.isMsgBeingDisplayed = YES;
|
|
|
|
[message.renderData.contentData
|
|
loadImageDataWithBlock:^(NSData *_Nullable standardImageRawData,
|
|
NSData *_Nullable landscapeImageRawData, NSError *_Nullable error) {
|
|
FIRInAppMessagingImageData *imageData = nil;
|
|
FIRInAppMessagingImageData *landscapeImageData = nil;
|
|
|
|
if (error) {
|
|
FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400019",
|
|
@"Error in loading image data for the message.");
|
|
|
|
FIRInAppMessagingDisplayMessage *erroredMessage =
|
|
[self displayMessageWithMessageDefinition:message
|
|
imageData:imageData
|
|
landscapeImageData:landscapeImageData
|
|
triggerType:triggerType];
|
|
// short-circuit to display error handling
|
|
[self displayErrorForMessage:erroredMessage error:error];
|
|
self.isMsgBeingDisplayed = NO;
|
|
return;
|
|
} else {
|
|
if (standardImageRawData) {
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
|
imageData = [[FIRInAppMessagingImageData alloc]
|
|
initWithImageURL:message.renderData.contentData.imageURL.absoluteString
|
|
imageData:standardImageRawData];
|
|
#pragma clang diagnostic pop
|
|
}
|
|
if (landscapeImageRawData) {
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
|
landscapeImageData = [[FIRInAppMessagingImageData alloc]
|
|
initWithImageURL:message.renderData.contentData.landscapeImageURL.absoluteString
|
|
imageData:landscapeImageRawData];
|
|
#pragma clang diagnostic pop
|
|
}
|
|
}
|
|
|
|
// On slow networks, image loading may take significant time,
|
|
// in which the value of `suppressMessageDisplay` could change.
|
|
if (self.suppressMessageDisplay) {
|
|
FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400042",
|
|
@"Message display suppressed by developer at message display time.");
|
|
self.isMsgBeingDisplayed = NO;
|
|
return;
|
|
}
|
|
|
|
self.impressionRecorded = NO;
|
|
|
|
FIRInAppMessagingDisplayMessage *displayMessage =
|
|
[self displayMessageWithMessageDefinition:message
|
|
imageData:imageData
|
|
landscapeImageData:landscapeImageData
|
|
triggerType:triggerType];
|
|
|
|
// A final `nil`-check, performed to avoid crashing the client app.
|
|
if (!displayMessage) {
|
|
FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400043",
|
|
@"Failed to construct a non-nil display message.");
|
|
return;
|
|
}
|
|
|
|
[self.messageDisplayComponent displayMessage:displayMessage displayDelegate:self];
|
|
}];
|
|
}
|
|
|
|
- (BOOL)enoughIntervalFromLastDisplay {
|
|
NSTimeInterval intervalFromLastDisplayInSeconds =
|
|
[self.timeFetcher currentTimestampInSeconds] - self.lastDisplayTime;
|
|
|
|
FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400005",
|
|
@"Interval time from last display is %lf seconds", intervalFromLastDisplayInSeconds);
|
|
|
|
return intervalFromLastDisplayInSeconds >= self.setting.displayMinIntervalInMinutes * 60.0;
|
|
}
|
|
|
|
- (void)checkAndDisplayNextAppLaunchMessage {
|
|
// synchronizing on self so that we won't potentially enter the render flow from two
|
|
// threads.
|
|
@synchronized(self) {
|
|
if (!self.messageDisplayComponent) {
|
|
FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400028", @"%@",
|
|
[[self class] logStringForNilMessageDisplayComponent]);
|
|
return;
|
|
}
|
|
|
|
if (self.suppressMessageDisplay) {
|
|
FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400029",
|
|
@"Message display is being suppressed. No regular message rendering.");
|
|
return;
|
|
}
|
|
|
|
if (self.isMsgBeingDisplayed) {
|
|
FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400030",
|
|
@"An in-app message display is in progress, do not over-display on top of it.");
|
|
return;
|
|
}
|
|
|
|
if ([self.messageCache hasTestMessage] || [self enoughIntervalFromLastDisplay]) {
|
|
// We can display test messages anytime or display regular messages when
|
|
// the display time interval has been reached
|
|
FIRIAMMessageDefinition *nextAppLaunchMessage = [self.messageCache nextOnAppLaunchDisplayMsg];
|
|
|
|
if (nextAppLaunchMessage) {
|
|
[self displayForMessage:nextAppLaunchMessage
|
|
triggerType:FIRInAppMessagingDisplayTriggerTypeOnAnalyticsEvent];
|
|
self.lastDisplayTime = [self.timeFetcher currentTimestampInSeconds];
|
|
} else {
|
|
FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400040",
|
|
@"No appropriate in-app message detected for display.");
|
|
}
|
|
} else {
|
|
FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400041",
|
|
@"Minimal display interval of %lf seconds has not been reached yet.",
|
|
self.setting.displayMinIntervalInMinutes * 60.0);
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)checkAndDisplayNextAppForegroundMessage {
|
|
// synchronizing on self so that we won't potentially enter the render flow from two
|
|
// threads: example like showing analytics triggered message and a regular app open
|
|
// triggered message concurrently
|
|
@synchronized(self) {
|
|
if (!self.messageDisplayComponent) {
|
|
FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400027", @"%@",
|
|
[[self class] logStringForNilMessageDisplayComponent]);
|
|
return;
|
|
}
|
|
|
|
if (self.suppressMessageDisplay) {
|
|
FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400016",
|
|
@"Message display is being suppressed. No regular message rendering.");
|
|
return;
|
|
}
|
|
|
|
if (self.isMsgBeingDisplayed) {
|
|
FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400002",
|
|
@"An in-app message display is in progress, do not over-display on top of it.");
|
|
return;
|
|
}
|
|
|
|
if ([self.messageCache hasTestMessage] || [self enoughIntervalFromLastDisplay]) {
|
|
// We can display test messages anytime or display regular messages when
|
|
// the display time interval has been reached
|
|
FIRIAMMessageDefinition *nextForegroundMessage = [self.messageCache nextOnAppOpenDisplayMsg];
|
|
|
|
if (nextForegroundMessage) {
|
|
[self displayForMessage:nextForegroundMessage
|
|
triggerType:FIRInAppMessagingDisplayTriggerTypeOnAppForeground];
|
|
self.lastDisplayTime = [self.timeFetcher currentTimestampInSeconds];
|
|
} else {
|
|
FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400001",
|
|
@"No appropriate in-app message detected for display.");
|
|
}
|
|
} else {
|
|
FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400003",
|
|
@"Minimal display interval of %lf seconds has not been reached yet.",
|
|
self.setting.displayMinIntervalInMinutes * 60.0);
|
|
}
|
|
}
|
|
}
|
|
@end
|
|
|
|
#endif // TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_VISION
|