diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 29963b894..683b6a581 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -9,6 +9,11 @@ /* Begin PBXBuildFile section */ 340757C21E5602D6001F15DD /* AttachmentSharing.m in Sources */ = {isa = PBXBuildFile; fileRef = 340757C11E5602D6001F15DD /* AttachmentSharing.m */; }; 341BB7491DB727EE001E2975 /* JSQMediaItem+OWS.m in Sources */ = {isa = PBXBuildFile; fileRef = 341BB7481DB727EE001E2975 /* JSQMediaItem+OWS.m */; }; + 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 */; }; @@ -625,6 +630,13 @@ 340757C11E5602D6001F15DD /* AttachmentSharing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AttachmentSharing.m; sourceTree = ""; }; 341BB7471DB727EE001E2975 /* JSQMediaItem+OWS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "JSQMediaItem+OWS.h"; sourceTree = ""; }; 341BB7481DB727EE001E2975 /* JSQMediaItem+OWS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "JSQMediaItem+OWS.m"; sourceTree = ""; }; + 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 = ""; }; @@ -1345,6 +1357,16 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 34330A581E7875FB00DF2FB9 /* Fonts */ = { + isa = PBXGroup; + children = ( + 34330A5B1E787A9800DF2FB9 /* dripicons-v2.ttf */, + 34330A5D1E787BD800DF2FB9 /* ElegantIcons.ttf */, + 34330A591E7875FB00DF2FB9 /* fontawesome-webfont.ttf */, + ); + path = Fonts; + sourceTree = ""; + }; 4505C2BD1E648E6E00CEBF41 /* ExperienceUpgrades */ = { isa = PBXGroup; children = ( @@ -2089,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; @@ -2157,19 +2181,21 @@ B62D53F41A23CC8B009AAF82 /* TSMessageAdapters */ = { isa = PBXGroup; children = ( - A5E9D4BA1A65FAD800E4481C /* TSVideoAttachmentAdapter.h */, - A5E9D4B91A65FAD800E4481C /* TSVideoAttachmentAdapter.m */, - B6A3EB491A423B3800B2236B /* TSPhotoAdapter.h */, - B6A3EB4A1A423B3800B2236B /* TSPhotoAdapter.m */, + 34330A5F1E788EA900DF2FB9 /* AttachmentUploadView.h */, + 34330A601E788EA900DF2FB9 /* AttachmentUploadView.m */, + 341BB7471DB727EE001E2975 /* JSQMediaItem+OWS.h */, + 341BB7481DB727EE001E2975 /* JSQMediaItem+OWS.m */, + 45666ECE1D995B94008FE134 /* OWSMessageData.h */, + 4526BD481CA61C8D00166BC8 /* OWSMessageEditing.h */, 4CE0E3751B95453C007210CF /* TSAnimatedAdapter.h */, 4CE0E3761B954546007210CF /* TSAnimatedAdapter.m */, + B6D3CBCE1C1376BE00C039DF /* TSContentAdapters.h */, B62D53F51A23CCAD009AAF82 /* TSMessageAdapter.h */, B62D53F61A23CCAD009AAF82 /* TSMessageAdapter.m */, - B6D3CBCE1C1376BE00C039DF /* TSContentAdapters.h */, - 341BB7471DB727EE001E2975 /* JSQMediaItem+OWS.h */, - 341BB7481DB727EE001E2975 /* JSQMediaItem+OWS.m */, - 4526BD481CA61C8D00166BC8 /* OWSMessageEditing.h */, - 45666ECE1D995B94008FE134 /* OWSMessageData.h */, + B6A3EB491A423B3800B2236B /* TSPhotoAdapter.h */, + B6A3EB4A1A423B3800B2236B /* TSPhotoAdapter.m */, + A5E9D4BA1A65FAD800E4481C /* TSVideoAttachmentAdapter.h */, + A5E9D4B91A65FAD800E4481C /* TSVideoAttachmentAdapter.m */, ); name = TSMessageAdapters; path = TSMessageAdapaters; @@ -2536,6 +2562,7 @@ isa = PBXGroup; children = ( B657DDC91911A40500F45B0C /* Signal.entitlements */, + 34330A581E7875FB00DF2FB9 /* Fonts */, B633C4FD1A1D190B0059AC12 /* Images */, B67EBF5C19194AC60084CCFD /* Settings.bundle */, 76EB03C118170B33006006FC /* src */, @@ -2914,6 +2941,7 @@ E94066151DFC5B7B00B15392 /* ContactsPicker.xib in Resources */, AD41D7B61A6F6F0600241130 /* play_button@2x.png in Resources */, AD83FF3F1A73426500B5C81A /* audio_pause_button_blue.png in Resources */, + 34330A5A1E7875FB00DF2FB9 /* fontawesome-webfont.ttf in Resources */, 348A08421E6A044E0057E290 /* MessagesViewController.xib in Resources */, 45E1F3A31DEF1DF000852CF1 /* NoSignalContactsView.xib in Resources */, A5509ECA1A69AB8B00ABA4BC /* Main.storyboard in Resources */, @@ -2922,6 +2950,7 @@ AD83FF421A73426500B5C81A /* audio_play_button.png in Resources */, 45F2B1981D9CA207000D2C69 /* OWSOutgoingMessageCollectionViewCell.xib in Resources */, 45C681C41D305C9E0050903A /* OWSCallCollectionViewCell.xib in Resources */, + 34330A5C1E787A9800DF2FB9 /* dripicons-v2.ttf in Resources */, B633C5C41A1D190B0059AC12 /* mute_on@2x.png in Resources */, B633C5CE1A1D190B0059AC12 /* quit@2x.png in Resources */, AD83FF441A73426500B5C81A /* audio_pause_button.png in Resources */, @@ -2960,6 +2989,7 @@ B10C9B5F1A7049EC00ECA2BF /* pause_icon.png in Resources */, AD83FF471A73428300B5C81A /* audio_play_button_blue.png in Resources */, 348A08441E6A1D2C0057E290 /* OWSMessagesToolbarContentView.xib in Resources */, + 34330A5E1E787BD800DF2FB9 /* ElegantIcons.ttf in Resources */, AD83FF451A73426500B5C81A /* audio_pause_button@2x.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -3242,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 */, @@ -3351,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/Fonts/ElegantIcons.ttf b/Signal/Fonts/ElegantIcons.ttf new file mode 100755 index 000000000..12ff68002 Binary files /dev/null and b/Signal/Fonts/ElegantIcons.ttf differ diff --git a/Signal/Fonts/dripicons-v2.ttf b/Signal/Fonts/dripicons-v2.ttf new file mode 100755 index 000000000..041e33364 Binary files /dev/null and b/Signal/Fonts/dripicons-v2.ttf differ diff --git a/Signal/Fonts/fontawesome-webfont.ttf b/Signal/Fonts/fontawesome-webfont.ttf new file mode 100644 index 000000000..f221e50a2 Binary files /dev/null and b/Signal/Fonts/fontawesome-webfont.ttf differ diff --git a/Signal/Signal-Info.plist b/Signal/Signal-Info.plist index 1751dac8f..9c5fcfa24 100644 --- a/Signal/Signal-Info.plist +++ b/Signal/Signal-Info.plist @@ -110,5 +110,11 @@ UIViewControllerBasedStatusBarAppearance + UIAppFonts + + dripicons-v2.ttf + ElegantIcons.ttf + fontawesome-webfont.ttf + diff --git a/Signal/src/Models/TSMessageAdapaters/AttachmentUploadView.h b/Signal/src/Models/TSMessageAdapaters/AttachmentUploadView.h new file mode 100644 index 000000000..493d71c54 --- /dev/null +++ b/Signal/src/Models/TSMessageAdapaters/AttachmentUploadView.h @@ -0,0 +1,26 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +NS_ASSUME_NONNULL_BEGIN + +@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 + +- (instancetype)initWithAttachment:(TSAttachmentStream *)attachment + superview:(UIView *)superview + attachmentStateCallback:(AttachmentStateBlock _Nullable)attachmentStateCallback; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Signal/src/Models/TSMessageAdapaters/AttachmentUploadView.m b/Signal/src/Models/TSMessageAdapaters/AttachmentUploadView.m new file mode 100644 index 000000000..cdbd55f64 --- /dev/null +++ b/Signal/src/Models/TSMessageAdapaters/AttachmentUploadView.m @@ -0,0 +1,123 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +#import "AttachmentUploadView.h" +#import "OWSProgressView.h" +#import "OWSUploadingService.h" +#import "TSAttachmentStream.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface AttachmentUploadView () + +@property (nonatomic) TSAttachmentStream *attachment; + +@property (nonatomic) OWSProgressView *progressView; + +@property (nonatomic) CALayer *maskLayer; + +@property (nonatomic) AttachmentStateBlock _Nullable attachmentStateCallback; + +@property (nonatomic) BOOL isAttachmentReady; + +@end + +#pragma mark - + +@implementation AttachmentUploadView + +- (instancetype)initWithAttachment:(TSAttachmentStream *)attachment + superview:(UIView *)superview + attachmentStateCallback:(AttachmentStateBlock _Nullable)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 isEqual:attachmentID]) { + if (!isnan(progress)) { + [_progressView setProgress:progress]; + self.isAttachmentReady = self.attachment.isUploaded; + } else { + OWSAssert(0); + self.isAttachmentReady = YES; + } + } +} + +@end + +NS_ASSUME_NONNULL_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.h b/Signal/src/Models/TSMessageAdapaters/TSMessageAdapter.h index 363c0f8ee..1e3428d60 100644 --- a/Signal/src/Models/TSMessageAdapaters/TSMessageAdapter.h +++ b/Signal/src/Models/TSMessageAdapaters/TSMessageAdapter.h @@ -1,9 +1,5 @@ // -// TSMessageAdapter.h -// Signal -// -// Created by Frederic Jacobs on 24/11/14. -// Copyright (c) 2014 Open Whisper Systems. All rights reserved. +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. // #import "OWSMessageData.h" @@ -25,8 +21,8 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic) TSInteraction *interaction; @property (readonly) TSInfoMessageType infoMessageType; @property (nonatomic, readonly) CGFloat mediaViewAlpha; -@property (nonatomic, readonly) BOOL isOutgoingAndDelivered; @property (nonatomic, readonly) BOOL isMediaBeingSent; +@property (nonatomic, readonly) BOOL isOutgoingAndDelivered; @end 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/util/UIFont+OWS.h b/Signal/src/util/UIFont+OWS.h index 9e20c7066..47eea3079 100644 --- a/Signal/src/util/UIFont+OWS.h +++ b/Signal/src/util/UIFont+OWS.h @@ -1,9 +1,5 @@ // -// UIFont+OWS.h -// Signal -// -// Created by Dylan Bourgeois on 25/11/14. -// Copyright (c) 2014 Open Whisper Systems. All rights reserved. +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. // #import @@ -20,8 +16,13 @@ + (UIFont *)ows_boldFontWithSize:(CGFloat)size; +#pragma mark - Icon Fonts + ++ (UIFont *)ows_fontAwesomeFont:(CGFloat)size; ++ (UIFont *)ows_dripIconsFont:(CGFloat)size; ++ (UIFont *)ows_elegantIconsFont:(CGFloat)size; -#pragma mark Dynamic Type +#pragma mark - Dynamic Type + (UIFont *)ows_dynamicTypeBodyFont; + (UIFont *)ows_dynamicTypeTitle2Font; diff --git a/Signal/src/util/UIFont+OWS.m b/Signal/src/util/UIFont+OWS.m index 613feddfd..8f5aa895e 100644 --- a/Signal/src/util/UIFont+OWS.m +++ b/Signal/src/util/UIFont+OWS.m @@ -1,9 +1,5 @@ // -// UIFont+OWS.m -// Signal -// -// Created by Dylan Bourgeois on 25/11/14. -// Copyright (c) 2014 Open Whisper Systems. All rights reserved. +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. // #import "UIFont+OWS.h" @@ -47,7 +43,24 @@ return [UIFont boldSystemFontOfSize:size]; } -#pragma mark Dynamic Type +#pragma mark - Icon Fonts + ++ (UIFont *)ows_fontAwesomeFont:(CGFloat)size +{ + return [UIFont fontWithName:@"FontAwesome" size:size]; +} + ++ (UIFont *)ows_dripIconsFont:(CGFloat)size +{ + return [UIFont fontWithName:@"dripicons-v2" size:size]; +} + ++ (UIFont *)ows_elegantIconsFont:(CGFloat)size +{ + return [UIFont fontWithName:@"ElegantIcons" size:size]; +} + +#pragma mark - Dynamic Type + (UIFont *)ows_dynamicTypeBodyFont { return [UIFont preferredFontForTextStyle:UIFontTextStyleBody]; diff --git a/Signal/src/view controllers/MessagesViewController.m b/Signal/src/view controllers/MessagesViewController.m index 12ffdcab2..ec57dfa9c 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, retain) JSQMessagesBubbleImage *outgoingBubbleImageData; -@property (nonatomic, retain) JSQMessagesBubbleImage *incomingBubbleImageData; -@property (nonatomic, retain) JSQMessagesBubbleImage *currentlyOutgoingBubbleImageData; -@property (nonatomic, retain) JSQMessagesBubbleImage *outgoingMessageFailedImageData; - -@property (nonatomic, strong) NSTimer *audioPlayerPoller; -@property (nonatomic, strong) 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) TSThread *thread; +@property (nonatomic) TSMessageAdapter *lastDeliveredMessage; +@property (nonatomic) YapDatabaseConnection *editingDatabaseConnection; +@property (nonatomic) YapDatabaseConnection *uiDatabaseConnection; +@property (nonatomic) YapDatabaseViewMappings *messageMappings; + +@property (nonatomic) JSQMessagesBubbleImage *outgoingBubbleImageData; +@property (nonatomic) JSQMessagesBubbleImage *incomingBubbleImageData; +@property (nonatomic) JSQMessagesBubbleImage *currentlyOutgoingBubbleImageData; +@property (nonatomic) JSQMessagesBubbleImage *outgoingMessageFailedImageData; + +@property (nonatomic) NSTimer *audioPlayerPoller; +@property (nonatomic) TSVideoAttachmentAdapter *currentMediaAdapter; + +@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 @@ -1248,20 +1248,17 @@ typedef enum : NSUInteger { return !![self collectionView:self.collectionView attributedTextForCellBottomLabelAtIndexPath:indexPath]; } -- (id)nextOutgoingMessage:(NSIndexPath *)indexPath +- (TSOutgoingMessage *)nextOutgoingMessage:(NSIndexPath *)indexPath { - id nextMessage = - [self messageAtIndexPath:[NSIndexPath indexPathForRow:indexPath.row + 1 inSection:indexPath.section]]; - int i = 1; - - while (indexPath.item + i < [self.collectionView numberOfItemsInSection:indexPath.section] - 1 - && !nextMessage.isOutgoingAndDelivered) { - i++; - nextMessage = - [self messageAtIndexPath:[NSIndexPath indexPathForRow:indexPath.row + i inSection:indexPath.section]]; + NSInteger rowCount = [self.collectionView numberOfItemsInSection:indexPath.section]; + for (NSInteger row = indexPath.row + 1; row < rowCount; row++) { + id nextMessage = [self messageAtIndexPath:[NSIndexPath indexPathForRow:row + inSection:indexPath.section]]; + if ([nextMessage isKindOfClass:[TSOutgoingMessage class]]) { + return (TSOutgoingMessage *)nextMessage; + } } - - return nextMessage; + return nil; } - (NSAttributedString *)collectionView:(JSQMessagesCollectionView *)collectionView @@ -1276,26 +1273,46 @@ typedef enum : NSUInteger { if (message.messageType == TSOutgoingMessageAdapter) { TSOutgoingMessage *outgoingMessage = (TSOutgoingMessage *)message.interaction; if (outgoingMessage.messageState == TSOutgoingMessageStateUnsent) { - return [[NSAttributedString alloc] initWithString:NSLocalizedString(@"FAILED_SENDING_TEXT", nil)]; - } else if (message.isOutgoingAndDelivered) { - NSAttributedString *deliveredString = - [[NSAttributedString alloc] initWithString:NSLocalizedString(@"DELIVERED_MESSAGE_TEXT", @"")]; + return [[NSAttributedString alloc] initWithString:NSLocalizedString(@"MESSAGE_STATUS_FAILED", + @"message footer for failed messages")]; + } else if (outgoingMessage.messageState == TSOutgoingMessageStateSent || + outgoingMessage.messageState == TSOutgoingMessageStateDelivered) { + NSString *text = (outgoingMessage.messageState == TSOutgoingMessageStateSent + ? NSLocalizedString(@"MESSAGE_STATUS_SENT", + @"message footer for sent messages") + : NSLocalizedString(@"MESSAGE_STATUS_DELIVERED", + @"message footer for delivered messages")); + NSAttributedString *result = [[NSAttributedString alloc] initWithString:text]; // Show when it's the last message in the thread if (indexPath.item == [self.collectionView numberOfItemsInSection:indexPath.section] - 1) { [self updateLastDeliveredMessage:message]; - return deliveredString; + return result; } - // Or when the next message is *not* an outgoing delivered message. - TSMessageAdapter *nextMessage = [self nextOutgoingMessage:indexPath]; - if (!nextMessage.isOutgoingAndDelivered) { + // Or when the next message is *not* an outgoing sent/delivered message. + TSOutgoingMessage *nextMessage = [self nextOutgoingMessage:indexPath]; + if (nextMessage && + nextMessage.messageState != TSOutgoingMessageStateSent && + nextMessage.messageState != TSOutgoingMessageStateDelivered) { [self updateLastDeliveredMessage:message]; - return deliveredString; + return result; } } else if (message.isMediaBeingSent) { - return [[NSAttributedString alloc] initWithString:NSLocalizedString(@"UPLOADING_MESSAGE_TEXT", - @"message footer while attachment is uploading")]; + return [[NSAttributedString alloc] initWithString:NSLocalizedString(@"MESSAGE_STATUS_UPLOADING", + @"message footer while attachment is uploading")]; + } else { + OWSAssert(outgoingMessage.messageState == TSOutgoingMessageStateAttemptingOut); + // Show an "..." ellisis icon. + // + // TODO: It'd be nice to animate this, but JSQMessageViewController doesn't give us a great way to do so. + // We already have problems with unstable cell layout; we don't want to exacerbate them. + NSAttributedString *result = + [[NSAttributedString alloc] initWithString:@"/" + attributes:@{ + NSFontAttributeName: [UIFont ows_dripIconsFont:14.f], + }]; + return result; } } else if (message.messageType == TSIncomingMessageAdapter && [self.thread isKindOfClass:[TSGroupThread class]]) { TSIncomingMessage *incomingMessage = (TSIncomingMessage *)message.interaction; diff --git a/Signal/src/views/OWSProgressView.h b/Signal/src/views/OWSProgressView.h new file mode 100644 index 000000000..768a8a942 --- /dev/null +++ b/Signal/src/views/OWSProgressView.h @@ -0,0 +1,16 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface OWSProgressView : UIView + +@property (nonatomic) UIColor *color; +@property (nonatomic) CGFloat progress; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Signal/src/views/OWSProgressView.m b/Signal/src/views/OWSProgressView.m new file mode 100644 index 000000000..63cfe4caf --- /dev/null +++ b/Signal/src/views/OWSProgressView.m @@ -0,0 +1,124 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +#import "OWSProgressView.h" + +NS_ASSUME_NONNULL_BEGIN + +@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 + +NS_ASSUME_NONNULL_END diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings index 4ad171d37..1c46c5a1f 100644 --- a/Signal/translations/en.lproj/Localizable.strings +++ b/Signal/translations/en.lproj/Localizable.strings @@ -25,9 +25,6 @@ /* No comment provided by engineer. */ "ANSWER_CALL_BUTTON_TITLE" = "Answer"; -/* No comment provided by engineer. */ -"APN_FETCHED_FAILED" = "New Message! Open app to read it."; - /* No comment provided by engineer. */ "APN_Message" = "New Message!"; @@ -163,9 +160,6 @@ /* {{number of days}} embedded in strings, e.g. 'Alice updated disappearing messages expiration to {{5 days}}'. See other *_TIME_AMOUNT strings */ "DAYS_TIME_AMOUNT" = "%u days"; -/* No comment provided by engineer. */ -"DELIVERED_MESSAGE_TEXT" = "Delivered"; - /* {{Short Date}} when device last communicated with Signal Server. */ "DEVICE_LAST_ACTIVE_AT_LABEL" = "Last active: %@"; @@ -343,9 +337,6 @@ /* action sheet header when re-sending message which failed because of untrusted identity keys */ "FAILED_SENDING_BECAUSE_UNTRUSTED_IDENTITY_KEY" = "You must accept the new safety number before you will be able to send."; -/* No comment provided by engineer. */ -"FAILED_SENDING_TEXT" = "Sending failed. Tap to retry."; - /* alert title */ "FAILED_VERIFICATION_TITLE" = "Failed to Verify Safety Number!"; @@ -499,6 +490,18 @@ /* No comment provided by engineer. */ "MESSAGE_COMPOSEVIEW_TITLE" = "New Message"; +/* message footer for delivered messages */ +"MESSAGE_STATUS_DELIVERED" = "Delivered"; + +/* message footer for failed messages */ +"MESSAGE_STATUS_FAILED" = "Sending failed. Tap to retry."; + +/* message footer for sent messages */ +"MESSAGE_STATUS_SENT" = "Sent"; + +/* message footer while attachment is uploading */ +"MESSAGE_STATUS_UPLOADING" = "Uploading..."; + /* The subtitle for the messages view title indicates that the title can be tapped to access settings for this conversation. */ "MESSAGES_VIEW_TITLE_SUBTITLE" = "Tap here for settings"; @@ -985,9 +988,6 @@ /* No comment provided by engineer. */ "Upgrading Signal ..." = "Upgrading Signal ..."; -/* message footer while attachment is uploading */ -"UPLOADING_MESSAGE_TEXT" = "Uploading..."; - /* button text for back button on verification view */ "VERIFICATION_BACK_BUTTON" = "Back";