FIRIAMDisplayExecutor.m 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751
  1. /*
  2. * Copyright 2017 Google
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #import <TargetConditionals.h>
  17. #if TARGET_OS_IOS || TARGET_OS_TV
  18. #import <UIKit/UIKit.h>
  19. #import "FirebaseCore/Sources/Private/FirebaseCoreInternal.h"
  20. #import "FirebaseInAppMessaging/Sources/FIRCore+InAppMessaging.h"
  21. #import "FirebaseInAppMessaging/Sources/Private/Data/FIRIAMMessageContentData.h"
  22. #import "FirebaseInAppMessaging/Sources/Private/Data/FIRIAMMessageDefinition.h"
  23. #import "FirebaseInAppMessaging/Sources/Private/Flows/FIRIAMActivityLogger.h"
  24. #import "FirebaseInAppMessaging/Sources/Private/Flows/FIRIAMDisplayExecutor.h"
  25. #import "FirebaseInAppMessaging/Sources/Public/FirebaseInAppMessaging/FIRInAppMessaging.h"
  26. #import "FirebaseInAppMessaging/Sources/RenderingObjects/FIRInAppMessagingRenderingPrivate.h"
  27. #import "FirebaseInAppMessaging/Sources/Runtime/FIRIAMSDKRuntimeErrorCodes.h"
  28. #import "FirebaseABTesting/Sources/Private/FirebaseABTestingInternal.h"
  29. @implementation FIRIAMDisplaySetting
  30. @end
  31. @interface FIRIAMDisplayExecutor () <FIRInAppMessagingDisplayDelegate>
  32. @property(nonatomic) id<FIRIAMTimeFetcher> timeFetcher;
  33. // YES if a message is being rendered at this time
  34. @property(nonatomic) BOOL isMsgBeingDisplayed;
  35. @property(nonatomic) NSTimeInterval lastDisplayTime;
  36. @property(nonatomic, nonnull, readonly) FIRInAppMessaging *inAppMessaging;
  37. @property(nonatomic, nonnull, readonly) FIRIAMDisplaySetting *setting;
  38. @property(nonatomic, nonnull, readonly) FIRIAMMessageClientCache *messageCache;
  39. @property(nonatomic, nonnull, readonly) id<FIRIAMBookKeeper> displayBookKeeper;
  40. @property(nonatomic) BOOL impressionRecorded;
  41. @property(nonatomic, nonnull, readonly) id<FIRIAMAnalyticsEventLogger> analyticsEventLogger;
  42. @property(nonatomic, nonnull, readonly) FIRIAMActionURLFollower *actionURLFollower;
  43. // Used for displaying the test on device message error alert.
  44. @property(nonatomic, strong) UIWindow *alertWindow;
  45. @end
  46. @implementation FIRIAMDisplayExecutor {
  47. FIRIAMMessageDefinition *_currentMsgBeingDisplayed;
  48. }
  49. + (NSString *)logStringForNilMessageDisplayComponent {
  50. #if TARGET_OS_IOS
  51. return @"Message display component is not present yet. No display should happen.";
  52. #else // TARGET_OS_TV
  53. return @"There is no default UI for tvOS. You must implement a messageDisplayComponent and set "
  54. @"it on the InAppMessaging singleton. See "
  55. @"https://firebase.google.com/docs/in-app-messaging/"
  56. @"customize-messages#create_your_own_message_display_library.";
  57. #endif
  58. }
  59. #pragma mark - FIRInAppMessagingDisplayDelegate methods
  60. - (void)messageClicked:(FIRInAppMessagingDisplayMessage *)inAppMessage
  61. withAction:(FIRInAppMessagingAction *)action {
  62. // Call through to app-side delegate.
  63. __weak id<FIRInAppMessagingDisplayDelegate> appSideDelegate = self.inAppMessaging.delegate;
  64. if ([appSideDelegate respondsToSelector:@selector(messageClicked:withAction:)]) {
  65. [appSideDelegate messageClicked:inAppMessage withAction:action];
  66. }
  67. self.isMsgBeingDisplayed = NO;
  68. if (!_currentMsgBeingDisplayed.renderData.messageID) {
  69. FIRLogWarning(kFIRLoggerInAppMessaging, @"I-IAM400030",
  70. @"messageClicked called but "
  71. "there is no current message ID.");
  72. return;
  73. }
  74. if (_currentMsgBeingDisplayed.isTestMessage) {
  75. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400031",
  76. @"A test message clicked. Do test event impression/click analytics logging");
  77. [self.analyticsEventLogger
  78. logAnalyticsEventForType:FIRIAMAnalyticsEventTestMessageImpression
  79. forCampaignID:_currentMsgBeingDisplayed.renderData.messageID
  80. withCampaignName:_currentMsgBeingDisplayed.renderData.name
  81. eventTimeInMs:nil
  82. completion:^(BOOL success) {
  83. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400036",
  84. @"Logging analytics event for url following %@",
  85. success ? @"succeeded" : @"failed");
  86. }];
  87. [self.analyticsEventLogger
  88. logAnalyticsEventForType:FIRIAMAnalyticsEventTestMessageClick
  89. forCampaignID:_currentMsgBeingDisplayed.renderData.messageID
  90. withCampaignName:_currentMsgBeingDisplayed.renderData.name
  91. eventTimeInMs:nil
  92. completion:^(BOOL success) {
  93. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400039",
  94. @"Logging analytics event for url following %@",
  95. success ? @"succeeded" : @"failed");
  96. }];
  97. } else {
  98. // Logging the impression
  99. [self recordValidImpression:_currentMsgBeingDisplayed.renderData.messageID
  100. withMessageName:_currentMsgBeingDisplayed.renderData.name];
  101. if (action.actionURL) {
  102. [self.analyticsEventLogger
  103. logAnalyticsEventForType:FIRIAMAnalyticsEventActionURLFollow
  104. forCampaignID:_currentMsgBeingDisplayed.renderData.messageID
  105. withCampaignName:_currentMsgBeingDisplayed.renderData.name
  106. eventTimeInMs:nil
  107. completion:^(BOOL success) {
  108. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400032",
  109. @"Logging analytics event for url following %@",
  110. success ? @"succeeded" : @"failed");
  111. }];
  112. }
  113. }
  114. NSURL *actionURL = action.actionURL;
  115. if (!actionURL) {
  116. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400033",
  117. @"messageClicked called but "
  118. "there is no action url specified in the message data.");
  119. // it's equivalent to closing the message with no further action
  120. return;
  121. } else {
  122. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400037", @"Following action url %@",
  123. actionURL.absoluteString);
  124. @try {
  125. [self.actionURLFollower
  126. followActionURL:actionURL
  127. withCompletionBlock:^(BOOL success) {
  128. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400034",
  129. @"Seeing %@ from following action URL", success ? @"success" : @"error");
  130. }];
  131. } @catch (NSException *e) {
  132. FIRLogWarning(kFIRLoggerInAppMessaging, @"I-IAM400035",
  133. @"Exception encountered in following "
  134. "action url (%@): %@ ",
  135. actionURL, e.description);
  136. @throw;
  137. }
  138. }
  139. }
  140. - (void)messageDismissed:(FIRInAppMessagingDisplayMessage *)inAppMessage
  141. dismissType:(FIRInAppMessagingDismissType)dismissType {
  142. // Call through to app-side delegate.
  143. __weak id<FIRInAppMessagingDisplayDelegate> appSideDelegate = self.inAppMessaging.delegate;
  144. if ([appSideDelegate respondsToSelector:@selector(messageDismissed:dismissType:)]) {
  145. [appSideDelegate messageDismissed:inAppMessage dismissType:dismissType];
  146. }
  147. self.isMsgBeingDisplayed = NO;
  148. if (!_currentMsgBeingDisplayed.renderData.messageID) {
  149. FIRLogWarning(kFIRLoggerInAppMessaging, @"I-IAM400014",
  150. @"messageDismissedWithType called but "
  151. "there is no current message ID.");
  152. return;
  153. }
  154. if (_currentMsgBeingDisplayed.isTestMessage) {
  155. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400020",
  156. @"A test message dismissed. Record the impression event.");
  157. [self.analyticsEventLogger
  158. logAnalyticsEventForType:FIRIAMAnalyticsEventTestMessageImpression
  159. forCampaignID:_currentMsgBeingDisplayed.renderData.messageID
  160. withCampaignName:_currentMsgBeingDisplayed.renderData.name
  161. eventTimeInMs:nil
  162. completion:^(BOOL success) {
  163. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400038",
  164. @"Logging analytics event for url following %@",
  165. success ? @"succeeded" : @"failed");
  166. }];
  167. return;
  168. }
  169. // Logging the impression
  170. [self recordValidImpression:_currentMsgBeingDisplayed.renderData.messageID
  171. withMessageName:_currentMsgBeingDisplayed.renderData.name];
  172. FIRIAMAnalyticsLogEventType logEventType = dismissType == FIRInAppMessagingDismissTypeAuto
  173. ? FIRIAMAnalyticsEventMessageDismissAuto
  174. : FIRIAMAnalyticsEventMessageDismissClick;
  175. [self.analyticsEventLogger
  176. logAnalyticsEventForType:logEventType
  177. forCampaignID:_currentMsgBeingDisplayed.renderData.messageID
  178. withCampaignName:_currentMsgBeingDisplayed.renderData.name
  179. eventTimeInMs:nil
  180. completion:^(BOOL success) {
  181. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400004",
  182. @"Logging analytics event for message dismiss %@",
  183. success ? @"succeeded" : @"failed");
  184. }];
  185. }
  186. - (void)impressionDetectedForMessage:(FIRInAppMessagingDisplayMessage *)inAppMessage {
  187. __weak id<FIRInAppMessagingDisplayDelegate> appSideDelegate = self.inAppMessaging.delegate;
  188. if ([appSideDelegate respondsToSelector:@selector(impressionDetectedForMessage:)]) {
  189. [appSideDelegate impressionDetectedForMessage:inAppMessage];
  190. }
  191. if (!_currentMsgBeingDisplayed.renderData.messageID) {
  192. FIRLogWarning(kFIRLoggerInAppMessaging, @"I-IAM400022",
  193. @"impressionDetected called but "
  194. "there is no current message ID.");
  195. return;
  196. }
  197. // If this is an experimental FIAM, activate the experiment.
  198. if (inAppMessage.campaignInfo.experimentPayload) {
  199. [[FIRExperimentController sharedInstance]
  200. activateExperiment:inAppMessage.campaignInfo.experimentPayload
  201. forServiceOrigin:@"fiam"];
  202. }
  203. if (!_currentMsgBeingDisplayed.isTestMessage) {
  204. // Displayed long enough to be a valid impression.
  205. [self recordValidImpression:_currentMsgBeingDisplayed.renderData.messageID
  206. withMessageName:_currentMsgBeingDisplayed.renderData.name];
  207. } else {
  208. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400011",
  209. @"A test message. Record the test message impression event.");
  210. return;
  211. }
  212. }
  213. - (void)displayErrorForMessage:(FIRInAppMessagingDisplayMessage *)inAppMessage
  214. error:(NSError *)error {
  215. __weak id<FIRInAppMessagingDisplayDelegate> appSideDelegate = self.inAppMessaging.delegate;
  216. if ([appSideDelegate respondsToSelector:@selector(displayErrorForMessage:error:)]) {
  217. [appSideDelegate displayErrorForMessage:inAppMessage error:error];
  218. }
  219. self.isMsgBeingDisplayed = NO;
  220. if (!_currentMsgBeingDisplayed.renderData.messageID) {
  221. FIRLogWarning(kFIRLoggerInAppMessaging, @"I-IAM400017",
  222. @"displayErrorEncountered called but "
  223. "there is no current message ID.");
  224. return;
  225. }
  226. NSString *messageID = _currentMsgBeingDisplayed.renderData.messageID;
  227. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400009",
  228. @"Display ran into error for message %@: %@", messageID, error);
  229. if (_currentMsgBeingDisplayed.isTestMessage) {
  230. [self displayMessageLoadError:error];
  231. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400012",
  232. @"A test message. No analytics tracking "
  233. "from image data loading failure");
  234. return;
  235. }
  236. // we remove the message from the client side cache so that it won't be retried until next time
  237. // it's fetched again from server.
  238. [self.messageCache removeMessageWithId:messageID];
  239. NSString *messageName = _currentMsgBeingDisplayed.renderData.name;
  240. if ([error.domain isEqualToString:NSURLErrorDomain]) {
  241. [self.analyticsEventLogger
  242. logAnalyticsEventForType:FIRIAMAnalyticsEventImageFetchError
  243. forCampaignID:messageID
  244. withCampaignName:messageName
  245. eventTimeInMs:nil
  246. completion:^(BOOL success) {
  247. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400010",
  248. @"Logging analytics event for image fetch error %@",
  249. success ? @"succeeded" : @"failed");
  250. }];
  251. } else if (error.code == FIRIAMSDKRuntimeErrorNonImageMimetypeFromImageURL) {
  252. [self.analyticsEventLogger
  253. logAnalyticsEventForType:FIRIAMAnalyticsEventImageFormatUnsupported
  254. forCampaignID:messageID
  255. withCampaignName:messageName
  256. eventTimeInMs:nil
  257. completion:^(BOOL success) {
  258. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400013",
  259. @"Logging analytics event for image format error %@",
  260. success ? @"succeeded" : @"failed");
  261. }];
  262. }
  263. }
  264. - (void)recordValidImpression:(NSString *)messageID withMessageName:(NSString *)messageName {
  265. if (!self.impressionRecorded) {
  266. [self.displayBookKeeper recordNewImpressionForMessage:messageID
  267. withStartTimestampInSeconds:self.lastDisplayTime];
  268. self.impressionRecorded = YES;
  269. [self.messageCache removeMessageWithId:messageID];
  270. // Log an impression analytics event as well.
  271. [self.analyticsEventLogger
  272. logAnalyticsEventForType:FIRIAMAnalyticsEventMessageImpression
  273. forCampaignID:messageID
  274. withCampaignName:messageName
  275. eventTimeInMs:nil
  276. completion:^(BOOL success) {
  277. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400007",
  278. @"Logging analytics event for impression %@",
  279. success ? @"succeeded" : @"failed");
  280. }];
  281. }
  282. }
  283. - (void)displayMessageLoadError:(NSError *)error {
  284. NSString *errorMsg = error.userInfo[NSLocalizedDescriptionKey]
  285. ? error.userInfo[NSLocalizedDescriptionKey]
  286. : NSLocalizedString(@"Message loading failed", nil);
  287. UIAlertController *alert = [UIAlertController
  288. alertControllerWithTitle:@"Firebase InAppMessaging fail to load a test message"
  289. message:errorMsg
  290. preferredStyle:UIAlertControllerStyleAlert];
  291. UIAlertAction *defaultAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"OK", nil)
  292. style:UIAlertActionStyleDefault
  293. handler:^(UIAlertAction *action) {
  294. self.alertWindow.hidden = NO;
  295. self.alertWindow = nil;
  296. }];
  297. [alert addAction:defaultAction];
  298. dispatch_async(dispatch_get_main_queue(), ^{
  299. #if defined(__IPHONE_13_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000
  300. if (@available(iOS 13.0, tvOS 13.0, *)) {
  301. UIWindowScene *foregroundedScene = nil;
  302. for (UIWindowScene *connectedScene in [UIApplication sharedApplication].connectedScenes) {
  303. if (connectedScene.activationState == UISceneActivationStateForegroundActive) {
  304. foregroundedScene = connectedScene;
  305. break;
  306. }
  307. }
  308. if (foregroundedScene == nil) {
  309. return;
  310. }
  311. self.alertWindow = [[UIWindow alloc] initWithWindowScene:foregroundedScene];
  312. }
  313. #else // defined(__IPHONE_13_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000
  314. self.alertWindow = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
  315. #endif
  316. UIViewController *alertViewController = [[UIViewController alloc] init];
  317. self.alertWindow.rootViewController = alertViewController;
  318. self.alertWindow.hidden = NO;
  319. [alertViewController presentViewController:alert animated:YES completion:nil];
  320. });
  321. }
  322. - (instancetype)initWithInAppMessaging:(FIRInAppMessaging *)inAppMessaging
  323. setting:(FIRIAMDisplaySetting *)setting
  324. messageCache:(FIRIAMMessageClientCache *)cache
  325. timeFetcher:(id<FIRIAMTimeFetcher>)timeFetcher
  326. bookKeeper:(id<FIRIAMBookKeeper>)displayBookKeeper
  327. actionURLFollower:(FIRIAMActionURLFollower *)actionURLFollower
  328. activityLogger:(FIRIAMActivityLogger *)activityLogger
  329. analyticsEventLogger:(id<FIRIAMAnalyticsEventLogger>)analyticsEventLogger {
  330. if (self = [super init]) {
  331. _inAppMessaging = inAppMessaging;
  332. _timeFetcher = timeFetcher;
  333. _lastDisplayTime = displayBookKeeper.lastDisplayTime;
  334. _setting = setting;
  335. _messageCache = cache;
  336. _displayBookKeeper = displayBookKeeper;
  337. _isMsgBeingDisplayed = NO;
  338. _analyticsEventLogger = analyticsEventLogger;
  339. _actionURLFollower = actionURLFollower;
  340. _suppressMessageDisplay = NO; // always allow message display on startup
  341. }
  342. return self;
  343. }
  344. - (void)checkAndDisplayNextContextualMessageForAnalyticsEvent:(NSString *)eventName {
  345. // synchronizing on self so that we won't potentially enter the render flow from two
  346. // threads: example like showing analytics triggered message and a regular app open
  347. // triggered message
  348. @synchronized(self) {
  349. if (self.suppressMessageDisplay) {
  350. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400015",
  351. @"Message display is being suppressed. No contextual message rendering.");
  352. return;
  353. }
  354. if (!self.messageDisplayComponent) {
  355. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400026", @"%@",
  356. [[self class] logStringForNilMessageDisplayComponent]);
  357. return;
  358. }
  359. if (self.isMsgBeingDisplayed) {
  360. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400008",
  361. @"An in-app message display is in progress, do not check analytics event "
  362. "based message for now.");
  363. return;
  364. }
  365. // Pop up next analytics event based message to be displayed
  366. FIRIAMMessageDefinition *nextAnalyticsBasedMessage =
  367. [self.messageCache nextOnFirebaseAnalyticEventDisplayMsg:eventName];
  368. if (nextAnalyticsBasedMessage) {
  369. [self displayForMessage:nextAnalyticsBasedMessage
  370. triggerType:FIRInAppMessagingDisplayTriggerTypeOnAnalyticsEvent];
  371. }
  372. }
  373. }
  374. - (FIRInAppMessagingCardDisplay *)
  375. cardDisplayMessageWithMessageDefinition:(FIRIAMMessageDefinition *)definition
  376. portraitImageData:(FIRInAppMessagingImageData *)portraitImageData
  377. landscapeImageData:
  378. (nullable FIRInAppMessagingImageData *)landscapeImageData
  379. triggerType:(FIRInAppMessagingDisplayTriggerType)triggerType {
  380. // For easier reference in this method.
  381. FIRIAMMessageRenderData *renderData = definition.renderData;
  382. NSString *title = renderData.contentData.titleText;
  383. NSString *body = renderData.contentData.bodyText;
  384. // Action button data is never nil for a card message.
  385. #pragma clang diagnostic push
  386. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  387. FIRInAppMessagingActionButton *primaryActionButton = [[FIRInAppMessagingActionButton alloc]
  388. initWithButtonText:renderData.contentData.actionButtonText
  389. buttonTextColor:renderData.renderingEffectSettings.btnTextColor
  390. backgroundColor:renderData.renderingEffectSettings.btnBGColor];
  391. #pragma clang diagnostic pop
  392. FIRInAppMessagingActionButton *secondaryActionButton = nil;
  393. if (definition.renderData.contentData.secondaryActionButtonText) {
  394. #pragma clang diagnostic push
  395. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  396. secondaryActionButton = [[FIRInAppMessagingActionButton alloc]
  397. initWithButtonText:renderData.contentData.secondaryActionButtonText
  398. buttonTextColor:renderData.renderingEffectSettings.secondaryActionBtnTextColor
  399. backgroundColor:renderData.renderingEffectSettings.btnBGColor];
  400. #pragma clang diagnostic pop
  401. }
  402. FIRInAppMessagingCardDisplay *cardMessage = [[FIRInAppMessagingCardDisplay alloc]
  403. initWithMessageID:renderData.messageID
  404. campaignName:renderData.name
  405. experimentPayload:definition.experimentPayload
  406. renderAsTestMessage:definition.isTestMessage
  407. triggerType:triggerType
  408. titleText:title
  409. textColor:renderData.renderingEffectSettings.textColor
  410. portraitImageData:portraitImageData
  411. backgroundColor:renderData.renderingEffectSettings.displayBGColor
  412. primaryActionButton:primaryActionButton
  413. primaryActionURL:definition.renderData.contentData.actionURL
  414. appData:definition.appData];
  415. cardMessage.body = body;
  416. cardMessage.landscapeImageData = landscapeImageData;
  417. cardMessage.secondaryActionButton = secondaryActionButton;
  418. cardMessage.secondaryActionURL = definition.renderData.contentData.secondaryActionURL;
  419. return cardMessage;
  420. }
  421. - (FIRInAppMessagingBannerDisplay *)
  422. bannerDisplayMessageWithMessageDefinition:(FIRIAMMessageDefinition *)definition
  423. imageData:(FIRInAppMessagingImageData *)imageData
  424. triggerType:(FIRInAppMessagingDisplayTriggerType)triggerType {
  425. NSString *title = definition.renderData.contentData.titleText;
  426. NSString *body = definition.renderData.contentData.bodyText;
  427. #pragma clang diagnostic push
  428. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  429. FIRInAppMessagingBannerDisplay *bannerMessage = [[FIRInAppMessagingBannerDisplay alloc]
  430. initWithMessageID:definition.renderData.messageID
  431. campaignName:definition.renderData.name
  432. experimentPayload:definition.experimentPayload
  433. renderAsTestMessage:definition.isTestMessage
  434. triggerType:triggerType
  435. titleText:title
  436. bodyText:body
  437. textColor:definition.renderData.renderingEffectSettings.textColor
  438. backgroundColor:definition.renderData.renderingEffectSettings.displayBGColor
  439. imageData:imageData
  440. actionURL:definition.renderData.contentData.actionURL
  441. appData:definition.appData];
  442. #pragma clang diagnostic pop
  443. return bannerMessage;
  444. }
  445. - (FIRInAppMessagingImageOnlyDisplay *)
  446. imageOnlyDisplayMessageWithMessageDefinition:(FIRIAMMessageDefinition *)definition
  447. imageData:(FIRInAppMessagingImageData *)imageData
  448. triggerType:(FIRInAppMessagingDisplayTriggerType)triggerType {
  449. #pragma clang diagnostic push
  450. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  451. FIRInAppMessagingImageOnlyDisplay *imageOnlyMessage = [[FIRInAppMessagingImageOnlyDisplay alloc]
  452. initWithMessageID:definition.renderData.messageID
  453. campaignName:definition.renderData.name
  454. experimentPayload:definition.experimentPayload
  455. renderAsTestMessage:definition.isTestMessage
  456. triggerType:triggerType
  457. imageData:imageData
  458. actionURL:definition.renderData.contentData.actionURL
  459. appData:definition.appData];
  460. #pragma clang diagnostic pop
  461. return imageOnlyMessage;
  462. }
  463. - (FIRInAppMessagingModalDisplay *)
  464. modalDisplayMessageWithMessageDefinition:(FIRIAMMessageDefinition *)definition
  465. imageData:(FIRInAppMessagingImageData *)imageData
  466. triggerType:(FIRInAppMessagingDisplayTriggerType)triggerType {
  467. // For easier reference in this method.
  468. FIRIAMMessageRenderData *renderData = definition.renderData;
  469. NSString *title = renderData.contentData.titleText;
  470. NSString *body = renderData.contentData.bodyText;
  471. FIRInAppMessagingActionButton *actionButton = nil;
  472. if (definition.renderData.contentData.actionButtonText) {
  473. #pragma clang diagnostic push
  474. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  475. actionButton = [[FIRInAppMessagingActionButton alloc]
  476. initWithButtonText:renderData.contentData.actionButtonText
  477. buttonTextColor:renderData.renderingEffectSettings.btnTextColor
  478. backgroundColor:renderData.renderingEffectSettings.btnBGColor];
  479. #pragma clang diagnostic pop
  480. }
  481. #pragma clang diagnostic push
  482. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  483. FIRInAppMessagingModalDisplay *modalViewMessage = [[FIRInAppMessagingModalDisplay alloc]
  484. initWithMessageID:definition.renderData.messageID
  485. campaignName:definition.renderData.name
  486. experimentPayload:definition.experimentPayload
  487. renderAsTestMessage:definition.isTestMessage
  488. triggerType:triggerType
  489. titleText:title
  490. bodyText:body
  491. textColor:renderData.renderingEffectSettings.textColor
  492. backgroundColor:renderData.renderingEffectSettings.displayBGColor
  493. imageData:imageData
  494. actionButton:actionButton
  495. actionURL:definition.renderData.contentData.actionURL
  496. appData:definition.appData];
  497. #pragma clang diagnostic pop
  498. return modalViewMessage;
  499. }
  500. - (FIRInAppMessagingDisplayMessage *)
  501. displayMessageWithMessageDefinition:(FIRIAMMessageDefinition *)definition
  502. imageData:(FIRInAppMessagingImageData *)imageData
  503. landscapeImageData:(nullable FIRInAppMessagingImageData *)landscapeImageData
  504. triggerType:(FIRInAppMessagingDisplayTriggerType)triggerType {
  505. switch (definition.renderData.renderingEffectSettings.viewMode) {
  506. case FIRIAMRenderAsCardView:
  507. if (imageData == nil) {
  508. // Image data should never nil for a valid card message.
  509. return nil;
  510. }
  511. return [self cardDisplayMessageWithMessageDefinition:definition
  512. portraitImageData:imageData
  513. landscapeImageData:landscapeImageData
  514. triggerType:triggerType];
  515. case FIRIAMRenderAsBannerView:
  516. return [self bannerDisplayMessageWithMessageDefinition:definition
  517. imageData:imageData
  518. triggerType:triggerType];
  519. case FIRIAMRenderAsModalView:
  520. return [self modalDisplayMessageWithMessageDefinition:definition
  521. imageData:imageData
  522. triggerType:triggerType];
  523. case FIRIAMRenderAsImageOnlyView:
  524. return [self imageOnlyDisplayMessageWithMessageDefinition:definition
  525. imageData:imageData
  526. triggerType:triggerType];
  527. default:
  528. return nil;
  529. }
  530. }
  531. - (void)displayForMessage:(FIRIAMMessageDefinition *)message
  532. triggerType:(FIRInAppMessagingDisplayTriggerType)triggerType {
  533. _currentMsgBeingDisplayed = message;
  534. [message.renderData.contentData
  535. loadImageDataWithBlock:^(NSData *_Nullable standardImageRawData,
  536. NSData *_Nullable landscapeImageRawData, NSError *_Nullable error) {
  537. FIRInAppMessagingImageData *imageData = nil;
  538. FIRInAppMessagingImageData *landscapeImageData = nil;
  539. if (error) {
  540. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400019",
  541. @"Error in loading image data for the message.");
  542. FIRInAppMessagingDisplayMessage *erroredMessage =
  543. [self displayMessageWithMessageDefinition:message
  544. imageData:imageData
  545. landscapeImageData:landscapeImageData
  546. triggerType:triggerType];
  547. // short-circuit to display error handling
  548. [self displayErrorForMessage:erroredMessage error:error];
  549. return;
  550. } else {
  551. if (standardImageRawData) {
  552. #pragma clang diagnostic push
  553. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  554. imageData = [[FIRInAppMessagingImageData alloc]
  555. initWithImageURL:message.renderData.contentData.imageURL.absoluteString
  556. imageData:standardImageRawData];
  557. #pragma clang diagnostic pop
  558. }
  559. if (landscapeImageRawData) {
  560. #pragma clang diagnostic push
  561. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  562. landscapeImageData = [[FIRInAppMessagingImageData alloc]
  563. initWithImageURL:message.renderData.contentData.landscapeImageURL.absoluteString
  564. imageData:landscapeImageRawData];
  565. #pragma clang diagnostic pop
  566. }
  567. }
  568. self.impressionRecorded = NO;
  569. self.isMsgBeingDisplayed = YES;
  570. FIRInAppMessagingDisplayMessage *displayMessage =
  571. [self displayMessageWithMessageDefinition:message
  572. imageData:imageData
  573. landscapeImageData:landscapeImageData
  574. triggerType:triggerType];
  575. [self.messageDisplayComponent displayMessage:displayMessage displayDelegate:self];
  576. }];
  577. }
  578. - (BOOL)enoughIntervalFromLastDisplay {
  579. NSTimeInterval intervalFromLastDisplayInSeconds =
  580. [self.timeFetcher currentTimestampInSeconds] - self.lastDisplayTime;
  581. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400005",
  582. @"Interval time from last display is %lf seconds", intervalFromLastDisplayInSeconds);
  583. return intervalFromLastDisplayInSeconds >= self.setting.displayMinIntervalInMinutes * 60.0;
  584. }
  585. - (void)checkAndDisplayNextAppLaunchMessage {
  586. // synchronizing on self so that we won't potentially enter the render flow from two
  587. // threads.
  588. @synchronized(self) {
  589. if (!self.messageDisplayComponent) {
  590. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400028", @"%@",
  591. [[self class] logStringForNilMessageDisplayComponent]);
  592. return;
  593. }
  594. if (self.suppressMessageDisplay) {
  595. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400029",
  596. @"Message display is being suppressed. No regular message rendering.");
  597. return;
  598. }
  599. if (self.isMsgBeingDisplayed) {
  600. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400030",
  601. @"An in-app message display is in progress, do not over-display on top of it.");
  602. return;
  603. }
  604. if ([self.messageCache hasTestMessage] || [self enoughIntervalFromLastDisplay]) {
  605. // We can display test messages anytime or display regular messages when
  606. // the display time interval has been reached
  607. FIRIAMMessageDefinition *nextAppLaunchMessage = [self.messageCache nextOnAppLaunchDisplayMsg];
  608. if (nextAppLaunchMessage) {
  609. [self displayForMessage:nextAppLaunchMessage
  610. triggerType:FIRInAppMessagingDisplayTriggerTypeOnAnalyticsEvent];
  611. self.lastDisplayTime = [self.timeFetcher currentTimestampInSeconds];
  612. } else {
  613. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400040",
  614. @"No appropriate in-app message detected for display.");
  615. }
  616. } else {
  617. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400041",
  618. @"Minimal display interval of %lf seconds has not been reached yet.",
  619. self.setting.displayMinIntervalInMinutes * 60.0);
  620. }
  621. }
  622. }
  623. - (void)checkAndDisplayNextAppForegroundMessage {
  624. // synchronizing on self so that we won't potentially enter the render flow from two
  625. // threads: example like showing analytics triggered message and a regular app open
  626. // triggered message concurrently
  627. @synchronized(self) {
  628. if (!self.messageDisplayComponent) {
  629. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400027", @"%@",
  630. [[self class] logStringForNilMessageDisplayComponent]);
  631. return;
  632. }
  633. if (self.suppressMessageDisplay) {
  634. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400016",
  635. @"Message display is being suppressed. No regular message rendering.");
  636. return;
  637. }
  638. if (self.isMsgBeingDisplayed) {
  639. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400002",
  640. @"An in-app message display is in progress, do not over-display on top of it.");
  641. return;
  642. }
  643. if ([self.messageCache hasTestMessage] || [self enoughIntervalFromLastDisplay]) {
  644. // We can display test messages anytime or display regular messages when
  645. // the display time interval has been reached
  646. FIRIAMMessageDefinition *nextForegroundMessage = [self.messageCache nextOnAppOpenDisplayMsg];
  647. if (nextForegroundMessage) {
  648. [self displayForMessage:nextForegroundMessage
  649. triggerType:FIRInAppMessagingDisplayTriggerTypeOnAppForeground];
  650. self.lastDisplayTime = [self.timeFetcher currentTimestampInSeconds];
  651. } else {
  652. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400001",
  653. @"No appropriate in-app message detected for display.");
  654. }
  655. } else {
  656. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400003",
  657. @"Minimal display interval of %lf seconds has not been reached yet.",
  658. self.setting.displayMinIntervalInMinutes * 60.0);
  659. }
  660. }
  661. }
  662. @end
  663. #endif // TARGET_OS_IOS || TARGET_OS_TV