FIRIAMDisplayExecutor.m 33 KB

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