Signal-iOS/SignalServiceKit/src/Contacts/SignalAccount.m
Max Radermacher 60888c85c0
Improve storage service system contact handling
- If we’re a primary device and the name is wrong, update it.
- If we’re a linked device and the name hasn’t changed, don’t update it.
2023-02-01 16:55:39 -08:00

312 lines
10 KiB
Objective-C

//
// Copyright 2017 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
#import "SignalAccount.h"
#import "Contact.h"
#import "ContactsManagerProtocol.h"
#import "NSData+Image.h"
#import "SSKEnvironment.h"
#import "SignalRecipient.h"
#import "UIImage+OWS.h"
#import <SignalCoreKit/Cryptography.h>
#import <SignalCoreKit/NSString+OWS.h>
#import <SignalServiceKit/SignalServiceKit-Swift.h>
NS_ASSUME_NONNULL_BEGIN
NSUInteger const SignalAccountSchemaVersion = 1;
/* We need to query the system preferences to achieve the behaviour at Messages on iOS.
If we ask NSPersonNameComponentsFormatter for "short" we will get the nickname if it exists but if it _doesn't_ exit we'll just get the first name. (Or the name pattern the user has selected in their system preferences. This means that in the conversation list in the left, where Messages displays the full name of a contact if they don't have a nickname, we'd just display the Short Name. To match the behaviour we ask UserDefaults for the value of this key and prefer to use the nickname, if available, in the conversation list.
*/
static NSString *kSignalPreferNicknamesPreference = @"NSPersonNameDefaultShouldPreferNicknamesPreference";
@interface SignalAccount ()
@property (nonatomic, readonly) NSUInteger accountSchemaVersion;
@property (nonatomic) NSString *multipleAccountLabelText;
@property (nonatomic, nullable) Contact *contact;
@end
#pragma mark -
@implementation SignalAccount
+ (TSFTSIndexMode)FTSIndexMode
{
return TSFTSIndexModeAlways;
}
- (instancetype)initWithSignalRecipient:(SignalRecipient *)signalRecipient
contact:(nullable Contact *)contact
contactAvatarHash:(nullable NSData *)contactAvatarHash
multipleAccountLabelText:(nullable NSString *)multipleAccountLabelText
{
return [self initWithSignalServiceAddress:signalRecipient.address
contact:contact
contactAvatarHash:contactAvatarHash
multipleAccountLabelText:multipleAccountLabelText];
}
- (instancetype)initWithSignalServiceAddress:(SignalServiceAddress *)serviceAddress
{
return [self initWithSignalServiceAddress:serviceAddress contact:nil multipleAccountLabelText:nil];
}
- (instancetype)initWithSignalServiceAddress:(SignalServiceAddress *)serviceAddress
contact:(nullable Contact *)contact
multipleAccountLabelText:(nullable NSString *)multipleAccountLabelText
{
return [self initWithSignalServiceAddress:serviceAddress
contact:contact
contactAvatarHash:nil
multipleAccountLabelText:multipleAccountLabelText];
}
- (instancetype)initWithSignalServiceAddress:(SignalServiceAddress *)serviceAddress
contact:(nullable Contact *)contact
contactAvatarHash:(nullable NSData *)contactAvatarHash
multipleAccountLabelText:(nullable NSString *)multipleAccountLabelText
{
OWSAssertDebug(serviceAddress.isValid);
if (self = [super init]) {
_recipientUUID = serviceAddress.uuidString;
_recipientPhoneNumber = serviceAddress.phoneNumber;
_accountSchemaVersion = SignalAccountSchemaVersion;
_contact = contact;
_contactAvatarHash = contactAvatarHash;
_multipleAccountLabelText = multipleAccountLabelText;
}
return self;
}
- (nullable instancetype)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (!self) {
return self;
}
// Migrating from an everyone has a phone number world to a
// world in which we have UUIDs
if (_accountSchemaVersion == 0) {
// Rename recipientId to recipientPhoneNumber
_recipientPhoneNumber = [coder decodeObjectForKey:@"recipientId"];
OWSAssert(_recipientPhoneNumber != nil);
}
_accountSchemaVersion = SignalAccountSchemaVersion;
return self;
}
- (instancetype)initWithContact:(nullable Contact *)contact
contactAvatarHash:(nullable NSData *)contactAvatarHash
multipleAccountLabelText:(NSString *)multipleAccountLabelText
recipientPhoneNumber:(nullable NSString *)recipientPhoneNumber
recipientUUID:(nullable NSString *)recipientUUID
{
self = [super init];
if (!self) {
return self;
}
OWSAssertDebug(recipientPhoneNumber != nil || recipientUUID != nil);
_contact = contact;
_contactAvatarHash = contactAvatarHash;
_multipleAccountLabelText = multipleAccountLabelText;
_recipientPhoneNumber = recipientPhoneNumber;
_recipientUUID = recipientUUID;
_accountSchemaVersion = SignalAccountSchemaVersion;
return self;
}
// --- CODE GENERATION MARKER
// This snippet is generated by /Scripts/sds_codegen/sds_generate.py. Do not manually edit it, instead run `sds_codegen.sh`.
// clang-format off
- (instancetype)initWithGrdbId:(int64_t)grdbId
uniqueId:(NSString *)uniqueId
contact:(nullable Contact *)contact
contactAvatarHash:(nullable NSData *)contactAvatarHash
contactAvatarJpegDataObsolete:(nullable NSData *)contactAvatarJpegDataObsolete
multipleAccountLabelText:(NSString *)multipleAccountLabelText
recipientPhoneNumber:(nullable NSString *)recipientPhoneNumber
recipientUUID:(nullable NSString *)recipientUUID
{
self = [super initWithGrdbId:grdbId
uniqueId:uniqueId];
if (!self) {
return self;
}
_contact = contact;
_contactAvatarHash = contactAvatarHash;
_contactAvatarJpegDataObsolete = contactAvatarJpegDataObsolete;
_multipleAccountLabelText = multipleAccountLabelText;
_recipientPhoneNumber = recipientPhoneNumber;
_recipientUUID = recipientUUID;
[self sdsFinalizeSignalAccount];
return self;
}
// clang-format on
// --- CODE GENERATION MARKER
- (void)sdsFinalizeSignalAccount
{
_contactAvatarJpegDataObsolete = nil;
}
- (BOOL)shouldUseNicknames
{
return [[NSUserDefaults standardUserDefaults] boolForKey:kSignalPreferNicknamesPreference];
}
- (nullable NSPersonNameComponents *)contactPersonNameComponents
{
NSPersonNameComponents *nameComponents = [NSPersonNameComponents new];
// Check if we have a first name or last name, if we do we can use them directly.
if (self.contactFirstName.length > 0 || self.contactLastName.length > 0) {
nameComponents.givenName = self.contactFirstName;
nameComponents.familyName = self.contactLastName;
} else if (self.contactFullName.length > 0) {
// Originally we had a heuristic to try and guess which was the given and family name. It
// wasn't always correct. We're probably better off just sticking the entire name in the
// given name.
nameComponents.givenName = self.contactFullName;
}
nameComponents.nickname = self.contactNicknameIfAvailable;
if (nameComponents.givenName.length < 1 && nameComponents.familyName.length < 1
&& nameComponents.nickname.length < 1) {
return nil;
}
return nameComponents;
}
- (nullable NSString *)contactPreferredDisplayName
{
NSPersonNameComponents *_Nullable components = self.contactPersonNameComponents;
if (components == nil) {
return nil;
}
NSString *result = nil;
// If we have a nickname check what the user prefers.
if (components.nickname.length > 0 && self.shouldUseNicknames) {
result = components.nickname;
} else if (components.givenName.length > 0 || components.familyName.length > 0) {
return [[OWSFormat formatNameComponents:components] filterStringForDisplay];
} else {
// The components might have a nickname but !shouldUseNicknames.
OWSLogWarn(@"Invalid name components.");
return nil;
}
result = result.filterStringForDisplay;
if (result.length > 0) {
return result;
} else {
return nil;
}
}
- (nullable NSString *)contactNicknameIfAvailable
{
if (!self.shouldUseNicknames) {
return nil;
}
NSString *nickname = self.contact.nickname;
if (nickname.length > 0)
{
return nickname;
}
else
{
return nil;
}
}
- (nullable NSString *)contactFullName
{
return self.contact.fullName.filterStringForDisplay;
}
- (nullable NSString *)contactFirstName
{
return self.contact.firstName.filterStringForDisplay;
}
- (nullable NSString *)contactLastName
{
return self.contact.lastName.filterStringForDisplay;
}
- (NSString *)multipleAccountLabelText
{
NSString *_Nullable result = _multipleAccountLabelText.filterStringForDisplay;
return result != nil ? result : @"";
}
- (SignalServiceAddress *)recipientAddress
{
return [[SignalServiceAddress alloc] initWithUuidString:self.recipientUUID phoneNumber:self.recipientPhoneNumber];
}
- (void)anyDidInsertWithTransaction:(SDSAnyWriteTransaction *)transaction
{
[super anyDidInsertWithTransaction:transaction];
[self.modelReadCaches.signalAccountReadCache didInsertOrUpdateSignalAccount:self transaction:transaction];
}
- (void)anyDidUpdateWithTransaction:(SDSAnyWriteTransaction *)transaction
{
[super anyDidUpdateWithTransaction:transaction];
[self.modelReadCaches.signalAccountReadCache didInsertOrUpdateSignalAccount:self transaction:transaction];
}
- (void)anyDidRemoveWithTransaction:(SDSAnyWriteTransaction *)transaction
{
[super anyDidRemoveWithTransaction:transaction];
[self.modelReadCaches.signalAccountReadCache didRemoveSignalAccount:self transaction:transaction];
}
- (void)updateWithContact:(nullable Contact *)contact transaction:(SDSAnyWriteTransaction *)transaction
{
[self anyUpdateWithTransaction:transaction
block:^(SignalAccount *account) {
account.contact = contact;
}];
}
#if TESTABLE_BUILD
- (void)replaceContactForTests:(nullable Contact *)contact
{
self.contact = contact;
}
#endif
@end
NS_ASSUME_NONNULL_END