diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index d71b97d6e..683b6a581 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -12,6 +12,8 @@ 34330A5A1E7875FB00DF2FB9 /* fontawesome-webfont.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 34330A591E7875FB00DF2FB9 /* fontawesome-webfont.ttf */; }; 34330A5C1E787A9800DF2FB9 /* dripicons-v2.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 34330A5B1E787A9800DF2FB9 /* dripicons-v2.ttf */; }; 34330A5E1E787BD800DF2FB9 /* ElegantIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 34330A5D1E787BD800DF2FB9 /* ElegantIcons.ttf */; }; + 34330A611E788EA900DF2FB9 /* AttachmentUploadView.m in Sources */ = {isa = PBXBuildFile; fileRef = 34330A601E788EA900DF2FB9 /* AttachmentUploadView.m */; }; + 34330AA31E79686200DF2FB9 /* OWSProgressView.m in Sources */ = {isa = PBXBuildFile; fileRef = 34330AA21E79686200DF2FB9 /* OWSProgressView.m */; }; 344F2F671E57A932000D9322 /* UIViewController+OWS.m in Sources */ = {isa = PBXBuildFile; fileRef = 344F2F661E57A932000D9322 /* UIViewController+OWS.m */; }; 34535D821E256BE9008A4747 /* UIView+OWS.m in Sources */ = {isa = PBXBuildFile; fileRef = 34535D811E256BE9008A4747 /* UIView+OWS.m */; }; 348A08421E6A044E0057E290 /* MessagesViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 348A08411E6A044E0057E290 /* MessagesViewController.xib */; }; @@ -631,6 +633,10 @@ 34330A591E7875FB00DF2FB9 /* fontawesome-webfont.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "fontawesome-webfont.ttf"; sourceTree = ""; }; 34330A5B1E787A9800DF2FB9 /* dripicons-v2.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "dripicons-v2.ttf"; sourceTree = ""; }; 34330A5D1E787BD800DF2FB9 /* ElegantIcons.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = ElegantIcons.ttf; sourceTree = ""; }; + 34330A5F1E788EA900DF2FB9 /* AttachmentUploadView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AttachmentUploadView.h; sourceTree = ""; }; + 34330A601E788EA900DF2FB9 /* AttachmentUploadView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AttachmentUploadView.m; sourceTree = ""; }; + 34330AA11E79686200DF2FB9 /* OWSProgressView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSProgressView.h; sourceTree = ""; }; + 34330AA21E79686200DF2FB9 /* OWSProgressView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSProgressView.m; sourceTree = ""; }; 344F2F651E57A932000D9322 /* UIViewController+OWS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIViewController+OWS.h"; path = "util/UIViewController+OWS.h"; sourceTree = ""; }; 344F2F661E57A932000D9322 /* UIViewController+OWS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIViewController+OWS.m"; path = "util/UIViewController+OWS.m"; sourceTree = ""; }; 34535D801E256BE9008A4747 /* UIView+OWS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIView+OWS.h"; sourceTree = ""; }; @@ -2105,38 +2111,40 @@ 76EB052B18170B33006006FC /* Views */ = { isa = PBXGroup; children = ( + 45F3AEB51DFDE7900080CE33 /* AvatarImageView.swift */, + 451764291DE939FD00EDB8B9 /* ContactCell.swift */, + 451764281DE939FD00EDB8B9 /* ContactCell.xib */, + 76EB052E18170B33006006FC /* ContactTableViewCell.h */, + 76EB052F18170B33006006FC /* ContactTableViewCell.m */, + A5509ECB1A69B1D600ABA4BC /* CountryCodeTableViewCell.h */, + A5509ECC1A69B1D600ABA4BC /* CountryCodeTableViewCell.m */, 45B201751DAECBFE00C461E0 /* HighlightableLabel.swift */, - 45C681C11D305C9E0050903A /* OWSDisplayedMessageCollectionViewCell.h */, - 45C681C21D305C9E0050903A /* OWSDisplayedMessageCollectionViewCell.m */, - 45C681C31D305C9E0050903A /* OWSDisplayedMessageCollectionViewCell.xib */, + FCAC963D19FEF99A0046DFC5 /* InboxTableViewCell.h */, + FCAC963E19FEF99A0046DFC5 /* InboxTableViewCell.m */, + 4531C9C21DD8E6D800F08304 /* JSQMessagesCollectionViewCell+OWS.h */, + 4531C9C31DD8E6D800F08304 /* JSQMessagesCollectionViewCell+OWS.m */, + 45E1F3A41DEF20A100852CF1 /* NoSignalContactsView.swift */, + 45E1F3A21DEF1DF000852CF1 /* NoSignalContactsView.xib */, 45C681B91D305C080050903A /* OWSCallCollectionViewCell.h */, 45C681BA1D305C080050903A /* OWSCallCollectionViewCell.m */, 45C681C01D305C9E0050903A /* OWSCallCollectionViewCell.xib */, + 459311FA1D75C948008DD4F0 /* OWSDeviceTableViewCell.h */, + 459311FB1D75C948008DD4F0 /* OWSDeviceTableViewCell.m */, + 45C681C11D305C9E0050903A /* OWSDisplayedMessageCollectionViewCell.h */, + 45C681C21D305C9E0050903A /* OWSDisplayedMessageCollectionViewCell.m */, + 45C681C31D305C9E0050903A /* OWSDisplayedMessageCollectionViewCell.xib */, + 450873C91D9D86F4006B54F2 /* OWSExpirableMessageView.h */, + 450873C11D9D5149006B54F2 /* OWSExpirationTimerView.h */, + 450873C21D9D5149006B54F2 /* OWSExpirationTimerView.m */, 450873C51D9D867B006B54F2 /* OWSIncomingMessageCollectionViewCell.h */, 450873C61D9D867B006B54F2 /* OWSIncomingMessageCollectionViewCell.m */, 45F2B1951D9CA207000D2C69 /* OWSIncomingMessageCollectionViewCell.xib */, 45F2B1921D9C9F48000D2C69 /* OWSOutgoingMessageCollectionViewCell.h */, 45F2B1931D9C9F48000D2C69 /* OWSOutgoingMessageCollectionViewCell.m */, 45F2B1961D9CA207000D2C69 /* OWSOutgoingMessageCollectionViewCell.xib */, - A5509ECB1A69B1D600ABA4BC /* CountryCodeTableViewCell.h */, - A5509ECC1A69B1D600ABA4BC /* CountryCodeTableViewCell.m */, - FCAC963D19FEF99A0046DFC5 /* InboxTableViewCell.h */, - FCAC963E19FEF99A0046DFC5 /* InboxTableViewCell.m */, - 76EB052E18170B33006006FC /* ContactTableViewCell.h */, - 76EB052F18170B33006006FC /* ContactTableViewCell.m */, - 451764291DE939FD00EDB8B9 /* ContactCell.swift */, - 451764281DE939FD00EDB8B9 /* ContactCell.xib */, + 34330AA11E79686200DF2FB9 /* OWSProgressView.h */, + 34330AA21E79686200DF2FB9 /* OWSProgressView.m */, 76EB053818170B33006006FC /* xibs */, - 459311FA1D75C948008DD4F0 /* OWSDeviceTableViewCell.h */, - 459311FB1D75C948008DD4F0 /* OWSDeviceTableViewCell.m */, - 450873C11D9D5149006B54F2 /* OWSExpirationTimerView.h */, - 450873C21D9D5149006B54F2 /* OWSExpirationTimerView.m */, - 450873C91D9D86F4006B54F2 /* OWSExpirableMessageView.h */, - 4531C9C21DD8E6D800F08304 /* JSQMessagesCollectionViewCell+OWS.h */, - 4531C9C31DD8E6D800F08304 /* JSQMessagesCollectionViewCell+OWS.m */, - 45E1F3A21DEF1DF000852CF1 /* NoSignalContactsView.xib */, - 45E1F3A41DEF20A100852CF1 /* NoSignalContactsView.swift */, - 45F3AEB51DFDE7900080CE33 /* AvatarImageView.swift */, ); name = Views; path = views; @@ -2173,19 +2181,21 @@ B62D53F41A23CC8B009AAF82 /* TSMessageAdapters */ = { isa = PBXGroup; children = ( - A5E9D4BA1A65FAD800E4481C /* TSVideoAttachmentAdapter.h */, - A5E9D4B91A65FAD800E4481C /* TSVideoAttachmentAdapter.m */, - B6A3EB491A423B3800B2236B /* TSPhotoAdapter.h */, - B6A3EB4A1A423B3800B2236B /* TSPhotoAdapter.m */, - 4CE0E3751B95453C007210CF /* TSAnimatedAdapter.h */, - 4CE0E3761B954546007210CF /* TSAnimatedAdapter.m */, - B62D53F51A23CCAD009AAF82 /* TSMessageAdapter.h */, - B62D53F61A23CCAD009AAF82 /* TSMessageAdapter.m */, - B6D3CBCE1C1376BE00C039DF /* TSContentAdapters.h */, + 34330A5F1E788EA900DF2FB9 /* AttachmentUploadView.h */, + 34330A601E788EA900DF2FB9 /* AttachmentUploadView.m */, 341BB7471DB727EE001E2975 /* JSQMediaItem+OWS.h */, 341BB7481DB727EE001E2975 /* JSQMediaItem+OWS.m */, - 4526BD481CA61C8D00166BC8 /* OWSMessageEditing.h */, 45666ECE1D995B94008FE134 /* OWSMessageData.h */, + 4526BD481CA61C8D00166BC8 /* OWSMessageEditing.h */, + 4CE0E3751B95453C007210CF /* TSAnimatedAdapter.h */, + 4CE0E3761B954546007210CF /* TSAnimatedAdapter.m */, + B6D3CBCE1C1376BE00C039DF /* TSContentAdapters.h */, + B62D53F51A23CCAD009AAF82 /* TSMessageAdapter.h */, + B62D53F61A23CCAD009AAF82 /* TSMessageAdapter.m */, + B6A3EB491A423B3800B2236B /* TSPhotoAdapter.h */, + B6A3EB4A1A423B3800B2236B /* TSPhotoAdapter.m */, + A5E9D4BA1A65FAD800E4481C /* TSVideoAttachmentAdapter.h */, + A5E9D4B91A65FAD800E4481C /* TSVideoAttachmentAdapter.m */, ); name = TSMessageAdapters; path = TSMessageAdapaters; @@ -3262,6 +3272,7 @@ B63761ED19E1FBE8005735D1 /* HttpRequestOrResponse.m in Sources */, 76EB05A018170B33006006FC /* IpAddress.m in Sources */, FCAC965119FF0A6E0046DFC5 /* MessagesViewController.m in Sources */, + 34330AA31E79686200DF2FB9 /* OWSProgressView.m in Sources */, 453D28BA1D332DB100D523F0 /* OWSMessagesBubblesSizeCalculator.m in Sources */, 45F170AC1E2F0351003FC1F2 /* CallAudioSession.swift in Sources */, B68EF9BB1C0B1EBD009C3DCD /* FLAnimatedImageView.m in Sources */, @@ -3371,6 +3382,7 @@ 45C681BC1D305C080050903A /* OWSCallCollectionViewCell.m in Sources */, 76EB064018170B33006006FC /* AnonymousTerminator.m in Sources */, 76EB058818170B33006006FC /* PropertyListPreferences.m in Sources */, + 34330A611E788EA900DF2FB9 /* AttachmentUploadView.m in Sources */, 76EB05B218170B33006006FC /* DH3KKeyAgreementProtocol.m in Sources */, B63761EC19E1FBE8005735D1 /* HttpRequest.m in Sources */, 45666F7B1D9C0533008FE134 /* OWSDatabaseMigration.m in Sources */, diff --git a/Signal/src/Models/TSMessageAdapaters/AttachmentUploadView.h b/Signal/src/Models/TSMessageAdapaters/AttachmentUploadView.h new file mode 100644 index 000000000..ef03346f9 --- /dev/null +++ b/Signal/src/Models/TSMessageAdapaters/AttachmentUploadView.h @@ -0,0 +1,24 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +@class TSAttachmentStream; + +typedef void (^AttachmentStateBlock)(BOOL isAttachmentReady); + +// This entity is used by various attachment adapters to +// coordinate view state with attachment uploads. +// During attachment uploads we want to: +// +// * Dim the media view using a mask layer. +// * Show and update a progress bar. +// * Disable any media view controls using a callback. +@interface AttachmentUploadView : NSObject + +@property (nonatomic, readonly) TSAttachmentStream *attachment; + +- (instancetype)initWithAttachment:(TSAttachmentStream *)attachment + superview:(UIView *)superview + attachmentStateCallback:(AttachmentStateBlock)attachmentStateCallback; + +@end diff --git a/Signal/src/Models/TSMessageAdapaters/AttachmentUploadView.m b/Signal/src/Models/TSMessageAdapaters/AttachmentUploadView.m new file mode 100644 index 000000000..a454a8130 --- /dev/null +++ b/Signal/src/Models/TSMessageAdapaters/AttachmentUploadView.m @@ -0,0 +1,118 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +#import "AttachmentUploadView.h" +#import "OWSProgressView.h" +#import "OWSUploadingService.h" +#import "TSAttachmentStream.h" + +@interface AttachmentUploadView () + +@property (nonatomic) TSAttachmentStream *attachment; + +@property (nonatomic) OWSProgressView *progressView; + +@property (nonatomic) CALayer *maskLayer; + +@property (nonatomic) AttachmentStateBlock attachmentStateCallback; + +@property (nonatomic) BOOL isAttachmentReady; + +@end + +#pragma mark - + +@implementation AttachmentUploadView + +- (instancetype)initWithAttachment:(TSAttachmentStream *)attachment + superview:(UIView *)superview + attachmentStateCallback:(AttachmentStateBlock)attachmentStateCallback +{ + self = [super init]; + + if (self) { + OWSAssert(attachment); + OWSAssert(superview); + + self.attachment = attachment; + self.attachmentStateCallback = attachmentStateCallback; + + _maskLayer = [CALayer layer]; + [_maskLayer setBackgroundColor:[UIColor blackColor].CGColor]; + [_maskLayer setOpacity:0.4f]; + [_maskLayer setFrame:superview.frame]; + [superview.layer addSublayer:_maskLayer]; + + const CGFloat progressWidth = round(superview.frame.size.width * 0.45f); + const CGFloat progressHeight = round(progressWidth * 0.11f); + CGRect progressFrame = CGRectMake(round((superview.frame.size.width - progressWidth) * 0.5f), + round((superview.frame.size.height - progressHeight) * 0.5f), + progressWidth, + progressHeight); + // The progress view is white. It will only be shown + // while the mask layer is visible, so it will show up + // even against all-white attachments. + _progressView = [OWSProgressView new]; + _progressView.color = [UIColor whiteColor]; + _progressView.frame = progressFrame; + [superview addSubview:_progressView]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(attachmentUploadProgress:) + name:kAttachmentUploadProgressNotification + object:nil]; + + _isAttachmentReady = self.attachment.isUploaded; + + [self ensureViewState]; + + if (attachmentStateCallback) { + self.attachmentStateCallback(_isAttachmentReady); + } + } + return self; +} + +- (void)dealloc +{ + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +- (void)setIsAttachmentReady:(BOOL)isAttachmentReady +{ + if (_isAttachmentReady == isAttachmentReady) { + return; + } + + _isAttachmentReady = isAttachmentReady; + + [self ensureViewState]; + + if (self.attachmentStateCallback) { + self.attachmentStateCallback(isAttachmentReady); + } +} + +- (void)ensureViewState +{ + _maskLayer.hidden = self.isAttachmentReady; + _progressView.hidden = self.isAttachmentReady; +} + +- (void)attachmentUploadProgress:(NSNotification *)notification +{ + NSDictionary *userinfo = [notification userInfo]; + double progress = [[userinfo objectForKey:kAttachmentUploadProgressKey] doubleValue]; + NSString *attachmentID = [userinfo objectForKey:kAttachmentUploadAttachmentIDKey]; + if ([self.attachment.uniqueId isEqualToString:attachmentID]) { + if (!isnan(progress)) { + [_progressView setProgress:(float)progress]; + self.isAttachmentReady = self.attachment.isUploaded; + } else { + self.isAttachmentReady = YES; + } + } +} + +@end diff --git a/Signal/src/Models/TSMessageAdapaters/TSAnimatedAdapter.h b/Signal/src/Models/TSMessageAdapaters/TSAnimatedAdapter.h index 3eb21f78b..1c94cfc66 100644 --- a/Signal/src/Models/TSMessageAdapaters/TSAnimatedAdapter.h +++ b/Signal/src/Models/TSMessageAdapaters/TSAnimatedAdapter.h @@ -1,9 +1,5 @@ // -// TSAnimatedAdapter.h -// Signal -// -// Created by Mike Okner (@mikeokner) on 2015-09-01. -// Copyright (c) 2015 Open Whisper Systems. All rights reserved. +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. // #import "OWSMessageEditing.h" @@ -13,7 +9,7 @@ @interface TSAnimatedAdapter : JSQMediaItem -- (instancetype)initWithAttachment:(TSAttachmentStream *)attachment; +- (instancetype)initWithAttachment:(TSAttachmentStream *)attachment incoming:(BOOL)incoming; - (BOOL)isImage; - (BOOL)isAudio; diff --git a/Signal/src/Models/TSMessageAdapaters/TSAnimatedAdapter.m b/Signal/src/Models/TSMessageAdapaters/TSAnimatedAdapter.m index 5beb00343..9a5962e9a 100644 --- a/Signal/src/Models/TSMessageAdapaters/TSAnimatedAdapter.m +++ b/Signal/src/Models/TSMessageAdapaters/TSAnimatedAdapter.m @@ -1,30 +1,30 @@ // -// TSAnimatedAdapter.m -// Signal -// -// Created by Mike Okner (@mikeokner) on 2015-09-01. -// Copyright (c) 2015 Open Whisper Systems. All rights reserved. +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. // #import "TSAnimatedAdapter.h" +#import "AttachmentUploadView.h" #import "FLAnimatedImage.h" -#import "TSAttachmentStream.h" #import "JSQMediaItem+OWS.h" +#import "TSAttachmentStream.h" #import #import #import @interface TSAnimatedAdapter () -@property (strong, nonatomic) UIImageView *cachedImageView; -@property (strong, nonatomic) UIImage *image; -@property (strong, nonatomic) TSAttachmentStream *attachment; +@property (nonatomic) UIImageView *cachedImageView; +@property (nonatomic) UIImage *image; +@property (nonatomic) TSAttachmentStream *attachment; +@property (nonatomic) AttachmentUploadView *attachmentUploadView; +@property (nonatomic) BOOL incoming; @end @implementation TSAnimatedAdapter -- (instancetype)initWithAttachment:(TSAttachmentStream *)attachment { +- (instancetype)initWithAttachment:(TSAttachmentStream *)attachment incoming:(BOOL)incoming +{ self = [super init]; if (self) { @@ -33,6 +33,7 @@ _attachmentId = attachment.uniqueId; _image = [attachment image]; _fileData = [NSData dataWithContentsOfURL:[attachment mediaURL]]; + _incoming = incoming; } return self; @@ -78,6 +79,12 @@ [JSQMessagesMediaViewBubbleImageMasker applyBubbleImageMaskToMediaView:imageView isOutgoing:self.appliesMediaViewMaskAsOutgoing]; self.cachedImageView = imageView; + + if (!self.incoming) { + self.attachmentUploadView = [[AttachmentUploadView alloc] initWithAttachment:self.attachment + superview:imageView + attachmentStateCallback:nil]; + } } return self.cachedImageView; diff --git a/Signal/src/Models/TSMessageAdapaters/TSMessageAdapter.m b/Signal/src/Models/TSMessageAdapaters/TSMessageAdapter.m index 0d9c37f79..0d4faab01 100644 --- a/Signal/src/Models/TSMessageAdapaters/TSMessageAdapter.m +++ b/Signal/src/Models/TSMessageAdapaters/TSMessageAdapter.m @@ -127,15 +127,19 @@ for (NSString *attachmentID in message.attachmentIds) { TSAttachment *attachment = [TSAttachment fetchObjectWithUniqueID:attachmentID]; + BOOL isIncomingAttachment = [interaction isKindOfClass:[TSIncomingMessage class]]; + if ([attachment isKindOfClass:[TSAttachmentStream class]]) { TSAttachmentStream *stream = (TSAttachmentStream *)attachment; if ([stream isAnimated]) { - adapter.mediaItem = [[TSAnimatedAdapter alloc] initWithAttachment:stream]; + adapter.mediaItem = + [[TSAnimatedAdapter alloc] initWithAttachment:stream incoming:isIncomingAttachment]; adapter.mediaItem.appliesMediaViewMaskAsOutgoing = [interaction isKindOfClass:[TSOutgoingMessage class]]; break; } else if ([stream isImage]) { - adapter.mediaItem = [[TSPhotoAdapter alloc] initWithAttachment:stream]; + adapter.mediaItem = + [[TSPhotoAdapter alloc] initWithAttachment:stream incoming:isIncomingAttachment]; adapter.mediaItem.appliesMediaViewMaskAsOutgoing = [interaction isKindOfClass:[TSOutgoingMessage class]]; break; diff --git a/Signal/src/Models/TSMessageAdapaters/TSPhotoAdapter.h b/Signal/src/Models/TSMessageAdapaters/TSPhotoAdapter.h index f59525eaa..2e40ad11e 100644 --- a/Signal/src/Models/TSMessageAdapaters/TSPhotoAdapter.h +++ b/Signal/src/Models/TSMessageAdapaters/TSPhotoAdapter.h @@ -1,5 +1,6 @@ -// Created by Frederic Jacobs on 17/12/14. -// Copyright (c) 2014 Open Whisper Systems. All rights reserved. +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// #import "OWSMessageEditing.h" #import @@ -8,7 +9,7 @@ @interface TSPhotoAdapter : JSQPhotoMediaItem -- (instancetype)initWithAttachment:(TSAttachmentStream *)attachment; +- (instancetype)initWithAttachment:(TSAttachmentStream *)attachment incoming:(BOOL)incoming; - (BOOL)isImage; - (BOOL)isAudio; diff --git a/Signal/src/Models/TSMessageAdapaters/TSPhotoAdapter.m b/Signal/src/Models/TSMessageAdapaters/TSPhotoAdapter.m index ff63c4807..2a2c31d13 100644 --- a/Signal/src/Models/TSMessageAdapaters/TSPhotoAdapter.m +++ b/Signal/src/Models/TSMessageAdapaters/TSPhotoAdapter.m @@ -3,18 +3,23 @@ // #import "TSPhotoAdapter.h" -#import "TSAttachmentStream.h" +#import "AttachmentUploadView.h" #import "JSQMediaItem+OWS.h" +#import "TSAttachmentStream.h" #import @interface TSPhotoAdapter () -@property (strong, nonatomic) UIImageView *cachedImageView; +@property (nonatomic) UIImageView *cachedImageView; +@property (nonatomic) AttachmentUploadView *attachmentUploadView; +@property (nonatomic) BOOL incoming; + @end @implementation TSPhotoAdapter -- (instancetype)initWithAttachment:(TSAttachmentStream *)attachment { +- (instancetype)initWithAttachment:(TSAttachmentStream *)attachment incoming:(BOOL)incoming +{ self = [super initWithImage:attachment.image]; if (!self) { @@ -24,6 +29,7 @@ _cachedImageView = nil; _attachment = attachment; _attachmentId = attachment.uniqueId; + _incoming = incoming; return self; } @@ -58,6 +64,12 @@ [JSQMessagesMediaViewBubbleImageMasker applyBubbleImageMaskToMediaView:imageView isOutgoing:self.appliesMediaViewMaskAsOutgoing]; self.cachedImageView = imageView; + + if (!self.incoming) { + self.attachmentUploadView = [[AttachmentUploadView alloc] initWithAttachment:self.attachment + superview:imageView + attachmentStateCallback:nil]; + } } return self.cachedImageView; diff --git a/Signal/src/Models/TSMessageAdapaters/TSVideoAttachmentAdapter.m b/Signal/src/Models/TSMessageAdapaters/TSVideoAttachmentAdapter.m index 35c887694..7b4d122c3 100644 --- a/Signal/src/Models/TSMessageAdapaters/TSVideoAttachmentAdapter.m +++ b/Signal/src/Models/TSMessageAdapaters/TSVideoAttachmentAdapter.m @@ -1,13 +1,14 @@ -// Created by Frederic Jacobs on 17/12/14. -// Copyright (c) 2014 Open Whisper Systems. All rights reserved. +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// #import "TSVideoAttachmentAdapter.h" +#import "AttachmentUploadView.h" +#import "JSQMediaItem+OWS.h" #import "MIMETypeUtil.h" #import "TSAttachmentStream.h" #import "TSMessagesManager.h" #import "TSStorageManager+keyingMaterial.h" -#import "JSQMediaItem+OWS.h" -#import #import #import #import @@ -15,18 +16,17 @@ @interface TSVideoAttachmentAdapter () -@property UIImage *image; -@property (strong, nonatomic) UIImageView *cachedImageView; -@property (strong, nonatomic) UIImageView *videoPlayButton; -@property (strong, nonatomic) CALayer *maskLayer; -@property (strong, nonatomic) FFCircularProgressView *progressView; -@property (strong, nonatomic) TSAttachmentStream *attachment; -@property (strong, nonatomic) UIProgressView *audioProgress; -@property (strong, nonatomic) SCWaveformView *waveform; -@property (strong, nonatomic) UIButton *audioPlayPauseButton; -@property (strong, nonatomic) UILabel *durationLabel; -@property (strong, nonatomic) UIView *audioBubble; +@property (nonatomic) UIImage *image; +@property (nonatomic) UIImageView *cachedImageView; +@property (nonatomic) UIImageView *videoPlayButton; +@property (nonatomic) TSAttachmentStream *attachment; +@property (nonatomic) UIProgressView *audioProgress; +@property (nonatomic) SCWaveformView *waveform; +@property (nonatomic) UIButton *audioPlayPauseButton; +@property (nonatomic) UILabel *durationLabel; +@property (nonatomic) UIView *audioBubble; @property (nonatomic) BOOL incoming; +@property (nonatomic) AttachmentUploadView *attachmentUploadView; @end @@ -121,23 +121,16 @@ _videoPlayButton.frame = CGRectMake((size.width / 2) - 18, (size.height / 2) - 18, 37, 37); [self.cachedImageView addSubview:_videoPlayButton]; _videoPlayButton.hidden = YES; - _maskLayer = [CALayer layer]; - [_maskLayer setBackgroundColor:[UIColor blackColor].CGColor]; - [_maskLayer setOpacity:0.4f]; - [_maskLayer setFrame:self.cachedImageView.frame]; - [self.cachedImageView.layer addSublayer:_maskLayer]; - _progressView = [[FFCircularProgressView alloc] - initWithFrame:CGRectMake((size.width / 2) - 18, (size.height / 2) - 18, 37, 37)]; - [_cachedImageView addSubview:_progressView]; - if (_attachment.isDownloaded) { - _videoPlayButton.hidden = NO; - _maskLayer.hidden = YES; - _progressView.hidden = YES; + + if (!_incoming) { + __weak TSVideoAttachmentAdapter *weakSelf = self; + self.attachmentUploadView = [[AttachmentUploadView alloc] initWithAttachment:self.attachment + superview:imageView + attachmentStateCallback:^(BOOL isAttachmentReady) { + weakSelf.videoPlayButton.hidden + = !isAttachmentReady; + }]; } - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(attachmentUploadProgress:) - name:@"attachmentUploadProgress" - object:nil]; } } else if ([self isAudio]) { NSError *err = NULL; @@ -192,7 +185,20 @@ [_audioBubble addSubview:_audioPlayPauseButton]; [_audioBubble addSubview:_durationLabel]; + if (!_incoming) { + __weak TSVideoAttachmentAdapter *weakSelf = self; + self.attachmentUploadView = [[AttachmentUploadView alloc] initWithAttachment:self.attachment + superview:_audioBubble + attachmentStateCallback:^(BOOL isAttachmentReady) { + weakSelf.audioPlayPauseButton.enabled + = isAttachmentReady; + }]; + } + return _audioBubble; + } else { + // Unknown media type. + OWSAssert(0); } return self.cachedImageView; } @@ -215,35 +221,6 @@ return [super hash]; } -- (void)attachmentUploadProgress:(NSNotification *)notification { - NSDictionary *userinfo = [notification userInfo]; - double progress = [[userinfo objectForKey:@"progress"] doubleValue]; - NSString *attachmentID = [userinfo objectForKey:@"attachmentID"]; - if ([_attachmentId isEqualToString:attachmentID]) { - NSLog(@"is downloaded: %d", _attachment.isDownloaded); - if (!isnan(progress)) { - [_progressView setProgress:(float)progress]; - } - if (progress >= 1) { - _maskLayer.hidden = YES; - _progressView.hidden = YES; - _videoPlayButton.hidden = NO; - _attachment.isDownloaded = YES; // TODO isn't this redundant with attachment processor? - [[TSMessagesManager sharedManager] - .dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - [_attachment saveWithTransaction:transaction]; - }]; - } - } - // set progress on bar -} - -- (void)dealloc { - _image = nil; - _cachedImageView = nil; - [[NSNotificationCenter defaultCenter] removeObserver:self]; -} - - (void)setAppliesMediaViewMaskAsOutgoing:(BOOL)appliesMediaViewMaskAsOutgoing { [super setAppliesMediaViewMaskAsOutgoing:appliesMediaViewMaskAsOutgoing]; _cachedImageView = nil; diff --git a/Signal/src/view controllers/MessagesViewController.m b/Signal/src/view controllers/MessagesViewController.m index 95a49864c..d718a5ddc 100644 --- a/Signal/src/view controllers/MessagesViewController.m +++ b/Signal/src/view controllers/MessagesViewController.m @@ -180,29 +180,29 @@ typedef enum : NSUInteger { NSUInteger _unreadCount; } -@property TSThread *thread; -@property TSMessageAdapter *lastDeliveredMessage; -@property (nonatomic, strong) YapDatabaseConnection *editingDatabaseConnection; -@property (nonatomic, strong) YapDatabaseConnection *uiDatabaseConnection; -@property (nonatomic, strong) YapDatabaseViewMappings *messageMappings; +@property (nonatomic) TSThread *thread; +@property (nonatomic) TSMessageAdapter *lastDeliveredMessage; +@property (nonatomic) YapDatabaseConnection *editingDatabaseConnection; +@property (nonatomic) YapDatabaseConnection *uiDatabaseConnection; +@property (nonatomic) YapDatabaseViewMappings *messageMappings; -@property (nonatomic, retain) JSQMessagesBubbleImage *outgoingBubbleImageData; -@property (nonatomic, retain) JSQMessagesBubbleImage *incomingBubbleImageData; -@property (nonatomic, retain) JSQMessagesBubbleImage *currentlyOutgoingBubbleImageData; -@property (nonatomic, retain) JSQMessagesBubbleImage *outgoingMessageFailedImageData; +@property (nonatomic) JSQMessagesBubbleImage *outgoingBubbleImageData; +@property (nonatomic) JSQMessagesBubbleImage *incomingBubbleImageData; +@property (nonatomic) JSQMessagesBubbleImage *currentlyOutgoingBubbleImageData; +@property (nonatomic) JSQMessagesBubbleImage *outgoingMessageFailedImageData; -@property (nonatomic, strong) NSTimer *audioPlayerPoller; -@property (nonatomic, strong) TSVideoAttachmentAdapter *currentMediaAdapter; +@property (nonatomic) NSTimer *audioPlayerPoller; +@property (nonatomic) TSVideoAttachmentAdapter *currentMediaAdapter; -@property (nonatomic, retain) NSTimer *readTimer; -@property (nonatomic, strong) UIView *navigationBarTitleView; -@property (nonatomic, strong) UILabel *navigationBarTitleLabel; -@property (nonatomic, strong) UILabel *navigationBarSubtitleLabel; -@property (nonatomic, retain) UIButton *attachButton; +@property (nonatomic) NSTimer *readTimer; +@property (nonatomic) UIView *navigationBarTitleView; +@property (nonatomic) UILabel *navigationBarTitleLabel; +@property (nonatomic) UILabel *navigationBarSubtitleLabel; +@property (nonatomic) UIButton *attachButton; @property (nonatomic) CGFloat previousCollectionViewFrameWidth; -@property NSUInteger page; +@property (nonatomic) NSUInteger page; @property (nonatomic) BOOL composeOnOpen; @property (nonatomic) BOOL peek; @@ -215,7 +215,7 @@ typedef enum : NSUInteger { @property (nonatomic, readonly) TSNetworkManager *networkManager; @property (nonatomic, readonly) OutboundCallInitiator *outboundCallInitiator; -@property NSCache *messageAdapterCache; +@property (nonatomic) NSCache *messageAdapterCache; @end diff --git a/Signal/src/views/OWSProgressView.h b/Signal/src/views/OWSProgressView.h new file mode 100644 index 000000000..f94a4d2d5 --- /dev/null +++ b/Signal/src/views/OWSProgressView.h @@ -0,0 +1,14 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +#pragma once + +#import + +@interface OWSProgressView : UIView + +@property (nonatomic) UIColor *color; +@property (nonatomic) CGFloat progress; + +@end diff --git a/Signal/src/views/OWSProgressView.m b/Signal/src/views/OWSProgressView.m new file mode 100644 index 000000000..9e7ca977e --- /dev/null +++ b/Signal/src/views/OWSProgressView.m @@ -0,0 +1,120 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +#import "OWSProgressView.h" + +@interface OWSProgressView () + +@property (nonatomic) CAShapeLayer *borderLayer; +@property (nonatomic) CAShapeLayer *progressLayer; + +@end + +#pragma mark - + +@implementation OWSProgressView + +- (id)init +{ + self = [super init]; + if (self) { + [self initCommon]; + } + + return self; +} + +- (id)initWithFrame:(CGRect)frame +{ + self = [super initWithFrame:frame]; + if (self) { + [self initCommon]; + } + return self; +} + +- (void)initCommon +{ + self.opaque = NO; + self.userInteractionEnabled = NO; + self.backgroundColor = [UIColor clearColor]; + self.color = [UIColor whiteColor]; + + self.borderLayer = [CAShapeLayer new]; + self.borderLayer.fillColor = self.color.CGColor; + [self.layer addSublayer:self.borderLayer]; + + self.progressLayer = [CAShapeLayer new]; + self.progressLayer.fillColor = self.color.CGColor; + [self.layer addSublayer:self.progressLayer]; + + [self setContentCompressionResistancePriority:UILayoutPriorityDefaultHigh forAxis:UILayoutConstraintAxisVertical]; + [self setContentHuggingPriority:UILayoutPriorityDefaultHigh forAxis:UILayoutConstraintAxisVertical]; +} + +- (void)layoutSubviews +{ + [super layoutSubviews]; + [self update]; +} + +- (void)setProgress:(CGFloat)progress +{ + if (_progress != progress) { + _progress = progress; + [self update]; + } +} + +- (void)setColor:(UIColor *)color +{ + if (![_color isEqual:color]) { + _color = color; + [self update]; + } +} + +- (void)update +{ + CGFloat kBorderThickness = self.bounds.size.height * 0.15f; + CGFloat kOuterRadius = self.bounds.size.height * 0.3f; + CGFloat kInnerRadius = kOuterRadius - kBorderThickness; + // We want to slightly overlap the border with the progress + // to achieve a clean effect. + CGFloat kProgressInset = kBorderThickness - 0.5f; + + UIBezierPath *borderPath = [UIBezierPath new]; + + // Add the outer border. + [borderPath appendPath:[UIBezierPath bezierPathWithRoundedRect:self.bounds cornerRadius:kOuterRadius]]; + [borderPath + appendPath:[UIBezierPath bezierPathWithRoundedRect:CGRectInset(self.bounds, kBorderThickness, kBorderThickness) + cornerRadius:kInnerRadius]]; + + self.borderLayer.path = borderPath.CGPath; + self.borderLayer.fillColor = self.color.CGColor; + self.borderLayer.fillRule = kCAFillRuleEvenOdd; + + UIBezierPath *progressPath = [UIBezierPath new]; + + // Add the inner progress. + CGRect progressRect = CGRectInset(self.bounds, kProgressInset, kProgressInset); + progressRect.size.width *= MAX(0.f, MIN(1.f, self.progress)); + [progressPath appendPath:[UIBezierPath bezierPathWithRect:progressRect]]; + + self.progressLayer.path = progressPath.CGPath; + self.progressLayer.fillColor = self.color.CGColor; +} + +- (CGSize)sizeThatFits:(CGSize)size +{ + return CGSizeMake(150, 16); +} + +- (CGSize)intrinsicContentSize +{ + return CGSizeMake(UIViewNoIntrinsicMetric, 16); +} + +@end