FIRIAMDisplayExecutor.m 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626
  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 <FirebaseCore/FIRLogger.h>
  17. #import "FIRCore+InAppMessaging.h"
  18. #import "FIRIAMActivityLogger.h"
  19. #import "FIRIAMDisplayExecutor.h"
  20. #import "FIRIAMMessageContentData.h"
  21. #import "FIRIAMMessageDefinition.h"
  22. #import "FIRIAMSDKRuntimeErrorCodes.h"
  23. #import "FIRInAppMessaging.h"
  24. #import "FIRInAppMessagingRenderingPrivate.h"
  25. @implementation FIRIAMDisplaySetting
  26. @end
  27. @interface FIRIAMDisplayExecutor () <FIRInAppMessagingDisplayDelegate>
  28. @property(nonatomic) id<FIRIAMTimeFetcher> timeFetcher;
  29. // YES if a message is being rendered at this time
  30. @property(nonatomic) BOOL isMsgBeingDisplayed;
  31. @property(nonatomic) NSTimeInterval lastDisplayTime;
  32. @property(nonatomic, nonnull, readonly) FIRInAppMessaging *inAppMessaging;
  33. @property(nonatomic, nonnull, readonly) FIRIAMDisplaySetting *setting;
  34. @property(nonatomic, nonnull, readonly) FIRIAMMessageClientCache *messageCache;
  35. @property(nonatomic, nonnull, readonly) id<FIRIAMBookKeeper> displayBookKeeper;
  36. @property(nonatomic) BOOL impressionRecorded;
  37. @property(nonatomic, nonnull, readonly) id<FIRIAMAnalyticsEventLogger> analyticsEventLogger;
  38. @property(nonatomic, nonnull, readonly) FIRIAMActionURLFollower *actionURLFollower;
  39. @end
  40. @implementation FIRIAMDisplayExecutor {
  41. FIRIAMMessageDefinition *_currentMsgBeingDisplayed;
  42. }
  43. #pragma mark - FIRInAppMessagingDisplayDelegate methods
  44. - (void)messageClicked:(FIRInAppMessagingDisplayMessage *)inAppMessage
  45. withAction:(FIRInAppMessagingAction *)action {
  46. // Call through to app-side delegate.
  47. __weak id<FIRInAppMessagingDisplayDelegate> appSideDelegate = self.inAppMessaging.delegate;
  48. if ([appSideDelegate respondsToSelector:@selector(messageClicked:withAction:)]) {
  49. [appSideDelegate messageClicked:inAppMessage withAction:action];
  50. } else if ([appSideDelegate respondsToSelector:@selector(messageClicked:)]) {
  51. // Deprecated method is called only as a fall-back.
  52. #pragma clang diagnostic push
  53. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  54. [appSideDelegate messageClicked:inAppMessage];
  55. #pragma clang diagnostic pop
  56. }
  57. self.isMsgBeingDisplayed = NO;
  58. if (!_currentMsgBeingDisplayed.renderData.messageID) {
  59. FIRLogWarning(kFIRLoggerInAppMessaging, @"I-IAM400030",
  60. @"messageClicked called but "
  61. "there is no current message ID.");
  62. return;
  63. }
  64. if (_currentMsgBeingDisplayed.isTestMessage) {
  65. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400031",
  66. @"A test message clicked. Do test event impression/click analytics logging");
  67. [self.analyticsEventLogger
  68. logAnalyticsEventForType:FIRIAMAnalyticsEventTestMessageImpression
  69. forCampaignID:_currentMsgBeingDisplayed.renderData.messageID
  70. withCampaignName:_currentMsgBeingDisplayed.renderData.name
  71. eventTimeInMs:nil
  72. completion:^(BOOL success) {
  73. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400036",
  74. @"Logging analytics event for url following %@",
  75. success ? @"succeeded" : @"failed");
  76. }];
  77. [self.analyticsEventLogger
  78. logAnalyticsEventForType:FIRIAMAnalyticsEventTestMessageClick
  79. forCampaignID:_currentMsgBeingDisplayed.renderData.messageID
  80. withCampaignName:_currentMsgBeingDisplayed.renderData.name
  81. eventTimeInMs:nil
  82. completion:^(BOOL success) {
  83. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400039",
  84. @"Logging analytics event for url following %@",
  85. success ? @"succeeded" : @"failed");
  86. }];
  87. } else {
  88. // Logging the impression
  89. [self recordValidImpression:_currentMsgBeingDisplayed.renderData.messageID
  90. withMessageName:_currentMsgBeingDisplayed.renderData.name];
  91. [self.analyticsEventLogger
  92. logAnalyticsEventForType:FIRIAMAnalyticsEventActionURLFollow
  93. forCampaignID:_currentMsgBeingDisplayed.renderData.messageID
  94. withCampaignName:_currentMsgBeingDisplayed.renderData.name
  95. eventTimeInMs:nil
  96. completion:^(BOOL success) {
  97. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400032",
  98. @"Logging analytics event for url following %@",
  99. success ? @"succeeded" : @"failed");
  100. }];
  101. }
  102. NSURL *actionURL = action.actionURL;
  103. if (!actionURL) {
  104. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400033",
  105. @"messageClicked called but "
  106. "there is no action url specified in the message data.");
  107. // it's equivalent to closing the message with no further action
  108. return;
  109. } else {
  110. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400037", @"Following action url %@",
  111. actionURL.absoluteString);
  112. @try {
  113. [self.actionURLFollower
  114. followActionURL:actionURL
  115. withCompletionBlock:^(BOOL success) {
  116. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400034",
  117. @"Seeing %@ from following action URL", success ? @"success" : @"error");
  118. }];
  119. } @catch (NSException *e) {
  120. FIRLogWarning(kFIRLoggerInAppMessaging, @"I-IAM400035",
  121. @"Exception encountered in following "
  122. "action url (%@): %@ ",
  123. actionURL, e.description);
  124. @throw;
  125. }
  126. }
  127. }
  128. - (void)messageDismissed:(FIRInAppMessagingDisplayMessage *)inAppMessage
  129. dismissType:(FIRInAppMessagingDismissType)dismissType {
  130. // Call through to app-side delegate.
  131. __weak id<FIRInAppMessagingDisplayDelegate> appSideDelegate = self.inAppMessaging.delegate;
  132. if ([appSideDelegate respondsToSelector:@selector(messageDismissed:dismissType:)]) {
  133. [appSideDelegate messageDismissed:inAppMessage dismissType:dismissType];
  134. }
  135. self.isMsgBeingDisplayed = NO;
  136. if (!_currentMsgBeingDisplayed.renderData.messageID) {
  137. FIRLogWarning(kFIRLoggerInAppMessaging, @"I-IAM400014",
  138. @"messageDismissedWithType called but "
  139. "there is no current message ID.");
  140. return;
  141. }
  142. if (_currentMsgBeingDisplayed.isTestMessage) {
  143. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400020",
  144. @"A test message dismissed. Record the impression event.");
  145. [self.analyticsEventLogger
  146. logAnalyticsEventForType:FIRIAMAnalyticsEventTestMessageImpression
  147. forCampaignID:_currentMsgBeingDisplayed.renderData.messageID
  148. withCampaignName:_currentMsgBeingDisplayed.renderData.name
  149. eventTimeInMs:nil
  150. completion:^(BOOL success) {
  151. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400038",
  152. @"Logging analytics event for url following %@",
  153. success ? @"succeeded" : @"failed");
  154. }];
  155. return;
  156. }
  157. // Logging the impression
  158. [self recordValidImpression:_currentMsgBeingDisplayed.renderData.messageID
  159. withMessageName:_currentMsgBeingDisplayed.renderData.name];
  160. FIRIAMAnalyticsLogEventType logEventType = dismissType == FIRInAppMessagingDismissTypeAuto
  161. ? FIRIAMAnalyticsEventMessageDismissAuto
  162. : FIRIAMAnalyticsEventMessageDismissClick;
  163. [self.analyticsEventLogger
  164. logAnalyticsEventForType:logEventType
  165. forCampaignID:_currentMsgBeingDisplayed.renderData.messageID
  166. withCampaignName:_currentMsgBeingDisplayed.renderData.name
  167. eventTimeInMs:nil
  168. completion:^(BOOL success) {
  169. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400004",
  170. @"Logging analytics event for message dismiss %@",
  171. success ? @"succeeded" : @"failed");
  172. }];
  173. }
  174. - (void)impressionDetectedForMessage:(FIRInAppMessagingDisplayMessage *)inAppMessage {
  175. __weak id<FIRInAppMessagingDisplayDelegate> appSideDelegate = self.inAppMessaging.delegate;
  176. if ([appSideDelegate respondsToSelector:@selector(impressionDetectedForMessage:)]) {
  177. [appSideDelegate impressionDetectedForMessage:inAppMessage];
  178. }
  179. if (!_currentMsgBeingDisplayed.renderData.messageID) {
  180. FIRLogWarning(kFIRLoggerInAppMessaging, @"I-IAM400022",
  181. @"impressionDetected called but "
  182. "there is no current message ID.");
  183. return;
  184. }
  185. if (!_currentMsgBeingDisplayed.isTestMessage) {
  186. // Displayed long enough to be a valid impression.
  187. [self recordValidImpression:_currentMsgBeingDisplayed.renderData.messageID
  188. withMessageName:_currentMsgBeingDisplayed.renderData.name];
  189. } else {
  190. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400011",
  191. @"A test message. Record the test message impression event.");
  192. return;
  193. }
  194. }
  195. - (void)displayErrorForMessage:(FIRInAppMessagingDisplayMessage *)inAppMessage
  196. error:(NSError *)error {
  197. __weak id<FIRInAppMessagingDisplayDelegate> appSideDelegate = self.inAppMessaging.delegate;
  198. if ([appSideDelegate respondsToSelector:@selector(displayErrorForMessage:error:)]) {
  199. [appSideDelegate displayErrorForMessage:inAppMessage error:error];
  200. }
  201. self.isMsgBeingDisplayed = NO;
  202. if (!_currentMsgBeingDisplayed.renderData.messageID) {
  203. FIRLogWarning(kFIRLoggerInAppMessaging, @"I-IAM400017",
  204. @"displayErrorEncountered called but "
  205. "there is no current message ID.");
  206. return;
  207. }
  208. NSString *messageID = _currentMsgBeingDisplayed.renderData.messageID;
  209. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400009",
  210. @"Display ran into error for message %@: %@", messageID, error);
  211. if (_currentMsgBeingDisplayed.isTestMessage) {
  212. [self displayMessageLoadError:error];
  213. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400012",
  214. @"A test message. No analytics tracking "
  215. "from image data loading failure");
  216. return;
  217. }
  218. // we remove the message from the client side cache so that it won't be retried until next time
  219. // it's fetched again from server.
  220. [self.messageCache removeMessageWithId:messageID];
  221. NSString *messageName = _currentMsgBeingDisplayed.renderData.name;
  222. if ([error.domain isEqualToString:NSURLErrorDomain]) {
  223. [self.analyticsEventLogger
  224. logAnalyticsEventForType:FIRIAMAnalyticsEventImageFetchError
  225. forCampaignID:messageID
  226. withCampaignName:messageName
  227. eventTimeInMs:nil
  228. completion:^(BOOL success) {
  229. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400010",
  230. @"Logging analytics event for image fetch error %@",
  231. success ? @"succeeded" : @"failed");
  232. }];
  233. } else if (error.code == FIRIAMSDKRuntimeErrorNonImageMimetypeFromImageURL) {
  234. [self.analyticsEventLogger
  235. logAnalyticsEventForType:FIRIAMAnalyticsEventImageFormatUnsupported
  236. forCampaignID:messageID
  237. withCampaignName:messageName
  238. eventTimeInMs:nil
  239. completion:^(BOOL success) {
  240. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400013",
  241. @"Logging analytics event for image format error %@",
  242. success ? @"succeeded" : @"failed");
  243. }];
  244. }
  245. }
  246. - (void)recordValidImpression:(NSString *)messageID withMessageName:(NSString *)messageName {
  247. if (!self.impressionRecorded) {
  248. [self.displayBookKeeper recordNewImpressionForMessage:messageID
  249. withStartTimestampInSeconds:self.lastDisplayTime];
  250. self.impressionRecorded = YES;
  251. [self.messageCache removeMessageWithId:messageID];
  252. // Log an impression analytics event as well.
  253. [self.analyticsEventLogger
  254. logAnalyticsEventForType:FIRIAMAnalyticsEventMessageImpression
  255. forCampaignID:messageID
  256. withCampaignName:messageName
  257. eventTimeInMs:nil
  258. completion:^(BOOL success) {
  259. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400007",
  260. @"Logging analytics event for impression %@",
  261. success ? @"succeeded" : @"failed");
  262. }];
  263. }
  264. }
  265. - (void)displayMessageLoadError:(NSError *)error {
  266. NSString *errorMsg = error.userInfo[NSLocalizedDescriptionKey]
  267. ? error.userInfo[NSLocalizedDescriptionKey]
  268. : @"Message loading failed";
  269. UIAlertController *alert = [UIAlertController
  270. alertControllerWithTitle:@"Firebase InAppMessaging fail to load a test message"
  271. message:errorMsg
  272. preferredStyle:UIAlertControllerStyleAlert];
  273. UIAlertAction *defaultAction = [UIAlertAction actionWithTitle:@"OK"
  274. style:UIAlertActionStyleDefault
  275. handler:^(UIAlertAction *action){
  276. }];
  277. [alert addAction:defaultAction];
  278. [[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:alert
  279. animated:YES
  280. completion:nil];
  281. }
  282. - (instancetype)initWithInAppMessaging:(FIRInAppMessaging *)inAppMessaging
  283. setting:(FIRIAMDisplaySetting *)setting
  284. messageCache:(FIRIAMMessageClientCache *)cache
  285. timeFetcher:(id<FIRIAMTimeFetcher>)timeFetcher
  286. bookKeeper:(id<FIRIAMBookKeeper>)displayBookKeeper
  287. actionURLFollower:(FIRIAMActionURLFollower *)actionURLFollower
  288. activityLogger:(FIRIAMActivityLogger *)activityLogger
  289. analyticsEventLogger:(id<FIRIAMAnalyticsEventLogger>)analyticsEventLogger {
  290. if (self = [super init]) {
  291. _inAppMessaging = inAppMessaging;
  292. _timeFetcher = timeFetcher;
  293. _lastDisplayTime = displayBookKeeper.lastDisplayTime;
  294. _setting = setting;
  295. _messageCache = cache;
  296. _displayBookKeeper = displayBookKeeper;
  297. _isMsgBeingDisplayed = NO;
  298. _analyticsEventLogger = analyticsEventLogger;
  299. _actionURLFollower = actionURLFollower;
  300. _suppressMessageDisplay = NO; // always allow message display on startup
  301. }
  302. return self;
  303. }
  304. - (void)checkAndDisplayNextContextualMessageForAnalyticsEvent:(NSString *)eventName {
  305. // synchronizing on self so that we won't potentially enter the render flow from two
  306. // threads: example like showing analytics triggered message and a regular app open
  307. // triggered message
  308. @synchronized(self) {
  309. if (self.suppressMessageDisplay) {
  310. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400015",
  311. @"Message display is being suppressed. No contextual message rendering.");
  312. return;
  313. }
  314. if (!self.messageDisplayComponent) {
  315. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400026",
  316. @"Message display component is not present yet. No display should happen.");
  317. return;
  318. }
  319. if (self.isMsgBeingDisplayed) {
  320. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400008",
  321. @"An in-app message display is in progress, do not check analytics event "
  322. "based message for now.");
  323. return;
  324. }
  325. // Pop up next analytics event based message to be displayed
  326. FIRIAMMessageDefinition *nextAnalyticsBasedMessage =
  327. [self.messageCache nextOnFirebaseAnalyticEventDisplayMsg:eventName];
  328. if (nextAnalyticsBasedMessage) {
  329. [self displayForMessage:nextAnalyticsBasedMessage
  330. triggerType:FIRInAppMessagingDisplayTriggerTypeOnAnalyticsEvent];
  331. }
  332. }
  333. }
  334. - (FIRInAppMessagingCardDisplay *)
  335. cardDisplayMessageWithMessageDefinition:(FIRIAMMessageDefinition *)definition
  336. portraitImageData:(FIRInAppMessagingImageData *)portraitImageData
  337. landscapeImageData:
  338. (nullable FIRInAppMessagingImageData *)landscapeImageData
  339. triggerType:(FIRInAppMessagingDisplayTriggerType)triggerType {
  340. // For easier reference in this method.
  341. FIRIAMMessageRenderData *renderData = definition.renderData;
  342. NSString *title = renderData.contentData.titleText;
  343. NSString *body = renderData.contentData.bodyText;
  344. FIRInAppMessagingActionButton *primaryActionButton = nil;
  345. if (definition.renderData.contentData.actionButtonText) {
  346. primaryActionButton = [[FIRInAppMessagingActionButton alloc]
  347. initWithButtonText:renderData.contentData.actionButtonText
  348. buttonTextColor:renderData.renderingEffectSettings.btnTextColor
  349. backgroundColor:renderData.renderingEffectSettings.btnBGColor];
  350. }
  351. FIRInAppMessagingActionButton *secondaryActionButton = nil;
  352. if (definition.renderData.contentData.secondaryActionButtonText) {
  353. secondaryActionButton = [[FIRInAppMessagingActionButton alloc]
  354. initWithButtonText:renderData.contentData.secondaryActionButtonText
  355. buttonTextColor:renderData.renderingEffectSettings.secondaryActionBtnTextColor
  356. backgroundColor:renderData.renderingEffectSettings.btnBGColor];
  357. }
  358. FIRInAppMessagingCardDisplay *cardMessage = [[FIRInAppMessagingCardDisplay alloc]
  359. initWithMessageID:renderData.messageID
  360. campaignName:renderData.name
  361. renderAsTestMessage:definition.isTestMessage
  362. triggerType:triggerType
  363. titleText:title
  364. textColor:renderData.renderingEffectSettings.textColor
  365. portraitImageData:portraitImageData
  366. backgroundColor:renderData.renderingEffectSettings.displayBGColor
  367. primaryActionButton:primaryActionButton
  368. primaryActionURL:definition.renderData.contentData.actionURL];
  369. cardMessage.body = body;
  370. cardMessage.landscapeImageData = landscapeImageData;
  371. cardMessage.secondaryActionButton = secondaryActionButton;
  372. cardMessage.secondaryActionURL = definition.renderData.contentData.secondaryActionURL;
  373. return cardMessage;
  374. }
  375. - (FIRInAppMessagingBannerDisplay *)
  376. bannerDisplayMessageWithMessageDefinition:(FIRIAMMessageDefinition *)definition
  377. imageData:(FIRInAppMessagingImageData *)imageData
  378. triggerType:(FIRInAppMessagingDisplayTriggerType)triggerType {
  379. NSString *title = definition.renderData.contentData.titleText;
  380. NSString *body = definition.renderData.contentData.bodyText;
  381. FIRInAppMessagingBannerDisplay *bannerMessage = [[FIRInAppMessagingBannerDisplay alloc]
  382. initWithMessageID:definition.renderData.messageID
  383. campaignName:definition.renderData.name
  384. renderAsTestMessage:definition.isTestMessage
  385. triggerType:triggerType
  386. titleText:title
  387. bodyText:body
  388. textColor:definition.renderData.renderingEffectSettings.textColor
  389. backgroundColor:definition.renderData.renderingEffectSettings.displayBGColor
  390. imageData:imageData
  391. actionURL:definition.renderData.contentData.actionURL];
  392. return bannerMessage;
  393. }
  394. - (FIRInAppMessagingImageOnlyDisplay *)
  395. imageOnlyDisplayMessageWithMessageDefinition:(FIRIAMMessageDefinition *)definition
  396. imageData:(FIRInAppMessagingImageData *)imageData
  397. triggerType:(FIRInAppMessagingDisplayTriggerType)triggerType {
  398. FIRInAppMessagingImageOnlyDisplay *imageOnlyMessage = [[FIRInAppMessagingImageOnlyDisplay alloc]
  399. initWithMessageID:definition.renderData.messageID
  400. campaignName:definition.renderData.name
  401. renderAsTestMessage:definition.isTestMessage
  402. triggerType:triggerType
  403. imageData:imageData
  404. actionURL:definition.renderData.contentData.actionURL];
  405. return imageOnlyMessage;
  406. }
  407. - (FIRInAppMessagingModalDisplay *)
  408. modalDisplayMessageWithMessageDefinition:(FIRIAMMessageDefinition *)definition
  409. imageData:(FIRInAppMessagingImageData *)imageData
  410. triggerType:(FIRInAppMessagingDisplayTriggerType)triggerType {
  411. // For easier reference in this method.
  412. FIRIAMMessageRenderData *renderData = definition.renderData;
  413. NSString *title = renderData.contentData.titleText;
  414. NSString *body = renderData.contentData.bodyText;
  415. FIRInAppMessagingActionButton *actionButton = nil;
  416. if (definition.renderData.contentData.actionButtonText) {
  417. actionButton = [[FIRInAppMessagingActionButton alloc]
  418. initWithButtonText:renderData.contentData.actionButtonText
  419. buttonTextColor:renderData.renderingEffectSettings.btnTextColor
  420. backgroundColor:renderData.renderingEffectSettings.btnBGColor];
  421. }
  422. FIRInAppMessagingModalDisplay *modalViewMessage = [[FIRInAppMessagingModalDisplay alloc]
  423. initWithMessageID:definition.renderData.messageID
  424. campaignName:definition.renderData.name
  425. renderAsTestMessage:definition.isTestMessage
  426. triggerType:triggerType
  427. titleText:title
  428. bodyText:body
  429. textColor:renderData.renderingEffectSettings.textColor
  430. backgroundColor:renderData.renderingEffectSettings.displayBGColor
  431. imageData:imageData
  432. actionButton:actionButton
  433. actionURL:definition.renderData.contentData.actionURL];
  434. return modalViewMessage;
  435. }
  436. - (FIRInAppMessagingDisplayMessage *)
  437. displayMessageWithMessageDefinition:(FIRIAMMessageDefinition *)definition
  438. imageData:(FIRInAppMessagingImageData *)imageData
  439. landscapeImageData:(nullable FIRInAppMessagingImageData *)landscapeImageData
  440. triggerType:(FIRInAppMessagingDisplayTriggerType)triggerType {
  441. switch (definition.renderData.renderingEffectSettings.viewMode) {
  442. case FIRIAMRenderAsCardView:
  443. return [self cardDisplayMessageWithMessageDefinition:definition
  444. portraitImageData:imageData
  445. landscapeImageData:landscapeImageData
  446. triggerType:triggerType];
  447. case FIRIAMRenderAsBannerView:
  448. return [self bannerDisplayMessageWithMessageDefinition:definition
  449. imageData:imageData
  450. triggerType:triggerType];
  451. case FIRIAMRenderAsModalView:
  452. return [self modalDisplayMessageWithMessageDefinition:definition
  453. imageData:imageData
  454. triggerType:triggerType];
  455. case FIRIAMRenderAsImageOnlyView:
  456. return [self imageOnlyDisplayMessageWithMessageDefinition:definition
  457. imageData:imageData
  458. triggerType:triggerType];
  459. default:
  460. return nil;
  461. }
  462. }
  463. - (void)displayForMessage:(FIRIAMMessageDefinition *)message
  464. triggerType:(FIRInAppMessagingDisplayTriggerType)triggerType {
  465. _currentMsgBeingDisplayed = message;
  466. [message.renderData.contentData
  467. loadImageDataWithBlock:^(NSData *_Nullable standardImageRawData,
  468. NSData *_Nullable landscapeImageRawData, NSError *_Nullable error) {
  469. FIRInAppMessagingImageData *imageData = nil;
  470. FIRInAppMessagingImageData *landscapeImageData = nil;
  471. if (error) {
  472. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400019",
  473. @"Error in loading image data for the message.");
  474. FIRInAppMessagingDisplayMessage *erroredMessage =
  475. [self displayMessageWithMessageDefinition:message
  476. imageData:imageData
  477. landscapeImageData:landscapeImageData
  478. triggerType:triggerType];
  479. // short-circuit to display error handling
  480. [self displayErrorForMessage:erroredMessage error:error];
  481. return;
  482. } else {
  483. if (standardImageRawData) {
  484. imageData = [[FIRInAppMessagingImageData alloc]
  485. initWithImageURL:message.renderData.contentData.imageURL.absoluteString
  486. imageData:standardImageRawData];
  487. }
  488. if (landscapeImageRawData) {
  489. landscapeImageData = [[FIRInAppMessagingImageData alloc]
  490. initWithImageURL:message.renderData.contentData.landscapeImageURL.absoluteString
  491. imageData:landscapeImageRawData];
  492. }
  493. }
  494. self.impressionRecorded = NO;
  495. self.isMsgBeingDisplayed = YES;
  496. FIRInAppMessagingDisplayMessage *displayMessage =
  497. [self displayMessageWithMessageDefinition:message
  498. imageData:imageData
  499. landscapeImageData:landscapeImageData
  500. triggerType:triggerType];
  501. [self.messageDisplayComponent displayMessage:displayMessage displayDelegate:self];
  502. }];
  503. }
  504. - (BOOL)enoughIntervalFromLastDisplay {
  505. NSTimeInterval intervalFromLastDisplayInSeconds =
  506. [self.timeFetcher currentTimestampInSeconds] - self.lastDisplayTime;
  507. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400005",
  508. @"Interval time from last display is %lf seconds", intervalFromLastDisplayInSeconds);
  509. return intervalFromLastDisplayInSeconds >= self.setting.displayMinIntervalInMinutes * 60.0;
  510. }
  511. - (void)checkAndDisplayNextAppForegroundMessage {
  512. // synchronizing on self so that we won't potentially enter the render flow from two
  513. // threads: example like showing analytics triggered message and a regular app open
  514. // triggered message concurrently
  515. @synchronized(self) {
  516. if (!self.messageDisplayComponent) {
  517. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400027",
  518. @"Message display component is not present yet. No display should happen.");
  519. return;
  520. }
  521. if (self.suppressMessageDisplay) {
  522. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400016",
  523. @"Message display is being suppressed. No regular message rendering.");
  524. return;
  525. }
  526. if (self.isMsgBeingDisplayed) {
  527. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400002",
  528. @"An in-app message display is in progress, do not over-display on top of it.");
  529. return;
  530. }
  531. if ([self.messageCache hasTestMessage] || [self enoughIntervalFromLastDisplay]) {
  532. // We can display test messages anytime or display regular messages when
  533. // the display time interval has been reached
  534. FIRIAMMessageDefinition *nextForegroundMessage = [self.messageCache nextOnAppOpenDisplayMsg];
  535. if (nextForegroundMessage) {
  536. [self displayForMessage:nextForegroundMessage
  537. triggerType:FIRInAppMessagingDisplayTriggerTypeOnAppForeground];
  538. self.lastDisplayTime = [self.timeFetcher currentTimestampInSeconds];
  539. } else {
  540. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400001",
  541. @"No appropriate in-app message detected for display.");
  542. }
  543. } else {
  544. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400003",
  545. @"Minimal display interval of %lf seconds has not been reached yet.",
  546. self.setting.displayMinIntervalInMinutes * 60.0);
  547. }
  548. }
  549. }
  550. @end