Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support card template display in FIAM iOS SDK #2947

Merged
merged 67 commits into from May 24, 2019
Merged
Show file tree
Hide file tree
Changes from 66 commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
d4ba006
Add card scene to storyboard, use IBDesignable rounded corners view
christibbs Feb 11, 2019
8ba72d8
Use IBdesignable rounded corners view for modal message
christibbs Feb 19, 2019
ed6db2d
Add card message type
christibbs Feb 19, 2019
cdacb13
Parse card message type, add new fields for secondary actions and lan…
christibbs Feb 19, 2019
ce90659
Display a really basic card message
christibbs Feb 20, 2019
f0d3149
Naive landscape image loading (needs error handling)
christibbs Mar 6, 2019
16c8ce8
Quick bug fix
christibbs Mar 6, 2019
866abe8
Wire up Display to show cards
christibbs Mar 7, 2019
49d2b21
Wire up Display to show cards
christibbs Mar 7, 2019
73189cf
Fix tablet width
christibbs Mar 7, 2019
7f68cf6
Pick right image for orientation
christibbs Mar 11, 2019
5cf57ba
Remove placeholder image sizing constraints at build time
christibbs Mar 19, 2019
6d5de7e
Add more test functionality
christibbs Mar 19, 2019
413189f
Better image sizes
christibbs Mar 19, 2019
306898e
Size message image out
christibbs Mar 21, 2019
18e31d2
Switch to body text view rather than body label
christibbs Mar 21, 2019
4d25033
Merge in master
christibbs Mar 26, 2019
6ca1a68
Make the body text view scrollable. Set correct text color on button.
christibbs Mar 27, 2019
d434545
Hook up buttons and text color
christibbs Mar 27, 2019
b87c0e5
Include action URL in messageClicked callback
christibbs Mar 27, 2019
d39b560
Fix action URL
christibbs Mar 27, 2019
4ffc0bd
Update pod specs
christibbs Mar 27, 2019
5f313f5
Update message clicked delegate to pass an action object, not just th…
christibbs Mar 28, 2019
3a408f1
Merge branch 'master' into fiam-card-display
christibbs Apr 8, 2019
6b19a1e
Change podspec versions back, hook up background color
christibbs Apr 8, 2019
a8df5be
Merge in master
christibbs Apr 16, 2019
04c8ad1
Don't animate scroll to top on scroll view
christibbs Apr 17, 2019
81584b3
Fix text scrolling bug
christibbs Apr 17, 2019
f4723cd
Fix portrait and landscape fetching code. Fix background color bug
christibbs Apr 24, 2019
95ae8be
Merge branch 'master' into fiam-card-display
christibbs Apr 24, 2019
4db3a3d
Flesh out FIRIAMMessageContentDataWithImageURLTests
christibbs Apr 29, 2019
d274e21
Fix up FIRIAMFetchFlowTests
christibbs Apr 29, 2019
3c8f8b3
Fix up FIRIAMMessageClientCacheTests
christibbs Apr 29, 2019
ad34c58
Fix up FIRIAMMessageDisplayForTesting
christibbs Apr 29, 2019
fac48d1
Fix fetching in FIRIAMMessageContentDataWithImageURL
christibbs Apr 29, 2019
121d595
Tests for message parsing
christibbs Apr 30, 2019
0decd64
Merge branch 'master' into fiam-card-display
christibbs Apr 30, 2019
dde5f48
Fix up view controllers for banner, modal, image only. Add a view con…
christibbs May 1, 2019
43ca3b2
Add required vertical compression resistance for primary and secondar…
christibbs May 6, 2019
6d8ee20
Add UI tests for card view controller
christibbs May 6, 2019
669245b
Add back deprecated messageClicked: method, call it if the newer meth…
christibbs May 6, 2019
7224d2e
Run scripts/styles.sh
christibbs May 7, 2019
6f807db
Merge branch 'master' into fiam-card-display
christibbs May 7, 2019
e03ecea
Documentation cleanup
christibbs May 7, 2019
ea331cf
Fix style in FIRIAMMessageContentDataWithImageURLTests.m
christibbs May 8, 2019
f719288
Add missing copyright notices
christibbs May 8, 2019
340dc0b
Merge branch 'master' into fiam-card-display
christibbs May 8, 2019
d248d86
Bump versions of both FIAM SDKS
christibbs May 13, 2019
af666be
Remove default.profraw
christibbs May 14, 2019
997b560
Address some logging punctuation nits. Cancel landscape image load if…
christibbs May 14, 2019
cd5ab98
Add missing error flow in image fetch. A few comment nits.
christibbs May 14, 2019
248c877
Shorten up init method for CardDisplayMessage
christibbs May 14, 2019
b158aca
Documentation updates
christibbs May 14, 2019
508739a
Deintegrate cocoa pods
christibbs May 14, 2019
01cdf45
Include landscape image data in test image load class
christibbs May 14, 2019
64d99d4
Fix Swift naming for InAppMessagingAction in UItests
christibbs May 15, 2019
1483cf6
Remove card message initializer from public API, place into a private…
christibbs May 15, 2019
ad4fbff
Merge branch 'master' into fiam-card-display
christibbs May 16, 2019
71b8817
Mark some properties on the card object as readonly that should've be…
christibbs May 16, 2019
5230541
Bump dependency on InAppMessaging given changes
christibbs May 17, 2019
1aa960a
Merge branch 'master' into fiam-card-display
christibbs May 20, 2019
3d2c22f
Bump versions in podspecs for both SDKs
christibbs May 20, 2019
8263366
Merge branch 'master' into fiam-card-display
christibbs May 23, 2019
8d00b77
Make all view model properties for message subclasses readonly
christibbs May 24, 2019
c26df20
Revert changes to other message subclasses
christibbs May 24, 2019
c863e1e
Make card view model properties readonly
christibbs May 24, 2019
ebcb426
Run styles.sh
christibbs May 24, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
45 changes: 42 additions & 3 deletions Firebase/InAppMessaging/Data/FIRIAMFetchResponseParser.m
Expand Up @@ -178,12 +178,16 @@ - (FIRIAMMessageDefinition *)convertToMessageDefinitionWithMessageDict:(NSDictio

NSDictionary *content = (NSDictionary *)contentNode;
FIRIAMRenderingMode mode;
UIColor *viewCardBackgroundColor, *btnBgColor, *btnTxtColor, *titleTextColor;
UIColor *viewCardBackgroundColor, *btnBgColor, *btnTxtColor, *secondaryBtnTxtColor,
*titleTextColor;
viewCardBackgroundColor = btnBgColor = btnTxtColor = titleTextColor = nil;

NSString *title, *body, *imageURLStr, *actionURLStr, *actionButtonText;
title = body = imageURLStr = actionButtonText = actionURLStr = nil;
NSString *title, *body, *imageURLStr, *landscapeImageURLStr, *actionURLStr,
*secondaryActionURLStr, *actionButtonText, *secondaryActionButtonText;
title = body = imageURLStr = landscapeImageURLStr = actionButtonText =
secondaryActionButtonText = actionURLStr = secondaryActionURLStr = nil;
christibbs marked this conversation as resolved.
Show resolved Hide resolved

// TODO: Refactor this giant if-else block into separate parsing methods per message type.
if ([content[@"banner"] isKindOfClass:[NSDictionary class]]) {
NSDictionary *bannerNode = (NSDictionary *)contentNode[@"banner"];
mode = FIRIAMRenderAsBannerView;
Expand Down Expand Up @@ -227,6 +231,30 @@ - (FIRIAMMessageDefinition *)convertToMessageDefinitionWithMessageDict:(NSDictio
return nil;
}
actionURLStr = imageOnlyNode[@"action"][@"actionUrl"];
} else if ([content[@"card"] isKindOfClass:[NSDictionary class]]) {
christibbs marked this conversation as resolved.
Show resolved Hide resolved
mode = FIRIAMRenderAsCardView;
NSDictionary *cardNode = (NSDictionary *)contentNode[@"card"];
title = cardNode[@"title"][@"text"];
titleTextColor = [UIColor firiam_colorWithHexString:cardNode[@"title"][@"hexColor"]];

body = cardNode[@"body"][@"text"];

imageURLStr = cardNode[@"portraitImageUrl"];
landscapeImageURLStr = cardNode[@"landscapeImageUrl"];

viewCardBackgroundColor = [UIColor firiam_colorWithHexString:cardNode[@"backgroundHexColor"]];

actionButtonText = cardNode[@"primaryActionButton"][@"text"][@"text"];
btnTxtColor = [UIColor
firiam_colorWithHexString:cardNode[@"primaryActionButton"][@"text"][@"hexColor"]];

secondaryActionButtonText = cardNode[@"secondaryActionButton"][@"text"][@"text"];
secondaryBtnTxtColor = [UIColor
firiam_colorWithHexString:cardNode[@"secondaryActionButton"][@"text"][@"hexColor"]];

actionURLStr = cardNode[@"primaryAction"][@"actionUrl"];
secondaryActionURLStr = cardNode[@"secondaryAction"][@"actionUrl"];

} else {
// Unknown message type
FIRLogWarning(kFIRLoggerInAppMessaging, @"I-IAM900003",
Expand All @@ -241,7 +269,11 @@ - (FIRIAMMessageDefinition *)convertToMessageDefinitionWithMessageDict:(NSDictio
}

NSURL *imageURL = (imageURLStr.length == 0) ? nil : [NSURL URLWithString:imageURLStr];
NSURL *landscapeImageURL =
(landscapeImageURLStr.length == 0) ? nil : [NSURL URLWithString:landscapeImageURLStr];
NSURL *actionURL = (actionURLStr.length == 0) ? nil : [NSURL URLWithString:actionURLStr];
NSURL *secondaryActionURL =
(secondaryActionURLStr.length == 0) ? nil : [NSURL URLWithString:secondaryActionURLStr];
FIRIAMRenderingEffectSetting *renderEffect =
[FIRIAMRenderingEffectSetting getDefaultRenderingEffectSetting];
renderEffect.viewMode = mode;
Expand All @@ -258,6 +290,10 @@ - (FIRIAMMessageDefinition *)convertToMessageDefinitionWithMessageDict:(NSDictio
renderEffect.btnTextColor = btnTxtColor;
}

if (secondaryBtnTxtColor) {
renderEffect.secondaryActionBtnTextColor = secondaryBtnTxtColor;
}

if (titleTextColor) {
renderEffect.textColor = titleTextColor;
}
Expand All @@ -284,8 +320,11 @@ - (FIRIAMMessageDefinition *)convertToMessageDefinitionWithMessageDict:(NSDictio
[[FIRIAMMessageContentDataWithImageURL alloc] initWithMessageTitle:title
messageBody:body
actionButtonText:actionButtonText
secondaryActionButtonText:secondaryActionButtonText
actionURL:actionURL
secondaryActionURL:secondaryActionURL
imageURL:imageURL
landscapeImageURL:landscapeImageURL
usingURLSession:nil];

FIRIAMMessageRenderData *renderData =
Expand Down
20 changes: 14 additions & 6 deletions Firebase/InAppMessaging/Data/FIRIAMMessageContentData.h
Expand Up @@ -24,16 +24,24 @@ NS_ASSUME_NONNULL_BEGIN
@property(nonatomic, readonly, nonnull) NSString *titleText;
@property(nonatomic, readonly, nonnull) NSString *bodyText;
@property(nonatomic, readonly, nullable) NSString *actionButtonText;
@property(nonatomic, readonly, nullable) NSString *secondaryActionButtonText;
@property(nonatomic, readonly, nullable) NSURL *actionURL;
@property(nonatomic, readonly, nullable) NSURL *secondaryActionURL;
@property(nonatomic, readonly, nullable) NSURL *imageURL;
@property(nonatomic, readonly, nullable) NSURL *landscapeImageURL;

// Load image data and report the result in the callback block.
// Expect these cases in the callback block
// If error happens, error parameter will be non-nil.
// If no error happens and imageData parameter is nil, it indicates the case that there
// is no image assoicated with the message.
// If error is nil and imageData is not nil, then the image data is loaded successfully
// Load image data, which can potentially have two images (one for landscape display). If only
// one image URL exists, that image is loaded and its data is passed in the callback block.
//
// If both standard and landscape URLs exist, then both images are fetched asynchronously. If the
// standard image fails to load, an error will be returned in the callback block and both image data
// slots will be empty.
// If only the landscape image fails to load, the standard image will be returned in the callback
// block and the error will be nil.
// If no error happens and the imageData parameter is nil, it indicates the case that there is no
// image associated with the message.
- (void)loadImageDataWithBlock:(void (^)(NSData *_Nullable imageData,
NSData *_Nullable landscapeImageData,
NSError *_Nullable error))block;

// convert to a description string of the content
Expand Down
Expand Up @@ -40,8 +40,11 @@ NS_ASSUME_NONNULL_BEGIN
- (instancetype)initWithMessageTitle:(NSString *)title
messageBody:(NSString *)body
actionButtonText:(nullable NSString *)actionButtonText
secondaryActionButtonText:(nullable NSString *)secondaryActionButtonText
actionURL:(nullable NSURL *)actionURL
secondaryActionURL:(nullable NSURL *)secondaryActionURL
imageURL:(nullable NSURL *)imageURL
landscapeImageURL:(nullable NSURL *)landscapeImageURL
usingURLSession:(nullable NSURLSession *)URLSession;
@end
NS_ASSUME_NONNULL_END
155 changes: 111 additions & 44 deletions Firebase/InAppMessaging/Data/FIRIAMMessageContentDataWithImageURL.m
Expand Up @@ -28,24 +28,33 @@ @interface FIRIAMMessageContentDataWithImageURL ()
@property(nonatomic, readwrite, nonnull, copy) NSString *titleText;
@property(nonatomic, readwrite, nonnull, copy) NSString *bodyText;
@property(nonatomic, copy, nullable) NSString *actionButtonText;
@property(nonatomic, copy, nullable) NSString *secondaryActionButtonText;
@property(nonatomic, copy, nullable) NSURL *actionURL;
@property(nonatomic, copy, nullable) NSURL *secondaryActionURL;
@property(nonatomic, nullable, copy) NSURL *imageURL;
@property(nonatomic, nullable, copy) NSURL *landscapeImageURL;
@property(readonly) NSURLSession *URLSession;
@end

@implementation FIRIAMMessageContentDataWithImageURL
- (instancetype)initWithMessageTitle:(NSString *)title
messageBody:(NSString *)body
actionButtonText:(nullable NSString *)actionButtonText
secondaryActionButtonText:(nullable NSString *)secondaryActionButtonText
actionURL:(nullable NSURL *)actionURL
secondaryActionURL:(nullable NSURL *)secondaryActionURL
imageURL:(nullable NSURL *)imageURL
landscapeImageURL:(nullable NSURL *)landscapeImageURL
usingURLSession:(nullable NSURLSession *)URLSession {
if (self = [super init]) {
_titleText = title;
_bodyText = body;
_imageURL = imageURL;
_landscapeImageURL = landscapeImageURL;
_actionButtonText = actionButtonText;
_secondaryActionButtonText = secondaryActionButtonText;
_actionURL = actionURL;
_secondaryActionURL = secondaryActionURL;

if (imageURL) {
_URLSession = URLSession ? URLSession : [NSURLSession sharedSession];
Expand Down Expand Up @@ -74,65 +83,123 @@ - (nullable NSString *)getActionButtonText {
return _actionButtonText;
}

- (void)loadImageDataWithBlock:(void (^)(NSData *_Nullable imageData,
- (void)loadImageDataWithBlock:(void (^)(NSData *_Nullable standardImageData,
NSData *_Nullable landscapeImageData,
NSError *_Nullable error))block {
if (!block) {
// no need for any further action if block is nil
return;
}

if (!_imageURL) {
if (!_imageURL && !_landscapeImageURL) {
// no image data since image url is nil
block(nil, nil);
block(nil, nil, nil);
} else if (!_landscapeImageURL) {
// Only fetch standard image.
[self fetchImageFromURL:_imageURL
withBlock:^(NSData *_Nullable imageData, NSError *_Nullable error) {
block(imageData, nil, error);
}];
} else if (!_imageURL) {
// Only fetch portrait image.
[self fetchImageFromURL:_landscapeImageURL
withBlock:^(NSData *_Nullable imageData, NSError *_Nullable error) {
block(nil, imageData, error);
}];
} else {
NSURLRequest *imageDataRequest = [NSURLRequest requestWithURL:_imageURL];
NSURLSessionDataTask *task = [_URLSession
dataTaskWithRequest:imageDataRequest
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
FIRLogWarning(kFIRLoggerInAppMessaging, @"I-IAM000003",
@"Error in fetching image: %@", error);
block(nil, error);
} else {
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if (httpResponse.statusCode == SuccessHTTPStatusCode) {
if (httpResponse.MIMEType == nil || ![httpResponse.MIMEType hasPrefix:@"image"]) {
NSString *errorDesc =
[NSString stringWithFormat:@"None image MIME type %@"
" detected for url %@",
httpResponse.MIMEType, self.imageURL];
FIRLogWarning(kFIRLoggerInAppMessaging, @"I-IAM000004", @"%@", errorDesc);

NSError *error =
[NSError errorWithDomain:kFirebaseInAppMessagingErrorDomain
code:FIRIAMSDKRuntimeErrorNonImageMimetypeFromImageURL
userInfo:@{NSLocalizedDescriptionKey : errorDesc}];
block(nil, error);
} else {
block(data, nil);
}
} else {
// Fetch both images separately, call completion when they're both fetched.
__block NSData *portrait = nil;
__block NSData *landscape = nil;
__block NSError *landscapeImageLoadError = nil;

[self fetchImageFromURL:_imageURL
withBlock:^(NSData *_Nullable imageData, NSError *_Nullable error) {
__weak FIRIAMMessageContentDataWithImageURL *weakSelf = self;

// If the portrait image fails to load, we treat this as a failure.
if (error) {
// Cancel landscape image fetch.
[weakSelf.URLSession invalidateAndCancel];

block(nil, nil, error);
christibbs marked this conversation as resolved.
Show resolved Hide resolved
return;
}

portrait = imageData;
if (landscape || landscapeImageLoadError) {
christibbs marked this conversation as resolved.
Show resolved Hide resolved
block(portrait, landscape, nil);
}
}];

[self fetchImageFromURL:_landscapeImageURL
withBlock:^(NSData *_Nullable imageData, NSError *_Nullable error) {
if (error) {
landscapeImageLoadError = error;
} else {
landscape = imageData;
}

if (portrait) {
block(portrait, landscape, nil);
}
}];
}
}

- (void)fetchImageFromURL:(NSURL *)imageURL
withBlock:(void (^)(NSData *_Nullable imageData, NSError *_Nullable error))block {
NSURLRequest *imageDataRequest = [NSURLRequest requestWithURL:imageURL];
NSURLSessionDataTask *task = [_URLSession
dataTaskWithRequest:imageDataRequest
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
FIRLogWarning(kFIRLoggerInAppMessaging, @"I-IAM000003", @"Error in fetching image: %@",
error);
block(nil, error);
} else {
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if (httpResponse.statusCode == SuccessHTTPStatusCode) {
if (httpResponse.MIMEType == nil || ![httpResponse.MIMEType hasPrefix:@"image"]) {
NSString *errorDesc =
[NSString stringWithFormat:@"Failed HTTP request to crawl image %@: "
"HTTP status code as %ld",
self->_imageURL, (long)httpResponse.statusCode];
FIRLogWarning(kFIRLoggerInAppMessaging, @"I-IAM000001", @"%@", errorDesc);
[NSString stringWithFormat:@"No image MIME type %@"
" detected for URL %@",
httpResponse.MIMEType, self.imageURL];
FIRLogWarning(kFIRLoggerInAppMessaging, @"I-IAM000004", @"%@", errorDesc);

NSError *error =
[NSError errorWithDomain:NSURLErrorDomain
code:httpResponse.statusCode
[NSError errorWithDomain:kFirebaseInAppMessagingErrorDomain
code:FIRIAMSDKRuntimeErrorNonImageMimetypeFromImageURL
userInfo:@{NSLocalizedDescriptionKey : errorDesc}];
block(nil, error);
} else {
block(data, nil);
}
} else {
FIRLogWarning(kFIRLoggerInAppMessaging, @"I-IAM000002",
@"Internal error: got a non http response from fetching image for "
@"image url as %@",
self->_imageURL);
NSString *errorDesc =
[NSString stringWithFormat:@"Failed HTTP request to crawl image %@: "
"HTTP status code as %ld",
self->_imageURL, (long)httpResponse.statusCode];
FIRLogWarning(kFIRLoggerInAppMessaging, @"I-IAM000001", @"%@", errorDesc);
NSError *error = [NSError errorWithDomain:NSURLErrorDomain
code:httpResponse.statusCode
userInfo:@{NSLocalizedDescriptionKey : errorDesc}];
block(nil, error);
}
} else {
christibbs marked this conversation as resolved.
Show resolved Hide resolved
NSString *errorDesc =
[NSString stringWithFormat:@"Internal error: got a non HTTP response from "
@"fetching image for image URL as %@",
imageURL];
FIRLogWarning(kFIRLoggerInAppMessaging, @"I-IAM000002", @"%@", errorDesc);
NSError *error = [NSError errorWithDomain:NSURLErrorDomain
code:FIRIAMSDKRuntimeErrorNonHTTPResponseForImage
userInfo:@{NSLocalizedDescriptionKey : errorDesc}];
block(nil, error);
}
}];
[task resume];
}
}
}];
[task resume];
}

@end
6 changes: 5 additions & 1 deletion Firebase/InAppMessaging/Data/FIRIAMRenderingEffectSetting.h
Expand Up @@ -22,7 +22,8 @@ NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSInteger, FIRIAMRenderingMode) {
FIRIAMRenderAsBannerView,
FIRIAMRenderAsModalView,
FIRIAMRenderAsImageOnlyView
FIRIAMRenderAsImageOnlyView,
FIRIAMRenderAsCardView
};

/**
Expand All @@ -42,6 +43,9 @@ typedef NS_ENUM(NSInteger, FIRIAMRenderingMode) {
// text color for action button
@property(nonatomic, copy) UIColor *btnTextColor;

// text color for secondary action button
@property(nonatomic, copy) UIColor *secondaryActionBtnTextColor;

// background color for action button
@property(nonatomic, copy) UIColor *btnBGColor;

Expand Down