Enable profile pictures
This commit is contained in:
parent
7aa4e83700
commit
859384afaf
|
@ -58,7 +58,7 @@ final class DisplayNameVC : OnboardingBaseViewController {
|
|||
guard !displayName.isEmpty else {
|
||||
return OWSAlerts.showErrorAlert(message: NSLocalizedString("Please pick a display name", comment: ""))
|
||||
}
|
||||
guard displayName.allSatisfy({ "0"..."9" ~= $0 || "a"..."z" ~= $0 || "A"..."Z" ~= $0 || $0 == "_" }) else {
|
||||
guard displayName.allSatisfy({ "0"..."9" ~= $0 || "a"..."z" ~= $0 || "A"..."Z" ~= $0 || $0 == "_" || $0 == " " }) else {
|
||||
return OWSAlerts.showErrorAlert(message: NSLocalizedString("Please pick a display name that consists of only a-z, A-Z, 0-9 and _ characters", comment: ""))
|
||||
}
|
||||
guard !OWSProfileManager.shared().isProfileNameTooLong(displayName) else {
|
||||
|
|
|
@ -57,7 +57,9 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
handler:^(UIAlertAction *_Nonnull action) {
|
||||
[self.delegate clearAvatar];
|
||||
}];
|
||||
[actionSheet addAction:clearAction];
|
||||
|
||||
// TODO: enable this once we support removing avatars (as opposed to replacing)
|
||||
// [actionSheet addAction:clearAction];
|
||||
}
|
||||
|
||||
[self.delegate.fromViewController presentAlert:actionSheet];
|
||||
|
|
|
@ -396,7 +396,7 @@ NSString *const kProfileView_LastPresentedDate = @"kProfileView_LastPresentedDat
|
|||
return [OWSAlerts showErrorAlertWithMessage:NSLocalizedString(@"Please pick a display name", @"")];
|
||||
}
|
||||
|
||||
NSCharacterSet *allowedCharacters = [NSCharacterSet characterSetWithCharactersInString:@"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"];
|
||||
NSCharacterSet *allowedCharacters = [NSCharacterSet characterSetWithCharactersInString:@"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_ "];
|
||||
if ([normalizedProfileName rangeOfCharacterFromSet:allowedCharacters.invertedSet].location != NSNotFound) {
|
||||
return [OWSAlerts showErrorAlertWithMessage:NSLocalizedString(@"Please pick a display name that consists of only a-z, A-Z, 0-9 and _ characters", @"")];
|
||||
}
|
||||
|
|
|
@ -1165,12 +1165,10 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error);
|
|||
}
|
||||
NSString *_Nullable avatarUrlPathAtStart = userProfile.avatarUrlPath;
|
||||
|
||||
if (userProfile.profileKey.keyData.length < 1 || userProfile.avatarUrlPath.length < 1) {
|
||||
if (userProfile.avatarUrlPath.length < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
OWSAES256Key *profileKeyAtStart = userProfile.profileKey;
|
||||
|
||||
NSString *fileName = [self generateAvatarFilename];
|
||||
NSString *filePath = [OWSUserProfile profileAvatarFilepathWithFilename:fileName];
|
||||
|
||||
|
@ -1188,66 +1186,6 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error);
|
|||
NSString *tempDirectory = OWSTemporaryDirectory();
|
||||
NSString *tempFilePath = [tempDirectory stringByAppendingPathComponent:fileName];
|
||||
|
||||
void (^completionHandler)(NSURLResponse *_Nonnull, NSURL *_Nullable, NSError *_Nullable) = ^(
|
||||
NSURLResponse *_Nonnull response, NSURL *_Nullable filePathParam, NSError *_Nullable error) {
|
||||
// Ensure disk IO and decryption occurs off the main thread.
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
NSData *_Nullable encryptedData = [NSData dataWithContentsOfFile:tempFilePath];
|
||||
NSData *_Nullable decryptedData = encryptedData;
|
||||
UIImage *_Nullable image = nil;
|
||||
if (decryptedData) {
|
||||
BOOL success = [decryptedData writeToFile:filePath atomically:YES];
|
||||
if (success) {
|
||||
image = [UIImage imageWithContentsOfFile:filePath];
|
||||
}
|
||||
}
|
||||
|
||||
@synchronized(self.currentAvatarDownloads)
|
||||
{
|
||||
[self.currentAvatarDownloads removeObject:userProfile.recipientId];
|
||||
}
|
||||
|
||||
OWSUserProfile *latestUserProfile =
|
||||
[OWSUserProfile getOrBuildUserProfileForRecipientId:userProfile.recipientId
|
||||
dbConnection:self.dbConnection];
|
||||
if (latestUserProfile.profileKey.keyData.length < 1
|
||||
|| ![latestUserProfile.profileKey isEqual:userProfile.profileKey]) {
|
||||
OWSLogWarn(@"Ignoring avatar download for obsolete user profile.");
|
||||
} else if (![avatarUrlPathAtStart isEqualToString:latestUserProfile.avatarUrlPath]) {
|
||||
OWSLogInfo(@"avatar url has changed during download");
|
||||
if (latestUserProfile.avatarUrlPath.length > 0) {
|
||||
[self downloadAvatarForUserProfile:latestUserProfile];
|
||||
}
|
||||
} else if (error) {
|
||||
OWSLogError(@"avatar download for %@ failed with error: %@", userProfile.recipientId, error);
|
||||
} else if (!encryptedData) {
|
||||
OWSLogError(@"avatar encrypted data for %@ could not be read.", userProfile.recipientId);
|
||||
} else if (!decryptedData) {
|
||||
OWSLogError(@"avatar data for %@ could not be decrypted.", userProfile.recipientId);
|
||||
} else if (!image) {
|
||||
OWSLogError(
|
||||
@"avatar image for %@ could not be loaded with error: %@", userProfile.recipientId, error);
|
||||
} else {
|
||||
[self updateProfileAvatarCache:image filename:fileName];
|
||||
|
||||
[latestUserProfile updateWithAvatarFileName:fileName dbConnection:self.dbConnection completion:nil];
|
||||
}
|
||||
|
||||
// If we're updating the profile that corresponds to our local number,
|
||||
// update the local profile as well.
|
||||
NSString *_Nullable localNumber = self.tsAccountManager.localNumber;
|
||||
if (localNumber && [localNumber isEqualToString:userProfile.recipientId]) {
|
||||
OWSUserProfile *localUserProfile = self.localUserProfile;
|
||||
OWSAssertDebug(localUserProfile);
|
||||
|
||||
[localUserProfile updateWithAvatarFileName:fileName dbConnection:self.dbConnection completion:nil];
|
||||
[self updateProfileAvatarCache:image filename:fileName];
|
||||
}
|
||||
|
||||
OWSAssertDebug(backgroundTask);
|
||||
backgroundTask = nil;
|
||||
});
|
||||
};
|
||||
|
||||
NSString *profilePictureURL = userProfile.avatarUrlPath;
|
||||
NSError *serializationError;
|
||||
|
@ -1261,15 +1199,49 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error);
|
|||
return;
|
||||
}
|
||||
|
||||
__block *downloadTask = [self.avatarHTTPManager downloadTaskWithRequest:request
|
||||
progress:^(NSProgress *_Nonnull downloadProgress) {
|
||||
OWSLogVerbose(
|
||||
@"Downloading avatar for %@ %f", userProfile.recipientId, downloadProgress.fractionCompleted);
|
||||
NSURLSession* session = [NSURLSession sharedSession];
|
||||
|
||||
NSURLSessionTask* downloadTask = [session downloadTaskWithRequest:request completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
|
||||
|
||||
@synchronized(self.currentAvatarDownloads)
|
||||
{
|
||||
[self.currentAvatarDownloads removeObject:userProfile.recipientId];
|
||||
}
|
||||
destination:^NSURL *_Nonnull(NSURL *_Nonnull targetPath, NSURLResponse *_Nonnull response) {
|
||||
return [NSURL fileURLWithPath:tempFilePath];
|
||||
|
||||
if (error) {
|
||||
OWSLogError(@"Dowload failed: %@", error);
|
||||
return;
|
||||
}
|
||||
completionHandler:completionHandler];
|
||||
|
||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||
NSURL *fileURL = [NSURL fileURLWithPath:filePath];
|
||||
NSError *moveError;
|
||||
if (![fileManager moveItemAtURL:location toURL:fileURL error:&moveError]) {
|
||||
OWSLogError(@"MoveItemAtURL for avatar failed: %@", moveError);
|
||||
return;
|
||||
}
|
||||
|
||||
UIImage *image = [UIImage imageWithContentsOfFile:[fileURL path]];
|
||||
if (image) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
|
||||
[self updateProfileAvatarCache:image filename:fileName];
|
||||
|
||||
OWSUserProfile *latestUserProfile =
|
||||
[OWSUserProfile getOrBuildUserProfileForRecipientId:userProfile.recipientId
|
||||
dbConnection:self.dbConnection];
|
||||
|
||||
[latestUserProfile updateWithAvatarFileName:fileName dbConnection:self.dbConnection completion:^{
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
postNotificationNameAsync:OWSContactsManagerSignalAccountsDidChangeNotification
|
||||
object:nil];
|
||||
}];
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}];
|
||||
|
||||
[downloadTask resume];
|
||||
});
|
||||
}
|
||||
|
@ -1334,8 +1306,11 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error);
|
|||
{
|
||||
OWSUserProfile *userProfile = [OWSUserProfile getOrBuildUserProfileForRecipientId:contactID transaction:transaction];
|
||||
NSString *oldProfilePictureURL = userProfile.avatarUrlPath;
|
||||
[userProfile updateWithProfileName:displayName avatarUrlPath:profilePictureURL avatarFileName:@"" transaction:transaction completion:nil];
|
||||
if (![oldProfilePictureURL isEqual:profilePictureURL]) {
|
||||
// Note: we keep using the old file name until we have the new one
|
||||
// (otherwise the profile picuture would disspear for a short time)
|
||||
NSString *oldAvatarFileName = userProfile.avatarFileName;
|
||||
[userProfile updateWithProfileName:displayName avatarUrlPath:profilePictureURL avatarFileName:oldAvatarFileName transaction:transaction completion:nil];
|
||||
if (profilePictureURL && ![oldProfilePictureURL isEqual:profilePictureURL]) {
|
||||
[self downloadAvatarForUserProfile:userProfile];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ typedef void (^OWSAvatarDrawBlock)(CGContextRef context);
|
|||
OWSAvatarBuilder *avatarBuilder;
|
||||
if ([thread isKindOfClass:[TSContactThread class]]) {
|
||||
TSContactThread *contactThread = (TSContactThread *)thread;
|
||||
return [LKIdenticon generateIconWithString:contactThread.contactIdentifier size:((CGFloat)diameter)];
|
||||
avatarBuilder = [[OWSContactAvatarBuilder alloc] initWithSignalId:contactThread.contactIdentifier colorName:contactThread.conversationColorName diameter:diameter];
|
||||
} else if ([thread isKindOfClass:[TSGroupThread class]]) {
|
||||
avatarBuilder = [[OWSGroupAvatarBuilder alloc] initWithThread:(TSGroupThread *)thread diameter:diameter];
|
||||
} else {
|
||||
|
|
|
@ -247,7 +247,7 @@ message DataMessage {
|
|||
// Loki: A custom message for our profile
|
||||
message LokiProfile {
|
||||
optional string displayName = 1;
|
||||
optional string profilePicture = 2;
|
||||
optional AttachmentPointer avatar = 2;
|
||||
}
|
||||
|
||||
optional string body = 1;
|
||||
|
|
|
@ -10,6 +10,7 @@ public final class LokiStorageAPI : LokiDotNetAPI {
|
|||
@objc public static let server = "https://file.lokinet.org"
|
||||
// #endif
|
||||
private static let deviceLinkType = "network.loki.messenger.devicemapping"
|
||||
private static let attachmentType = "net.app.core.oembed"
|
||||
|
||||
// MARK: Database
|
||||
override internal class var authTokenCollection: String { return "LokiStorageAuthTokenCollection" }
|
||||
|
|
|
@ -99,6 +99,9 @@ public final class LokiPublicChatAPI : LokiDotNetAPI {
|
|||
print("[Loki] Couldn't parse message for public chat channel with ID: \(channel) on server: \(server) from: \(message).")
|
||||
return nil
|
||||
}
|
||||
|
||||
let avatarUrl = value["avatar"] as? String ?? nil;
|
||||
|
||||
let displayName = user["name"] as? String ?? NSLocalizedString("Anonymous", comment: "")
|
||||
let lastMessageServerID = getLastMessageServerID(for: channel, on: server)
|
||||
if serverID > (lastMessageServerID ?? 0) { setLastMessageServerID(for: channel, on: server, to: serverID) }
|
||||
|
@ -125,7 +128,7 @@ public final class LokiPublicChatAPI : LokiDotNetAPI {
|
|||
}
|
||||
return LokiPublicChatMessage.Attachment(kind: kind, server: server, serverID: serverID, contentType: contentType, size: size, fileName: fileName, flags: flags, width: width, height: height, caption: caption, url: url, linkPreviewURL: linkPreviewURL, linkPreviewTitle: linkPreviewTitle)
|
||||
}
|
||||
let result = LokiPublicChatMessage(serverID: serverID, hexEncodedPublicKey: hexEncodedPublicKey, displayName: displayName, body: body, type: publicChatMessageType, timestamp: timestamp, quote: quote, attachments: attachments, signature: signature)
|
||||
let result = LokiPublicChatMessage(serverID: serverID, hexEncodedPublicKey: hexEncodedPublicKey, displayName: displayName, avatar: avatarUrl, body: body, type: publicChatMessageType, timestamp: timestamp, quote: quote, attachments: attachments, signature: signature)
|
||||
guard result.hasValidSignature() else {
|
||||
print("[Loki] Ignoring public chat message with invalid signature.")
|
||||
return nil
|
||||
|
@ -162,7 +165,7 @@ public final class LokiPublicChatAPI : LokiDotNetAPI {
|
|||
throw Error.parsingFailed
|
||||
}
|
||||
let timestamp = UInt64(date.timeIntervalSince1970) * 1000
|
||||
return LokiPublicChatMessage(serverID: serverID, hexEncodedPublicKey: userHexEncodedPublicKey, displayName: displayName, body: body, type: publicChatMessageType, timestamp: timestamp, quote: signedMessage.quote, attachments: signedMessage.attachments, signature: signedMessage.signature)
|
||||
return LokiPublicChatMessage(serverID: serverID, hexEncodedPublicKey: userHexEncodedPublicKey, displayName: displayName, avatar: signedMessage.avatar, body: body, type: publicChatMessageType, timestamp: timestamp, quote: signedMessage.quote, attachments: signedMessage.attachments, signature: signedMessage.signature)
|
||||
}
|
||||
}.recover(on: DispatchQueue.global()) { error -> Promise<LokiPublicChatMessage> in
|
||||
if let error = error as? NetworkManagerError, error.statusCode == 401 {
|
||||
|
|
|
@ -5,6 +5,7 @@ public final class LokiPublicChatMessage : NSObject {
|
|||
public let serverID: UInt64?
|
||||
public let hexEncodedPublicKey: String
|
||||
public let displayName: String
|
||||
public let avatar: String?
|
||||
public let body: String
|
||||
/// - Note: Expressed as milliseconds since 00:00:00 UTC on 1 January 1970.
|
||||
public let timestamp: UInt64
|
||||
|
@ -66,10 +67,11 @@ public final class LokiPublicChatMessage : NSObject {
|
|||
}
|
||||
|
||||
// MARK: Initialization
|
||||
public init(serverID: UInt64?, hexEncodedPublicKey: String, displayName: String, body: String, type: String, timestamp: UInt64, quote: Quote?, attachments: [Attachment], signature: Signature?) {
|
||||
public init(serverID: UInt64?, hexEncodedPublicKey: String, displayName: String, avatar: String?, body: String, type: String, timestamp: UInt64, quote: Quote?, attachments: [Attachment], signature: Signature?) {
|
||||
self.serverID = serverID
|
||||
self.hexEncodedPublicKey = hexEncodedPublicKey
|
||||
self.displayName = displayName
|
||||
self.avatar = avatar
|
||||
self.body = body
|
||||
self.type = type
|
||||
self.timestamp = timestamp
|
||||
|
@ -79,7 +81,7 @@ public final class LokiPublicChatMessage : NSObject {
|
|||
super.init()
|
||||
}
|
||||
|
||||
@objc public convenience init(hexEncodedPublicKey: String, displayName: String, body: String, type: String, timestamp: UInt64, quotedMessageTimestamp: UInt64, quoteeHexEncodedPublicKey: String?, quotedMessageBody: String?, quotedMessageServerID: UInt64, signatureData: Data?, signatureVersion: UInt64) {
|
||||
@objc public convenience init(hexEncodedPublicKey: String, displayName: String, avatar: String?, body: String, type: String, timestamp: UInt64, quotedMessageTimestamp: UInt64, quoteeHexEncodedPublicKey: String?, quotedMessageBody: String?, quotedMessageServerID: UInt64, signatureData: Data?, signatureVersion: UInt64) {
|
||||
let quote: Quote?
|
||||
if quotedMessageTimestamp != 0, let quoteeHexEncodedPublicKey = quoteeHexEncodedPublicKey, let quotedMessageBody = quotedMessageBody {
|
||||
let quotedMessageServerID = (quotedMessageServerID != 0) ? quotedMessageServerID : nil
|
||||
|
@ -93,7 +95,7 @@ public final class LokiPublicChatMessage : NSObject {
|
|||
} else {
|
||||
signature = nil
|
||||
}
|
||||
self.init(serverID: nil, hexEncodedPublicKey: hexEncodedPublicKey, displayName: displayName, body: body, type: type, timestamp: timestamp, quote: quote, attachments: [], signature: signature)
|
||||
self.init(serverID: nil, hexEncodedPublicKey: hexEncodedPublicKey, displayName: displayName, avatar: avatar, body: body, type: type, timestamp: timestamp, quote: quote, attachments: [], signature: signature)
|
||||
}
|
||||
|
||||
// MARK: Crypto
|
||||
|
@ -108,7 +110,7 @@ public final class LokiPublicChatMessage : NSObject {
|
|||
return nil
|
||||
}
|
||||
let signature = Signature(data: signatureData, version: signatureVersion)
|
||||
return LokiPublicChatMessage(serverID: serverID, hexEncodedPublicKey: hexEncodedPublicKey, displayName: displayName, body: body, type: type, timestamp: timestamp, quote: quote, attachments: attachments, signature: signature)
|
||||
return LokiPublicChatMessage(serverID: serverID, hexEncodedPublicKey: hexEncodedPublicKey, displayName: displayName, avatar: avatar, body: body, type: type, timestamp: timestamp, quote: quote, attachments: attachments, signature: signature)
|
||||
}
|
||||
|
||||
internal func hasValidSignature() -> Bool {
|
||||
|
@ -128,6 +130,11 @@ public final class LokiPublicChatMessage : NSObject {
|
|||
value["sig"] = signature.data.toHexString()
|
||||
value["sigver"] = signature.version
|
||||
}
|
||||
|
||||
if let avatar = avatar {
|
||||
value["avatar"] = avatar;
|
||||
}
|
||||
|
||||
let annotation: JSON = [ "type" : type, "value" : value ]
|
||||
let attachmentAnnotations: [JSON] = attachments.map { attachment in
|
||||
let type: String
|
||||
|
|
|
@ -120,6 +120,14 @@ public final class LokiPublicChatPoller : NSObject {
|
|||
signalLinkPreview.setImage(try! attachment.build())
|
||||
dataMessage.setPreview([ try! signalLinkPreview.build() ])
|
||||
}
|
||||
|
||||
let profile = SSKProtoDataMessageLokiProfile.builder()
|
||||
if let avatar = message.avatar {
|
||||
profile.setProfilePicture(avatar)
|
||||
profile.setDisplayName(message.displayName)
|
||||
dataMessage.setProfile(try! profile.build())
|
||||
}
|
||||
|
||||
dataMessage.setTimestamp(message.timestamp)
|
||||
dataMessage.setGroup(try! groupContext.build())
|
||||
if let quote = message.quote {
|
||||
|
|
|
@ -1335,6 +1335,14 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
[newMemberIds addObjectsFromArray:oldGroupThread.groupModel.groupMemberIds];
|
||||
}
|
||||
|
||||
NSString *hexEncodedPublicKey = ([LKDatabaseUtilities getMasterHexEncodedPublicKeyFor:envelope.source in:transaction] ?: envelope.source);
|
||||
TSContactThread *thread =
|
||||
[TSContactThread getOrCreateThreadWithContactId:hexEncodedPublicKey transaction:transaction];
|
||||
|
||||
NSString *profilePictureURL = dataMessage.profile.profilePicture;
|
||||
NSString *displayName = dataMessage.profile.displayName;
|
||||
[self.profileManager updateProfileForContactWithID:thread.contactIdentifier displayName:displayName profilePictureURL:profilePictureURL with:transaction];
|
||||
|
||||
switch (dataMessage.group.type) {
|
||||
case SSKProtoGroupContextTypeUpdate: {
|
||||
// Ensures that the thread exists but doesn't update it.
|
||||
|
@ -1536,8 +1544,9 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
if (rawProfilePictureURL != nil && rawProfilePictureURL.length > 0) {
|
||||
profilePictureURL = rawProfilePictureURL;
|
||||
}
|
||||
|
||||
[self.profileManager updateProfileForContactWithID:thread.contactIdentifier displayName:displayName profilePictureURL:profilePictureURL with:transaction];
|
||||
|
||||
|
||||
// Loki: Parse Loki specific properties if needed
|
||||
if (envelope.isPtpMessage) { incomingMessage.isP2P = YES; }
|
||||
|
||||
|
|
|
@ -1201,6 +1201,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
|
|||
NSString *userHexEncodedPublicKey = OWSIdentityManager.sharedManager.identityKeyPair.hexEncodedPublicKey;
|
||||
NSString *displayName = SSKEnvironment.shared.profileManager.localProfileName;
|
||||
if (displayName == nil) { displayName = @"Anonymous"; }
|
||||
NSString *avatarUrl = SSKEnvironment.shared.profileManager.profilePictureURL;
|
||||
TSQuotedMessage *quote = message.quotedMessage;
|
||||
uint64_t quoteID = quote.timestamp;
|
||||
NSString *quoteeHexEncodedPublicKey = quote.authorId;
|
||||
|
@ -1211,7 +1212,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
|
|||
}];
|
||||
}
|
||||
NSString *body = (message.body != nil && message.body.length > 0) ? message.body : [NSString stringWithFormat:@"%@", @(message.timestamp)]; // Workaround for the fact that the back-end doesn't accept messages without a body
|
||||
LKGroupMessage *groupMessage = [[LKGroupMessage alloc] initWithHexEncodedPublicKey:userHexEncodedPublicKey displayName:displayName body:body type:LKPublicChatAPI.publicChatMessageType
|
||||
LKGroupMessage *groupMessage = [[LKGroupMessage alloc] initWithHexEncodedPublicKey:userHexEncodedPublicKey displayName:displayName avatar:avatarUrl body:body type:LKPublicChatAPI.publicChatMessageType
|
||||
timestamp:message.timestamp quotedMessageTimestamp:quoteID quoteeHexEncodedPublicKey:quoteeHexEncodedPublicKey quotedMessageBody:quote.body quotedMessageServerID:quotedMessageServerID signatureData:nil signatureVersion:0];
|
||||
OWSLinkPreview *linkPreview = message.linkPreview;
|
||||
if (linkPreview != nil) {
|
||||
|
|
Loading…
Reference in New Issue