From 96e155c75e3e6e1a4623778ded8fc562a0d64218 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Thu, 11 May 2017 15:26:37 -0400 Subject: [PATCH] Rework appearance of voice messages and audio attachments. // FREEBIE --- Podfile | 8 +- Podfile.lock | 16 +- Signal.xcodeproj/project.pbxproj | 12 +- .../xcshareddata/Signal.xcscmblueprint | 31 +- .../pause_black_40@1x.png | Bin 1880 -> 1424 bytes .../pause_black_40@2x.png | Bin 2902 -> 1600 bytes .../pause_black_40@3x.png | Bin 3974 -> 1960 bytes .../play_black_40@1x.png | Bin 1874 -> 1407 bytes .../play_black_40@2x.png | Bin 2911 -> 1748 bytes .../play_black_40@3x.png | Bin 3996 -> 2129 bytes .../TSGenericAttachmentAdapter.m | 2 +- .../TSVideoAttachmentAdapter.m | 318 +++++++++++------- Signal/src/UIColor+OWS.h | 2 + Signal/src/UIColor+OWS.m | 14 + Signal/src/UIView+OWS.m | 24 +- Signal/src/util/OWSMath.h | 30 ++ Signal/src/util/OWSMath.m | 9 + Signal/src/views/AudioProgressView.swift | 103 ++++++ 18 files changed, 404 insertions(+), 165 deletions(-) create mode 100644 Signal/src/util/OWSMath.h create mode 100644 Signal/src/util/OWSMath.m create mode 100644 Signal/src/views/AudioProgressView.swift diff --git a/Podfile b/Podfile index 526421c56..53a6f5122 100644 --- a/Podfile +++ b/Podfile @@ -3,10 +3,10 @@ source 'https://github.com/CocoaPods/Specs.git' target 'Signal' do pod 'SocketRocket', :git => 'https://github.com/facebook/SocketRocket.git' - pod 'AxolotlKit', git: 'https://github.com/WhisperSystems/SignalProtocolKit.git' - #pod 'AxolotlKit', path: '../SignalProtocolKit' - pod 'SignalServiceKit', git: 'https://github.com/WhisperSystems/SignalServiceKit.git' - #pod 'SignalServiceKit', path: '../SignalServiceKit' + #pod 'AxolotlKit', git: 'https://github.com/WhisperSystems/SignalProtocolKit.git' + pod 'AxolotlKit', path: '../SignalProtocolKit' + #pod 'SignalServiceKit', git: 'https://github.com/WhisperSystems/SignalServiceKit.git' + pod 'SignalServiceKit', path: '../SignalServiceKit' pod 'OpenSSL' pod 'JSQMessagesViewController', git: 'https://github.com/WhisperSystems/JSQMessagesViewController.git', branch: 'mkirk/position-edit-menu' #pod 'JSQMessagesViewController' path: '../JSQMessagesViewController' diff --git a/Podfile.lock b/Podfile.lock index 93280668d..c07d968eb 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -110,34 +110,28 @@ PODS: - YapDatabase/SQLCipher/Core DEPENDENCIES: - - AxolotlKit (from `https://github.com/WhisperSystems/SignalProtocolKit.git`) + - AxolotlKit (from `../SignalProtocolKit`) - JSQMessagesViewController (from `https://github.com/WhisperSystems/JSQMessagesViewController.git`, branch `mkirk/position-edit-menu`) - OpenSSL - PureLayout - - SignalServiceKit (from `https://github.com/WhisperSystems/SignalServiceKit.git`) + - SignalServiceKit (from `../SignalServiceKit`) - SocketRocket (from `https://github.com/facebook/SocketRocket.git`) EXTERNAL SOURCES: AxolotlKit: - :git: https://github.com/WhisperSystems/SignalProtocolKit.git + :path: ../SignalProtocolKit JSQMessagesViewController: :branch: mkirk/position-edit-menu :git: https://github.com/WhisperSystems/JSQMessagesViewController.git SignalServiceKit: - :git: https://github.com/WhisperSystems/SignalServiceKit.git + :path: ../SignalServiceKit SocketRocket: :git: https://github.com/facebook/SocketRocket.git CHECKOUT OPTIONS: - AxolotlKit: - :commit: bce663486ac34c70594deae8260b3cd29dd086e9 - :git: https://github.com/WhisperSystems/SignalProtocolKit.git JSQMessagesViewController: :commit: 7054e4b13ee5bcd6d524adb6dc9a726e8c466308 :git: https://github.com/WhisperSystems/JSQMessagesViewController.git - SignalServiceKit: - :commit: 2dc7c7cf292a3b97b356a67149c9d17684968f22 - :git: https://github.com/WhisperSystems/SignalServiceKit.git SocketRocket: :commit: 877ac7438be3ad0b45ef5ca3969574e4b97112bf :git: https://github.com/facebook/SocketRocket.git @@ -164,6 +158,6 @@ SPEC CHECKSUMS: UnionFind: c33be5adb12983981d6e827ea94fc7f9e370f52d YapDatabase: b1e43555a34a5298e23a045be96817a5ef0da58f -PODFILE CHECKSUM: 549de6756fe8eab98647be8561b3988361f62e85 +PODFILE CHECKSUM: aa4b7d6fd28dbc083b0342a47c1c96ca54a2899e COCOAPODS: 1.2.1 diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index dd905600a..6793982f3 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 34009B671EC4CB11001D95D1 /* OWSMath.m in Sources */ = {isa = PBXBuildFile; fileRef = 34009B661EC4CB11001D95D1 /* OWSMath.m */; }; 3400C7931EAF89CD008A8584 /* SendExternalFileViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3400C7911EAF89CD008A8584 /* SendExternalFileViewController.m */; }; 3400C7961EAF99F4008A8584 /* SelectThreadViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3400C7951EAF99F4008A8584 /* SelectThreadViewController.m */; }; 3400C7991EAFB772008A8584 /* ThreadViewHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 3400C7981EAFB772008A8584 /* ThreadViewHelper.m */; }; @@ -71,6 +72,7 @@ 34D5CCA91EAE3D30005515DB /* GroupViewHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D5CCA81EAE3D30005515DB /* GroupViewHelper.m */; }; 34D5CCB11EAE7E7F005515DB /* SelectRecipientViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D5CCB01EAE7E7F005515DB /* SelectRecipientViewController.m */; }; 34DFCB851E8E04B500053165 /* AddToBlockListViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34DFCB841E8E04B500053165 /* AddToBlockListViewController.m */; }; + 34E3E5681EC4B19400495BAC /* AudioProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34E3E5671EC4B19400495BAC /* AudioProgressView.swift */; }; 34FD93701E3BD43A00109093 /* OWSAnyTouchGestureRecognizer.m in Sources */ = {isa = PBXBuildFile; fileRef = 34FD936F1E3BD43A00109093 /* OWSAnyTouchGestureRecognizer.m */; }; 450573FE1E78A06D00615BB4 /* OWS103EnableVideoCalling.m in Sources */ = {isa = PBXBuildFile; fileRef = 450573FD1E78A06D00615BB4 /* OWS103EnableVideoCalling.m */; }; 4505C2BF1E648EA300CEBF41 /* ExperienceUpgrade.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4505C2BE1E648EA300CEBF41 /* ExperienceUpgrade.swift */; }; @@ -354,6 +356,8 @@ /* Begin PBXFileReference section */ 1B5E7D6C9007F5E5761D79DD /* libPods-SignalTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-SignalTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 34009B651EC4CB11001D95D1 /* OWSMath.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSMath.h; sourceTree = ""; }; + 34009B661EC4CB11001D95D1 /* OWSMath.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSMath.m; sourceTree = ""; }; 3400C7901EAF89CD008A8584 /* SendExternalFileViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SendExternalFileViewController.h; sourceTree = ""; }; 3400C7911EAF89CD008A8584 /* SendExternalFileViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SendExternalFileViewController.m; sourceTree = ""; }; 3400C7941EAF99F4008A8584 /* SelectThreadViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SelectThreadViewController.h; sourceTree = ""; }; @@ -469,6 +473,7 @@ 34D5CCB01EAE7E7F005515DB /* SelectRecipientViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SelectRecipientViewController.m; sourceTree = ""; }; 34DFCB831E8E04B400053165 /* AddToBlockListViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AddToBlockListViewController.h; sourceTree = ""; }; 34DFCB841E8E04B500053165 /* AddToBlockListViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AddToBlockListViewController.m; sourceTree = ""; }; + 34E3E5671EC4B19400495BAC /* AudioProgressView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AudioProgressView.swift; sourceTree = ""; }; 34FD936E1E3BD43A00109093 /* OWSAnyTouchGestureRecognizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OWSAnyTouchGestureRecognizer.h; path = views/OWSAnyTouchGestureRecognizer.h; sourceTree = ""; }; 34FD936F1E3BD43A00109093 /* OWSAnyTouchGestureRecognizer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OWSAnyTouchGestureRecognizer.m; path = views/OWSAnyTouchGestureRecognizer.m; sourceTree = ""; }; 450573FC1E78A06D00615BB4 /* OWS103EnableVideoCalling.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OWS103EnableVideoCalling.h; path = Migrations/OWS103EnableVideoCalling.h; sourceTree = ""; }; @@ -1256,11 +1261,14 @@ 34D5CC951EA6AFAD005515DB /* OWSContactsSyncing.m */, 45CD81F01DC03A22004C9430 /* OWSLogger.h */, 45CD81F11DC03A22004C9430 /* OWSLogger.m */, + 34009B651EC4CB11001D95D1 /* OWSMath.h */, + 34009B661EC4CB11001D95D1 /* OWSMath.m */, 45666F541D9B2827008FE134 /* OWSScrubbingLogFormatter.h */, 45666F551D9B2827008FE134 /* OWSScrubbingLogFormatter.m */, 4579431C1E7C8CE9008ED0C0 /* Pastelog.h */, 4579431D1E7C8CE9008ED0C0 /* Pastelog.m */, 450DF2041E0D74AC003D14BE /* Platform.swift */, + 4542F0951EBB9E9A00C7EE92 /* Promise+retainUntilComplete.swift */, 76EB04F518170B33006006FC /* StringUtil.h */, 76EB04F618170B33006006FC /* StringUtil.m */, 345670FF1E89A5F1006EE662 /* ThreadUtil.h */, @@ -1273,7 +1281,6 @@ 76EB04FB18170B33006006FC /* Util.h */, 45F170D51E315310003FC1F2 /* Weak.swift */, 45F170CB1E310E22003FC1F2 /* WeakTimer.swift */, - 4542F0951EBB9E9A00C7EE92 /* Promise+retainUntilComplete.swift */, ); path = util; sourceTree = ""; @@ -1291,6 +1298,7 @@ isa = PBXGroup; children = ( 452EA09D1EA7ABE00078744B /* AttachmentPointerView.swift */, + 34E3E5671EC4B19400495BAC /* AudioProgressView.swift */, 45F3AEB51DFDE7900080CE33 /* AvatarImageView.swift */, 451764291DE939FD00EDB8B9 /* ContactCell.swift */, 451764281DE939FD00EDB8B9 /* ContactCell.xib */, @@ -2063,6 +2071,7 @@ 76EB058218170B33006006FC /* Environment.m in Sources */, 34B3F8921E8DF1710035BE1A /* SignalAttachment.swift in Sources */, 45464DBC1DFA041F001D3FD6 /* DataChannelMessage.swift in Sources */, + 34E3E5681EC4B19400495BAC /* AudioProgressView.swift in Sources */, 450DF2051E0D74AC003D14BE /* Platform.swift in Sources */, 3472229F1EB22FFE00E53955 /* AddToGroupViewController.m in Sources */, 45666F561D9B2827008FE134 /* OWSScrubbingLogFormatter.m in Sources */, @@ -2086,6 +2095,7 @@ 45F170AC1E2F0351003FC1F2 /* CallAudioSession.swift in Sources */, 34B3F8801E8DF1700035BE1A /* InviteFlow.swift in Sources */, B68EF9BB1C0B1EBD009C3DCD /* FLAnimatedImageView.m in Sources */, + 34009B671EC4CB11001D95D1 /* OWSMath.m in Sources */, 45F170CC1E310E22003FC1F2 /* WeakTimer.swift in Sources */, 34B3F8871E8DF1700035BE1A /* NotificationSettingsViewController.m in Sources */, A5E9D4BB1A65FAD800E4481C /* TSVideoAttachmentAdapter.m in Sources */, diff --git a/Signal.xcworkspace/xcshareddata/Signal.xcscmblueprint b/Signal.xcworkspace/xcshareddata/Signal.xcscmblueprint index be8da6ae6..5f588d65c 100644 --- a/Signal.xcworkspace/xcshareddata/Signal.xcscmblueprint +++ b/Signal.xcworkspace/xcshareddata/Signal.xcscmblueprint @@ -4,36 +4,43 @@ }, "DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : { - "5D79A077E31B3FE97A3C6613CBFFDD71C314D14C+++ED4C31A" : 0, - "37054CE35CE656680D6FFFA9EE19249E0D149C5E+++901E7D4" : 0, - "8176314449001F06FB0E5B588C62133EAA2FE911+++31C7255" : 9223372036854775807, "8176314449001F06FB0E5B588C62133EAA2FE911+++72E8629" : 9223372036854775807, - "D74FB800F048CB516BB4BC70047F7CC676D291B9+++375B249" : 0, "5D79A077E31B3FE97A3C6613CBFFDD71C314D14C+++0BB03DB" : 0, + "01DE8628B025BC69C8C7D8B4612D57BE2C08B62C+++6A1C9FC" : 0, "ABB939127996C66F7E852A780552ADEEF03C6B13+++69179A3" : 0, + "5D79A077E31B3FE97A3C6613CBFFDD71C314D14C+++ED4C31A" : 0, + "90530B99EB0008E7A50951FDFBE02169118FA649+++EF2C0B3" : 0, + "D74FB800F048CB516BB4BC70047F7CC676D291B9+++375B249" : 0, + "37054CE35CE656680D6FFFA9EE19249E0D149C5E+++901E7D4" : 0, + "8176314449001F06FB0E5B588C62133EAA2FE911+++E19D6E3" : 9223372036854775807, "5D79A077E31B3FE97A3C6613CBFFDD71C314D14C+++03D0758" : 0, "37054CE35CE656680D6FFFA9EE19249E0D149C5E+++E57A04A" : 0, - "8176314449001F06FB0E5B588C62133EAA2FE911+++E19D6E3" : 9223372036854775807, - "90530B99EB0008E7A50951FDFBE02169118FA649+++EF2C0B3" : 0 + "8176314449001F06FB0E5B588C62133EAA2FE911+++31C7255" : 9223372036854775807 }, "DVTSourceControlWorkspaceBlueprintIdentifierKey" : "D0F297E7-A82D-4657-A941-96B268F80ABC", "DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : { - "5D79A077E31B3FE97A3C6613CBFFDD71C314D14C+++ED4C31A" : "Signal-iOS\/", - "37054CE35CE656680D6FFFA9EE19249E0D149C5E+++901E7D4" : "SignalServiceKit\/", - "8176314449001F06FB0E5B588C62133EAA2FE911+++31C7255" : "Signal-iOS-5\/Carthage\/", "8176314449001F06FB0E5B588C62133EAA2FE911+++72E8629" : "Signal-iOS-2\/Carthage\/", - "D74FB800F048CB516BB4BC70047F7CC676D291B9+++375B249" : "Signal-iOS\/Pods\/", "5D79A077E31B3FE97A3C6613CBFFDD71C314D14C+++0BB03DB" : "Signal-iOS-2\/", + "01DE8628B025BC69C8C7D8B4612D57BE2C08B62C+++6A1C9FC" : "SignalProtocolKit\/", "ABB939127996C66F7E852A780552ADEEF03C6B13+++69179A3" : "SocketRocket\/", + "5D79A077E31B3FE97A3C6613CBFFDD71C314D14C+++ED4C31A" : "Signal-iOS\/", + "90530B99EB0008E7A50951FDFBE02169118FA649+++EF2C0B3" : "JSQMessagesViewController\/", + "D74FB800F048CB516BB4BC70047F7CC676D291B9+++375B249" : "Signal-iOS\/Pods\/", + "37054CE35CE656680D6FFFA9EE19249E0D149C5E+++901E7D4" : "SignalServiceKit\/", + "8176314449001F06FB0E5B588C62133EAA2FE911+++E19D6E3" : "Signal-iOS\/Carthage\/", "5D79A077E31B3FE97A3C6613CBFFDD71C314D14C+++03D0758" : "Signal-iOS-5\/", "37054CE35CE656680D6FFFA9EE19249E0D149C5E+++E57A04A" : "SignalServiceKit\/", - "8176314449001F06FB0E5B588C62133EAA2FE911+++E19D6E3" : "Signal-iOS\/Carthage\/", - "90530B99EB0008E7A50951FDFBE02169118FA649+++EF2C0B3" : "JSQMessagesViewController\/" + "8176314449001F06FB0E5B588C62133EAA2FE911+++31C7255" : "Signal-iOS-5\/Carthage\/" }, "DVTSourceControlWorkspaceBlueprintNameKey" : "Signal", "DVTSourceControlWorkspaceBlueprintVersion" : 204, "DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "Signal.xcworkspace", "DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [ + { + "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:WhisperSystems\/SignalProtocolKit.git", + "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", + "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "01DE8628B025BC69C8C7D8B4612D57BE2C08B62C+++6A1C9FC" + }, { "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:WhisperSystems\/SignalProtocolKit.git", "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", diff --git a/Signal/Images.xcassets/audio_pause_black_40.imageset/pause_black_40@1x.png b/Signal/Images.xcassets/audio_pause_black_40.imageset/pause_black_40@1x.png index ce91744d91284be654b36ac7ae8f20a1b175749a..e45cf7d05e191e814214c701dc6d30c8f67563fc 100644 GIT binary patch delta 319 zcmV-F0l@y)4v-75-vbRbEio}vF*7 zKoEuFUt{MftSr2Nw-Kzofp`KN8*48jRw5Q+CmujZqn(X^Z^XdtkdS3pV)F(bnYTNW z9loU4Bw>&M36KB@kU$K9T3~Y1F%Z>f`; z$hT@=e`=>{-#e{)SGt8=*M2C;P+3SW< delta 778 zcmV+l1NHon3)l{@-vbRaEiyP%F*iCeG&(Uclk5Y5e=b+A4FCWF&q+i=GYwQYfSChM1@Q64!*zxI1T$@1RNLp+9qKd zmcSTe9j`@u$od$5!cFkf_Np-)Z$JmkVXjJbfNZZ|5l#${d4kvs%w^6>G)fk4_-D|D zLDeF`Q!uBw2hwJupTHY1aaHmXdo|2$jlOk}e@I^u$NSEZ4m`?`xvkNcb`bReZkI%R ziLU29`wD(OK*srClwC$2HfvaG&ALp)H)xkcUS-Tx5c@$_=mh&rFh<%Iwy$8Vs5V5c zjmAdo=iw1#kkZz|fH6VE{_bFmwBCo-uvRf<#Wgj-HXeuJ;fiOx<0=KE`kmmAXsuzb ze|Bs9g0;+O|XsgC-Cd3Zdt=xdt-%C6Fi5rPnS1v-SR>F2xnu3 zQWK2%{uf(nf_ItCcG$8Gv$BS@?!{`QCYa&84R*zXDz;g}TCZbyQxklSa}$olf-1II z!&)z5c~cY2aCVZXVnGF)iGx!8=nFUmf6-dQS{ZaHH9=plci<$nx}sJMpI5uTH>2U- z;>-DMFh*K)TfC^XS!K7ik<<_Wb9V6i~ zTo@#A*sv~=q{?$dWgAvMoR??;+@_b^j|}%Db5yqF4;4M&c+kmU0{WG)8JNqQaFxo- z;*qff?v%%%SLthvv9%mK!$drEhlBeP{b`s459uS|xY*a`*bM0FuEmGW@!GYPpFU=p z%@p+Y(?@f_k7E2dgddOm8AQ3VK3Ma4>+58w;R*~_ppF&z1-Z(Q)@I3yt^fc407*qo IM6N<$g64mIcK`qY diff --git a/Signal/Images.xcassets/audio_pause_black_40.imageset/pause_black_40@2x.png b/Signal/Images.xcassets/audio_pause_black_40.imageset/pause_black_40@2x.png index af1c730ca989a659f653126d2f16890e38b6ac45..8eba9aebba2a38a8d425d859faad0e03ab483cd4 100644 GIT binary patch delta 496 zcmca6c7SKYeP&)$T|>hVLt`rgQ!68*$#0k&>;GO)p3T6(xZTsmF{Fa=?d^qr%#I?h z51qJ#ChTDmt)wp28=+Dx`!sDUC63DzxKQOYl(Rj)13BFe4CvVCoeDW{aH08GS z+nSxFyB7;`GqMOcFyJ6o#3!u%Ah`W&c7U^`uzoRr9otpIY{5 zRFyTKrTR;h9_Njx-OEp&S-WE1YuP2=t~0P(KAA9^ zDdQCr#{$iSZN`lZvzXGX>xGedlyf2^6H3mPg_v8`-HR(?N|)Vt&!)mZ#Q$yoLAn3i z?p*hM7A_P~+F$x{_8IU0D%(nagc&dWJLB-?f8Q4@ahF>(_1zQuz`iN(`z`mp{5m)2 jZ8D#gG~N*XA=>Dl(dX{}f?4i30}yz+`njxgN@xNA5uC<$ delta 1808 zcmV+r2k-d64AvH~-vbRaEiyP%F*iCeG&(Xklk5Y5f3xiCH2?qx%}GQ-RCodHn@emJ zRUF2_M(`0+q)H)3X-EJAiGmAlEu@JkS|5o~To8Vy=|2fQ@J2Na; z5D+R5e<~0v5GoKV5GoKV5GoKV5GoKVkah(Yrk$`%zE*wL7IC@S+r=Fs=oh~dN5wPZ zh&U!@jw}nfRPH;)ZDNPGR%{U~Y5GrN#>FA=qmsv=V$_1f>Jy2b zDk>fAT8imWu-zgvuBjyO1#L5uX5Z8-KL>H6VU`Fm0 z54&F!xxZQB4~nHT?VLW_FLIva+~;#J8M{h+Ox!1)6-(ojliY>HVkv(?{6lojeXi~2 zL@pY&!p#cwjQFYO<{~#5&{8O@k=i4oYi2;(Pl?xv^}-?r-X(q`y1CJUrb1w=RGt>i ze*ovP7sb-^l|HH)niaWMoDfY1n$VVOJR)`HMH4-!{yiBg?pAJQzUe{R!x`!+n;Kyk zMHBmx`dl@$#tI2IB$_TX=B|iaB#NboiF{lA>&3i)WzYf9^f43BoC~x|#jt4NC|(|~ zdR$=KBBHrn%oG&rm}tf0YL};(S6UoqeNflQq+Xtc@#g|geUYlgB6HPx}@RSvrusf9b>` z=Fj44u@u}q_(`<9v>1O-Op2Y_(c&FdKb2Y7sYL%oiN8==%~kB>D2|!1cDC7Y?;KLZMeBXUOeH=3rQ;ev@$eBS^Cue>6LN%;H#%^Cfd3#(Oy?}`b*7g(f)YCn-_cw z$PEGe88oK;yI$F54(7;Li)v%Me{w}9uPs2kM!>>Eo= zhJ*03G0Km}AL459DZ(!bYOD2I)n5@eNdd;;WqqU*kFjsl`4o_;eKJbYtoF{R-w4fJ zGJz+6M>|zbeNgU?PXUQ@)CpG26EP=DF1p-_Hn<+`vpHMe0)DJWe`lj;52$UKGjegh zz(Y?Y7jJls4c`J@P-MG%%X#C`*qLS@=<{Cm>=FFlt)49g9+pR6;0=$l;aflgza{c4 z7d$U6n(8I{^LMh{rtLK`ZOd;1?RWrq+E1t6pyq9_H&-eO zI3V(xL{dDY9p4a{f7!2y~8 zncWM@VQstj)iM({M`E<0(Taz5JYbHxz0Ou6$ zVwgCD=4mFP#VsCMyTn|9-;;Jxp^%5fJlwHC16u6hqS5{BtIhlXVeU*E&~|r*^2m(_ zD<0Z%TRf}NR&`E0q9@ev6`RGhKrV8U+lq%Kv=sttr1FSp;%UDXcZv19>Jgh9{nKf1jRRGoNYuS#gfysL6a1S~^%7QIqj zD{d6miAzKmliD~g?iE=uHzc~`s!tpdOJ_~eZDJ9VcG#&R%^6r~j2YD_G9x=gW>$+> zNfSjL7n!L%0Aj|S5Z$+{w3!YniA_hvl`O4l+CLDG*J5$J|)Yx?q+s0|T?Hr;B4q1>@Vh`~5;3WsVkZ zm~dgoM^=@EY$jQ$HB3tk4<73hU-ZN>)Vy{R%XpF`s-f0=+}y0OOtdx9;}@A*)_Cj&uYCdKdYzi{&uxu z-LCBZPdCl4dW&W=GWCDyS--0C#o_1SyT0$-_`ZHymEQm4`R{jq-?))qSGlpg%D(0L zuPT!&x6`+sljk>2WB0E-->hk#f9JRBC{=6*%( zKi$jsCJGe%+&j-QD4bi&;-l1#K17<8d2>HlJ&0 zcs<#_%K0rYL{_&n{QovB%k=#FSG!K1ZfUTWEV7?=r8wtJz4K|01v0BI7o}Y)Q0lH; zb8gzVhY@oH|82TAt$E|AlWEQ!+xO}{N?0?W_xF_MjdLz1T-kQ#>R)?K(c?bXcdrUx zRkS5`uh)a8%lF7wmWS0R#MlS=Ti(iZH~l9Z_D|j-v~bH@fe3u5(4Ij0geA`liw~Ic X@bxHf-Z7(+0SG)@{an^LB{Ts5R3&(; delta 2887 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 diff --git a/Signal/Images.xcassets/audio_play_black_40.imageset/play_black_40@1x.png b/Signal/Images.xcassets/audio_play_black_40.imageset/play_black_40@1x.png index 435dd73db6d849f85a3a84711f385de4a3f22f02..97218beab50ec51d296f9ced7f6b4aac1ce4ee31 100644 GIT binary patch delta 302 zcmV+}0nz@_4*v?U-vbRbEio}vF*7x;t~5 z!!^#}5wHn~UR4G!5Y0$CI$1uj2r#3+CS5HsnHvJip7rGi?WYfT0A{QW6wY^do7=M~ zQQNZ+%0DkTsPCB%WbbCc4445kUn+a07*qoM6N<$f>F1A AvH$=8 delta 772 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 delta 2909 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 d@9A+cQwmq|c%$ZC+#{CXWnpS @property (nonatomic) UIImage *image; @property (nonatomic, nullable) UIView *cachedMediaView; @property (nonatomic) TSAttachmentStream *attachment; @property (nonatomic, nullable) UIButton *audioPlayPauseButton; @property (nonatomic, nullable) UILabel *audioBottomLabel; +@property (nonatomic, nullable) AudioProgressView *audioProgressView; @property (nonatomic) BOOL incoming; @property (nonatomic, nullable) AttachmentUploadView *attachmentUploadView; @property (nonatomic) BOOL isAudioPlaying; @@ -59,9 +61,10 @@ NS_ASSUME_NONNULL_BEGIN - (void)clearAllViews { - [_cachedMediaView removeFromSuperview]; - _cachedMediaView = nil; - _attachmentUploadView = nil; + [self.cachedMediaView removeFromSuperview]; + self.cachedMediaView = nil; + self.attachmentUploadView = nil; + self.audioProgressView = nil; } - (void)clearCachedMediaViews @@ -89,7 +92,11 @@ NS_ASSUME_NONNULL_BEGIN OWSAssert([NSThread isMainThread]); self.audioProgressSeconds = progress; - self.audioDurationSeconds = duration; + if (duration > 0) { + self.audioDurationSeconds = duration; + } + + [self updateAudioProgressView]; [self updateAudioBottomLabel]; } @@ -102,35 +109,52 @@ NS_ASSUME_NONNULL_BEGIN [ViewControllerUtils formatDurationSeconds:(long)round(self.audioProgressSeconds)], [ViewControllerUtils formatDurationSeconds:(long)round(self.audioDurationSeconds)]]; } else { - NSError *error; - unsigned long long fileSize = - [[NSFileManager defaultManager] attributesOfItemAtPath:self.attachment.filePath error:&error].fileSize; - OWSAssert(!error); - NSString *bottomText = [ViewControllerUtils formatFileSize:fileSize]; - self.audioBottomLabel.text = bottomText; + self.audioBottomLabel.text = [NSString + stringWithFormat:@"%@", [ViewControllerUtils formatDurationSeconds:(long)round(self.audioDurationSeconds)]]; } } -- (void)setAudioIcon:(UIImage *)image +- (void)setAudioIcon:(UIImage *)icon iconColor:(UIColor *)iconColor { - [_audioPlayPauseButton setImage:image forState:UIControlStateNormal]; - [_audioPlayPauseButton setImage:image forState:UIControlStateDisabled]; - _audioPlayPauseButton.layer.opacity = 0.8f; + icon = [icon imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; + [_audioPlayPauseButton setImage:icon forState:UIControlStateNormal]; + [_audioPlayPauseButton setImage:icon forState:UIControlStateDisabled]; + _audioPlayPauseButton.imageView.tintColor = iconColor; } - (void)setAudioIconToPlay { - [self setAudioIcon:[UIImage imageNamed:(self.incoming ? @"audio_play_black_40" : @"audio_play_white_40")]]; + [self setAudioIcon:[UIImage imageNamed:@"audio_play_black_40"] + iconColor:[self audioColorWithOpacity:self.incoming ? 0.2f : 0.1f]]; } - (void)setAudioIconToPause { - [self setAudioIcon:[UIImage imageNamed:(self.incoming ? @"audio_pause_black_40" : @"audio_pause_white_40")]]; + [self setAudioIcon:[UIImage imageNamed:@"audio_pause_black_40"] + iconColor:[self audioColorWithOpacity:self.incoming ? 0.2f : 0.1f]]; +} + +- (void)setIsAudioPlaying:(BOOL)isAudioPlaying +{ + _isAudioPlaying = isAudioPlaying; + + [self updateAudioProgressView]; +} + +- (void)updateAudioProgressView +{ + [self.audioProgressView + setProgress:(self.audioDurationSeconds > 0 ? self.audioProgressSeconds / self.audioDurationSeconds : 0.f)]; + + self.audioProgressView.horizontalBarColor = [self audioColorWithOpacity:0.75f]; + self.audioProgressView.progressColor + = (self.isAudioPlaying ? [self audioColorWithOpacity:self.incoming ? 0.2f : 0.1f] + : [self audioColorWithOpacity:0.4f]); } #pragma mark - JSQMessageMediaData protocol -- (CGFloat)bubbleHeight +- (CGFloat)audioBubbleHeight { - return 35.f; + return 45.f; } - (CGFloat)iconSize @@ -140,111 +164,32 @@ NS_ASSUME_NONNULL_BEGIN - (CGFloat)vMargin { - return 10.f; + return 5.f; } - (UIColor *)audioTextColor { - return (self.incoming ? [UIColor colorWithWhite:0.2 alpha:1.f] : [UIColor whiteColor]); + return (self.incoming ? [UIColor colorWithWhite:0.2f alpha:1.f] : [UIColor whiteColor]); +} + +- (UIColor *)audioColorWithOpacity:(CGFloat)alpha +{ + return [self.audioTextColor blendWithColor:self.bubbleBackgroundColor alpha:alpha]; +} + +- (UIColor *)bubbleBackgroundColor +{ + return self.incoming ? [UIColor jsq_messageBubbleLightGrayColor] : [UIColor ows_materialBlueColor]; } - (UIView *)mediaView { - CGSize size = [self mediaViewDisplaySize]; if ([self isVideo]) { if (self.cachedMediaView == nil) { - UIImageView *imageView = [[UIImageView alloc] initWithImage:self.image]; - imageView.contentMode = UIViewContentModeScaleAspectFill; - imageView.frame = CGRectMake(0.0f, 0.0f, size.width, size.height); - imageView.clipsToBounds = YES; - [JSQMessagesMediaViewBubbleImageMasker applyBubbleImageMaskToMediaView:imageView - isOutgoing:self.appliesMediaViewMaskAsOutgoing]; - self.cachedMediaView = imageView; - UIImage *img = [UIImage imageNamed:@"play_button"]; - UIImageView *videoPlayButton = [[UIImageView alloc] initWithImage:img]; - videoPlayButton.frame = CGRectMake((size.width / 2) - 18, (size.height / 2) - 18, 37, 37); - [self.cachedMediaView addSubview:videoPlayButton]; - - if (!_incoming) { - self.attachmentUploadView = [[AttachmentUploadView alloc] initWithAttachment:self.attachment - superview:imageView - attachmentStateCallback:^(BOOL isAttachmentReady) { - videoPlayButton.hidden = !isAttachmentReady; - }]; - } + self.cachedMediaView = [self createVideoMediaView]; } } else if ([self isAudio]) { if (self.cachedMediaView == nil) { - CGSize viewSize = [self mediaViewDisplaySize]; - UIColor *textColor = [self audioTextColor]; - - _cachedMediaView = [[UIView alloc] initWithFrame:CGRectMake(0.f, 0.f, viewSize.width, viewSize.height)]; - - _cachedMediaView.backgroundColor - = self.incoming ? [UIColor jsq_messageBubbleLightGrayColor] : [UIColor ows_materialBlueColor]; - [JSQMessagesMediaViewBubbleImageMasker applyBubbleImageMaskToMediaView:_cachedMediaView - isOutgoing:!self.incoming]; - - const CGFloat kBubbleTailWidth = 6.f; - CGRect contentFrame = CGRectMake(self.incoming ? kBubbleTailWidth : 0.f, - self.vMargin, - viewSize.width - kBubbleTailWidth - (self.incoming ? 10 : 15), - viewSize.height - self.vMargin * 2); - - CGRect iconFrame = CGRectMake(round(contentFrame.origin.x + 10.f), - round(contentFrame.origin.y + (contentFrame.size.height - self.iconSize) * 0.5f), - self.iconSize, - self.iconSize); - _audioPlayPauseButton = [[UIButton alloc] initWithFrame:iconFrame]; - _audioPlayPauseButton.enabled = NO; - [_cachedMediaView addSubview:_audioPlayPauseButton]; - - const CGFloat kLabelHSpacing = 3; - const CGFloat kLabelVSpacing = 2; - NSString *topText = - [self.attachment.filename stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; - if (topText.length < 1) { - topText = [MIMETypeUtil fileExtensionForMIMEType:self.attachment.contentType].uppercaseString; - } - if (topText.length < 1) { - topText = NSLocalizedString(@"GENERIC_ATTACHMENT_LABEL", @"A label for generic attachments."); - } - UILabel *topLabel = [UILabel new]; - topLabel.text = topText; - topLabel.textColor = textColor; - topLabel.lineBreakMode = NSLineBreakByTruncatingMiddle; - topLabel.font = [UIFont ows_regularFontWithSize:ScaleFromIPhone5To7Plus(13.f, 15.f)]; - [topLabel sizeToFit]; - [_cachedMediaView addSubview:topLabel]; - - UILabel *audioBottomLabel = [UILabel new]; - self.audioBottomLabel = audioBottomLabel; - [self updateAudioBottomLabel]; - audioBottomLabel.textColor = [textColor colorWithAlphaComponent:0.85f]; - audioBottomLabel.lineBreakMode = NSLineBreakByTruncatingMiddle; - audioBottomLabel.font = [UIFont ows_regularFontWithSize:ScaleFromIPhone5To7Plus(11.f, 13.f)]; - [audioBottomLabel sizeToFit]; - [_cachedMediaView addSubview:audioBottomLabel]; - - CGRect topLabelFrame = CGRectZero; - topLabelFrame.size = topLabel.bounds.size; - topLabelFrame.origin.x = round(iconFrame.origin.x + iconFrame.size.width + kLabelHSpacing); - topLabelFrame.origin.y = round(contentFrame.origin.y - + (contentFrame.size.height - - (topLabel.frame.size.height + audioBottomLabel.frame.size.height + kLabelVSpacing)) - * 0.5f); - topLabelFrame.size.width - = round((contentFrame.origin.x + contentFrame.size.width) - topLabelFrame.origin.x); - topLabel.frame = topLabelFrame; - - CGRect audioBottomLabelFrame = topLabelFrame; - audioBottomLabelFrame.origin.y += topLabelFrame.size.height + kLabelVSpacing; - audioBottomLabel.frame = audioBottomLabelFrame; - - if (!self.incoming) { - self.attachmentUploadView = [[AttachmentUploadView alloc] initWithAttachment:self.attachment - superview:_cachedMediaView - attachmentStateCallback:nil]; - } + self.cachedMediaView = [self createAudioMediaView]; } if (self.isAudioPlaying) { @@ -259,10 +204,150 @@ NS_ASSUME_NONNULL_BEGIN return self.cachedMediaView; } +- (UIView *)createVideoMediaView +{ + OWSAssert([self isVideo]); + + CGSize size = [self mediaViewDisplaySize]; + + UIImageView *imageView = [[UIImageView alloc] initWithImage:self.image]; + imageView.contentMode = UIViewContentModeScaleAspectFill; + imageView.frame = CGRectMake(0.0f, 0.0f, size.width, size.height); + imageView.clipsToBounds = YES; + [JSQMessagesMediaViewBubbleImageMasker applyBubbleImageMaskToMediaView:imageView + isOutgoing:self.appliesMediaViewMaskAsOutgoing]; + UIImage *img = [UIImage imageNamed:@"play_button"]; + UIImageView *videoPlayButton = [[UIImageView alloc] initWithImage:img]; + videoPlayButton.frame = CGRectMake((size.width / 2) - 18, (size.height / 2) - 18, 37, 37); + [imageView addSubview:videoPlayButton]; + + if (!_incoming) { + self.attachmentUploadView = [[AttachmentUploadView alloc] initWithAttachment:self.attachment + superview:imageView + attachmentStateCallback:^(BOOL isAttachmentReady) { + videoPlayButton.hidden = !isAttachmentReady; + }]; + } + + return imageView; +} + +- (BOOL)isVoiceMessage +{ + OWSAssert([self isAudio]); + + return (self.attachment.isVoiceMessage || self.attachment.filename.length < 1); +} + +- (UIView *)createAudioMediaView +{ + OWSAssert([self isAudio]); + + [self ensureAudioDurationSeconds]; + + CGSize viewSize = [self mediaViewDisplaySize]; + UIColor *textColor = [self audioTextColor]; + + UIView *mediaView = [[UIView alloc] initWithFrame:CGRectMake(0.f, 0.f, viewSize.width, viewSize.height)]; + + mediaView.backgroundColor = self.bubbleBackgroundColor; + [JSQMessagesMediaViewBubbleImageMasker applyBubbleImageMaskToMediaView:mediaView isOutgoing:!self.incoming]; + + const CGFloat kBubbleTailWidth = 6.f; + CGRect contentFrame = CGRectMake(self.incoming ? kBubbleTailWidth : 0.f, + self.vMargin, + viewSize.width - kBubbleTailWidth - 15, + viewSize.height - self.vMargin * 2); + + CGRect iconFrame = CGRectMake((CGFloat)round(contentFrame.origin.x + 5.f), + (CGFloat)round(contentFrame.origin.y + (contentFrame.size.height - self.iconSize) * 0.5f), + self.iconSize, + self.iconSize); + _audioPlayPauseButton = [[UIButton alloc] initWithFrame:iconFrame]; + _audioPlayPauseButton.enabled = NO; + [mediaView addSubview:_audioPlayPauseButton]; + + const CGFloat kLabelHSpacing = 3; + const CGFloat kLabelVSpacing = 2; + NSString *topText = [[self.attachment.filename stringByDeletingPathExtension] + stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + if (topText.length < 1) { + topText = [MIMETypeUtil fileExtensionForMIMEType:self.attachment.contentType].uppercaseString; + } + if (topText.length < 1) { + topText = NSLocalizedString(@"GENERIC_ATTACHMENT_LABEL", @"A label for generic attachments."); + } + if (self.isVoiceMessage) { + topText = nil; + } + UILabel *topLabel = [UILabel new]; + topLabel.text = topText; + topLabel.textColor = [textColor colorWithAlphaComponent:0.85f]; + topLabel.lineBreakMode = NSLineBreakByTruncatingMiddle; + topLabel.font = [UIFont ows_regularFontWithSize:ScaleFromIPhone5To7Plus(11.f, 13.f)]; + [topLabel sizeToFit]; + [mediaView addSubview:topLabel]; + + AudioProgressView *audioProgressView = [AudioProgressView new]; + self.audioProgressView = audioProgressView; + audioProgressView.delegate = self; + [self updateAudioProgressView]; + [mediaView addSubview:audioProgressView]; + + UILabel *bottomLabel = [UILabel new]; + self.audioBottomLabel = bottomLabel; + [self updateAudioBottomLabel]; + bottomLabel.textColor = [textColor colorWithAlphaComponent:0.85f]; + bottomLabel.lineBreakMode = NSLineBreakByTruncatingMiddle; + bottomLabel.font = [UIFont ows_regularFontWithSize:ScaleFromIPhone5To7Plus(11.f, 13.f)]; + [bottomLabel sizeToFit]; + [mediaView addSubview:bottomLabel]; + + const CGFloat topLabelHeight = ceil(topLabel.font.lineHeight); + const CGFloat kAudioProgressViewHeight = 12.f; + const CGFloat bottomLabelHeight = ceil(bottomLabel.font.lineHeight); + CGRect labelsBounds = CGRectZero; + labelsBounds.origin.x = (CGFloat)round(iconFrame.origin.x + iconFrame.size.width + kLabelHSpacing); + labelsBounds.size.width = contentFrame.origin.x + contentFrame.size.width - labelsBounds.origin.x; + labelsBounds.size.height = topLabelHeight + kAudioProgressViewHeight + bottomLabelHeight + kLabelVSpacing * 2; + labelsBounds.origin.y + = (CGFloat)round(contentFrame.origin.y + (contentFrame.size.height - labelsBounds.size.height) * 0.5f); + + topLabel.frame = CGRectMake(labelsBounds.origin.x, labelsBounds.origin.y, labelsBounds.size.width, topLabelHeight); + audioProgressView.frame = CGRectMake(labelsBounds.origin.x, + labelsBounds.origin.y + topLabelHeight + kLabelVSpacing, + labelsBounds.size.width, + kAudioProgressViewHeight); + bottomLabel.frame = CGRectMake(labelsBounds.origin.x, + labelsBounds.origin.y + topLabelHeight + kAudioProgressViewHeight + kLabelVSpacing * 2, + labelsBounds.size.width, + bottomLabelHeight); + + if (!self.incoming) { + self.attachmentUploadView = [[AttachmentUploadView alloc] initWithAttachment:self.attachment + superview:mediaView + attachmentStateCallback:nil]; + } + + return mediaView; +} + +- (void)ensureAudioDurationSeconds +{ + if (self.audioDurationSeconds == 0.f) { + NSError *error; + AVAudioPlayer *audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:self.fileURL error:&error]; + OWSAssert(!error); + if (!error) { + self.audioDurationSeconds = (CGFloat)[audioPlayer duration]; + } + } +} + - (CGSize)mediaViewDisplaySize { CGSize size = [super mediaViewDisplaySize]; if ([self isAudio]) { - size.height = ceil(self.bubbleHeight + self.vMargin * 2); + size.height = (CGFloat)ceil(self.audioBubbleHeight + self.vMargin * 2); } else if ([self isVideo]) { return [self ows_adjustBubbleSize:size forImage:self.image]; } @@ -333,6 +418,13 @@ NS_ASSUME_NONNULL_BEGIN } } +#pragma mark - AudioProgressViewDelegate + +- (void)AudioProgressViewWasScrubbedWithProgress:(CGFloat)progress +{ + // TODO: +} + #pragma mark - OWSMessageMediaAdapter - (void)setCellVisible:(BOOL)isVisible diff --git a/Signal/src/UIColor+OWS.h b/Signal/src/UIColor+OWS.h index c0851ac36..f7e99d65e 100644 --- a/Signal/src/UIColor+OWS.h +++ b/Signal/src/UIColor+OWS.h @@ -22,4 +22,6 @@ + (UIColor *)backgroundColorForContact:(NSString *)contactIdentifier; + (UIColor *)colorWithRGBHex:(unsigned long)value; +- (UIColor *)blendWithColor:(UIColor *)otherColor alpha:(CGFloat)alpha; + @end diff --git a/Signal/src/UIColor+OWS.m b/Signal/src/UIColor+OWS.m index e925242b2..73c8c16dc 100644 --- a/Signal/src/UIColor+OWS.m +++ b/Signal/src/UIColor+OWS.m @@ -3,6 +3,7 @@ // #import "Cryptography.h" +#import "OWSMath.h" #import "UIColor+OWS.h" @implementation UIColor (OWS) @@ -125,4 +126,17 @@ return [UIColor colorWithRed:red green:green blue:blue alpha:1.f]; } +- (UIColor *)blendWithColor:(UIColor *)otherColor alpha:(CGFloat)alpha +{ + CGFloat r0, g0, b0, a0; + [self getRed:&r0 green:&g0 blue:&b0 alpha:&a0]; + CGFloat r1, g1, b1, a1; + [otherColor getRed:&r1 green:&g1 blue:&b1 alpha:&a1]; + + return [UIColor colorWithRed:CGFloatLerp(r0, r1, alpha) + green:CGFloatLerp(g0, g1, alpha) + blue:CGFloatLerp(b0, b1, alpha) + alpha:CGFloatLerp(a0, a1, alpha)]; +} + @end diff --git a/Signal/src/UIView+OWS.m b/Signal/src/UIView+OWS.m index 2c34c25ee..29bd42674 100644 --- a/Signal/src/UIView+OWS.m +++ b/Signal/src/UIView+OWS.m @@ -2,31 +2,9 @@ // Copyright (c) 2017 Open Whisper Systems. All rights reserved. // +#import "OWSMath.h" #import "UIView+OWS.h" -// TODO: We'll eventually want to promote these into an OWSMath.h header. -static inline CGFloat Clamp(CGFloat value, CGFloat minValue, CGFloat maxValue) -{ - return MAX(minValue, MIN(maxValue, value)); -} - -static inline CGFloat Clamp01(CGFloat value) -{ - return Clamp(value, 0.f, 1.f); -} - -static inline CGFloat CGFloatLerp(CGFloat left, CGFloat right, CGFloat alpha) -{ - alpha = Clamp01(alpha); - - return (left * (1.f - alpha)) + (right * alpha); -} - -static inline CGFloat CGFloatInverseLerp(CGFloat value, CGFloat minValue, CGFloat maxValue) -{ - return (value - minValue) / (maxValue - minValue); -} - static inline CGFloat ScreenShortDimension() { return MIN([UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height); diff --git a/Signal/src/util/OWSMath.h b/Signal/src/util/OWSMath.h new file mode 100644 index 000000000..ef8b503df --- /dev/null +++ b/Signal/src/util/OWSMath.h @@ -0,0 +1,30 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +NS_ASSUME_NONNULL_BEGIN + +// TODO: We'll eventually want to promote these into an OWSMath.h header. +static inline CGFloat Clamp(CGFloat value, CGFloat minValue, CGFloat maxValue) +{ + return MAX(minValue, MIN(maxValue, value)); +} + +static inline CGFloat Clamp01(CGFloat value) +{ + return Clamp(value, 0.f, 1.f); +} + +static inline CGFloat CGFloatLerp(CGFloat left, CGFloat right, CGFloat alpha) +{ + alpha = Clamp01(alpha); + + return (left * (1.f - alpha)) + (right * alpha); +} + +static inline CGFloat CGFloatInverseLerp(CGFloat value, CGFloat minValue, CGFloat maxValue) +{ + return (value - minValue) / (maxValue - minValue); +} + +NS_ASSUME_NONNULL_END diff --git a/Signal/src/util/OWSMath.m b/Signal/src/util/OWSMath.m new file mode 100644 index 000000000..f802593da --- /dev/null +++ b/Signal/src/util/OWSMath.m @@ -0,0 +1,9 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +#import "OWSMath.h" + +NS_ASSUME_NONNULL_BEGIN + +NS_ASSUME_NONNULL_END diff --git a/Signal/src/views/AudioProgressView.swift b/Signal/src/views/AudioProgressView.swift new file mode 100644 index 000000000..37e26cd87 --- /dev/null +++ b/Signal/src/views/AudioProgressView.swift @@ -0,0 +1,103 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +import UIKit + +@objc protocol AudioProgressViewDelegate: class { + func AudioProgressViewWasScrubbed(progress: CGFloat) +} + +@objc class AudioProgressView: UIView { + + public weak var delegate: AudioProgressViewDelegate? + + override var bounds: CGRect { + didSet { + if oldValue != bounds { + updateSubviews() + } + } + } + + override var frame: CGRect { + didSet { + if oldValue != frame { + updateSubviews() + } + } + } + + var horizontalBarColor = UIColor.black { + didSet { + updateContent() + } + } + + var progressColor = UIColor.blue { + didSet { + updateContent() + } + } + + private let horizontalBarLayer: CAShapeLayer + private let progressLayer: CAShapeLayer + + var progress: CGFloat = 0 { + didSet { + if oldValue != progress { + updateContent() + } + } + } + + @available(*, unavailable, message:"use delegate: constructor instead.") + required init?(coder aDecoder: NSCoder) { + self.horizontalBarLayer = CAShapeLayer() + self.progressLayer = CAShapeLayer() + + super.init(coder: aDecoder) + + assertionFailure() + } + + public required init() { + self.horizontalBarLayer = CAShapeLayer() + self.progressLayer = CAShapeLayer() + + super.init(frame:CGRect.zero) + + self.layer.addSublayer(self.horizontalBarLayer) + self.layer.addSublayer(self.progressLayer) + } + + internal func updateSubviews() { + AssertIsOnMainThread() + + self.horizontalBarLayer.frame = self.bounds + self.progressLayer.frame = self.bounds + + updateContent() + } + + internal func updateContent() { + AssertIsOnMainThread() + + let horizontalBarPath = UIBezierPath() + let horizontalBarHeightFraction = CGFloat(0.25) + let horizontalBarHeight = bounds.size.height * horizontalBarHeightFraction + horizontalBarPath.append(UIBezierPath(rect: CGRect(x: 0, y:(bounds.size.height - horizontalBarHeight) * 0.5, width:bounds.size.width, height:horizontalBarHeight))) + horizontalBarLayer.path = horizontalBarPath.cgPath + horizontalBarLayer.fillColor = horizontalBarColor.cgColor + + let progressHeight = bounds.self.height + let progressWidth = progressHeight * 0.15 + let progressX = (bounds.self.width - progressWidth) * max(0.0, min(1.0, progress)) + let progressBounds = CGRect(x:progressX, y:0, width:progressWidth, height:progressHeight) + let progressCornerRadius = progressWidth * 0.5 + let progressPath = UIBezierPath() + progressPath.append(UIBezierPath(roundedRect: progressBounds, cornerRadius: progressCornerRadius)) + progressLayer.path = progressPath.cgPath + progressLayer.fillColor = progressColor.cgColor + } +}