From 87f4b0ac2edb33069a9947e01116f3a7390ec839 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Wed, 25 Jul 2018 14:02:25 -0400 Subject: [PATCH 01/15] Clean up data. --- SignalServiceKit/src/Contacts/Contact.m | 8 +- SignalServiceKit/src/Contacts/TSThread.m | 16 +- .../Attachments/OWSAttachmentsProcessor.m | 10 +- .../src/Network/API/OWSUploadOperation.m | 10 +- .../src/Network/WebSockets/TSSocketManager.m | 5 +- SignalServiceKit/src/Util/Cryptography.h | 33 ++- SignalServiceKit/src/Util/Cryptography.m | 202 +++++++++++------- 7 files changed, 175 insertions(+), 109 deletions(-) diff --git a/SignalServiceKit/src/Contacts/Contact.m b/SignalServiceKit/src/Contacts/Contact.m index c088eda73..d57b5b9b3 100644 --- a/SignalServiceKit/src/Contacts/Contact.m +++ b/SignalServiceKit/src/Contacts/Contact.m @@ -107,8 +107,12 @@ NS_ASSUME_NONNULL_BEGIN NSData *_Nullable avatarData = [Contact avatarDataForCNContact:cnContact]; if (avatarData) { NSUInteger hashValue = 0; - NSData *hashData = [Cryptography computeSHA256Digest:avatarData truncatedToBytes:sizeof(hashValue)]; - [hashData getBytes:&hashValue length:sizeof(hashValue)]; + NSData *_Nullable hashData = [Cryptography computeSHA256Digest:avatarData truncatedToBytes:sizeof(hashValue)]; + if (hashData) { + [hashData getBytes:&hashValue length:sizeof(hashValue)]; + } else { + OWSProdLogAndFail(@"%@ could not compute hash for avatar.", self.logTag); + } _imageHash = hashValue; } else { _imageHash = 0; diff --git a/SignalServiceKit/src/Contacts/TSThread.m b/SignalServiceKit/src/Contacts/TSThread.m index 187eb2206..a6108a0fd 100644 --- a/SignalServiceKit/src/Contacts/TSThread.m +++ b/SignalServiceKit/src/Contacts/TSThread.m @@ -455,12 +455,16 @@ NS_ASSUME_NONNULL_BEGIN { NSData *contactData = [colorSeed dataUsingEncoding:NSUTF8StringEncoding]; - NSUInteger hashingLength = sizeof(unsigned long long); - unsigned long long choose; - NSData *hashData = [Cryptography computeSHA256Digest:contactData truncatedToBytes:hashingLength]; - [hashData getBytes:&choose length:hashingLength]; - - NSUInteger index = (choose % [self.conversationColorNames count]); + unsigned long long hash = 0; + NSUInteger hashingLength = sizeof(hash); + NSData *_Nullable hashData = [Cryptography computeSHA256Digest:contactData truncatedToBytes:hashingLength]; + if (hashData) { + [hashData getBytes:&hash length:hashingLength]; + } else { + OWSProdLogAndFail(@"%@ could not compute hash for color seed.", self.logTag); + } + + NSUInteger index = (hash % [self.conversationColorNames count]); return [self.conversationColorNames objectAtIndex:index]; } diff --git a/SignalServiceKit/src/Messages/Attachments/OWSAttachmentsProcessor.m b/SignalServiceKit/src/Messages/Attachments/OWSAttachmentsProcessor.m index 0e1132786..36a45191b 100644 --- a/SignalServiceKit/src/Messages/Attachments/OWSAttachmentsProcessor.m +++ b/SignalServiceKit/src/Messages/Attachments/OWSAttachmentsProcessor.m @@ -222,11 +222,11 @@ static const CGFloat kAttachmentDownloadProgressTheta = 0.001f; failure:(void (^)(NSError *error))failureHandler { NSError *decryptError; - NSData *plaintext = [Cryptography decryptAttachment:cipherText - withKey:attachment.encryptionKey - digest:attachment.digest - unpaddedSize:attachment.byteCount - error:&decryptError]; + NSData *_Nullable plaintext = [Cryptography decryptAttachment:cipherText + withKey:attachment.encryptionKey + digest:attachment.digest + unpaddedSize:attachment.byteCount + error:&decryptError]; if (decryptError) { DDLogError(@"%@ failed to decrypt with error: %@", self.logTag, decryptError); diff --git a/SignalServiceKit/src/Network/API/OWSUploadOperation.m b/SignalServiceKit/src/Network/API/OWSUploadOperation.m index 1789f2aeb..efaaff82e 100644 --- a/SignalServiceKit/src/Network/API/OWSUploadOperation.m +++ b/SignalServiceKit/src/Network/API/OWSUploadOperation.m @@ -120,9 +120,15 @@ static const CGFloat kAttachmentUploadProgressTheta = 0.001f; NSData *encryptionKey; NSData *digest; - NSData *encryptedAttachmentData = + NSData *_Nullable encryptedAttachmentData = [Cryptography encryptAttachmentData:attachmentData outKey:&encryptionKey outDigest:&digest]; - + if (!encryptedAttachmentData) { + OWSProdLogAndFail(@"%@ could not encrypt attachment data.", self.logTag); + error = OWSErrorMakeFailedToSendOutgoingMessageError(); + error.isRetryable = YES; + [self reportError:error]; + return; + } attachmentStream.encryptionKey = encryptionKey; attachmentStream.digest = digest; diff --git a/SignalServiceKit/src/Network/WebSockets/TSSocketManager.m b/SignalServiceKit/src/Network/WebSockets/TSSocketManager.m index a187f8275..65708f149 100644 --- a/SignalServiceKit/src/Network/WebSockets/TSSocketManager.m +++ b/SignalServiceKit/src/Network/WebSockets/TSSocketManager.m @@ -738,8 +738,9 @@ NSString *const kNSNotification_SocketManagerStateDidChange = @"kNSNotification_ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ @try { - NSData *decryptedPayload = [Cryptography decryptAppleMessagePayload:message.body - withSignalingKey:TSAccountManager.signalingKey]; + NSData *_Nullable decryptedPayload = + [Cryptography decryptAppleMessagePayload:message.body + withSignalingKey:TSAccountManager.signalingKey]; if (!decryptedPayload) { DDLogWarn(@"%@ Failed to decrypt incoming payload or bad HMAC", self.logTag); diff --git a/SignalServiceKit/src/Util/Cryptography.h b/SignalServiceKit/src/Util/Cryptography.h index b4a5deced..3f0023b71 100755 --- a/SignalServiceKit/src/Util/Cryptography.h +++ b/SignalServiceKit/src/Util/Cryptography.h @@ -39,35 +39,30 @@ typedef NS_ENUM(NSInteger, TSMACType) { + (uint32_t)randomUInt32; + (uint64_t)randomUInt64; -#pragma mark SHA and HMAC methods +#pragma mark - SHA and HMAC methods // Full length SHA256 digest for `data` -+ (NSData *)computeSHA256Digest:(NSData *)data; ++ (nullable NSData *)computeSHA256Digest:(NSData *)data; // Truncated SHA256 digest for `data` -+ (NSData *)computeSHA256Digest:(NSData *)data truncatedToBytes:(NSUInteger)truncatedBytes; ++ (nullable NSData *)computeSHA256Digest:(NSData *)data truncatedToBytes:(NSUInteger)truncatedBytes; -+ (NSString *)truncatedSHA1Base64EncodedWithoutPadding:(NSString *)string; -+ (NSString *)computeSHA1DigestForString:(NSString *)input; ++ (nullable NSString *)truncatedSHA1Base64EncodedWithoutPadding:(NSString *)string; -+ (NSData *)computeSHA256HMAC:(NSData *)dataToHMAC withHMACKey:(NSData *)HMACKey; -+ (NSData *)computeSHA1HMAC:(NSData *)dataToHMAC withHMACKey:(NSData *)HMACKey; -+ (NSData *)truncatedSHA1HMAC:(NSData *)dataToHMAC withHMACKey:(NSData *)HMACKey truncation:(NSUInteger)bytes; - -+ (NSData *)decryptAppleMessagePayload:(NSData *)payload withSignalingKey:(NSString *)signalingKeyString; ++ (nullable NSData *)decryptAppleMessagePayload:(NSData *)payload withSignalingKey:(NSString *)signalingKeyString; #pragma mark encrypt and decrypt attachment data // Though digest can and will be nil for legacy clients, we now reject attachments lacking a digest. -+ (NSData *)decryptAttachment:(NSData *)dataToDecrypt - withKey:(NSData *)key - digest:(nullable NSData *)digest - unpaddedSize:(UInt32)unpaddedSize - error:(NSError **)error; - -+ (NSData *)encryptAttachmentData:(NSData *)attachmentData - outKey:(NSData *_Nonnull *_Nullable)outKey - outDigest:(NSData *_Nonnull *_Nullable)outDigest; ++ (nullable NSData *)decryptAttachment:(NSData *)dataToDecrypt + withKey:(NSData *)key + digest:(nullable NSData *)digest + unpaddedSize:(UInt32)unpaddedSize + error:(NSError **)error; + ++ (nullable NSData *)encryptAttachmentData:(NSData *)attachmentData + outKey:(NSData *_Nonnull *_Nullable)outKey + outDigest:(NSData *_Nonnull *_Nullable)outDigest; + (nullable NSData *)encryptAESGCMWithData:(NSData *)plaintextData key:(OWSAES256Key *)key; + (nullable NSData *)decryptAESGCMWithData:(NSData *)encryptedData key:(OWSAES256Key *)key; diff --git a/SignalServiceKit/src/Util/Cryptography.m b/SignalServiceKit/src/Util/Cryptography.m index a74ac4cff..68b0d7bfe 100755 --- a/SignalServiceKit/src/Util/Cryptography.m +++ b/SignalServiceKit/src/Util/Cryptography.m @@ -18,7 +18,6 @@ NS_ASSUME_NONNULL_BEGIN - // Returned by many OpenSSL functions - indicating success const int kOpenSSLSuccess = 1; @@ -98,7 +97,7 @@ const NSUInteger kAES256_KeyByteLength = 32; @implementation Cryptography -#pragma mark random bytes methods +#pragma mark - random bytes methods + (NSData *)generateRandomBytes:(NSUInteger)numberBytes { @@ -123,93 +122,119 @@ const NSUInteger kAES256_KeyByteLength = 32; return result; } -#pragma mark SHA1 +#pragma mark - SHA1 -+ (NSString *)truncatedSHA1Base64EncodedWithoutPadding:(NSString *)string { - /* used by TSContactManager to send hashed/truncated contact list to server */ - NSMutableData *hashData = [NSMutableData dataWithLength:20]; +// Used by TSContactManager to send hashed/truncated contact list to server. ++ (nullable NSString *)truncatedSHA1Base64EncodedWithoutPadding:(NSString *)string +{ + NSData *_Nullable stringData = [string dataUsingEncoding:NSUTF8StringEncoding]; + if (!stringData) { + OWSFail(@"%@ could not convert string to utf-8.", self.logTag); + return nil; + } + if (stringData.length >= UINT32_MAX) { + OWSFail(@"%@ string data is too long.", self.logTag); + return nil; + } + uint32_t dataLength = (uint32_t)stringData.length; - CC_SHA1([string dataUsingEncoding:NSUTF8StringEncoding].bytes, - (unsigned int)[string dataUsingEncoding:NSUTF8StringEncoding].length, - hashData.mutableBytes); + NSMutableData *hashData = [NSMutableData dataWithLength:20]; + CC_SHA1(stringData.bytes, dataLength, hashData.mutableBytes); NSData *truncatedData = [hashData subdataWithRange:NSMakeRange(0, 10)]; - return [[truncatedData base64EncodedString] stringByReplacingOccurrencesOfString:@"=" withString:@""]; } -+ (NSString *)computeSHA1DigestForString:(NSString *)input { - // Here we are taking in our string hash, placing that inside of a C Char Array, then parsing it through the SHA1 - // encryption method. - const char *cstr = [input cStringUsingEncoding:NSUTF8StringEncoding]; - NSData *data = [NSData dataWithBytes:cstr length:input.length]; - uint8_t digest[CC_SHA1_DIGEST_LENGTH]; - - CC_SHA1(data.bytes, (unsigned int)data.length, digest); - - NSMutableString *output = [NSMutableString stringWithCapacity:CC_SHA1_DIGEST_LENGTH * 2]; +#pragma mark - SHA256 Digest - for (int i = 0; i < CC_SHA1_DIGEST_LENGTH; i++) { - [output appendFormat:@"%02x", digest[i]]; - } - - return output; -} - -#pragma mark SHA256 Digest -+ (NSData *)computeSHA256Digest:(NSData *)data ++ (nullable NSData *)computeSHA256Digest:(NSData *)data { - return [self computeSHA256Digest:(NSData *)data truncatedToBytes:CC_SHA256_DIGEST_LENGTH]; + return [self computeSHA256Digest:data truncatedToBytes:CC_SHA256_DIGEST_LENGTH]; } -+ (NSData *)computeSHA256Digest:(NSData *)data truncatedToBytes:(NSUInteger)truncatedBytes ++ (nullable NSData *)computeSHA256Digest:(NSData *)data truncatedToBytes:(NSUInteger)truncatedBytes { + if (data.length >= UINT32_MAX) { + OWSFail(@"%@ data is too long.", self.logTag); + return nil; + } + uint32_t dataLength = (uint32_t)data.length; + uint8_t digest[CC_SHA256_DIGEST_LENGTH]; - CC_SHA256(data.bytes, (unsigned int)data.length, digest); + CC_SHA256(data.bytes, dataLength, digest); return [[NSData dataWithBytes:digest length:CC_SHA256_DIGEST_LENGTH] subdataWithRange:NSMakeRange(0, truncatedBytes)]; } +#pragma mark - HMAC/SHA256 + ++ (nullable NSData *)computeSHA256HMAC:(NSData *)data withHMACKey:(NSData *)HMACKey +{ + if (data.length >= SIZE_MAX) { + OWSFail(@"%@ data is too long.", self.logTag); + return nil; + } + size_t dataLength = (size_t)data.length; + if (HMACKey.length >= SIZE_MAX) { + OWSFail(@"%@ HMAC key is too long.", self.logTag); + return nil; + } + size_t hmacKeyLength = (size_t)HMACKey.length; -#pragma mark HMAC/SHA256 -+ (NSData *)computeSHA256HMAC:(NSData *)dataToHMAC withHMACKey:(NSData *)HMACKey { uint8_t ourHmac[CC_SHA256_DIGEST_LENGTH] = {0}; - CCHmac(kCCHmacAlgSHA256, [HMACKey bytes], [HMACKey length], [dataToHMAC bytes], [dataToHMAC length], ourHmac); + CCHmac(kCCHmacAlgSHA256, [HMACKey bytes], hmacKeyLength, [data bytes], dataLength, ourHmac); return [NSData dataWithBytes:ourHmac length:CC_SHA256_DIGEST_LENGTH]; } -+ (NSData *)computeSHA1HMAC:(NSData *)dataToHMAC withHMACKey:(NSData *)HMACKey { ++ (nullable NSData *)computeSHA1HMAC:(NSData *)data withHMACKey:(NSData *)HMACKey +{ + if (data.length >= SIZE_MAX) { + OWSFail(@"%@ data is too long.", self.logTag); + return nil; + } + size_t dataLength = (size_t)data.length; + if (HMACKey.length >= SIZE_MAX) { + OWSFail(@"%@ HMAC key is too long.", self.logTag); + return nil; + } + size_t hmacKeyLength = (size_t)HMACKey.length; + uint8_t ourHmac[CC_SHA256_DIGEST_LENGTH] = {0}; - CCHmac(kCCHmacAlgSHA1, [HMACKey bytes], [HMACKey length], [dataToHMAC bytes], [dataToHMAC length], ourHmac); + CCHmac(kCCHmacAlgSHA1, [HMACKey bytes], hmacKeyLength, [data bytes], dataLength, ourHmac); return [NSData dataWithBytes:ourHmac length:CC_SHA256_DIGEST_LENGTH]; } - -+ (NSData *)truncatedSHA1HMAC:(NSData *)dataToHMAC withHMACKey:(NSData *)HMACKey truncation:(NSUInteger)bytes { ++ (nullable NSData *)truncatedSHA1HMAC:(NSData *)dataToHMAC withHMACKey:(NSData *)HMACKey truncation:(NSUInteger)bytes +{ return [[Cryptography computeSHA1HMAC:dataToHMAC withHMACKey:HMACKey] subdataWithRange:NSMakeRange(0, bytes)]; } -+ (NSData *)truncatedSHA256HMAC:(NSData *)dataToHMAC withHMACKey:(NSData *)HMACKey truncation:(NSUInteger)bytes { ++ (nullable NSData *)truncatedSHA256HMAC:(NSData *)dataToHMAC withHMACKey:(NSData *)HMACKey truncation:(NSUInteger)bytes +{ return [[Cryptography computeSHA256HMAC:dataToHMAC withHMACKey:HMACKey] subdataWithRange:NSMakeRange(0, bytes)]; } - -#pragma mark AES CBC Mode +#pragma mark - AES CBC Mode /** * AES256 CBC encrypt then mac. Used to decrypt both signal messages and attachment blobs * * @return decrypted data or nil if hmac invalid/decryption fails */ -+ (NSData *)decryptCBCMode:(NSData *)dataToDecrypt - key:(NSData *)key - IV:(NSData *)iv - version:(nullable NSData *)version - HMACKey:(NSData *)hmacKey - HMACType:(TSMACType)hmacType - matchingHMAC:(NSData *)hmac - digest:(nullable NSData *)digest ++ (nullable NSData *)decryptCBCMode:(NSData *)dataToDecrypt + key:(NSData *)key + IV:(NSData *)iv + version:(nullable NSData *)version + HMACKey:(NSData *)hmacKey + HMACType:(TSMACType)hmacType + matchingHMAC:(NSData *)hmac + digest:(nullable NSData *)digest { + if (dataToDecrypt.length >= (SIZE_MAX - kCCBlockSizeAES128)) { + OWSFail(@"%@ data is too long.", self.logTag); + return nil; + } + // Verify hmac of: version? || iv || encrypted data NSMutableData *dataToAuth = [NSMutableData data]; if (version != nil) { @@ -219,7 +244,7 @@ const NSUInteger kAES256_KeyByteLength = 32; [dataToAuth appendData:iv]; [dataToAuth appendData:dataToDecrypt]; - NSData *ourHmacData; + NSData *_Nullable ourHmacData; if (hmacType == TSHMACSHA1Truncated10Bytes) { ourHmacData = [Cryptography truncatedSHA1HMAC:dataToAuth withHMACKey:hmacKey truncation:10]; @@ -245,7 +270,7 @@ const NSUInteger kAES256_KeyByteLength = 32; if (digest) { DDLogDebug(@"%@ verifying their digest", self.logTag); [dataToAuth appendData:ourHmacData]; - NSData *ourDigest = [Cryptography computeSHA256Digest:dataToAuth]; + NSData *_Nullable ourDigest = [Cryptography computeSHA256Digest:dataToAuth]; if (!ourDigest || ![ourDigest ows_constantTimeIsEqualToData:digest]) { DDLogWarn(@"%@ Bad digest on decrypting payload", self.logTag); // Don't log digest in prod @@ -288,8 +313,10 @@ const NSUInteger kAES256_KeyByteLength = 32; return nil; } -#pragma mark methods which use AES CBC -+ (NSData *)decryptAppleMessagePayload:(NSData *)payload withSignalingKey:(NSString *)signalingKeyString { +#pragma mark - methods which use AES CBC + ++ (nullable NSData *)decryptAppleMessagePayload:(NSData *)payload withSignalingKey:(NSString *)signalingKeyString +{ OWSAssert(payload); OWSAssert(signalingKeyString); @@ -317,11 +344,11 @@ const NSUInteger kAES256_KeyByteLength = 32; digest:nil]; } -+ (NSData *)decryptAttachment:(NSData *)dataToDecrypt - withKey:(NSData *)key - digest:(nullable NSData *)digest - unpaddedSize:(UInt32)unpaddedSize - error:(NSError **)error ++ (nullable NSData *)decryptAttachment:(NSData *)dataToDecrypt + withKey:(NSData *)key + digest:(nullable NSData *)digest + unpaddedSize:(UInt32)unpaddedSize + error:(NSError **)error { if (digest.length <= 0) { // This *could* happen with sufficiently outdated clients. @@ -352,15 +379,20 @@ const NSUInteger kAES256_KeyByteLength = 32; NSData *hmac = [dataToDecrypt subdataWithRange:NSMakeRange([dataToDecrypt length] - HMAC256_OUTPUT_LENGTH, HMAC256_OUTPUT_LENGTH)]; - NSData *paddedPlainText = [Cryptography decryptCBCMode:encryptedAttachment - key:encryptionKey - IV:iv - version:nil - HMACKey:hmacKey - HMACType:TSHMACSHA256AttachementType - matchingHMAC:hmac - digest:digest]; - if (unpaddedSize == 0) { + NSData *_Nullable paddedPlainText = [Cryptography decryptCBCMode:encryptedAttachment + key:encryptionKey + IV:iv + version:nil + HMACKey:hmacKey + HMACType:TSHMACSHA256AttachementType + matchingHMAC:hmac + digest:digest]; + if (!paddedPlainText) { + OWSFail(@"%@ couldn't decrypt attachment.", self.logTag); + *error = OWSErrorWithCodeDescription( + OWSErrorCodeFailedToDecryptMessage, NSLocalizedString(@"ERROR_MESSAGE_INVALID_MESSAGE", @"")); + return nil; + } else if (unpaddedSize == 0) { // Work around for legacy iOS client's which weren't setting padding size. // Since we know those clients pre-date attachment padding we return the entire data. DDLogWarn(@"%@ Decrypted attachment with unspecified size.", self.logTag); @@ -399,10 +431,16 @@ const NSUInteger kAES256_KeyByteLength = 32; } } -+ (NSData *)encryptAttachmentData:(NSData *)attachmentData - outKey:(NSData *_Nonnull *_Nullable)outKey - outDigest:(NSData *_Nonnull *_Nullable)outDigest ++ (nullable NSData *)encryptAttachmentData:(NSData *)attachmentData + outKey:(NSData *_Nonnull *_Nullable)outKey + outDigest:(NSData *_Nonnull *_Nullable)outDigest { + // Due to paddedSize, we need to divide by two. + if (attachmentData.length >= SIZE_MAX / 2) { + DDLogError(@"%@ data is too long.", self.logTag); + return nil; + } + NSData *iv = [Cryptography generateRandomBytes:AES_CBC_IV_LENGTH]; NSData *encryptionKey = [Cryptography generateRandomBytes:AES_KEY_SIZE]; NSData *hmacKey = [Cryptography generateRandomBytes:HMAC256_KEY_LENGTH]; @@ -453,13 +491,22 @@ const NSUInteger kAES256_KeyByteLength = 32; [encryptedPaddedData appendData:cipherText]; // compute hmac of: iv || encrypted data - NSData *hmac = + NSData *_Nullable hmac = [Cryptography truncatedSHA256HMAC:encryptedPaddedData withHMACKey:hmacKey truncation:HMAC256_OUTPUT_LENGTH]; + if (!hmac) { + OWSFail(@"%@ could not compute SHA 256 HMAC.", self.logTag); + return nil; + } [encryptedPaddedData appendData:hmac]; // compute digest of: iv || encrypted data || hmac - *outDigest = [self computeSHA256Digest:encryptedPaddedData]; + NSData *_Nullable digest = [self computeSHA256Digest:encryptedPaddedData]; + if (!digest) { + OWSFail(@"%@ data is too long.", self.logTag); + return nil; + } + *outDigest = digest; return [encryptedPaddedData copy]; } @@ -594,6 +641,11 @@ const NSUInteger kAES256_KeyByteLength = 32; // feeding each chunk to EVP_DecryptUpdate, which can be called multiple times. // For simplicity, we currently decrypt the entire ciphertext in one shot. int decryptedBytes = 0; + if (ciphertext.length >= INT_MAX) { + OWSFail(@"%@ ciphertext too large", self.logTag); + return nil; + } + if (EVP_DecryptUpdate(ctx, plaintext.mutableBytes, &decryptedBytes, ciphertext.bytes, (int)ciphertext.length) != kOpenSSLSuccess) { OWSFail(@"%@ decryptUpdate failed", self.logTag); @@ -606,6 +658,10 @@ const NSUInteger kAES256_KeyByteLength = 32; } // Set expected tag value. Works in OpenSSL 1.0.1d and later + if (authTagFromEncrypt.length >= INT_MAX) { + OWSFail(@"%@ authTagFromEncrypt too large", self.logTag); + return nil; + } if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, (int)authTagFromEncrypt.length, (void *)authTagFromEncrypt.bytes) != kOpenSSLSuccess) { OWSFail(@"%@ Failed to set auth tag in decrypt.", self.logTag); From 0fb3ac85a7e003cacef5d020bb4ad9c32e38fe9e Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Thu, 26 Jul 2018 17:32:52 -0600 Subject: [PATCH 02/15] Sync translations --- .../translations/id.lproj/Localizable.strings | 12 ++++---- .../it_IT.lproj/Localizable.strings | 30 +++++++++---------- .../th_TH.lproj/Localizable.strings | 10 +++---- .../tr_TR.lproj/Localizable.strings | 26 ++++++++-------- 4 files changed, 39 insertions(+), 39 deletions(-) diff --git a/Signal/translations/id.lproj/Localizable.strings b/Signal/translations/id.lproj/Localizable.strings index a8e9cae6c..b589da6d7 100644 --- a/Signal/translations/id.lproj/Localizable.strings +++ b/Signal/translations/id.lproj/Localizable.strings @@ -609,10 +609,10 @@ "DATABASE_VIEW_OVERLAY_TITLE" = "Mengoptimalkan Database"; /* Format string for a relative time, expressed as a certain number of hours in the past. Embeds {{The number of hours}}. */ -"DATE_HOURS_AGO_FORMAT" = "%@ jam lalu"; +"DATE_HOURS_AGO_FORMAT" = "%@ Jam Lalu"; /* Format string for a relative time, expressed as a certain number of minutes in the past. Embeds {{The number of minutes}}. */ -"DATE_MINUTES_AGO_FORMAT" = "%@ Menit lalu"; +"DATE_MINUTES_AGO_FORMAT" = "%@ Menit Lalu"; /* The present; the current time. */ "DATE_NOW" = "Sekarang"; @@ -868,7 +868,7 @@ "ERROR_MESSAGE_INVALID_KEY_EXCEPTION" = "Kunci penerima tidak cocok"; /* No comment provided by engineer. */ -"ERROR_MESSAGE_INVALID_MESSAGE" = "Pesan diterima tidak tersinkronisasi"; +"ERROR_MESSAGE_INVALID_MESSAGE" = "Pesan yang diterima tidak sinkron."; /* No comment provided by engineer. */ "ERROR_MESSAGE_INVALID_VERSION" = "Menerima pesan yang tidak cocok dengan versi ini"; @@ -886,7 +886,7 @@ "ERROR_MESSAGE_UNKNOWN_ERROR" = "Sebuah kegagagalan tak dikenal muncul."; /* No comment provided by engineer. */ -"ERROR_MESSAGE_WRONG_TRUSTED_IDENTITY_KEY" = "Nomor aman diubah"; +"ERROR_MESSAGE_WRONG_TRUSTED_IDENTITY_KEY" = "Nomor keamanan diubah"; /* Format string for 'unregistered user' error. Embeds {{the unregistered user's name or signal id}}. */ "ERROR_UNREGISTERED_USER_FORMAT" = "Pengguna tidak terdaftar: %@"; @@ -1494,7 +1494,7 @@ "PLAY_BUTTON_ACCESSABILITY_LABEL" = "Mainkan Media"; /* Label indicating that the user is not verified. Embeds {{the user's name or phone number}}. */ -"PRIVACY_IDENTITY_IS_NOT_VERIFIED_FORMAT" = "Anda tidak menandai %@telah terverifikasi."; +"PRIVACY_IDENTITY_IS_NOT_VERIFIED_FORMAT" = "Anda belum menandai %@ telah terverifikasi."; /* Badge indicating that the user is verified. */ "PRIVACY_IDENTITY_IS_VERIFIED_BADGE" = "Terverifikasi"; @@ -2121,7 +2121,7 @@ "SHARE_EXTENSION_VIEW_TITLE" = "Bagikan ke Signal"; /* Action sheet item */ -"SHOW_SAFETY_NUMBER_ACTION" = "Tunjukkan nomor pengamanan"; +"SHOW_SAFETY_NUMBER_ACTION" = "Tunjukkan nomor keamanan"; /* notification action */ "SHOW_THREAD_BUTTON_TITLE" = "Tunjukkan pembicaraan"; diff --git a/Signal/translations/it_IT.lproj/Localizable.strings b/Signal/translations/it_IT.lproj/Localizable.strings index b1e4e7f9f..02e583fab 100644 --- a/Signal/translations/it_IT.lproj/Localizable.strings +++ b/Signal/translations/it_IT.lproj/Localizable.strings @@ -12,7 +12,7 @@ /* Label for 'send message' button in contact view. Label for button that lets you send a message to a contact. */ -"ACTION_SEND_MESSAGE" = "Manda messaggio"; +"ACTION_SEND_MESSAGE" = "Invia messaggio"; /* Label for 'share contact' button. */ "ACTION_SHARE_CONTACT" = "Condivi contatto"; @@ -168,7 +168,7 @@ "ATTACHMENT_PICKER_DOCUMENTS_PICKED_DIRECTORY_FAILED_ALERT_TITLE" = "Documento non supportato"; /* Short text label for a voice message attachment, used for thread preview and on the lock screen */ -"ATTACHMENT_TYPE_VOICE_MESSAGE" = "Messaggio Vocale"; +"ATTACHMENT_TYPE_VOICE_MESSAGE" = "Messaggio vocale"; /* action sheet button title to enable built in speaker during a call */ "AUDIO_ROUTE_BUILT_IN_SPEAKER" = "Vivavoce"; @@ -742,7 +742,7 @@ "EDIT_TXT" = "Modifica"; /* body of email sent to contacts when inviting to install Signal. Embeds {{link to install Signal}} and {{link to the Signal home page}} */ -"EMAIL_INVITE_BODY" = "Ehi,\n\nUltimamente sto usando Signal per mantenere private le conversazioni sul mio iPhone. Vorrei che anche te la installassi, così potremo essere sicuri che saremo gli unici a poter leggere i nostri messaggi e ad ascoltare le nostre chiamate.\n\nSignal è disponibile per gli iPhone e per Android. Scaricalo qui: %@\n\nSignal funziona come la tua solita app di messaggistica. Possiamo mandarci immagini, video, fare chiamate e creare chat di gruppo. Ma la ciliegina sulla torta è che nessuno potrà vedere né sentire nulla di tutto ciò, nemmeno chi sta dietro a Signal!\n\nPuoi scoprire di più su Open Whisper Systems, i creatori di Signal, qui: %@"; +"EMAIL_INVITE_BODY" = "Ehi,\n\nUltimamente sto usando Signal per mantenere private le conversazioni sul mio iPhone. Vorrei che anche tu la installassi, così potremo essere sicuri di essere gli unici a poter leggere i nostri messaggi e ad ascoltare le nostre chiamate.\n\nSignal è disponibile per iPhone e Android. Scaricalo qui: %@\n\nSignal funziona come la tua solita app di messaggistica. Possiamo mandarci immagini, video, fare chiamate e creare chat di gruppo. Ma la ciliegina sulla torta è che nessuno potrà vedere né sentire nulla di tutto ciò, nemmeno chi produce Signal!\n\nPuoi scoprire di più su Open Whisper Systems, i creatori di Signal, qui: %@"; /* subject of email sent to contacts when inviting to install Signal */ "EMAIL_INVITE_SUBJECT" = "Passiamo a Signal"; @@ -754,7 +754,7 @@ "EMPTY_ARCHIVE_FIRST_TITLE" = "Inizia la tua prima conversazione su Signal!"; /* No comment provided by engineer. */ -"EMPTY_ARCHIVE_TEXT" = "Puoi archiviare conversazioni inattive dalla lista Chat per consultarle successivamente."; +"EMPTY_ARCHIVE_TEXT" = "Puoi archiviare conversazioni inattive dalla lista chat per consultarle successivamente."; /* No comment provided by engineer. */ "EMPTY_ARCHIVE_TITLE" = "Pulisci le tue conversazioni"; @@ -781,10 +781,10 @@ "ENABLE_2FA_VIEW_CONFIRM_PIN_INSTRUCTIONS" = "Conferma il tuo PIN"; /* Error indicating that attempt to disable 'two-factor auth' failed. */ -"ENABLE_2FA_VIEW_COULD_NOT_DISABLE_2FA" = "Impossibile disattivare il Blocco Registrazione."; +"ENABLE_2FA_VIEW_COULD_NOT_DISABLE_2FA" = "Impossibile disattivare il blocco registrazione."; /* Error indicating that attempt to enable 'two-factor auth' failed. */ -"ENABLE_2FA_VIEW_COULD_NOT_ENABLE_2FA" = "Impossibile abilitare il Blocco Registrazione."; +"ENABLE_2FA_VIEW_COULD_NOT_ENABLE_2FA" = "Impossibile abilitare il blocco registrazione."; /* Label for the 'enable two-factor auth' item in the settings view */ "ENABLE_2FA_VIEW_DISABLE_2FA" = "Disabilita"; @@ -799,13 +799,13 @@ "ENABLE_2FA_VIEW_PIN_DOES_NOT_MATCH" = "Il PIN non corrisponde. "; /* Indicates that user should select a 'two factor auth pin'. */ -"ENABLE_2FA_VIEW_SELECT_PIN_INSTRUCTIONS" = "Inserisci un PIN per il Blocco Registrazione. Questo PIN ti verrà richiesto la prossima volta che verrà effettuata una registrazione con questo numero di telefono."; +"ENABLE_2FA_VIEW_SELECT_PIN_INSTRUCTIONS" = "Inserisci un PIN per il blocco registrazione. Questo PIN ti verrà richiesto la prossima volta che verrà effettuata una registrazione con questo numero di telefono."; /* Indicates that user has 'two factor auth pin' disabled. */ -"ENABLE_2FA_VIEW_STATUS_DISABLED_INSTRUCTIONS" = "Per aumentare la sicurezza, attiva il PIN di Blocco Registrazione che diventerà necessario per registrare nuovamente questo numero di telefono con Signal."; +"ENABLE_2FA_VIEW_STATUS_DISABLED_INSTRUCTIONS" = "Per aumentare la sicurezza, attiva il PIN di blocco registrazione che diventerà necessario per registrare nuovamente questo numero di telefono con Signal."; /* Indicates that user has 'two factor auth pin' enabled. */ -"ENABLE_2FA_VIEW_STATUS_ENABLED_INSTRUCTIONS" = "È attivo il Blocco Registrazione. Dovrai inserire il tuo PIN quando registrerai nuovamente il tuo numero di telefono con Signal."; +"ENABLE_2FA_VIEW_STATUS_ENABLED_INSTRUCTIONS" = "È attivo il blocco registrazione. Dovrai inserire il tuo PIN quando registrerai nuovamente il tuo numero di telefono con Signal."; /* Title for the 'enable two factor auth PIN' views. */ "ENABLE_2FA_VIEW_TITLE" = "Blocco registrazione"; @@ -931,7 +931,7 @@ "GIF_PICKER_FAILURE_ALERT_TITLE" = "Impossibile recuperare la GIF selezionata"; /* Alert message shown when user tries to search for GIFs without entering any search terms. */ -"GIF_PICKER_VIEW_MISSING_QUERY" = "Inserite la vostra la ricerca."; +"GIF_PICKER_VIEW_MISSING_QUERY" = "Inserisci la tua ricerca."; /* Title for the 'GIF picker' dialog. */ "GIF_PICKER_VIEW_TITLE" = "Ricerca GIF"; @@ -1353,7 +1353,7 @@ "NETWORK_STATUS_HEADER" = "Stato della rete"; /* No comment provided by engineer. */ -"NETWORK_STATUS_OFFLINE" = "Offline"; +"NETWORK_STATUS_OFFLINE" = "Sconnesso"; /* A label the cell that lets you add a new member to a group. */ "NEW_CONVERSATION_FIND_BY_PHONE_NUMBER" = "Cerca per numero"; @@ -2202,7 +2202,7 @@ "UNKNOWN_CONTACT_BLOCK_OFFER" = "Utente non presente nei contatti. Vuoi bloccare questo utente?"; /* Displayed if for some reason we can't determine a contacts phone number *or* name */ -"UNKNOWN_CONTACT_NAME" = "Contatto Sconosciuto"; +"UNKNOWN_CONTACT_NAME" = "Contatto sconosciuto"; /* Indicates an unknown or unrecognizable value. */ "UNKNOWN_VALUE" = "Sconosciuto"; @@ -2214,13 +2214,13 @@ "UNLINK_CONFIRMATION_ALERT_BODY" = "Dissociando questo dispositivo, non sarà più possibile inviare o ricevere messaggi."; /* Alert title for confirming device deletion */ -"UNLINK_CONFIRMATION_ALERT_TITLE" = "Dissocia \"%@\" ?"; +"UNLINK_CONFIRMATION_ALERT_TITLE" = "Dissociare \"%@\"?"; /* Alert title when unlinking device fails */ "UNLINKING_FAILED_ALERT_TITLE" = "Signal non è riuscito a dissociare il tuo dispositivo."; /* Label text in device manager for a device with no name */ -"UNNAMED_DEVICE" = "Dispositivo Senza Nome"; +"UNNAMED_DEVICE" = "Dispositivo senza nome"; /* No comment provided by engineer. */ "UNREGISTER_SIGNAL_FAIL" = "Cancellazione da Signal fallita."; @@ -2274,7 +2274,7 @@ "UPGRADE_EXPERIENCE_INTRODUCING_READ_RECEIPTS_TITLE" = "Introduzione delle ricevute di lettura"; /* Description of video calling to upgrading (existing) users */ -"UPGRADE_EXPERIENCE_VIDEO_DESCRIPTION" = "Signal ora supporta le chiamate video sicure. Inizia la chiamata come di consueto, tocca il pulsante della telecamere e fai ciao."; +"UPGRADE_EXPERIENCE_VIDEO_DESCRIPTION" = "Signal ora supporta le videochiamate sicure. Inizia la chiamata come di consueto, tocca il pulsante della fotocamera e fai ciao."; /* Header for upgrade experience */ "UPGRADE_EXPERIENCE_VIDEO_TITLE" = "Videochiamate sicure!"; diff --git a/Signal/translations/th_TH.lproj/Localizable.strings b/Signal/translations/th_TH.lproj/Localizable.strings index 3dec917cd..12c53c625 100644 --- a/Signal/translations/th_TH.lproj/Localizable.strings +++ b/Signal/translations/th_TH.lproj/Localizable.strings @@ -742,7 +742,7 @@ "EDIT_TXT" = "แก้ไข"; /* body of email sent to contacts when inviting to install Signal. Embeds {{link to install Signal}} and {{link to the Signal home page}} */ -"EMAIL_INVITE_BODY" = "สวัสดี\n\nเมื่อไม่นานนี้ ฉันได้ใช้ Signal เพื่อรักษาการสนทนาบนไอโฟนของฉันให้เป็นส่วนตัว ฉันอยากให้คุณติดตั้งเหมือนกัน เพื่อให้เรามั่นใจได้ว่ามีแค่คุณและฉันที่สามารถอ่านข้อความของเราหรือได้ยินเสียงคุยของเรา\n\nSignal ใช้งานได้บนไอโฟนและแอนดรอยด์ โหลดเลยที่นี่: %@\n\nSignal ทำงานเหมือนแอปส่งข้อความที่คุณมีอยู่ เราสามารถส่งรูปและวิดีโอ โทรออก และเริ่มแชตกลุ่ม สิ่งที่ดีที่สุดคือไม่มีใครคนอื่นเห็นสิ่งเหล่านี้ แม้แต่ผู้คนที่สร้าง Signal ก็จะไม่เห็น!\n\nคุณสามารถอ่านเพิ่มเติมเกี่ยวกับ Open Whisper Systems ผู้สร้าง Signal ได้ที่นี่: %@"; +"EMAIL_INVITE_BODY" = "สวัสดี\n\nเมื่อไม่นานนี้ ฉันได้ใช้ Signal เพื่อรักษาการสนทนาบน iPhone ของฉันให้เป็นส่วนตัว ฉันอยากให้คุณติดตั้งเหมือนกัน เพื่อให้เรามั่นใจได้ว่ามีแค่คุณและฉันที่สามารถอ่านข้อความของเราหรือได้ยินเสียงคุยของเรา\n\nSignal ใช้งานได้บน iPhone และ Android โหลดเลยที่นี่: %@\n\nSignal ทำงานเหมือนแอปส่งข้อความที่คุณมีอยู่ เราสามารถส่งรูปและวิดีโอ โทรออก และเริ่มแชตกลุ่ม สิ่งที่ดีที่สุดคือไม่มีใครคนอื่นเห็นสิ่งเหล่านี้ แม้แต่ผู้คนที่สร้าง Signal ก็จะไม่เห็น!\n\nคุณสามารถอ่านเพิ่มเติมเกี่ยวกับ Open Whisper Systems ผู้สร้าง Signal ได้ที่นี่: %@"; /* subject of email sent to contacts when inviting to install Signal */ "EMAIL_INVITE_SUBJECT" = "เปลี่ยนมาใช้ Signal กันเถอะ"; @@ -1464,7 +1464,7 @@ "PHONE_NUMBER_TYPE_HOME_FAX" = "แฟกซ์ที่บ้าน"; /* Label for 'iPhone' phone numbers. */ -"PHONE_NUMBER_TYPE_IPHONE" = "ไอโฟน"; +"PHONE_NUMBER_TYPE_IPHONE" = "iPhone"; /* Label for 'Main' phone numbers. */ "PHONE_NUMBER_TYPE_MAIN" = "หลัก"; @@ -1761,13 +1761,13 @@ "RETRY_BUTTON_TEXT" = "ลองใหม่"; /* button title to confirm adding a recipient to a group when their safety number has recently changed */ -"SAFETY_NUMBER_CHANGED_CONFIRM_ADD_TO_GROUP_ACTION" = "เพิ่มไปที่กลุ่มเสมอ"; +"SAFETY_NUMBER_CHANGED_CONFIRM_ADD_TO_GROUP_ACTION" = "ยืนยันที่จะเพิ่มไปที่กลุ่ม"; /* alert button text to confirm placing an outgoing call after the recipients Safety Number has changed. */ -"SAFETY_NUMBER_CHANGED_CONFIRM_CALL_ACTION" = "โทรเสมอ"; +"SAFETY_NUMBER_CHANGED_CONFIRM_CALL_ACTION" = "ยืนยันที่จะโทร"; /* button title to confirm sending to a recipient whose safety number recently changed */ -"SAFETY_NUMBER_CHANGED_CONFIRM_SEND_ACTION" = "ส่งเสมอ"; +"SAFETY_NUMBER_CHANGED_CONFIRM_SEND_ACTION" = "ยืนยันที่จะส่ง"; /* Snippet to share {{safety number}} with a friend. sent e.g. via SMS */ "SAFETY_NUMBER_SHARE_FORMAT" = "รหัสความปลอดภัย Signal ของเรา:\n%@"; diff --git a/Signal/translations/tr_TR.lproj/Localizable.strings b/Signal/translations/tr_TR.lproj/Localizable.strings index 543f1b058..5f82040f0 100644 --- a/Signal/translations/tr_TR.lproj/Localizable.strings +++ b/Signal/translations/tr_TR.lproj/Localizable.strings @@ -249,7 +249,7 @@ "BLOCK_LIST_VIEW_BLOCKED_ALERT_MESSAGE_FORMAT" = "%@ engellendi"; /* The title of the 'user blocked' alert. */ -"BLOCK_LIST_VIEW_BLOCKED_ALERT_TITLE" = "Kullanıcı Engellenmiş"; +"BLOCK_LIST_VIEW_BLOCKED_ALERT_TITLE" = "Kullanıcı Engellendi"; /* The message of the 'You can't block yourself' alert. */ "BLOCK_LIST_VIEW_CANT_BLOCK_SELF_ALERT_MESSAGE" = "Kendinizi engelleyemezsiniz."; @@ -324,7 +324,7 @@ "CALL_VIEW_HANGUP_LABEL" = "Aramayı sonlandır"; /* Accessibility label for muting the microphone */ -"CALL_VIEW_MUTE_LABEL" = "Sessiz"; +"CALL_VIEW_MUTE_LABEL" = "Sessize al"; /* Reminder to the user of the benefits of enabling CallKit and disabling CallKit privacy. */ "CALL_VIEW_SETTINGS_NAG_DESCRIPTION_ALL" = "Eğer ayarlarınızı değiştirirseniz gelen aramaları kilit ekranınızdan yanıtlayabilir ve gelen aramalardaki ismi ve numarayı görebilirsiniz.\n\nDetaylar için gizlilik ayarlarına göz atın."; @@ -703,7 +703,7 @@ "DOMAIN_FRONTING_COUNTRY_VIEW_SECTION_HEADER" = "Sansür Atlatma Lokasyonu"; /* Alert body for when the user has just tried to edit a contacts after declining to give Signal contacts permissions */ -"EDIT_CONTACT_WITHOUT_CONTACTS_PERMISSION_ALERT_BODY" = "Ayarlar uygulamasından erişim verebilirsiniz"; +"EDIT_CONTACT_WITHOUT_CONTACTS_PERMISSION_ALERT_BODY" = "Ayarlar uygulamasından erişim verebilirsiniz."; /* Alert title for when the user has just tried to edit a contacts after declining to give Signal contacts permissions */ "EDIT_CONTACT_WITHOUT_CONTACTS_PERMISSION_ALERT_TITLE" = "Signal Kişi Bilgilerini Düzenlemek İçin Kişi Erişimine İhtiyacı Var"; @@ -802,10 +802,10 @@ "ENABLE_2FA_VIEW_SELECT_PIN_INSTRUCTIONS" = "Bir Kayıt Kilidi PIN'i giriniz. Bu telefon numarasıyla Signal'e tekrar kaydolduğunuzda bu PIN kodunu girmeniz istenecektir."; /* Indicates that user has 'two factor auth pin' disabled. */ -"ENABLE_2FA_VIEW_STATUS_DISABLED_INSTRUCTIONS" = "Arttırılmış güvenlik için, bu telefon numarasıyla Signal'e tekrar kaydolduğunuzda sorulmak üzere bir kayıt kilidi PIN'i aktifleştirin."; +"ENABLE_2FA_VIEW_STATUS_DISABLED_INSTRUCTIONS" = "Daha fazla güvenlik için, bu telefon numarası Signal'e tekrar kaydedilirken sorulacak olan Kayıt Kilidi PIN'ini etkinleştirin."; /* Indicates that user has 'two factor auth pin' enabled. */ -"ENABLE_2FA_VIEW_STATUS_ENABLED_INSTRUCTIONS" = "Kayıt Kilidi aktifleştirildi. Bu numarayla Signal'e tekrar kaydolduğunuzda PIN kodunuzu girmeniz gerekecek."; +"ENABLE_2FA_VIEW_STATUS_ENABLED_INSTRUCTIONS" = "Kayıt Kilidi etkinleştirildi. Numaranızla Signal'e tekrar kaydolurken PIN kodunuzu girmeniz gerekecektir."; /* Title for the 'enable two factor auth PIN' views. */ "ENABLE_2FA_VIEW_TITLE" = "Kayıt Kilidi"; @@ -832,7 +832,7 @@ "ERROR_DESCRIPTION_MESSAGE_SEND_FAILED_DUE_TO_FAILED_ATTACHMENT_WRITE" = "Eklenti yazımı başarısız olduğundan dolayı gönderilemedi."; /* Generic error used whenever Signal can't contact the server */ -"ERROR_DESCRIPTION_NO_INTERNET" = "Sinyal internete bağlanamadı. Lütfen başka bir WiFi ağından deneyin veya mobil veri kullanın."; +"ERROR_DESCRIPTION_NO_INTERNET" = "Signal İnternet'e bağlanamadı. Lütfen farklı bir WiFi ağından veya mobil veriden tekrar deneyin."; /* Error indicating that an outgoing message had no valid recipients. */ "ERROR_DESCRIPTION_NO_VALID_RECIPIENTS" = "Mesaj gönderimi geçerli alıcı olmadığından başarısız oldu."; @@ -979,7 +979,7 @@ "GROUP_MEMBERS_SECTION_TITLE_MEMBERS" = "Üyeler"; /* Title for the 'no longer verified' section of the 'group members' view. */ -"GROUP_MEMBERS_SECTION_TITLE_NO_LONGER_VERIFIED" = "Artık Doğrulanmış değil"; +"GROUP_MEMBERS_SECTION_TITLE_NO_LONGER_VERIFIED" = "Artık Doğrulanmış Değil"; /* Button label for the 'send message to group member' button */ "GROUP_MEMBERS_SEND_MESSAGE" = "Mesaj Gönder"; @@ -1075,7 +1075,7 @@ "INVITE_FRIENDS_PICKER_TITLE" = "Arkadaş Davet Et"; /* Alert warning that sending an invite to multiple users will create a group message whose recipients will be able to see each other. */ -"INVITE_WARNING_MULTIPLE_INVITES_BY_TEXT" = "Aynı anda birden çok kullanıcıyı davet etmek, alıcıların birbirlerini görebilecekleri bir grup mesajı gönderir."; +"INVITE_WARNING_MULTIPLE_INVITES_BY_TEXT" = "Aynı anda birden fazla kullanıcıyı davet etmek, alıcıların birbirlerini görebilecekleri bir grup mesajı gönderir."; /* Slider label embeds {{TIME_AMOUNT}}, e.g. '2 hours'. See *_TIME_AMOUNT strings for examples. */ "KEEP_MESSAGES_DURATION" = "Mesajlar %@ geçtikten sonra kayboluyor."; @@ -1090,7 +1090,7 @@ "LEAVE_GROUP_ACTION" = "Grupdan ayrıl"; /* report an invalid linking code */ -"LINK_DEVICE_INVALID_CODE_BODY" = "Bu QR kodu geçerli değil, bağlamak istediğiniz cihazda görüntülenen QR kodunu taradığınızdan emin olun."; +"LINK_DEVICE_INVALID_CODE_BODY" = "Bu karekod geçerli değil, bağlamak istediğiniz cihazda görüntülenen karekodu taradığınızdan emin olun."; /* report an invalid linking code */ "LINK_DEVICE_INVALID_CODE_TITLE" = "Cihaz Bağlanması Başarısız Oldu"; @@ -1521,7 +1521,7 @@ "PRIVACY_VERIFICATION_FAILED_NO_SAFETY_NUMBERS_IN_CLIPBOARD" = "Signal, panonuzda herhangi bir güvenlik numarası bulamadı. Doğru şekilde kopyaladınız mı?"; /* Alert body when verifying with {{contact name}} */ -"PRIVACY_VERIFICATION_FAILED_THEY_HAVE_WRONG_KEY_FOR_ME" = "Signal kullanıcılarının her çifti ayrı bir güvenlik numarası paylaşıyor. %@ *sizin* emniyet numaranızı gösterdiğini tekrar kontrol edin."; +"PRIVACY_VERIFICATION_FAILED_THEY_HAVE_WRONG_KEY_FOR_ME" = "Signal kullanıcılarının her çifti ayrı bir güvenlik numarası paylaşır. %@ *size* özel olan güvenlik numarasını görüntülüyor olduğunu kontrol edin."; /* alert body */ "PRIVACY_VERIFICATION_FAILED_WITH_OLD_LOCAL_VERSION" = "Signal'in eski bir sürümünü kullanıyorsunuz. Doğrulamadan önce güncellemeniz gerekiyor."; @@ -2049,7 +2049,7 @@ "SETTINGS_SCREEN_SECURITY_DETAIL" = "Uygulama geçişleri esnasında Signal önizlemelerinin görülmez."; /* Settings table section footer. */ -"SETTINGS_SECTION_CALL_KIT_DESCRIPTION" = "IOS Çağrı Entegrasyonu, Signal çağrılarını kilit ekranınızda ve sistemin arama geçmişinde gösterir. İsteğe bağlı olarak kişinin adını ve numarasını da gösterebilirsiniz. ICloud etkinleştirilirse, bu arama geçmişi Apple ile paylaşılacaktır."; +"SETTINGS_SECTION_CALL_KIT_DESCRIPTION" = "IOS Çağrı Entegrasyonu, Signal çağrılarını kilit ekranınızda ve sistemin arama geçmişinde gösterir. İsteğe bağlı olarak kişinin adını ve numarasını da gösterebilirsiniz. ICloud etkinleştirilmiş ise, bu arama geçmişi Apple ile paylaşılacaktır."; /* Label for the notifications section of conversation settings view. */ "SETTINGS_SECTION_NOTIFICATIONS" = "Bildirimler"; @@ -2238,7 +2238,7 @@ "UPDATE_GROUP_CANT_REMOVE_MEMBERS_ALERT_TITLE" = "İzin Verilmiyor"; /* Description of CallKit to upgrading (existing) users */ -"UPGRADE_EXPERIENCE_CALLKIT_DESCRIPTION" = "IOS çağrı entegrasyonu ile kilit ekranınızdan gelen aramaları cevaplamak kolaydır. Varsayılan olarak arayanı anonimleştiririz, bu yüzden de özeldir."; +"UPGRADE_EXPERIENCE_CALLKIT_DESCRIPTION" = "IOS çağrı entegrasyonu ile kilit ekranınızdan gelen aramaları cevaplamak kolaydır. Varsayılan olarak arayan bilgilerini saklarız."; /* button label shown once when when user upgrades app, in context of call kit */ "UPGRADE_EXPERIENCE_CALLKIT_PRIVACY_SETTINGS_BUTTON" = "Daha fazlasını gizlilik ayarlarınızdan öğrenin."; @@ -2247,7 +2247,7 @@ "UPGRADE_EXPERIENCE_CALLKIT_TITLE" = "Cevaplamak için Sadece Kaydırın"; /* Description for notification audio customization */ -"UPGRADE_EXPERIENCE_INTRODUCING_NOTIFICATION_AUDIO_DESCRIPTION" = "Artık varsayılan ve konuşma başına bildirim seslerini seçebilirsiniz ve aramalar her sistem kişisi için seçtiğiniz zil sesine uygun olacaktır."; +"UPGRADE_EXPERIENCE_INTRODUCING_NOTIFICATION_AUDIO_DESCRIPTION" = "Artık varsayılan ve sohbet başına bildirim seslerini seçebilirsiniz ve aramalar her sistem kişisi için seçtiğiniz zil sesine uygun olacaktır."; /* button label shown one time, after upgrade */ "UPGRADE_EXPERIENCE_INTRODUCING_NOTIFICATION_AUDIO_SETTINGS_BUTTON" = "Bildirim Ayarlarını Gözden Geçirin"; From 181d99d80e41ea8640ce74e863caea817cc3f3d6 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Thu, 26 Jul 2018 17:33:09 -0600 Subject: [PATCH 03/15] "Bump build to 2.28.0.14." --- Signal/Signal-Info.plist | 2 +- SignalShareExtension/Info.plist | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Signal/Signal-Info.plist b/Signal/Signal-Info.plist index ef60dcb6f..1b457d818 100644 --- a/Signal/Signal-Info.plist +++ b/Signal/Signal-Info.plist @@ -38,7 +38,7 @@ CFBundleVersion - 2.28.0.13 + 2.28.0.14 ITSAppUsesNonExemptEncryption LOGS_EMAIL diff --git a/SignalShareExtension/Info.plist b/SignalShareExtension/Info.plist index c313a2949..4b69208d6 100644 --- a/SignalShareExtension/Info.plist +++ b/SignalShareExtension/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString 2.28.0 CFBundleVersion - 2.28.0.13 + 2.28.0.14 ITSAppUsesNonExemptEncryption NSAppTransportSecurity From 04c00ff28b56425811281bb4092f67d1ae8d1acc Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Mon, 30 Jul 2018 17:01:40 -0600 Subject: [PATCH 04/15] Fix letterboxed video on M68 Upstream changed how this is handled. --- Signal/src/views/RemoteVideoView.m | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Signal/src/views/RemoteVideoView.m b/Signal/src/views/RemoteVideoView.m index aa7f885ce..0f69c8597 100644 --- a/Signal/src/views/RemoteVideoView.m +++ b/Signal/src/views/RemoteVideoView.m @@ -41,7 +41,9 @@ NS_ASSUME_NONNULL_BEGIN // RTCMTLVideoView requires the MTKView class, available only in iOS9+ // So check that it exists before proceeding. if ([MTKView class]) { - _videoRenderer = [[RTCMTLVideoView alloc] initWithFrame:CGRectZero]; + RTCMTLVideoView *rtcMetalView = [[RTCMTLVideoView alloc] initWithFrame:CGRectZero]; + rtcMetalView.videoContentMode = UIViewContentModeScaleAspectFill; + _videoRenderer = rtcMetalView; [self addSubview:_videoRenderer]; [_videoRenderer autoPinEdgesToSuperviewEdges]; // HACK: Although RTCMTLVideo view is positioned to the top edge of the screen From e5e5bbddc99648d8b177ec916bc66cd65b998fe1 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Thu, 26 Jul 2018 23:08:51 -0600 Subject: [PATCH 05/15] Menu UX - Use longpress gesture to implement pannable tap gesture, e.g. slide between actions, select whichever one is below on touchup - Remove swipe to dismiss, in line with System Action sheet --- .../MenuActionsViewController.swift | 92 +++++++++++++++---- 1 file changed, 74 insertions(+), 18 deletions(-) diff --git a/Signal/src/ViewControllers/MenuActionsViewController.swift b/Signal/src/ViewControllers/MenuActionsViewController.swift index d18f28aa1..0e20c2f92 100644 --- a/Signal/src/ViewControllers/MenuActionsViewController.swift +++ b/Signal/src/ViewControllers/MenuActionsViewController.swift @@ -71,10 +71,6 @@ class MenuActionsViewController: UIViewController, MenuActionSheetDelegate { let tapGesture = UITapGestureRecognizer(target: self, action: #selector(didTapBackground)) self.view.addGestureRecognizer(tapGesture) - - let swipeGesture = UISwipeGestureRecognizer(target: self, action: #selector(didSwipeBackground)) - swipeGesture.direction = .down - self.view.addGestureRecognizer(swipeGesture) } override func viewDidAppear(_ animated: Bool) { @@ -249,11 +245,6 @@ class MenuActionsViewController: UIViewController, MenuActionSheetDelegate { animateDismiss(action: nil) } - @objc - func didSwipeBackground(gesture: UISwipeGestureRecognizer) { - animateDismiss(action: nil) - } - // MARK: MenuActionSheetDelegate func actionSheet(_ actionSheet: MenuActionSheetView, didSelectAction action: MenuAction) { @@ -269,6 +260,7 @@ class MenuActionSheetView: UIView, MenuActionViewDelegate { private let actionStackView: UIStackView private var actions: [MenuAction] + private var actionViews: [MenuActionView] weak var delegate: MenuActionSheetDelegate? override var bounds: CGRect { @@ -288,29 +280,57 @@ class MenuActionSheetView: UIView, MenuActionViewDelegate { actionStackView.spacing = CGHairlineWidth() actions = [] + actionViews = [] super.init(frame: frame) backgroundColor = UIColor.ows_light10 addSubview(actionStackView) - actionStackView.ows_autoPinToSuperviewEdges() + actionStackView.autoPinEdgesToSuperviewEdges() self.clipsToBounds = true - // Prevent panning from percolating to the superview, which would - // cause us to dismiss - let panGestureSink = UIPanGestureRecognizer(target: nil, action: nil) - self.addGestureRecognizer(panGestureSink) + let touchGesture = UILongPressGestureRecognizer(target: self, action: #selector(didTouch(gesture:))) + touchGesture.minimumPressDuration = 0.0 + touchGesture.allowableMovement = CGFloat.greatestFiniteMagnitude + self.addGestureRecognizer(touchGesture) } required init?(coder aDecoder: NSCoder) { fatalError("not implemented") } + @objc + public func didTouch(gesture: UIGestureRecognizer) { + switch gesture.state { + case .possible: + break + case .began: + let location = gesture.location(in: self) + highlightActionView(location: location, fromView: self) + case .changed: + let location = gesture.location(in: self) + highlightActionView(location: location, fromView: self) + case .ended: + Logger.debug("\(logTag) in \(#function) ended") + let location = gesture.location(in: self) + selectActionView(location: location, fromView: self) + case .cancelled: + Logger.debug("\(logTag) in \(#function) canceled") + unhighlightAllActionViews() + case .failed: + Logger.debug("\(logTag) in \(#function) failed") + unhighlightAllActionViews() + } + } + public func addAction(_ action: MenuAction) { + actions.append(action) + let actionView = MenuActionView(action: action) actionView.delegate = self - actions.append(action) + actionViews.append(actionView) + self.actionStackView.addArrangedSubview(actionView) } @@ -329,6 +349,41 @@ class MenuActionSheetView: UIView, MenuActionViewDelegate { mask.path = path.cgPath self.layer.mask = mask } + + private func unhighlightAllActionViews() { + for actionView in actionViews { + actionView.isHighlighted = false + } + } + + private func actionView(touchedBy touchPoint: CGPoint, fromView: UIView) -> MenuActionView? { + for actionView in actionViews { + let convertedPoint = actionView.convert(touchPoint, from: fromView) + if actionView.point(inside: convertedPoint, with: nil) { + return actionView + } + } + return nil + } + + private func highlightActionView(location: CGPoint, fromView: UIView) { + guard let touchedView = actionView(touchedBy: location, fromView: fromView) else { + unhighlightAllActionViews() + return + } + touchedView.isHighlighted = true + self.actionViews.filter { $0 != touchedView }.forEach { $0.isHighlighted = false } + } + + private func selectActionView(location: CGPoint, fromView: UIView) { + guard let selectedView: MenuActionView = actionView(touchedBy: location, fromView: fromView) else { + unhighlightAllActionViews() + return + } + selectedView.isHighlighted = true + self.actionViews.filter { $0 != selectedView }.forEach { $0.isHighlighted = false } + delegate?.actionSheet(self, didSelectAction: selectedView.action) + } } protocol MenuActionViewDelegate: class { @@ -337,7 +392,7 @@ protocol MenuActionViewDelegate: class { class MenuActionView: UIButton { public weak var delegate: MenuActionViewDelegate? - private let action: MenuAction + public let action: MenuAction required init(action: MenuAction) { self.action = action @@ -378,14 +433,15 @@ class MenuActionView: UIButton { contentRow.isUserInteractionEnabled = false self.addSubview(contentRow) - contentRow.ows_autoPinToSuperviewMargins() + contentRow.autoPinEdgesToSuperviewMargins() contentRow.autoSetDimension(.height, toSize: 56, relation: .greaterThanOrEqual) - self.addTarget(self, action: #selector(didPress(sender:)), for: .touchUpInside) + self.isUserInteractionEnabled = false } override var isHighlighted: Bool { didSet { + Logger.debug("\(logTag) in \(#function) \(oldValue) -> \(isHighlighted)") self.backgroundColor = isHighlighted ? UIColor.ows_light10 : UIColor.white } } From 29c459fe60718f7f89ca55cc209486274cd24835 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Mon, 30 Jul 2018 17:46:34 -0600 Subject: [PATCH 06/15] Haptic feedback when changing menu action selection // FREEBIE --- Signal.xcodeproj/project.pbxproj | 4 ++ Signal/src/UserInterface/HapticFeedback.swift | 51 +++++++++++++++++++ .../MenuActionsViewController.swift | 11 +++- 3 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 Signal/src/UserInterface/HapticFeedback.swift diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 58a36d5e8..f302588cc 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -414,6 +414,7 @@ 45FBC5C81DF8575700E9B410 /* CallKitCallManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45FBC59A1DF8575700E9B410 /* CallKitCallManager.swift */; }; 45FBC5D11DF8592E00E9B410 /* SignalCall.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45FBC5D01DF8592E00E9B410 /* SignalCall.swift */; }; 4AC4EA13C8A444455DAB351F /* Pods_SignalMessaging.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 264242150E87D10A357DB07B /* Pods_SignalMessaging.framework */; }; + 4C090A1B210FD9C7001FD7F9 /* HapticFeedback.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C090A1A210FD9C7001FD7F9 /* HapticFeedback.swift */; }; 4C11AA5020FD59C700351FBD /* MessageStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C11AA4F20FD59C700351FBD /* MessageStatusView.swift */; }; 4C13C9F620E57BA30089A98B /* ColorPickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C13C9F520E57BA30089A98B /* ColorPickerViewController.swift */; }; 4C20B2B720CA0034001BAC90 /* ThreadViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4542DF51208B82E9007B4E76 /* ThreadViewModel.swift */; }; @@ -1080,6 +1081,7 @@ 45FBC59A1DF8575700E9B410 /* CallKitCallManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallKitCallManager.swift; sourceTree = ""; }; 45FBC5D01DF8592E00E9B410 /* SignalCall.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignalCall.swift; sourceTree = ""; }; 45FDA43420A4D22700396358 /* OWSNavigationBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OWSNavigationBar.swift; sourceTree = ""; }; + 4C090A1A210FD9C7001FD7F9 /* HapticFeedback.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = HapticFeedback.swift; path = UserInterface/HapticFeedback.swift; sourceTree = ""; }; 4C11AA4F20FD59C700351FBD /* MessageStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageStatusView.swift; sourceTree = ""; }; 4C13C9F520E57BA30089A98B /* ColorPickerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorPickerViewController.swift; sourceTree = ""; }; 4C20B2B820CA10DE001BAC90 /* ConversationSearchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationSearchViewController.swift; sourceTree = ""; }; @@ -1855,6 +1857,7 @@ isa = PBXGroup; children = ( 450DF2071E0DD29E003D14BE /* Notifications */, + 4C090A1A210FD9C7001FD7F9 /* HapticFeedback.swift */, 34FD936E1E3BD43A00109093 /* OWSAnyTouchGestureRecognizer.h */, 34FD936F1E3BD43A00109093 /* OWSAnyTouchGestureRecognizer.m */, 34B3F8331E8DF1700035BE1A /* ViewControllers */, @@ -3254,6 +3257,7 @@ 34A55F3720485465002CC6DE /* OWS2FARegistrationViewController.m in Sources */, 340FC8AD204DAC8D007AEB0F /* OWSLinkedDevicesTableViewController.m in Sources */, 340FC8AA204DAC8D007AEB0F /* NotificationSettingsViewController.m in Sources */, + 4C090A1B210FD9C7001FD7F9 /* HapticFeedback.swift in Sources */, 3496744F2076ACD000080B5F /* LongTextViewController.swift in Sources */, 34FD93701E3BD43A00109093 /* OWSAnyTouchGestureRecognizer.m in Sources */, 34B3F8931E8DF1710035BE1A /* SignalsNavigationController.m in Sources */, diff --git a/Signal/src/UserInterface/HapticFeedback.swift b/Signal/src/UserInterface/HapticFeedback.swift new file mode 100644 index 000000000..beb47fd6a --- /dev/null +++ b/Signal/src/UserInterface/HapticFeedback.swift @@ -0,0 +1,51 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +import Foundation + +protocol HapticAdapter { + func selectionChanged() +} + +class LegacyHapticAdapter: NSObject, HapticAdapter { + + // MARK: HapticAdapter + + func selectionChanged() { + // do nothing + } +} + +@available(iOS 10, *) +class FeedbackGeneratorHapticAdapter: NSObject, HapticAdapter { + let selectionFeedbackGenerator: UISelectionFeedbackGenerator + + override init() { + selectionFeedbackGenerator = UISelectionFeedbackGenerator() + selectionFeedbackGenerator.prepare() + } + + // MARK: HapticAdapter + + func selectionChanged() { + selectionFeedbackGenerator.selectionChanged() + selectionFeedbackGenerator.prepare() + } +} + +class HapticFeedback: HapticAdapter { + let adapter: HapticAdapter + + init() { + if #available(iOS 10, *) { + adapter = FeedbackGeneratorHapticAdapter() + } else { + adapter = LegacyHapticAdapter() + } + } + + func selectionChanged() { + adapter.selectionChanged() + } +} diff --git a/Signal/src/ViewControllers/MenuActionsViewController.swift b/Signal/src/ViewControllers/MenuActionsViewController.swift index 0e20c2f92..cf9f22813 100644 --- a/Signal/src/ViewControllers/MenuActionsViewController.swift +++ b/Signal/src/ViewControllers/MenuActionsViewController.swift @@ -261,6 +261,9 @@ class MenuActionSheetView: UIView, MenuActionViewDelegate { private let actionStackView: UIStackView private var actions: [MenuAction] private var actionViews: [MenuActionView] + private var hapticFeedback: HapticFeedback + private var hasEverHighlightedAction = false + weak var delegate: MenuActionSheetDelegate? override var bounds: CGRect { @@ -281,6 +284,7 @@ class MenuActionSheetView: UIView, MenuActionViewDelegate { actions = [] actionViews = [] + hapticFeedback = HapticFeedback() super.init(frame: frame) @@ -371,7 +375,13 @@ class MenuActionSheetView: UIView, MenuActionViewDelegate { unhighlightAllActionViews() return } + + if hasEverHighlightedAction, !touchedView.isHighlighted { + self.hapticFeedback.selectionChanged() + } touchedView.isHighlighted = true + hasEverHighlightedAction = true + self.actionViews.filter { $0 != touchedView }.forEach { $0.isHighlighted = false } } @@ -441,7 +451,6 @@ class MenuActionView: UIButton { override var isHighlighted: Bool { didSet { - Logger.debug("\(logTag) in \(#function) \(oldValue) -> \(isHighlighted)") self.backgroundColor = isHighlighted ? UIColor.ows_light10 : UIColor.white } } From bfe1eb5503d5abf868d2db470b34ea875020f468 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Mon, 30 Jul 2018 18:31:08 -0600 Subject: [PATCH 07/15] Move reminder views into scrollable content // FREEBIE --- .../HomeView/HomeViewController.m | 259 +++++++++++------- 1 file changed, 160 insertions(+), 99 deletions(-) diff --git a/Signal/src/ViewControllers/HomeView/HomeViewController.m b/Signal/src/ViewControllers/HomeView/HomeViewController.m index ba50c92be..4249fe394 100644 --- a/Signal/src/ViewControllers/HomeView/HomeViewController.m +++ b/Signal/src/ViewControllers/HomeView/HomeViewController.m @@ -40,6 +40,23 @@ typedef NS_ENUM(NSInteger, HomeViewMode) { HomeViewMode_Inbox, }; +// The bulk of the content in this view is driven by a YapDB view/mapping. +// However, we also want to optionally include ReminderView's at the top +// and an "Archived Conversations" button at the bottom. Rather than introduce +// index-offsets into the Mapping calculation, we introduce two pseudo groups +// to add a top and bottom section to the content, and create cells for those +// sections without consulting the YapMapping. +// This is a bit of a hack, but it consolidates the hacks into the Reminder/Archive section +// and allows us to leaves the bulk of the content logic on the happy path. +NSString *const kReminderViewPseudoGroup = @"kReminderViewPseudoGroup"; +NSString *const kArchiveButtonPseudoGroup = @"kArchiveButtonPseudoGroup"; + +typedef NS_ENUM(NSInteger, HomeViewControllerSection) { + HomeViewControllerSectionReminders, + HomeViewControllerSectionConversations, + HomeViewControllerSectionArchiveButton, +}; + NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversationsReuseIdentifier"; @interface HomeViewController () )previewingContext @@ -542,7 +556,7 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations BOOL isShowingSearchResults = !self.searchResultsController.view.hidden; if (isShowingSearchResults) { OWSAssert(self.searchBar.text.ows_stripped.length > 0); - self.tableView.contentOffset = CGPointZero; + [self scrollSearchBarToTopAnimated:NO]; } else if (self.lastThread) { OWSAssert(self.searchBar.text.ows_stripped.length == 0); @@ -734,31 +748,30 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations return (NSInteger)[self.threadMappings numberOfSections]; } -- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)aSection { - NSInteger result = (NSInteger)[self.threadMappings numberOfItemsInSection:(NSUInteger)section]; - if (self.hasArchivedThreadsRow) { - // Add the "archived conversations" row. - result++; + HomeViewControllerSection section = (HomeViewControllerSection)aSection; + switch (section) { + case HomeViewControllerSectionReminders: { + return self.hasVisibleReminders ? 1 : 0; + } + case HomeViewControllerSectionConversations: { + NSInteger result = (NSInteger)[self.threadMappings numberOfItemsInSection:(NSUInteger)section]; + return result; + } + case HomeViewControllerSectionArchiveButton: { + return self.hasArchivedThreadsRow ? 1 : 0; + } } - return result; -} -- (BOOL)isIndexPathForArchivedConversations:(NSIndexPath *)indexPath -{ - if (self.homeViewMode != HomeViewMode_Inbox) { - return NO; - } - if (indexPath.section != 0) { - return NO; - } - NSInteger cellCount = (NSInteger)[self.threadMappings numberOfItemsInSection:(NSUInteger)0]; - return indexPath.row == cellCount; + OWSFail(@"%@ failure: unexpected section: %lu", self.logTag, (unsigned long)section); + return 0; } - (ThreadViewModel *)threadViewModelForIndexPath:(NSIndexPath *)indexPath { TSThread *threadRecord = [self threadForIndexPath:indexPath]; + OWSAssert(threadRecord); ThreadViewModel *_Nullable cachedThreadViewModel = [self.threadViewModelCache objectForKey:threadRecord.uniqueId]; if (cachedThreadViewModel) { @@ -775,11 +788,21 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { - if ([self isIndexPathForArchivedConversations:indexPath]) { - return [self cellForArchivedConversationsRow:tableView]; - } else { - return [self tableView:tableView cellForConversationAtIndexPath:indexPath]; + HomeViewControllerSection section = (HomeViewControllerSection)indexPath.section; + switch (section) { + case HomeViewControllerSectionReminders: { + return self.reminderViewCell; + } + case HomeViewControllerSectionConversations: { + return [self tableView:tableView cellForConversationAtIndexPath:indexPath]; + } + case HomeViewControllerSectionArchiveButton: { + return [self cellForArchivedConversationsRow:tableView]; + } } + + OWSFail(@"%@ failure: unexpected section: %lu", self.logTag, (unsigned long)section); + return [UITableViewCell new]; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForConversationAtIndexPath:(NSIndexPath *)indexPath @@ -875,56 +898,70 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations - (nullable NSArray *)tableView:(UITableView *)tableView editActionsForRowAtIndexPath:(NSIndexPath *)indexPath { - if ([self isIndexPathForArchivedConversations:indexPath]) { - return @[]; - } - - UITableViewRowAction *deleteAction = - [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleDefault - title:NSLocalizedString(@"TXT_DELETE_TITLE", nil) - handler:^(UITableViewRowAction *action, NSIndexPath *swipedIndexPath) { - [self tableViewCellTappedDelete:swipedIndexPath]; - }]; - - UITableViewRowAction *archiveAction; - if (self.homeViewMode == HomeViewMode_Inbox) { - archiveAction = [UITableViewRowAction - rowActionWithStyle:UITableViewRowActionStyleNormal - title:NSLocalizedString(@"ARCHIVE_ACTION", - @"Pressing this button moves a thread from the inbox to the archive") - handler:^(UITableViewRowAction *_Nonnull action, NSIndexPath *_Nonnull tappedIndexPath) { - [self archiveIndexPath:tappedIndexPath]; - [Environment.preferences setHasArchivedAMessage:YES]; - }]; + HomeViewControllerSection section = (HomeViewControllerSection)indexPath.section; + switch (section) { + case HomeViewControllerSectionReminders: { + return @[]; + } + case HomeViewControllerSectionConversations: { + UITableViewRowAction *deleteAction = + [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleDefault + title:NSLocalizedString(@"TXT_DELETE_TITLE", nil) + handler:^(UITableViewRowAction *action, NSIndexPath *swipedIndexPath) { + [self tableViewCellTappedDelete:swipedIndexPath]; + }]; + + UITableViewRowAction *archiveAction; + if (self.homeViewMode == HomeViewMode_Inbox) { + archiveAction = [UITableViewRowAction + rowActionWithStyle:UITableViewRowActionStyleNormal + title:NSLocalizedString(@"ARCHIVE_ACTION", + @"Pressing this button moves a thread from the inbox to the archive") + handler:^(UITableViewRowAction *_Nonnull action, NSIndexPath *_Nonnull tappedIndexPath) { + [self archiveIndexPath:tappedIndexPath]; + [Environment.preferences setHasArchivedAMessage:YES]; + }]; + + } else { + archiveAction = [UITableViewRowAction + rowActionWithStyle:UITableViewRowActionStyleNormal + title:NSLocalizedString(@"UNARCHIVE_ACTION", + @"Pressing this button moves an archived thread from the archive back to " + @"the inbox") + handler:^(UITableViewRowAction *_Nonnull action, NSIndexPath *_Nonnull tappedIndexPath) { + [self archiveIndexPath:tappedIndexPath]; + }]; + } - } else { - archiveAction = [UITableViewRowAction - rowActionWithStyle:UITableViewRowActionStyleNormal - title:NSLocalizedString(@"UNARCHIVE_ACTION", - @"Pressing this button moves an archived thread from the archive back to the inbox") - handler:^(UITableViewRowAction *_Nonnull action, NSIndexPath *_Nonnull tappedIndexPath) { - [self archiveIndexPath:tappedIndexPath]; - }]; + return @[ deleteAction, archiveAction ]; + } + case HomeViewControllerSectionArchiveButton: { + return @[]; + } } - - - return @[ deleteAction, archiveAction ]; } - (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath { - if ([self isIndexPathForArchivedConversations:indexPath]) { - return NO; + HomeViewControllerSection section = (HomeViewControllerSection)indexPath.section; + switch (section) { + case HomeViewControllerSectionReminders: { + return NO; + } + case HomeViewControllerSectionConversations: { + return YES; + } + case HomeViewControllerSectionArchiveButton: { + return NO; + } } - - return YES; } #pragma mark - UISearchBarDelegate - (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar { - [self.tableView setContentOffset:CGPointZero animated:NO]; + [self scrollSearchBarToTopAnimated:NO]; [self updateSearchResultsVisibility]; @@ -977,13 +1014,19 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations self.searchResultsController.view.hidden = !isSearching; if (isSearching) { - [self.tableView setContentOffset:CGPointZero animated:NO]; + [self scrollSearchBarToTopAnimated:NO]; self.tableView.scrollEnabled = NO; } else { self.tableView.scrollEnabled = YES; } } +- (void)scrollSearchBarToTopAnimated:(BOOL)isAnimated +{ + CGFloat topInset = self.topLayoutGuide.length; + [self.tableView setContentOffset:CGPointMake(0, -topInset) animated:isAnimated]; +} + #pragma mark - UIScrollViewDelegate - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView @@ -1004,6 +1047,11 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations - (void)tableViewCellTappedDelete:(NSIndexPath *)indexPath { + if (indexPath.section != HomeViewControllerSectionConversations) { + OWSFail(@"%@ failure: unexpected section: %lu", self.logTag, (unsigned long)indexPath.section); + return; + } + TSThread *thread = [self threadForIndexPath:indexPath]; if ([thread isKindOfClass:[TSGroupThread class]]) { @@ -1055,6 +1103,11 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations - (void)archiveIndexPath:(NSIndexPath *)indexPath { + if (indexPath.section != HomeViewControllerSectionConversations) { + OWSFail(@"%@ failure: unexpected section: %lu", self.logTag, (unsigned long)indexPath.section); + return; + } + TSThread *thread = [self threadForIndexPath:indexPath]; [self.editingDbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { @@ -1075,15 +1128,22 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations DDLogInfo(@"%@ %s %ld %ld", self.logTag, __PRETTY_FUNCTION__, (long)indexPath.row, (long)indexPath.section); [self.searchBar resignFirstResponder]; - - if ([self isIndexPathForArchivedConversations:indexPath]) { - [self showArchivedConversations]; - return; + HomeViewControllerSection section = (HomeViewControllerSection)indexPath.section; + switch (section) { + case HomeViewControllerSectionReminders: { + break; + } + case HomeViewControllerSectionConversations: { + TSThread *thread = [self threadForIndexPath:indexPath]; + [self presentThread:thread action:ConversationViewActionNone]; + [tableView deselectRowAtIndexPath:indexPath animated:YES]; + break; + } + case HomeViewControllerSectionArchiveButton: { + [self showArchivedConversations]; + break; + } } - - TSThread *thread = [self threadForIndexPath:indexPath]; - [self presentThread:thread action:ConversationViewActionNone]; - [tableView deselectRowAtIndexPath:indexPath animated:YES]; } - (void)presentThread:(TSThread *)thread action:(ConversationViewAction)action @@ -1218,8 +1278,9 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations { OWSAssertIsOnMainThread(); - self.threadMappings = [[YapDatabaseViewMappings alloc] initWithGroups:@[ self.currentGrouping ] - view:TSThreadDatabaseViewExtensionName]; + self.threadMappings = [[YapDatabaseViewMappings alloc] + initWithGroups:@[ kReminderViewPseudoGroup, self.currentGrouping, kArchiveButtonPseudoGroup ] + view:TSThreadDatabaseViewExtensionName]; [self.threadMappings setIsReversed:YES forGroup:self.currentGrouping]; [self resetMappings]; From 78b4df95a5d93078650e65ce5dfa9640a77a3488 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Mon, 30 Jul 2018 19:25:18 -0600 Subject: [PATCH 08/15] fixup call banner offsets --- .../ViewControllers/OWSNavigationController.m | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/SignalMessaging/ViewControllers/OWSNavigationController.m b/SignalMessaging/ViewControllers/OWSNavigationController.m index bd38547ee..4fa9f5853 100644 --- a/SignalMessaging/ViewControllers/OWSNavigationController.m +++ b/SignalMessaging/ViewControllers/OWSNavigationController.m @@ -133,18 +133,11 @@ NS_ASSUME_NONNULL_BEGIN if (@available(iOS 11.0, *)) { if (OWSWindowManager.sharedManager.hasCall) { - if (UIDevice.currentDevice.isIPhoneX) { - // iPhoneX computes status bar height differently. - // IOS_DEVICE_CONSTANT - self.additionalSafeAreaInsets = UIEdgeInsetsMake(navbar.navbarWithoutStatusHeight + 20, 0, 0, 0); - - } else { - self.additionalSafeAreaInsets - = UIEdgeInsetsMake(navbar.navbarWithoutStatusHeight + CurrentAppContext().statusBarHeight, 0, 0, 0); - } + self.additionalSafeAreaInsets = UIEdgeInsetsMake(20, 0, 0, 0); } else { self.additionalSafeAreaInsets = UIEdgeInsetsZero; } + // in iOS11 we have to ensure the navbar frame *in* layoutSubviews. [navbar layoutSubviews]; } else { From def8b43daa181a5edc6e82fbaa2ac98960fa70da Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Tue, 31 Jul 2018 14:28:41 -0600 Subject: [PATCH 09/15] iOS9/10 fixups --- .../HomeView/HomeViewController.m | 6 +++++- .../ViewControllers/OWSNavigationController.m | 15 ++------------ SignalMessaging/Views/OWSNavigationBar.swift | 20 +++++++++++++------ 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/Signal/src/ViewControllers/HomeView/HomeViewController.m b/Signal/src/ViewControllers/HomeView/HomeViewController.m index 4249fe394..49e95f7bd 100644 --- a/Signal/src/ViewControllers/HomeView/HomeViewController.m +++ b/Signal/src/ViewControllers/HomeView/HomeViewController.m @@ -418,7 +418,11 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations [self addChildViewController:searchResultsController]; [self.view addSubview:searchResultsController.view]; [searchResultsController.view autoPinEdgesToSuperviewEdgesWithInsets:UIEdgeInsetsZero excludingEdge:ALEdgeTop]; - [searchResultsController.view autoPinTopToSuperviewMarginWithInset:56]; + if (@available(iOS 11, *)) { + [searchResultsController.view autoPinTopToSuperviewMarginWithInset:56]; + } else { + [searchResultsController.view autoPinToTopLayoutGuideOfViewController:self withInset:40]; + } searchResultsController.view.hidden = YES; [self updateBarButtonItems]; diff --git a/SignalMessaging/ViewControllers/OWSNavigationController.m b/SignalMessaging/ViewControllers/OWSNavigationController.m index 4fa9f5853..7a9d3e17e 100644 --- a/SignalMessaging/ViewControllers/OWSNavigationController.m +++ b/SignalMessaging/ViewControllers/OWSNavigationController.m @@ -141,20 +141,9 @@ NS_ASSUME_NONNULL_BEGIN // in iOS11 we have to ensure the navbar frame *in* layoutSubviews. [navbar layoutSubviews]; } else { - // Pre iOS11 we size the navbar, and position it vertically once. + // in iOS9/10 we only need to size the navbar once [navbar sizeToFit]; - - if (OWSWindowManager.sharedManager.hasCall) { - CGRect oldFrame = navbar.frame; - CGRect newFrame = oldFrame; - newFrame.size.height = navbar.callBannerHeight; - navbar.frame = newFrame; - } else { - CGRect oldFrame = navbar.frame; - CGRect newFrame - = CGRectMake(oldFrame.origin.x, navbar.statusBarHeight, oldFrame.size.width, oldFrame.size.height); - navbar.frame = newFrame; - } + [navbar layoutIfNeeded]; // Since the navbar's frame was updated, we need to be sure our child VC's // container view is updated. diff --git a/SignalMessaging/Views/OWSNavigationBar.swift b/SignalMessaging/Views/OWSNavigationBar.swift index 04014a96d..c05a63ded 100644 --- a/SignalMessaging/Views/OWSNavigationBar.swift +++ b/SignalMessaging/Views/OWSNavigationBar.swift @@ -94,11 +94,18 @@ public class OWSNavigationBar: UINavigationBar { if #available(iOS 11, *) { return super.sizeThatFits(size) - } else { - // pre iOS11, sizeThatFits is repeatedly called to determine how much space to reserve for that navbar. + } else if #available(iOS 10, *) { + // iOS10 + // sizeThatFits is repeatedly called to determine how much space to reserve for that navbar. // That is, increasing this causes the child view controller to be pushed down. // (as of iOS11, this is not used and instead we use additionalSafeAreaInsets) return CGSize(width: fullWidth, height: navbarWithoutStatusHeight + statusBarHeight) + } else { + // iOS9 + // sizeThatFits is repeatedly called to determine how much space to reserve for that navbar. + // That is, increasing this causes the child view controller to be pushed down. + // (as of iOS11, this is not used and instead we use additionalSafeAreaInsets) + return CGSize(width: fullWidth, height: navbarWithoutStatusHeight + callBannerHeight + 20) } } @@ -108,15 +115,16 @@ public class OWSNavigationBar: UINavigationBar { return } + guard #available(iOS 11, *) else { + super.layoutSubviews() + return + } + self.frame = CGRect(x: 0, y: callBannerHeight, width: fullWidth, height: navbarWithoutStatusHeight) self.bounds = CGRect(x: 0, y: 0, width: fullWidth, height: navbarWithoutStatusHeight) super.layoutSubviews() - guard #available(iOS 11, *) else { - return - } - // This is only necessary on iOS11, which has some private views within that lay outside of the navbar. // They aren't actually visible behind the call status bar, but they looks strange during present/dismiss // animations for modal VC's From 026ef02ce5e81bf1838e7ea2c42235af0c87baec Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Tue, 31 Jul 2018 16:00:35 -0400 Subject: [PATCH 10/15] Refine 'new message' animations. --- .../Cells/OWSMessageBubbleView.m | 6 +++ .../ConversationViewController.m | 45 +++++++------------ .../ConversationView/ConversationViewLayout.m | 20 +++++++++ 3 files changed, 41 insertions(+), 30 deletions(-) diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m index 199ed9033..19822ff7a 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m @@ -508,12 +508,18 @@ NS_ASSUME_NONNULL_BEGIN [self.bubbleView addPartnerView:shadowView]; [self.bubbleView addPartnerView:clipView]; + // Prevent the layer from animating changes. + [CATransaction begin]; + [CATransaction setDisableActions:YES]; + OWSAssert(buttonsView.backgroundColor); shadowView.fillColor = buttonsView.backgroundColor; shadowView.layer.shadowColor = [UIColor blackColor].CGColor; shadowView.layer.shadowOpacity = 0.12f; shadowView.layer.shadowOffset = CGSizeZero; shadowView.layer.shadowRadius = 1.f; + + [CATransaction commit]; } - (BOOL)contactShareHasSpacerTop diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m index 35e50c88a..a2f8eb578 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m @@ -3489,55 +3489,40 @@ typedef enum : NSUInteger { OWSAssert(rowChanges); // If user sends a new outgoing message, don't animate the change. - BOOL isOnlyInsertingNewOutgoingMessages = YES; - BOOL isOnlyUpdatingLastOutgoingMessage = YES; - NSNumber *_Nullable lastUpdateRow = nil; - NSNumber *_Nullable lastNonUpdateRow = nil; + BOOL isOnlyModifyingLastMessage = YES; for (YapDatabaseViewRowChange *rowChange in rowChanges) { switch (rowChange.type) { case YapDatabaseViewChangeDelete: - isOnlyInsertingNewOutgoingMessages = NO; - isOnlyUpdatingLastOutgoingMessage = NO; - if (!lastNonUpdateRow || lastNonUpdateRow.integerValue < rowChange.indexPath.row) { - lastNonUpdateRow = @(rowChange.indexPath.row); - } + isOnlyModifyingLastMessage = NO; break; case YapDatabaseViewChangeInsert: { - isOnlyUpdatingLastOutgoingMessage = NO; ConversationViewItem *_Nullable viewItem = [self viewItemForIndex:(NSInteger)rowChange.finalIndex]; - if ([viewItem.interaction isKindOfClass:[TSOutgoingMessage class]] + if (([viewItem.interaction isKindOfClass:[TSIncomingMessage class]] || + [viewItem.interaction isKindOfClass:[TSOutgoingMessage class]]) && rowChange.finalIndex >= oldViewItemCount) { continue; } - if (!lastNonUpdateRow || lastNonUpdateRow.unsignedIntegerValue < rowChange.finalIndex) { - lastNonUpdateRow = @(rowChange.finalIndex); - } + isOnlyModifyingLastMessage = NO; } case YapDatabaseViewChangeMove: - isOnlyInsertingNewOutgoingMessages = NO; - isOnlyUpdatingLastOutgoingMessage = NO; - if (!lastNonUpdateRow || lastNonUpdateRow.integerValue < rowChange.indexPath.row) { - lastNonUpdateRow = @(rowChange.indexPath.row); - } - if (!lastNonUpdateRow || lastNonUpdateRow.unsignedIntegerValue < rowChange.finalIndex) { - lastNonUpdateRow = @(rowChange.finalIndex); - } + isOnlyModifyingLastMessage = NO; break; case YapDatabaseViewChangeUpdate: { - isOnlyInsertingNewOutgoingMessages = NO; - ConversationViewItem *_Nullable viewItem = [self viewItemForIndex:(NSInteger)rowChange.finalIndex]; - if (![viewItem.interaction isKindOfClass:[TSOutgoingMessage class]] - || rowChange.indexPath.row != (NSInteger)(oldViewItemCount - 1)) { - isOnlyUpdatingLastOutgoingMessage = NO; + if (rowChange.changes == YapDatabaseViewChangedDependency) { + continue; } - if (!lastUpdateRow || lastUpdateRow.integerValue < rowChange.indexPath.row) { - lastUpdateRow = @(rowChange.indexPath.row); + ConversationViewItem *_Nullable viewItem = [self viewItemForIndex:(NSInteger)rowChange.finalIndex]; + if (([viewItem.interaction isKindOfClass:[TSIncomingMessage class]] || + [viewItem.interaction isKindOfClass:[TSOutgoingMessage class]]) + && rowChange.finalIndex >= oldViewItemCount) { + continue; } + isOnlyModifyingLastMessage = NO; break; } } } - BOOL shouldAnimateRowUpdates = !(isOnlyInsertingNewOutgoingMessages || isOnlyUpdatingLastOutgoingMessage); + BOOL shouldAnimateRowUpdates = !isOnlyModifyingLastMessage; return shouldAnimateRowUpdates; } diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewLayout.m b/Signal/src/ViewControllers/ConversationView/ConversationViewLayout.m index 305b9686b..a65bb8a31 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewLayout.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewLayout.m @@ -172,6 +172,26 @@ NS_ASSUME_NONNULL_BEGIN return self.collectionView.bounds.size.width != newBounds.size.width; } +- (nullable UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath: + (NSIndexPath *)indexPath +{ + UICollectionViewLayoutAttributes *_Nullable layoutAttributes = + [super initialLayoutAttributesForAppearingItemAtIndexPath:indexPath]; + // Don't fade in new cells. + layoutAttributes.alpha = 1.f; + return layoutAttributes; +} + +- (nullable UICollectionViewLayoutAttributes *)finalLayoutAttributesForDisappearingItemAtIndexPath: + (NSIndexPath *)indexPath +{ + UICollectionViewLayoutAttributes *_Nullable layoutAttributes = + [super finalLayoutAttributesForDisappearingItemAtIndexPath:indexPath]; + // Don't fade in new cells. + layoutAttributes.alpha = 1.f; + return layoutAttributes; +} + @end NS_ASSUME_NONNULL_END From 24d85898e1be6436baaad05069ac5dc4e7dc9b6e Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Tue, 31 Jul 2018 16:02:39 -0400 Subject: [PATCH 11/15] Refine 'new message' animations. --- .../ConversationView/ConversationViewController.m | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m index a2f8eb578..b63a84a02 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m @@ -3478,6 +3478,9 @@ typedef enum : NSUInteger { } else { [UIView performWithoutAnimation:^{ [self.collectionView performBatchUpdates:batchUpdates completion:batchUpdatesCompletion]; + if (scrollToBottom) { + [self scrollToBottomAnimated:NO]; + } }]; } self.lastReloadDate = [NSDate new]; From 995c2f2a2af997aef45ac5ebd7954d2a73dad3ba Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Tue, 31 Jul 2018 16:06:58 -0400 Subject: [PATCH 12/15] Refine 'new message' animations. --- .../ConversationView/ConversationViewController.m | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m index b63a84a02..d7fdf01ee 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m @@ -3468,9 +3468,9 @@ typedef enum : NSUInteger { } [self updateLastVisibleTimestamp]; - - if (scrollToBottom) { - [self scrollToBottomAnimated:shouldAnimateScrollToBottom && shouldAnimateUpdates]; + + if (scrollToBottom && shouldAnimateUpdates) { + [self scrollToBottomAnimated:shouldAnimateScrollToBottom]; } }; if (shouldAnimateUpdates) { From cd6225c438ce614a3d56e7058342553c138576c5 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Tue, 31 Jul 2018 17:43:27 -0400 Subject: [PATCH 13/15] Respond to CR. --- .../ConversationView/ConversationViewLayout.m | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewLayout.m b/Signal/src/ViewControllers/ConversationView/ConversationViewLayout.m index a65bb8a31..305b9686b 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewLayout.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewLayout.m @@ -172,26 +172,6 @@ NS_ASSUME_NONNULL_BEGIN return self.collectionView.bounds.size.width != newBounds.size.width; } -- (nullable UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath: - (NSIndexPath *)indexPath -{ - UICollectionViewLayoutAttributes *_Nullable layoutAttributes = - [super initialLayoutAttributesForAppearingItemAtIndexPath:indexPath]; - // Don't fade in new cells. - layoutAttributes.alpha = 1.f; - return layoutAttributes; -} - -- (nullable UICollectionViewLayoutAttributes *)finalLayoutAttributesForDisappearingItemAtIndexPath: - (NSIndexPath *)indexPath -{ - UICollectionViewLayoutAttributes *_Nullable layoutAttributes = - [super finalLayoutAttributesForDisappearingItemAtIndexPath:indexPath]; - // Don't fade in new cells. - layoutAttributes.alpha = 1.f; - return layoutAttributes; -} - @end NS_ASSUME_NONNULL_END From d713c4158f44c40a96ad1cf9683199e4cf0d6372 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Tue, 31 Jul 2018 16:07:11 -0600 Subject: [PATCH 14/15] sync translations --- .../translations/da.lproj/Localizable.strings | 2 +- .../translations/de.lproj/Localizable.strings | 26 +++++++++---------- .../translations/es.lproj/Localizable.strings | 4 +-- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Signal/translations/da.lproj/Localizable.strings b/Signal/translations/da.lproj/Localizable.strings index b518f8d40..1b839258d 100644 --- a/Signal/translations/da.lproj/Localizable.strings +++ b/Signal/translations/da.lproj/Localizable.strings @@ -2121,7 +2121,7 @@ "SHARE_EXTENSION_VIEW_TITLE" = "Share to Signal"; /* Action sheet item */ -"SHOW_SAFETY_NUMBER_ACTION" = "Show Safety Number"; +"SHOW_SAFETY_NUMBER_ACTION" = "Vis sikkerhedsnummer"; /* notification action */ "SHOW_THREAD_BUTTON_TITLE" = "Vis Samtale"; diff --git a/Signal/translations/de.lproj/Localizable.strings b/Signal/translations/de.lproj/Localizable.strings index 5812d64e8..6d26d8cb1 100644 --- a/Signal/translations/de.lproj/Localizable.strings +++ b/Signal/translations/de.lproj/Localizable.strings @@ -402,7 +402,7 @@ "CONFIRM_LEAVE_GROUP_TITLE" = "Wirklich verlassen?"; /* Button text */ -"CONFIRM_LINK_NEW_DEVICE_ACTION" = "Neues Gerät verknüpfen"; +"CONFIRM_LINK_NEW_DEVICE_ACTION" = "Neues Gerät koppeln"; /* Action sheet body presented when a user's SN has recently changed. Embeds {{contact's name or phone number}} */ "CONFIRM_SENDING_TO_CHANGED_IDENTITY_BODY_FORMAT" = "%@hat Signal vielleicht erneut installiert oder das Gerät gewechselt. Verifiziert eure gemeinsame Sicherheitsnummer zur Sicherstellung der Privatsphäre."; @@ -676,7 +676,7 @@ "DEVICE_LAST_ACTIVE_AT_LABEL" = "Zuletzt aktiv: %@"; /* {{Short Date}} when device was linked. */ -"DEVICE_LINKED_AT_LABEL" = "Verknüpft: %@"; +"DEVICE_LINKED_AT_LABEL" = "Gekoppelt: %@"; /* Alert title that can occur when viewing device manager. */ "DEVICE_LIST_UPDATE_FAILED_TITLE" = "Aktualisieren der Geräteliste gescheitert."; @@ -1090,34 +1090,34 @@ "LEAVE_GROUP_ACTION" = "Gruppe verlassen"; /* report an invalid linking code */ -"LINK_DEVICE_INVALID_CODE_BODY" = "Dieser QR-Code ist ungültig. Stelle bitte sicher, dass du den QR-Code einscannst, der auf dem zu verknüpfenden Gerät angezeigt wird."; +"LINK_DEVICE_INVALID_CODE_BODY" = "Dieser QR-Code ist ungültig. Stelle bitte sicher, dass du den QR-Code einscannst, der auf dem zu koppelnden Gerät angezeigt wird."; /* report an invalid linking code */ -"LINK_DEVICE_INVALID_CODE_TITLE" = "Verknüpfen des Geräts gescheitert"; +"LINK_DEVICE_INVALID_CODE_TITLE" = "Koppeln des Geräts gescheitert"; /* confirm the users intent to link a new device */ "LINK_DEVICE_PERMISSION_ALERT_BODY" = "Dieses Gerät wird in der Lage sein, deine Gruppen und Kontakte zu sehen, alle deine Nachrichten zu lesen und Nachrichten in deinem Namen zu versenden."; /* confirm the users intent to link a new device */ -"LINK_DEVICE_PERMISSION_ALERT_TITLE" = "Gerät verknüpfen?"; +"LINK_DEVICE_PERMISSION_ALERT_TITLE" = "Gerät koppeln?"; /* attempt another linking */ "LINK_DEVICE_RESTART" = "Erneut versuchen"; /* QR Scanning screen instructions, placed alongside a camera view for scanning QR Codes */ -"LINK_DEVICE_SCANNING_INSTRUCTIONS" = "Scanne zum Verknüpfen den auf dem Gerät angezeigten QR-Code ein."; +"LINK_DEVICE_SCANNING_INSTRUCTIONS" = "Scanne zum Koppeln den auf dem Gerät angezeigten QR-Code ein."; /* Subheading for 'Link New Device' navigation */ "LINK_NEW_DEVICE_SUBTITLE" = "QR-Code einscannen"; /* Navigation title when scanning QR code to add new device. */ -"LINK_NEW_DEVICE_TITLE" = "Neues Gerät verknüpfen"; +"LINK_NEW_DEVICE_TITLE" = "Neues Gerät koppeln"; /* Menu item and navbar title for the device manager */ -"LINKED_DEVICES_TITLE" = "Verknüpfte Geräte"; +"LINKED_DEVICES_TITLE" = "Gekoppelte Geräte"; /* Alert Title */ -"LINKING_DEVICE_FAILED_TITLE" = "Verknüpfen des Geräts gescheitert"; +"LINKING_DEVICE_FAILED_TITLE" = "Gerätekopplung gescheitert"; /* table cell label in conversation settings */ "LIST_GROUP_MEMBERS_ACTION" = "Gruppenmitglieder"; @@ -2208,16 +2208,16 @@ "UNKNOWN_VALUE" = "Unbekannt"; /* button title for unlinking a device */ -"UNLINK_ACTION" = "Entfernen"; +"UNLINK_ACTION" = "Entkoppeln"; /* Alert message to confirm unlinking a device */ -"UNLINK_CONFIRMATION_ALERT_BODY" = "Nach Entfernen dieses Geräts wird es keine weiteren Nachrichten senden oder empfangen können."; +"UNLINK_CONFIRMATION_ALERT_BODY" = "Nach Entkoppeln dieses Geräts wird es keine weiteren Nachrichten senden oder empfangen können."; /* Alert title for confirming device deletion */ -"UNLINK_CONFIRMATION_ALERT_TITLE" = "»%@« entfernen?"; +"UNLINK_CONFIRMATION_ALERT_TITLE" = "»%@« entkoppeln?"; /* Alert title when unlinking device fails */ -"UNLINKING_FAILED_ALERT_TITLE" = "Die Verknüpfung für dein Gerät konnte nicht entfernt werden."; +"UNLINKING_FAILED_ALERT_TITLE" = "Signal konnte dein Gerät nicht entkoppeln."; /* Label text in device manager for a device with no name */ "UNNAMED_DEVICE" = "Unbenanntes Gerät"; diff --git a/Signal/translations/es.lproj/Localizable.strings b/Signal/translations/es.lproj/Localizable.strings index 429b96f82..c1be91fdc 100644 --- a/Signal/translations/es.lproj/Localizable.strings +++ b/Signal/translations/es.lproj/Localizable.strings @@ -1431,7 +1431,7 @@ "OPEN_SETTINGS_BUTTON" = "Ajustes"; /* Info Message when {{other user}} disables or doesn't support disappearing messages */ -"OTHER_DISABLED_DISAPPEARING_MESSAGES_CONFIGURATION" = "%@ ha desactivado la caducidad de mensajes."; +"OTHER_DISABLED_DISAPPEARING_MESSAGES_CONFIGURATION" = "%@ ha desactivado los mensajes con caducidad."; /* Info Message when {{other user}} updates message expiration to {{time amount}}, see the *_TIME_AMOUNT strings for context. */ "OTHER_UPDATED_DISAPPEARING_MESSAGES_CONFIGURATION" = "%@ ha fijado la caducidad de mensajes a %@."; @@ -2343,7 +2343,7 @@ "WAITING_TO_COMPLETE_DEVICE_LINK_TEXT" = "Completa la configuración en la aplicación de escritorio."; /* Info Message when you disable disappearing messages */ -"YOU_DISABLED_DISAPPEARING_MESSAGES_CONFIGURATION" = "Has desactivado la caducidad de mensajes."; +"YOU_DISABLED_DISAPPEARING_MESSAGES_CONFIGURATION" = "Has desactivado los mensajes con caducidad."; /* Info message embedding a {{time amount}}, see the *_TIME_AMOUNT strings for context. */ "YOU_UPDATED_DISAPPEARING_MESSAGES_CONFIGURATION" = "Has fijado la caducidad de mensajes a %@."; From 48a69c46d82430cecf712c5c832d9696d0a35eb8 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Tue, 31 Jul 2018 16:07:22 -0600 Subject: [PATCH 15/15] "Bump build to 2.28.0.15." --- Signal/Signal-Info.plist | 2 +- SignalShareExtension/Info.plist | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Signal/Signal-Info.plist b/Signal/Signal-Info.plist index 1b457d818..cd17c4326 100644 --- a/Signal/Signal-Info.plist +++ b/Signal/Signal-Info.plist @@ -38,7 +38,7 @@ CFBundleVersion - 2.28.0.14 + 2.28.0.15 ITSAppUsesNonExemptEncryption LOGS_EMAIL diff --git a/SignalShareExtension/Info.plist b/SignalShareExtension/Info.plist index 4b69208d6..4727a5757 100644 --- a/SignalShareExtension/Info.plist +++ b/SignalShareExtension/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString 2.28.0 CFBundleVersion - 2.28.0.14 + 2.28.0.15 ITSAppUsesNonExemptEncryption NSAppTransportSecurity