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
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 'OpenSSL'
pod 'PastelogKit', '~> 1.3'

View File

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

View File

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

View File

@ -6,6 +6,7 @@
#import "Environment.h"
#import "NotificationsManager.h"
#import "OWSContactsManager.h"
#import "OWSStaleNotificationObserver.h"
#import "PreferencesUtil.h"
#import "PushManager.h"
#import "Release.h"
@ -15,9 +16,9 @@
#import "TSSocketManager.h"
#import "TextSecureKitEnv.h"
#import "VersionMigrations.h"
#import "OWSStaleNotificationObserver.h"
#import <SignalServiceKit/OWSDisappearingMessagesJob.h>
#import <SignalServiceKit/OWSIncomingMessageReadObserver.h>
#import <SignalServiceKit/OWSMessageSender.h>
static NSString *const kStoryboardName = @"Storyboard";
static NSString *const kInitialViewControllerIdentifier = @"UserInitialViewController";
@ -128,8 +129,16 @@ static NSString *const kURLHostVerifyPrefix = @"verify";
[TextSecureKitEnv sharedEnv].contactsManager = [Environment getCurrent].contactsManager;
[[TSStorageManager sharedManager] setupDatabase];
[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.staleNotificationObserver = [OWSStaleNotificationObserver new];

View File

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

View File

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

View File

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

View File

@ -305,4 +305,31 @@
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

View File

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

View File

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

View File

@ -29,8 +29,28 @@ static NSString *const kCallSegue = @"2.0_6.0_Call_Segue";
@class PhoneManager;
@class SignalsViewController;
@class TSGroupThread;
@class ContactsUpdater;
@class TSNetworkManager;
@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) id<Logging> logging;
@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 *zrtpVersionId;
@property (nonatomic, readonly) OWSContactsManager *contactsManager;
@property (nonatomic, readonly) ContactsUpdater *contactsUpdater;
@property (nonatomic, readonly) TSNetworkManager *networkManager;
@property (nonatomic, readonly) SignalsViewController *signalsViewController;
@property (nonatomic, readonly, weak) UINavigationController *signUpFlowNavigationController;
@ -53,21 +75,6 @@ static NSString *const kCallSegue = @"2.0_6.0_Call_Segue";
+ (SecureEndPoint *)getSecureEndPointToDefaultRelayServer;
+ (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;
+ (void)setCurrent:(Environment *)curEnvironment;
+ (id<Logging>)logging;

View File

@ -1,7 +1,7 @@
#import "Environment.h"
#import "Constraints.h"
#import "DH3KKeyAgreementProtocol.h"
#import "DebugLogger.h"
#import "Environment.h"
#import "FunctionalUtil.h"
#import "KeyAgreementProtocol.h"
#import "MessagesViewController.h"
@ -10,6 +10,7 @@
#import "SignalsViewController.h"
#import "TSContactThread.h"
#import "TSGroupThread.h"
#import <SignalServiceKit/ContactsUpdater.h>
#define isRegisteredUserDefaultString @"isRegistered"
@ -17,10 +18,6 @@ static Environment *environment = nil;
@implementation Environment
@synthesize testingAndLegacyOptions, errorNoter, keyAgreementProtocolsInDescendingPriority, logging,
masterServerSecureEndPoint, defaultRelayName, relayServerHostNameSuffix, certificate, serverPort, zrtpClientId,
zrtpVersionId, phoneManager, recentCallManager, contactsManager;
+ (Environment *)getCurrent {
NSAssert((environment != nil), @"Environment is not defined.");
return environment;
@ -54,20 +51,23 @@ static Environment *environment = nil;
return [SecureEndPoint secureEndPointForHost:location identifiedByCertificate:env.certificate];
}
+ (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 {
- (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
{
ows_require(errorNoter != nil);
ows_require(zrtpClientId != nil);
ows_require(zrtpVersionId != nil);
@ -82,23 +82,29 @@ static Environment *environment = nil;
return [p isKindOfClass:DH3KKeyAgreementProtocol.class];
}]);
Environment *e = [Environment new];
e->errorNoter = errorNoter;
e->logging = logging;
e->testingAndLegacyOptions = testingAndLegacyOptions;
e->serverPort = serverPort;
e->masterServerSecureEndPoint = [SecureEndPoint
self = [super init];
if (!self) {
return self;
}
_errorNoter = errorNoter;
_logging = logging;
_testingAndLegacyOptions = testingAndLegacyOptions;
_serverPort = serverPort;
_masterServerSecureEndPoint = [SecureEndPoint
secureEndPointForHost:[HostNameEndPoint hostNameEndPointWithHostName:masterServerHostName andPort:serverPort]
identifiedByCertificate:certificate];
e->defaultRelayName = defaultRelayName;
e->certificate = certificate;
e->relayServerHostNameSuffix = relayServerHostNameSuffix;
e->keyAgreementProtocolsInDescendingPriority = keyAgreementProtocolsInDescendingPriority;
e->phoneManager = phoneManager;
e->recentCallManager = recentCallManager;
e->zrtpClientId = zrtpClientId;
e->zrtpVersionId = zrtpVersionId;
e->contactsManager = contactsManager;
_defaultRelayName = defaultRelayName;
_certificate = certificate;
_relayServerHostNameSuffix = relayServerHostNameSuffix;
_keyAgreementProtocolsInDescendingPriority = keyAgreementProtocolsInDescendingPriority;
_phoneManager = phoneManager;
_recentCallManager = recentCallManager;
_zrtpClientId = zrtpClientId;
_zrtpVersionId = zrtpVersionId;
_contactsManager = contactsManager;
_networkManager = networkManager;
if (recentCallManager != nil) {
// 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];
}
return e;
return self;
}
+ (PhoneManager *)phoneManager {
return Environment.getCurrent.phoneManager;
}
+ (id<Logging>)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.

View File

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

View File

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

View File

@ -38,8 +38,6 @@
#import "TSIncomingMessage.h"
#import "TSInfoMessage.h"
#import "TSInvalidIdentityKeyErrorMessage.h"
#import "TSMessagesManager+attachments.h"
#import "TSMessagesManager+sendMessages.h"
#import "UIFont+OWS.h"
#import "UIUtil.h"
#import <AddressBookUI/AddressBookUI.h>
@ -53,11 +51,16 @@
#import <JSQSystemSoundPlayer.h>
#import <MobileCoreServices/UTCoreTypes.h>
#import <SignalServiceKit/MimeTypeUtil.h>
#import <SignalServiceKit/OWSAttachmentsProcessor.h>
#import <SignalServiceKit/OWSDisappearingMessagesConfiguration.h>
#import <SignalServiceKit/OWSFingerprint.h>
#import <SignalServiceKit/OWSFingerprintBuilder.h>
#import <SignalServiceKit/OWSMessageSender.h>
#import <SignalServiceKit/SignalRecipient.h>
#import <SignalServiceKit/TSAccountManager.h>
#import <SignalServiceKit/TSInvalidIdentityKeySendingErrorMessage.h>
#import <SignalServiceKit/TSMessagesManager.h>
#import <SignalServiceKit/TSNetworkManager.h>
#import <YapDatabase/YapDatabaseView.h>
@import Photos;
@ -116,7 +119,11 @@ typedef enum : NSUInteger {
@property (nonatomic, readonly) TSStorageManager *storageManager;
@property (nonatomic, readonly) OWSContactsManager *contactsManager;
@property (nonatomic, readonly) ContactsUpdater *contactsUpdater;
@property (nonatomic, readonly) OWSDisappearingMessagesJob *disappearingMessagesJob;
@property (nonatomic, readonly) TSMessagesManager *messagesManager;
@property (nonatomic, readonly) TSNetworkManager *networkManager;
@property (nonatomic, readonly) OWSMessageSender *messageSender;
@property NSCache *messageAdapterCache;
@ -136,9 +143,16 @@ typedef enum : NSUInteger {
return self;
}
_contactsManager = [[Environment getCurrent] contactsManager];
_contactsManager = [Environment getCurrent].contactsManager;
_contactsUpdater = [Environment getCurrent].contactsUpdater;
_storageManager = [TSStorageManager sharedManager];
_disappearingMessagesJob = [[OWSDisappearingMessagesJob alloc] initWithStorageManager:_storageManager];
_messagesManager = [TSMessagesManager sharedManager];
_networkManager = [TSNetworkManager sharedManager];
_messageSender = [[OWSMessageSender alloc] initWithNetworkManager:_networkManager
storageManager:_storageManager
contactsManager:_contactsManager
contactsUpdater:_contactsUpdater];
return self;
}
@ -150,9 +164,16 @@ typedef enum : NSUInteger {
return self;
}
_contactsManager = [[Environment getCurrent] contactsManager];
_contactsManager = [Environment getCurrent].contactsManager;
_contactsUpdater = [Environment getCurrent].contactsUpdater;
_storageManager = [TSStorageManager sharedManager];
_disappearingMessagesJob = [[OWSDisappearingMessagesJob alloc] initWithStorageManager:_storageManager];
_messagesManager = [TSMessagesManager sharedManager];
_networkManager = [TSNetworkManager sharedManager];
_messageSender = [[OWSMessageSender alloc] initWithNetworkManager:_networkManager
storageManager:_storageManager
contactsManager:_contactsManager
contactsUpdater:_contactsUpdater];
return self;
}
@ -397,12 +418,12 @@ typedef enum : NSUInteger {
- (void)updateBackButtonAsync {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSUInteger count = [[TSMessagesManager sharedManager] unreadMessagesCountExcept:self.thread];
dispatch_async(dispatch_get_main_queue(), ^{
if (self) {
[self setUnreadCount:count];
}
});
NSUInteger count = [self.messagesManager unreadMessagesCountExcept:self.thread];
dispatch_async(dispatch_get_main_queue(), ^{
if (self) {
[self setUnreadCount:count];
}
});
});
}
@ -650,13 +671,12 @@ typedef enum : NSUInteger {
messageBody:text];
}
[[TSMessagesManager sharedManager] sendMessage:message
inThread:self.thread
[self.messageSender sendMessage:message
success:^{
DDLogInfo(@"%@ Successfully sent message.", self.tag);
}
failure:^{
DDLogWarn(@"%@ Failed to deliver message.", self.tag);
failure:^(NSError *error) {
DDLogWarn(@"%@ Failed to deliver message with error: %@", self.tag, error);
}];
[self finishSendingMessage];
}
@ -797,7 +817,13 @@ typedef enum : NSUInteger {
[self fixupiOS10EmojiBugForTextView:cell.textView];
// 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.linkTextAttributes = @{
NSForegroundColorAttributeName : cell.textView.textColor,
@ -937,9 +963,9 @@ typedef enum : NSUInteger {
if (indexPath.row == 0) {
showDate = YES;
} 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]];
NSTimeInterval timeDifference = [currentMessage.date timeIntervalSinceDate:previousMessage.date];
@ -953,7 +979,7 @@ typedef enum : NSUInteger {
- (NSAttributedString *)collectionView:(JSQMessagesCollectionView *)collectionView
attributedTextForCellTopLabelAtIndexPath:(NSIndexPath *)indexPath {
if ([self showDateAtIndexPath:indexPath]) {
TSMessageAdapter *currentMessage = [self messageAtIndexPath:indexPath];
id<OWSMessageData> currentMessage = [self messageAtIndexPath:indexPath];
return [[JSQMessagesTimestampFormatter sharedFormatter] attributedTimestampForDate:currentMessage.date];
}
@ -963,7 +989,7 @@ typedef enum : NSUInteger {
- (BOOL)shouldShowMessageStatusAtIndexPath:(NSIndexPath *)indexPath
{
TSMessageAdapter *currentMessage = [self messageAtIndexPath:indexPath];
id<OWSMessageData> currentMessage = [self messageAtIndexPath:indexPath];
if (currentMessage.isExpiringMessage) {
return YES;
@ -972,13 +998,14 @@ typedef enum : NSUInteger {
return !![self collectionView:self.collectionView attributedTextForCellBottomLabelAtIndexPath:indexPath];
}
- (TSMessageAdapter *)nextOutgoingMessage:(NSIndexPath *)indexPath {
TSMessageAdapter *nextMessage =
- (id<OWSMessageData>)nextOutgoingMessage:(NSIndexPath *)indexPath
{
id<OWSMessageData> nextMessage =
[self messageAtIndexPath:[NSIndexPath indexPathForRow:indexPath.row + 1 inSection:indexPath.section]];
int i = 1;
while (indexPath.item + i < [self.collectionView numberOfItemsInSection:indexPath.section] - 1 &&
![self isMessageOutgoingAndDelivered:nextMessage]) {
while (indexPath.item + i < [self.collectionView numberOfItemsInSection:indexPath.section] - 1
&& !nextMessage.isOutgoingAndDelivered) {
i++;
nextMessage =
[self messageAtIndexPath:[NSIndexPath indexPathForRow:indexPath.row + i inSection:indexPath.section]];
@ -987,18 +1014,6 @@ typedef enum : NSUInteger {
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
attributedTextForCellBottomLabelAtIndexPath:(NSIndexPath *)indexPath
{
@ -1011,11 +1026,8 @@ typedef enum : NSUInteger {
if (message.messageType == TSOutgoingMessageAdapter) {
TSOutgoingMessage *outgoingMessage = (TSOutgoingMessage *)message.interaction;
if (outgoingMessage.messageState == TSOutgoingMessageStateUnsent) {
NSAttributedString *failedString =
[[NSAttributedString alloc] initWithString:NSLocalizedString(@"FAILED_SENDING_TEXT", nil)];
return failedString;
} else if ([self isMessageOutgoingAndDelivered:message]) {
return [[NSAttributedString alloc] initWithString:NSLocalizedString(@"FAILED_SENDING_TEXT", nil)];
} else if (message.isOutgoingAndDelivered) {
NSAttributedString *deliveredString =
[[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.
TSMessageAdapter *nextMessage = [self nextOutgoingMessage:indexPath];
if (![self isMessageOutgoingAndDelivered:nextMessage]) {
if (!nextMessage.isOutgoingAndDelivered) {
[self updateLastDeliveredMessage:message];
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]]) {
TSIncomingMessage *incomingMessage = (TSIncomingMessage *)message.interaction;
@ -1101,12 +1116,17 @@ typedef enum : NSUInteger {
switch (messageItem.messageType) {
case TSOutgoingMessageAdapter: {
TSOutgoingMessage *outgoingMessage = (TSOutgoingMessage *)messageItem;
TSOutgoingMessage *outgoingMessage = (TSOutgoingMessage *)interaction;
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: {
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.
// see: https://github.com/WhisperSystems/Signal-iOS/issues/1254
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 {
[self dismissKeyBoard];
[DJWActionSheet showInView:self.parentViewController.view
withTitle:nil
withTitle:message.mostRecentFailureText
cancelButtonTitle:NSLocalizedString(@"TXT_CANCEL_TITLE", @"")
destructiveButtonTitle:NSLocalizedString(@"TXT_DELETE_TITLE", @"")
otherButtonTitles:@[ NSLocalizedString(@"SEND_AGAIN_BUTTON", @"") ]
tapBlock:^(DJWActionSheet *actionSheet, NSInteger tappedButtonIndex) {
if (tappedButtonIndex == actionSheet.cancelButtonIndex) {
DDLogDebug(@"User Cancelled");
} else if (tappedButtonIndex == actionSheet.destructiveButtonIndex) {
[self.editingDatabaseConnection
readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[message removeWithTransaction:transaction];
}];
} else {
[[TSMessagesManager sharedManager] sendMessage:message
inThread:self.thread
success:nil
failure:nil];
[self finishSendingMessage];
}
if (tappedButtonIndex == actionSheet.cancelButtonIndex) {
DDLogDebug(@"%@ User cancelled unsent dialog", self.tag);
} else if (tappedButtonIndex == actionSheet.destructiveButtonIndex) {
DDLogInfo(@"%@ User chose to delete unsent message.", self.tag);
[message remove];
} else {
[self.messageSender sendMessage:message
success:^{
DDLogInfo(@"%@ Successfully resent failed message.", self.tag);
}
failure:^(NSError *_Nonnull error) {
DDLogWarn(@"%@ Failed to send message with error: %@", self.tag, error);
}];
}
}];
}
@ -1459,7 +1489,21 @@ typedef enum : NSUInteger {
break;
case 1:
DDLogInfo(@"%@ Remote Key Changed actions: Accepted new identity key", self.tag);
[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;
default:
DDLogInfo(@"%@ Remote Key Changed actions: Unhandled button pressed: %d",
@ -1564,7 +1608,7 @@ typedef enum : NSUInteger {
// Video picked from library or captured with camera
NSURL *videoURL = info[UIImagePickerControllerMediaURL];
[self sendQualityAdjustedAttachment:videoURL];
[self sendQualityAdjustedAttachmentForVideo:videoURL];
} else if (picker.sourceType == UIImagePickerControllerSourceTypeCamera) {
// Static Image captured from camera
@ -1649,13 +1693,16 @@ typedef enum : NSUInteger {
DDLogVerbose(@"Sending attachment. Size in bytes: %lu, contentType: %@",
(unsigned long)attachmentData.length,
attachmentType);
[[TSMessagesManager sharedManager] sendAttachment:attachmentData
contentType:attachmentType
inMessage:message
thread:self.thread
success:nil
failure:nil];
[self.messageSender sendAttachmentData:attachmentData
contentType:attachmentType
inMessage:message
success:^{
DDLogDebug(@"%@ Successfully sent message attachment.", self.tag);
}
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];
}
- (void)sendQualityAdjustedAttachment:(NSURL *)movieURL {
- (void)sendQualityAdjustedAttachmentForVideo:(NSURL *)movieURL {
AVAsset *video = [AVAsset assetWithURL:movieURL];
AVAssetExportSession *exportSession =
[AVAssetExportSession exportSessionWithAsset:video presetName:AVAssetExportPresetMediumQuality];
@ -1841,7 +1888,6 @@ typedef enum : NSUInteger {
[self.messageAdapterCache removeObjectForKey:collectionKey.key];
}
[self.collectionView reloadItemsAtIndexPaths:@[ rowChange.indexPath ]];
scrollToBottom = YES;
break;
}
}
@ -1854,7 +1900,9 @@ typedef enum : NSUInteger {
[self.collectionView reloadData];
}
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
canPerformAction:(SEL)action
forItemAtIndexPath:(NSIndexPath *)indexPath
withSender:(id)sender {
TSMessageAdapter *messageAdapter = [self messageAtIndexPath:indexPath];
// HACK make sure method exists before calling since messageAtIndexPath doesn't
// always return TSMessageAdapters - it can also return JSQCall!
if ([messageAdapter respondsToSelector:@selector(canPerformEditingAction:)]) {
return [messageAdapter canPerformEditingAction:action];
}
else {
return NO;
}
withSender:(id)sender
{
id<OWSMessageData> messageData = [self messageAtIndexPath:indexPath];
return [messageData canPerformEditingAction:action];
}
- (void)collectionView:(UICollectionView *)collectionView
performAction:(SEL)action
forItemAtIndexPath:(NSIndexPath *)indexPath
withSender:(id)sender {
[[self messageAtIndexPath:indexPath] performEditingAction:action];
withSender:(id)sender
{
id<OWSMessageData> messageData = [self messageAtIndexPath:indexPath];
[messageData performEditingAction:action];
}
- (void)updateGroupModelTo:(TSGroupModel *)newGroupModel
@ -2037,15 +2080,24 @@ typedef enum : NSUInteger {
message.customMessage = updateGroupInfo;
}];
if (newGroupModel.groupImage != nil) {
[[TSMessagesManager sharedManager] sendAttachment:UIImagePNGRepresentation(newGroupModel.groupImage)
contentType:OWSMimeTypeImagePng
inMessage:message
thread:groupThread
success:nil
failure:nil];
if (newGroupModel.groupImage) {
[self.messageSender sendAttachmentData:UIImagePNGRepresentation(newGroupModel.groupImage)
contentType:OWSMimeTypeImagePng
inMessage:message
success:^{
DDLogDebug(@"%@ Successfully sent group update with avatar", self.tag);
}
failure:^(NSError *_Nonnull error) {
DDLogError(@"%@ Failed to send group avatar update with error: %@", self.tag, error);
}];
} 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;

View File

@ -20,20 +20,50 @@
#import <MobileCoreServices/UTCoreTypes.h>
#import <SignalServiceKit/MimeTypeUtil.h>
#import <SignalServiceKit/NSDate+millisecondTimeStamp.h>
#import <SignalServiceKit/OWSMessageSender.h>
#import <SignalServiceKit/TSAccountManager.h>
#import <SignalServiceKit/TSMessagesManager+attachments.h>
#import <SignalServiceKit/TSMessagesManager+sendMessages.h>
static NSString *const kUnwindToMessagesViewSegue = @"UnwindToMessagesViewSegue";
@interface NewGroupViewController () {
NSArray *contacts;
}
@property TSGroupThread *thread;
@property (nonatomic, readonly) OWSMessageSender *messageSender;
@end
@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 {
_thread = gThread;
}
@ -124,76 +154,53 @@ static NSString *const kUnwindToMessagesViewSegue = @"UnwindToMessagesViewSegue"
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 alertControllerWithTitle:NSLocalizedString(@"GROUP_CREATING", nil)
message:nil
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];
}];
});
}
failure:^{
dispatch_async(dispatch_get_main_queue(), ^{
[self
dismissViewControllerAnimated:YES
completion:^{
[TSStorageManager.sharedManager.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction){
[self.thread removeWithTransaction:transaction];
}];
[self presentViewController:alertController
animated:YES
completion:^{
TSOutgoingMessage *message =
[[TSOutgoingMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
inThread:self.thread
messageBody:@""
attachmentIds:[NSMutableArray new]];
SignalAlertView(NSLocalizedString(@"GROUP_CREATING_FAILED", nil),
NSLocalizedString(@"NETWORK_ERROR_RECOVERY", nil));
}];
});
}];
} else {
[[TSMessagesManager sharedManager] sendMessage:message
inThread:self.thread
success:^{
dispatch_async(dispatch_get_main_queue(), ^{
[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));
}];
});
}];
}
}];
message.groupMetaMessage = TSGroupMessageNew;
message.customMessage = NSLocalizedString(@"GROUP_CREATED", nil);
if (model.groupImage) {
[self.messageSender sendAttachmentData:UIImagePNGRepresentation(model.groupImage)
contentType:OWSMimeTypeImagePng
inMessage:message
success:popToThread
failure:removeThreadWithError];
} else {
[self.messageSender sendMessage:message success:popToThread failure:removeThreadWithError];
}
}];
}

View File

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

View File

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

View File

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

View File

@ -2,12 +2,10 @@
#import "TSAttachmentStream.h"
#import "TSContentAdapters.h"
#import "TSInteraction.h"
#import "TSOutgoingMessage.h"
#import <MobileCoreServices/MobileCoreServices.h>
#import <XCTest/XCTest.h>
static NSString * const kTestingInteractionId = @"some-fake-testing-id";
@interface TSMessageAdapter (Testing)
// expose some private setters for ease of testing setup
@ -19,7 +17,7 @@ static NSString * const kTestingInteractionId = @"some-fake-testing-id";
@interface TSMessageAdapterTest : XCTestCase
@property TSMessageAdapter *messageAdapter;
@property TSInteraction *interaction;
@property TSOutgoingMessage *message;
@property (readonly) NSData *fakeAudioData;
@end
@ -42,11 +40,11 @@ static NSString * const kTestingInteractionId = @"some-fake-testing-id";
{
[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.interaction save];
self.messageAdapter.interaction = self.interaction;
self.messageAdapter = [TSMessageAdapter new];
self.messageAdapter.interaction = self.message;
}
- (void)tearDown
@ -94,7 +92,7 @@ static NSString * const kTestingInteractionId = @"some-fake-testing-id";
- (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];
XCTAssertTrue([self.messageAdapter canPerformEditingAction:@selector(delete:)]);
@ -107,7 +105,7 @@ static NSString * const kTestingInteractionId = @"some-fake-testing-id";
- (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];
XCTAssertTrue([self.messageAdapter canPerformEditingAction:@selector(delete:)]);
@ -124,53 +122,73 @@ static NSString * const kTestingInteractionId = @"some-fake-testing-id";
- (void)testPerformDeleteEditingActionWithNonMediaMessage
{
XCTAssertNotNil([TSInteraction fetchObjectWithUniqueID:kTestingInteractionId]);
XCTAssertNotNil([TSMessage fetchObjectWithUniqueID:self.message.uniqueId]);
[self.messageAdapter performEditingAction:@selector(delete:)];
XCTAssertNil([TSInteraction fetchObjectWithUniqueID:kTestingInteractionId]);
XCTAssertNil([TSMessage fetchObjectWithUniqueID:self.message.uniqueId]);
}
- (void)testPerformDeleteActionWithPhotoMessage
{
XCTAssertNotNil([TSInteraction fetchObjectWithUniqueID:kTestingInteractionId]);
XCTAssertNotNil([TSMessage fetchObjectWithUniqueID:self.message.uniqueId]);
self.messageAdapter.mediaItem = [[TSPhotoAdapter alloc] init];
[self.messageAdapter performEditingAction:@selector(delete:)];
XCTAssertNil([TSInteraction fetchObjectWithUniqueID:kTestingInteractionId]);
XCTAssertNil([TSMessage fetchObjectWithUniqueID:self.message.uniqueId]);
// TODO assert files are deleted
}
- (void)testPerformDeleteEditingActionWithAnimatedMessage
{
XCTAssertNotNil([TSInteraction fetchObjectWithUniqueID:kTestingInteractionId]);
XCTAssertNotNil([TSMessage fetchObjectWithUniqueID:self.message.uniqueId]);
self.messageAdapter.mediaItem = [[TSAnimatedAdapter alloc] init];
[self.messageAdapter performEditingAction:@selector(delete:)];
XCTAssertNil([TSInteraction fetchObjectWithUniqueID:kTestingInteractionId]);
XCTAssertNil([TSMessage fetchObjectWithUniqueID:self.message.uniqueId]);
// TODO assert files are deleted
}
- (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];
// Sanity Check
XCTAssert([[NSFileManager defaultManager] fileExistsAtPath:videoAttachment.filePath]);
[self.messageAdapter performEditingAction:@selector(delete:)];
XCTAssertNil([TSInteraction fetchObjectWithUniqueID:kTestingInteractionId]);
// TODO assert files are deleted
XCTAssertNil([TSMessage fetchObjectWithUniqueID:self.message.uniqueId]);
XCTAssertFalse([[NSFileManager defaultManager] fileExistsAtPath:videoAttachment.filePath]);
}
- (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];
// Sanity Check
XCTAssert([[NSFileManager defaultManager] fileExistsAtPath:audioAttachment.filePath]);
[self.messageAdapter performEditingAction:@selector(delete:)];
XCTAssertNil([TSInteraction fetchObjectWithUniqueID:kTestingInteractionId]);
// TODO assert files are deleted
XCTAssertNil([TSMessage fetchObjectWithUniqueID:self.message.uniqueId]);
XCTAssertFalse([[NSFileManager defaultManager] fileExistsAtPath:audioAttachment.filePath]);
}
// Test Copy
@ -201,7 +219,10 @@ static NSString * const kTestingInteractionId = @"some-fake-testing-id";
{
// reset the paste board for clean slate test
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 performEditingAction:@selector(copy:)];
@ -215,7 +236,9 @@ static NSString * const kTestingInteractionId = @"some-fake-testing-id";
UIPasteboard.generalPasteboard.items = @[];
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 performEditingAction:@selector(copy:)];
@ -227,7 +250,9 @@ static NSString * const kTestingInteractionId = @"some-fake-testing-id";
UIPasteboard.generalPasteboard.items = @[];
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 performEditingAction:@selector(copy:)];
@ -239,7 +264,9 @@ static NSString * const kTestingInteractionId = @"some-fake-testing-id";
UIPasteboard.generalPasteboard.items = @[];
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 performEditingAction:@selector(copy:)];

View File

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