FIRIAMDisplayExecutor.m 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693
  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. #pragma clang diagnostic push
  347. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  348. primaryActionButton = [[FIRInAppMessagingActionButton alloc]
  349. initWithButtonText:renderData.contentData.actionButtonText
  350. buttonTextColor:renderData.renderingEffectSettings.btnTextColor
  351. backgroundColor:renderData.renderingEffectSettings.btnBGColor];
  352. #pragma clang diagnostic pop
  353. }
  354. FIRInAppMessagingActionButton *secondaryActionButton = nil;
  355. if (definition.renderData.contentData.secondaryActionButtonText) {
  356. #pragma clang diagnostic push
  357. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  358. secondaryActionButton = [[FIRInAppMessagingActionButton alloc]
  359. initWithButtonText:renderData.contentData.secondaryActionButtonText
  360. buttonTextColor:renderData.renderingEffectSettings.secondaryActionBtnTextColor
  361. backgroundColor:renderData.renderingEffectSettings.btnBGColor];
  362. #pragma clang diagnostic pop
  363. }
  364. FIRInAppMessagingCardDisplay *cardMessage = [[FIRInAppMessagingCardDisplay alloc]
  365. initWithMessageID:renderData.messageID
  366. campaignName:renderData.name
  367. renderAsTestMessage:definition.isTestMessage
  368. triggerType:triggerType
  369. titleText:title
  370. textColor:renderData.renderingEffectSettings.textColor
  371. portraitImageData:portraitImageData
  372. backgroundColor:renderData.renderingEffectSettings.displayBGColor
  373. primaryActionButton:primaryActionButton
  374. primaryActionURL:definition.renderData.contentData.actionURL];
  375. cardMessage.body = body;
  376. cardMessage.landscapeImageData = landscapeImageData;
  377. cardMessage.secondaryActionButton = secondaryActionButton;
  378. cardMessage.secondaryActionURL = definition.renderData.contentData.secondaryActionURL;
  379. return cardMessage;
  380. }
  381. - (FIRInAppMessagingBannerDisplay *)
  382. bannerDisplayMessageWithMessageDefinition:(FIRIAMMessageDefinition *)definition
  383. imageData:(FIRInAppMessagingImageData *)imageData
  384. triggerType:(FIRInAppMessagingDisplayTriggerType)triggerType {
  385. NSString *title = definition.renderData.contentData.titleText;
  386. NSString *body = definition.renderData.contentData.bodyText;
  387. #pragma clang diagnostic push
  388. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  389. FIRInAppMessagingBannerDisplay *bannerMessage = [[FIRInAppMessagingBannerDisplay alloc]
  390. initWithMessageID:definition.renderData.messageID
  391. campaignName:definition.renderData.name
  392. renderAsTestMessage:definition.isTestMessage
  393. triggerType:triggerType
  394. titleText:title
  395. bodyText:body
  396. textColor:definition.renderData.renderingEffectSettings.textColor
  397. backgroundColor:definition.renderData.renderingEffectSettings.displayBGColor
  398. imageData:imageData
  399. actionURL:definition.renderData.contentData.actionURL];
  400. #pragma clang diagnostic pop
  401. return bannerMessage;
  402. }
  403. - (FIRInAppMessagingImageOnlyDisplay *)
  404. imageOnlyDisplayMessageWithMessageDefinition:(FIRIAMMessageDefinition *)definition
  405. imageData:(FIRInAppMessagingImageData *)imageData
  406. triggerType:(FIRInAppMessagingDisplayTriggerType)triggerType {
  407. #pragma clang diagnostic push
  408. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  409. FIRInAppMessagingImageOnlyDisplay *imageOnlyMessage = [[FIRInAppMessagingImageOnlyDisplay alloc]
  410. initWithMessageID:definition.renderData.messageID
  411. campaignName:definition.renderData.name
  412. renderAsTestMessage:definition.isTestMessage
  413. triggerType:triggerType
  414. imageData:imageData
  415. actionURL:definition.renderData.contentData.actionURL];
  416. #pragma clang diagnostic pop
  417. return imageOnlyMessage;
  418. }
  419. - (FIRInAppMessagingModalDisplay *)
  420. modalDisplayMessageWithMessageDefinition:(FIRIAMMessageDefinition *)definition
  421. imageData:(FIRInAppMessagingImageData *)imageData
  422. triggerType:(FIRInAppMessagingDisplayTriggerType)triggerType {
  423. // For easier reference in this method.
  424. FIRIAMMessageRenderData *renderData = definition.renderData;
  425. NSString *title = renderData.contentData.titleText;
  426. NSString *body = renderData.contentData.bodyText;
  427. FIRInAppMessagingActionButton *actionButton = nil;
  428. if (definition.renderData.contentData.actionButtonText) {
  429. #pragma clang diagnostic push
  430. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  431. actionButton = [[FIRInAppMessagingActionButton alloc]
  432. initWithButtonText:renderData.contentData.actionButtonText
  433. buttonTextColor:renderData.renderingEffectSettings.btnTextColor
  434. backgroundColor:renderData.renderingEffectSettings.btnBGColor];
  435. #pragma clang diagnostic pop
  436. }
  437. #pragma clang diagnostic push
  438. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  439. FIRInAppMessagingModalDisplay *modalViewMessage = [[FIRInAppMessagingModalDisplay alloc]
  440. initWithMessageID:definition.renderData.messageID
  441. campaignName:definition.renderData.name
  442. renderAsTestMessage:definition.isTestMessage
  443. triggerType:triggerType
  444. titleText:title
  445. bodyText:body
  446. textColor:renderData.renderingEffectSettings.textColor
  447. backgroundColor:renderData.renderingEffectSettings.displayBGColor
  448. imageData:imageData
  449. actionButton:actionButton
  450. actionURL:definition.renderData.contentData.actionURL];
  451. #pragma clang diagnostic pop
  452. return modalViewMessage;
  453. }
  454. - (FIRInAppMessagingDisplayMessage *)
  455. displayMessageWithMessageDefinition:(FIRIAMMessageDefinition *)definition
  456. imageData:(FIRInAppMessagingImageData *)imageData
  457. landscapeImageData:(nullable FIRInAppMessagingImageData *)landscapeImageData
  458. triggerType:(FIRInAppMessagingDisplayTriggerType)triggerType {
  459. switch (definition.renderData.renderingEffectSettings.viewMode) {
  460. case FIRIAMRenderAsCardView:
  461. return [self cardDisplayMessageWithMessageDefinition:definition
  462. portraitImageData:imageData
  463. landscapeImageData:landscapeImageData
  464. triggerType:triggerType];
  465. case FIRIAMRenderAsBannerView:
  466. return [self bannerDisplayMessageWithMessageDefinition:definition
  467. imageData:imageData
  468. triggerType:triggerType];
  469. case FIRIAMRenderAsModalView:
  470. return [self modalDisplayMessageWithMessageDefinition:definition
  471. imageData:imageData
  472. triggerType:triggerType];
  473. case FIRIAMRenderAsImageOnlyView:
  474. return [self imageOnlyDisplayMessageWithMessageDefinition:definition
  475. imageData:imageData
  476. triggerType:triggerType];
  477. default:
  478. return nil;
  479. }
  480. }
  481. - (void)displayForMessage:(FIRIAMMessageDefinition *)message
  482. triggerType:(FIRInAppMessagingDisplayTriggerType)triggerType {
  483. _currentMsgBeingDisplayed = message;
  484. [message.renderData.contentData
  485. loadImageDataWithBlock:^(NSData *_Nullable standardImageRawData,
  486. NSData *_Nullable landscapeImageRawData, NSError *_Nullable error) {
  487. FIRInAppMessagingImageData *imageData = nil;
  488. FIRInAppMessagingImageData *landscapeImageData = nil;
  489. if (error) {
  490. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400019",
  491. @"Error in loading image data for the message.");
  492. FIRInAppMessagingDisplayMessage *erroredMessage =
  493. [self displayMessageWithMessageDefinition:message
  494. imageData:imageData
  495. landscapeImageData:landscapeImageData
  496. triggerType:triggerType];
  497. // short-circuit to display error handling
  498. [self displayErrorForMessage:erroredMessage error:error];
  499. return;
  500. } else {
  501. if (standardImageRawData) {
  502. #pragma clang diagnostic push
  503. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  504. imageData = [[FIRInAppMessagingImageData alloc]
  505. initWithImageURL:message.renderData.contentData.imageURL.absoluteString
  506. imageData:standardImageRawData];
  507. #pragma clang diagnostic pop
  508. }
  509. if (landscapeImageRawData) {
  510. #pragma clang diagnostic push
  511. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  512. landscapeImageData = [[FIRInAppMessagingImageData alloc]
  513. initWithImageURL:message.renderData.contentData.landscapeImageURL.absoluteString
  514. imageData:landscapeImageRawData];
  515. #pragma clang diagnostic pop
  516. }
  517. }
  518. self.impressionRecorded = NO;
  519. self.isMsgBeingDisplayed = YES;
  520. FIRInAppMessagingDisplayMessage *displayMessage =
  521. [self displayMessageWithMessageDefinition:message
  522. imageData:imageData
  523. landscapeImageData:landscapeImageData
  524. triggerType:triggerType];
  525. [self.messageDisplayComponent displayMessage:displayMessage displayDelegate:self];
  526. }];
  527. }
  528. - (BOOL)enoughIntervalFromLastDisplay {
  529. NSTimeInterval intervalFromLastDisplayInSeconds =
  530. [self.timeFetcher currentTimestampInSeconds] - self.lastDisplayTime;
  531. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400005",
  532. @"Interval time from last display is %lf seconds", intervalFromLastDisplayInSeconds);
  533. return intervalFromLastDisplayInSeconds >= self.setting.displayMinIntervalInMinutes * 60.0;
  534. }
  535. - (void)checkAndDisplayNextAppLaunchMessage {
  536. // synchronizing on self so that we won't potentially enter the render flow from two
  537. // threads.
  538. @synchronized(self) {
  539. if (!self.messageDisplayComponent) {
  540. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400028",
  541. @"Message display component is not present yet. No display should happen.");
  542. return;
  543. }
  544. if (self.suppressMessageDisplay) {
  545. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400029",
  546. @"Message display is being suppressed. No regular message rendering.");
  547. return;
  548. }
  549. if (self.isMsgBeingDisplayed) {
  550. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400030",
  551. @"An in-app message display is in progress, do not over-display on top of it.");
  552. return;
  553. }
  554. if ([self.messageCache hasTestMessage] || [self enoughIntervalFromLastDisplay]) {
  555. // We can display test messages anytime or display regular messages when
  556. // the display time interval has been reached
  557. FIRIAMMessageDefinition *nextAppLaunchMessage = [self.messageCache nextOnAppLaunchDisplayMsg];
  558. if (nextAppLaunchMessage) {
  559. [self displayForMessage:nextAppLaunchMessage
  560. triggerType:FIRInAppMessagingDisplayTriggerTypeOnAnalyticsEvent];
  561. self.lastDisplayTime = [self.timeFetcher currentTimestampInSeconds];
  562. } else {
  563. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400040",
  564. @"No appropriate in-app message detected for display.");
  565. }
  566. } else {
  567. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400041",
  568. @"Minimal display interval of %lf seconds has not been reached yet.",
  569. self.setting.displayMinIntervalInMinutes * 60.0);
  570. }
  571. }
  572. }
  573. - (void)checkAndDisplayNextAppForegroundMessage {
  574. // synchronizing on self so that we won't potentially enter the render flow from two
  575. // threads: example like showing analytics triggered message and a regular app open
  576. // triggered message concurrently
  577. @synchronized(self) {
  578. if (!self.messageDisplayComponent) {
  579. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400027",
  580. @"Message display component is not present yet. No display should happen.");
  581. return;
  582. }
  583. if (self.suppressMessageDisplay) {
  584. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400016",
  585. @"Message display is being suppressed. No regular message rendering.");
  586. return;
  587. }
  588. if (self.isMsgBeingDisplayed) {
  589. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400002",
  590. @"An in-app message display is in progress, do not over-display on top of it.");
  591. return;
  592. }
  593. if ([self.messageCache hasTestMessage] || [self enoughIntervalFromLastDisplay]) {
  594. // We can display test messages anytime or display regular messages when
  595. // the display time interval has been reached
  596. FIRIAMMessageDefinition *nextForegroundMessage = [self.messageCache nextOnAppOpenDisplayMsg];
  597. if (nextForegroundMessage) {
  598. [self displayForMessage:nextForegroundMessage
  599. triggerType:FIRInAppMessagingDisplayTriggerTypeOnAppForeground];
  600. self.lastDisplayTime = [self.timeFetcher currentTimestampInSeconds];
  601. } else {
  602. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400001",
  603. @"No appropriate in-app message detected for display.");
  604. }
  605. } else {
  606. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM400003",
  607. @"Minimal display interval of %lf seconds has not been reached yet.",
  608. self.setting.displayMinIntervalInMinutes * 60.0);
  609. }
  610. }
  611. }
  612. @end