From 80fbc093d978d6190db1ed0eefdb78b2cdd82d44 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Tue, 28 Mar 2017 15:29:08 -0400 Subject: [PATCH] Handle oversize text messages and arbitrary attachments. // FREEBIE --- Signal.xcodeproj/project.pbxproj | 6 + .../file-black-60.imageset/Contents.json | 23 ++ .../file-icon-60@1x.png | Bin 0 -> 1773 bytes .../file-icon-60@2x.png | Bin 0 -> 2397 bytes .../file-icon-60@3x.png | Bin 0 -> 2941 bytes .../file-white-60.imageset/Contents.json | 23 ++ .../file-white-60@1x.png | Bin 0 -> 1786 bytes .../file-white-60@2x.png | Bin 0 -> 2439 bytes .../file-white-60@3x.png | Bin 0 -> 2954 bytes .../Models/OWSMessagesBubblesSizeCalculator.m | 5 +- .../TSMessageAdapaters/AttachmentUploadView.m | 3 + .../TSMessageAdapaters/TSAnimatedAdapter.h | 4 - .../TSMessageAdapaters/TSAnimatedAdapter.m | 14 -- .../TSGenericAttachmentAdapter.h | 14 ++ .../TSGenericAttachmentAdapter.m | 196 ++++++++++++++++++ .../TSMessageAdapaters/TSMessageAdapter.m | 33 ++- .../TSMessageAdapaters/TSPhotoAdapter.h | 4 - .../TSMessageAdapaters/TSPhotoAdapter.m | 14 -- .../TSVideoAttachmentAdapter.h | 1 - .../TSVideoAttachmentAdapter.m | 4 - .../DebugUITableViewController.m | 53 ++++- .../view controllers/SignalAttachment.swift | 28 +++ .../translations/en.lproj/Localizable.strings | 12 ++ 23 files changed, 388 insertions(+), 49 deletions(-) create mode 100644 Signal/Images.xcassets/file-black-60.imageset/Contents.json create mode 100644 Signal/Images.xcassets/file-black-60.imageset/file-icon-60@1x.png create mode 100644 Signal/Images.xcassets/file-black-60.imageset/file-icon-60@2x.png create mode 100644 Signal/Images.xcassets/file-black-60.imageset/file-icon-60@3x.png create mode 100644 Signal/Images.xcassets/file-white-60.imageset/Contents.json create mode 100644 Signal/Images.xcassets/file-white-60.imageset/file-white-60@1x.png create mode 100644 Signal/Images.xcassets/file-white-60.imageset/file-white-60@2x.png create mode 100644 Signal/Images.xcassets/file-white-60.imageset/file-white-60@3x.png create mode 100644 Signal/src/Models/TSMessageAdapaters/TSGenericAttachmentAdapter.h create mode 100644 Signal/src/Models/TSMessageAdapaters/TSGenericAttachmentAdapter.m diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 319303bf0..85cadd6f9 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -17,6 +17,7 @@ 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 */; }; 345671011E89A5F1006EE662 /* ThreadUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 345671001E89A5F1006EE662 /* ThreadUtil.m */; }; + 3456710A1E8A9F5D006EE662 /* TSGenericAttachmentAdapter.m in Sources */ = {isa = PBXBuildFile; fileRef = 345671091E8A9F5D006EE662 /* TSGenericAttachmentAdapter.m */; }; 34802DD71E899CFB0032EA1D /* DebugUITableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34802DD61E899CFB0032EA1D /* DebugUITableViewController.m */; }; 348A08421E6A044E0057E290 /* MessagesViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 348A08411E6A044E0057E290 /* MessagesViewController.xib */; }; 348A08441E6A1D2C0057E290 /* OWSMessagesToolbarContentView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 348A08431E6A1D2C0057E290 /* OWSMessagesToolbarContentView.xib */; }; @@ -404,6 +405,8 @@ 34535D811E256BE9008A4747 /* UIView+OWS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIView+OWS.m"; sourceTree = ""; }; 345670FF1E89A5F1006EE662 /* ThreadUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ThreadUtil.h; sourceTree = ""; }; 345671001E89A5F1006EE662 /* ThreadUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ThreadUtil.m; sourceTree = ""; }; + 345671081E8A9F5D006EE662 /* TSGenericAttachmentAdapter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSGenericAttachmentAdapter.h; sourceTree = ""; }; + 345671091E8A9F5D006EE662 /* TSGenericAttachmentAdapter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSGenericAttachmentAdapter.m; sourceTree = ""; }; 34802DD51E899CFB0032EA1D /* DebugUITableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DebugUITableViewController.h; sourceTree = ""; }; 34802DD61E899CFB0032EA1D /* DebugUITableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DebugUITableViewController.m; sourceTree = ""; }; 348A08411E6A044E0057E290 /* MessagesViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MessagesViewController.xib; sourceTree = ""; }; @@ -1428,6 +1431,8 @@ 4CE0E3751B95453C007210CF /* TSAnimatedAdapter.h */, 4CE0E3761B954546007210CF /* TSAnimatedAdapter.m */, B6D3CBCE1C1376BE00C039DF /* TSContentAdapters.h */, + 345671081E8A9F5D006EE662 /* TSGenericAttachmentAdapter.h */, + 345671091E8A9F5D006EE662 /* TSGenericAttachmentAdapter.m */, B62D53F51A23CCAD009AAF82 /* TSMessageAdapter.h */, B62D53F61A23CCAD009AAF82 /* TSMessageAdapter.m */, B6A3EB491A423B3800B2236B /* TSPhotoAdapter.h */, @@ -2156,6 +2161,7 @@ 458E38311D6682450094BD24 /* OWSQRCodeScanningViewController.m in Sources */, 451A13B11E13DED2000A50FD /* CallNotificationsAdapter.swift in Sources */, 76EB062418170B33006006FC /* PriorityQueue.m in Sources */, + 3456710A1E8A9F5D006EE662 /* TSGenericAttachmentAdapter.m in Sources */, 450DF2091E0DD2C6003D14BE /* UserNotificationsAdaptee.swift in Sources */, B6BADBE71B88D1AC0086A80D /* LockInteractionController.m in Sources */, 76EB061A18170B33006006FC /* DiscardingLog.m in Sources */, diff --git a/Signal/Images.xcassets/file-black-60.imageset/Contents.json b/Signal/Images.xcassets/file-black-60.imageset/Contents.json new file mode 100644 index 000000000..9dd859a99 --- /dev/null +++ b/Signal/Images.xcassets/file-black-60.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "file-icon-60@1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "file-icon-60@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "file-icon-60@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Signal/Images.xcassets/file-black-60.imageset/file-icon-60@1x.png b/Signal/Images.xcassets/file-black-60.imageset/file-icon-60@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..873b272875b3ef7e8a8ffc1d39bdd693936f0e26 GIT binary patch literal 1773 zcmeAS@N?(olHy`uVBq!ia0vp^HXzKw1|+Ti+$;i8Ea{HEjtmSN`?>!lvI6;>1s;*b z3=G`DAk4@xYmNj^jZ$VvL`j6Nk5zJhu3lnFep0GlMQ#C5HPEmMD* z7iAWdWaj57fXq!y$}cUkRZ;?31P2gzmSmOrNrwBXptL9l?5C7u{nVV)+|<01VxT@ltkwa;7OoM+krjyr5X-=U2=SW@$mLc+ zsm1v@rJx`P&C4vYGqM4D2wfNvA`o59`MCu}sl~-WZFZ(mEy!Z%>LU==*qOuZKo&&T z;9ryp^l2i;q38AY7TN8=P8YBrMjVZ}i#idDEsmUcsu?37XaN2iDOxH&ew$aC` z+6I^stbFrRGSezu5=&C;j0_CTtqhEp^uQ%R{x==!0?*Qm%qzS+FQD&)RVT?S$ojJFZ=`xb`tHFeQ4rIEG}f zzKz=Hci2IIm!&YI#6&=li)(_$i-nDHSygt-6L#!tYP-K+k*kwP!-NIKvaGDULhcs# zJa$hqJ^1?Wjn@{3*L>MB(>Cqd+34eakKCMRzv1GkykLEFmgR%v$+;I*eJw1Q`?{>O zHvMOu-I+7>hW4Zl+3!2DFP$m%`QP|Yuw&^LmYVH8Yifm`U$`-~tX$1P)0VTx{m`z? z$#$N*C06f=JoLT8M#}IPSB>yZ{|7f~)OLqlI$R)esb=33r$0-tN8NWRlHOr7nf-;g zYVM!3vdq@wM&Y9KBU-tYe)H-u86PlpNM5SVJAdX{`IhOvX1QOMPU5q@G&8i*Wa5GM zY6aPU#O>~ts{7aoCop?VzSnp+SeoUKiw@(ZB`^Qo5q{~Y$W!-pf}F~sr4!ZM*Inz+ zf2kVoG-Ec~3%lI>$a}|cKaaFhFPrj`wPGv3Qa$U7gbDMu`!{GdtjkoY=Ml*Z)%NME zXLLE$YJTQ&@`})zzwXFOm+or2+!YjeR^~#my#2yxiDI*T2X|V0dcyl>{a1nJx(gRB z<}tpn{QcVH2kXgs&sFCb{-0%d&opMa^4+O(o-@e4{qpp|9ks>{lNIKN9NMFFDJL{y z)}lH6T4%JC&W5!v)jaic1!cl~p)>9+UJ=cGO7jUX!>`LT??v(J2EAco{i`VFw4?Wo ze&d3!V`<#~RgSF5mythI#=F6**{;zY^&3srap75OY1IrcPNWZQrem@r6`oETG Y|DT=??|lmI^gtDmr>mdKI;Vst0Mr+M=Kufz literal 0 HcmV?d00001 diff --git a/Signal/Images.xcassets/file-black-60.imageset/file-icon-60@2x.png b/Signal/Images.xcassets/file-black-60.imageset/file-icon-60@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..fd1009293d2c9bbf28158c326a2c1d1bba6245e0 GIT binary patch literal 2397 zcmeAS@N?(olHy`uVBq!ia0vp^6(G#P1|%(0%q{^bmUKs7M+SzC{oH>NS%G}c0*}aI z1_o|n5N2eUHAe!dMkO;Oq9nrC$0|8LS1&OoKPgqOBDVmjnt{Ql!V1XDO)W`OsL0L9 zE4HezRRXK90GK4GQ<#=IWDQi z$wiq3C7Jno3LtY6lk!VTY?YKi7Qq3;oh6xR2%GYXq22;|P#+|tZ>VRWk4;-@MJ5hy zAQ_z6Qj+1mDkv?=0sAQ>SwA%=H8(Y{q!_5r5UX{-u!Us=@X|;H|DmcSHI?NFQ0yQqM*Fr0Zt1>^#?3-8iWg& zt_n-6T~Z-xH9Np~>X(p|X$MZIUtzr>E*kvz!$%==-W`Xd`VF?1{^i*A*7mVr)Z-tK z&Mpzj>U-|G^U6GlYG}O~ve|6H1l}Fi{}_MDW#yJn|C;|r;^VdH3;s@7aej&&|Hoin z^Bb*oIoFp5Ugk}1TPLevJwG8=_CaKv-?Ph>``aay_fMJ`{pAM(b6nJeTJvKk@~`_G zV3p)OGofPYguQ%xCnm2jU1e0}ASw{+G!yMy9Ds7pTwsbYHw_&wu?Vp-+qEOk}&Gcm2k);3{)PUxznqKy8B{S=Dp`i_O+9c` zd9Lu3hMWV%HX8Oe?OAJ{RPgWqY|(T~SELl^$n-Z~>*G0!4lH*vd+^J__~_05S!cqE z9QJiaO^m&g>y!KyVeO35k6&k560@-8i6>7l`@xx?ygyC+Cq3u+q6>F}4!1Bq z*VH?G+xG1-uDsfc?sLjDP2ZS9YGPY!kJy?mDBF;K#m)YA-lcD5P5*Ch{b;+6!FK!lvI6;>1s;*b z3=G`DAk4@xYmNj^jY?)nL`j6Nk5zJhu3lnFep0GlMQ#C5H3Nf9g%yyQn_7~nP?4LH zS8P>bs{~eI1!RMS^_3LBN=mYAl_Got6rA&mQWZ?~O!N$t?6?#Z6l{u8(yW49+@RWl zJX@uVl9B=|ef{$Ca=mh6z5JqdeM3u2OML?)eIp}XpbFjM%Dj@q3f;V7Wr!g#b6ir3 zlZ!G7N;32F6hP)CCgqow*eWT3EP?}wJ4-Ut5H{r%L%jv`pgu@O-%!s$ADgz+icB2Z zKr%SBr6j|BRZv=#1NKu&vVLk#YHn&?Nik5LAy(^vVGGxY;>e1`0*GbcK!o_s2IO+9 zpw#00oKjE_gyvGs1{@~boCJkYwXNnb|4F) zYw$111o|`)$`{ zLn;{G&b93q4V7tse=3RTenX13JrnEPXs3RYI#zC}r>@nk;k=@qbq!a$E(Bgs)Mphr za_E*@GE1CQudH3(+X_{~FXZ0P$`_A6?%`7VV4uJjM#-sl-dEW3WE|xu z1b*DE`AT1VUj3$voK;3sH^;x2J!8S|71z`E^L8JdZ+Q4}#5(s5)*Q>~mMyqTBt?yvOzaU$>#kLvdQUGwwo>((f( zelq)f&TqC|J+l^kXU_PzTjc9kG4pLX{w#aX_{i@6Z z?pV!T7vBq#rXn?`?i=1-zELja%njEoEOP^$4lb{e*u1x^R8uUlou!G7lUL^d5xJl% z`_tYmeOaE>bWUTzq~M?{^N+l;%4%A-mFl@d^I;>yNt8d|I=k}*xf4G+E(aWyD4Y1r%Jp`pIra!g2M&A z(*~0o&G~p)-frMIoYt@)cU3ss+?bzJW%o^3F7x#MmvqBNFC$(qKQ^sqiMnKZ@?6cz z6D!lK-?%IPHxg}meI_r#S}o$+<5|JXpPKt+ijKxIroGSn#<*+O-JGzzh6PW%7%g}O zwp=#T*~!>6O`Gk3iNgiI+Z&#+aYU_?Xt>PaXq|g_lC(m`&LjpuW|p^Gc)dq8jRwR3 zqzF}(y^rr0=&WqK`TpsylIlwF`#qQ69f>+^J$Eyg=?3ZIxwEUc2{qm0;LI#ccyNQw z>&J=0H@*doJUnb1RR#yItMLew2ydV03+L`=(tD^s-}lajF7w3915BN>KWJQw-*{=U vY~MegCGMN*ziiJf>%m�IQ*v#_wO!f5lIcm!lvI6;>1s;*b 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 z + +@class TSAttachmentStream; + +@interface TSGenericAttachmentAdapter : JSQMediaItem + +- (instancetype)initWithAttachment:(TSAttachmentStream *)attachment incoming:(BOOL)incoming; + +@end diff --git a/Signal/src/Models/TSMessageAdapaters/TSGenericAttachmentAdapter.m b/Signal/src/Models/TSMessageAdapaters/TSGenericAttachmentAdapter.m new file mode 100644 index 000000000..3627ef66e --- /dev/null +++ b/Signal/src/Models/TSMessageAdapaters/TSGenericAttachmentAdapter.m @@ -0,0 +1,196 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +#import "TSGenericAttachmentAdapter.h" +#import "AttachmentUploadView.h" +#import "TSAttachmentStream.h" +#import "UIColor+JSQMessages.h" +#import "UIColor+OWS.h" +#import "UIFont+OWS.h" +#import +#import +#import +#import + +@interface TSGenericAttachmentAdapter () + +@property (nonatomic) UIView *cachedMediaView; +@property (nonatomic) TSAttachmentStream *attachment; +@property (nonatomic) AttachmentUploadView *attachmentUploadView; +@property (nonatomic) BOOL incoming; +@property (nonatomic) NSString *attachmentId; + +@end + +@implementation TSGenericAttachmentAdapter + +- (instancetype)initWithAttachment:(TSAttachmentStream *)attachment incoming:(BOOL)incoming +{ + self = [super init]; + + if (self) { + _attachment = attachment; + _attachmentId = attachment.uniqueId; + _incoming = incoming; + } + + return self; +} + +- (void)clearCachedMediaViews +{ + [super clearCachedMediaViews]; + _cachedMediaView = nil; +} + +- (void)setAppliesMediaViewMaskAsOutgoing:(BOOL)appliesMediaViewMaskAsOutgoing +{ + [super setAppliesMediaViewMaskAsOutgoing:appliesMediaViewMaskAsOutgoing]; + _cachedMediaView = nil; +} + +// TODO: Should we override hash or mediaHash? +- (NSUInteger)mediaHash +{ + return [self.attachment.uniqueId hash]; +} + +#pragma mark - JSQMessageMediaData protocol + +- (CGFloat)iconSize +{ + return 60.f; +} + +- (CGFloat)hMargin +{ + return 10.f; +} + +- (CGFloat)vMargin +{ + return 10.f; +} + +- (UIFont *)attachmentLabelFont +{ + return [UIFont ows_regularFontWithSize:11.f]; +} + +- (UIFont *)fileTypeLabelFont +{ + return [UIFont ows_mediumFontWithSize:16.f]; +} + +- (UIView *)mediaView +{ + if (_cachedMediaView == nil) { + CGSize viewSize = [self mediaViewDisplaySize]; + UIColor *textColor = (self.incoming ? [UIColor blackColor] : [UIColor whiteColor]); + + JSQMessagesBubbleImageFactory *bubbleFactory = [[JSQMessagesBubbleImageFactory alloc] init]; + JSQMessagesBubbleImage *bubbleImageData = (self.incoming + ? [bubbleFactory incomingMessagesBubbleImageWithColor:[UIColor jsq_messageBubbleLightGrayColor]] + : [bubbleFactory outgoingMessagesBubbleImageWithColor:[UIColor ows_materialBlueColor]]); + UIImage *bubbleImage = [bubbleImageData messageBubbleImage]; + OWSAssert(bubbleImage); + UIImageView *bubbleImageView = [[UIImageView alloc] initWithImage:bubbleImage]; + _cachedMediaView = bubbleImageView; + _cachedMediaView.frame = CGRectMake(0.f, 0.f, viewSize.width, viewSize.height); + + const CGFloat kBubbleTailWidth = 6.f; + CGRect contentFrame = CGRectMake(self.incoming ? kBubbleTailWidth : 0.f, + self.vMargin, + viewSize.width - kBubbleTailWidth, + viewSize.height - self.vMargin * 2.f); + + UIImage *image = [UIImage imageNamed:(self.incoming ? @"file-black-60" : @"file-white-60")]; + OWSAssert(image); + UIImageView *imageView = [[UIImageView alloc] initWithImage:image]; + CGRect iconFrame = CGRectMake(round(contentFrame.origin.x + (contentFrame.size.width - self.iconSize) * 0.5f), + round(contentFrame.origin.y), + self.iconSize, + self.iconSize); + imageView.frame = iconFrame; + [_cachedMediaView addSubview:imageView]; + + NSString *fileExtension = [MIMETypeUtil fileExtensionForMIMEType:self.attachment.contentType]; + if (fileExtension.length < 1) { + fileExtension = NSLocalizedString(@"GENERIC_ATTACHMENT_DEFAULT_TYPE", + @"A default label for attachment whose file extension cannot be determined."); + } + + UILabel *fileTypeLabel = [UILabel new]; + fileTypeLabel.text = fileExtension.uppercaseString; + fileTypeLabel.textColor = textColor; + fileTypeLabel.lineBreakMode = NSLineBreakByTruncatingTail; + fileTypeLabel.font = [self fileTypeLabelFont]; + CGRect fileTypeLabelFrame = CGRectZero; + fileTypeLabelFrame.size = [fileTypeLabel sizeThatFits:CGSizeZero]; + fileTypeLabelFrame.size.width = floor(MIN(self.iconSize * 0.5f, fileTypeLabelFrame.size.width)); + // Center on icon. + fileTypeLabelFrame.origin.x + = round(iconFrame.origin.x + (iconFrame.size.width - fileTypeLabelFrame.size.width) * 0.5f); + fileTypeLabelFrame.origin.y + = round(iconFrame.origin.y + (iconFrame.size.height - fileTypeLabelFrame.size.height) * 0.5f + 5); + fileTypeLabel.frame = fileTypeLabelFrame; + [_cachedMediaView addSubview:fileTypeLabel]; + + UILabel *attachmentLabel = [UILabel new]; + attachmentLabel.text = NSLocalizedString(@"GENERIC_ATTACHMENT_LABEL", @"A label for generic attachments."); + attachmentLabel.textColor = [textColor colorWithAlphaComponent:0.85f]; + attachmentLabel.lineBreakMode = NSLineBreakByTruncatingTail; + attachmentLabel.font = [self attachmentLabelFont]; + [attachmentLabel sizeToFit]; + CGRect attachmentLabelFrame = CGRectZero; + attachmentLabelFrame.size = attachmentLabel.bounds.size; + attachmentLabelFrame.origin.x + = round(contentFrame.origin.x + (contentFrame.size.width - attachmentLabelFrame.size.width) * 0.5f); + attachmentLabelFrame.origin.y + = round(contentFrame.origin.y + contentFrame.size.height - attachmentLabelFrame.size.height); + attachmentLabel.frame = attachmentLabelFrame; + [_cachedMediaView addSubview:attachmentLabel]; + + if (!self.incoming) { + self.attachmentUploadView = [[AttachmentUploadView alloc] initWithAttachment:self.attachment + superview:_cachedMediaView + attachmentStateCallback:nil]; + } + } + + return _cachedMediaView; +} + +- (CGSize)mediaViewDisplaySize +{ + const CGFloat kVSpacing = 1.f; + return CGSizeMake(100, ceil(self.iconSize + self.attachmentLabelFont.lineHeight + kVSpacing + self.vMargin * 2)); +} + +#pragma mark - OWSMessageEditing Protocol + +- (BOOL)canPerformEditingAction:(SEL)action +{ + if (action == @selector(copy:)) { + NSString *utiType = [MIMETypeUtil utiTypeForMIMEType:self.attachment.contentType]; + return utiType.length > 0; + } + return NO; +} + +- (void)performEditingAction:(SEL)action +{ + if (action == @selector(copy:)) { + NSString *utiType = [MIMETypeUtil utiTypeForMIMEType:self.attachment.contentType]; + OWSAssert(utiType.length > 0); + NSData *data = [NSData dataWithContentsOfURL:self.attachment.mediaURL]; + [UIPasteboard.generalPasteboard setData:data forPasteboardType:utiType]; + } else { + // Shouldn't get here, as only supported actions should be exposed via canPerformEditingAction + NSString *actionString = NSStringFromSelector(action); + DDLogError(@"'%@' action unsupported for %@: attachmentId=%@", actionString, [self class], self.attachmentId); + } +} + +@end diff --git a/Signal/src/Models/TSMessageAdapaters/TSMessageAdapter.m b/Signal/src/Models/TSMessageAdapaters/TSMessageAdapter.m index 92bac48d8..bf3082fa4 100644 --- a/Signal/src/Models/TSMessageAdapaters/TSMessageAdapter.m +++ b/Signal/src/Models/TSMessageAdapaters/TSMessageAdapter.m @@ -4,20 +4,21 @@ #import "AttachmentSharing.h" #import "OWSCall.h" +#import "Signal-Swift.h" #import "TSAttachmentPointer.h" #import "TSAttachmentStream.h" #import "TSCall.h" #import "TSContactThread.h" #import "TSContentAdapters.h" #import "TSErrorMessage.h" +#import "TSGenericAttachmentAdapter.h" #import "TSGroupThread.h" #import "TSIncomingMessage.h" #import "TSInfoMessage.h" #import "TSOutgoingMessage.h" -#import "Signal-Swift.h" +#import "TSOversizeTextAttachmentAdapter.h" #import - @interface TSMessageAdapter () // --- @@ -131,7 +132,25 @@ if ([attachment isKindOfClass:[TSAttachmentStream class]]) { TSAttachmentStream *stream = (TSAttachmentStream *)attachment; - if ([stream isAnimated]) { + if ([attachment.contentType isEqualToString:OWSMimeTypeOversizeTextMessage]) { + NSData *textData = [NSData dataWithContentsOfURL:stream.mediaURL]; + NSString *fullText = [[NSString alloc] initWithData:textData encoding:NSUTF8StringEncoding]; + // TODO: Tune this value. + const NSUInteger kMaxTextDisplayLength = 256; + NSString *displayText = fullText; + if (fullText.length > kMaxTextDisplayLength) { + // Trim whitespace before _AND_ after slicing the snipper from the string. + NSString *snippet = + [[[fullText stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] + substringWithRange:NSMakeRange(0, kMaxTextDisplayLength)] + stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + displayText = + [NSString stringWithFormat:NSLocalizedString(@"OVERSIZE_TEXT_DISPLAY_FORMAT", + @"A display format for oversize text messages."), + snippet]; + } + adapter.messageBody = displayText; + } else if ([stream isAnimated]) { adapter.mediaItem = [[TSAnimatedAdapter alloc] initWithAttachment:stream incoming:isIncomingAttachment]; adapter.mediaItem.appliesMediaViewMaskAsOutgoing = @@ -143,13 +162,19 @@ adapter.mediaItem.appliesMediaViewMaskAsOutgoing = [interaction isKindOfClass:[TSOutgoingMessage class]]; break; - } else { + } else if ([stream isVideo]) { adapter.mediaItem = [[TSVideoAttachmentAdapter alloc] initWithAttachment:stream incoming:[interaction isKindOfClass:[TSIncomingMessage class]]]; adapter.mediaItem.appliesMediaViewMaskAsOutgoing = [interaction isKindOfClass:[TSOutgoingMessage class]]; break; + } else { + adapter.mediaItem = [[TSGenericAttachmentAdapter alloc] + initWithAttachment:stream + incoming:[interaction isKindOfClass:[TSIncomingMessage class]]]; + adapter.mediaItem.appliesMediaViewMaskAsOutgoing = YES; + break; } } else if ([attachment isKindOfClass:[TSAttachmentPointer class]]) { TSAttachmentPointer *pointer = (TSAttachmentPointer *)attachment; diff --git a/Signal/src/Models/TSMessageAdapaters/TSPhotoAdapter.h b/Signal/src/Models/TSMessageAdapaters/TSPhotoAdapter.h index 2e40ad11e..b824c2a8c 100644 --- a/Signal/src/Models/TSMessageAdapaters/TSPhotoAdapter.h +++ b/Signal/src/Models/TSMessageAdapaters/TSPhotoAdapter.h @@ -11,10 +11,6 @@ - (instancetype)initWithAttachment:(TSAttachmentStream *)attachment incoming:(BOOL)incoming; -- (BOOL)isImage; -- (BOOL)isAudio; -- (BOOL)isVideo; - @property TSAttachmentStream *attachment; @property NSString *attachmentId; diff --git a/Signal/src/Models/TSMessageAdapaters/TSPhotoAdapter.m b/Signal/src/Models/TSMessageAdapaters/TSPhotoAdapter.m index 7e3ebbb02..71dcb51e2 100644 --- a/Signal/src/Models/TSMessageAdapaters/TSPhotoAdapter.m +++ b/Signal/src/Models/TSMessageAdapaters/TSPhotoAdapter.m @@ -80,20 +80,6 @@ return [self ows_adjustBubbleSize:[super mediaViewDisplaySize] forImage:self.image]; } -- (BOOL)isImage { - return YES; -} - - -- (BOOL)isAudio { - return NO; -} - - -- (BOOL)isVideo { - return NO; -} - #pragma mark - OWSMessageEditing Protocol - (BOOL)canPerformEditingAction:(SEL)action diff --git a/Signal/src/Models/TSMessageAdapaters/TSVideoAttachmentAdapter.h b/Signal/src/Models/TSMessageAdapaters/TSVideoAttachmentAdapter.h index 51ed816d6..0829cdd0a 100644 --- a/Signal/src/Models/TSMessageAdapaters/TSVideoAttachmentAdapter.h +++ b/Signal/src/Models/TSMessageAdapaters/TSVideoAttachmentAdapter.h @@ -15,7 +15,6 @@ - (instancetype)initWithAttachment:(TSAttachmentStream *)attachment incoming:(BOOL)incoming; -- (BOOL)isImage; - (BOOL)isAudio; - (BOOL)isVideo; - (void)setAudioProgressFromFloat:(float)progress; diff --git a/Signal/src/Models/TSMessageAdapaters/TSVideoAttachmentAdapter.m b/Signal/src/Models/TSMessageAdapaters/TSVideoAttachmentAdapter.m index eec46ed1a..93b7b60e8 100644 --- a/Signal/src/Models/TSMessageAdapaters/TSVideoAttachmentAdapter.m +++ b/Signal/src/Models/TSMessageAdapaters/TSVideoAttachmentAdapter.m @@ -46,10 +46,6 @@ return self; } -- (BOOL)isImage { - return NO; -} - - (BOOL)isAudio { return [MIMETypeUtil isSupportedAudioMIMEType:_contentType]; } diff --git a/Signal/src/view controllers/DebugUITableViewController.m b/Signal/src/view controllers/DebugUITableViewController.m index da109a9b9..17e3534c6 100644 --- a/Signal/src/view controllers/DebugUITableViewController.m +++ b/Signal/src/view controllers/DebugUITableViewController.m @@ -3,8 +3,9 @@ // #import "DebugUITableViewController.h" -#import "ThreadUtil.h" #import "Environment.h" +#import "Signal-Swift.h" +#import "ThreadUtil.h" #import NS_ASSUME_NONNULL_BEGIN @@ -220,7 +221,7 @@ NSString * const kDebugUITableCellIdentifier = @"kDebugUITableCellIdentifier"; return self.class.tag; } -#pragma mark - Factory and presentation +#pragma mark - Factory Methods + (void)presentDebugUIForThread:(TSThread *)thread fromViewController:(UIViewController *)fromViewController { @@ -232,11 +233,24 @@ NSString * const kDebugUITableCellIdentifier = @"kDebugUITableCellIdentifier"; [contents addSection:[OWSTableSection sectionWithTitle:@"Messages View" items:@[ - [OWSTableItem actionWithTitle:@"Send 100 messages" + [OWSTableItem actionWithTitle:@"Send 10 messages (1/sec.)" + actionBlock:^{ + [DebugUITableViewController sendTextMessage:10 + thread:thread]; + }], + [OWSTableItem actionWithTitle:@"Send 100 messages (1/sec.)" actionBlock:^{ [DebugUITableViewController sendTextMessage:100 thread:thread]; }], + [OWSTableItem actionWithTitle:@"Send text/x-signal-plain" + actionBlock:^{ + [DebugUITableViewController sendOversizeTextMessage:thread]; + }], + [OWSTableItem actionWithTitle:@"Send unknown/mimetype" + actionBlock:^{ + [DebugUITableViewController sendUnknownMimetypeAttachment:thread]; + }], ]]]; DebugUITableViewController *viewController = [DebugUITableViewController new]; @@ -259,6 +273,39 @@ NSString * const kDebugUITableCellIdentifier = @"kDebugUITableCellIdentifier"; }); } ++ (void)sendOversizeTextMessage:(TSThread *)thread { + OWSMessageSender *messageSender = [Environment getCurrent].messageSender; + NSString *message = @"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse consequat, ligula et tincidunt mattis, nisl risus ultricies justo, vitae dictum augue risus vel ante. Suspendisse convallis bibendum lectus. Etiam molestie nisi ac orci sodales sollicitudin vitae eu quam. Morbi lacinia scelerisque risus. Quisque sagittis mauris enim, ac vestibulum dui commodo quis. Nullam at commodo nisl, ut pulvinar dui. Nunc tempus volutpat sagittis. Vestibulum eget maximus sem, sit amet tristique ex posuere."; + SignalAttachment *attachment = [SignalAttachment oversizeTextAttachmentWithText:message]; + [ThreadUtil sendMessageWithAttachment:attachment + inThread:thread + messageSender:messageSender]; +} + ++ (NSData*)createRandomNSDataOfSize:(size_t)size +{ + OWSAssert(size % 4 == 0); + + NSMutableData* data = [NSMutableData dataWithCapacity:size]; + for (size_t i = 0; i < size / 4; ++i) + { + u_int32_t randomBits = arc4random(); + [data appendBytes:(void *)&randomBits length:4]; + } + return data; +} + ++ (void)sendUnknownMimetypeAttachment:(TSThread *)thread { + OWSMessageSender *messageSender = [Environment getCurrent].messageSender; + SignalAttachment *attachment = [SignalAttachment genericAttachmentWithData:[self createRandomNSDataOfSize:256] + dataUTI:SignalAttachment.kUnknownTestAttachmentUTI]; + [ThreadUtil sendMessageWithAttachment:attachment + inThread:thread + messageSender:messageSender]; +} + +#pragma mark - Presentation + - (void)presentFromViewController:(UIViewController *)fromViewController { OWSAssert(fromViewController); diff --git a/Signal/src/view controllers/SignalAttachment.swift b/Signal/src/view controllers/SignalAttachment.swift index 27d9cf09c..57f6265f5 100644 --- a/Signal/src/view controllers/SignalAttachment.swift +++ b/Signal/src/view controllers/SignalAttachment.swift @@ -69,6 +69,9 @@ class SignalAttachment: NSObject { // See: https://developer.apple.com/library/content/documentation/Miscellaneous/Reference/UTIRef/Articles/System-DeclaredUniformTypeIdentifiers.html let dataUTI: String + static let kOversizeTextAttachmentUTI = "org.whispersystems.oversize-text-attachment" + static let kUnknownTestAttachmentUTI = "org.whispersystems.unknown" + var error: SignalAttachmentError? { didSet { AssertIsOnMainThread() @@ -137,6 +140,12 @@ class SignalAttachment: NSObject { // Returns the MIME type for this attachment or nil if no MIME type // can be identified. var mimeType: String? { + if dataUTI == SignalAttachment.kOversizeTextAttachmentUTI { + return OWSMimeTypeOversizeTextMessage + } + if dataUTI == SignalAttachment.kUnknownTestAttachmentUTI { + return OWSMimeTypeUnknownForTests + } let mimeType = UTTypeCopyPreferredTagWithClass(dataUTI as CFString, kUTTagClassMIMEType) guard mimeType != nil else { return nil @@ -147,6 +156,12 @@ class SignalAttachment: NSObject { // Returns the file extension for this attachment or nil if no file extension // can be identified. var fileExtension: String? { + if dataUTI == SignalAttachment.kOversizeTextAttachmentUTI || + dataUTI == SignalAttachment.kUnknownTestAttachmentUTI { + assert(false) + return "" + } + guard let fileExtension = UTTypeCopyPreferredTagWithClass(dataUTI as CFString, kUTTagClassFilenameExtension) else { return nil @@ -508,6 +523,19 @@ class SignalAttachment: NSObject { maxFileSize : kMaxFileSizeAudio) } + // MARK: Oversize Text Attachments + + // Factory method for oversize text attachments. + // + // NOTE: The attachment returned by this method may not be valid. + // Check the attachment's error property. + public class func oversizeTextAttachment(text: String?) -> SignalAttachment { + return newAttachment(data : text?.data(using: .utf8), + dataUTI : kOversizeTextAttachmentUTI, + validUTISet : nil, + maxFileSize : kMaxFileSizeGeneric) + } + // MARK: Generic Attachments // Factory method for generic attachments. diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings index 38c5757cb..a9ca26be4 100644 --- a/Signal/translations/en.lproj/Localizable.strings +++ b/Signal/translations/en.lproj/Localizable.strings @@ -97,6 +97,9 @@ /* No comment provided by engineer. */ "ATTACHMENT_QUEUED" = "New attachment queued for retrieval."; +/* A message indicating that an attachment of unknown type was received. */ +"ATTACHMENT_UNKNOWN_TYPE" = "Unknown attachment received"; + /* No comment provided by engineer. */ "AUDIO_PERMISSION_MESSAGE" = "Signal requires access to your microphone to work properly. You can grant this permission in the Settings app >> Privacy >> Microphone >> Signal"; @@ -325,6 +328,12 @@ /* Accessibilty label for finishing new group */ "FINISH_GROUP_CREATION_LABEL" = "Finish creating group"; +/* A default label for attachment whose file extension cannot be determined. */ +"GENERIC_ATTACHMENT_DEFAULT_TYPE" = "?"; + +/* A label for generic attachments. */ +"GENERIC_ATTACHMENT_LABEL" = "Attachment"; + /* No comment provided by engineer. */ "GROUP_AVATAR_CHANGED" = "Avatar changed. "; @@ -601,6 +610,9 @@ /* No comment provided by engineer. */ "OUTGOING_INCOMPLETE_CALL" = "Incomplete outgoing call"; +/* A display format for oversize text messages. */ +"OVERSIZE_TEXT_DISPLAY_FORMAT" = "%@… [Tap For More]"; + /* Alert body when verifying with {{contact name}} */ "PRIVACY_VERIFICATION_FAILED_I_HAVE_WRONG_KEY_FOR_THEM" = "This doesn't look like your safety number with %@. Are you verifying the correct contact?";