Fix issues in media gallery cells; Improve debug galleries.

This commit is contained in:
Matthew Chen 2018-11-06 09:31:52 -05:00
parent 88a1186e4e
commit 60c5a84dd2
12 changed files with 177 additions and 63 deletions

View File

@ -2287,28 +2287,28 @@
B633C4FD1A1D190B0059AC12 /* Images */ = {
isa = PBXGroup;
children = (
34B6A90A218BA1D0007C4606 /* typing-animation.gif */,
AD83FF461A73428300B5C81A /* audio_play_button_blue.png */,
AD83FF381A73426500B5C81A /* audio_pause_button_blue.png */,
AD83FF391A73426500B5C81A /* audio_pause_button_blue@2x.png */,
AD83FF3D1A73426500B5C81A /* audio_pause_button.png */,
AD83FF3E1A73426500B5C81A /* audio_pause_button@2x.png */,
AD83FF461A73428300B5C81A /* audio_play_button_blue.png */,
AD83FF3A1A73426500B5C81A /* audio_play_button_blue@2x.png */,
AD83FF3B1A73426500B5C81A /* audio_play_button.png */,
AD83FF3C1A73426500B5C81A /* audio_play_button@2x.png */,
AD83FF3D1A73426500B5C81A /* audio_pause_button.png */,
AD83FF3E1A73426500B5C81A /* audio_pause_button@2x.png */,
B10C9B5B1A7049EC00ECA2BF /* pause_icon.png */,
B10C9B5C1A7049EC00ECA2BF /* pause_icon@2x.png */,
B10C9B5D1A7049EC00ECA2BF /* play_icon.png */,
B10C9B5E1A7049EC00ECA2BF /* play_icon@2x.png */,
B633C5041A1D190B0059AC12 /* call@2x.png */,
B633C50B1A1D190B0059AC12 /* contact_default_feed.png */,
B633C51B1A1D190B0059AC12 /* endcall@2x.png */,
FC5CDF371A3393DD00B47253 /* error_white@2x.png */,
B633C5411A1D190B0059AC12 /* mute_off@2x.png */,
B633C5421A1D190B0059AC12 /* mute_on@2x.png */,
B10C9B5B1A7049EC00ECA2BF /* pause_icon.png */,
B10C9B5C1A7049EC00ECA2BF /* pause_icon@2x.png */,
B10C9B5D1A7049EC00ECA2BF /* play_icon.png */,
B10C9B5E1A7049EC00ECA2BF /* play_icon@2x.png */,
FC91203F1A39EFB70074545C /* qr@2x.png */,
B633C54C1A1D190B0059AC12 /* quit@2x.png */,
B633C5501A1D190B0059AC12 /* savephoto@2x.png */,
34B6A90A218BA1D0007C4606 /* typing-animation.gif */,
FC5CDF381A3393DD00B47253 /* warning_white@2x.png */,
);
path = Images;

View File

@ -27,6 +27,7 @@
#import "OWSBackup.h"
#import "OWSBackupIO.h"
#import "OWSBezierPathView.h"
#import "OWSBubbleShapeView.h"
#import "OWSBubbleView.h"
#import "OWSCallNotificationsAdaptee.h"
#import "OWSDatabaseMigration.h"

View File

@ -51,6 +51,12 @@ public class ConversationMediaView: UIView {
}
}
private func addMediaSubview(_ subview: UIView) {
addSubview(subview)
subview.autoPinEdgesToSuperviewEdges()
// TODO: Possibly add upload/download indicator here.
}
private func configureForAnimatedImage(attachmentStream: TSAttachmentStream) {
guard let cacheKey = attachmentStream.uniqueId else {
owsFailDebug("Attachment stream missing unique ID.")
@ -65,8 +71,7 @@ public class ConversationMediaView: UIView {
animatedImageView.layer.minificationFilter = kCAFilterTrilinear
animatedImageView.layer.magnificationFilter = kCAFilterTrilinear
animatedImageView.backgroundColor = Theme.offBackgroundColor
addSubview(animatedImageView)
animatedImageView.autoPinEdgesToSuperviewEdges()
addMediaSubview(animatedImageView)
loadBlock = { [weak self] in
guard let strongSelf = self else {
return
@ -112,8 +117,7 @@ public class ConversationMediaView: UIView {
stillImageView.layer.minificationFilter = kCAFilterTrilinear
stillImageView.layer.magnificationFilter = kCAFilterTrilinear
stillImageView.backgroundColor = Theme.offBackgroundColor
addSubview(stillImageView)
stillImageView.autoPinEdgesToSuperviewEdges()
addMediaSubview(stillImageView)
loadBlock = { [weak self] in
guard let strongSelf = self else {
return
@ -158,8 +162,7 @@ public class ConversationMediaView: UIView {
stillImageView.layer.minificationFilter = kCAFilterTrilinear
stillImageView.layer.magnificationFilter = kCAFilterTrilinear
stillImageView.backgroundColor = Theme.offBackgroundColor
addSubview(stillImageView)
stillImageView.autoPinEdgesToSuperviewEdges()
addMediaSubview(stillImageView)
// TODO: Hide during upload/download.
let videoPlayIcon = UIImage(named: "play_button")

View File

@ -7,11 +7,18 @@ import Foundation
@objc(OWSMediaGalleryCellView)
public class MediaGalleryCellView: UIStackView {
private let items: [ConversationMediaGalleryItem]
private let itemViews: [ConversationMediaView]
@objc
public let itemViews: [ConversationMediaView]
private static let kSpacingPts: CGFloat = 2
private static let kMaxItems = 5
@available(*, unavailable, message: "use other init() instead.")
required public init(coder aDecoder: NSCoder) {
notImplemented()
}
@objc
public required init(mediaCache: NSCache<NSString, AnyObject>,
items: [ConversationMediaGalleryItem],
@ -24,7 +31,8 @@ public class MediaGalleryCellView: UIStackView {
super.init(frame: .zero)
backgroundColor = Theme.backgroundColor
// UIStackView's backgroundColor property has no effect.
addBackgroundView(withBackgroundColor: Theme.backgroundColor)
createContents(maxMessageWidth: maxMessageWidth)
}
@ -173,11 +181,6 @@ public class MediaGalleryCellView: UIStackView {
}
}
@available(*, unavailable, message: "use other init() instead.")
required public init(coder aDecoder: NSCoder) {
notImplemented()
}
private class func itemsToDisplay(forItems items: [ConversationMediaGalleryItem]) -> [ConversationMediaGalleryItem] {
// TODO: Unless design changes, we want to display
// items which are still downloading and invalid

View File

@ -23,7 +23,8 @@ typedef NS_ENUM(NSUInteger, OWSBubbleShapeViewMode) {
@property (nonatomic) CAShapeLayer *shapeLayer;
@property (nonatomic) CAShapeLayer *maskLayer;
@property (nonatomic, weak) OWSBubbleView *bubbleView;
@property (nonatomic, nullable, weak) OWSBubbleView *bubbleView;
@property (nonatomic) BOOL isConfigured;
@end
@ -33,7 +34,6 @@ typedef NS_ENUM(NSUInteger, OWSBubbleShapeViewMode) {
- (void)configure
{
self.mode = OWSBubbleShapeViewMode_Draw;
self.opaque = NO;
self.backgroundColor = [UIColor clearColor];
self.layoutMargins = UIEdgeInsetsZero;
@ -42,8 +42,11 @@ typedef NS_ENUM(NSUInteger, OWSBubbleShapeViewMode) {
[self.layer addSublayer:self.shapeLayer];
self.maskLayer = [CAShapeLayer new];
}
self.isConfigured = YES;
[self updateLayers];
}
- (instancetype)initDraw
{
@ -100,7 +103,6 @@ typedef NS_ENUM(NSUInteger, OWSBubbleShapeViewMode) {
_innerShadowOpacity = opacity;
[self configure];
[self updateLayers];
return self;
}
@ -176,15 +178,24 @@ typedef NS_ENUM(NSUInteger, OWSBubbleShapeViewMode) {
[self updateLayers];
}
- (void)setBubbleView:(nullable OWSBubbleView *)bubbleView
{
_bubbleView = bubbleView;
[self updateLayers];
}
- (void)updateLayers
{
if (!self.shapeLayer) {
return;
}
if (!self.bubbleView) {
return;
}
if (!self.isConfigured) {
return;
}
// Prevent the layer from animating changes.
[CATransaction begin];

View File

@ -21,7 +21,7 @@ typedef NS_OPTIONS(NSUInteger, OWSDirectionalRectCorner) {
- (void)updateLayers;
- (void)setBubbleView:(OWSBubbleView *)bubbleView;
- (void)setBubbleView:(nullable OWSBubbleView *)bubbleView;
@end

View File

@ -351,15 +351,6 @@ const UIDataDetectorTypes kOWSAllowedDataDetectorTypes
if (self.hasBodyMediaWithThumbnail) {
[self.stackView addArrangedSubview:bodyMediaView];
OWSBubbleShapeView *innerShadowView = [[OWSBubbleShapeView alloc]
initInnerShadowWithColor:(Theme.isDarkThemeEnabled ? UIColor.ows_whiteColor
: UIColor.ows_blackColor)
radius:0.5f
opacity:0.15f];
[bodyMediaView addSubview:innerShadowView];
[self.bubbleView addPartnerView:innerShadowView];
[self.viewConstraints addObjectsFromArray:[innerShadowView ows_autoPinToSuperviewEdges]];
} else {
OWSAssertDebug(self.cellType == OWSMessageCellType_ContactShare);
@ -620,7 +611,8 @@ const UIDataDetectorTypes kOWSAllowedDataDetectorTypes
- (BOOL)hasFullWidthMediaView
{
return (self.hasBodyMediaWithThumbnail || self.cellType == OWSMessageCellType_ContactShare);
return (self.hasBodyMediaWithThumbnail || self.cellType == OWSMessageCellType_ContactShare
|| self.cellType == OWSMessageCellType_MediaGallery);
}
- (BOOL)canFooterOverlayMedia
@ -802,6 +794,14 @@ const UIDataDetectorTypes kOWSAllowedDataDetectorTypes
self.unloadCellContentBlock = ^{
[galleryView unloadMedia];
};
for (UIView *itemView in galleryView.itemViews) {
OWSBubbleShapeView *strokeView = [[OWSBubbleShapeView alloc] initDraw];
strokeView.strokeColor = [UIColor colorWithWhite:0.5f alpha:0.4f];
strokeView.strokeThickness = 1.f;
[itemView addSubview:strokeView];
[self.bubbleView addPartnerView:strokeView];
[self.viewConstraints addObjectsFromArray:[strokeView ows_autoPinToSuperviewEdges]];
}
return galleryView;
}
@ -821,6 +821,14 @@ const UIDataDetectorTypes kOWSAllowedDataDetectorTypes
};
[self addAttachmentUploadViewIfNecessary];
OWSBubbleShapeView *innerShadowView = [[OWSBubbleShapeView alloc]
initInnerShadowWithColor:(Theme.isDarkThemeEnabled ? UIColor.ows_whiteColor : UIColor.ows_blackColor)
radius:0.5f
opacity:0.15f];
[mediaView addSubview:innerShadowView];
[self.bubbleView addPartnerView:innerShadowView];
[self.viewConstraints addObjectsFromArray:[innerShadowView ows_autoPinToSuperviewEdges]];
return mediaView;
}

View File

@ -4645,52 +4645,85 @@ typedef OWSContact * (^OWSContactBlock)(YapDatabaseReadWriteTransaction *transac
const uint32_t kMinImageCount = 2;
const uint32_t kMaxImageCount = 10;
uint32_t imageCount = kMinImageCount + arc4random_uniform(kMaxImageCount - kMinImageCount);
[self sendMediaGalleryInThread:thread imageCount:imageCount];
NSString *_Nullable messageBody = (arc4random_uniform(2) > 0 ? @"This is the media gallery title..." : nil);
[self sendMediaGalleryInThread:thread imageCount:imageCount messageBody:messageBody];
}
+ (void)sendExemplaryMediaGalleriesInThread:(TSThread *)thread
{
OWSLogInfo(@"");
[self sendMediaGalleryInThread:thread imageCount:2];
[self sendMediaGalleryInThread:thread imageCount:3];
[self sendMediaGalleryInThread:thread imageCount:4];
[self sendMediaGalleryInThread:thread imageCount:5];
[self sendMediaGalleryInThread:thread imageCount:6];
[self sendMediaGalleryInThread:thread imageCount:2 messageBody:nil];
[self sendMediaGalleryInThread:thread imageCount:3 messageBody:nil];
[self sendMediaGalleryInThread:thread imageCount:4 messageBody:nil];
[self sendMediaGalleryInThread:thread imageCount:5 messageBody:nil];
[self sendMediaGalleryInThread:thread imageCount:6 messageBody:nil];
[self sendMediaGalleryInThread:thread imageCount:7 messageBody:nil];
NSString *messageBody = @"This is the media gallery title...";
[self sendMediaGalleryInThread:thread imageCount:2 messageBody:messageBody];
[self sendMediaGalleryInThread:thread imageCount:3 messageBody:messageBody];
[self sendMediaGalleryInThread:thread imageCount:4 messageBody:messageBody];
[self sendMediaGalleryInThread:thread imageCount:5 messageBody:messageBody];
[self sendMediaGalleryInThread:thread imageCount:6 messageBody:messageBody];
[self sendMediaGalleryInThread:thread imageCount:7 messageBody:messageBody];
}
+ (void)sendMediaGalleryInThread:(TSThread *)thread imageCount:(uint32_t)imageCount
+ (void)sendMediaGalleryInThread:(TSThread *)thread
imageCount:(uint32_t)imageCount
messageBody:(nullable NSString *)messageBody
fakeAssetLoaders:(NSArray<DebugUIMessagesAssetLoader *> *)fakeAssetLoaders
{
OWSAssertDebug(imageCount > 0);
OWSLogInfo(@"");
NSMutableArray<SignalAttachment *> *attachments = [NSMutableArray new];
for (uint32_t i = 0; i < imageCount; i++) {
UIColor *imageColor = [UIColor colorWithRed:arc4random_uniform(256) / 255.f
green:arc4random_uniform(256) / 255.f
blue:arc4random_uniform(256) / 255.f
alpha:1.f];
UIImage *image = [UIImage imageWithColor:imageColor size:CGSizeMake(10.f, 10.f)];
OWSAssertDebug(image);
NSData *pngData = UIImagePNGRepresentation(image);
OWSAssertDebug(pngData);
NSString *filePath = [OWSFileSystem temporaryFilePathWithFileExtension:@"png"];
[pngData writeToFile:filePath atomically:YES];
OWSAssertDebug([NSFileManager.defaultManager fileExistsAtPath:filePath]);
DataSource *dataSource = [DataSourcePath dataSourceWithFilePath:filePath shouldDeleteOnDeallocation:YES];
SignalAttachment *attachment = [SignalAttachment attachmentWithDataSource:dataSource
dataUTI:(NSString *)kUTTypePNG
imageQuality:TSImageQualityOriginal];
DebugUIMessagesAssetLoader *fakeAssetLoader
= fakeAssetLoaders[arc4random_uniform((uint32_t)fakeAssetLoaders.count)];
OWSAssertDebug([NSFileManager.defaultManager fileExistsAtPath:fakeAssetLoader.filePath]);
DataSource *dataSource =
[DataSourcePath dataSourceWithFilePath:fakeAssetLoader.filePath shouldDeleteOnDeallocation:NO];
SignalAttachment *attachment =
[SignalAttachment attachmentWithDataSource:dataSource
dataUTI:[MIMETypeUtil utiTypeForMIMEType:fakeAssetLoader.mimeType]
imageQuality:TSImageQualityOriginal];
[attachments addObject:attachment];
}
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
TSOutgoingMessage *message =
[ThreadUtil enqueueMessageWithAttachments:attachments inThread:thread quotedReplyModel:nil];
TSOutgoingMessage *message = [ThreadUtil enqueueMessageWithAttachments:attachments
messageBody:messageBody
inThread:thread
quotedReplyModel:nil];
OWSLogError(@"timestamp: %llu.", message.timestamp);
}];
}
+ (void)sendMediaGalleryInThread:(TSThread *)thread
imageCount:(uint32_t)imageCount
messageBody:(nullable NSString *)messageBody
{
OWSAssertDebug(thread);
NSArray<DebugUIMessagesAssetLoader *> *fakeAssetLoaders = @[
[DebugUIMessagesAssetLoader jpegInstance],
[DebugUIMessagesAssetLoader largePngInstance],
[DebugUIMessagesAssetLoader tinyPngInstance],
[DebugUIMessagesAssetLoader gifInstance],
[DebugUIMessagesAssetLoader mp4Instance],
];
[DebugUIMessagesAssetLoader prepareAssetLoaders:fakeAssetLoaders
success:^{
[self sendMediaGalleryInThread:thread
imageCount:imageCount
messageBody:messageBody
fakeAssetLoaders:fakeAssetLoaders];
}
failure:^{
OWSLogError(@"Could not prepare fake asset loaders.");
}];
}
#endif
@end

View File

@ -41,6 +41,12 @@ NS_ASSUME_NONNULL_BEGIN
+ (instancetype)oversizeTextInstance;
+ (instancetype)oversizeTextInstanceWithText:(NSString *)text;
#pragma mark -
+ (void)prepareAssetLoaders:(NSArray<DebugUIMessagesAssetLoader *> *)assetLoaders
success:(dispatch_block_t)success
failure:(dispatch_block_t)failure;
@end
NS_ASSUME_NONNULL_END

View File

@ -5,9 +5,11 @@
#import "DebugUIMessagesAssetLoader.h"
#import <AFNetworking/AFHTTPSessionManager.h>
#import <AFNetworking/AFNetworking.h>
#import <PromiseKit/AnyPromise.h>
#import <SignalCoreKit/Randomness.h>
#import <SignalServiceKit/MIMETypeUtil.h>
#import <SignalServiceKit/OWSFileSystem.h>
#import <SignalServiceKit/SignalServiceKit-Swift.h>
#import <SignalServiceKit/TSAttachment.h>
NS_ASSUME_NONNULL_BEGIN
@ -151,6 +153,9 @@ NS_ASSUME_NONNULL_BEGIN
OWSAssertDebug(label.length > 0);
@autoreleasepool {
imageSize.width /= UIScreen.mainScreen.scale;
imageSize.height /= UIScreen.mainScreen.scale;
CGRect frame = CGRectZero;
frame.size = imageSize;
CGFloat smallDimension = MIN(imageSize.width, imageSize.height);
@ -575,6 +580,49 @@ NS_ASSUME_NONNULL_BEGIN
return instance;
}
#pragma mark -
+ (void)prepareAssetLoaders:(NSArray<DebugUIMessagesAssetLoader *> *)assetLoaders
success:(dispatch_block_t)success
failure:(dispatch_block_t)failure
{
NSMutableArray<AnyPromise *> *promises = [NSMutableArray array];
NSMutableArray<NSError *> *errors = [NSMutableArray array];
for (DebugUIMessagesAssetLoader *assetLoader in assetLoaders) {
// Use chained promises to make the code more readable.
AnyPromise *promise = [AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
assetLoader.prepareBlock(
^{
// The value doesn't matter, we just need any non-NSError value.
resolve(@(1));
},
^{
NSError *error =
[NSError errorWithDomain:@"DebugUI"
code:0
userInfo:@{ NSLocalizedDescriptionKey : @"Could not prepare fake assets." }];
@synchronized(errors) {
[errors addObject:error];
}
resolve(error);
});
}];
[promises addObject:promise];
}
// We could use PMKJoin() or PMKWhen().
[PMKJoin(promises)
.then(^(id value) {
success();
})
.catch(^(id error) {
OWSLogError(@"Could not prepare fake asset loaders: %@.", error);
failure();
}) retainUntilComplete];
}
@end
NS_ASSUME_NONNULL_END

View File

@ -54,6 +54,7 @@ NS_ASSUME_NONNULL_BEGIN
quotedReplyModel:(nullable OWSQuotedReplyModel *)quotedReplyModel;
+ (TSOutgoingMessage *)enqueueMessageWithAttachments:(NSArray<SignalAttachment *> *)attachments
messageBody:(nullable NSString *)messageBody
inThread:(TSThread *)thread
quotedReplyModel:(nullable OWSQuotedReplyModel *)quotedReplyModel;

View File

@ -96,11 +96,13 @@ NS_ASSUME_NONNULL_BEGIN
return [self enqueueMessageWithAttachments:@[
attachment,
]
messageBody:attachment.captionText
inThread:thread
quotedReplyModel:quotedReplyModel];
}
+ (TSOutgoingMessage *)enqueueMessageWithAttachments:(NSArray<SignalAttachment *> *)attachments
messageBody:(nullable NSString *)messageBody
inThread:(TSThread *)thread
quotedReplyModel:(nullable OWSQuotedReplyModel *)quotedReplyModel
{
@ -117,8 +119,6 @@ NS_ASSUME_NONNULL_BEGIN
uint32_t expiresInSeconds = (configuration.isEnabled ? configuration.durationSeconds : 0);
BOOL isVoiceMessage = (attachments.count == 1 && attachments.lastObject.isVoiceMessage);
// TODO: Support multi-image captions.
NSString *_Nullable messageBody = attachments.lastObject.captionText;
TSOutgoingMessage *message =
[[TSOutgoingMessage alloc] initOutgoingMessageWithTimestamp:[NSDate ows_millisecondTimeStamp]
inThread:thread