FIRIAMDisplayExecutor.m 33 KB

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