mirror of
https://github.com/oxen-io/session-ios.git
synced 2023-12-13 21:30:14 +01:00
Unfork JSQMessagesViewController
Geting back on upstream fixes a couple bugs (see ##Bugfixes), and also will make future updates easier. The unforking process was basically this: * move custom message types (Calls and DisplayedMessages) classes from our custom JSQMVC fork into Signal-iOS. * Move any method customization into our subclass. Including ColletionView stuff, bubble sizing, and gesture behavior Bug Fixes --------- * Fix mis-sized incoming media bubbles. Bubble size was being cached by interaction id. Which broke when receiving an attachment. The problem is that incoming media messages were initially the height of a "Downloading Attachment" info message. Instead we use the mediaHash for media messages to expire the bubble size when the media changes. * fix missized bubble when MVC did appear The MessagesViewController isn't sized correctly until ViewWillAppear. This caused the first round of bubbles to be rendered incorrectly (they assumed a larger container than they had). I think is reflected in the current version of the app by a reflow occurring shortly after the view appears. Chores ------ * bump travis to build with xcode8 * specify RQV development team for device build. required by xcode 8 beta Cleanup ------ * Refactor messageing XIB so that elements are hangning outside of the views frame * Fix compiler warning with explicit cast * delete deprecated lineBreakmode, it's the default value anyway. // FREEBIE
This commit is contained in:
parent
987ce5f832
commit
4d320d6015
22 changed files with 1160 additions and 65 deletions
2
Podfile
2
Podfile
|
@ -9,7 +9,7 @@ target 'Signal' do
|
|||
pod 'FFCircularProgressView', '~> 0.5'
|
||||
pod 'SCWaveformView', '~> 1.0'
|
||||
pod 'DJWActionSheet'
|
||||
pod 'JSQMessagesViewController', :git => 'https://github.com/WhisperSystems/JSQMessagesViewController', :branch => 'JSignalQ'
|
||||
pod 'JSQMessagesViewController'
|
||||
target 'SignalTests' do
|
||||
inherit! :search_paths
|
||||
end
|
||||
|
|
14
Podfile.lock
14
Podfile.lock
|
@ -30,7 +30,7 @@ PODS:
|
|||
- DJWActionSheet (1.0.4)
|
||||
- FFCircularProgressView (0.5)
|
||||
- HKDFKit (0.0.3)
|
||||
- JSQMessagesViewController (7.1.0):
|
||||
- JSQMessagesViewController (7.3.3):
|
||||
- JSQSystemSoundPlayer (~> 2.0.1)
|
||||
- JSQSystemSoundPlayer (2.0.1)
|
||||
- libPhoneNumber-iOS (0.8.14)
|
||||
|
@ -115,7 +115,7 @@ PODS:
|
|||
DEPENDENCIES:
|
||||
- DJWActionSheet
|
||||
- FFCircularProgressView (~> 0.5)
|
||||
- JSQMessagesViewController (from `https://github.com/WhisperSystems/JSQMessagesViewController`, branch `JSignalQ`)
|
||||
- JSQMessagesViewController
|
||||
- OpenSSL (~> 1.0.208)
|
||||
- PastelogKit (~> 1.3)
|
||||
- SCWaveformView (~> 1.0)
|
||||
|
@ -123,18 +123,12 @@ DEPENDENCIES:
|
|||
- SocketRocket (from `https://github.com/facebook/SocketRocket.git`)
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
JSQMessagesViewController:
|
||||
:branch: JSignalQ
|
||||
:git: https://github.com/WhisperSystems/JSQMessagesViewController
|
||||
SignalServiceKit:
|
||||
:git: https://github.com/WhisperSystems/SignalServiceKit.git
|
||||
SocketRocket:
|
||||
:git: https://github.com/facebook/SocketRocket.git
|
||||
|
||||
CHECKOUT OPTIONS:
|
||||
JSQMessagesViewController:
|
||||
:commit: 225b1baa11125ea84d4b960d700834b5b0a40ee1
|
||||
:git: https://github.com/WhisperSystems/JSQMessagesViewController
|
||||
SignalServiceKit:
|
||||
:commit: f537b6f19265b0f0845f15b3155cdac4f1913dc6
|
||||
:git: https://github.com/WhisperSystems/SignalServiceKit.git
|
||||
|
@ -150,7 +144,7 @@ SPEC CHECKSUMS:
|
|||
DJWActionSheet: 2fe54b1298a7f0fe44462233752c76a530e0cd80
|
||||
FFCircularProgressView: 683a4ab1e1bd613246a3dffa61503ffdebcde8d8
|
||||
HKDFKit: c058305d6f64b84f28c50bd7aa89574625bcb62a
|
||||
JSQMessagesViewController: ca11f86fa68ca70835f05e169df9244147c1dc40
|
||||
JSQMessagesViewController: 0ee3f80237268192a3e8337fd0d787f1a1bf5a7a
|
||||
JSQSystemSoundPlayer: c5850e77a4363ffd374cd851154b9af93264ed8d
|
||||
libPhoneNumber-iOS: fb165271ebe7fb32e55da97b83219382f2f9d409
|
||||
Mantle: bc40bb061d8c2c6fb48d5083e04d928c3b7f73d9
|
||||
|
@ -167,6 +161,6 @@ SPEC CHECKSUMS:
|
|||
UnionFind: c33be5adb12983981d6e827ea94fc7f9e370f52d
|
||||
YapDatabase: 713d4018cfacbd6e77dd430710ca84730e450980
|
||||
|
||||
PODFILE CHECKSUM: 860bce87f11d7ce3a8a80c10f8d35ef83699531e
|
||||
PODFILE CHECKSUM: 060ff4edf8b7a110984cb2c1ffef3f6e19a6b8b6
|
||||
|
||||
COCOAPODS: 1.0.1
|
||||
|
|
|
@ -9,9 +9,24 @@
|
|||
/* Begin PBXBuildFile section */
|
||||
0DD55B166906AF3368995978 /* libPods-Signal.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 80CD5E19DD23200E7926EEA7 /* libPods-Signal.a */; };
|
||||
30209C98DABCE82064B4EAF5 /* libPods-SignalTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A33D3C7EB4B17BDBD47F0FCC /* libPods-SignalTests.a */; };
|
||||
453D28B31D32B87100D523F0 /* JSQErrorMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = 453D28B01D32B87100D523F0 /* JSQErrorMessage.m */; };
|
||||
453D28B41D32B87100D523F0 /* JSQInfoMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = 453D28B21D32B87100D523F0 /* JSQInfoMessage.m */; };
|
||||
453D28B71D32BA5F00D523F0 /* JSQDisplayedMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = 453D28B61D32BA5F00D523F0 /* JSQDisplayedMessage.m */; };
|
||||
453D28BA1D332DB100D523F0 /* OWSMessagesBubblesSizeCalculator.m in Sources */ = {isa = PBXBuildFile; fileRef = 453D28B91D332DB100D523F0 /* OWSMessagesBubblesSizeCalculator.m */; };
|
||||
453D28BB1D332DB100D523F0 /* OWSMessagesBubblesSizeCalculator.m in Sources */ = {isa = PBXBuildFile; fileRef = 453D28B91D332DB100D523F0 /* OWSMessagesBubblesSizeCalculator.m */; };
|
||||
45843D1F1D2236B30013E85A /* OWSContactsSearcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 45843D1E1D2236B30013E85A /* OWSContactsSearcher.m */; };
|
||||
45843D201D2236B30013E85A /* OWSContactsSearcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 45843D1E1D2236B30013E85A /* OWSContactsSearcher.m */; };
|
||||
45843D221D223BA10013E85A /* OWSContactsSearcherTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 45843D211D223BA10013E85A /* OWSContactsSearcherTest.m */; };
|
||||
45C681B71D305A580050903A /* JSQCall.m in Sources */ = {isa = PBXBuildFile; fileRef = 45C681B61D305A580050903A /* JSQCall.m */; };
|
||||
45C681B81D305A580050903A /* JSQCall.m in Sources */ = {isa = PBXBuildFile; fileRef = 45C681B61D305A580050903A /* JSQCall.m */; };
|
||||
45C681BC1D305C080050903A /* JSQCallCollectionViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 45C681BA1D305C080050903A /* JSQCallCollectionViewCell.m */; };
|
||||
45C681BD1D305C080050903A /* JSQCallCollectionViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 45C681BA1D305C080050903A /* JSQCallCollectionViewCell.m */; };
|
||||
45C681C41D305C9E0050903A /* JSQCallCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 45C681C01D305C9E0050903A /* JSQCallCollectionViewCell.xib */; };
|
||||
45C681C51D305C9E0050903A /* JSQCallCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 45C681C01D305C9E0050903A /* JSQCallCollectionViewCell.xib */; };
|
||||
45C681C61D305C9E0050903A /* JSQDisplayedMessageCollectionViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 45C681C21D305C9E0050903A /* JSQDisplayedMessageCollectionViewCell.m */; };
|
||||
45C681C71D305C9E0050903A /* JSQDisplayedMessageCollectionViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 45C681C21D305C9E0050903A /* JSQDisplayedMessageCollectionViewCell.m */; };
|
||||
45C681C81D305C9E0050903A /* JSQDisplayedMessageCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 45C681C31D305C9E0050903A /* JSQDisplayedMessageCollectionViewCell.xib */; };
|
||||
45C681C91D305C9E0050903A /* JSQDisplayedMessageCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 45C681C31D305C9E0050903A /* JSQDisplayedMessageCollectionViewCell.xib */; };
|
||||
45CB2FA81CB7146C00E1B343 /* Launch Screen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 45CB2FA71CB7146C00E1B343 /* Launch Screen.storyboard */; };
|
||||
4CE0E3771B954546007210CF /* TSAnimatedAdapter.m in Sources */ = {isa = PBXBuildFile; fileRef = 4CE0E3761B954546007210CF /* TSAnimatedAdapter.m */; };
|
||||
701231B518ECAA4500D456C4 /* EvpMessageDigest.m in Sources */ = {isa = PBXBuildFile; fileRef = 701231B418ECAA4500D456C4 /* EvpMessageDigest.m */; };
|
||||
|
@ -491,10 +506,26 @@
|
|||
|
||||
/* Begin PBXFileReference section */
|
||||
453CC0361D08E1A60040EBA3 /* sn */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sn; path = translations/sn.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
453D28AF1D32B87100D523F0 /* JSQErrorMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSQErrorMessage.h; sourceTree = "<group>"; };
|
||||
453D28B01D32B87100D523F0 /* JSQErrorMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQErrorMessage.m; sourceTree = "<group>"; };
|
||||
453D28B11D32B87100D523F0 /* JSQInfoMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSQInfoMessage.h; sourceTree = "<group>"; };
|
||||
453D28B21D32B87100D523F0 /* JSQInfoMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQInfoMessage.m; sourceTree = "<group>"; };
|
||||
453D28B51D32BA5F00D523F0 /* JSQDisplayedMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSQDisplayedMessage.h; sourceTree = "<group>"; };
|
||||
453D28B61D32BA5F00D523F0 /* JSQDisplayedMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQDisplayedMessage.m; sourceTree = "<group>"; };
|
||||
453D28B81D332DB100D523F0 /* OWSMessagesBubblesSizeCalculator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSMessagesBubblesSizeCalculator.h; sourceTree = "<group>"; };
|
||||
453D28B91D332DB100D523F0 /* OWSMessagesBubblesSizeCalculator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSMessagesBubblesSizeCalculator.m; sourceTree = "<group>"; };
|
||||
454B35071D08EED80026D658 /* mk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = mk; path = translations/mk.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
45843D1D1D2236B30013E85A /* OWSContactsSearcher.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSContactsSearcher.h; sourceTree = "<group>"; };
|
||||
45843D1E1D2236B30013E85A /* OWSContactsSearcher.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSContactsSearcher.m; sourceTree = "<group>"; };
|
||||
45843D211D223BA10013E85A /* OWSContactsSearcherTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSContactsSearcherTest.m; sourceTree = "<group>"; };
|
||||
45C681B51D305A580050903A /* JSQCall.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSQCall.h; sourceTree = "<group>"; };
|
||||
45C681B61D305A580050903A /* JSQCall.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQCall.m; sourceTree = "<group>"; };
|
||||
45C681B91D305C080050903A /* JSQCallCollectionViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSQCallCollectionViewCell.h; sourceTree = "<group>"; };
|
||||
45C681BA1D305C080050903A /* JSQCallCollectionViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQCallCollectionViewCell.m; sourceTree = "<group>"; };
|
||||
45C681C01D305C9E0050903A /* JSQCallCollectionViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = JSQCallCollectionViewCell.xib; sourceTree = "<group>"; };
|
||||
45C681C11D305C9E0050903A /* JSQDisplayedMessageCollectionViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSQDisplayedMessageCollectionViewCell.h; sourceTree = "<group>"; };
|
||||
45C681C21D305C9E0050903A /* JSQDisplayedMessageCollectionViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQDisplayedMessageCollectionViewCell.m; sourceTree = "<group>"; };
|
||||
45C681C31D305C9E0050903A /* JSQDisplayedMessageCollectionViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = JSQDisplayedMessageCollectionViewCell.xib; sourceTree = "<group>"; };
|
||||
45CB2FA71CB7146C00E1B343 /* Launch Screen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = "Launch Screen.storyboard"; path = "Signal/src/util/Launch Screen.storyboard"; sourceTree = SOURCE_ROOT; };
|
||||
45E282DE1D08E67800ADD4C8 /* gl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = gl; path = translations/gl.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
45E282DF1D08E6CC00ADD4C8 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = id; path = translations/id.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
|
@ -1094,6 +1125,16 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
B62D53F41A23CC8B009AAF82 /* TSMessageAdapters */,
|
||||
453D28B51D32BA5F00D523F0 /* JSQDisplayedMessage.h */,
|
||||
453D28B61D32BA5F00D523F0 /* JSQDisplayedMessage.m */,
|
||||
453D28AF1D32B87100D523F0 /* JSQErrorMessage.h */,
|
||||
453D28B01D32B87100D523F0 /* JSQErrorMessage.m */,
|
||||
453D28B11D32B87100D523F0 /* JSQInfoMessage.h */,
|
||||
453D28B21D32B87100D523F0 /* JSQInfoMessage.m */,
|
||||
45C681B51D305A580050903A /* JSQCall.h */,
|
||||
45C681B61D305A580050903A /* JSQCall.m */,
|
||||
453D28B81D332DB100D523F0 /* OWSMessagesBubblesSizeCalculator.h */,
|
||||
453D28B91D332DB100D523F0 /* OWSMessagesBubblesSizeCalculator.m */,
|
||||
);
|
||||
path = Models;
|
||||
sourceTree = "<group>";
|
||||
|
@ -1638,6 +1679,12 @@
|
|||
76EB052B18170B33006006FC /* Views */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
45C681C11D305C9E0050903A /* JSQDisplayedMessageCollectionViewCell.h */,
|
||||
45C681C21D305C9E0050903A /* JSQDisplayedMessageCollectionViewCell.m */,
|
||||
45C681C31D305C9E0050903A /* JSQDisplayedMessageCollectionViewCell.xib */,
|
||||
45C681B91D305C080050903A /* JSQCallCollectionViewCell.h */,
|
||||
45C681BA1D305C080050903A /* JSQCallCollectionViewCell.m */,
|
||||
45C681C01D305C9E0050903A /* JSQCallCollectionViewCell.xib */,
|
||||
A5509ECB1A69B1D600ABA4BC /* CountryCodeTableViewCell.h */,
|
||||
A5509ECC1A69B1D600ABA4BC /* CountryCodeTableViewCell.m */,
|
||||
FCAC963D19FEF99A0046DFC5 /* InboxTableViewCell.h */,
|
||||
|
@ -2399,6 +2446,7 @@
|
|||
A5509ECA1A69AB8B00ABA4BC /* Storyboard.storyboard in Resources */,
|
||||
A507A3B11A6C60E300BEED0D /* InboxTableViewCell.xib in Resources */,
|
||||
AD83FF421A73426500B5C81A /* audio_play_button.png in Resources */,
|
||||
45C681C41D305C9E0050903A /* JSQCallCollectionViewCell.xib in Resources */,
|
||||
B633C5C41A1D190B0059AC12 /* mute_on@2x.png in Resources */,
|
||||
B633C5CE1A1D190B0059AC12 /* quit@2x.png in Resources */,
|
||||
AD83FF441A73426500B5C81A /* audio_pause_button.png in Resources */,
|
||||
|
@ -2407,6 +2455,7 @@
|
|||
B633C59D1A1D190B0059AC12 /* endcall@2x.png in Resources */,
|
||||
FC5CDF391A3393DD00B47253 /* error_white@2x.png in Resources */,
|
||||
B633C5D21A1D190B0059AC12 /* savephoto@2x.png in Resources */,
|
||||
45C681C81D305C9E0050903A /* JSQDisplayedMessageCollectionViewCell.xib in Resources */,
|
||||
B10C9B611A7049EC00ECA2BF /* play_icon.png in Resources */,
|
||||
AD83FF401A73426500B5C81A /* audio_pause_button_blue@2x.png in Resources */,
|
||||
B66DBF4A19D5BBC8006EA940 /* Images.xcassets in Resources */,
|
||||
|
@ -2443,6 +2492,8 @@
|
|||
files = (
|
||||
B660F6D41C29868000687D6E /* whisperFake.cer in Resources */,
|
||||
76EB060118170B33006006FC /* InitiateSignal.proto in Resources */,
|
||||
45C681C91D305C9E0050903A /* JSQDisplayedMessageCollectionViewCell.xib in Resources */,
|
||||
45C681C51D305C9E0050903A /* JSQCallCollectionViewCell.xib in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -2601,6 +2652,7 @@
|
|||
76EB05A618170B33006006FC /* RtpPacket.m in Sources */,
|
||||
76EB064218170B33006006FC /* StringUtil.m in Sources */,
|
||||
A547DD741A70A87800103EC7 /* DJWActionSheet+OWS.m in Sources */,
|
||||
45C681B71D305A580050903A /* JSQCall.m in Sources */,
|
||||
76EB062618170B33006006FC /* Queue.m in Sources */,
|
||||
D221A09A169C9E5E00537ABF /* main.m in Sources */,
|
||||
45843D1F1D2236B30013E85A /* OWSContactsSearcher.m in Sources */,
|
||||
|
@ -2611,15 +2663,18 @@
|
|||
76EB05FE18170B33006006FC /* InitiateSignal.pb.m in Sources */,
|
||||
76EB05CA18170B33006006FC /* RecipientUnavailable.m in Sources */,
|
||||
E197B61418BBEC1A00F073E5 /* DropoutTracker.m in Sources */,
|
||||
453D28B41D32B87100D523F0 /* JSQInfoMessage.m in Sources */,
|
||||
FCAC963C19FEF9280046DFC5 /* SignalsViewController.m in Sources */,
|
||||
76EB05DA18170B33006006FC /* LowLatencyConnector.m in Sources */,
|
||||
76EB05EE18170B33006006FC /* CallTermination.m in Sources */,
|
||||
B66B9F7D1AEAF40500E2E609 /* NotificationSettingsOptionsViewController.m in Sources */,
|
||||
453D28B31D32B87100D523F0 /* JSQErrorMessage.m in Sources */,
|
||||
E1CD329618BCFF9900B1A496 /* SoundInstance.m in Sources */,
|
||||
76EB05B418170B33006006FC /* HashChain.m in Sources */,
|
||||
76EB05E418170B33006006FC /* UdpSocket.m in Sources */,
|
||||
76EB058218170B33006006FC /* Environment.m in Sources */,
|
||||
76EB064418170B33006006FC /* ThreadManager.m in Sources */,
|
||||
45C681C61D305C9E0050903A /* JSQDisplayedMessageCollectionViewCell.m in Sources */,
|
||||
E197B61E18BBEC6D00F073E5 /* AudioRouter.m in Sources */,
|
||||
E197B60D18BBEC1A00F073E5 /* AudioSocket.m in Sources */,
|
||||
A5D0699B1A50E9CB004CB540 /* ShowGroupMembersViewController.m in Sources */,
|
||||
|
@ -2632,6 +2687,7 @@
|
|||
B63761ED19E1FBE8005735D1 /* HttpRequestOrResponse.m in Sources */,
|
||||
76EB05A018170B33006006FC /* IpAddress.m in Sources */,
|
||||
FCAC965119FF0A6E0046DFC5 /* MessagesViewController.m in Sources */,
|
||||
453D28BA1D332DB100D523F0 /* OWSMessagesBubblesSizeCalculator.m in Sources */,
|
||||
B68EF9BB1C0B1EBD009C3DCD /* FLAnimatedImageView.m in Sources */,
|
||||
A5E9D4BB1A65FAD800E4481C /* TSVideoAttachmentAdapter.m in Sources */,
|
||||
E197B61118BBEC1A00F073E5 /* AudioProcessor.m in Sources */,
|
||||
|
@ -2670,6 +2726,7 @@
|
|||
E16E5BF018AAC40200B7C403 /* EvpKeyAgreement.m in Sources */,
|
||||
FCFD25821A154B3800F4C644 /* CodeVerificationViewController.m in Sources */,
|
||||
B65EDA1219E1BE6400AAA7CB /* RPAPICall.m in Sources */,
|
||||
453D28B71D32BA5F00D523F0 /* JSQDisplayedMessage.m in Sources */,
|
||||
76EB05DC18170B33006006FC /* StreamPair.m in Sources */,
|
||||
76EB064618170B33006006FC /* TimeUtil.m in Sources */,
|
||||
70BAFD5D190584BE00FA5E0B /* NotificationTracker.m in Sources */,
|
||||
|
@ -2707,6 +2764,7 @@
|
|||
B63761E319E1F487005735D1 /* AFHTTPSessionManager+SignalMethods.m in Sources */,
|
||||
76EB05CC18170B33006006FC /* ShortAuthenticationStringGenerator.m in Sources */,
|
||||
E16E5BEF18AAC40200B7C403 /* EC25KeyAgreementProtocol.m in Sources */,
|
||||
45C681BC1D305C080050903A /* JSQCallCollectionViewCell.m in Sources */,
|
||||
76EB064018170B33006006FC /* AnonymousTerminator.m in Sources */,
|
||||
76EB058818170B33006006FC /* PropertyListPreferences.m in Sources */,
|
||||
76EB05B218170B33006006FC /* DH3KKeyAgreementProtocol.m in Sources */,
|
||||
|
@ -2785,6 +2843,7 @@
|
|||
B660F7341C29988E00687D6E /* RtpSocket.m in Sources */,
|
||||
B660F7351C29988E00687D6E /* SequenceCounter.m in Sources */,
|
||||
B660F7361C29988E00687D6E /* SrtpSocket.m in Sources */,
|
||||
45C681C71D305C9E0050903A /* JSQDisplayedMessageCollectionViewCell.m in Sources */,
|
||||
B660F7371C29988E00687D6E /* SrtpStream.m in Sources */,
|
||||
B660F7381C29988E00687D6E /* DH3KKeyAgreementParticipant.m in Sources */,
|
||||
B660F7391C29988E00687D6E /* DH3KKeyAgreementProtocol.m in Sources */,
|
||||
|
@ -2805,6 +2864,7 @@
|
|||
B660F7481C29988E00687D6E /* RecipientUnavailable.m in Sources */,
|
||||
45843D201D2236B30013E85A /* OWSContactsSearcher.m in Sources */,
|
||||
B660F7491C29988E00687D6E /* ShortAuthenticationStringGenerator.m in Sources */,
|
||||
45C681BD1D305C080050903A /* JSQCallCollectionViewCell.m in Sources */,
|
||||
B660F74A1C29988E00687D6E /* ZrtpHandshakeResult.m in Sources */,
|
||||
B660F74B1C29988E00687D6E /* ZrtpHandshakeSocket.m in Sources */,
|
||||
B660F74C1C29988E00687D6E /* ZrtpInitiator.m in Sources */,
|
||||
|
@ -2815,6 +2875,7 @@
|
|||
B660F7511C29988E00687D6E /* StreamPair.m in Sources */,
|
||||
B660F7521C29988E00687D6E /* Certificate.m in Sources */,
|
||||
B660F7531C29988E00687D6E /* NetworkStream.m in Sources */,
|
||||
453D28BB1D332DB100D523F0 /* OWSMessagesBubblesSizeCalculator.m in Sources */,
|
||||
B660F7541C29988E00687D6E /* SecureEndPoint.m in Sources */,
|
||||
B660F7551C29988E00687D6E /* UdpSocket.m in Sources */,
|
||||
45843D221D223BA10013E85A /* OWSContactsSearcherTest.m in Sources */,
|
||||
|
@ -2835,6 +2896,7 @@
|
|||
B660F7641C29988E00687D6E /* InitiateSignal.pb.m in Sources */,
|
||||
B660F7651C29988E00687D6E /* InitiatorSessionDescriptor.m in Sources */,
|
||||
B660F7661C29988E00687D6E /* ResponderSessionDescriptor.m in Sources */,
|
||||
45C681B81D305A580050903A /* JSQCall.m in Sources */,
|
||||
B660F7671C29988E00687D6E /* SignalUtil.m in Sources */,
|
||||
B660F7681C29988E00687D6E /* CategorizingLogger.m in Sources */,
|
||||
B660F7691C29988E00687D6E /* DecayingSampleEstimator.m in Sources */,
|
||||
|
|
76
Signal/src/Models/JSQCall.h
Normal file
76
Signal/src/Models/JSQCall.h
Normal file
|
@ -0,0 +1,76 @@
|
|||
//
|
||||
// JSQCall.h
|
||||
// JSQMessages
|
||||
//
|
||||
// Created by Dylan Bourgeois on 20/11/14.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "JSQMessageData.h"
|
||||
#import "TSMessageAdapter.h"
|
||||
|
||||
typedef enum : NSUInteger {
|
||||
kCallOutgoing = 1,
|
||||
kCallIncoming = 2,
|
||||
kCallMissed = 3,
|
||||
kGroupUpdateJoin = 4,
|
||||
kGroupUpdateLeft = 5,
|
||||
kGroupUpdate = 6
|
||||
} CallStatus;
|
||||
|
||||
|
||||
@interface JSQCall : NSObject <JSQMessageData, NSCoding, NSCopying>
|
||||
|
||||
/*
|
||||
* Returns the string Id of the user who initiated the call
|
||||
*/
|
||||
@property (copy, nonatomic, readonly) NSString *senderId;
|
||||
|
||||
|
||||
/*
|
||||
* Returns the display name for user who initiated the call
|
||||
*/
|
||||
@property (copy, nonatomic, readonly) NSString *senderDisplayName;
|
||||
|
||||
/*
|
||||
* Returns date of the call
|
||||
*/
|
||||
@property (copy, nonatomic, readonly) NSDate *date;
|
||||
|
||||
/*
|
||||
* Returns the call status
|
||||
* @see CallStatus
|
||||
*/
|
||||
@property (nonatomic) CallStatus status;
|
||||
|
||||
/*
|
||||
* Returns message type for adapter
|
||||
*/
|
||||
@property (nonatomic) TSMessageAdapterType messageType;
|
||||
|
||||
/*
|
||||
* User can configure whether a thumbnail is used in the display of this cell or not
|
||||
*/
|
||||
@property (nonatomic) BOOL useThumbnail;
|
||||
|
||||
/**
|
||||
* String to be displayed
|
||||
*/
|
||||
|
||||
@property (nonatomic, copy) NSString *detailString;
|
||||
|
||||
|
||||
#pragma mark - Initialization
|
||||
|
||||
- (instancetype)initWithCallerId:(NSString *)callerId
|
||||
callerDisplayName:(NSString *)callerDisplayName
|
||||
date:(NSDate *)date
|
||||
status:(CallStatus)status
|
||||
displayString:(NSString*)detailString;
|
||||
|
||||
-(NSString*)dateText;
|
||||
|
||||
-(UIImage*)thumbnailImage;
|
||||
|
||||
@end
|
170
Signal/src/Models/JSQCall.m
Normal file
170
Signal/src/Models/JSQCall.m
Normal file
|
@ -0,0 +1,170 @@
|
|||
//
|
||||
// JSQCall.m
|
||||
// JSQMessages
|
||||
//
|
||||
// Created by Dylan Bourgeois on 20/11/14.
|
||||
//
|
||||
|
||||
#import "JSQCall.h"
|
||||
|
||||
#import "JSQMessagesTimestampFormatter.h"
|
||||
#import "UIImage+JSQMessages.h"
|
||||
|
||||
@implementation JSQCall
|
||||
|
||||
#pragma mark - Initialzation
|
||||
|
||||
-(instancetype)initWithCallerId:(NSString *)senderId
|
||||
callerDisplayName:(NSString *)senderDisplayName
|
||||
date:(NSDate *)date
|
||||
status:(CallStatus)status
|
||||
displayString:(NSString *)detailString
|
||||
{
|
||||
NSParameterAssert(senderId != nil);
|
||||
NSParameterAssert(senderDisplayName != nil);
|
||||
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_senderId = [senderId copy];
|
||||
_senderDisplayName = [senderDisplayName copy];
|
||||
_date = [date copy];
|
||||
_status = status;
|
||||
_messageType = TSCallAdapter;
|
||||
_detailString = [detailString stringByAppendingFormat:@" "];
|
||||
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
-(id)init
|
||||
{
|
||||
NSAssert(NO,@"%s is not a valid initializer for %@. Use %@ instead", __PRETTY_FUNCTION__, [self class], NSStringFromSelector(@selector(initWithCallerId:callerDisplayName:date:status:displayString:)));
|
||||
return nil;
|
||||
}
|
||||
|
||||
-(void)dealloc
|
||||
{
|
||||
_senderId = nil;
|
||||
_senderDisplayName = nil;
|
||||
_date = nil;
|
||||
}
|
||||
|
||||
-(NSString*)dateText
|
||||
{
|
||||
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
|
||||
dateFormatter.timeStyle = NSDateFormatterShortStyle;
|
||||
dateFormatter.dateStyle = NSDateFormatterMediumStyle;
|
||||
dateFormatter.doesRelativeDateFormatting = YES;
|
||||
return [dateFormatter stringFromDate:_date];
|
||||
}
|
||||
|
||||
-(UIImage*)thumbnailImage {
|
||||
// This relies on those assets being in the project
|
||||
if(!_useThumbnail) {
|
||||
return nil;
|
||||
}
|
||||
switch (_status) {
|
||||
case kCallOutgoing:
|
||||
return [UIImage imageNamed:@"statCallOutgoing--blue"];
|
||||
break;
|
||||
case kCallIncoming:
|
||||
case kCallMissed:
|
||||
return [UIImage imageNamed:@"statCallIncoming--blue"];
|
||||
break;
|
||||
case kGroupUpdate:
|
||||
return [UIImage imageNamed:@"statRefreshedGroup--blue"];
|
||||
break;
|
||||
case kGroupUpdateLeft:
|
||||
return [UIImage imageNamed:@"statLeftGroup--blue"];
|
||||
break;
|
||||
case kGroupUpdateJoin:
|
||||
return [UIImage imageNamed:@"statJoinedGroup--blue"];
|
||||
break;
|
||||
default:
|
||||
return nil;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - NSObject
|
||||
|
||||
-(BOOL)isEqual:(id)object
|
||||
{
|
||||
if (self==object) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
if (![object isKindOfClass:[self class]])
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
JSQCall * aCall = (JSQCall*)object;
|
||||
|
||||
return [self.senderId isEqualToString:aCall.senderId]
|
||||
&& [self.senderDisplayName isEqualToString:aCall.senderDisplayName]
|
||||
&& ([self.date compare:aCall.date] == NSOrderedSame)
|
||||
&& self.status == aCall.status;
|
||||
}
|
||||
|
||||
-(NSUInteger)hash
|
||||
{
|
||||
return self.senderId.hash ^ self.date.hash;
|
||||
}
|
||||
|
||||
-(NSString*)description
|
||||
{
|
||||
return [NSString stringWithFormat:@"<%@: senderId=%@, senderDisplayName=%@, date=%@>",
|
||||
[self class], self.senderId, self.senderDisplayName, self.date];
|
||||
}
|
||||
|
||||
#pragma mark - JSQMessageData
|
||||
|
||||
//TODO I'm not sure this is right. It affects bubble rendering.
|
||||
- (BOOL)isMediaMessage {
|
||||
return NO;
|
||||
}
|
||||
|
||||
#pragma mark - NSCoding
|
||||
|
||||
-(instancetype)initWithCoder:(NSCoder *)aDecoder
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_senderId = [aDecoder decodeObjectForKey:NSStringFromSelector(@selector(senderId))];
|
||||
_senderDisplayName = [aDecoder decodeObjectForKey:NSStringFromSelector(@selector(senderDisplayName))];
|
||||
_date = [aDecoder decodeObjectForKey:NSStringFromSelector(@selector(date))];
|
||||
_status = (CallStatus)[aDecoder decodeIntegerForKey:NSStringFromSelector(@selector(status))];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)aCoder
|
||||
{
|
||||
[aCoder encodeObject:self.senderId forKey:NSStringFromSelector(@selector(senderId))];
|
||||
[aCoder encodeObject:self.senderDisplayName forKey:NSStringFromSelector(@selector(senderDisplayName))];
|
||||
[aCoder encodeObject:self.date forKey:NSStringFromSelector(@selector(date))];
|
||||
[aCoder encodeDouble:self.status forKey:NSStringFromSelector(@selector(status))];
|
||||
}
|
||||
|
||||
#pragma mark - NSCopying
|
||||
|
||||
-(instancetype)copyWithZone:(NSZone *)zone
|
||||
{
|
||||
return [[[self class] allocWithZone:zone]initWithCallerId:self.senderId
|
||||
callerDisplayName:self.senderDisplayName
|
||||
date:self.date
|
||||
status:self.status
|
||||
displayString:self.detailString];
|
||||
}
|
||||
|
||||
- (NSUInteger)messageHash{
|
||||
return self.hash;
|
||||
}
|
||||
|
||||
- (NSString *)text{
|
||||
return _detailString;
|
||||
}
|
||||
|
||||
@end
|
45
Signal/src/Models/JSQDisplayedMessage.h
Normal file
45
Signal/src/Models/JSQDisplayedMessage.h
Normal file
|
@ -0,0 +1,45 @@
|
|||
//
|
||||
// JSQDisplayedMessage.h
|
||||
// JSQMessages
|
||||
//
|
||||
// Created by Dylan Bourgeois on 29/11/14.
|
||||
// Copyright (c) 2014 Hexed Bits. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "JSQMessageData.h"
|
||||
#import "TSMessageAdapter.h"
|
||||
|
||||
/* JSQDisplayed message is the parent class for displaying information to the user
|
||||
* from within the conversation view. Do not use directly :
|
||||
*
|
||||
* @see JSQInfoMessage
|
||||
* @see JSQErrorMessage
|
||||
*
|
||||
*/
|
||||
|
||||
@interface JSQDisplayedMessage : NSObject <JSQMessageData>
|
||||
|
||||
/*
|
||||
* Returns the unique identifier of the person affected by the displayed message
|
||||
*/
|
||||
@property (copy, nonatomic, readonly) NSString *senderId;
|
||||
|
||||
|
||||
/*
|
||||
* Returns the name of the person affected by the displayed message
|
||||
*/
|
||||
@property (copy, nonatomic, readonly) NSString *senderDisplayName;
|
||||
|
||||
/*
|
||||
* Returns date of the displayed message
|
||||
*/
|
||||
@property (copy, nonatomic, readonly) NSDate *date;
|
||||
|
||||
#pragma mark - Initializer
|
||||
|
||||
-(instancetype)initWithSenderId:(NSString*)senderId
|
||||
senderDisplayName:(NSString*)senderDisplayName
|
||||
date:(NSDate*)date;
|
||||
|
||||
@end
|
44
Signal/src/Models/JSQDisplayedMessage.m
Normal file
44
Signal/src/Models/JSQDisplayedMessage.m
Normal file
|
@ -0,0 +1,44 @@
|
|||
//
|
||||
// JSQDisplayedMessage.m
|
||||
// JSQMessages
|
||||
//
|
||||
// Created by Dylan Bourgeois on 29/11/14.
|
||||
// Copyright (c) 2014 Hexed Bits. All rights reserved.
|
||||
//
|
||||
|
||||
#import "JSQDisplayedMessage.h"
|
||||
|
||||
@implementation JSQDisplayedMessage
|
||||
|
||||
-(id)init
|
||||
{
|
||||
NSAssert(NO,@"%s is not a valid initializer for %@. Use %@ instead", __PRETTY_FUNCTION__, [self class], NSStringFromSelector(@selector(initWithSenderId:senderDisplayName:date:)));
|
||||
return nil;
|
||||
}
|
||||
|
||||
-(instancetype)initWithSenderId:(NSString*)senderId
|
||||
senderDisplayName:(NSString*)senderDisplayName
|
||||
date:(NSDate*)date
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self) {
|
||||
_senderId = [senderId copy];
|
||||
_senderDisplayName = [senderDisplayName copy];
|
||||
_date = [date copy];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSUInteger)messageHash
|
||||
{
|
||||
return self.date.hash ^ self.senderId.hash;
|
||||
}
|
||||
|
||||
- (BOOL)isMediaMessage
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
36
Signal/src/Models/JSQErrorMessage.h
Normal file
36
Signal/src/Models/JSQErrorMessage.h
Normal file
|
@ -0,0 +1,36 @@
|
|||
//
|
||||
// JSQErrorMessage.h
|
||||
// JSQMessages
|
||||
//
|
||||
// Created by Dylan Bourgeois on 29/11/14.
|
||||
// Copyright (c) 2014 Hexed Bits. All rights reserved.
|
||||
//
|
||||
|
||||
#import "JSQDisplayedMessage.h"
|
||||
|
||||
typedef NS_ENUM(NSInteger, JSQErrorMessageType){
|
||||
JSQErrorMessageNoSession,
|
||||
JSQErrorMessageWrongTrustedIdentityKey,
|
||||
JSQErrorMessageInvalidKeyException,
|
||||
JSQErrorMessageMissingKeyId,
|
||||
JSQErrorMessageInvalidMessage,
|
||||
JSQErrorMessageDuplicateMessage,
|
||||
JSQErrorMessageInvalidVersion
|
||||
};
|
||||
|
||||
@interface JSQErrorMessage : JSQDisplayedMessage
|
||||
|
||||
@property (nonatomic) JSQErrorMessageType errorMessageType;
|
||||
|
||||
@property (nonatomic) TSMessageAdapterType messageType;
|
||||
|
||||
#pragma mark - Initialization
|
||||
|
||||
- (instancetype)initWithErrorType:(JSQErrorMessageType)messageType
|
||||
senderId:(NSString*)senderId
|
||||
senderDisplayName:(NSString*)senderDisplayName
|
||||
date:(NSDate*)date;
|
||||
|
||||
- (NSString*)text;
|
||||
|
||||
@end
|
75
Signal/src/Models/JSQErrorMessage.m
Normal file
75
Signal/src/Models/JSQErrorMessage.m
Normal file
|
@ -0,0 +1,75 @@
|
|||
//
|
||||
// JSQErrorMessage.m
|
||||
// JSQMessages
|
||||
//
|
||||
// Created by Dylan Bourgeois on 29/11/14.
|
||||
// Copyright (c) 2014 Hexed Bits. All rights reserved.
|
||||
//
|
||||
|
||||
#import "JSQErrorMessage.h"
|
||||
|
||||
@implementation JSQErrorMessage
|
||||
|
||||
- (instancetype)initWithErrorType:(JSQErrorMessageType)messageType
|
||||
senderId:(NSString *)senderId
|
||||
senderDisplayName:(NSString *)senderDisplayName
|
||||
date:(NSDate *)date
|
||||
{
|
||||
self = [super initWithSenderId:senderId senderDisplayName:senderDisplayName date:date];
|
||||
|
||||
if (self) {
|
||||
_errorMessageType = messageType;
|
||||
_messageType = TSErrorMessageAdapter;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString*)text
|
||||
{
|
||||
switch (self.errorMessageType) {
|
||||
case JSQErrorMessageNoSession:
|
||||
return [NSString stringWithFormat:@"No session error"];
|
||||
break;
|
||||
case JSQErrorMessageWrongTrustedIdentityKey:
|
||||
return [NSString stringWithFormat:@"Error : Wrong trusted identity key for %@.", self.senderDisplayName];
|
||||
break;
|
||||
case JSQErrorMessageInvalidKeyException:
|
||||
return [NSString stringWithFormat:@"Error : Invalid key exception for %@.", self.senderDisplayName];
|
||||
break;
|
||||
case JSQErrorMessageMissingKeyId:
|
||||
return [NSString stringWithFormat:@"Error: Missing key identifier for %@", self.senderDisplayName];
|
||||
break;
|
||||
case JSQErrorMessageInvalidMessage:
|
||||
return [NSString stringWithFormat:@"Error: Invalid message"];
|
||||
break;
|
||||
case JSQErrorMessageDuplicateMessage:
|
||||
return [NSString stringWithFormat:@"Error: Duplicate message"];
|
||||
break;
|
||||
case JSQErrorMessageInvalidVersion:
|
||||
return [NSString stringWithFormat:@"Error: Invalid version for contact %@.", self.senderDisplayName];
|
||||
break;
|
||||
|
||||
default:
|
||||
return nil;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSUInteger)hash
|
||||
{
|
||||
return self.senderId.hash ^ self.date.hash;
|
||||
}
|
||||
|
||||
- (NSString*)description
|
||||
{
|
||||
return [NSString stringWithFormat:@"<%@: senderId=%@, senderDisplayName=%@, date=%@, type=%ld>",
|
||||
[self class], self.senderId, self.senderDisplayName, self.date, self.errorMessageType];
|
||||
}
|
||||
|
||||
-(TSMessageAdapterType)messageType
|
||||
{
|
||||
return TSErrorMessageAdapter;
|
||||
}
|
||||
|
||||
@end
|
31
Signal/src/Models/JSQInfoMessage.h
Normal file
31
Signal/src/Models/JSQInfoMessage.h
Normal file
|
@ -0,0 +1,31 @@
|
|||
//
|
||||
// JSQInfoMessage.h
|
||||
// JSQMessages
|
||||
//
|
||||
// Created by Dylan Bourgeois on 29/11/14.
|
||||
// Copyright (c) 2014 Hexed Bits. All rights reserved.
|
||||
//
|
||||
|
||||
#import "JSQDisplayedMessage.h"
|
||||
|
||||
typedef NS_ENUM(NSInteger, JSQInfoMessageType){
|
||||
JSQInfoMessageTypeSessionDidEnd,
|
||||
};
|
||||
|
||||
@interface JSQInfoMessage : JSQDisplayedMessage
|
||||
|
||||
@property (nonatomic) JSQInfoMessageType infoMessageType;
|
||||
|
||||
@property (nonatomic) TSMessageAdapterType messageType;
|
||||
|
||||
#pragma mark - Initialization
|
||||
|
||||
- (instancetype)initWithInfoType:(JSQInfoMessageType)messageType
|
||||
senderId:(NSString*)senderId
|
||||
senderDisplayName:(NSString*)senderDisplayName
|
||||
date:(NSDate*)date;
|
||||
|
||||
- (NSString*)text;
|
||||
|
||||
|
||||
@end
|
54
Signal/src/Models/JSQInfoMessage.m
Normal file
54
Signal/src/Models/JSQInfoMessage.m
Normal file
|
@ -0,0 +1,54 @@
|
|||
//
|
||||
// JSQInfoMessage.m
|
||||
// JSQMessages
|
||||
//
|
||||
// Created by Dylan Bourgeois on 29/11/14.
|
||||
// Copyright (c) 2014 Hexed Bits. All rights reserved.
|
||||
//
|
||||
|
||||
#import "JSQInfoMessage.h"
|
||||
|
||||
@implementation JSQInfoMessage
|
||||
|
||||
- (instancetype)initWithInfoType:(JSQInfoMessageType)messageType
|
||||
senderId:(NSString *)senderId
|
||||
senderDisplayName:(NSString *)senderDisplayName
|
||||
date:(NSDate *)date
|
||||
{
|
||||
//@discussion: NSParameterAssert() ?
|
||||
|
||||
self = [super initWithSenderId:senderId senderDisplayName:senderDisplayName date:date];
|
||||
|
||||
if (self) {
|
||||
_infoMessageType = messageType;
|
||||
_messageType = TSInfoMessageAdapter;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
-(NSString*)text
|
||||
{
|
||||
switch (self.infoMessageType) {
|
||||
case JSQInfoMessageTypeSessionDidEnd:
|
||||
return [NSString stringWithFormat:@"Session with %@ ended.", self.senderDisplayName];
|
||||
break;
|
||||
|
||||
default:
|
||||
return nil;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
-(NSUInteger)hash
|
||||
{
|
||||
return self.senderId.hash ^ self.date.hash;
|
||||
}
|
||||
|
||||
-(NSString*)description
|
||||
{
|
||||
return [NSString stringWithFormat:@"<%@: senderId=%@, senderDisplayName=%@, date=%@, type=%ld>",
|
||||
[self class], self.senderId, self.senderDisplayName, self.date, self.infoMessageType];
|
||||
}
|
||||
|
||||
@end
|
13
Signal/src/Models/OWSMessagesBubblesSizeCalculator.h
Normal file
13
Signal/src/Models/OWSMessagesBubblesSizeCalculator.h
Normal file
|
@ -0,0 +1,13 @@
|
|||
//
|
||||
// OWSMessagesBubblesSizeCalculator.h
|
||||
// Signal
|
||||
//
|
||||
// Created by Michael Kirk on 7/10/16.
|
||||
// Copyright © 2016 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import <JSQMessagesViewController/JSQMessagesBubblesSizeCalculator.h>
|
||||
|
||||
@interface OWSMessagesBubblesSizeCalculator : JSQMessagesBubblesSizeCalculator
|
||||
|
||||
@end
|
45
Signal/src/Models/OWSMessagesBubblesSizeCalculator.m
Normal file
45
Signal/src/Models/OWSMessagesBubblesSizeCalculator.m
Normal file
|
@ -0,0 +1,45 @@
|
|||
//
|
||||
// OWSMessagesBubblesSizeCalculator.m
|
||||
// Signal
|
||||
//
|
||||
// Created by Michael Kirk on 7/10/16.
|
||||
// Copyright © 2016 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "OWSMessagesBubblesSizeCalculator.h"
|
||||
#import "TSMessageAdapter.h"
|
||||
#import "JSQDisplayedMessageCollectionViewCell.h"
|
||||
|
||||
@implementation OWSMessagesBubblesSizeCalculator
|
||||
|
||||
/**
|
||||
* Computes and returns the size of the `messageBubbleImageView` property
|
||||
* of a `JSQMessagesCollectionViewCell` for the specified messageData at indexPath.
|
||||
*
|
||||
* @param messageData A message data object.
|
||||
* @param indexPath The index path at which messageData is located.
|
||||
* @param layout The layout object asking for this information.
|
||||
*
|
||||
* @return A sizes that specifies the required dimensions to display the entire message contents.
|
||||
* Note, this is *not* the entire cell, but only its message bubble.
|
||||
*/
|
||||
- (CGSize)messageBubbleSizeForMessageData:(id<JSQMessageData>)messageData
|
||||
atIndexPath:(NSIndexPath *)indexPath
|
||||
withLayout:(JSQMessagesCollectionViewFlowLayout *)layout
|
||||
{
|
||||
CGSize superSize = [super messageBubbleSizeForMessageData:messageData
|
||||
atIndexPath:indexPath
|
||||
withLayout:layout];
|
||||
|
||||
TSMessageAdapter *message = (TSMessageAdapter *)messageData;
|
||||
if (message.messageType == TSInfoMessageAdapter ||
|
||||
message.messageType == TSErrorMessageAdapter) {
|
||||
|
||||
// Prevent cropping message text by accounting for message container/icon
|
||||
superSize.height = OWSDisplayedMessageCellHeight;
|
||||
}
|
||||
|
||||
return superSize;
|
||||
}
|
||||
|
||||
@end
|
|
@ -15,6 +15,16 @@
|
|||
|
||||
#define ME_MESSAGE_IDENTIFIER @"Me";
|
||||
|
||||
typedef NS_ENUM(NSInteger, TSMessageAdapterType) {
|
||||
TSIncomingMessageAdapter,
|
||||
TSOutgoingMessageAdapter,
|
||||
TSCallAdapter,
|
||||
TSInfoMessageAdapter,
|
||||
TSErrorMessageAdapter,
|
||||
TSMediaAttachmentAdapter,
|
||||
TSGenericTextMessageAdapter, //Used when message direction is unknown (outgoing or incoming)
|
||||
};
|
||||
|
||||
@interface TSMessageAdapter : NSObject <JSQMessageData>
|
||||
|
||||
+ (id<JSQMessageData>)messageViewDataWithInteraction:(TSInteraction *)interaction inThread:(TSThread *)thread;
|
||||
|
|
|
@ -58,6 +58,7 @@
|
|||
+ (id<JSQMessageData>)messageViewDataWithInteraction:(TSInteraction *)interaction inThread:(TSThread *)thread {
|
||||
TSMessageAdapter *adapter = [[TSMessageAdapter alloc] init];
|
||||
adapter.messageDate = interaction.date;
|
||||
// TODO casting a string to an integer? At least need a comment here explaining why we are doing this.
|
||||
adapter.identifier = (NSUInteger)interaction.uniqueId;
|
||||
|
||||
if ([thread isKindOfClass:[TSContactThread class]]) {
|
||||
|
@ -230,7 +231,7 @@
|
|||
if (self.thread) {
|
||||
return _thread.name;
|
||||
}
|
||||
return self.senderDisplayName;
|
||||
return _senderDisplayName;
|
||||
}
|
||||
|
||||
- (NSDate *)date {
|
||||
|
@ -249,8 +250,13 @@
|
|||
return self.messageBody;
|
||||
}
|
||||
|
||||
- (NSUInteger)messageHash {
|
||||
return self.identifier;
|
||||
- (NSUInteger)messageHash
|
||||
{
|
||||
if (self.isMediaMessage) {
|
||||
return [self.mediaItem mediaHash];
|
||||
} else {
|
||||
return self.identifier;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSInteger)messageState {
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#import "FingerprintViewController.h"
|
||||
#import "FullImageViewController.h"
|
||||
#import "JSQCallCollectionViewCell.h"
|
||||
#import "JSQDisplayedMessageCollectionViewCell.h"
|
||||
#import "MessagesViewController.h"
|
||||
#import "NSDate+millisecondTimeStamp.h"
|
||||
#import "NewGroupViewController.h"
|
||||
|
@ -30,7 +31,16 @@
|
|||
#import "TSAttachmentPointer.h"
|
||||
#import "TSContentAdapters.h"
|
||||
#import "TSDatabaseView.h"
|
||||
#import "OWSMessagesBubblesSizeCalculator.h"
|
||||
//TODO should JSQInfoMessage be rolled into JSQDisplayedMessageCollectionViewCell?
|
||||
#import "JSQInfoMessage.h"
|
||||
#import "TSInfoMessage.h"
|
||||
//TODO should JSQErrorMessage be rolled into JSQDisplayedMessageCollectionViewCell?
|
||||
#import "JSQErrorMessage.h"
|
||||
#import "TSErrorMessage.h"
|
||||
//TODO should JSQCall be rolled into JSQCallCollectionViewCell?
|
||||
#import "JSQCall.h"
|
||||
#import "TSCall.h"
|
||||
#import "TSIncomingMessage.h"
|
||||
#import "TSInvalidIdentityKeyErrorMessage.h"
|
||||
#import "TSMessagesManager+attachments.h"
|
||||
|
@ -154,6 +164,12 @@ typedef enum : NSUInteger {
|
|||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
// JSQMVC width is 375px at this point (as specified by the xib), but this causes
|
||||
// our initial bubble calculations to be off since they happen before the containing
|
||||
// view is layed out. https://github.com/jessesquires/JSQMessagesViewController/issues/1257
|
||||
// Resetting here makes sure we've got a good initial width.
|
||||
[self resetFrame];
|
||||
|
||||
[self.navigationController.navigationBar setTranslucent:NO];
|
||||
|
||||
self.messageAdapterCache = [[NSCache alloc] init];
|
||||
|
@ -182,13 +198,22 @@ typedef enum : NSUInteger {
|
|||
[self initializeTextView];
|
||||
|
||||
[JSQMessagesCollectionViewCell registerMenuAction:@selector(delete:)];
|
||||
self.collectionView.collectionViewLayout.bubbleSizeCalculator = [[OWSMessagesBubblesSizeCalculator alloc] init];
|
||||
|
||||
[self initializeCollectionViewLayout];
|
||||
[self registerCustomMessageNibs];
|
||||
|
||||
self.senderId = ME_MESSAGE_IDENTIFIER;
|
||||
self.senderDisplayName = ME_MESSAGE_IDENTIFIER;
|
||||
}
|
||||
|
||||
- (void)registerCustomMessageNibs
|
||||
{
|
||||
[self.collectionView registerNib:[JSQCallCollectionViewCell nib]
|
||||
forCellWithReuseIdentifier:[JSQCallCollectionViewCell cellReuseIdentifier]];
|
||||
|
||||
[self.collectionView registerNib:[JSQDisplayedMessageCollectionViewCell nib]
|
||||
forCellWithReuseIdentifier:[JSQDisplayedMessageCollectionViewCell cellReuseIdentifier]];
|
||||
}
|
||||
|
||||
- (void)toggleObservers:(BOOL)shouldObserve {
|
||||
|
@ -499,13 +524,10 @@ typedef enum : NSUInteger {
|
|||
|
||||
- (void)initializeBubbles {
|
||||
JSQMessagesBubbleImageFactory *bubbleFactory = [[JSQMessagesBubbleImageFactory alloc] init];
|
||||
self.incomingBubbleImageData = [bubbleFactory incomingMessagesBubbleImageWithColor:[UIColor jsq_messageBubbleLightGrayColor]];
|
||||
self.outgoingBubbleImageData = [bubbleFactory outgoingMessagesBubbleImageWithColor:[UIColor ows_materialBlueColor]];
|
||||
self.incomingBubbleImageData =
|
||||
[bubbleFactory incomingMessagesBubbleImageWithColor:[UIColor jsq_messageBubbleLightGrayColor]];
|
||||
self.currentlyOutgoingBubbleImageData =
|
||||
[bubbleFactory outgoingMessageFailedBubbleImageWithColor:[UIColor ows_fadedBlueColor]];
|
||||
|
||||
self.outgoingMessageFailedImageData = [bubbleFactory outgoingMessageFailedBubbleImageWithColor:[UIColor grayColor]];
|
||||
self.currentlyOutgoingBubbleImageData = [bubbleFactory outgoingMessagesBubbleImageWithColor:[UIColor ows_fadedBlueColor]];
|
||||
self.outgoingMessageFailedImageData = [bubbleFactory outgoingMessagesBubbleImageWithColor:[UIColor grayColor]];
|
||||
}
|
||||
|
||||
- (void)initializeCollectionViewLayout {
|
||||
|
@ -686,11 +708,13 @@ typedef enum : NSUInteger {
|
|||
}
|
||||
|
||||
- (id<JSQMessageBubbleImageDataSource>)collectionView:(JSQMessagesCollectionView *)collectionView
|
||||
messageBubbleImageDataForItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
id<JSQMessageData> message = [self messageAtIndexPath:indexPath];
|
||||
messageBubbleImageDataForItemAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
TSInteraction *message = [self interactionAtIndexPath:indexPath];
|
||||
|
||||
if ([message.senderId isEqualToString:self.senderId]) {
|
||||
switch (message.messageState) {
|
||||
if ([message isKindOfClass:[TSOutgoingMessage class]]) {
|
||||
TSOutgoingMessage *outgoingMessage = (TSOutgoingMessage *)message;
|
||||
switch (outgoingMessage.messageState) {
|
||||
case TSOutgoingMessageStateUnsent:
|
||||
return self.outgoingMessageFailedImageData;
|
||||
case TSOutgoingMessageStateAttemptingOut:
|
||||
|
@ -711,25 +735,45 @@ typedef enum : NSUInteger {
|
|||
#pragma mark - UICollectionView DataSource
|
||||
|
||||
- (UICollectionViewCell *)collectionView:(JSQMessagesCollectionView *)collectionView
|
||||
cellForItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
TSMessageAdapter *msg = [self messageAtIndexPath:indexPath];
|
||||
cellForItemAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
TSMessageAdapter *message = [self messageAtIndexPath:indexPath];
|
||||
NSParameterAssert(message != nil);
|
||||
|
||||
switch (msg.messageType) {
|
||||
case TSIncomingMessageAdapter:
|
||||
return [self loadIncomingMessageCellForMessage:msg atIndexPath:indexPath];
|
||||
case TSOutgoingMessageAdapter:
|
||||
return [self loadOutgoingCellForMessage:msg atIndexPath:indexPath];
|
||||
case TSCallAdapter:
|
||||
return [self loadCallCellForCall:msg atIndexPath:indexPath];
|
||||
case TSInfoMessageAdapter:
|
||||
return [self loadInfoMessageCellForMessage:msg atIndexPath:indexPath];
|
||||
case TSErrorMessageAdapter:
|
||||
return [self loadErrorMessageCellForMessage:msg atIndexPath:indexPath];
|
||||
JSQMessagesCollectionViewCell *cell;
|
||||
switch (message.messageType) {
|
||||
case TSCallAdapter: {
|
||||
DDLogDebug(@"building cell for Call");
|
||||
JSQCall *call = (JSQCall *)message;
|
||||
cell = [self loadCallCellForCall:call atIndexPath:indexPath];
|
||||
} break;
|
||||
case TSInfoMessageAdapter: {
|
||||
DDLogDebug(@"building cell for InfoMessage");
|
||||
JSQInfoMessage *infoMessage = (JSQInfoMessage *)message;
|
||||
cell = [self loadInfoMessageCellForMessage:infoMessage atIndexPath:indexPath];
|
||||
} break;
|
||||
case TSErrorMessageAdapter: {
|
||||
DDLogDebug(@"building cell for ErrorMessage");
|
||||
JSQErrorMessage *errorMessage = (JSQErrorMessage *)message;
|
||||
cell = [self loadErrorMessageCellForMessage:errorMessage atIndexPath:indexPath];
|
||||
} break;
|
||||
case TSIncomingMessageAdapter: {
|
||||
DDLogDebug(@"building cell for incoming message: %@", message);
|
||||
cell = [self loadIncomingMessageCellForMessage:message atIndexPath:indexPath];
|
||||
|
||||
default:
|
||||
DDLogError(@"Something went wrong");
|
||||
return nil;
|
||||
} break;
|
||||
case TSOutgoingMessageAdapter: {
|
||||
DDLogDebug(@"building cell for incoming message: %@", message);
|
||||
cell = [self loadOutgoingCellForMessage:message atIndexPath:indexPath];
|
||||
} break;
|
||||
default: {
|
||||
DDLogDebug(@"using default cell constructor for message: %@", message);
|
||||
cell = (JSQMessagesCollectionViewCell *)[super collectionView:collectionView cellForItemAtIndexPath:indexPath];
|
||||
} break;
|
||||
}
|
||||
cell.delegate = collectionView;
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
#pragma mark - Loading message cells
|
||||
|
@ -764,26 +808,84 @@ typedef enum : NSUInteger {
|
|||
return cell;
|
||||
}
|
||||
|
||||
- (JSQCallCollectionViewCell *)loadCallCellForCall:(id<JSQMessageData>)call atIndexPath:(NSIndexPath *)indexPath {
|
||||
JSQCallCollectionViewCell *cell =
|
||||
(JSQCallCollectionViewCell *)[super collectionView:self.collectionView cellForItemAtIndexPath:indexPath];
|
||||
return cell;
|
||||
- (JSQCallCollectionViewCell *)loadCallCellForCall:(JSQCall *)call
|
||||
atIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
|
||||
JSQCallCollectionViewCell *callCell = [self.collectionView dequeueReusableCellWithReuseIdentifier:[JSQCallCollectionViewCell cellReuseIdentifier]
|
||||
forIndexPath:indexPath];
|
||||
|
||||
NSString *text = call.date != nil ? [call text] : call.senderDisplayName;
|
||||
NSString *allText = call.date != nil ? [text stringByAppendingString:[call dateText]] : text;
|
||||
|
||||
UIFont *boldFont = [UIFont fontWithName:@"HelveticaNeue-Medium" size:12.0f];
|
||||
UIFont *regularFont = [UIFont fontWithName:@"HelveticaNeue-Light" size:12.0f];
|
||||
|
||||
//TODO declarative dict
|
||||
NSDictionary *attrs = [NSDictionary dictionaryWithObjectsAndKeys:boldFont, NSFontAttributeName, nil];
|
||||
NSMutableAttributedString *attributedText = [[NSMutableAttributedString alloc] initWithString:allText
|
||||
attributes:attrs];
|
||||
|
||||
if([call date]!=nil) {
|
||||
// Not a group meta message
|
||||
NSDictionary *subAttrs = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||
regularFont, NSFontAttributeName, nil];
|
||||
|
||||
const NSRange range = NSMakeRange([text length],[[call dateText] length]);
|
||||
[attributedText setAttributes:subAttrs range:range];
|
||||
|
||||
BOOL isOutgoing = [self.senderId isEqualToString:call.senderId];
|
||||
if (isOutgoing)
|
||||
{
|
||||
callCell.outgoingCallImageView.image = [call thumbnailImage];
|
||||
} else {
|
||||
callCell.incomingCallImageView.image = [call thumbnailImage];
|
||||
}
|
||||
} else {
|
||||
// TODO wrt comment, does it make sense to receive a group meta message in a *call* or was this copy/paste misfire?
|
||||
// A group meta message
|
||||
callCell.incomingCallImageView.image = [call thumbnailImage];
|
||||
}
|
||||
callCell.cellLabel.attributedText = attributedText;
|
||||
callCell.cellLabel.numberOfLines = 0; // uses as many lines as it needs
|
||||
|
||||
// TODO is this a constant somewhere else already?
|
||||
callCell.cellLabel.textColor = [UIColor colorWithRed:32.f/255.f green:144.f/255.f blue:234.f/255.f alpha:1.f];
|
||||
|
||||
callCell.layer.shouldRasterize = YES;
|
||||
callCell.layer.rasterizationScale = [UIScreen mainScreen].scale;
|
||||
return callCell;
|
||||
}
|
||||
|
||||
- (JSQDisplayedMessageCollectionViewCell *)loadInfoMessageCellForMessage:(id<JSQMessageData>)message
|
||||
atIndexPath:(NSIndexPath *)indexPath {
|
||||
JSQDisplayedMessageCollectionViewCell *cell =
|
||||
(JSQDisplayedMessageCollectionViewCell *)[super collectionView:self.collectionView
|
||||
cellForItemAtIndexPath:indexPath];
|
||||
return cell;
|
||||
- (JSQDisplayedMessageCollectionViewCell *)loadInfoMessageCellForMessage:(JSQInfoMessage *)infoMessage
|
||||
atIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
JSQDisplayedMessageCollectionViewCell *infoCell = [self.collectionView dequeueReusableCellWithReuseIdentifier:[JSQDisplayedMessageCollectionViewCell cellReuseIdentifier]
|
||||
forIndexPath:indexPath];
|
||||
infoCell.cellLabel.text = [infoMessage text];
|
||||
infoCell.cellLabel.textColor = [UIColor darkGrayColor];
|
||||
|
||||
// TODO is this a constant somewhere else already?
|
||||
infoCell.textContainer.layer.borderColor = [[UIColor colorWithRed:239.f/255.f green:189.f/255.f blue:88.f/255.f alpha:1.0f] CGColor];
|
||||
infoCell.headerImageView.image = [UIImage imageNamed:@"warning_white"];
|
||||
infoCell.layer.shouldRasterize = YES;
|
||||
infoCell.layer.rasterizationScale = [UIScreen mainScreen].scale;
|
||||
return infoCell;
|
||||
}
|
||||
|
||||
- (JSQDisplayedMessageCollectionViewCell *)loadErrorMessageCellForMessage:(id<JSQMessageData>)message
|
||||
- (JSQDisplayedMessageCollectionViewCell *)loadErrorMessageCellForMessage:(JSQErrorMessage *)errorMessage
|
||||
atIndexPath:(NSIndexPath *)indexPath {
|
||||
JSQDisplayedMessageCollectionViewCell *cell =
|
||||
(JSQDisplayedMessageCollectionViewCell *)[super collectionView:self.collectionView
|
||||
cellForItemAtIndexPath:indexPath];
|
||||
return cell;
|
||||
JSQDisplayedMessageCollectionViewCell *errorCell = [self.collectionView dequeueReusableCellWithReuseIdentifier:[JSQDisplayedMessageCollectionViewCell cellReuseIdentifier]
|
||||
forIndexPath:indexPath];
|
||||
errorCell.cellLabel.text = [errorMessage text];
|
||||
errorCell.cellLabel.textColor = [UIColor darkGrayColor];
|
||||
|
||||
// TODO is this a constant somewhere else already?
|
||||
errorCell.textContainer.layer.borderColor = [[UIColor colorWithRed:195.f/255.f green:0 blue:22.f/255.f alpha:1.0f] CGColor];
|
||||
errorCell.headerImageView.image = [UIImage imageNamed:@"error_white"];
|
||||
errorCell.layer.shouldRasterize = YES;
|
||||
errorCell.layer.rasterizationScale = [UIScreen mainScreen].scale;
|
||||
return errorCell;
|
||||
}
|
||||
|
||||
#pragma mark - Adjusting cell label heights
|
||||
|
@ -831,9 +933,11 @@ typedef enum : NSUInteger {
|
|||
TSMessageAdapter *currentMessage = [self messageAtIndexPath:indexPath];
|
||||
|
||||
// If message failed, say that message should be tapped to retry;
|
||||
if (currentMessage.messageType == TSOutgoingMessageAdapter &&
|
||||
currentMessage.messageState == TSOutgoingMessageStateUnsent) {
|
||||
return YES;
|
||||
if (currentMessage.messageType == TSOutgoingMessageAdapter) {
|
||||
TSOutgoingMessage *outgoingMessage = (TSOutgoingMessage *)currentMessage;
|
||||
if(outgoingMessage.messageState == TSOutgoingMessageStateUnsent) {
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
if ([self.thread isKindOfClass:[TSGroupThread class]]) {
|
||||
|
@ -868,7 +972,13 @@ typedef enum : NSUInteger {
|
|||
}
|
||||
|
||||
- (BOOL)isMessageOutgoingAndDelivered:(TSMessageAdapter *)message {
|
||||
return message.messageType == TSOutgoingMessageAdapter && message.messageState == TSOutgoingMessageStateDelivered;
|
||||
if (message.messageType == TSOutgoingMessageAdapter) {
|
||||
TSOutgoingMessage *outgoingMessage = (TSOutgoingMessage *)message;
|
||||
if(outgoingMessage.messageState == TSOutgoingMessageStateDelivered) {
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
|
||||
|
@ -879,11 +989,14 @@ typedef enum : NSUInteger {
|
|||
textAttachment.bounds = CGRectMake(0, 0, 11.0f, 10.0f);
|
||||
|
||||
if ([self shouldShowMessageStatusAtIndexPath:indexPath]) {
|
||||
if (msg.messageType == TSOutgoingMessageAdapter && msg.messageState == TSOutgoingMessageStateUnsent) {
|
||||
NSMutableAttributedString *attrStr =
|
||||
if (msg.messageType == TSOutgoingMessageAdapter) {
|
||||
TSOutgoingMessage *outgoingMessage = (TSOutgoingMessage *)msg;
|
||||
if(outgoingMessage.messageState == TSOutgoingMessageStateUnsent) {
|
||||
NSMutableAttributedString *attrStr =
|
||||
[[NSMutableAttributedString alloc] initWithString:NSLocalizedString(@"FAILED_SENDING_TEXT", nil)];
|
||||
[attrStr appendAttributedString:[NSAttributedString attributedStringWithAttachment:textAttachment]];
|
||||
return attrStr;
|
||||
[attrStr appendAttributedString:[NSAttributedString attributedStringWithAttachment:textAttachment]];
|
||||
return attrStr;
|
||||
}
|
||||
}
|
||||
|
||||
if ([self.thread isKindOfClass:[TSGroupThread class]]) {
|
||||
|
@ -922,6 +1035,12 @@ typedef enum : NSUInteger {
|
|||
|
||||
#pragma mark - Actions
|
||||
|
||||
- (void)collectionView:(JSQMessagesCollectionView *)collectionView didTapCellAtIndexPath:(NSIndexPath *)indexPath touchLocation:(CGPoint)touchLocation
|
||||
{
|
||||
// Pass info/error message tapping to bubble tapping handler
|
||||
[self collectionView:collectionView didTapMessageBubbleAtIndexPath:indexPath];
|
||||
}
|
||||
|
||||
- (void)collectionView:(JSQMessagesCollectionView *)collectionView
|
||||
didTapMessageBubbleAtIndexPath:(NSIndexPath *)indexPath {
|
||||
TSMessageAdapter *messageItem =
|
||||
|
@ -929,10 +1048,13 @@ typedef enum : NSUInteger {
|
|||
TSInteraction *interaction = [self interactionAtIndexPath:indexPath];
|
||||
|
||||
switch (messageItem.messageType) {
|
||||
case TSOutgoingMessageAdapter:
|
||||
if (messageItem.messageState == TSOutgoingMessageStateUnsent) {
|
||||
case TSOutgoingMessageAdapter: {
|
||||
TSOutgoingMessage *outgoingMessage = (TSOutgoingMessage *)messageItem;
|
||||
if (outgoingMessage.messageState == TSOutgoingMessageStateUnsent) {
|
||||
[self handleUnsentMessageTap:(TSOutgoingMessage *)interaction];
|
||||
}
|
||||
}
|
||||
// No `break` as we want to fall through to capture tapping on media items
|
||||
case TSIncomingMessageAdapter: {
|
||||
BOOL isMediaMessage = [messageItem isMediaMessage];
|
||||
|
||||
|
@ -1111,11 +1233,14 @@ typedef enum : NSUInteger {
|
|||
case TSCallAdapter:
|
||||
break;
|
||||
default:
|
||||
DDLogDebug(@"Unhandled bubble touch for interaction: %@.", interaction);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)handleWarningTap:(TSInteraction *)interaction {
|
||||
//TODO why is handle warning tap expecting a TSIncomingMessage? I assumed it was for info messages, but maybe those aren't actionable.
|
||||
// Looks like we create an InfoMessage "attachment is downloading" and tapping on it may restart a stalled fetch
|
||||
if ([interaction isKindOfClass:[TSIncomingMessage class]]) {
|
||||
TSIncomingMessage *message = (TSIncomingMessage *)interaction;
|
||||
|
||||
|
@ -1129,6 +1254,7 @@ typedef enum : NSUInteger {
|
|||
if ([attachment isKindOfClass:[TSAttachmentPointer class]]) {
|
||||
TSAttachmentPointer *pointer = (TSAttachmentPointer *)attachment;
|
||||
|
||||
// FIXME possible for pointer to get stuck in isDownloading state if app is closed while downloading.
|
||||
if (!pointer.isDownloading) {
|
||||
[[TSMessagesManager sharedManager] retrieveAttachment:pointer messageId:message.uniqueId];
|
||||
}
|
||||
|
|
31
Signal/src/views/JSQCallCollectionViewCell.h
Normal file
31
Signal/src/views/JSQCallCollectionViewCell.h
Normal file
|
@ -0,0 +1,31 @@
|
|||
//
|
||||
// JSQCallCollectionViewCell.h
|
||||
// JSQMessages
|
||||
//
|
||||
// Created by Dylan Bourgeois on 20/11/14.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import <JSQMessagesViewController/JSQMessagesCollectionViewCell.h>
|
||||
|
||||
#define kCallCellHeight 40.0f
|
||||
#define kCallCellWidth 400.0f
|
||||
|
||||
@interface JSQCallCollectionViewCell : JSQMessagesCollectionViewCell
|
||||
|
||||
//TODO can we use an existing label from JSQMessagesCollectionViewCell?
|
||||
@property (weak, nonatomic, readonly) JSQMessagesLabel *cellLabel;
|
||||
|
||||
@property (weak, nonatomic, readonly) UIImageView *outgoingCallImageView;
|
||||
|
||||
@property (weak, nonatomic, readonly) UIImageView *incomingCallImageView;
|
||||
|
||||
|
||||
#pragma mark - Class methods
|
||||
|
||||
+ (UINib *)nib;
|
||||
|
||||
+ (NSString *)cellReuseIdentifier;
|
||||
|
||||
@end
|
63
Signal/src/views/JSQCallCollectionViewCell.m
Normal file
63
Signal/src/views/JSQCallCollectionViewCell.m
Normal file
|
@ -0,0 +1,63 @@
|
|||
//
|
||||
// JSQCallCollectionViewCell.m
|
||||
// JSQMessages
|
||||
//
|
||||
// Created by Dylan Bourgeois on 20/11/14.
|
||||
//
|
||||
|
||||
#import "JSQCallCollectionViewCell.h"
|
||||
|
||||
#import "UIView+JSQMessages.h"
|
||||
|
||||
|
||||
@interface JSQCallCollectionViewCell ()
|
||||
|
||||
@property (weak, nonatomic) IBOutlet JSQMessagesLabel *cellLabel;
|
||||
@property (weak, nonatomic) IBOutlet UIImageView *outgoingCallImageView;
|
||||
@property (weak, nonatomic) IBOutlet UIImageView *incomingCallImageView;
|
||||
|
||||
@end
|
||||
|
||||
@implementation JSQCallCollectionViewCell
|
||||
|
||||
#pragma mark - Class Methods
|
||||
|
||||
+ (UINib *)nib
|
||||
{
|
||||
return [UINib nibWithNibName:NSStringFromClass([self class]) bundle:[NSBundle mainBundle]];
|
||||
}
|
||||
|
||||
+ (NSString *)cellReuseIdentifier
|
||||
{
|
||||
return NSStringFromClass([self class]);
|
||||
}
|
||||
|
||||
#pragma mark - Initializer
|
||||
|
||||
-(void)awakeFromNib
|
||||
{
|
||||
[super awakeFromNib];
|
||||
|
||||
[self setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||
|
||||
self.backgroundColor = [UIColor whiteColor];
|
||||
|
||||
self.cellLabel.textAlignment = NSTextAlignmentCenter;
|
||||
self.cellLabel.font = [UIFont fontWithName:@"HelveticaNeue-Light" size:14.0f];
|
||||
self.cellLabel.textColor = [UIColor lightGrayColor];
|
||||
}
|
||||
|
||||
-(void)dealloc
|
||||
{
|
||||
_cellLabel = nil;
|
||||
}
|
||||
|
||||
#pragma mark - Collection view cell
|
||||
|
||||
-(void)prepareForReuse
|
||||
{
|
||||
[super prepareForReuse];
|
||||
self.cellLabel.text = nil;
|
||||
}
|
||||
|
||||
@end
|
61
Signal/src/views/JSQCallCollectionViewCell.xib
Normal file
61
Signal/src/views/JSQCallCollectionViewCell.xib
Normal file
|
@ -0,0 +1,61 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES">
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" id="Efo-Hk-7Hw" customClass="JSQCallCollectionViewCell">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="20"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="20"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="YEr-eC-P6i" customClass="JSQMessagesLabel">
|
||||
<rect key="frame" x="39" y="0.0" width="242" height="20"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="20" id="7nw-w2-91p"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="o2l-Ms-1mk">
|
||||
<rect key="frame" x="281" y="0.0" width="20" height="20"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="20" id="78L-mQ-gEo"/>
|
||||
<constraint firstAttribute="width" constant="20" id="olH-5o-XyR"/>
|
||||
</constraints>
|
||||
</imageView>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="H8m-r4-eEC">
|
||||
<rect key="frame" x="19" y="0.0" width="20" height="20"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="20" id="Qay-jM-aBk"/>
|
||||
<constraint firstAttribute="width" constant="20" id="RpE-jJ-cYX"/>
|
||||
</constraints>
|
||||
</imageView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
<constraints>
|
||||
<constraint firstItem="YEr-eC-P6i" firstAttribute="centerY" secondItem="o2l-Ms-1mk" secondAttribute="centerY" id="8wg-Tg-9Nh"/>
|
||||
<constraint firstAttribute="trailing" secondItem="YEr-eC-P6i" secondAttribute="trailing" constant="39" id="Hj7-z5-WMM"/>
|
||||
<constraint firstItem="YEr-eC-P6i" firstAttribute="leading" secondItem="H8m-r4-eEC" secondAttribute="trailing" id="ZZJ-jL-10d"/>
|
||||
<constraint firstItem="YEr-eC-P6i" firstAttribute="top" secondItem="Efo-Hk-7Hw" secondAttribute="top" id="rEI-cY-6lx"/>
|
||||
<constraint firstItem="YEr-eC-P6i" firstAttribute="leading" secondItem="Efo-Hk-7Hw" secondAttribute="leading" constant="39" id="uNd-aK-1hE"/>
|
||||
<constraint firstItem="YEr-eC-P6i" firstAttribute="centerY" secondItem="H8m-r4-eEC" secondAttribute="centerY" id="wg8-V9-pBf"/>
|
||||
<constraint firstItem="o2l-Ms-1mk" firstAttribute="leading" secondItem="YEr-eC-P6i" secondAttribute="trailing" id="wmR-MD-QeR"/>
|
||||
</constraints>
|
||||
<size key="customSize" width="320" height="20"/>
|
||||
<connections>
|
||||
<outlet property="cellLabel" destination="YEr-eC-P6i" id="jii-8O-zLL"/>
|
||||
<outlet property="incomingCallImageView" destination="H8m-r4-eEC" id="hVW-Ng-BnU"/>
|
||||
<outlet property="outgoingCallImageView" destination="o2l-Ms-1mk" id="Q5m-uX-80H"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="219" y="435"/>
|
||||
</collectionViewCell>
|
||||
</objects>
|
||||
</document>
|
21
Signal/src/views/JSQDisplayedMessageCollectionViewCell.h
Normal file
21
Signal/src/views/JSQDisplayedMessageCollectionViewCell.h
Normal file
|
@ -0,0 +1,21 @@
|
|||
//
|
||||
// JSQDisplayedMessageCollectionViewCell.h
|
||||
// JSQMessages
|
||||
//
|
||||
// Created by Dylan Bourgeois on 29/11/14.
|
||||
// Copyright (c) 2014 Hexed Bits. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <JSQMessagesViewController/JSQMessagesCollectionViewCell.h>
|
||||
|
||||
static const CGFloat OWSDisplayedMessageCellHeight = 70.0f;
|
||||
|
||||
@interface JSQDisplayedMessageCollectionViewCell : JSQMessagesCollectionViewCell
|
||||
|
||||
// TODO can we use existing label from superclass?
|
||||
@property (weak, nonatomic, readonly) JSQMessagesLabel * cellLabel;
|
||||
@property (weak, nonatomic, readonly) UIImageView * headerImageView;
|
||||
@property (strong, nonatomic, readonly) UIView *textContainer;
|
||||
|
||||
@end
|
63
Signal/src/views/JSQDisplayedMessageCollectionViewCell.m
Normal file
63
Signal/src/views/JSQDisplayedMessageCollectionViewCell.m
Normal file
|
@ -0,0 +1,63 @@
|
|||
//
|
||||
// JSQDisplayedMessageCollectionViewCell.m
|
||||
// JSQMessages
|
||||
//
|
||||
// Created by Dylan Bourgeois on 29/11/14.
|
||||
// Copyright (c) 2014 Hexed Bits. All rights reserved.
|
||||
//
|
||||
|
||||
#import "JSQDisplayedMessageCollectionViewCell.h"
|
||||
|
||||
#import <JSQMessagesViewController/UIView+JSQMessages.h>
|
||||
|
||||
@interface JSQDisplayedMessageCollectionViewCell ()
|
||||
|
||||
@property(weak, nonatomic) IBOutlet JSQMessagesLabel* cellLabel;
|
||||
@property (weak, nonatomic) IBOutlet UIImageView* headerImageView;
|
||||
@property (strong, nonatomic) IBOutlet UIView *textContainer;
|
||||
|
||||
@end
|
||||
|
||||
@implementation JSQDisplayedMessageCollectionViewCell
|
||||
|
||||
#pragma mark - Class Methods
|
||||
|
||||
+ (UINib *)nib
|
||||
{
|
||||
return [UINib nibWithNibName:NSStringFromClass([self class]) bundle:[NSBundle mainBundle]];
|
||||
}
|
||||
|
||||
+ (NSString *)cellReuseIdentifier
|
||||
{
|
||||
return NSStringFromClass([self class]);
|
||||
}
|
||||
|
||||
#pragma mark - Initializer
|
||||
|
||||
-(void)awakeFromNib
|
||||
{
|
||||
[super awakeFromNib];
|
||||
|
||||
[self setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||
|
||||
self.backgroundColor = [UIColor whiteColor];
|
||||
// self.cellLabelHeightConstraint.constant = 0.0f;
|
||||
|
||||
self.textContainer.layer.borderColor = [[UIColor lightGrayColor] CGColor];
|
||||
self.textContainer.layer.borderWidth = 0.75f;
|
||||
self.textContainer.layer.cornerRadius = 5.0f;
|
||||
self.cellLabel.textAlignment = NSTextAlignmentCenter;
|
||||
self.cellLabel.font = [UIFont fontWithName:@"HelveticaNeue-Light" size:14.0f];
|
||||
self.cellLabel.textColor = [UIColor lightGrayColor];
|
||||
}
|
||||
|
||||
#pragma mark - Collection view cell
|
||||
|
||||
-(void)prepareForReuse
|
||||
{
|
||||
[super prepareForReuse];
|
||||
|
||||
self.cellLabel.text = nil;
|
||||
}
|
||||
|
||||
@end
|
69
Signal/src/views/JSQDisplayedMessageCollectionViewCell.xib
Normal file
69
Signal/src/views/JSQDisplayedMessageCollectionViewCell.xib
Normal file
|
@ -0,0 +1,69 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES">
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
|
||||
<capability name="Alignment constraints with different attributes" minToolsVersion="5.1"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" id="eMU-z2-CzM" customClass="JSQDisplayedMessageCollectionViewCell">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="70"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="70"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="qCf-bs-dBd" userLabel="textContainer">
|
||||
<rect key="frame" x="0.0" y="22" width="320" height="48"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Info Message" textAlignment="center" lineBreakMode="wordWrap" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="OVa-Xw-5vl" customClass="JSQMessagesLabel">
|
||||
<rect key="frame" x="8" y="12" width="304" height="28"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="14" id="fed-2c-dqd"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<constraints>
|
||||
<constraint firstItem="OVa-Xw-5vl" firstAttribute="leading" secondItem="qCf-bs-dBd" secondAttribute="leading" constant="8" id="2IE-8k-czI"/>
|
||||
<constraint firstAttribute="bottom" secondItem="OVa-Xw-5vl" secondAttribute="bottom" constant="8" id="MtI-jW-t1x"/>
|
||||
<constraint firstAttribute="trailing" secondItem="OVa-Xw-5vl" secondAttribute="trailing" constant="8" id="Y8z-8G-PLt"/>
|
||||
<constraint firstItem="OVa-Xw-5vl" firstAttribute="top" secondItem="qCf-bs-dBd" secondAttribute="top" constant="12" id="v5B-tB-pOB"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="warning_white.png" translatesAutoresizingMaskIntoConstraints="NO" id="ePO-Cy-jUE">
|
||||
<rect key="frame" x="143" y="0.0" width="35" height="35"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="35" id="Llx-81-oyV"/>
|
||||
<constraint firstAttribute="width" constant="35" id="Nth-3D-Wo9"/>
|
||||
</constraints>
|
||||
</imageView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
<constraints>
|
||||
<constraint firstItem="ePO-Cy-jUE" firstAttribute="top" secondItem="eMU-z2-CzM" secondAttribute="top" id="D28-BQ-Qam"/>
|
||||
<constraint firstAttribute="trailing" secondItem="qCf-bs-dBd" secondAttribute="trailing" id="F3T-1l-nCg"/>
|
||||
<constraint firstItem="qCf-bs-dBd" firstAttribute="leading" secondItem="eMU-z2-CzM" secondAttribute="leading" id="OzF-VM-85V"/>
|
||||
<constraint firstAttribute="bottom" secondItem="qCf-bs-dBd" secondAttribute="bottom" id="PNq-zm-usq"/>
|
||||
<constraint firstItem="qCf-bs-dBd" firstAttribute="top" secondItem="ePO-Cy-jUE" secondAttribute="centerY" constant="4" id="UzU-DS-8WZ"/>
|
||||
<constraint firstItem="ePO-Cy-jUE" firstAttribute="centerX" secondItem="eMU-z2-CzM" secondAttribute="centerX" id="qtQ-mS-o6z"/>
|
||||
</constraints>
|
||||
<size key="customSize" width="320" height="55"/>
|
||||
<connections>
|
||||
<outlet property="cellLabel" destination="OVa-Xw-5vl" id="7PC-oj-dQZ"/>
|
||||
<outlet property="headerImageView" destination="ePO-Cy-jUE" id="4uq-2C-V7U"/>
|
||||
<outlet property="textContainer" destination="qCf-bs-dBd" id="fL7-LO-El1"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="219" y="433"/>
|
||||
</collectionViewCell>
|
||||
</objects>
|
||||
<resources>
|
||||
<image name="warning_white.png" width="100" height="100"/>
|
||||
</resources>
|
||||
</document>
|
Loading…
Reference in a new issue