Proyectos de Subversion Iphone Microlearning

Rev

| Ultima modificación | Ver Log |

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