Explain send failures for text and media messages

fixes #1231

Motivation
----------
Previously when messages failed to send, there was no reason given.
Furthermore, when media messages failed to send there was no indication
that any attempt to send the message even occurred, nor a retry
dialog.

UX Changes
----------
- Show "uploading" status for media
- Show specific error message in retry-send dialog
- Only scroll to bottom when new message is inserted
- Show specific errors when group creation fails

Code Changes
-----------
- Updated incorrect references to TSMessageAdapters which were actually
  references to OWSMessageData
- MessageSender was extracted from SSK MessagesManager
- access MessagesManager as property
- idiomatic init/properties for Env
- log contact intersections
- Move scroll-to-bottom animation to main thread.

// FREEBIE
This commit is contained in:
Michael Kirk 2016-10-14 16:59:58 -04:00
parent 7c32259a92
commit 33f6a95520
22 changed files with 562 additions and 357 deletions

View File

@ -3,7 +3,7 @@ source 'https://github.com/CocoaPods/Specs.git'
target 'Signal' do target 'Signal' do
pod 'SocketRocket', :git => 'https://github.com/facebook/SocketRocket.git' pod 'SocketRocket', :git => 'https://github.com/facebook/SocketRocket.git'
pod 'SignalServiceKit', git: 'https://github.com/WhisperSystems/SignalServiceKit.git' pod 'SignalServiceKit', git: 'https://github.com/WhisperSystems/SignalServiceKit.git', branch: 'mkirk/outgoing-media-status#1231'
#pod 'SignalServiceKit', path: '../SignalServiceKit' #pod 'SignalServiceKit', path: '../SignalServiceKit'
pod 'OpenSSL' pod 'OpenSSL'
pod 'PastelogKit', '~> 1.3' pod 'PastelogKit', '~> 1.3'

View File

@ -33,10 +33,10 @@ PODS:
- JSQMessagesViewController (7.3.4): - JSQMessagesViewController (7.3.4):
- JSQSystemSoundPlayer (~> 2.0.1) - JSQSystemSoundPlayer (~> 2.0.1)
- JSQSystemSoundPlayer (2.0.1) - JSQSystemSoundPlayer (2.0.1)
- libPhoneNumber-iOS (0.8.16) - libPhoneNumber-iOS (0.8.17)
- Mantle (2.0.7): - Mantle (2.1.0):
- Mantle/extobjc (= 2.0.7) - Mantle/extobjc (= 2.1.0)
- Mantle/extobjc (2.0.7) - Mantle/extobjc (2.1.0)
- OpenSSL (1.0.210) - OpenSSL (1.0.210)
- PastelogKit (1.3): - PastelogKit (1.3):
- CocoaLumberjack (~> 2.0) - CocoaLumberjack (~> 2.0)
@ -44,7 +44,7 @@ PODS:
- Reachability (3.2) - Reachability (3.2)
- SAMKeychain (1.5.2) - SAMKeychain (1.5.2)
- SCWaveformView (1.0.0) - SCWaveformView (1.0.0)
- SignalServiceKit (0.2.0): - SignalServiceKit (0.3.0):
- '25519' - '25519'
- AFNetworking - AFNetworking
- AxolotlKit - AxolotlKit
@ -122,19 +122,20 @@ DEPENDENCIES:
- OpenSSL - OpenSSL
- PastelogKit (~> 1.3) - PastelogKit (~> 1.3)
- SCWaveformView (~> 1.0) - SCWaveformView (~> 1.0)
- SignalServiceKit (from `https://github.com/WhisperSystems/SignalServiceKit.git`) - SignalServiceKit (from `https://github.com/WhisperSystems/SignalServiceKit.git`, branch `mkirk/outgoing-media-status#1231`)
- SocketRocket (from `https://github.com/facebook/SocketRocket.git`) - SocketRocket (from `https://github.com/facebook/SocketRocket.git`)
- ZXingObjC - ZXingObjC
EXTERNAL SOURCES: EXTERNAL SOURCES:
SignalServiceKit: SignalServiceKit:
:branch: mkirk/outgoing-media-status#1231
:git: https://github.com/WhisperSystems/SignalServiceKit.git :git: https://github.com/WhisperSystems/SignalServiceKit.git
SocketRocket: SocketRocket:
:git: https://github.com/facebook/SocketRocket.git :git: https://github.com/facebook/SocketRocket.git
CHECKOUT OPTIONS: CHECKOUT OPTIONS:
SignalServiceKit: SignalServiceKit:
:commit: d4c55d69404c99927da716c443997415ad7bc6ba :commit: 4ba1e86ec12c4e28de264fea59bd57af0aa3edea
:git: https://github.com/WhisperSystems/SignalServiceKit.git :git: https://github.com/WhisperSystems/SignalServiceKit.git
SocketRocket: SocketRocket:
:commit: 41b57bb2fc292a814f758441a05243eb38457027 :commit: 41b57bb2fc292a814f758441a05243eb38457027
@ -150,15 +151,15 @@ SPEC CHECKSUMS:
HKDFKit: c058305d6f64b84f28c50bd7aa89574625bcb62a HKDFKit: c058305d6f64b84f28c50bd7aa89574625bcb62a
JSQMessagesViewController: 39fed975e3c9f8eba7292071e29eeb541d105e66 JSQMessagesViewController: 39fed975e3c9f8eba7292071e29eeb541d105e66
JSQSystemSoundPlayer: c5850e77a4363ffd374cd851154b9af93264ed8d JSQSystemSoundPlayer: c5850e77a4363ffd374cd851154b9af93264ed8d
libPhoneNumber-iOS: acb5805f67892db37adc3440290a367923672b51 libPhoneNumber-iOS: 9f083847f8cb9b81064cff2ed2c98cbf18d9f9f2
Mantle: bc40bb061d8c2c6fb48d5083e04d928c3b7f73d9 Mantle: 2fa750afa478cd625a94230fbf1c13462f29395b
OpenSSL: 246ffb948e9d56466727fd318134af35f5aa764e OpenSSL: 246ffb948e9d56466727fd318134af35f5aa764e
PastelogKit: 7b475be4cf577713506a943dd940bcc0499c8bca PastelogKit: 7b475be4cf577713506a943dd940bcc0499c8bca
ProtocolBuffers: d509225eb2ea43d9582a59e94348fcf86e2abd65 ProtocolBuffers: d509225eb2ea43d9582a59e94348fcf86e2abd65
Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96 Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96
SAMKeychain: 1865333198217411f35327e8da61b43de79b635b SAMKeychain: 1865333198217411f35327e8da61b43de79b635b
SCWaveformView: 52a96750255d817e300565a80c81fb643e233e07 SCWaveformView: 52a96750255d817e300565a80c81fb643e233e07
SignalServiceKit: 4e7a552635e10f4d94f0a047fc6554e932340b30 SignalServiceKit: 8b115cfd63f9b814fa03fe61fd5d38ef9a548460
SocketRocket: dbb1554b8fc288ef8ef370d6285aeca7361be31e SocketRocket: dbb1554b8fc288ef8ef370d6285aeca7361be31e
SQLCipher: 4c768761421736a247ed6cf412d9045615d53dff SQLCipher: 4c768761421736a247ed6cf412d9045615d53dff
TwistedOakCollapsingFutures: f359b90f203e9ab13dfb92c9ff41842a7fe1cd0c TwistedOakCollapsingFutures: f359b90f203e9ab13dfb92c9ff41842a7fe1cd0c
@ -166,6 +167,6 @@ SPEC CHECKSUMS:
YapDatabase: b1e43555a34a5298e23a045be96817a5ef0da58f YapDatabase: b1e43555a34a5298e23a045be96817a5ef0da58f
ZXingObjC: bf15b3814f7a105b6d99f47da2333c93a063650a ZXingObjC: bf15b3814f7a105b6d99f47da2333c93a063650a
PODFILE CHECKSUM: 93ccdbbb243044904d772ee53e00154890a9f82f PODFILE CHECKSUM: 7dfde19734213e4ff876efa2ea10d536da2e0b47
COCOAPODS: 1.0.1 COCOAPODS: 1.0.1

View File

@ -21,7 +21,7 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>2.6.2</string> <string>2.6.3</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>CFBundleURLTypes</key> <key>CFBundleURLTypes</key>
@ -38,7 +38,7 @@
</dict> </dict>
</array> </array>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>2.6.2.0</string> <string>2.6.3.4</string>
<key>ITSAppUsesNonExemptEncryption</key> <key>ITSAppUsesNonExemptEncryption</key>
<false/> <false/>
<key>LOGS_EMAIL</key> <key>LOGS_EMAIL</key>

View File

@ -6,6 +6,7 @@
#import "Environment.h" #import "Environment.h"
#import "NotificationsManager.h" #import "NotificationsManager.h"
#import "OWSContactsManager.h" #import "OWSContactsManager.h"
#import "OWSStaleNotificationObserver.h"
#import "PreferencesUtil.h" #import "PreferencesUtil.h"
#import "PushManager.h" #import "PushManager.h"
#import "Release.h" #import "Release.h"
@ -15,9 +16,9 @@
#import "TSSocketManager.h" #import "TSSocketManager.h"
#import "TextSecureKitEnv.h" #import "TextSecureKitEnv.h"
#import "VersionMigrations.h" #import "VersionMigrations.h"
#import "OWSStaleNotificationObserver.h"
#import <SignalServiceKit/OWSDisappearingMessagesJob.h> #import <SignalServiceKit/OWSDisappearingMessagesJob.h>
#import <SignalServiceKit/OWSIncomingMessageReadObserver.h> #import <SignalServiceKit/OWSIncomingMessageReadObserver.h>
#import <SignalServiceKit/OWSMessageSender.h>
static NSString *const kStoryboardName = @"Storyboard"; static NSString *const kStoryboardName = @"Storyboard";
static NSString *const kInitialViewControllerIdentifier = @"UserInitialViewController"; static NSString *const kInitialViewControllerIdentifier = @"UserInitialViewController";
@ -128,8 +129,16 @@ static NSString *const kURLHostVerifyPrefix = @"verify";
[TextSecureKitEnv sharedEnv].contactsManager = [Environment getCurrent].contactsManager; [TextSecureKitEnv sharedEnv].contactsManager = [Environment getCurrent].contactsManager;
[[TSStorageManager sharedManager] setupDatabase]; [[TSStorageManager sharedManager] setupDatabase];
[TextSecureKitEnv sharedEnv].notificationsManager = [[NotificationsManager alloc] init]; [TextSecureKitEnv sharedEnv].notificationsManager = [[NotificationsManager alloc] init];
self.incomingMessageReadObserver = [[OWSIncomingMessageReadObserver alloc] initWithStorageManager:[TSStorageManager sharedManager]
messagesManager:[TSMessagesManager sharedManager]]; OWSMessageSender *messageSender =
[[OWSMessageSender alloc] initWithNetworkManager:[Environment getCurrent].networkManager
storageManager:[TSStorageManager sharedManager]
contactsManager:[Environment getCurrent].contactsManager
contactsUpdater:[Environment getCurrent].contactsUpdater];
self.incomingMessageReadObserver =
[[OWSIncomingMessageReadObserver alloc] initWithStorageManager:[TSStorageManager sharedManager]
messageSender:messageSender];
[self.incomingMessageReadObserver startObserving]; [self.incomingMessageReadObserver startObserving];
self.staleNotificationObserver = [OWSStaleNotificationObserver new]; self.staleNotificationObserver = [OWSStaleNotificationObserver new];

View File

@ -178,6 +178,11 @@ NS_ASSUME_NONNULL_BEGIN
return NO; return NO;
} }
- (BOOL)isOutgoingAndDelivered
{
return NO;
}
- (NSUInteger)messageHash - (NSUInteger)messageHash
{ {
return self.hash; return self.hash;

View File

@ -21,6 +21,7 @@ typedef NS_ENUM(NSInteger, TSMessageAdapterType) {
@property (nonatomic, readonly) TSMessageAdapterType messageType; @property (nonatomic, readonly) TSMessageAdapterType messageType;
@property (nonatomic, readonly) TSInteraction *interaction; @property (nonatomic, readonly) TSInteraction *interaction;
@property (nonatomic, readonly) BOOL isExpiringMessage; @property (nonatomic, readonly) BOOL isExpiringMessage;
@property (nonatomic, readonly) BOOL isOutgoingAndDelivered;
@property (nonatomic, readonly) BOOL shouldStartExpireTimer; @property (nonatomic, readonly) BOOL shouldStartExpireTimer;
@property (nonatomic, readonly) uint64_t expiresAtSeconds; @property (nonatomic, readonly) uint64_t expiresAtSeconds;
@property (nonatomic, readonly) uint32_t expiresInSeconds; @property (nonatomic, readonly) uint32_t expiresInSeconds;

View File

@ -23,6 +23,9 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic) TSInteraction *interaction; @property (nonatomic) TSInteraction *interaction;
@property (readonly) TSInfoMessageType infoMessageType; @property (readonly) TSInfoMessageType infoMessageType;
@property (nonatomic, readonly) CGFloat mediaViewAlpha;
@property (nonatomic, readonly) BOOL isOutgoingAndDelivered;
@property (nonatomic, readonly) BOOL isMediaBeingSent;
@end @end

View File

@ -305,4 +305,31 @@
return self.outgoingMessageStatus; return self.outgoingMessageStatus;
} }
- (CGFloat)mediaViewAlpha
{
return (CGFloat)(self.isMediaBeingSent ? 0.75 : 1);
}
- (BOOL)isMediaBeingSent
{
if ([self.interaction isKindOfClass:[TSOutgoingMessage class]]) {
TSOutgoingMessage *outgoingMessage = (TSOutgoingMessage *)self.interaction;
if (outgoingMessage.hasAttachments && outgoingMessage.messageState == TSOutgoingMessageStateAttemptingOut) {
return YES;
}
}
return NO;
}
- (BOOL)isOutgoingAndDelivered
{
if ([self.interaction isKindOfClass:[TSOutgoingMessage class]]) {
TSOutgoingMessage *outgoingMessage = (TSOutgoingMessage *)self.interaction;
if (outgoingMessage.messageState == TSOutgoingMessageStateDelivered) {
return YES;
}
}
return NO;
}
@end @end

View File

@ -225,7 +225,7 @@
_maskLayer.hidden = YES; _maskLayer.hidden = YES;
_progressView.hidden = YES; _progressView.hidden = YES;
_videoPlayButton.hidden = NO; _videoPlayButton.hidden = NO;
_attachment.isDownloaded = YES; _attachment.isDownloaded = YES; // TODO isn't this redundant with attachment processor?
[[TSMessagesManager sharedManager] [[TSMessagesManager sharedManager]
.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { .dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[_attachment saveWithTransaction:transaction]; [_attachment saveWithTransaction:transaction];

View File

@ -91,13 +91,16 @@ void onAddressBookChanged(ABAddressBookRef notifyAddressBook, CFDictionaryRef in
- (void)intersectContacts { - (void)intersectContacts {
[[ContactsUpdater sharedUpdater] updateSignalContactIntersectionWithABContacts:self.allContacts [[ContactsUpdater sharedUpdater] updateSignalContactIntersectionWithABContacts:self.allContacts
success:^{ success:^{
DDLogInfo(@"%@ Successfully intersected contacts.", self.tag);
} }
failure:^(NSError *error) { failure:^(NSError *error) {
[NSTimer scheduledTimerWithTimeInterval:60 DDLogWarn(@"%@ Failed to intersect contacts with error: %@. Rescheduling", self.tag, error);
target:self
selector:@selector(intersectContacts) [NSTimer scheduledTimerWithTimeInterval:60
userInfo:nil target:self
repeats:NO]; selector:@selector(intersectContacts)
userInfo:nil
repeats:NO];
}]; }];
} }
@ -423,4 +426,16 @@ void onAddressBookChanged(ABAddressBookRef notifyAddressBook, CFDictionaryRef in
return nil; return nil;
} }
#pragma mark - Logging
+ (NSString *)tag
{
return [NSString stringWithFormat:@"[%@]", self.class];
}
- (NSString *)tag
{
return self.class.tag;
}
@end @end

View File

@ -29,8 +29,28 @@ static NSString *const kCallSegue = @"2.0_6.0_Call_Segue";
@class PhoneManager; @class PhoneManager;
@class SignalsViewController; @class SignalsViewController;
@class TSGroupThread; @class TSGroupThread;
@class ContactsUpdater;
@class TSNetworkManager;
@interface Environment : NSObject @interface Environment : NSObject
- (instancetype)initWithLogging:(id<Logging>)logging
errorNoter:(ErrorHandlerBlock)errorNoter
serverPort:(in_port_t)serverPort
masterServerHostName:(NSString *)masterServerHostName
defaultRelayName:(NSString *)defaultRelayName
relayServerHostNameSuffix:(NSString *)relayServerHostNameSuffix
certificate:(Certificate *)certificate
supportedKeyAgreementProtocols:(NSArray *)keyAgreementProtocolsInDescendingPriority
phoneManager:(PhoneManager *)phoneManager
recentCallManager:(RecentCallManager *)recentCallManager
testingAndLegacyOptions:(NSArray *)testingAndLegacyOptions
zrtpClientId:(NSData *)zrtpClientId
zrtpVersionId:(NSData *)zrtpVersionId
contactsManager:(OWSContactsManager *)contactsManager
contactsUpdater:(ContactsUpdater *)contactsUpdater
networkManager:(TSNetworkManager *)networkManager;
@property (nonatomic, readonly) in_port_t serverPort; @property (nonatomic, readonly) in_port_t serverPort;
@property (nonatomic, readonly) id<Logging> logging; @property (nonatomic, readonly) id<Logging> logging;
@property (nonatomic, readonly) SecureEndPoint *masterServerSecureEndPoint; @property (nonatomic, readonly) SecureEndPoint *masterServerSecureEndPoint;
@ -45,6 +65,8 @@ static NSString *const kCallSegue = @"2.0_6.0_Call_Segue";
@property (nonatomic, readonly) NSData *zrtpClientId; @property (nonatomic, readonly) NSData *zrtpClientId;
@property (nonatomic, readonly) NSData *zrtpVersionId; @property (nonatomic, readonly) NSData *zrtpVersionId;
@property (nonatomic, readonly) OWSContactsManager *contactsManager; @property (nonatomic, readonly) OWSContactsManager *contactsManager;
@property (nonatomic, readonly) ContactsUpdater *contactsUpdater;
@property (nonatomic, readonly) TSNetworkManager *networkManager;
@property (nonatomic, readonly) SignalsViewController *signalsViewController; @property (nonatomic, readonly) SignalsViewController *signalsViewController;
@property (nonatomic, readonly, weak) UINavigationController *signUpFlowNavigationController; @property (nonatomic, readonly, weak) UINavigationController *signUpFlowNavigationController;
@ -53,21 +75,6 @@ static NSString *const kCallSegue = @"2.0_6.0_Call_Segue";
+ (SecureEndPoint *)getSecureEndPointToDefaultRelayServer; + (SecureEndPoint *)getSecureEndPointToDefaultRelayServer;
+ (SecureEndPoint *)getSecureEndPointToSignalingServerNamed:(NSString *)name; + (SecureEndPoint *)getSecureEndPointToSignalingServerNamed:(NSString *)name;
+ (Environment *)environmentWithLogging:(id<Logging>)logging
andErrorNoter:(ErrorHandlerBlock)errorNoter
andServerPort:(in_port_t)serverPort
andMasterServerHostName:(NSString *)masterServerHostName
andDefaultRelayName:(NSString *)defaultRelayName
andRelayServerHostNameSuffix:(NSString *)relayServerHostNameSuffix
andCertificate:(Certificate *)certificate
andSupportedKeyAgreementProtocols:(NSArray *)keyAgreementProtocolsInDescendingPriority
andPhoneManager:(PhoneManager *)phoneManager
andRecentCallManager:(RecentCallManager *)recentCallManager
andTestingAndLegacyOptions:(NSArray *)testingAndLegacyOptions
andZrtpClientId:(NSData *)zrtpClientId
andZrtpVersionId:(NSData *)zrtpVersionId
andContactsManager:(OWSContactsManager *)contactsManager;
+ (Environment *)getCurrent; + (Environment *)getCurrent;
+ (void)setCurrent:(Environment *)curEnvironment; + (void)setCurrent:(Environment *)curEnvironment;
+ (id<Logging>)logging; + (id<Logging>)logging;

View File

@ -1,7 +1,7 @@
#import "Environment.h"
#import "Constraints.h" #import "Constraints.h"
#import "DH3KKeyAgreementProtocol.h" #import "DH3KKeyAgreementProtocol.h"
#import "DebugLogger.h" #import "DebugLogger.h"
#import "Environment.h"
#import "FunctionalUtil.h" #import "FunctionalUtil.h"
#import "KeyAgreementProtocol.h" #import "KeyAgreementProtocol.h"
#import "MessagesViewController.h" #import "MessagesViewController.h"
@ -10,6 +10,7 @@
#import "SignalsViewController.h" #import "SignalsViewController.h"
#import "TSContactThread.h" #import "TSContactThread.h"
#import "TSGroupThread.h" #import "TSGroupThread.h"
#import <SignalServiceKit/ContactsUpdater.h>
#define isRegisteredUserDefaultString @"isRegistered" #define isRegisteredUserDefaultString @"isRegistered"
@ -17,10 +18,6 @@ static Environment *environment = nil;
@implementation Environment @implementation Environment
@synthesize testingAndLegacyOptions, errorNoter, keyAgreementProtocolsInDescendingPriority, logging,
masterServerSecureEndPoint, defaultRelayName, relayServerHostNameSuffix, certificate, serverPort, zrtpClientId,
zrtpVersionId, phoneManager, recentCallManager, contactsManager;
+ (Environment *)getCurrent { + (Environment *)getCurrent {
NSAssert((environment != nil), @"Environment is not defined."); NSAssert((environment != nil), @"Environment is not defined.");
return environment; return environment;
@ -54,20 +51,23 @@ static Environment *environment = nil;
return [SecureEndPoint secureEndPointForHost:location identifiedByCertificate:env.certificate]; return [SecureEndPoint secureEndPointForHost:location identifiedByCertificate:env.certificate];
} }
+ (Environment *)environmentWithLogging:(id<Logging>)logging - (instancetype)initWithLogging:(id<Logging>)logging
andErrorNoter:(ErrorHandlerBlock)errorNoter errorNoter:(ErrorHandlerBlock)errorNoter
andServerPort:(in_port_t)serverPort serverPort:(in_port_t)serverPort
andMasterServerHostName:(NSString *)masterServerHostName masterServerHostName:(NSString *)masterServerHostName
andDefaultRelayName:(NSString *)defaultRelayName defaultRelayName:(NSString *)defaultRelayName
andRelayServerHostNameSuffix:(NSString *)relayServerHostNameSuffix relayServerHostNameSuffix:(NSString *)relayServerHostNameSuffix
andCertificate:(Certificate *)certificate certificate:(Certificate *)certificate
andSupportedKeyAgreementProtocols:(NSArray *)keyAgreementProtocolsInDescendingPriority supportedKeyAgreementProtocols:(NSArray *)keyAgreementProtocolsInDescendingPriority
andPhoneManager:(PhoneManager *)phoneManager phoneManager:(PhoneManager *)phoneManager
andRecentCallManager:(RecentCallManager *)recentCallManager recentCallManager:(RecentCallManager *)recentCallManager
andTestingAndLegacyOptions:(NSArray *)testingAndLegacyOptions testingAndLegacyOptions:(NSArray *)testingAndLegacyOptions
andZrtpClientId:(NSData *)zrtpClientId zrtpClientId:(NSData *)zrtpClientId
andZrtpVersionId:(NSData *)zrtpVersionId zrtpVersionId:(NSData *)zrtpVersionId
andContactsManager:(OWSContactsManager *)contactsManager { contactsManager:(OWSContactsManager *)contactsManager
contactsUpdater:(ContactsUpdater *)contactsUpdater
networkManager:(TSNetworkManager *)networkManager
{
ows_require(errorNoter != nil); ows_require(errorNoter != nil);
ows_require(zrtpClientId != nil); ows_require(zrtpClientId != nil);
ows_require(zrtpVersionId != nil); ows_require(zrtpVersionId != nil);
@ -82,23 +82,29 @@ static Environment *environment = nil;
return [p isKindOfClass:DH3KKeyAgreementProtocol.class]; return [p isKindOfClass:DH3KKeyAgreementProtocol.class];
}]); }]);
Environment *e = [Environment new]; self = [super init];
e->errorNoter = errorNoter; if (!self) {
e->logging = logging; return self;
e->testingAndLegacyOptions = testingAndLegacyOptions; }
e->serverPort = serverPort;
e->masterServerSecureEndPoint = [SecureEndPoint _errorNoter = errorNoter;
_logging = logging;
_testingAndLegacyOptions = testingAndLegacyOptions;
_serverPort = serverPort;
_masterServerSecureEndPoint = [SecureEndPoint
secureEndPointForHost:[HostNameEndPoint hostNameEndPointWithHostName:masterServerHostName andPort:serverPort] secureEndPointForHost:[HostNameEndPoint hostNameEndPointWithHostName:masterServerHostName andPort:serverPort]
identifiedByCertificate:certificate]; identifiedByCertificate:certificate];
e->defaultRelayName = defaultRelayName;
e->certificate = certificate; _defaultRelayName = defaultRelayName;
e->relayServerHostNameSuffix = relayServerHostNameSuffix; _certificate = certificate;
e->keyAgreementProtocolsInDescendingPriority = keyAgreementProtocolsInDescendingPriority; _relayServerHostNameSuffix = relayServerHostNameSuffix;
e->phoneManager = phoneManager; _keyAgreementProtocolsInDescendingPriority = keyAgreementProtocolsInDescendingPriority;
e->recentCallManager = recentCallManager; _phoneManager = phoneManager;
e->zrtpClientId = zrtpClientId; _recentCallManager = recentCallManager;
e->zrtpVersionId = zrtpVersionId; _zrtpClientId = zrtpClientId;
e->contactsManager = contactsManager; _zrtpVersionId = zrtpVersionId;
_contactsManager = contactsManager;
_networkManager = networkManager;
if (recentCallManager != nil) { if (recentCallManager != nil) {
// recentCallManagers are nil in unit tests because they would require unnecessary allocations. Detailed // recentCallManagers are nil in unit tests because they would require unnecessary allocations. Detailed
@ -107,12 +113,13 @@ static Environment *environment = nil;
[recentCallManager watchForCallsThrough:phoneManager untilCancelled:nil]; [recentCallManager watchForCallsThrough:phoneManager untilCancelled:nil];
} }
return e; return self;
} }
+ (PhoneManager *)phoneManager { + (PhoneManager *)phoneManager {
return Environment.getCurrent.phoneManager; return Environment.getCurrent.phoneManager;
} }
+ (id<Logging>)logging { + (id<Logging>)logging {
// Many tests create objects that rely on Environment only for logging. // Many tests create objects that rely on Environment only for logging.
// So we bypass the nil check in getCurrent and silently don't log during unit testing, instead of failing hard. // So we bypass the nil check in getCurrent and silently don't log during unit testing, instead of failing hard.

View File

@ -1,8 +1,10 @@
#import "Release.h"
#import "DiscardingLog.h" #import "DiscardingLog.h"
#import "PhoneManager.h" #import "PhoneManager.h"
#import "PhoneNumberUtil.h" #import "PhoneNumberUtil.h"
#import "RecentCallManager.h" #import "RecentCallManager.h"
#import "Release.h" #import <SignalServiceKit/ContactsUpdater.h>
#import <SignalServiceKit/TSNetworkManager.h>
#define RELEASE_ZRTP_CLIENT_ID @"Whisper 000 ".encodedAsAscii #define RELEASE_ZRTP_CLIENT_ID @"Whisper 000 ".encodedAsAscii
#define RELEASE_ZRTP_VERSION_ID @"1.10".encodedAsAscii #define RELEASE_ZRTP_VERSION_ID @"1.10".encodedAsAscii
@ -42,23 +44,22 @@ static unsigned char DH3K_PRIME[] = {
DDLogError(@"%@: %@, %d", error, relatedInfo, causedTermination); DDLogError(@"%@: %@, %d", error, relatedInfo, causedTermination);
}; };
return [Environment return [[Environment alloc] initWithLogging:logging
environmentWithLogging:logging errorNoter:errorNoter
andErrorNoter:errorNoter serverPort:31337
andServerPort:31337 masterServerHostName:@"master.whispersystems.org"
andMasterServerHostName:@"master.whispersystems.org" defaultRelayName:@"relay"
andDefaultRelayName:@"relay" relayServerHostNameSuffix:@"whispersystems.org"
andRelayServerHostNameSuffix:@"whispersystems.org" certificate:[Certificate certificateFromResourcePath:@"redphone" ofType:@"cer"]
andCertificate:[Certificate certificateFromResourcePath:@"redphone" ofType:@"cer"] supportedKeyAgreementProtocols:[self supportedKeyAgreementProtocols]
andSupportedKeyAgreementProtocols:[self supportedKeyAgreementProtocols] phoneManager:[PhoneManager phoneManagerWithErrorHandler:errorNoter]
andPhoneManager:[PhoneManager phoneManagerWithErrorHandler:errorNoter] recentCallManager:[RecentCallManager new]
andRecentCallManager:[RecentCallManager new] testingAndLegacyOptions:@[ ENVIRONMENT_LEGACY_OPTION_RTP_PADDING_BIT_IMPLIES_EXTENSION_BIT_AND_TWELVE_EXTRA_ZERO_BYTES_IN_HEADER ]
andTestingAndLegacyOptions:@[ zrtpClientId:RELEASE_ZRTP_CLIENT_ID
ENVIRONMENT_LEGACY_OPTION_RTP_PADDING_BIT_IMPLIES_EXTENSION_BIT_AND_TWELVE_EXTRA_ZERO_BYTES_IN_HEADER zrtpVersionId:RELEASE_ZRTP_VERSION_ID
] contactsManager:[OWSContactsManager new]
andZrtpClientId:RELEASE_ZRTP_CLIENT_ID contactsUpdater:[ContactsUpdater sharedUpdater]
andZrtpVersionId:RELEASE_ZRTP_VERSION_ID networkManager:[TSNetworkManager sharedManager]];
andContactsManager:[OWSContactsManager new]];
} }
+ (Environment *)stagingEnvironmentWithLogging:(id<Logging>)logging { + (Environment *)stagingEnvironmentWithLogging:(id<Logging>)logging {
@ -66,23 +67,22 @@ static unsigned char DH3K_PRIME[] = {
DDLogError(@"%@: %@, %d", error, relatedInfo, causedTermination); DDLogError(@"%@: %@, %d", error, relatedInfo, causedTermination);
}; };
return [Environment return [[Environment alloc] initWithLogging:logging
environmentWithLogging:logging errorNoter:errorNoter
andErrorNoter:errorNoter serverPort:31337
andServerPort:31337 masterServerHostName:@"redphone-staging.whispersystems.org"
andMasterServerHostName:@"redphone-staging.whispersystems.org" defaultRelayName:@"redphone-staging-relay"
andDefaultRelayName:@"redphone-staging-relay" relayServerHostNameSuffix:@"whispersystems.org"
andRelayServerHostNameSuffix:@"whispersystems.org" certificate:[Certificate certificateFromResourcePath:@"redphone" ofType:@"cer"]
andCertificate:[Certificate certificateFromResourcePath:@"redphone" ofType:@"cer"] supportedKeyAgreementProtocols:[self supportedKeyAgreementProtocols]
andSupportedKeyAgreementProtocols:[self supportedKeyAgreementProtocols] phoneManager:[PhoneManager phoneManagerWithErrorHandler:errorNoter]
andPhoneManager:[PhoneManager phoneManagerWithErrorHandler:errorNoter] recentCallManager:[RecentCallManager new]
andRecentCallManager:[RecentCallManager new] testingAndLegacyOptions:@[ ENVIRONMENT_LEGACY_OPTION_RTP_PADDING_BIT_IMPLIES_EXTENSION_BIT_AND_TWELVE_EXTRA_ZERO_BYTES_IN_HEADER ]
andTestingAndLegacyOptions:@[ zrtpClientId:RELEASE_ZRTP_CLIENT_ID
ENVIRONMENT_LEGACY_OPTION_RTP_PADDING_BIT_IMPLIES_EXTENSION_BIT_AND_TWELVE_EXTRA_ZERO_BYTES_IN_HEADER zrtpVersionId:RELEASE_ZRTP_VERSION_ID
] contactsManager:[OWSContactsManager new]
andZrtpClientId:RELEASE_ZRTP_CLIENT_ID contactsUpdater:[ContactsUpdater sharedUpdater]
andZrtpVersionId:RELEASE_ZRTP_VERSION_ID networkManager:[TSNetworkManager sharedManager]];
andContactsManager:[OWSContactsManager new]];
} }
+ (Environment *)unitTestEnvironment:(NSArray *)testingAndLegacyOptions { + (Environment *)unitTestEnvironment:(NSArray *)testingAndLegacyOptions {
@ -91,21 +91,23 @@ static unsigned char DH3K_PRIME[] = {
keyAgreementProtocols = @[ [Release supportedDH3KKeyAgreementProtocol] ]; keyAgreementProtocols = @[ [Release supportedDH3KKeyAgreementProtocol] ];
} }
return [Environment environmentWithLogging:[DiscardingLog discardingLog] return [[Environment alloc] initWithLogging:[DiscardingLog discardingLog]
andErrorNoter:^(id error, id relatedInfo, bool causedTermination) { errorNoter:^(id error, id relatedInfo, bool causedTermination) {
} }
andServerPort:31337 serverPort:31337
andMasterServerHostName:@"master.whispersystems.org" masterServerHostName:@"master.whispersystems.org"
andDefaultRelayName:@"relay" defaultRelayName:@"relay"
andRelayServerHostNameSuffix:@"whispersystems.org" relayServerHostNameSuffix:@"whispersystems.org"
andCertificate:[Certificate certificateFromResourcePath:@"redphone" ofType:@"cer"] certificate:[Certificate certificateFromResourcePath:@"redphone" ofType:@"cer"]
andSupportedKeyAgreementProtocols:keyAgreementProtocols supportedKeyAgreementProtocols:keyAgreementProtocols
andPhoneManager:nil phoneManager:nil
andRecentCallManager:nil recentCallManager:nil
andTestingAndLegacyOptions:testingAndLegacyOptions testingAndLegacyOptions:testingAndLegacyOptions
andZrtpClientId:TESTING_ZRTP_CLIENT_ID zrtpClientId:TESTING_ZRTP_CLIENT_ID
andZrtpVersionId:TESTING_ZRTP_VERSION_ID zrtpVersionId:TESTING_ZRTP_VERSION_ID
andContactsManager:nil]; contactsManager:nil
contactsUpdater:[ContactsUpdater sharedUpdater]
networkManager:[TSNetworkManager sharedManager]];
} }
+ (NSArray *)supportedKeyAgreementProtocols { + (NSArray *)supportedKeyAgreementProtocols {

View File

@ -6,17 +6,18 @@
// Copyright (c) 2014 Open Whisper Systems. All rights reserved. // Copyright (c) 2014 Open Whisper Systems. All rights reserved.
// //
#import "PushManager.h"
#import "AppDelegate.h" #import "AppDelegate.h"
#import "OWSContactsManager.h"
#import "InCallViewController.h" #import "InCallViewController.h"
#import "NSData+ows_StripToken.h" #import "NSData+ows_StripToken.h"
#import "NSDate+millisecondTimeStamp.h" #import "NSDate+millisecondTimeStamp.h"
#import "NotificationTracker.h" #import "NotificationTracker.h"
#import "OWSContactsManager.h"
#import "PreferencesUtil.h" #import "PreferencesUtil.h"
#import "PushManager.h"
#import "RPServerRequestsManager.h" #import "RPServerRequestsManager.h"
#import "TSMessagesManager+sendMessages.h" #import "TSOutgoingMessage.h"
#import "TSSocketManager.h" #import "TSSocketManager.h"
#import <SignalServiceKit/OWSMessageSender.h>
#define pushManagerDomain @"org.whispersystems.pushmanager" #define pushManagerDomain @"org.whispersystems.pushmanager"
@ -29,6 +30,7 @@
@property (nonatomic, retain) NSMutableArray *currentNotifications; @property (nonatomic, retain) NSMutableArray *currentNotifications;
@property (nonatomic) UIBackgroundTaskIdentifier callBackgroundTask; @property (nonatomic) UIBackgroundTaskIdentifier callBackgroundTask;
@property (nonatomic, readonly) OWSContactsManager *contactsManager; @property (nonatomic, readonly) OWSContactsManager *contactsManager;
@property (nonatomic, readonly) OWSMessageSender *messageSender;
@end @end
@ -38,19 +40,38 @@
static PushManager *sharedManager = nil; static PushManager *sharedManager = nil;
static dispatch_once_t onceToken; static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{ dispatch_once(&onceToken, ^{
sharedManager = [self new]; sharedManager = [[self alloc] initDefault];
}); });
return sharedManager; return sharedManager;
} }
- (instancetype)init { - (instancetype)initDefault
{
return [self initWithContactsManager:[Environment getCurrent].contactsManager
notificationTracker:[NotificationTracker notificationTracker]
networkManager:[Environment getCurrent].networkManager
storageManager:[TSStorageManager sharedManager]
contactsUpdater:[Environment getCurrent].contactsUpdater];
}
- (instancetype)initWithContactsManager:(OWSContactsManager *)contactsManager
notificationTracker:(NotificationTracker *)notificationTracker
networkManager:(TSNetworkManager *)networkManager
storageManager:(TSStorageManager *)storageManager
contactsUpdater:(ContactsUpdater *)contactsUpdater
{
self = [super init]; self = [super init];
if (!self) { if (!self) {
return self; return self;
} }
_contactsManager = [Environment getCurrent].contactsManager; _contactsManager = contactsManager;
_notificationTracker = [NotificationTracker notificationTracker]; _notificationTracker = notificationTracker;
_messageSender = [[OWSMessageSender alloc] initWithNetworkManager:networkManager
storageManager:storageManager
contactsManager:contactsManager
contactsUpdater:contactsUpdater];
_missingPermissionsAlertView = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"ACTION_REQUIRED_TITLE", @"") _missingPermissionsAlertView = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"ACTION_REQUIRED_TITLE", @"")
message:NSLocalizedString(@"PUSH_SETTINGS_MESSAGE", @"") message:NSLocalizedString(@"PUSH_SETTINGS_MESSAGE", @"")
delegate:nil delegate:nil
@ -107,7 +128,7 @@
notification.category = Signal_Call_Category; notification.category = Signal_Call_Category;
notification.soundName = @"r.caf"; notification.soundName = @"r.caf";
[[PushManager sharedManager] presentNotification:notification]; [self presentNotification:notification];
_lastCallNotification = notification; _lastCallNotification = notification;
if (_callBackgroundTask == UIBackgroundTaskInvalid) { if (_callBackgroundTask == UIBackgroundTaskInvalid) {
@ -185,19 +206,21 @@
[[TSOutgoingMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp] [[TSOutgoingMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
inThread:thread inThread:thread
messageBody:responseInfo[UIUserNotificationActionResponseTypedTextKey]]; messageBody:responseInfo[UIUserNotificationActionResponseTypedTextKey]];
[[TSMessagesManager sharedManager] sendMessage:message [self.messageSender sendMessage:message
inThread:thread
success:^{ success:^{
[self markAllInThreadAsRead:notification.userInfo completionHandler:completionHandler]; [self markAllInThreadAsRead:notification.userInfo completionHandler:completionHandler];
[[[[Environment getCurrent] signalsViewController] tableView] reloadData]; [[[[Environment getCurrent] signalsViewController] tableView] reloadData];
} }
failure:^{ failure:^(NSError *error) {
UILocalNotification *failedSendNotif = [[UILocalNotification alloc] init]; // TODO Surface the specific error in the notification?
failedSendNotif.alertBody = DDLogError(@"Message send failed with error: %@", error);
[NSString stringWithFormat:NSLocalizedString(@"NOTIFICATION_SEND_FAILED", nil), [thread name]];
failedSendNotif.userInfo = @{Signal_Thread_UserInfo_Key : thread.uniqueId}; UILocalNotification *failedSendNotif = [[UILocalNotification alloc] init];
[[PushManager sharedManager] presentNotification:failedSendNotif]; failedSendNotif.alertBody =
completionHandler(); [NSString stringWithFormat:NSLocalizedString(@"NOTIFICATION_SEND_FAILED", nil), [thread name]];
failedSendNotif.userInfo = @{ Signal_Thread_UserInfo_Key : thread.uniqueId };
[self presentNotification:failedSendNotif];
completionHandler();
}]; }];
} }
} else if ([identifier isEqualToString:Signal_Call_Accept_Identifier]) { } else if ([identifier isEqualToString:Signal_Call_Accept_Identifier]) {

View File

@ -38,8 +38,6 @@
#import "TSIncomingMessage.h" #import "TSIncomingMessage.h"
#import "TSInfoMessage.h" #import "TSInfoMessage.h"
#import "TSInvalidIdentityKeyErrorMessage.h" #import "TSInvalidIdentityKeyErrorMessage.h"
#import "TSMessagesManager+attachments.h"
#import "TSMessagesManager+sendMessages.h"
#import "UIFont+OWS.h" #import "UIFont+OWS.h"
#import "UIUtil.h" #import "UIUtil.h"
#import <AddressBookUI/AddressBookUI.h> #import <AddressBookUI/AddressBookUI.h>
@ -53,11 +51,16 @@
#import <JSQSystemSoundPlayer.h> #import <JSQSystemSoundPlayer.h>
#import <MobileCoreServices/UTCoreTypes.h> #import <MobileCoreServices/UTCoreTypes.h>
#import <SignalServiceKit/MimeTypeUtil.h> #import <SignalServiceKit/MimeTypeUtil.h>
#import <SignalServiceKit/OWSAttachmentsProcessor.h>
#import <SignalServiceKit/OWSDisappearingMessagesConfiguration.h> #import <SignalServiceKit/OWSDisappearingMessagesConfiguration.h>
#import <SignalServiceKit/OWSFingerprint.h> #import <SignalServiceKit/OWSFingerprint.h>
#import <SignalServiceKit/OWSFingerprintBuilder.h> #import <SignalServiceKit/OWSFingerprintBuilder.h>
#import <SignalServiceKit/OWSMessageSender.h>
#import <SignalServiceKit/SignalRecipient.h> #import <SignalServiceKit/SignalRecipient.h>
#import <SignalServiceKit/TSAccountManager.h> #import <SignalServiceKit/TSAccountManager.h>
#import <SignalServiceKit/TSInvalidIdentityKeySendingErrorMessage.h>
#import <SignalServiceKit/TSMessagesManager.h>
#import <SignalServiceKit/TSNetworkManager.h>
#import <YapDatabase/YapDatabaseView.h> #import <YapDatabase/YapDatabaseView.h>
@import Photos; @import Photos;
@ -116,7 +119,11 @@ typedef enum : NSUInteger {
@property (nonatomic, readonly) TSStorageManager *storageManager; @property (nonatomic, readonly) TSStorageManager *storageManager;
@property (nonatomic, readonly) OWSContactsManager *contactsManager; @property (nonatomic, readonly) OWSContactsManager *contactsManager;
@property (nonatomic, readonly) ContactsUpdater *contactsUpdater;
@property (nonatomic, readonly) OWSDisappearingMessagesJob *disappearingMessagesJob; @property (nonatomic, readonly) OWSDisappearingMessagesJob *disappearingMessagesJob;
@property (nonatomic, readonly) TSMessagesManager *messagesManager;
@property (nonatomic, readonly) TSNetworkManager *networkManager;
@property (nonatomic, readonly) OWSMessageSender *messageSender;
@property NSCache *messageAdapterCache; @property NSCache *messageAdapterCache;
@ -136,9 +143,16 @@ typedef enum : NSUInteger {
return self; return self;
} }
_contactsManager = [[Environment getCurrent] contactsManager]; _contactsManager = [Environment getCurrent].contactsManager;
_contactsUpdater = [Environment getCurrent].contactsUpdater;
_storageManager = [TSStorageManager sharedManager]; _storageManager = [TSStorageManager sharedManager];
_disappearingMessagesJob = [[OWSDisappearingMessagesJob alloc] initWithStorageManager:_storageManager]; _disappearingMessagesJob = [[OWSDisappearingMessagesJob alloc] initWithStorageManager:_storageManager];
_messagesManager = [TSMessagesManager sharedManager];
_networkManager = [TSNetworkManager sharedManager];
_messageSender = [[OWSMessageSender alloc] initWithNetworkManager:_networkManager
storageManager:_storageManager
contactsManager:_contactsManager
contactsUpdater:_contactsUpdater];
return self; return self;
} }
@ -150,9 +164,16 @@ typedef enum : NSUInteger {
return self; return self;
} }
_contactsManager = [[Environment getCurrent] contactsManager]; _contactsManager = [Environment getCurrent].contactsManager;
_contactsUpdater = [Environment getCurrent].contactsUpdater;
_storageManager = [TSStorageManager sharedManager]; _storageManager = [TSStorageManager sharedManager];
_disappearingMessagesJob = [[OWSDisappearingMessagesJob alloc] initWithStorageManager:_storageManager]; _disappearingMessagesJob = [[OWSDisappearingMessagesJob alloc] initWithStorageManager:_storageManager];
_messagesManager = [TSMessagesManager sharedManager];
_networkManager = [TSNetworkManager sharedManager];
_messageSender = [[OWSMessageSender alloc] initWithNetworkManager:_networkManager
storageManager:_storageManager
contactsManager:_contactsManager
contactsUpdater:_contactsUpdater];
return self; return self;
} }
@ -397,12 +418,12 @@ typedef enum : NSUInteger {
- (void)updateBackButtonAsync { - (void)updateBackButtonAsync {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSUInteger count = [[TSMessagesManager sharedManager] unreadMessagesCountExcept:self.thread]; NSUInteger count = [self.messagesManager unreadMessagesCountExcept:self.thread];
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
if (self) { if (self) {
[self setUnreadCount:count]; [self setUnreadCount:count];
} }
}); });
}); });
} }
@ -650,13 +671,12 @@ typedef enum : NSUInteger {
messageBody:text]; messageBody:text];
} }
[[TSMessagesManager sharedManager] sendMessage:message [self.messageSender sendMessage:message
inThread:self.thread
success:^{ success:^{
DDLogInfo(@"%@ Successfully sent message.", self.tag); DDLogInfo(@"%@ Successfully sent message.", self.tag);
} }
failure:^{ failure:^(NSError *error) {
DDLogWarn(@"%@ Failed to deliver message.", self.tag); DDLogWarn(@"%@ Failed to deliver message with error: %@", self.tag, error);
}]; }];
[self finishSendingMessage]; [self finishSendingMessage];
} }
@ -797,7 +817,13 @@ typedef enum : NSUInteger {
[self fixupiOS10EmojiBugForTextView:cell.textView]; [self fixupiOS10EmojiBugForTextView:cell.textView];
// END HACK iOS10EmojiBug see: https://github.com/WhisperSystems/Signal-iOS/issues/1368 // END HACK iOS10EmojiBug see: https://github.com/WhisperSystems/Signal-iOS/issues/1368
if (!message.isMediaMessage) { if (message.isMediaMessage) {
if (![message isKindOfClass:[TSMessageAdapter class]]) {
DDLogError(@"%@ Unexpected media message:%@", self.tag, message.class);
}
TSMessageAdapter *messageAdapter = (TSMessageAdapter *)message;
cell.mediaView.alpha = messageAdapter.mediaViewAlpha;
} else {
cell.textView.textColor = [UIColor whiteColor]; cell.textView.textColor = [UIColor whiteColor];
cell.textView.linkTextAttributes = @{ cell.textView.linkTextAttributes = @{
NSForegroundColorAttributeName : cell.textView.textColor, NSForegroundColorAttributeName : cell.textView.textColor,
@ -937,9 +963,9 @@ typedef enum : NSUInteger {
if (indexPath.row == 0) { if (indexPath.row == 0) {
showDate = YES; showDate = YES;
} else { } else {
TSMessageAdapter *currentMessage = [self messageAtIndexPath:indexPath]; id<OWSMessageData> currentMessage = [self messageAtIndexPath:indexPath];
TSMessageAdapter *previousMessage = id<OWSMessageData> previousMessage =
[self messageAtIndexPath:[NSIndexPath indexPathForItem:indexPath.row - 1 inSection:indexPath.section]]; [self messageAtIndexPath:[NSIndexPath indexPathForItem:indexPath.row - 1 inSection:indexPath.section]];
NSTimeInterval timeDifference = [currentMessage.date timeIntervalSinceDate:previousMessage.date]; NSTimeInterval timeDifference = [currentMessage.date timeIntervalSinceDate:previousMessage.date];
@ -953,7 +979,7 @@ typedef enum : NSUInteger {
- (NSAttributedString *)collectionView:(JSQMessagesCollectionView *)collectionView - (NSAttributedString *)collectionView:(JSQMessagesCollectionView *)collectionView
attributedTextForCellTopLabelAtIndexPath:(NSIndexPath *)indexPath { attributedTextForCellTopLabelAtIndexPath:(NSIndexPath *)indexPath {
if ([self showDateAtIndexPath:indexPath]) { if ([self showDateAtIndexPath:indexPath]) {
TSMessageAdapter *currentMessage = [self messageAtIndexPath:indexPath]; id<OWSMessageData> currentMessage = [self messageAtIndexPath:indexPath];
return [[JSQMessagesTimestampFormatter sharedFormatter] attributedTimestampForDate:currentMessage.date]; return [[JSQMessagesTimestampFormatter sharedFormatter] attributedTimestampForDate:currentMessage.date];
} }
@ -963,7 +989,7 @@ typedef enum : NSUInteger {
- (BOOL)shouldShowMessageStatusAtIndexPath:(NSIndexPath *)indexPath - (BOOL)shouldShowMessageStatusAtIndexPath:(NSIndexPath *)indexPath
{ {
TSMessageAdapter *currentMessage = [self messageAtIndexPath:indexPath]; id<OWSMessageData> currentMessage = [self messageAtIndexPath:indexPath];
if (currentMessage.isExpiringMessage) { if (currentMessage.isExpiringMessage) {
return YES; return YES;
@ -972,13 +998,14 @@ typedef enum : NSUInteger {
return !![self collectionView:self.collectionView attributedTextForCellBottomLabelAtIndexPath:indexPath]; return !![self collectionView:self.collectionView attributedTextForCellBottomLabelAtIndexPath:indexPath];
} }
- (TSMessageAdapter *)nextOutgoingMessage:(NSIndexPath *)indexPath { - (id<OWSMessageData>)nextOutgoingMessage:(NSIndexPath *)indexPath
TSMessageAdapter *nextMessage = {
id<OWSMessageData> nextMessage =
[self messageAtIndexPath:[NSIndexPath indexPathForRow:indexPath.row + 1 inSection:indexPath.section]]; [self messageAtIndexPath:[NSIndexPath indexPathForRow:indexPath.row + 1 inSection:indexPath.section]];
int i = 1; int i = 1;
while (indexPath.item + i < [self.collectionView numberOfItemsInSection:indexPath.section] - 1 && while (indexPath.item + i < [self.collectionView numberOfItemsInSection:indexPath.section] - 1
![self isMessageOutgoingAndDelivered:nextMessage]) { && !nextMessage.isOutgoingAndDelivered) {
i++; i++;
nextMessage = nextMessage =
[self messageAtIndexPath:[NSIndexPath indexPathForRow:indexPath.row + i inSection:indexPath.section]]; [self messageAtIndexPath:[NSIndexPath indexPathForRow:indexPath.row + i inSection:indexPath.section]];
@ -987,18 +1014,6 @@ typedef enum : NSUInteger {
return nextMessage; return nextMessage;
} }
- (BOOL)isMessageOutgoingAndDelivered:(TSMessageAdapter *)message
{
if (message.messageType == TSOutgoingMessageAdapter) {
TSOutgoingMessage *outgoingMessage = (TSOutgoingMessage *)message;
if(outgoingMessage.messageState == TSOutgoingMessageStateDelivered) {
return YES;
}
}
return NO;
}
- (NSAttributedString *)collectionView:(JSQMessagesCollectionView *)collectionView - (NSAttributedString *)collectionView:(JSQMessagesCollectionView *)collectionView
attributedTextForCellBottomLabelAtIndexPath:(NSIndexPath *)indexPath attributedTextForCellBottomLabelAtIndexPath:(NSIndexPath *)indexPath
{ {
@ -1011,11 +1026,8 @@ typedef enum : NSUInteger {
if (message.messageType == TSOutgoingMessageAdapter) { if (message.messageType == TSOutgoingMessageAdapter) {
TSOutgoingMessage *outgoingMessage = (TSOutgoingMessage *)message.interaction; TSOutgoingMessage *outgoingMessage = (TSOutgoingMessage *)message.interaction;
if (outgoingMessage.messageState == TSOutgoingMessageStateUnsent) { if (outgoingMessage.messageState == TSOutgoingMessageStateUnsent) {
NSAttributedString *failedString = return [[NSAttributedString alloc] initWithString:NSLocalizedString(@"FAILED_SENDING_TEXT", nil)];
[[NSAttributedString alloc] initWithString:NSLocalizedString(@"FAILED_SENDING_TEXT", nil)]; } else if (message.isOutgoingAndDelivered) {
return failedString;
} else if ([self isMessageOutgoingAndDelivered:message]) {
NSAttributedString *deliveredString = NSAttributedString *deliveredString =
[[NSAttributedString alloc] initWithString:NSLocalizedString(@"DELIVERED_MESSAGE_TEXT", @"")]; [[NSAttributedString alloc] initWithString:NSLocalizedString(@"DELIVERED_MESSAGE_TEXT", @"")];
@ -1027,10 +1039,13 @@ typedef enum : NSUInteger {
// Or when the next message is *not* an outgoing delivered message. // Or when the next message is *not* an outgoing delivered message.
TSMessageAdapter *nextMessage = [self nextOutgoingMessage:indexPath]; TSMessageAdapter *nextMessage = [self nextOutgoingMessage:indexPath];
if (![self isMessageOutgoingAndDelivered:nextMessage]) { if (!nextMessage.isOutgoingAndDelivered) {
[self updateLastDeliveredMessage:message]; [self updateLastDeliveredMessage:message];
return deliveredString; return deliveredString;
} }
} else if (message.isMediaBeingSent) {
return [[NSAttributedString alloc] initWithString:NSLocalizedString(@"UPLOADING_MESSAGE_TEXT",
@"message footer while attachment is uploading")];
} }
} else if (message.messageType == TSIncomingMessageAdapter && [self.thread isKindOfClass:[TSGroupThread class]]) { } else if (message.messageType == TSIncomingMessageAdapter && [self.thread isKindOfClass:[TSGroupThread class]]) {
TSIncomingMessage *incomingMessage = (TSIncomingMessage *)message.interaction; TSIncomingMessage *incomingMessage = (TSIncomingMessage *)message.interaction;
@ -1101,12 +1116,17 @@ typedef enum : NSUInteger {
switch (messageItem.messageType) { switch (messageItem.messageType) {
case TSOutgoingMessageAdapter: { case TSOutgoingMessageAdapter: {
TSOutgoingMessage *outgoingMessage = (TSOutgoingMessage *)messageItem; TSOutgoingMessage *outgoingMessage = (TSOutgoingMessage *)interaction;
if (outgoingMessage.messageState == TSOutgoingMessageStateUnsent) { if (outgoingMessage.messageState == TSOutgoingMessageStateUnsent) {
[self handleUnsentMessageTap:(TSOutgoingMessage *)interaction]; [self handleUnsentMessageTap:outgoingMessage];
// This `break` is intentionally within the if.
// We want to activate fullscreen media view for sent items
// but not those which failed-to-send
break;
} }
// No `break` as we want to fall through to capture tapping on Outgoing media items too
} }
// No `break` as we want to fall through to capture tapping on media items
case TSIncomingMessageAdapter: { case TSIncomingMessageAdapter: {
BOOL isMediaMessage = [messageItem isMediaMessage]; BOOL isMediaMessage = [messageItem isMediaMessage];
@ -1306,7 +1326,17 @@ typedef enum : NSUInteger {
// FIXME possible for pointer to get stuck in isDownloading state if app is closed while downloading. // FIXME possible for pointer to get stuck in isDownloading state if app is closed while downloading.
// see: https://github.com/WhisperSystems/Signal-iOS/issues/1254 // see: https://github.com/WhisperSystems/Signal-iOS/issues/1254
if (!pointer.isDownloading) { if (!pointer.isDownloading) {
[[TSMessagesManager sharedManager] retrieveAttachment:pointer messageId:message.uniqueId]; OWSAttachmentsProcessor *processor =
[[OWSAttachmentsProcessor alloc] initWithAttachmentPointer:pointer
networkManager:self.networkManager];
[processor fetchAttachmentsForMessage:message
success:^(TSAttachmentStream *_Nonnull attachmentStream) {
DDLogInfo(
@"%@ Successfully redownloaded attachment in thread: %@", self.tag, message.thread);
}
failure:^(NSError *_Nonnull error) {
DDLogWarn(@"%@ Failed to redownload message with error: %@", self.tag, error);
}];
} }
} }
} }
@ -1401,25 +1431,25 @@ typedef enum : NSUInteger {
- (void)handleUnsentMessageTap:(TSOutgoingMessage *)message { - (void)handleUnsentMessageTap:(TSOutgoingMessage *)message {
[self dismissKeyBoard]; [self dismissKeyBoard];
[DJWActionSheet showInView:self.parentViewController.view [DJWActionSheet showInView:self.parentViewController.view
withTitle:nil withTitle:message.mostRecentFailureText
cancelButtonTitle:NSLocalizedString(@"TXT_CANCEL_TITLE", @"") cancelButtonTitle:NSLocalizedString(@"TXT_CANCEL_TITLE", @"")
destructiveButtonTitle:NSLocalizedString(@"TXT_DELETE_TITLE", @"") destructiveButtonTitle:NSLocalizedString(@"TXT_DELETE_TITLE", @"")
otherButtonTitles:@[ NSLocalizedString(@"SEND_AGAIN_BUTTON", @"") ] otherButtonTitles:@[ NSLocalizedString(@"SEND_AGAIN_BUTTON", @"") ]
tapBlock:^(DJWActionSheet *actionSheet, NSInteger tappedButtonIndex) { tapBlock:^(DJWActionSheet *actionSheet, NSInteger tappedButtonIndex) {
if (tappedButtonIndex == actionSheet.cancelButtonIndex) { if (tappedButtonIndex == actionSheet.cancelButtonIndex) {
DDLogDebug(@"User Cancelled"); DDLogDebug(@"%@ User cancelled unsent dialog", self.tag);
} else if (tappedButtonIndex == actionSheet.destructiveButtonIndex) { } else if (tappedButtonIndex == actionSheet.destructiveButtonIndex) {
[self.editingDatabaseConnection DDLogInfo(@"%@ User chose to delete unsent message.", self.tag);
readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { [message remove];
[message removeWithTransaction:transaction]; } else {
}]; [self.messageSender sendMessage:message
} else { success:^{
[[TSMessagesManager sharedManager] sendMessage:message DDLogInfo(@"%@ Successfully resent failed message.", self.tag);
inThread:self.thread }
success:nil failure:^(NSError *_Nonnull error) {
failure:nil]; DDLogWarn(@"%@ Failed to send message with error: %@", self.tag, error);
[self finishSendingMessage]; }];
} }
}]; }];
} }
@ -1459,7 +1489,21 @@ typedef enum : NSUInteger {
break; break;
case 1: case 1:
DDLogInfo(@"%@ Remote Key Changed actions: Accepted new identity key", self.tag); DDLogInfo(@"%@ Remote Key Changed actions: Accepted new identity key", self.tag);
[errorMessage acceptNewIdentityKey]; [errorMessage acceptNewIdentityKey];
if ([errorMessage isKindOfClass:[TSInvalidIdentityKeySendingErrorMessage class]]) {
[self.messageSender
resendMessageFromKeyError:(TSInvalidIdentityKeySendingErrorMessage *)
errorMessage
success:^{
DDLogDebug(@"%@ Successfully resent key-error message.", self.tag);
}
failure:^(NSError *_Nonnull error) {
DDLogError(@"%@ Failed to resend key-error message with error:%@",
self.tag,
error);
}];
}
break; break;
default: default:
DDLogInfo(@"%@ Remote Key Changed actions: Unhandled button pressed: %d", DDLogInfo(@"%@ Remote Key Changed actions: Unhandled button pressed: %d",
@ -1564,7 +1608,7 @@ typedef enum : NSUInteger {
// Video picked from library or captured with camera // Video picked from library or captured with camera
NSURL *videoURL = info[UIImagePickerControllerMediaURL]; NSURL *videoURL = info[UIImagePickerControllerMediaURL];
[self sendQualityAdjustedAttachment:videoURL]; [self sendQualityAdjustedAttachmentForVideo:videoURL];
} else if (picker.sourceType == UIImagePickerControllerSourceTypeCamera) { } else if (picker.sourceType == UIImagePickerControllerSourceTypeCamera) {
// Static Image captured from camera // Static Image captured from camera
@ -1649,13 +1693,16 @@ typedef enum : NSUInteger {
DDLogVerbose(@"Sending attachment. Size in bytes: %lu, contentType: %@", DDLogVerbose(@"Sending attachment. Size in bytes: %lu, contentType: %@",
(unsigned long)attachmentData.length, (unsigned long)attachmentData.length,
attachmentType); attachmentType);
[self.messageSender sendAttachmentData:attachmentData
[[TSMessagesManager sharedManager] sendAttachment:attachmentData contentType:attachmentType
contentType:attachmentType inMessage:message
inMessage:message success:^{
thread:self.thread DDLogDebug(@"%@ Successfully sent message attachment.", self.tag);
success:nil }
failure:nil]; failure:^(NSError *error) {
DDLogError(
@"%@ Failed to send message attachment with error: %@", self.tag, error);
}];
}]; }];
} }
@ -1672,7 +1719,7 @@ typedef enum : NSUInteger {
return [NSURL fileURLWithPath:basePath]; return [NSURL fileURLWithPath:basePath];
} }
- (void)sendQualityAdjustedAttachment:(NSURL *)movieURL { - (void)sendQualityAdjustedAttachmentForVideo:(NSURL *)movieURL {
AVAsset *video = [AVAsset assetWithURL:movieURL]; AVAsset *video = [AVAsset assetWithURL:movieURL];
AVAssetExportSession *exportSession = AVAssetExportSession *exportSession =
[AVAssetExportSession exportSessionWithAsset:video presetName:AVAssetExportPresetMediumQuality]; [AVAssetExportSession exportSessionWithAsset:video presetName:AVAssetExportPresetMediumQuality];
@ -1841,7 +1888,6 @@ typedef enum : NSUInteger {
[self.messageAdapterCache removeObjectForKey:collectionKey.key]; [self.messageAdapterCache removeObjectForKey:collectionKey.key];
} }
[self.collectionView reloadItemsAtIndexPaths:@[ rowChange.indexPath ]]; [self.collectionView reloadItemsAtIndexPaths:@[ rowChange.indexPath ]];
scrollToBottom = YES;
break; break;
} }
} }
@ -1854,7 +1900,9 @@ typedef enum : NSUInteger {
[self.collectionView reloadData]; [self.collectionView reloadData];
} }
if (scrollToBottom) { if (scrollToBottom) {
[self scrollToBottomAnimated:YES]; dispatch_async(dispatch_get_main_queue(), ^{
[self scrollToBottomAnimated:YES];
});
} }
}]; }];
} }
@ -1997,24 +2045,19 @@ typedef enum : NSUInteger {
- (BOOL)collectionView:(UICollectionView *)collectionView - (BOOL)collectionView:(UICollectionView *)collectionView
canPerformAction:(SEL)action canPerformAction:(SEL)action
forItemAtIndexPath:(NSIndexPath *)indexPath forItemAtIndexPath:(NSIndexPath *)indexPath
withSender:(id)sender { withSender:(id)sender
TSMessageAdapter *messageAdapter = [self messageAtIndexPath:indexPath]; {
// HACK make sure method exists before calling since messageAtIndexPath doesn't id<OWSMessageData> messageData = [self messageAtIndexPath:indexPath];
// always return TSMessageAdapters - it can also return JSQCall! return [messageData canPerformEditingAction:action];
if ([messageAdapter respondsToSelector:@selector(canPerformEditingAction:)]) {
return [messageAdapter canPerformEditingAction:action];
}
else {
return NO;
}
} }
- (void)collectionView:(UICollectionView *)collectionView - (void)collectionView:(UICollectionView *)collectionView
performAction:(SEL)action performAction:(SEL)action
forItemAtIndexPath:(NSIndexPath *)indexPath forItemAtIndexPath:(NSIndexPath *)indexPath
withSender:(id)sender { withSender:(id)sender
[[self messageAtIndexPath:indexPath] performEditingAction:action]; {
id<OWSMessageData> messageData = [self messageAtIndexPath:indexPath];
[messageData performEditingAction:action];
} }
- (void)updateGroupModelTo:(TSGroupModel *)newGroupModel - (void)updateGroupModelTo:(TSGroupModel *)newGroupModel
@ -2037,15 +2080,24 @@ typedef enum : NSUInteger {
message.customMessage = updateGroupInfo; message.customMessage = updateGroupInfo;
}]; }];
if (newGroupModel.groupImage != nil) { if (newGroupModel.groupImage) {
[[TSMessagesManager sharedManager] sendAttachment:UIImagePNGRepresentation(newGroupModel.groupImage) [self.messageSender sendAttachmentData:UIImagePNGRepresentation(newGroupModel.groupImage)
contentType:OWSMimeTypeImagePng contentType:OWSMimeTypeImagePng
inMessage:message inMessage:message
thread:groupThread success:^{
success:nil DDLogDebug(@"%@ Successfully sent group update with avatar", self.tag);
failure:nil]; }
failure:^(NSError *_Nonnull error) {
DDLogError(@"%@ Failed to send group avatar update with error: %@", self.tag, error);
}];
} else { } else {
[[TSMessagesManager sharedManager] sendMessage:message inThread:groupThread success:nil failure:nil]; [self.messageSender sendMessage:message
success:^{
DDLogDebug(@"%@ Successfully sent group update", self.tag);
}
failure:^(NSError *_Nonnull error) {
DDLogError(@"%@ Failed to send group update with error: %@", self.tag, error);
}];
} }
self.thread = groupThread; self.thread = groupThread;

View File

@ -20,20 +20,50 @@
#import <MobileCoreServices/UTCoreTypes.h> #import <MobileCoreServices/UTCoreTypes.h>
#import <SignalServiceKit/MimeTypeUtil.h> #import <SignalServiceKit/MimeTypeUtil.h>
#import <SignalServiceKit/NSDate+millisecondTimeStamp.h> #import <SignalServiceKit/NSDate+millisecondTimeStamp.h>
#import <SignalServiceKit/OWSMessageSender.h>
#import <SignalServiceKit/TSAccountManager.h> #import <SignalServiceKit/TSAccountManager.h>
#import <SignalServiceKit/TSMessagesManager+attachments.h>
#import <SignalServiceKit/TSMessagesManager+sendMessages.h>
static NSString *const kUnwindToMessagesViewSegue = @"UnwindToMessagesViewSegue"; static NSString *const kUnwindToMessagesViewSegue = @"UnwindToMessagesViewSegue";
@interface NewGroupViewController () { @interface NewGroupViewController () {
NSArray *contacts; NSArray *contacts;
} }
@property TSGroupThread *thread; @property TSGroupThread *thread;
@property (nonatomic, readonly) OWSMessageSender *messageSender;
@end @end
@implementation NewGroupViewController @implementation NewGroupViewController
- (instancetype)init
{
self = [super init];
if (!self) {
return self;
}
_messageSender = [[OWSMessageSender alloc] initWithNetworkManager:[Environment getCurrent].networkManager
storageManager:[TSStorageManager sharedManager]
contactsManager:[Environment getCurrent].contactsManager
contactsUpdater:[Environment getCurrent].contactsUpdater];
return self;
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (!self) {
return self;
}
_messageSender = [[OWSMessageSender alloc] initWithNetworkManager:[Environment getCurrent].networkManager
storageManager:[TSStorageManager sharedManager]
contactsManager:[Environment getCurrent].contactsManager
contactsUpdater:[Environment getCurrent].contactsUpdater];
return self;
}
- (void)configWithThread:(TSGroupThread *)gThread { - (void)configWithThread:(TSGroupThread *)gThread {
_thread = gThread; _thread = gThread;
} }
@ -124,76 +154,53 @@ static NSString *const kUnwindToMessagesViewSegue = @"UnwindToMessagesViewSegue"
self.thread = [TSGroupThread getOrCreateThreadWithGroupModel:model transaction:transaction]; self.thread = [TSGroupThread getOrCreateThreadWithGroupModel:model transaction:transaction];
}]; }];
void (^popToThread)() = ^{
dispatch_async(dispatch_get_main_queue(), ^{
[self dismissViewControllerAnimated:YES
completion:^{
[Environment messageGroup:self.thread];
}];
});
};
void (^removeThreadWithError)(NSError *error) = ^(NSError *error) {
[self.thread remove];
dispatch_async(dispatch_get_main_queue(), ^{
[self dismissViewControllerAnimated:YES
completion:^{
SignalAlertView(NSLocalizedString(@"GROUP_CREATING_FAILED", nil),
error.localizedDescription);
}];
});
};
UIAlertController *alertController = UIAlertController *alertController =
[UIAlertController alertControllerWithTitle:NSLocalizedString(@"GROUP_CREATING", nil) [UIAlertController alertControllerWithTitle:NSLocalizedString(@"GROUP_CREATING", nil)
message:nil message:nil
preferredStyle:UIAlertControllerStyleAlert]; preferredStyle:UIAlertControllerStyleAlert];
[self
presentViewController:alertController
animated:YES
completion:^{
TSOutgoingMessage *message =
[[TSOutgoingMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
inThread:self.thread
messageBody:@""
attachmentIds:[NSMutableArray new]];
message.groupMetaMessage = TSGroupMessageNew;
message.customMessage = NSLocalizedString(@"GROUP_CREATED", nil);
if (model.groupImage != nil) {
[[TSMessagesManager sharedManager] sendAttachment:UIImagePNGRepresentation(model.groupImage)
contentType:OWSMimeTypeImagePng
inMessage:message
thread:self.thread
success:^{
dispatch_async(dispatch_get_main_queue(), ^{
[self dismissViewControllerAnimated:YES
completion:^{
[Environment messageGroup:self.thread];
}];
}); [self presentViewController:alertController
} animated:YES
failure:^{ completion:^{
dispatch_async(dispatch_get_main_queue(), ^{ TSOutgoingMessage *message =
[self [[TSOutgoingMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
dismissViewControllerAnimated:YES inThread:self.thread
completion:^{ messageBody:@""
[TSStorageManager.sharedManager.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction){ attachmentIds:[NSMutableArray new]];
[self.thread removeWithTransaction:transaction];
}];
SignalAlertView(NSLocalizedString(@"GROUP_CREATING_FAILED", nil), message.groupMetaMessage = TSGroupMessageNew;
NSLocalizedString(@"NETWORK_ERROR_RECOVERY", nil)); message.customMessage = NSLocalizedString(@"GROUP_CREATED", nil);
}]; if (model.groupImage) {
}); [self.messageSender sendAttachmentData:UIImagePNGRepresentation(model.groupImage)
}]; contentType:OWSMimeTypeImagePng
} else { inMessage:message
[[TSMessagesManager sharedManager] sendMessage:message success:popToThread
inThread:self.thread failure:removeThreadWithError];
success:^{ } else {
dispatch_async(dispatch_get_main_queue(), ^{ [self.messageSender sendMessage:message success:popToThread failure:removeThreadWithError];
[self dismissViewControllerAnimated:YES }
completion:^{ }];
[Environment messageGroup:self.thread];
}];
});
}
failure:^{
dispatch_async(dispatch_get_main_queue(), ^{
[self
dismissViewControllerAnimated:YES
completion:^{
[TSStorageManager.sharedManager.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction){
[self.thread removeWithTransaction:transaction];
}];
SignalAlertView(NSLocalizedString(@"GROUP_CREATING_FAILED", nil),
NSLocalizedString(@"NETWORK_ERROR_RECOVERY", nil));
}];
});
}];
}
}];
} }

View File

@ -9,17 +9,18 @@
#import "OWSContactsManager.h" #import "OWSContactsManager.h"
#import "PhoneNumber.h" #import "PhoneNumber.h"
#import "ShowGroupMembersViewController.h" #import "ShowGroupMembersViewController.h"
#import "UIUtil.h"
#import "UIFont+OWS.h" #import "UIFont+OWS.h"
#import "UIUtil.h"
#import <25519/Curve25519.h> #import <25519/Curve25519.h>
#import <SignalServiceKit/NSDate+millisecondTimeStamp.h> #import <SignalServiceKit/NSDate+millisecondTimeStamp.h>
#import <SignalServiceKit/OWSDisappearingConfigurationUpdateInfoMessage.h> #import <SignalServiceKit/OWSDisappearingConfigurationUpdateInfoMessage.h>
#import <SignalServiceKit/OWSDisappearingMessagesConfiguration.h> #import <SignalServiceKit/OWSDisappearingMessagesConfiguration.h>
#import <SignalServiceKit/OWSFingerprint.h> #import <SignalServiceKit/OWSFingerprint.h>
#import <SignalServiceKit/OWSFingerprintBuilder.h> #import <SignalServiceKit/OWSFingerprintBuilder.h>
#import <SignalServiceKit/OWSMessageSender.h>
#import <SignalServiceKit/OWSNotifyRemoteOfUpdatedDisappearingConfigurationJob.h> #import <SignalServiceKit/OWSNotifyRemoteOfUpdatedDisappearingConfigurationJob.h>
#import <SignalServiceKit/TSGroupThread.h> #import <SignalServiceKit/TSGroupThread.h>
#import <SignalServiceKit/TSMessagesManager+sendMessages.h> #import <SignalServiceKit/TSOutgoingMessage.h>
#import <SignalServiceKit/TSStorageManager.h> #import <SignalServiceKit/TSStorageManager.h>
#import <SignalServiceKit/TSThread.h> #import <SignalServiceKit/TSThread.h>
@ -76,7 +77,7 @@ static NSString *const OWSConversationSettingsTableViewControllerSegueShowGroupM
@property (nonatomic, readonly) TSStorageManager *storageManager; @property (nonatomic, readonly) TSStorageManager *storageManager;
@property (nonatomic, readonly) OWSContactsManager *contactsManager; @property (nonatomic, readonly) OWSContactsManager *contactsManager;
@property (nonatomic, readonly) TSMessagesManager *messagesManager; @property (nonatomic, readonly) OWSMessageSender *messageSender;
@end @end
@ -90,8 +91,11 @@ static NSString *const OWSConversationSettingsTableViewControllerSegueShowGroupM
} }
_storageManager = [TSStorageManager sharedManager]; _storageManager = [TSStorageManager sharedManager];
_contactsManager = [[Environment getCurrent] contactsManager]; _contactsManager = [Environment getCurrent].contactsManager;
_messagesManager = [TSMessagesManager sharedManager]; _messageSender = [[OWSMessageSender alloc] initWithNetworkManager:[Environment getCurrent].networkManager
storageManager:_storageManager
contactsManager:_contactsManager
contactsUpdater:[Environment getCurrent].contactsUpdater];
return self; return self;
} }
@ -104,8 +108,11 @@ static NSString *const OWSConversationSettingsTableViewControllerSegueShowGroupM
} }
_storageManager = [TSStorageManager sharedManager]; _storageManager = [TSStorageManager sharedManager];
_contactsManager = [[Environment getCurrent] contactsManager]; _contactsManager = [Environment getCurrent].contactsManager;
_messagesManager = [TSMessagesManager sharedManager]; _messageSender = [[OWSMessageSender alloc] initWithNetworkManager:[Environment getCurrent].networkManager
storageManager:_storageManager
contactsManager:_contactsManager
contactsUpdater:[Environment getCurrent].contactsUpdater];
return self; return self;
} }
@ -207,7 +214,7 @@ static NSString *const OWSConversationSettingsTableViewControllerSegueShowGroupM
[OWSNotifyRemoteOfUpdatedDisappearingConfigurationJob [OWSNotifyRemoteOfUpdatedDisappearingConfigurationJob
runWithConfiguration:self.disappearingMessagesConfiguration runWithConfiguration:self.disappearingMessagesConfiguration
thread:self.thread thread:self.thread
messagesManager:self.messagesManager]; messageSender:self.messageSender];
} }
} }
@ -352,13 +359,12 @@ static NSString *const OWSConversationSettingsTableViewControllerSegueShowGroupM
inThread:gThread inThread:gThread
messageBody:@""]; messageBody:@""];
message.groupMetaMessage = TSGroupMessageQuit; message.groupMetaMessage = TSGroupMessageQuit;
[self.messagesManager sendMessage:message [self.messageSender sendMessage:message
inThread:gThread
success:^{ success:^{
DDLogInfo(@"%@ Successfully left group.", self.tag); DDLogInfo(@"%@ Successfully left group.", self.tag);
} }
failure:^{ failure:^(NSError *error) {
DDLogWarn(@"%@ Failed to leave group", self.tag); DDLogWarn(@"%@ Failed to leave group with error: %@", self.tag, error);
}]; }];
NSMutableArray *newGroupMemberIds = [NSMutableArray arrayWithArray:gThread.groupModel.groupMemberIds]; NSMutableArray *newGroupMemberIds = [NSMutableArray arrayWithArray:gThread.groupModel.groupMemberIds];

View File

@ -18,12 +18,13 @@
#import "TSAccountManager.h" #import "TSAccountManager.h"
#import "TSDatabaseView.h" #import "TSDatabaseView.h"
#import "TSGroupThread.h" #import "TSGroupThread.h"
#import "TSMessagesManager+sendMessages.h"
#import "TSStorageManager.h" #import "TSStorageManager.h"
#import "VersionMigrations.h" #import "VersionMigrations.h"
#import <SignalServiceKit/OWSMessageSender.h>
#import <SignalServiceKit/TSMessagesManager.h>
#import <SignalServiceKit/TSOutgoingMessage.h>
#import <YapDatabase/YapDatabaseViewChange.h> #import <YapDatabase/YapDatabaseViewChange.h>
#import "YapDatabaseViewConnection.h" #import <YapDatabase/YapDatabaseViewConnection.h>
#define CELL_HEIGHT 72.0f #define CELL_HEIGHT 72.0f
#define HEADER_HEIGHT 44.0f #define HEADER_HEIGHT 44.0f
@ -40,6 +41,8 @@ static NSString *const kShowSignupFlowSegue = @"showSignupFlow";
@property (nonatomic, retain) UISegmentedControl *segmentedControl; @property (nonatomic, retain) UISegmentedControl *segmentedControl;
@property (nonatomic, strong) id previewingContext; @property (nonatomic, strong) id previewingContext;
@property (nonatomic, readonly) OWSContactsManager *contactsManager; @property (nonatomic, readonly) OWSContactsManager *contactsManager;
@property (nonatomic, readonly) TSMessagesManager *messagesManager;
@property (nonatomic, readonly) OWSMessageSender *messageSender;
@end @end
@ -53,6 +56,11 @@ static NSString *const kShowSignupFlowSegue = @"showSignupFlow";
} }
_contactsManager = [Environment getCurrent].contactsManager; _contactsManager = [Environment getCurrent].contactsManager;
_messagesManager = [TSMessagesManager sharedManager];
_messageSender = [[OWSMessageSender alloc] initWithNetworkManager:[Environment getCurrent].networkManager
storageManager:[TSStorageManager sharedManager]
contactsManager:_contactsManager
contactsUpdater:[Environment getCurrent].contactsUpdater];
return self; return self;
} }
@ -65,6 +73,11 @@ static NSString *const kShowSignupFlowSegue = @"showSignupFlow";
} }
_contactsManager = [Environment getCurrent].contactsManager; _contactsManager = [Environment getCurrent].contactsManager;
_messagesManager = [TSMessagesManager sharedManager];
_messageSender = [[OWSMessageSender alloc] initWithNetworkManager:[Environment getCurrent].networkManager
storageManager:[TSStorageManager sharedManager]
contactsManager:_contactsManager
contactsUpdater:[Environment getCurrent].contactsUpdater];
return self; return self;
} }
@ -284,20 +297,19 @@ static NSString *const kShowSignupFlowSegue = @"showSignupFlow";
messageBody:@"" messageBody:@""
attachmentIds:[NSMutableArray new]]; attachmentIds:[NSMutableArray new]];
message.groupMetaMessage = TSGroupMessageQuit; message.groupMetaMessage = TSGroupMessageQuit;
[[TSMessagesManager sharedManager] sendMessage:message [self.messageSender sendMessage:message
inThread:thread
success:^{ success:^{
[self dismissViewControllerAnimated:YES [self dismissViewControllerAnimated:YES
completion:^{ completion:^{
[self deleteThread:thread]; [self deleteThread:thread];
}]; }];
} }
failure:^{ failure:^(NSError *error) {
[self dismissViewControllerAnimated:YES [self dismissViewControllerAnimated:YES
completion:^{ completion:^{
SignalAlertView(NSLocalizedString(@"GROUP_REMOVING_FAILED", nil), SignalAlertView(NSLocalizedString(@"GROUP_REMOVING_FAILED", nil),
NSLocalizedString(@"NETWORK_ERROR_RECOVERY", nil)); error.localizedRecoverySuggestion);
}]; }];
}]; }];
} else { } else {
[self deleteThread:thread]; [self deleteThread:thread];
@ -326,7 +338,7 @@ static NSString *const kShowSignupFlowSegue = @"showSignupFlow";
} }
- (NSNumber *)updateInboxCountLabel { - (NSNumber *)updateInboxCountLabel {
NSUInteger numberOfItems = [[TSMessagesManager sharedManager] unreadMessagesCount]; NSUInteger numberOfItems = [self.messagesManager unreadMessagesCount];
NSNumber *badgeNumber = [NSNumber numberWithUnsignedInteger:numberOfItems]; NSNumber *badgeNumber = [NSNumber numberWithUnsignedInteger:numberOfItems];
NSString *unreadString = NSLocalizedString(@"WHISPER_NAV_BAR_TITLE", nil); NSString *unreadString = NSLocalizedString(@"WHISPER_NAV_BAR_TITLE", nil);

View File

@ -24,6 +24,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)prepareForReuse - (void)prepareForReuse
{ {
[super prepareForReuse]; [super prepareForReuse];
self.mediaView.alpha = 1.0;
self.expirationTimerViewWidthConstraint.constant = 0.0f; self.expirationTimerViewWidthConstraint.constant = 0.0f;
} }

View File

@ -2,12 +2,10 @@
#import "TSAttachmentStream.h" #import "TSAttachmentStream.h"
#import "TSContentAdapters.h" #import "TSContentAdapters.h"
#import "TSInteraction.h" #import "TSOutgoingMessage.h"
#import <MobileCoreServices/MobileCoreServices.h> #import <MobileCoreServices/MobileCoreServices.h>
#import <XCTest/XCTest.h> #import <XCTest/XCTest.h>
static NSString * const kTestingInteractionId = @"some-fake-testing-id";
@interface TSMessageAdapter (Testing) @interface TSMessageAdapter (Testing)
// expose some private setters for ease of testing setup // expose some private setters for ease of testing setup
@ -19,7 +17,7 @@ static NSString * const kTestingInteractionId = @"some-fake-testing-id";
@interface TSMessageAdapterTest : XCTestCase @interface TSMessageAdapterTest : XCTestCase
@property TSMessageAdapter *messageAdapter; @property TSMessageAdapter *messageAdapter;
@property TSInteraction *interaction; @property TSOutgoingMessage *message;
@property (readonly) NSData *fakeAudioData; @property (readonly) NSData *fakeAudioData;
@end @end
@ -42,11 +40,11 @@ static NSString * const kTestingInteractionId = @"some-fake-testing-id";
{ {
[super setUp]; [super setUp];
self.messageAdapter = [[TSMessageAdapter alloc] init]; self.message = [[TSOutgoingMessage alloc] initWithTimestamp:1 inThread:nil messageBody:nil];
[self.message save];
self.interaction = [[TSInteraction alloc] initWithUniqueId:kTestingInteractionId]; self.messageAdapter = [TSMessageAdapter new];
[self.interaction save]; self.messageAdapter.interaction = self.message;
self.messageAdapter.interaction = self.interaction;
} }
- (void)tearDown - (void)tearDown
@ -94,7 +92,7 @@ static NSString * const kTestingInteractionId = @"some-fake-testing-id";
- (void)testCanPerformEditingActionWithVideoMessage - (void)testCanPerformEditingActionWithVideoMessage
{ {
TSAttachmentStream *videoAttachment = [[TSAttachmentStream alloc] initWithIdentifier:@"fake-video-message" encryptionKey:nil contentType:@"video/mp4"]; TSAttachmentStream *videoAttachment = [[TSAttachmentStream alloc] initWithContentType:@"video/mp4"];
self.messageAdapter.mediaItem = [[TSVideoAttachmentAdapter alloc] initWithAttachment:videoAttachment incoming:NO]; self.messageAdapter.mediaItem = [[TSVideoAttachmentAdapter alloc] initWithAttachment:videoAttachment incoming:NO];
XCTAssertTrue([self.messageAdapter canPerformEditingAction:@selector(delete:)]); XCTAssertTrue([self.messageAdapter canPerformEditingAction:@selector(delete:)]);
@ -107,7 +105,7 @@ static NSString * const kTestingInteractionId = @"some-fake-testing-id";
- (void)testCanPerformEditingActionWithAudioMessage - (void)testCanPerformEditingActionWithAudioMessage
{ {
TSAttachmentStream *audioAttachment = [[TSAttachmentStream alloc] initWithIdentifier:@"fake-audio-message" encryptionKey:nil contentType:@"audio/mp3"]; TSAttachmentStream *audioAttachment = [[TSAttachmentStream alloc] initWithContentType:@"audio/mp3"];
self.messageAdapter.mediaItem = [[TSVideoAttachmentAdapter alloc] initWithAttachment:audioAttachment incoming:NO]; self.messageAdapter.mediaItem = [[TSVideoAttachmentAdapter alloc] initWithAttachment:audioAttachment incoming:NO];
XCTAssertTrue([self.messageAdapter canPerformEditingAction:@selector(delete:)]); XCTAssertTrue([self.messageAdapter canPerformEditingAction:@selector(delete:)]);
@ -124,53 +122,73 @@ static NSString * const kTestingInteractionId = @"some-fake-testing-id";
- (void)testPerformDeleteEditingActionWithNonMediaMessage - (void)testPerformDeleteEditingActionWithNonMediaMessage
{ {
XCTAssertNotNil([TSInteraction fetchObjectWithUniqueID:kTestingInteractionId]); XCTAssertNotNil([TSMessage fetchObjectWithUniqueID:self.message.uniqueId]);
[self.messageAdapter performEditingAction:@selector(delete:)]; [self.messageAdapter performEditingAction:@selector(delete:)];
XCTAssertNil([TSInteraction fetchObjectWithUniqueID:kTestingInteractionId]); XCTAssertNil([TSMessage fetchObjectWithUniqueID:self.message.uniqueId]);
} }
- (void)testPerformDeleteActionWithPhotoMessage - (void)testPerformDeleteActionWithPhotoMessage
{ {
XCTAssertNotNil([TSInteraction fetchObjectWithUniqueID:kTestingInteractionId]); XCTAssertNotNil([TSMessage fetchObjectWithUniqueID:self.message.uniqueId]);
self.messageAdapter.mediaItem = [[TSPhotoAdapter alloc] init]; self.messageAdapter.mediaItem = [[TSPhotoAdapter alloc] init];
[self.messageAdapter performEditingAction:@selector(delete:)]; [self.messageAdapter performEditingAction:@selector(delete:)];
XCTAssertNil([TSInteraction fetchObjectWithUniqueID:kTestingInteractionId]); XCTAssertNil([TSMessage fetchObjectWithUniqueID:self.message.uniqueId]);
// TODO assert files are deleted // TODO assert files are deleted
} }
- (void)testPerformDeleteEditingActionWithAnimatedMessage - (void)testPerformDeleteEditingActionWithAnimatedMessage
{ {
XCTAssertNotNil([TSInteraction fetchObjectWithUniqueID:kTestingInteractionId]); XCTAssertNotNil([TSMessage fetchObjectWithUniqueID:self.message.uniqueId]);
self.messageAdapter.mediaItem = [[TSAnimatedAdapter alloc] init]; self.messageAdapter.mediaItem = [[TSAnimatedAdapter alloc] init];
[self.messageAdapter performEditingAction:@selector(delete:)]; [self.messageAdapter performEditingAction:@selector(delete:)];
XCTAssertNil([TSInteraction fetchObjectWithUniqueID:kTestingInteractionId]); XCTAssertNil([TSMessage fetchObjectWithUniqueID:self.message.uniqueId]);
// TODO assert files are deleted // TODO assert files are deleted
} }
- (void)testPerformDeleteEditingActionWithVideoMessage - (void)testPerformDeleteEditingActionWithVideoMessage
{ {
XCTAssertNotNil([TSInteraction fetchObjectWithUniqueID:kTestingInteractionId]); XCTAssertNotNil([TSMessage fetchObjectWithUniqueID:self.message.uniqueId]);
NSError *error;
TSAttachmentStream *videoAttachment = [[TSAttachmentStream alloc] initWithContentType:@"video/mp4"];
[videoAttachment writeData:[NSData new] error:&error];
[videoAttachment save];
[self.message.attachmentIds addObject:videoAttachment.uniqueId];
[self.message save];
TSAttachmentStream *videoAttachment = [[TSAttachmentStream alloc] initWithIdentifier:@"fake-video-message" encryptionKey:nil contentType:@"video/mp4"];
self.messageAdapter.mediaItem = [[TSVideoAttachmentAdapter alloc] initWithAttachment:videoAttachment incoming:NO]; self.messageAdapter.mediaItem = [[TSVideoAttachmentAdapter alloc] initWithAttachment:videoAttachment incoming:NO];
// Sanity Check
XCTAssert([[NSFileManager defaultManager] fileExistsAtPath:videoAttachment.filePath]);
[self.messageAdapter performEditingAction:@selector(delete:)]; [self.messageAdapter performEditingAction:@selector(delete:)];
XCTAssertNil([TSInteraction fetchObjectWithUniqueID:kTestingInteractionId]); XCTAssertNil([TSMessage fetchObjectWithUniqueID:self.message.uniqueId]);
// TODO assert files are deleted XCTAssertFalse([[NSFileManager defaultManager] fileExistsAtPath:videoAttachment.filePath]);
} }
- (void)testPerformDeleteEditingActionWithAudioMessage - (void)testPerformDeleteEditingActionWithAudioMessage
{ {
XCTAssertNotNil([TSInteraction fetchObjectWithUniqueID:kTestingInteractionId]); XCTAssertNotNil([TSMessage fetchObjectWithUniqueID:self.message.uniqueId]);
NSError *error;
TSAttachmentStream *audioAttachment = [[TSAttachmentStream alloc] initWithContentType:@"audio/mp3"];
[audioAttachment writeData:[NSData new] error:&error];
[audioAttachment save];
[self.message.attachmentIds addObject:audioAttachment.uniqueId];
[self.message save];
TSAttachmentStream *audioAttachment = [[TSAttachmentStream alloc] initWithIdentifier:@"fake-audio-message" encryptionKey:nil contentType:@"audio/mp3"];
self.messageAdapter.mediaItem = [[TSVideoAttachmentAdapter alloc] initWithAttachment:audioAttachment incoming:NO]; self.messageAdapter.mediaItem = [[TSVideoAttachmentAdapter alloc] initWithAttachment:audioAttachment incoming:NO];
// Sanity Check
XCTAssert([[NSFileManager defaultManager] fileExistsAtPath:audioAttachment.filePath]);
[self.messageAdapter performEditingAction:@selector(delete:)]; [self.messageAdapter performEditingAction:@selector(delete:)];
XCTAssertNil([TSInteraction fetchObjectWithUniqueID:kTestingInteractionId]); XCTAssertNil([TSMessage fetchObjectWithUniqueID:self.message.uniqueId]);
// TODO assert files are deleted XCTAssertFalse([[NSFileManager defaultManager] fileExistsAtPath:audioAttachment.filePath]);
} }
// Test Copy // Test Copy
@ -201,7 +219,10 @@ static NSString * const kTestingInteractionId = @"some-fake-testing-id";
{ {
// reset the paste board for clean slate test // reset the paste board for clean slate test
UIPasteboard.generalPasteboard.items = @[]; UIPasteboard.generalPasteboard.items = @[];
TSAttachmentStream *videoAttachment = [[TSAttachmentStream alloc] initWithIdentifier:@"fake-video" data:self.fakeVideoData key:nil contentType:@"video/mp4"];
NSError *error;
TSAttachmentStream *videoAttachment = [[TSAttachmentStream alloc] initWithContentType:@"video/mp4"];
[videoAttachment writeData:self.fakeVideoData error:&error];
self.messageAdapter.mediaItem = [[TSVideoAttachmentAdapter alloc] initWithAttachment:videoAttachment incoming:YES]; self.messageAdapter.mediaItem = [[TSVideoAttachmentAdapter alloc] initWithAttachment:videoAttachment incoming:YES];
[self.messageAdapter performEditingAction:@selector(copy:)]; [self.messageAdapter performEditingAction:@selector(copy:)];
@ -215,7 +236,9 @@ static NSString * const kTestingInteractionId = @"some-fake-testing-id";
UIPasteboard.generalPasteboard.items = @[]; UIPasteboard.generalPasteboard.items = @[];
XCTAssertNil([UIPasteboard.generalPasteboard dataForPasteboardType:(NSString *)kUTTypeMP3]); XCTAssertNil([UIPasteboard.generalPasteboard dataForPasteboardType:(NSString *)kUTTypeMP3]);
TSAttachmentStream *audioAttachment = [[TSAttachmentStream alloc] initWithIdentifier:@"fake-audio-message" data:self.fakeAudioData key:nil contentType:@"audio/mp3"]; NSError *error;
TSAttachmentStream *audioAttachment = [[TSAttachmentStream alloc] initWithContentType:@"audio/mp3"];
[audioAttachment writeData:self.fakeAudioData error:&error];
self.messageAdapter.mediaItem = [[TSVideoAttachmentAdapter alloc] initWithAttachment:audioAttachment incoming:NO]; self.messageAdapter.mediaItem = [[TSVideoAttachmentAdapter alloc] initWithAttachment:audioAttachment incoming:NO];
[self.messageAdapter performEditingAction:@selector(copy:)]; [self.messageAdapter performEditingAction:@selector(copy:)];
@ -227,7 +250,9 @@ static NSString * const kTestingInteractionId = @"some-fake-testing-id";
UIPasteboard.generalPasteboard.items = @[]; UIPasteboard.generalPasteboard.items = @[];
XCTAssertNil([UIPasteboard.generalPasteboard dataForPasteboardType:(NSString *)kUTTypeMPEG4Audio]); XCTAssertNil([UIPasteboard.generalPasteboard dataForPasteboardType:(NSString *)kUTTypeMPEG4Audio]);
TSAttachmentStream *audioAttachment = [[TSAttachmentStream alloc] initWithIdentifier:@"fake-audio-message" data:self.fakeAudioData key:nil contentType:@"audio/x-m4a"]; NSError *error;
TSAttachmentStream *audioAttachment = [[TSAttachmentStream alloc] initWithContentType:@"audio/x-m4a"];
[audioAttachment writeData:self.fakeAudioData error:&error];
self.messageAdapter.mediaItem = [[TSVideoAttachmentAdapter alloc] initWithAttachment:audioAttachment incoming:NO]; self.messageAdapter.mediaItem = [[TSVideoAttachmentAdapter alloc] initWithAttachment:audioAttachment incoming:NO];
[self.messageAdapter performEditingAction:@selector(copy:)]; [self.messageAdapter performEditingAction:@selector(copy:)];
@ -239,7 +264,9 @@ static NSString * const kTestingInteractionId = @"some-fake-testing-id";
UIPasteboard.generalPasteboard.items = @[]; UIPasteboard.generalPasteboard.items = @[];
XCTAssertNil([UIPasteboard.generalPasteboard dataForPasteboardType:(NSString *)kUTTypeAudio]); XCTAssertNil([UIPasteboard.generalPasteboard dataForPasteboardType:(NSString *)kUTTypeAudio]);
TSAttachmentStream *audioAttachment = [[TSAttachmentStream alloc] initWithIdentifier:@"fake-audio-message" data:self.fakeAudioData key:nil contentType:@"audio/wav"]; NSError *error;
TSAttachmentStream *audioAttachment = [[TSAttachmentStream alloc] initWithContentType:@"audio/wav"];
[audioAttachment writeData:self.fakeAudioData error:&error];
self.messageAdapter.mediaItem = [[TSVideoAttachmentAdapter alloc] initWithAttachment:audioAttachment incoming:NO]; self.messageAdapter.mediaItem = [[TSVideoAttachmentAdapter alloc] initWithAttachment:audioAttachment incoming:NO];
[self.messageAdapter performEditingAction:@selector(copy:)]; [self.messageAdapter performEditingAction:@selector(copy:)];

View File

@ -1,5 +1,5 @@
#!/bin/bash #!/bin/bash
TARGETS="Signal/src Pods/SignalServiceKit Pods/JSQMessagesViewController" TARGETS="Signal/src ../SignalServiceKit/src Pods/JSQMessagesViewController"
TMP="$(mktemp -d)" TMP="$(mktemp -d)"
STRINGFILE="Signal/translations/en.lproj/Localizable.strings" STRINGFILE="Signal/translations/en.lproj/Localizable.strings"