Ditch unused Signal code

This commit is contained in:
nielsandriesse 2020-11-16 10:34:47 +11:00 committed by Niels Andriesse
parent 14c87139c6
commit f36f447bec
574 changed files with 1866 additions and 40001 deletions

View File

@ -50,11 +50,11 @@ final class ConversationTitleView : UIView {
updateSubtitleForCurrentStatus()
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self, selector: #selector(handleProfileChangedNotification(_:)), name: NSNotification.Name(rawValue: kNSNotificationName_OtherUsersProfileDidChange), object: nil)
notificationCenter.addObserver(self, selector: #selector(handleCalculatingPoWNotification(_:)), name: .calculatingPoW, object: nil)
notificationCenter.addObserver(self, selector: #selector(handleRoutingNotification(_:)), name: .routing, object: nil)
notificationCenter.addObserver(self, selector: #selector(handleCalculatingMessagePoWNotification(_:)), name: .calculatingMessagePoW, object: nil)
notificationCenter.addObserver(self, selector: #selector(handleEncryptingMessageNotification(_:)), name: .encryptingMessage, object: nil)
notificationCenter.addObserver(self, selector: #selector(handleMessageSendingNotification(_:)), name: .messageSending, object: nil)
notificationCenter.addObserver(self, selector: #selector(handleMessageSentNotification(_:)), name: .messageSent, object: nil)
notificationCenter.addObserver(self, selector: #selector(handleMessageFailedNotification(_:)), name: .messageFailed, object: nil)
notificationCenter.addObserver(self, selector: #selector(handleMessageSendingFailedNotification(_:)), name: .messageSendingFailed, object: nil)
}
override init(frame: CGRect) {
@ -112,12 +112,12 @@ final class ConversationTitleView : UIView {
updateProfilePicture()
}
@objc private func handleCalculatingPoWNotification(_ notification: Notification) {
@objc private func handleCalculatingMessagePoWNotification(_ notification: Notification) {
guard let timestamp = notification.object as? NSNumber else { return }
setStatusIfNeeded(to: .calculatingPoW, forMessageWithTimestamp: timestamp)
}
@objc private func handleRoutingNotification(_ notification: Notification) {
@objc private func handleEncryptingMessageNotification(_ notification: Notification) {
guard let timestamp = notification.object as? NSNumber else { return }
setStatusIfNeeded(to: .routing, forMessageWithTimestamp: timestamp)
}
@ -136,7 +136,7 @@ final class ConversationTitleView : UIView {
}
}
@objc private func handleMessageFailedNotification(_ notification: Notification) {
@objc private func handleMessageSendingFailedNotification(_ notification: Notification) {
guard let timestamp = notification.object as? NSNumber else { return }
clearStatusIfNeededForMessageWithTimestamp(timestamp)
}
@ -149,14 +149,7 @@ final class ConversationTitleView : UIView {
uncheckedTargetInteraction = interaction
}
guard let targetInteraction = uncheckedTargetInteraction, targetInteraction.interactionType() == .outgoingMessage,
status.rawValue > (currentStatus?.rawValue ?? 0), let hexEncodedPublicKey = targetInteraction.thread.contactIdentifier() else { return }
var masterHexEncodedPublicKey: String!
let storage = OWSPrimaryStorage.shared()
storage.dbReadConnection.read { transaction in
masterHexEncodedPublicKey = storage.getMasterHexEncodedPublicKey(for: hexEncodedPublicKey, in: transaction) ?? hexEncodedPublicKey
}
let isSlaveDevice = masterHexEncodedPublicKey != hexEncodedPublicKey
guard !isSlaveDevice else { return }
status.rawValue > (currentStatus?.rawValue ?? 0) else { return }
currentStatus = status
}
@ -184,7 +177,7 @@ final class ConversationTitleView : UIView {
dateFormatter.timeStyle = .medium
dateFormatter.dateStyle = .medium
subtitle.append(NSAttributedString(string: "Muted until " + dateFormatter.string(from: muteEndDate)))
} else if let thread = self.thread as? TSGroupThread, !thread.isRSSFeed {
} else if let thread = self.thread as? TSGroupThread {
let storage = OWSPrimaryStorage.shared()
var userCount: Int?
if thread.groupModel.groupType == .closedGroup {

View File

@ -16,10 +16,10 @@
#import "ConversationViewCell.h"
#import "ConversationViewItem.h"
#import "DateUtil.h"
#import "FingerprintViewController.h"
#import "MediaDetailViewController.h"
#import "NotificationSettingsViewController.h"
#import "OWSAddToContactViewController.h"
#import "OWSAnyTouchGestureRecognizer.h"
#import "OWSAudioPlayer.h"
#import "OWSBackup.h"
@ -35,13 +35,12 @@
#import "OWSQuotedMessageView.h"
#import "OWSSessionResetJobRecord.h"
#import "OWSWindowManager.h"
#import "PinEntryView.h"
#import "PrivacySettingsTableViewController.h"
#import "RemoteVideoView.h"
#import "OWSQRCodeScanningViewController.h"
#import "SignalApp.h"
#import "UIViewController+Permissions.h"
#import "ViewControllerUtils.h"
#import <SessionProtocolKit/NSData+keyVersionByte.h>
#import <PureLayout/PureLayout.h>
#import <Reachability/Reachability.h>
@ -55,52 +54,36 @@
#import <SignalUtilitiesKit/ContactTableViewCell.h>
#import <SignalUtilitiesKit/Environment.h>
#import <SignalUtilitiesKit/OWSAudioPlayer.h>
#import <SignalUtilitiesKit/OWSContactAvatarBuilder.h>
#import <SignalUtilitiesKit/OWSContactsManager.h>
#import <SignalUtilitiesKit/OWSFormat.h>
#import <SignalUtilitiesKit/OWSPreferences.h>
#import <SignalUtilitiesKit/OWSProfileManager.h>
#import <SignalUtilitiesKit/OWSQuotedReplyModel.h>
#import <SignalUtilitiesKit/OWSSounds.h>
#import <SignalUtilitiesKit/OWSViewController.h>
#import <SignalUtilitiesKit/ThreadUtil.h>
#import <SignalUtilitiesKit/UIColor+OWS.h>
#import <SignalUtilitiesKit/UIFont+OWS.h>
#import <SignalUtilitiesKit/UIUtil.h>
#import <SignalUtilitiesKit/UIView+OWS.h>
#import <SignalUtilitiesKit/UIViewController+OWS.h>
#import <SignalUtilitiesKit/AppVersion.h>
#import <SignalUtilitiesKit/Contact.h>
#import <SignalUtilitiesKit/ContactsUpdater.h>
#import <SignalUtilitiesKit/DataSource.h>
#import <SignalUtilitiesKit/MIMETypeUtil.h>
#import <SignalUtilitiesKit/NSData+Image.h>
#import <SignalUtilitiesKit/NSNotificationCenter+OWS.h>
#import <SignalUtilitiesKit/NSString+SSK.h>
#import <SignalUtilitiesKit/NSTimer+OWS.h>
#import <SignalUtilitiesKit/OWSAnalytics.h>
#import <SignalUtilitiesKit/OWSAnalyticsEvents.h>
#import <SignalUtilitiesKit/OWSBackgroundTask.h>
#import <SignalUtilitiesKit/OWSCallMessageHandler.h>
#import <SignalUtilitiesKit/OWSContactsOutputStream.h>
#import <SignalUtilitiesKit/OWSDispatch.h>
#import <SignalUtilitiesKit/OWSEndSessionMessage.h>
#import <SignalUtilitiesKit/LKDeviceLinkMessage.h>
#import <SignalUtilitiesKit/OWSError.h>
#import <SignalUtilitiesKit/OWSFileSystem.h>
#import <SignalUtilitiesKit/OWSIdentityManager.h>
#import <SignalUtilitiesKit/OWSMediaGalleryFinder.h>
#import <SignalUtilitiesKit/OWSMessageManager.h>
#import <SignalUtilitiesKit/OWSMessageReceiver.h>
#import <SignalUtilitiesKit/OWSMessageSender.h>
#import <SignalUtilitiesKit/OWSOutgoingCallMessage.h>
#import <SignalUtilitiesKit/OWSPrimaryStorage+Calling.h>
#import <SignalUtilitiesKit/OWSPrimaryStorage+SessionStore.h>
#import <SignalUtilitiesKit/OWSProfileKeyMessage.h>
#import <SignalUtilitiesKit/OWSRecipientIdentity.h>
#import <SignalUtilitiesKit/OWSRequestFactory.h>
#import <SignalUtilitiesKit/OWSSignalService.h>
#import <SignalUtilitiesKit/PhoneNumber.h>
#import <SignalUtilitiesKit/SignalAccount.h>
#import <SignalUtilitiesKit/SignalRecipient.h>
#import <SignalUtilitiesKit/TSAccountManager.h>
@ -113,10 +96,10 @@
#import <SignalUtilitiesKit/TSGroupThread.h>
#import <SignalUtilitiesKit/TSIncomingMessage.h>
#import <SignalUtilitiesKit/TSInfoMessage.h>
#import <SignalUtilitiesKit/TSNetworkManager.h>
#import <SignalUtilitiesKit/TSOutgoingMessage.h>
#import <SignalUtilitiesKit/TSPreKeyManager.h>
#import <SignalUtilitiesKit/TSSocketManager.h>
#import <SignalUtilitiesKit/TSThread.h>
#import <SignalUtilitiesKit/LKGroupUtilities.h>
#import <SignalUtilitiesKit/UIImage+OWS.h>

View File

@ -18,7 +18,6 @@
#import <SignalCoreKit/NSObject+OWS.h>
#import <SignalCoreKit/OWSAsserts.h>
#import <SignalUtilitiesKit/SSKAsserts.h>
#import <SignalUtilitiesKit/OWSAnalytics.h>
#import <SignalUtilitiesKit/NSArray+Functional.h>
#import <SignalUtilitiesKit/NSSet+Functional.h>
#import <SignalUtilitiesKit/NSObject+Casting.h>

View File

@ -19,10 +19,6 @@ public class AccountManager: NSObject {
return OWSProfileManager.shared()
}
private var networkManager: TSNetworkManager {
return SSKEnvironment.shared.networkManager
}
private var preferences: OWSPreferences {
return Environment.shared.preferences
}
@ -119,28 +115,4 @@ public class AccountManager: NSObject {
let anyPromise = tsAccountManager.setIsManualMessageFetchEnabled(true)
return Promise(anyPromise).asVoid()
}
// MARK: Turn Server
func getTurnServerInfo() -> Promise<TurnServerInfo> {
return Promise { resolver in
self.networkManager.makeRequest(OWSRequestFactory.turnServerInfoRequest(),
success: { (_: URLSessionDataTask, responseObject: Any?) in
guard responseObject != nil else {
return resolver.reject(OWSErrorMakeUnableToProcessServerResponseError())
}
if let responseDictionary = responseObject as? [String: AnyObject] {
if let turnServerInfo = TurnServerInfo(attributes: responseDictionary) {
return resolver.fulfill(turnServerInfo)
}
Logger.error("unexpected server response:\(responseDictionary)")
}
return resolver.reject(OWSErrorMakeUnableToProcessServerResponseError())
},
failure: { (_: URLSessionDataTask, error: Error) in
return resolver.reject(error)
})
}
}
}

View File

@ -1,145 +0,0 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
import Foundation
import UIKit
import ContactsUI
class AddContactShareToExistingContactViewController: ContactsPicker, ContactsPickerDelegate, CNContactViewControllerDelegate {
// TODO - there are some hard coded assumptions in this VC that assume we are *pushed* onto a
// navigation controller. That seems fine for now, but if we need to be presented as a modal,
// or need to notify our presenter about our dismisall or other contact actions, a delegate
// would be helpful. It seems like this would require some broad changes to the ContactShareViewHelper,
// so I've left it as is for now, since it happens to work.
// weak var addToExistingContactDelegate: AddContactShareToExistingContactViewControllerDelegate?
let contactShare: ContactShareViewModel
required init(contactShare: ContactShareViewModel) {
self.contactShare = contactShare
super.init(allowsMultipleSelection: false, subtitleCellType: .none)
self.contactsPickerDelegate = self
}
required public init?(coder aDecoder: NSCoder) {
notImplemented()
}
@objc required public init(allowsMultipleSelection: Bool, subtitleCellType: SubtitleCellValue) {
notImplemented()
}
// MARK: - ContactsPickerDelegate
func contactsPicker(_: ContactsPicker, contactFetchDidFail error: NSError) {
owsFailDebug("with error: \(error)")
guard let navigationController = self.navigationController else {
owsFailDebug("navigationController was unexpectedly nil")
return
}
navigationController.popViewController(animated: true)
}
func contactsPickerDidCancel(_: ContactsPicker) {
Logger.debug("")
guard let navigationController = self.navigationController else {
owsFailDebug("navigationController was unexpectedly nil")
return
}
navigationController.popViewController(animated: true)
}
func contactsPicker(_: ContactsPicker, didSelectContact oldContact: Contact) {
Logger.debug("")
let contactsManager = Environment.shared.contactsManager
guard let oldCNContact = contactsManager?.cnContact(withId: oldContact.cnContactId) else {
owsFailDebug("could not load old CNContact.")
return
}
guard let newCNContact = OWSContacts.systemContact(for: self.contactShare.dbRecord, imageData: self.contactShare.avatarImageData) else {
owsFailDebug("could not load new CNContact.")
return
}
merge(oldCNContact: oldCNContact, newCNContact: newCNContact)
}
func merge(oldCNContact: CNContact, newCNContact: CNContact) {
Logger.debug("")
let mergedCNContact: CNContact = Contact.merge(cnContact: oldCNContact, newCNContact: newCNContact)
// Not actually a "new" contact, but this brings up the edit form rather than the "Read" form
// saving our users a tap in some cases when we already know they want to edit.
let contactViewController: CNContactViewController = CNContactViewController(forNewContact: mergedCNContact)
// Default title is "New Contact". We could give a more descriptive title, but anything
// seems redundant - the context is sufficiently clear.
contactViewController.title = ""
contactViewController.allowsActions = false
contactViewController.allowsEditing = true
contactViewController.delegate = self
let modal = OWSNavigationController(rootViewController: contactViewController)
self.present(modal, animated: true)
}
func contactsPicker(_: ContactsPicker, didSelectMultipleContacts contacts: [Contact]) {
Logger.debug("")
owsFailDebug("only supports single contact select")
guard let navigationController = self.navigationController else {
owsFailDebug("navigationController was unexpectedly nil")
return
}
navigationController.popViewController(animated: true)
}
func contactsPicker(_: ContactsPicker, shouldSelectContact contact: Contact) -> Bool {
return true
}
// MARK: - CNContactViewControllerDelegate
public func contactViewController(_ viewController: CNContactViewController, didCompleteWith contact: CNContact?) {
Logger.debug("")
guard let navigationController = self.navigationController else {
owsFailDebug("navigationController was unexpectedly nil")
return
}
// TODO this is weird - ideally we'd do something like
// self.delegate?.didFinishAddingContact
// and the delegate, which knows about our presentation context could do the right thing.
//
// As it is, we happen to always be *pushing* this view controller onto a navcontroller, so the
// following works in all current cases.
//
// If we ever wanted to do something different, like present this in a modal, we'd have to rethink.
// We want to pop *this* view *and* the still presented CNContactViewController in a single animation.
// Note this happens for *cancel* and for *done*. Unfortunately, I don't know of a way to detect the difference
// between the two, since both just call this method.
guard let myIndex = navigationController.viewControllers.firstIndex(of: self) else {
owsFailDebug("myIndex was unexpectedly nil")
navigationController.popViewController(animated: true)
navigationController.popViewController(animated: true)
return
}
let previousViewControllerIndex = navigationController.viewControllers.index(before: myIndex)
let previousViewController = navigationController.viewControllers[previousViewControllerIndex]
self.dismiss(animated: false) {
navigationController.popToViewController(previousViewController, animated: true)
}
}
}

View File

@ -4,8 +4,8 @@
#import "AddToBlockListViewController.h"
#import "BlockListUIUtils.h"
#import "ContactsViewHelper.h"
#import <SignalUtilitiesKit/OWSContactsManager.h>
#import <SignalUtilitiesKit/SSKEnvironment.h>
#import <SignalUtilitiesKit/OWSBlockingManager.h>
#import <SignalUtilitiesKit/SignalAccount.h>
NS_ASSUME_NONNULL_BEGIN
@ -51,8 +51,7 @@ NS_ASSUME_NONNULL_BEGIN
__weak AddToBlockListViewController *weakSelf = self;
[BlockListUIUtils showBlockPhoneNumberActionSheet:phoneNumber
fromViewController:self
blockingManager:self.contactsViewHelper.blockingManager
contactsManager:self.contactsViewHelper.contactsManager
blockingManager:SSKEnvironment.shared.blockingManager
completionBlock:^(BOOL isBlocked) {
if (isBlocked) {
[weakSelf.navigationController popViewControllerAnimated:YES];
@ -65,7 +64,7 @@ NS_ASSUME_NONNULL_BEGIN
OWSAssertDebug(signalAccount);
ContactsViewHelper *helper = self.contactsViewHelper;
return ![helper isRecipientIdBlocked:signalAccount.recipientId];
return ![SSKEnvironment.shared.blockingManager isRecipientIdBlocked:signalAccount.recipientId];
}
- (void)signalAccountWasSelected:(SignalAccount *)signalAccount
@ -73,15 +72,13 @@ NS_ASSUME_NONNULL_BEGIN
OWSAssertDebug(signalAccount);
__weak AddToBlockListViewController *weakSelf = self;
ContactsViewHelper *helper = self.contactsViewHelper;
if ([helper isRecipientIdBlocked:signalAccount.recipientId]) {
if ([SSKEnvironment.shared.blockingManager isRecipientIdBlocked:signalAccount.recipientId]) {
OWSFailDebug(@"Cannot add already blocked user to block list.");
return;
}
[BlockListUIUtils showBlockSignalAccountActionSheet:signalAccount
fromViewController:self
blockingManager:helper.blockingManager
contactsManager:helper.contactsManager
blockingManager:SSKEnvironment.shared.blockingManager
completionBlock:^(BOOL isBlocked) {
if (isBlocked) {
[weakSelf.navigationController popViewControllerAnimated:YES];

View File

@ -4,9 +4,9 @@
#import "AddToGroupViewController.h"
#import "BlockListUIUtils.h"
#import "ContactsViewHelper.h"
#import "Session-Swift.h"
#import <SignalUtilitiesKit/OWSContactsManager.h>
#import <SignalUtilitiesKit/SignalAccount.h>
NS_ASSUME_NONNULL_BEGIN
@ -52,12 +52,10 @@ NS_ASSUME_NONNULL_BEGIN
__weak AddToGroupViewController *weakSelf = self;
ContactsViewHelper *helper = self.contactsViewHelper;
if ([helper isRecipientIdBlocked:phoneNumber]) {
if ([SSKEnvironment.shared.blockingManager isRecipientIdBlocked:phoneNumber]) {
[BlockListUIUtils showUnblockPhoneNumberActionSheet:phoneNumber
fromViewController:self
blockingManager:helper.blockingManager
contactsManager:helper.contactsManager
blockingManager:SSKEnvironment.shared.blockingManager
completionBlock:^(BOOL isBlocked) {
if (!isBlocked) {
[weakSelf addToGroup:phoneNumber];
@ -66,22 +64,6 @@ NS_ASSUME_NONNULL_BEGIN
return;
}
BOOL didShowSNAlert = [SafetyNumberConfirmationAlert
presentAlertIfNecessaryWithRecipientId:phoneNumber
confirmationText:
NSLocalizedString(@"SAFETY_NUMBER_CHANGED_CONFIRM_ADD_TO_GROUP_ACTION",
@"button title to confirm adding a recipient to a group when their safety "
@"number has recently changed")
contactsManager:helper.contactsManager
completion:^(BOOL didConfirmIdentity) {
if (didConfirmIdentity) {
[weakSelf addToGroup:phoneNumber];
}
}];
if (didShowSNAlert) {
return;
}
[self addToGroup:phoneNumber];
}
@ -97,17 +79,15 @@ NS_ASSUME_NONNULL_BEGIN
OWSAssertDebug(signalAccount);
__weak AddToGroupViewController *weakSelf = self;
ContactsViewHelper *helper = self.contactsViewHelper;
if ([self.addToGroupDelegate isRecipientGroupMember:signalAccount.recipientId]) {
OWSFailDebug(@"Cannot add user to group member if already a member.");
return;
}
if ([helper isRecipientIdBlocked:signalAccount.recipientId]) {
if ([SSKEnvironment.shared.blockingManager isRecipientIdBlocked:signalAccount.recipientId]) {
[BlockListUIUtils showUnblockSignalAccountActionSheet:signalAccount
fromViewController:self
blockingManager:helper.blockingManager
contactsManager:helper.contactsManager
blockingManager:SSKEnvironment.shared.blockingManager
completionBlock:^(BOOL isBlocked) {
if (!isBlocked) {
[weakSelf addToGroup:signalAccount.recipientId];
@ -116,22 +96,6 @@ NS_ASSUME_NONNULL_BEGIN
return;
}
BOOL didShowSNAlert = [SafetyNumberConfirmationAlert
presentAlertIfNecessaryWithRecipientId:signalAccount.recipientId
confirmationText:
NSLocalizedString(@"SAFETY_NUMBER_CHANGED_CONFIRM_ADD_TO_GROUP_ACTION",
@"button title to confirm adding a recipient to a group when their safety "
@"number has recently changed")
contactsManager:helper.contactsManager
completion:^(BOOL didConfirmIdentity) {
if (didConfirmIdentity) {
[weakSelf addToGroup:signalAccount.recipientId];
}
}];
if (didShowSNAlert) {
return;
}
[self addToGroup:signalAccount.recipientId];
}

View File

@ -1,9 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "OWSTableViewController.h"
@interface AdvancedSettingsTableViewController : OWSTableViewController
@end

View File

@ -1,312 +0,0 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
#import "AdvancedSettingsTableViewController.h"
#import "DebugLogger.h"
#import "DomainFrontingCountryViewController.h"
#import "OWSCountryMetadata.h"
#import "Pastelog.h"
#import "Session-Swift.h"
#import "TSAccountManager.h"
#import <PromiseKit/AnyPromise.h>
#import <Reachability/Reachability.h>
#import <SignalUtilitiesKit/Environment.h>
#import <SignalUtilitiesKit/OWSPreferences.h>
#import <SignalUtilitiesKit/OWSSignalService.h>
NS_ASSUME_NONNULL_BEGIN
@interface AdvancedSettingsTableViewController ()
@property (nonatomic) Reachability *reachability;
@end
#pragma mark -
@implementation AdvancedSettingsTableViewController
- (void)loadView
{
[super loadView];
self.title = NSLocalizedString(@"SETTINGS_ADVANCED_TITLE", @"");
self.reachability = [Reachability reachabilityForInternetConnection];
[self observeNotifications];
[self updateTableContents];
}
- (void)observeNotifications
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(socketStateDidChange)
name:kNSNotification_OWSWebSocketStateDidChange
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(reachabilityChanged)
name:kReachabilityChangedNotification
object:nil];
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)socketStateDidChange
{
OWSAssertIsOnMainThread();
[self updateTableContents];
}
- (void)reachabilityChanged
{
OWSAssertIsOnMainThread();
[self updateTableContents];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self updateTableContents];
}
#pragma mark - Table Contents
- (void)updateTableContents
{
OWSTableContents *contents = [OWSTableContents new];
__weak AdvancedSettingsTableViewController *weakSelf = self;
OWSTableSection *loggingSection = [OWSTableSection new];
loggingSection.headerTitle = NSLocalizedString(@"LOGGING_SECTION", nil);
[loggingSection addItem:[OWSTableItem switchItemWithText:NSLocalizedString(@"SETTINGS_ADVANCED_DEBUGLOG", @"")
accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME(self, @"enable_debug_log")
isOnBlock:^{
return [OWSPreferences isLoggingEnabled];
}
isEnabledBlock:^{
return YES;
}
target:weakSelf
selector:@selector(didToggleEnableLogSwitch:)]];
if ([OWSPreferences isLoggingEnabled]) {
[loggingSection
addItem:[OWSTableItem actionItemWithText:NSLocalizedString(@"SETTINGS_ADVANCED_SUBMIT_DEBUGLOG", @"")
accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME(self, @"submit_debug_log")
actionBlock:^{
OWSLogInfo(@"Submitting debug logs");
[DDLog flushLog];
[Pastelog submitLogs];
}]];
}
[contents addSection:loggingSection];
OWSTableSection *pushNotificationsSection = [OWSTableSection new];
pushNotificationsSection.headerTitle
= NSLocalizedString(@"PUSH_REGISTER_TITLE", @"Used in table section header and alert view title contexts");
[pushNotificationsSection addItem:[OWSTableItem actionItemWithText:NSLocalizedString(@"REREGISTER_FOR_PUSH", nil)
accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME(
self, @"reregister_push_notifications")
actionBlock:^{
[weakSelf syncPushTokens];
}]];
[contents addSection:pushNotificationsSection];
// Censorship circumvention has certain disadvantages so it should only be
// used if necessary. Therefore:
//
// * We disable this setting if the user has a phone number from a censored region -
// censorship circumvention will be auto-activated for this user.
// * We disable this setting if the user is already connected; they're not being
// censored.
// * We continue to show this setting so long as it is set to allow users to disable
// it, for example when they leave a censored region.
OWSTableSection *censorshipSection = [OWSTableSection new];
censorshipSection.headerTitle = NSLocalizedString(@"SETTINGS_ADVANCED_CENSORSHIP_CIRCUMVENTION_HEADER",
@"Table header for the 'censorship circumvention' section.");
BOOL isAnySocketOpen = TSSocketManager.shared.highestSocketState == OWSWebSocketStateOpen;
if (OWSSignalService.sharedInstance.hasCensoredPhoneNumber) {
if (OWSSignalService.sharedInstance.isCensorshipCircumventionManuallyDisabled) {
censorshipSection.footerTitle
= NSLocalizedString(@"SETTINGS_ADVANCED_CENSORSHIP_CIRCUMVENTION_FOOTER_MANUALLY_DISABLED",
@"Table footer for the 'censorship circumvention' section shown when censorship circumvention has "
@"been manually disabled.");
} else {
censorshipSection.footerTitle = NSLocalizedString(
@"SETTINGS_ADVANCED_CENSORSHIP_CIRCUMVENTION_FOOTER_AUTO_ENABLED",
@"Table footer for the 'censorship circumvention' section shown when censorship circumvention has been "
@"auto-enabled based on local phone number.");
}
} else if (isAnySocketOpen) {
censorshipSection.footerTitle
= NSLocalizedString(@"SETTINGS_ADVANCED_CENSORSHIP_CIRCUMVENTION_FOOTER_WEBSOCKET_CONNECTED",
@"Table footer for the 'censorship circumvention' section shown when the app is connected to the "
@"Signal service.");
} else if (!self.reachability.isReachable) {
censorshipSection.footerTitle
= NSLocalizedString(@"SETTINGS_ADVANCED_CENSORSHIP_CIRCUMVENTION_FOOTER_NO_CONNECTION",
@"Table footer for the 'censorship circumvention' section shown when the app is not connected to the "
@"internet.");
} else {
censorshipSection.footerTitle = NSLocalizedString(@"SETTINGS_ADVANCED_CENSORSHIP_CIRCUMVENTION_FOOTER",
@"Table footer for the 'censorship circumvention' section when censorship circumvention can be manually "
@"enabled.");
}
// Do enable if :
//
// * ...Censorship circumvention is already manually enabled (to allow users to disable it).
//
// Otherwise, don't enable if:
//
// * ...Censorship circumvention is already enabled based on the local phone number.
// * ...The websocket is connected, since that demonstrates that no censorship is in effect.
// * ...The internet is not reachable, since we don't want to let users to activate
// censorship circumvention unnecessarily, e.g. if they just don't have a valid
// internet connection.
OWSTableSwitchBlock isCensorshipCircumventionOnBlock = ^{
return OWSSignalService.sharedInstance.isCensorshipCircumventionActive;
};
Reachability *reachability = self.reachability;
OWSTableSwitchBlock isManualCensorshipCircumventionOnEnabledBlock = ^{
OWSSignalService *service = OWSSignalService.sharedInstance;
if (service.isCensorshipCircumventionActive) {
return YES;
} else if (service.hasCensoredPhoneNumber && service.isCensorshipCircumventionManuallyDisabled) {
return YES;
} else if (TSSocketManager.shared.highestSocketState == OWSWebSocketStateOpen) {
return NO;
} else {
return reachability.isReachable;
}
};
[censorshipSection
addItem:[OWSTableItem switchItemWithText:NSLocalizedString(@"SETTINGS_ADVANCED_CENSORSHIP_CIRCUMVENTION",
@"Label for the 'manual censorship circumvention' switch.")
accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME(self, @"censorship_circumvention")
isOnBlock:isCensorshipCircumventionOnBlock
isEnabledBlock:isManualCensorshipCircumventionOnEnabledBlock
target:weakSelf
selector:@selector(didToggleEnableCensorshipCircumventionSwitch:)]];
if (OWSSignalService.sharedInstance.isCensorshipCircumventionManuallyActivated) {
OWSCountryMetadata *manualCensorshipCircumventionCountry =
[weakSelf ensureManualCensorshipCircumventionCountry];
OWSAssertDebug(manualCensorshipCircumventionCountry);
NSString *text = [NSString
stringWithFormat:NSLocalizedString(@"SETTINGS_ADVANCED_CENSORSHIP_CIRCUMVENTION_COUNTRY_FORMAT",
@"Label for the 'manual censorship circumvention' country. Embeds {{the manual "
@"censorship circumvention country}}."),
manualCensorshipCircumventionCountry.localizedCountryName];
[censorshipSection addItem:[OWSTableItem disclosureItemWithText:text
actionBlock:^{
[weakSelf showDomainFrontingCountryView];
}]];
}
[contents addSection:censorshipSection];
self.contents = contents;
}
- (void)showDomainFrontingCountryView
{
DomainFrontingCountryViewController *vc = [DomainFrontingCountryViewController new];
[self.navigationController pushViewController:vc animated:YES];
}
- (OWSCountryMetadata *)ensureManualCensorshipCircumventionCountry
{
OWSAssertIsOnMainThread();
OWSCountryMetadata *countryMetadata = nil;
NSString *countryCode = OWSSignalService.sharedInstance.manualCensorshipCircumventionCountryCode;
if (countryCode) {
countryMetadata = [OWSCountryMetadata countryMetadataForCountryCode:countryCode];
}
if (!countryMetadata) {
countryCode = [PhoneNumber defaultCountryCode];
if (countryCode) {
countryMetadata = [OWSCountryMetadata countryMetadataForCountryCode:countryCode];
}
}
if (!countryMetadata) {
countryCode = @"US";
countryMetadata = [OWSCountryMetadata countryMetadataForCountryCode:countryCode];
OWSAssertDebug(countryMetadata);
}
if (countryMetadata) {
// Ensure the "manual censorship circumvention" country state is in sync.
OWSSignalService.sharedInstance.manualCensorshipCircumventionCountryCode = countryCode;
}
return countryMetadata;
}
#pragma mark - Actions
- (void)syncPushTokens
{
OWSSyncPushTokensJob *job =
[[OWSSyncPushTokensJob alloc] initWithAccountManager:AppEnvironment.shared.accountManager
preferences:Environment.shared.preferences];
job.uploadOnlyIfStale = NO;
[job run]
.then(^{
[OWSAlerts showAlertWithTitle:NSLocalizedString(@"PUSH_REGISTER_SUCCESS",
@"Title of alert shown when push tokens sync job succeeds.")];
})
.catch(^(NSError *error) {
[OWSAlerts showAlertWithTitle:NSLocalizedString(@"REGISTRATION_BODY",
@"Title of alert shown when push tokens sync job fails.")];
});
}
- (void)didToggleEnableLogSwitch:(UISwitch *)sender
{
if (!sender.isOn) {
OWSLogInfo(@"disabling logging.");
[[DebugLogger sharedLogger] wipeLogs];
[[DebugLogger sharedLogger] disableFileLogging];
} else {
[[DebugLogger sharedLogger] enableFileLogging];
OWSLogInfo(@"enabling logging.");
}
[OWSPreferences setIsLoggingEnabled:sender.isOn];
[self updateTableContents];
}
- (void)didToggleEnableCensorshipCircumventionSwitch:(UISwitch *)sender
{
OWSSignalService *service = OWSSignalService.sharedInstance;
if (sender.isOn) {
service.isCensorshipCircumventionManuallyDisabled = NO;
service.isCensorshipCircumventionManuallyActivated = YES;
} else {
service.isCensorshipCircumventionManuallyDisabled = YES;
service.isCensorshipCircumventionManuallyActivated = NO;
}
[self updateTableContents];
}
@end
NS_ASSUME_NONNULL_END

View File

@ -8,31 +8,24 @@
#import "OWSBackup.h"
#import "OWSOrphanDataCleaner.h"
#import "OWSScreenLockUI.h"
#import "Pastelog.h"
#import "Session-Swift.h"
#import "SignalApp.h"
#import "SignalsNavigationController.h"
#import "ViewControllerUtils.h"
#import <PromiseKit/AnyPromise.h>
#import <SignalCoreKit/iOSVersions.h>
#import <SignalUtilitiesKit/AppSetup.h>
#import <SignalUtilitiesKit/Environment.h>
#import <SignalUtilitiesKit/OWSContactsManager.h>
#import <SignalUtilitiesKit/OWSNavigationController.h>
#import <SignalUtilitiesKit/OWSPreferences.h>
#import <SignalUtilitiesKit/OWSProfileManager.h>
#import <SignalUtilitiesKit/VersionMigrations.h>
#import <SignalUtilitiesKit/AppReadiness.h>
#import <SignalUtilitiesKit/NSUserDefaults+OWS.h>
#import <SignalUtilitiesKit/OWS2FAManager.h>
#import <SignalUtilitiesKit/OWSBatchMessageProcessor.h>
#import <SignalUtilitiesKit/OWSDisappearingMessagesJob.h>
#import <SignalUtilitiesKit/OWSFailedAttachmentDownloadsJob.h>
#import <SignalUtilitiesKit/OWSFailedMessagesJob.h>
#import <SignalUtilitiesKit/OWSIncompleteCallsJob.h>
#import <SignalUtilitiesKit/OWSMath.h>
#import <SignalUtilitiesKit/OWSMessageManager.h>
#import <SignalUtilitiesKit/OWSMessageSender.h>
#import <SignalUtilitiesKit/OWSPrimaryStorage+Calling.h>
#import <SignalUtilitiesKit/OWSReadReceiptManager.h>
#import <SignalUtilitiesKit/SSKEnvironment.h>
@ -40,7 +33,7 @@
#import <SignalUtilitiesKit/TSAccountManager.h>
#import <SignalUtilitiesKit/TSDatabaseView.h>
#import <SignalUtilitiesKit/TSPreKeyManager.h>
#import <SignalUtilitiesKit/TSSocketManager.h>
#import <YapDatabase/YapDatabaseCryptoUtils.h>
#import <sys/utsname.h>
@ -118,20 +111,6 @@ static NSTimeInterval launchStartedAt;
return SSKEnvironment.shared.disappearingMessagesJob;
}
- (TSSocketManager *)socketManager
{
OWSAssertDebug(SSKEnvironment.shared.socketManager);
return SSKEnvironment.shared.socketManager;
}
- (OWSMessageManager *)messageManager
{
OWSAssertDebug(SSKEnvironment.shared.messageManager);
return SSKEnvironment.shared.messageManager;
}
- (OWSWindowManager *)windowManager
{
return Environment.shared.windowManager;
@ -279,18 +258,12 @@ static NSTimeInterval launchStartedAt;
selector:@selector(registrationStateDidChange)
name:RegistrationStateDidChangeNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(registrationLockDidChange:)
name:NSNotificationName_2FAStateDidChange
object:nil];
// Loki - Observe data nuke request notifications
[NSNotificationCenter.defaultCenter addObserver:self selector:@selector(handleDataNukeRequested:) name:NSNotification.dataNukeRequested object:nil];
OWSLogInfo(@"application: didFinishLaunchingWithOptions completed.");
[OWSAnalytics appLaunchDidBegin];
return YES;
}
@ -394,41 +367,6 @@ static NSTimeInterval launchStartedAt;
}
}
- (void)showLaunchFailureUI:(NSError *)error
{
// Disable normal functioning of app.
self.didAppLaunchFail = YES;
// We perform a subset of the [application:didFinishLaunchingWithOptions:].
[AppVersion sharedInstance];
[self startupLogging];
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Show the launch screen
self.window.rootViewController =
[[UIStoryboard storyboardWithName:@"Launch Screen" bundle:nil] instantiateInitialViewController];
[self.window makeKeyAndVisible];
UIAlertController *alert =
[UIAlertController alertControllerWithTitle:NSLocalizedString(@"APP_LAUNCH_FAILURE_ALERT_TITLE",
@"Title for the 'app launch failed' alert.")
message:NSLocalizedString(@"APP_LAUNCH_FAILURE_ALERT_MESSAGE",
@"Message for the 'app launch failed' alert.")
preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"SETTINGS_ADVANCED_SUBMIT_DEBUGLOG", nil)
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *_Nonnull action) {
[Pastelog submitLogsWithCompletion:^{
OWSFail(@"Exiting after sharing debug logs.");
}];
}]];
UIViewController *fromViewController = [[UIApplication sharedApplication] frontmostViewController];
[fromViewController presentAlert:alert];
}
- (void)startupLogging
{
OWSLogInfo(@"iOS Version: %@", [UIDevice currentDevice].systemVersion);
@ -498,16 +436,6 @@ static NSTimeInterval launchStartedAt;
[[[OWSFailedMessagesJob alloc] initWithPrimaryStorage:self.primaryStorage] run];
[[[OWSFailedAttachmentDownloadsJob alloc] initWithPrimaryStorage:self.primaryStorage] run];
});
} else {
OWSLogInfo(@"Running post launch block for unregistered user.");
// Unregistered user should have no unread messages. e.g. if you delete your account.
[AppEnvironment.shared.notificationPresenter clearAllNotifications];
UITapGestureRecognizer *gesture =
[[UITapGestureRecognizer alloc] initWithTarget:[Pastelog class] action:@selector(submitLogs)];
gesture.numberOfTapsRequired = 8;
[self.window addGestureRecognizer:gesture];
}
}); // end dispatchOnce for first time we become active
@ -640,8 +568,6 @@ static NSTimeInterval launchStartedAt;
[self ensureRootViewController];
[self.messageManager startObserving];
[self.udManager setup];
[self preheatDatabaseViews];
@ -657,8 +583,6 @@ static NSTimeInterval launchStartedAt;
if (appVersion.lastAppVersion.length > 0
&& ![appVersion.lastAppVersion isEqualToString:appVersion.currentAppVersion]) {
[[self.tsAccountManager updateAccountAttributes] retainUntilComplete];
[SSKEnvironment.shared.syncManager sendConfigurationSyncMessage];
}
}
}
@ -734,18 +658,6 @@ static NSTimeInterval launchStartedAt;
[UIViewController attemptRotationToDeviceOrientation];
}
#pragma mark - Status Bar Interaction
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesBegan:touches withEvent:event];
CGPoint location = [[[event allTouches] anyObject] locationInView:[self window]];
CGRect statusBarFrame = [UIApplication sharedApplication].statusBarFrame;
if (CGRectContainsPoint(statusBarFrame, location)) {
[[NSNotificationCenter defaultCenter] postNotificationName:TappedStatusBarNotification object:nil];
}
}
#pragma mark - Notifications
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
@ -953,7 +865,6 @@ static NSTimeInterval launchStartedAt;
[[LKPushNotificationManager unregisterWithToken:deviceToken isForcedUpdate:YES] retainUntilComplete];
}
[ThreadUtil deleteAllContent];
[SSKEnvironment.shared.messageSenderJobQueue clearAllJobs];
[SSKEnvironment.shared.identityManager clearIdentityKey];
[SNSnodeAPI clearSnodePool];
[self stopPoller];

View File

@ -34,9 +34,6 @@ import SignalUtilitiesKit
// @objc
// public var outboundCallInitiator: OutboundCallInitiator
@objc
public var messageFetcherJob: MessageFetcherJob
@objc
public var accountManager: AccountManager
@ -81,9 +78,6 @@ import SignalUtilitiesKit
private override init() {
self.callMessageHandler = WebRTCCallMessageHandler()
// self.callService = CallService()
// self.outboundCallInitiator = OutboundCallInitiator()
self.messageFetcherJob = MessageFetcherJob()
self.accountManager = AccountManager()
self.notificationPresenter = NotificationPresenter()
self.pushRegistrationManager = PushRegistrationManager()

View File

@ -120,14 +120,6 @@ protocol NotificationPresenterAdaptee: class {
func cancelNotifications(threadId: String)
func clearAllNotifications()
var hasReceivedSyncMessageRecently: Bool { get }
}
extension NotificationPresenterAdaptee {
var hasReceivedSyncMessageRecently: Bool {
return OWSDeviceManager.shared().hasReceivedSyncMessage(inLastSeconds: 60)
}
}
@objc(OWSNotificationPresenter)
@ -153,10 +145,6 @@ public class NotificationPresenter: NSObject, NotificationsProtocol {
// MARK: - Dependencies
var contactsManager: OWSContactsManager {
return Environment.shared.contactsManager
}
var identityManager: OWSIdentityManager {
return OWSIdentityManager.shared()
}
@ -209,140 +197,6 @@ public class NotificationPresenter: NSObject, NotificationsProtocol {
return adaptee.registerNotificationSettings()
}
// func presentIncomingCall(_ call: SignalCall, callerName: String) {
//
// let notificationTitle: String?
// switch previewType {
// case .noNameNoPreview:
// notificationTitle = nil
// case .nameNoPreview, .namePreview:
// notificationTitle = callerName
// }
// let notificationBody = NotificationStrings.incomingCallBody
//
// let remotePhoneNumber = call.remotePhoneNumber
// let thread = TSContactThread.getOrCreateThread(contactId: remotePhoneNumber)
//
// guard let threadId = thread.uniqueId else {
// owsFailDebug("threadId was unexpectedly nil")
// return
// }
//
// let userInfo = [
// AppNotificationUserInfoKey.threadId: threadId,
// AppNotificationUserInfoKey.localCallId: call.localId.uuidString
// ]
//
// DispatchQueue.main.async {
// self.adaptee.notify(category: .incomingCall,
// title: notificationTitle,
// body: notificationBody,
// userInfo: userInfo,
// sound: .defaultiOSIncomingRingtone,
// replacingIdentifier: call.localId.uuidString)
// }
// }
//
// func presentMissedCall(_ call: SignalCall, callerName: String) {
// let notificationTitle: String?
// switch previewType {
// case .noNameNoPreview:
// notificationTitle = nil
// case .nameNoPreview, .namePreview:
// notificationTitle = callerName
// }
// let notificationBody = NotificationStrings.missedCallBody
//
// let remotePhoneNumber = call.remotePhoneNumber
// let thread = TSContactThread.getOrCreateThread(contactId: remotePhoneNumber)
//
// guard let threadId = thread.uniqueId else {
// owsFailDebug("threadId was unexpectedly nil")
// return
// }
//
// let userInfo = [
// AppNotificationUserInfoKey.threadId: threadId,
// AppNotificationUserInfoKey.callBackNumber: remotePhoneNumber
// ]
//
// DispatchQueue.main.async {
// let sound = self.requestSound(thread: thread)
// self.adaptee.notify(category: .missedCall,
// title: notificationTitle,
// body: notificationBody,
// userInfo: userInfo,
// sound: sound,
// replacingIdentifier: call.localId.uuidString)
// }
// }
//
// public func presentMissedCallBecauseOfNoLongerVerifiedIdentity(call: SignalCall, callerName: String) {
// let notificationTitle: String?
// switch previewType {
// case .noNameNoPreview:
// notificationTitle = nil
// case .nameNoPreview, .namePreview:
// notificationTitle = callerName
// }
// let notificationBody = NotificationStrings.missedCallBecauseOfIdentityChangeBody
//
// let remotePhoneNumber = call.remotePhoneNumber
// let thread = TSContactThread.getOrCreateThread(contactId: remotePhoneNumber)
// guard let threadId = thread.uniqueId else {
// owsFailDebug("threadId was unexpectedly nil")
// return
// }
//
// let userInfo = [
// AppNotificationUserInfoKey.threadId: threadId
// ]
//
// DispatchQueue.main.async {
// let sound = self.requestSound(thread: thread)
// self.adaptee.notify(category: .missedCallFromNoLongerVerifiedIdentity,
// title: notificationTitle,
// body: notificationBody,
// userInfo: userInfo,
// sound: sound,
// replacingIdentifier: call.localId.uuidString)
// }
// }
//
// public func presentMissedCallBecauseOfNewIdentity(call: SignalCall, callerName: String) {
// let notificationTitle: String?
// switch previewType {
// case .noNameNoPreview:
// notificationTitle = nil
// case .nameNoPreview, .namePreview:
// notificationTitle = callerName
// }
// let notificationBody = NotificationStrings.missedCallBecauseOfIdentityChangeBody
//
// let remotePhoneNumber = call.remotePhoneNumber
// let thread = TSContactThread.getOrCreateThread(contactId: remotePhoneNumber)
//
// guard let threadId = thread.uniqueId else {
// owsFailDebug("threadId was unexpectedly nil")
// return
// }
//
// let userInfo = [
// AppNotificationUserInfoKey.threadId: threadId,
// AppNotificationUserInfoKey.callBackNumber: remotePhoneNumber
// ]
//
// DispatchQueue.main.async {
// let sound = self.requestSound(thread: thread)
// self.adaptee.notify(category: .missedCall,
// title: notificationTitle,
// body: notificationBody,
// userInfo: userInfo,
// sound: sound,
// replacingIdentifier: call.localId.uuidString)
// }
// }
public func notifyUser(for incomingMessage: TSIncomingMessage, in thread: TSThread, transaction: YapDatabaseReadTransaction) {
guard !thread.isMuted else {
@ -359,7 +213,7 @@ public class NotificationPresenter: NSObject, NotificationsProtocol {
// for more details.
let messageText = DisplayableText.filterNotificationText(rawMessageText)
let senderName = OWSUserProfile.fetch(uniqueId: incomingMessage.authorId, transaction: transaction)?.profileName ?? contactsManager.displayName(forPhoneIdentifier: incomingMessage.authorId)
let senderName = SSKEnvironment.shared.profileManager.profileNameForRecipient(withID: incomingMessage.authorId, avoidingWriteTransaction: true)!
let notificationTitle: String?
switch previewType {
@ -401,12 +255,6 @@ public class NotificationPresenter: NSObject, NotificationsProtocol {
// Don't reply from lockscreen if anyone in this conversation is
// "no longer verified".
var category = AppNotificationCategory.incomingMessage
for recipientId in thread.recipientIdentifiers {
if self.identityManager.verificationState(forRecipientId: recipientId) == .noLongerVerified {
category = AppNotificationCategory.incomingMessageFromNoLongerVerifiedIdentity
break
}
}
let userInfo = [
AppNotificationUserInfoKey.threadId: threadId
@ -554,14 +402,6 @@ class NotificationActionHandler {
return SignalApp.shared()
}
var messageSender: MessageSender {
return SSKEnvironment.shared.messageSender
}
// var callUIAdapter: CallUIAdapter {
// return AppEnvironment.shared.callService.callUIAdapter
// }
var notificationPresenter: NotificationPresenter {
return AppEnvironment.shared.notificationPresenter
}
@ -572,41 +412,6 @@ class NotificationActionHandler {
// MARK: -
// func answerCall(userInfo: [AnyHashable: Any]) throws -> Promise<Void> {
// guard let localCallIdString = userInfo[AppNotificationUserInfoKey.localCallId] as? String else {
// throw NotificationError.failDebug("localCallIdString was unexpectedly nil")
// }
//
// guard let localCallId = UUID(uuidString: localCallIdString) else {
// throw NotificationError.failDebug("unable to build localCallId. localCallIdString: \(localCallIdString)")
// }
//
// callUIAdapter.answerCall(localId: localCallId)
// return Promise.value(())
// }
//
// func callBack(userInfo: [AnyHashable: Any]) throws -> Promise<Void> {
// guard let recipientId = userInfo[AppNotificationUserInfoKey.callBackNumber] as? String else {
// throw NotificationError.failDebug("recipientId was unexpectedly nil")
// }
//
// callUIAdapter.startAndShowOutgoingCall(recipientId: recipientId, hasLocalVideo: false)
// return Promise.value(())
// }
//
// func declineCall(userInfo: [AnyHashable: Any]) throws -> Promise<Void> {
// guard let localCallIdString = userInfo[AppNotificationUserInfoKey.localCallId] as? String else {
// throw NotificationError.failDebug("localCallIdString was unexpectedly nil")
// }
//
// guard let localCallId = UUID(uuidString: localCallIdString) else {
// throw NotificationError.failDebug("unable to build localCallId. localCallIdString: \(localCallIdString)")
// }
//
// callUIAdapter.declineCall(localId: localCallId)
// return Promise.value(())
// }
func markAsRead(userInfo: [AnyHashable: Any]) throws -> Promise<Void> {
guard let threadId = userInfo[AppNotificationUserInfoKey.threadId] as? String else {
throw NotificationError.failDebug("threadId was unexpectedly nil")
@ -629,15 +434,18 @@ class NotificationActionHandler {
}
return markAsRead(thread: thread).then { () -> Promise<Void> in
let sendPromise = ThreadUtil.sendMessageNonDurably(text: replyText,
thread: thread,
quotedReplyModel: nil,
messageSender: self.messageSender)
// TODO TODO TODO
// let sendPromise = ThreadUtil.sendMessageNonDurably(text: replyText,
// thread: thread,
// quotedReplyModel: nil,
// messageSender: self.messageSender)
return sendPromise.recover { error in
Logger.warn("Failed to send reply message from notification with error: \(error)")
self.notificationPresenter.notifyForFailedSend(inThread: thread)
}
// return sendPromise.recover { error in
// Logger.warn("Failed to send reply message from notification with error: \(error)")
// self.notificationPresenter.notifyForFailedSend(inThread: thread)
// }
return Promise<Void>.value(())
}
}
@ -666,25 +474,6 @@ class NotificationActionHandler {
}
}
extension ThreadUtil {
static var dbReadConnection: YapDatabaseConnection {
return OWSPrimaryStorage.shared().dbReadConnection
}
class func sendMessageNonDurably(text: String, thread: TSThread, quotedReplyModel: OWSQuotedReplyModel?, messageSender: MessageSender) -> Promise<Void> {
return Promise { resolver in
self.dbReadConnection.read { transaction in
_ = self.sendMessageNonDurably(withText: text,
in: thread,
quotedReplyModel: quotedReplyModel,
transaction: transaction,
messageSender: messageSender,
completion: resolver.resolve)
}
}
}
}
enum NotificationError: Error {
case assertionError(description: String)
}

View File

@ -9,7 +9,7 @@ public class AvatarTableViewCell: UITableViewCell {
private let columns: UIStackView
private let textRows: UIStackView
private let avatarView: AvatarImageView
// private let avatarView: AvatarImageView
private let _textLabel: UILabel
override public var textLabel: UILabel? {
@ -27,8 +27,8 @@ public class AvatarTableViewCell: UITableViewCell {
@objc
public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
self.avatarView = AvatarImageView()
avatarView.autoSetDimensions(to: CGSize(width: CGFloat(kStandardAvatarSize), height: CGFloat(kStandardAvatarSize)))
// self.avatarView = AvatarImageView()
// avatarView.autoSetDimensions(to: CGSize(width: CGFloat(kStandardAvatarSize), height: CGFloat(kStandardAvatarSize)))
self._textLabel = UILabel()
self._detailTextLabel = UILabel()
@ -36,7 +36,7 @@ public class AvatarTableViewCell: UITableViewCell {
self.textRows = UIStackView(arrangedSubviews: [_textLabel, _detailTextLabel])
textRows.axis = .vertical
self.columns = UIStackView(arrangedSubviews: [avatarView, textRows])
self.columns = UIStackView(arrangedSubviews: [ textRows ])
columns.axis = .horizontal
columns.spacing = CGFloat(kContactCellAvatarTextMargin)
@ -54,7 +54,7 @@ public class AvatarTableViewCell: UITableViewCell {
@objc
public func configure(image: UIImage?, text: String?, detailText: String?) {
self.avatarView.image = image
// self.avatarView.image = image
self.textLabel?.text = text
self.detailTextLabel?.text = detailText
@ -65,7 +65,7 @@ public class AvatarTableViewCell: UITableViewCell {
public override func prepareForReuse() {
super.prepareForReuse()
self.avatarView.image = nil
// self.avatarView.image = nil
self.textLabel?.text = nil
self.detailTextLabel?.text = nil
}

View File

@ -6,9 +6,9 @@
#import "OWSNavigationController.h"
#import "Session-Swift.h"
#import <MobileCoreServices/UTCoreTypes.h>
#import <SignalUtilitiesKit/OWSContactsManager.h>
#import <SignalUtilitiesKit/UIUtil.h>
#import <SignalUtilitiesKit/PhoneNumber.h>
#import <SignalUtilitiesKit/TSGroupModel.h>
#import <SignalUtilitiesKit/TSGroupThread.h>
#import <SignalUtilitiesKit/TSThread.h>

View File

@ -1,13 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import <SignalUtilitiesKit/OWSViewController.h>
NS_ASSUME_NONNULL_BEGIN
@interface BlockListViewController : OWSViewController
@end
NS_ASSUME_NONNULL_END

View File

@ -1,175 +0,0 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
#import "BlockListViewController.h"
#import "AddToBlockListViewController.h"
#import "BlockListUIUtils.h"
#import "ContactTableViewCell.h"
#import "ContactsViewHelper.h"
#import "OWSTableViewController.h"
#import "PhoneNumber.h"
#import "Session-Swift.h"
#import "UIFont+OWS.h"
#import "UIView+OWS.h"
#import <SignalUtilitiesKit/Environment.h>
#import <SignalUtilitiesKit/OWSContactsManager.h>
#import <SignalUtilitiesKit/OWSBlockingManager.h>
#import <SignalUtilitiesKit/TSGroupThread.h>
NS_ASSUME_NONNULL_BEGIN
@interface BlockListViewController () <ContactsViewHelperDelegate>
@property (nonatomic, readonly) ContactsViewHelper *contactsViewHelper;
@property (nonatomic, readonly) OWSTableViewController *tableViewController;
@end
#pragma mark -
@implementation BlockListViewController
- (OWSBlockingManager *)blockingManager
{
return OWSBlockingManager.sharedManager;
}
- (void)loadView
{
[super loadView];
_contactsViewHelper = [[ContactsViewHelper alloc] initWithDelegate:self];
self.title
= NSLocalizedString(@"SETTINGS_BLOCK_LIST_TITLE", @"Label for the block list section of the settings view");
_tableViewController = [OWSTableViewController new];
[self.view addSubview:self.tableViewController.view];
[self addChildViewController:self.tableViewController];
[_tableViewController.view autoPinEdgesToSuperviewEdges];
self.tableViewController.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableViewController.tableView.estimatedRowHeight = 60;
[self updateTableContents];
}
#pragma mark - Table view data source
- (void)updateTableContents
{
OWSTableContents *contents = [OWSTableContents new];
__weak BlockListViewController *weakSelf = self;
ContactsViewHelper *helper = self.contactsViewHelper;
// "Add" section
OWSTableSection *addSection = [OWSTableSection new];
addSection.footerTitle = NSLocalizedString(
@"BLOCK_USER_BEHAVIOR_EXPLANATION", @"An explanation of the consequences of blocking another user.");
[addSection
addItem:[OWSTableItem
disclosureItemWithText:NSLocalizedString(@"SETTINGS_BLOCK_LIST_ADD_BUTTON",
@"A label for the 'add phone number' button in the block list table.")
accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME(self, @"add")
actionBlock:^{
AddToBlockListViewController *vc = [[AddToBlockListViewController alloc] init];
[weakSelf.navigationController pushViewController:vc animated:YES];
}]];
[contents addSection:addSection];
// "Blocklist" section
NSArray<NSString *> *blockedPhoneNumbers =
[self.blockingManager.blockedPhoneNumbers sortedArrayUsingSelector:@selector(compare:)];
if (blockedPhoneNumbers.count > 0) {
OWSTableSection *blockedContactsSection = [OWSTableSection new];
blockedContactsSection.headerTitle = NSLocalizedString(
@"BLOCK_LIST_BLOCKED_USERS_SECTION", @"Section header for users that have been blocked");
for (NSString *phoneNumber in blockedPhoneNumbers) {
[blockedContactsSection addItem:[OWSTableItem
itemWithCustomCellBlock:^{
ContactTableViewCell *cell = [ContactTableViewCell new];
[cell configureWithRecipientId:phoneNumber];
cell.accessibilityIdentifier = ACCESSIBILITY_IDENTIFIER_WITH_NAME(
BlockListViewController, @"user");
return cell;
}
customRowHeight:UITableViewAutomaticDimension
actionBlock:^{
[BlockListUIUtils
showUnblockPhoneNumberActionSheet:phoneNumber
fromViewController:weakSelf
blockingManager:helper.blockingManager
contactsManager:helper.contactsManager
completionBlock:^(BOOL isBlocked) {
[weakSelf updateTableContents];
}];
}]];
}
[contents addSection:blockedContactsSection];
}
NSArray<TSGroupModel *> *blockedGroups = self.blockingManager.blockedGroups;
if (blockedGroups.count > 0) {
OWSTableSection *blockedGroupsSection = [OWSTableSection new];
blockedGroupsSection.headerTitle = NSLocalizedString(
@"BLOCK_LIST_BLOCKED_GROUPS_SECTION", @"Section header for groups that have been blocked");
for (TSGroupModel *blockedGroup in blockedGroups) {
UIImage *_Nullable image = blockedGroup.groupImage;
if (!image) {
NSString *conversationColorName =
[TSGroupThread defaultConversationColorNameForGroupId:blockedGroup.groupId];
image = [OWSGroupAvatarBuilder defaultAvatarForGroupId:blockedGroup.groupId
conversationColorName:conversationColorName
diameter:kStandardAvatarSize];
}
NSString *groupName
= blockedGroup.groupName.length > 0 ? blockedGroup.groupName : TSGroupThread.defaultGroupName;
[blockedGroupsSection addItem:[OWSTableItem
itemWithCustomCellBlock:^{
OWSAvatarTableViewCell *cell = [OWSAvatarTableViewCell new];
[cell configureWithImage:image
text:groupName
detailText:nil];
return cell;
}
customRowHeight:UITableViewAutomaticDimension
actionBlock:^{
[BlockListUIUtils showUnblockGroupActionSheet:blockedGroup
displayName:groupName
fromViewController:weakSelf
blockingManager:helper.blockingManager
completionBlock:^(BOOL isBlocked) {
[weakSelf updateTableContents];
}];
}]];
}
[contents addSection:blockedGroupsSection];
}
self.tableViewController.contents = contents;
}
#pragma mark - ContactsViewHelperDelegate
- (void)contactsViewHelperDidUpdateContacts
{
[self updateTableContents];
}
- (BOOL)shouldHideLocalNumber
{
return YES;
}
@end
NS_ASSUME_NONNULL_END

View File

@ -1,532 +0,0 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
import Foundation
@objc
class OWSColorPickerAccessoryView: NeverClearView {
override var intrinsicContentSize: CGSize {
return CGSize(width: kSwatchSize, height: kSwatchSize)
}
override func sizeThatFits(_ size: CGSize) -> CGSize {
return self.intrinsicContentSize
}
let kSwatchSize: CGFloat = 24
@objc
required init(color: UIColor) {
super.init(frame: .zero)
let circleView = CircleView()
circleView.backgroundColor = color
addSubview(circleView)
circleView.autoSetDimensions(to: CGSize(width: kSwatchSize, height: kSwatchSize))
circleView.autoPinEdgesToSuperviewEdges()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
@objc (OWSCircleView)
class CircleView: UIView {
override var bounds: CGRect {
didSet {
self.layer.cornerRadius = self.bounds.size.height / 2
}
}
}
protocol ColorViewDelegate: class {
func colorViewWasTapped(_ colorView: ColorView)
}
class ColorView: UIView {
public weak var delegate: ColorViewDelegate?
public let conversationColor: OWSConversationColor
private let swatchView: CircleView
private let selectedRing: CircleView
public var isSelected: Bool = false {
didSet {
self.selectedRing.isHidden = !isSelected
}
}
required init(conversationColor: OWSConversationColor) {
self.conversationColor = conversationColor
self.swatchView = CircleView()
self.selectedRing = CircleView()
super.init(frame: .zero)
self.addSubview(selectedRing)
self.addSubview(swatchView)
// Selected Ring
let cellHeight: CGFloat = ScaleFromIPhone5(60)
selectedRing.autoSetDimensions(to: CGSize(width: cellHeight, height: cellHeight))
selectedRing.layer.borderColor = Theme.secondaryColor.cgColor
selectedRing.layer.borderWidth = 2
selectedRing.autoPinEdgesToSuperviewEdges()
selectedRing.isHidden = true
// Color Swatch
swatchView.backgroundColor = conversationColor.primaryColor
let swatchSize: CGFloat = ScaleFromIPhone5(46)
swatchView.autoSetDimensions(to: CGSize(width: swatchSize, height: swatchSize))
swatchView.autoCenterInSuperview()
// gestures
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(didTap))
self.addGestureRecognizer(tapGesture)
}
required init?(coder aDecoder: NSCoder) {
notImplemented()
}
// MARK: Actions
@objc
func didTap() {
delegate?.colorViewWasTapped(self)
}
}
@objc
protocol ColorPickerDelegate: class {
func colorPicker(_ colorPicker: ColorPicker, didPickConversationColor conversationColor: OWSConversationColor)
}
@objc(OWSColorPicker)
class ColorPicker: NSObject, ColorPickerViewDelegate {
@objc
public weak var delegate: ColorPickerDelegate?
@objc
let sheetViewController: SheetViewController
@objc
init(thread: TSThread) {
let colorName = thread.conversationColorName
let currentConversationColor = OWSConversationColor.conversationColorOrDefault(colorName: colorName)
sheetViewController = SheetViewController()
super.init()
let colorPickerView = ColorPickerView(thread: thread)
colorPickerView.delegate = self
colorPickerView.select(conversationColor: currentConversationColor)
sheetViewController.contentView.addSubview(colorPickerView)
colorPickerView.autoPinEdgesToSuperviewEdges()
}
// MARK: ColorPickerViewDelegate
func colorPickerView(_ colorPickerView: ColorPickerView, didPickConversationColor conversationColor: OWSConversationColor) {
self.delegate?.colorPicker(self, didPickConversationColor: conversationColor)
}
}
protocol ColorPickerViewDelegate: class {
func colorPickerView(_ colorPickerView: ColorPickerView, didPickConversationColor conversationColor: OWSConversationColor)
}
class ColorPickerView: UIView, ColorViewDelegate {
private let colorViews: [ColorView]
let conversationStyle: ConversationStyle
var outgoingMessageView = OWSMessageBubbleView(forAutoLayout: ())
var incomingMessageView = OWSMessageBubbleView(forAutoLayout: ())
weak var delegate: ColorPickerViewDelegate?
// This is mostly a developer convenience - OWSMessageCell asserts at some point
// that the available method width is greater than 0.
// We ultimately use the width of the picker view which will be larger.
let kMinimumConversationWidth: CGFloat = 300
override var bounds: CGRect {
didSet {
updateMockConversationView()
}
}
let mockConversationView: UIView = UIView()
init(thread: TSThread) {
let allConversationColors = OWSConversationColor.conversationColorNames.map { OWSConversationColor.conversationColorOrDefault(colorName: $0) }
self.colorViews = allConversationColors.map { ColorView(conversationColor: $0) }
self.conversationStyle = ConversationStyle(thread: thread)
super.init(frame: .zero)
colorViews.forEach { $0.delegate = self }
let headerView = self.buildHeaderView()
mockConversationView.layoutMargins = UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 16)
mockConversationView.backgroundColor = Theme.backgroundColor
self.updateMockConversationView()
let paletteView = self.buildPaletteView(colorViews: colorViews)
let rowsStackView = UIStackView(arrangedSubviews: [headerView, mockConversationView, paletteView])
rowsStackView.axis = .vertical
addSubview(rowsStackView)
rowsStackView.autoPinEdgesToSuperviewEdges()
}
required init?(coder aDecoder: NSCoder) {
notImplemented()
}
// MARK: ColorViewDelegate
func colorViewWasTapped(_ colorView: ColorView) {
self.select(conversationColor: colorView.conversationColor)
self.delegate?.colorPickerView(self, didPickConversationColor: colorView.conversationColor)
updateMockConversationView()
}
fileprivate func select(conversationColor selectedConversationColor: OWSConversationColor) {
colorViews.forEach { colorView in
colorView.isSelected = colorView.conversationColor == selectedConversationColor
}
}
// MARK: View Building
private func buildHeaderView() -> UIView {
let headerView = UIView()
headerView.layoutMargins = UIEdgeInsets(top: 15, left: 16, bottom: 15, right: 16)
let titleLabel = UILabel()
titleLabel.text = NSLocalizedString("COLOR_PICKER_SHEET_TITLE", comment: "Modal Sheet title when picking a conversation color.")
titleLabel.textAlignment = .center
titleLabel.font = UIFont.ows_dynamicTypeBody.ows_mediumWeight()
titleLabel.textColor = Theme.primaryColor
headerView.addSubview(titleLabel)
titleLabel.ows_autoPinToSuperviewMargins()
let bottomBorderView = UIView()
bottomBorderView.backgroundColor = Theme.hairlineColor
headerView.addSubview(bottomBorderView)
bottomBorderView.autoPinEdgesToSuperviewEdges(with: .zero, excludingEdge: .top)
bottomBorderView.autoSetDimension(.height, toSize: CGHairlineWidth())
return headerView
}
private func updateMockConversationView() {
/*
conversationStyle.viewWidth = max(bounds.size.width, kMinimumConversationWidth)
mockConversationView.subviews.forEach { $0.removeFromSuperview() }
// outgoing
outgoingMessageView = OWSMessageBubbleView(forAutoLayout: ())
let outgoingItem = MockConversationViewItem()
let outgoingText = NSLocalizedString("COLOR_PICKER_DEMO_MESSAGE_1", comment: "The first of two messages demonstrating the chosen conversation color, by rendering this message in an outgoing message bubble.")
outgoingItem.interaction = MockOutgoingMessage(messageBody: outgoingText)
outgoingItem.displayableBodyText = DisplayableText.displayableText(outgoingText)
outgoingItem.interactionType = .outgoingMessage
outgoingMessageView.viewItem = outgoingItem
outgoingMessageView.cellMediaCache = NSCache()
outgoingMessageView.conversationStyle = conversationStyle
outgoingMessageView.configureViews()
outgoingMessageView.loadContent()
let outgoingCell = UIView()
outgoingCell.addSubview(outgoingMessageView)
outgoingMessageView.autoPinEdgesToSuperviewEdges(with: .zero, excludingEdge: .leading)
let outgoingSize = outgoingMessageView.measureSize()
outgoingMessageView.autoSetDimensions(to: outgoingSize)
// incoming
incomingMessageView = OWSMessageBubbleView(forAutoLayout: ())
let incomingItem = MockConversationViewItem()
let incomingText = NSLocalizedString("COLOR_PICKER_DEMO_MESSAGE_2", comment: "The second of two messages demonstrating the chosen conversation color, by rendering this message in an incoming message bubble.")
incomingItem.interaction = MockIncomingMessage(messageBody: incomingText)
incomingItem.displayableBodyText = DisplayableText.displayableText(incomingText)
incomingItem.interactionType = .incomingMessage
incomingMessageView.viewItem = incomingItem
incomingMessageView.cellMediaCache = NSCache()
incomingMessageView.conversationStyle = conversationStyle
incomingMessageView.configureViews()
incomingMessageView.loadContent()
let incomingCell = UIView()
incomingCell.addSubview(incomingMessageView)
incomingMessageView.autoPinEdgesToSuperviewEdges(with: .zero, excludingEdge: .trailing)
let incomingSize = incomingMessageView.measureSize()
incomingMessageView.autoSetDimensions(to: incomingSize)
let messagesStackView = UIStackView(arrangedSubviews: [outgoingCell, incomingCell])
messagesStackView.axis = .vertical
messagesStackView.spacing = 12
mockConversationView.addSubview(messagesStackView)
messagesStackView.autoPinEdgesToSuperviewMargins()
*/
}
private func buildPaletteView(colorViews: [ColorView]) -> UIView {
let paletteView = UIView()
let paletteMargin = ScaleFromIPhone5(12)
paletteView.layoutMargins = UIEdgeInsets(top: paletteMargin, left: paletteMargin, bottom: 0, right: paletteMargin)
let kRowLength = 4
let rows: [UIView] = colorViews.chunked(by: kRowLength).map { colorViewsInRow in
let row = UIStackView(arrangedSubviews: colorViewsInRow)
row.distribution = UIStackView.Distribution.equalSpacing
return row
}
let rowsStackView = UIStackView(arrangedSubviews: rows)
rowsStackView.axis = .vertical
rowsStackView.spacing = ScaleFromIPhone5To7Plus(12, 30)
paletteView.addSubview(rowsStackView)
rowsStackView.ows_autoPinToSuperviewMargins()
// no-op gesture to keep taps from dismissing SheetView
paletteView.addGestureRecognizer(UITapGestureRecognizer(target: nil, action: nil))
return paletteView
}
}
// MARK: Mock Classes for rendering demo conversation
/*
@objc
private class MockConversationViewItem: NSObject, ConversationViewItem {
var userCanDeleteGroupMessage: Bool = false
var isRSSFeed: Bool = false
var interaction: TSInteraction = TSMessage()
var interactionType: OWSInteractionType = OWSInteractionType.unknown
var quotedReply: OWSQuotedReplyModel?
var isGroupThread: Bool = false
var hasBodyText: Bool = true
var isQuotedReply: Bool = false
var hasQuotedAttachment: Bool = false
var hasQuotedText: Bool = false
var hasCellHeader: Bool = false
var isExpiringMessage: Bool = false
var shouldShowDate: Bool = false
var shouldShowSenderAvatar: Bool = false
var senderName: NSAttributedString?
var shouldHideFooter: Bool = false
var isFirstInCluster: Bool = true
var isLastInCluster: Bool = true
var unreadIndicator: OWSUnreadIndicator?
var lastAudioMessageView: OWSAudioMessageView?
var audioDurationSeconds: CGFloat = 0
var audioProgressSeconds: CGFloat = 0
var messageCellType: OWSMessageCellType = .textOnlyMessage
var displayableBodyText: DisplayableText?
var attachmentStream: TSAttachmentStream?
var attachmentPointer: TSAttachmentPointer?
var mediaSize: CGSize = .zero
var displayableQuotedText: DisplayableText?
var quotedAttachmentMimetype: String?
var quotedRecipientId: String?
var didCellMediaFailToLoad: Bool = false
var contactShare: ContactShareViewModel?
var systemMessageText: String?
var authorConversationColorName: String?
var hasBodyTextActionContent: Bool = false
var hasMediaActionContent: Bool = false
var mediaAlbumItems: [ConversationMediaAlbumItem]?
var hasCachedLayoutState: Bool = false
var linkPreview: OWSLinkPreview?
var linkPreviewAttachment: TSAttachment?
override init() {
super.init()
}
func itemId() -> String {
return interaction.uniqueId!
}
func dequeueCell(for collectionView: UICollectionView, indexPath: IndexPath) -> ConversationViewCell {
owsFailDebug("unexpected invocation")
return ConversationViewCell(forAutoLayout: ())
}
func replace(_ interaction: TSInteraction, transaction: YapDatabaseReadTransaction) {
owsFailDebug("unexpected invocation")
return
}
func clearCachedLayoutState() {
owsFailDebug("unexpected invocation")
return
}
func copyMediaAction() {
owsFailDebug("unexpected invocation")
return
}
func copyTextAction() {
owsFailDebug("unexpected invocation")
return
}
func shareMediaAction() {
owsFailDebug("unexpected invocation")
return
}
func shareTextAction() {
owsFailDebug("unexpected invocation")
return
}
func saveMediaAction() {
owsFailDebug("unexpected invocation")
return
}
func deleteAction() {
owsFailDebug("unexpected invocation")
return
}
func canCopyMedia() -> Bool {
owsFailDebug("unexpected invocation")
return false
}
func canSaveMedia() -> Bool {
owsFailDebug("unexpected invocation")
return false
}
func audioPlaybackState() -> AudioPlaybackState {
owsFailDebug("unexpected invocation")
return AudioPlaybackState.paused
}
func setAudioPlaybackState(_ state: AudioPlaybackState) {
owsFailDebug("unexpected invocation")
return
}
func setAudioProgress(_ progress: CGFloat, duration: CGFloat) {
owsFailDebug("unexpected invocation")
return
}
func cellSize() -> CGSize {
owsFailDebug("unexpected invocation")
return CGSize.zero
}
func vSpacing(withPreviousLayoutItem previousLayoutItem: ConversationViewLayoutItem) -> CGFloat {
owsFailDebug("unexpected invocation")
return 2
}
func firstValidAlbumAttachment() -> TSAttachmentStream? {
owsFailDebug("unexpected invocation")
return nil
}
func mediaAlbumHasFailedAttachment() -> Bool {
owsFailDebug("unexpected invocation")
return false
}
}
*/
private class MockIncomingMessage: TSIncomingMessage {
init(messageBody: String) {
super.init(incomingMessageWithTimestamp: NSDate.ows_millisecondTimeStamp(),
in: TSThread(),
authorId: "+fake-id",
sourceDeviceId: 1,
messageBody: messageBody,
attachmentIds: [],
expiresInSeconds: 0,
quotedMessage: nil,
contactShare: nil,
linkPreview: nil,
serverTimestamp: nil,
wasReceivedByUD: false)
}
required init(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
required init(dictionary dictionaryValue: [String: Any]!) throws {
fatalError("init(dictionary:) has not been implemented")
}
override func save(with transaction: YapDatabaseReadWriteTransaction) {
// no - op
owsFailDebug("shouldn't save mock message")
}
}
private class MockOutgoingMessage: TSOutgoingMessage {
init(messageBody: String) {
super.init(outgoingMessageWithTimestamp: NSDate.ows_millisecondTimeStamp(),
in: nil,
messageBody: messageBody,
attachmentIds: [],
expiresInSeconds: 0,
expireStartedAt: 0,
isVoiceMessage: false,
groupMetaMessage: .unspecified,
quotedMessage: nil,
contactShare: nil,
linkPreview: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
required init(dictionary dictionaryValue: [String: Any]!) throws {
fatalError("init(dictionary:) has not been implemented")
}
override func save(with transaction: YapDatabaseReadWriteTransaction) {
// no - op
owsFailDebug("shouldn't save mock message")
}
class MockOutgoingMessageRecipientState: TSOutgoingMessageRecipientState {
override var state: OWSOutgoingMessageRecipientState {
return OWSOutgoingMessageRecipientState.sent
}
override var deliveryTimestamp: NSNumber? {
return NSNumber(value: NSDate.ows_millisecondTimeStamp())
}
override var readTimestamp: NSNumber? {
return NSNumber(value: NSDate.ows_millisecondTimeStamp())
}
}
override func readRecipientIds() -> [String] {
// makes message appear as read
return ["fake-non-empty-id"]
}
override func recipientState(forRecipientId recipientId: String) -> TSOutgoingMessageRecipientState? {
return MockOutgoingMessageRecipientState()
}
}

View File

@ -1,162 +0,0 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
import UIKit
import Contacts
import SignalUtilitiesKit
class ContactCell: UITableViewCell {
public static let kSeparatorHInset: CGFloat = CGFloat(kAvatarDiameter) + 16 + 8
static let kAvatarSpacing: CGFloat = 6
static let kAvatarDiameter: UInt = 40
let contactImageView: AvatarImageView
let textStackView: UIStackView
let titleLabel: UILabel
var subtitleLabel: UILabel
var contact: Contact?
var showsWhenSelected: Bool = false
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
self.contactImageView = AvatarImageView()
self.textStackView = UIStackView()
self.titleLabel = UILabel()
self.titleLabel.font = UIFont.ows_dynamicTypeBody
self.subtitleLabel = UILabel()
self.subtitleLabel.font = UIFont.ows_dynamicTypeSubheadline
super.init(style: style, reuseIdentifier: reuseIdentifier)
selectionStyle = UITableViewCell.SelectionStyle.none
textStackView.axis = .vertical
textStackView.addArrangedSubview(titleLabel)
contactImageView.autoSetDimensions(to: CGSize(width: CGFloat(ContactCell.kAvatarDiameter), height: CGFloat(ContactCell.kAvatarDiameter)))
let contentColumns: UIStackView = UIStackView(arrangedSubviews: [contactImageView, textStackView])
contentColumns.axis = .horizontal
contentColumns.spacing = ContactCell.kAvatarSpacing
contentColumns.alignment = .center
self.contentView.addSubview(contentColumns)
contentColumns.autoPinEdgesToSuperviewMargins()
NotificationCenter.default.addObserver(self, selector: #selector(self.didChangePreferredContentSize), name: UIContentSizeCategory.didChangeNotification, object: nil)
}
required init?(coder aDecoder: NSCoder) {
notImplemented()
}
override func prepareForReuse() {
accessoryType = .none
self.subtitleLabel.removeFromSuperview()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
if showsWhenSelected {
accessoryType = selected ? .checkmark : .none
}
}
@objc func didChangePreferredContentSize() {
self.titleLabel.font = UIFont.ows_dynamicTypeBody
self.subtitleLabel.font = UIFont.ows_dynamicTypeSubheadline
}
func configure(contact: Contact, subtitleType: SubtitleCellValue, showsWhenSelected: Bool, contactsManager: OWSContactsManager) {
OWSTableItem.configureCell(self)
self.contact = contact
self.showsWhenSelected = showsWhenSelected
self.titleLabel.textColor = Theme.primaryColor
self.subtitleLabel.textColor = Theme.secondaryColor
let cnContact = contactsManager.cnContact(withId: contact.cnContactId)
titleLabel.attributedText = cnContact?.formattedFullName(font: titleLabel.font)
updateSubtitle(subtitleType: subtitleType, contact: contact)
if let contactImage = contactsManager.avatarImage(forCNContactId: contact.cnContactId) {
contactImageView.image = contactImage
} else {
let contactIdForDeterminingBackgroundColor: String
if let signalId = contact.parsedPhoneNumbers.first?.toE164() {
contactIdForDeterminingBackgroundColor = signalId
} else {
contactIdForDeterminingBackgroundColor = contact.fullName
}
let avatarBuilder = OWSContactAvatarBuilder(nonSignalName: contact.fullName,
colorSeed: contactIdForDeterminingBackgroundColor,
diameter: ContactCell.kAvatarDiameter)
contactImageView.image = avatarBuilder.build()
}
}
func updateSubtitle(subtitleType: SubtitleCellValue, contact: Contact) {
switch subtitleType {
case .none:
assert(self.subtitleLabel.superview == nil)
break
case .phoneNumber:
self.textStackView.addArrangedSubview(self.subtitleLabel)
if let firstPhoneNumber = contact.userTextPhoneNumbers.first {
self.subtitleLabel.text = firstPhoneNumber
} else {
self.subtitleLabel.text = NSLocalizedString("CONTACT_PICKER_NO_PHONE_NUMBERS_AVAILABLE", comment: "table cell subtitle when contact card has no known phone number")
}
case .email:
self.textStackView.addArrangedSubview(self.subtitleLabel)
if let firstEmail = contact.emails.first {
self.subtitleLabel.text = firstEmail
} else {
self.subtitleLabel.text = NSLocalizedString("CONTACT_PICKER_NO_EMAILS_AVAILABLE", comment: "table cell subtitle when contact card has no email")
}
}
}
}
fileprivate extension CNContact {
/**
* Bold the sorting portion of the name. e.g. if we sort by family name, bold the family name.
*/
func formattedFullName(font: UIFont) -> NSAttributedString? {
let keyToHighlight = ContactSortOrder == .familyName ? CNContactFamilyNameKey : CNContactGivenNameKey
let boldDescriptor = font.fontDescriptor.withSymbolicTraits(.traitBold)
let boldAttributes = [
NSAttributedString.Key.font: UIFont(descriptor: boldDescriptor!, size: 0)
]
if let attributedName = CNContactFormatter.attributedString(from: self, style: .fullName, defaultAttributes: nil) {
let highlightedName = attributedName.mutableCopy() as! NSMutableAttributedString
highlightedName.enumerateAttributes(in: NSRange(location: 0, length: highlightedName.length), options: [], using: { (attrs, range, _) in
if let property = attrs[NSAttributedString.Key(rawValue: CNContactPropertyAttribute)] as? String, property == keyToHighlight {
highlightedName.addAttributes(boldAttributes, range: range)
}
})
return highlightedName
}
if let emailAddress = self.emailAddresses.first?.value {
return NSAttributedString(string: emailAddress as String, attributes: boldAttributes)
}
if let phoneNumber = self.phoneNumbers.first?.value.stringValue {
return NSAttributedString(string: phoneNumber, attributes: boldAttributes)
}
return nil
}
}

View File

@ -1,215 +0,0 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
import Foundation
import SignalUtilitiesKit
import ContactsUI
import MessageUI
@objc
public protocol ContactShareViewHelperDelegate: class {
func didCreateOrEditContact()
}
@objc
public class ContactShareViewHelper: NSObject, CNContactViewControllerDelegate {
@objc
weak var delegate: ContactShareViewHelperDelegate?
let contactsManager: OWSContactsManager
@objc
public required init(contactsManager: OWSContactsManager) {
AssertIsOnMainThread()
self.contactsManager = contactsManager
super.init()
}
// MARK: Actions
@objc
public func sendMessage(contactShare: ContactShareViewModel, fromViewController: UIViewController) {
Logger.info("")
presentThreadAndPeform(action: .compose, contactShare: contactShare, fromViewController: fromViewController)
}
@objc
public func audioCall(contactShare: ContactShareViewModel, fromViewController: UIViewController) {
Logger.info("")
presentThreadAndPeform(action: .audioCall, contactShare: contactShare, fromViewController: fromViewController)
}
@objc
public func videoCall(contactShare: ContactShareViewModel, fromViewController: UIViewController) {
Logger.info("")
presentThreadAndPeform(action: .videoCall, contactShare: contactShare, fromViewController: fromViewController)
}
private func presentThreadAndPeform(action: ConversationViewAction, contactShare: ContactShareViewModel, fromViewController: UIViewController) {
// TODO: We're taking the first Signal account id. We might
// want to let the user select if there's more than one.
let phoneNumbers = contactShare.systemContactsWithSignalAccountPhoneNumbers(contactsManager)
guard phoneNumbers.count > 0 else {
owsFailDebug("missing Signal recipient id.")
return
}
guard phoneNumbers.count > 1 else {
let recipientId = phoneNumbers.first!
SignalApp.shared().presentConversation(forRecipientId: recipientId, action: action, animated: true)
return
}
showPhoneNumberPicker(phoneNumbers: phoneNumbers, fromViewController: fromViewController, completion: { (recipientId) in
SignalApp.shared().presentConversation(forRecipientId: recipientId, action: action, animated: true)
})
}
@objc
public func showInviteContact(contactShare: ContactShareViewModel, fromViewController: UIViewController) {
Logger.info("")
guard MFMessageComposeViewController.canSendText() else {
Logger.info("Device cannot send text")
OWSAlerts.showErrorAlert(message: NSLocalizedString("UNSUPPORTED_FEATURE_ERROR", comment: ""))
return
}
let phoneNumbers = contactShare.e164PhoneNumbers()
guard phoneNumbers.count > 0 else {
owsFailDebug("no phone numbers.")
return
}
let inviteFlow = InviteFlow(presentingViewController: fromViewController)
inviteFlow.sendSMSTo(phoneNumbers: phoneNumbers)
}
@objc
func showAddToContacts(contactShare: ContactShareViewModel, fromViewController: UIViewController) {
Logger.info("")
let actionSheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
actionSheet.addAction(UIAlertAction(title: NSLocalizedString("CONVERSATION_SETTINGS_NEW_CONTACT",
comment: "Label for 'new contact' button in conversation settings view."),
style: .default) { _ in
self.didPressCreateNewContact(contactShare: contactShare, fromViewController: fromViewController)
})
actionSheet.addAction(UIAlertAction(title: NSLocalizedString("CONVERSATION_SETTINGS_ADD_TO_EXISTING_CONTACT",
comment: "Label for 'new contact' button in conversation settings view."),
style: .default) { _ in
self.didPressAddToExistingContact(contactShare: contactShare, fromViewController: fromViewController)
})
actionSheet.addAction(OWSAlerts.cancelAction)
fromViewController.presentAlert(actionSheet)
}
private func showPhoneNumberPicker(phoneNumbers: [String], fromViewController: UIViewController, completion :@escaping ((String) -> Void)) {
let actionSheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
for phoneNumber in phoneNumbers {
actionSheet.addAction(UIAlertAction(title: PhoneNumber.bestEffortLocalizedPhoneNumber(withE164: phoneNumber),
style: .default) { _ in
completion(phoneNumber)
})
}
actionSheet.addAction(OWSAlerts.cancelAction)
fromViewController.presentAlert(actionSheet)
}
func didPressCreateNewContact(contactShare: ContactShareViewModel, fromViewController: UIViewController) {
Logger.info("")
presentNewContactView(contactShare: contactShare, fromViewController: fromViewController)
}
func didPressAddToExistingContact(contactShare: ContactShareViewModel, fromViewController: UIViewController) {
Logger.info("")
presentSelectAddToExistingContactView(contactShare: contactShare, fromViewController: fromViewController)
}
// MARK: -
private func presentNewContactView(contactShare: ContactShareViewModel, fromViewController: UIViewController) {
guard contactsManager.supportsContactEditing else {
owsFailDebug("Contact editing not supported")
return
}
guard let systemContact = OWSContacts.systemContact(for: contactShare.dbRecord, imageData: contactShare.avatarImageData) else {
owsFailDebug("Could not derive system contact.")
return
}
guard contactsManager.isSystemContactsAuthorized else {
ContactsViewHelper.presentMissingContactAccessAlertController(from: fromViewController)
return
}
let contactViewController = CNContactViewController(forNewContact: systemContact)
contactViewController.delegate = self
contactViewController.allowsActions = false
contactViewController.allowsEditing = true
contactViewController.navigationItem.leftBarButtonItem = UIBarButtonItem(title: CommonStrings.cancelButton,
style: .plain,
target: self,
action: #selector(didFinishEditingContact))
let modal = OWSNavigationController(rootViewController: contactViewController)
fromViewController.present(modal, animated: true)
}
private func presentSelectAddToExistingContactView(contactShare: ContactShareViewModel, fromViewController: UIViewController) {
guard contactsManager.supportsContactEditing else {
owsFailDebug("Contact editing not supported")
return
}
guard contactsManager.isSystemContactsAuthorized else {
ContactsViewHelper.presentMissingContactAccessAlertController(from: fromViewController)
return
}
guard let navigationController = fromViewController.navigationController else {
owsFailDebug("missing navigationController")
return
}
let viewController = AddContactShareToExistingContactViewController(contactShare: contactShare)
navigationController.pushViewController(viewController, animated: true)
}
// MARK: - CNContactViewControllerDelegate
@objc public func contactViewController(_ viewController: CNContactViewController, didCompleteWith contact: CNContact?) {
Logger.info("")
guard let delegate = delegate else {
owsFailDebug("missing delegate")
return
}
delegate.didCreateOrEditContact()
}
@objc public func didFinishEditingContact() {
Logger.info("")
guard let delegate = delegate else {
owsFailDebug("missing delegate")
return
}
delegate.didCreateOrEditContact()
}
}

View File

@ -1,679 +0,0 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
import Foundation
import SignalUtilitiesKit
import SignalUtilitiesKit
import Reachability
import ContactsUI
import MessageUI
class ContactViewController: OWSViewController, ContactShareViewHelperDelegate {
enum ContactViewMode {
case systemContactWithSignal,
systemContactWithoutSignal,
nonSystemContact,
noPhoneNumber,
unknown
}
private var hasLoadedView = false
private var viewMode = ContactViewMode.unknown {
didSet {
AssertIsOnMainThread()
if oldValue != viewMode && hasLoadedView {
updateContent()
}
}
}
private let contactsManager: OWSContactsManager
private var reachability: Reachability?
private let contactShare: ContactShareViewModel
private var contactShareViewHelper: ContactShareViewHelper
private weak var postDismissNavigationController: UINavigationController?
// MARK: - Initializers
@available(*, unavailable, message: "use init(call:) constructor instead.")
required init?(coder aDecoder: NSCoder) {
notImplemented()
}
@objc
required init(contactShare: ContactShareViewModel) {
contactsManager = Environment.shared.contactsManager
self.contactShare = contactShare
self.contactShareViewHelper = ContactShareViewHelper(contactsManager: contactsManager)
super.init(nibName: nil, bundle: nil)
contactShareViewHelper.delegate = self
updateMode()
NotificationCenter.default.addObserver(forName: .OWSContactsManagerSignalAccountsDidChange, object: nil, queue: nil) { [weak self] _ in
guard let strongSelf = self else { return }
strongSelf.updateMode()
}
reachability = Reachability.forInternetConnection()
NotificationCenter.default.addObserver(forName: .reachabilityChanged, object: nil, queue: nil) { [weak self] _ in
guard let strongSelf = self else { return }
strongSelf.updateMode()
}
}
// MARK: - View Lifecycle
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
guard let navigationController = self.navigationController else {
owsFailDebug("navigationController was unexpectedly nil")
return
}
// self.navigationController is nil in viewWillDisappear when transition via message/call buttons
// so we maintain our own reference to restore the navigation bars.
postDismissNavigationController = navigationController
navigationController.isNavigationBarHidden = true
contactsManager.requestSystemContactsOnce(completion: { [weak self] _ in
guard let strongSelf = self else { return }
strongSelf.updateMode()
})
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if self.presentedViewController == nil {
// No need to do this when we're disappearing due to a modal presentation.
// We'll eventually return to to this view and need to hide again. But also, there is a visible
// animation glitch where the navigation bar for this view controller starts to appear while
// the whole nav stack is about to be obscured by the modal we are presenting.
guard let postDismissNavigationController = self.postDismissNavigationController else {
owsFailDebug("postDismissNavigationController was unexpectedly nil")
return
}
postDismissNavigationController.setNavigationBarHidden(false, animated: animated)
}
}
override func loadView() {
super.loadView()
self.view.preservesSuperviewLayoutMargins = false
self.view.backgroundColor = heroBackgroundColor()
updateContent()
hasLoadedView = true
}
private func updateMode() {
AssertIsOnMainThread()
guard contactShare.e164PhoneNumbers().count > 0 else {
viewMode = .noPhoneNumber
return
}
if systemContactsWithSignalAccountsForContact().count > 0 {
viewMode = .systemContactWithSignal
return
}
if systemContactsForContact().count > 0 {
viewMode = .systemContactWithoutSignal
return
}
viewMode = .nonSystemContact
}
private func systemContactsWithSignalAccountsForContact() -> [String] {
AssertIsOnMainThread()
return contactShare.systemContactsWithSignalAccountPhoneNumbers(contactsManager)
}
private func systemContactsForContact() -> [String] {
AssertIsOnMainThread()
return contactShare.systemContactPhoneNumbers(contactsManager)
}
private func updateContent() {
AssertIsOnMainThread()
guard let rootView = self.view else {
owsFailDebug("missing root view.")
return
}
for subview in rootView.subviews {
subview.removeFromSuperview()
}
let topView = createTopView()
rootView.addSubview(topView)
topView.autoPinEdge(.top, to: .top, of: view)
topView.autoPinWidthToSuperview()
// This view provides a background "below the fold".
let bottomView = UIView.container()
bottomView.backgroundColor = Theme.backgroundColor
self.view.addSubview(bottomView)
bottomView.layoutMargins = .zero
bottomView.autoPinWidthToSuperview()
bottomView.autoPinEdge(.top, to: .bottom, of: topView)
bottomView.autoPinEdge(toSuperviewEdge: .bottom)
let scrollView = UIScrollView()
scrollView.preservesSuperviewLayoutMargins = false
self.view.addSubview(scrollView)
scrollView.layoutMargins = .zero
scrollView.autoPinWidthToSuperview()
scrollView.autoPinEdge(.top, to: .bottom, of: topView)
scrollView.autoPinEdge(toSuperviewEdge: .bottom)
let fieldsView = createFieldsView()
scrollView.addSubview(fieldsView)
fieldsView.autoPinLeadingToSuperviewMargin()
fieldsView.autoPinTrailingToSuperviewMargin()
fieldsView.autoPinEdge(toSuperviewEdge: .top)
fieldsView.autoPinEdge(toSuperviewEdge: .bottom)
}
private func heroBackgroundColor() -> UIColor {
return (Theme.isDarkThemeEnabled
? UIColor(rgbHex: 0x272727)
: UIColor(rgbHex: 0xefeff4))
}
private func createTopView() -> UIView {
AssertIsOnMainThread()
let topView = UIView.container()
topView.backgroundColor = heroBackgroundColor()
topView.preservesSuperviewLayoutMargins = false
// Back Button
let backButtonSize = CGFloat(50)
let backButton = TappableView(actionBlock: { [weak self] in
guard let strongSelf = self else { return }
strongSelf.didPressDismiss()
})
backButton.autoSetDimension(.width, toSize: backButtonSize)
backButton.autoSetDimension(.height, toSize: backButtonSize)
topView.addSubview(backButton)
backButton.autoPinEdge(toSuperviewEdge: .top)
backButton.autoPinLeadingToSuperviewMargin()
let backIconName = (CurrentAppContext().isRTL ? "system_disclosure_indicator" : "system_disclosure_indicator_rtl")
guard let backIconImage = UIImage(named: backIconName) else {
owsFailDebug("missing icon.")
return topView
}
let backIconView = UIImageView(image: backIconImage.withRenderingMode(.alwaysTemplate))
backIconView.contentMode = .scaleAspectFit
backIconView.tintColor = Theme.primaryColor.withAlphaComponent(0.6)
backButton.addSubview(backIconView)
backIconView.autoCenterInSuperview()
let avatarSize: CGFloat = 100
let avatarView = AvatarImageView()
avatarView.image = contactShare.getAvatarImage(diameter: avatarSize, contactsManager: contactsManager)
topView.addSubview(avatarView)
avatarView.autoPinEdge(toSuperviewEdge: .top, withInset: 20)
avatarView.autoHCenterInSuperview()
avatarView.autoSetDimension(.width, toSize: avatarSize)
avatarView.autoSetDimension(.height, toSize: avatarSize)
let nameLabel = UILabel()
nameLabel.text = contactShare.displayName
nameLabel.font = UIFont.ows_dynamicTypeTitle1
nameLabel.textColor = Theme.primaryColor
nameLabel.lineBreakMode = .byTruncatingTail
nameLabel.textAlignment = .center
topView.addSubview(nameLabel)
nameLabel.autoPinEdge(.top, to: .bottom, of: avatarView, withOffset: 10)
nameLabel.autoPinLeadingToSuperviewMargin(withInset: hMargin)
nameLabel.autoPinTrailingToSuperviewMargin(withInset: hMargin)
var lastView: UIView = nameLabel
for phoneNumber in systemContactsWithSignalAccountsForContact() {
let phoneNumberLabel = UILabel()
phoneNumberLabel.text = PhoneNumber.bestEffortLocalizedPhoneNumber(withE164: phoneNumber)
phoneNumberLabel.font = UIFont.ows_dynamicTypeFootnote
phoneNumberLabel.textColor = Theme.primaryColor
phoneNumberLabel.lineBreakMode = .byTruncatingTail
phoneNumberLabel.textAlignment = .center
topView.addSubview(phoneNumberLabel)
phoneNumberLabel.autoPinEdge(.top, to: .bottom, of: lastView, withOffset: 5)
phoneNumberLabel.autoPinLeadingToSuperviewMargin(withInset: hMargin)
phoneNumberLabel.autoPinTrailingToSuperviewMargin(withInset: hMargin)
lastView = phoneNumberLabel
}
switch viewMode {
case .systemContactWithSignal:
// Show actions buttons for system contacts with a Signal account.
let stackView = UIStackView()
stackView.axis = .horizontal
stackView.distribution = .fillEqually
stackView.addArrangedSubview(createCircleActionButton(text: NSLocalizedString("ACTION_SEND_MESSAGE",
comment: "Label for 'send message' button in contact view."),
imageName: "contact_view_message",
actionBlock: { [weak self] in
guard let strongSelf = self else { return }
strongSelf.didPressSendMessage()
}))
stackView.addArrangedSubview(createCircleActionButton(text: NSLocalizedString("ACTION_AUDIO_CALL",
comment: "Label for 'audio call' button in contact view."),
imageName: "contact_view_audio_call",
actionBlock: { [weak self] in
guard let strongSelf = self else { return }
strongSelf.didPressAudioCall()
}))
stackView.addArrangedSubview(createCircleActionButton(text: NSLocalizedString("ACTION_VIDEO_CALL",
comment: "Label for 'video call' button in contact view."),
imageName: "contact_view_video_call",
actionBlock: { [weak self] in
guard let strongSelf = self else { return }
strongSelf.didPressVideoCall()
}))
topView.addSubview(stackView)
stackView.autoPinEdge(.top, to: .bottom, of: lastView, withOffset: 20)
stackView.autoPinLeadingToSuperviewMargin(withInset: hMargin)
stackView.autoPinTrailingToSuperviewMargin(withInset: hMargin)
lastView = stackView
case .systemContactWithoutSignal:
// Show invite button for system contacts without a Signal account.
let inviteButton = createLargePillButton(text: NSLocalizedString("ACTION_INVITE",
comment: "Label for 'invite' button in contact view."),
actionBlock: { [weak self] in
guard let strongSelf = self else { return }
strongSelf.didPressInvite()
})
topView.addSubview(inviteButton)
inviteButton.autoPinEdge(.top, to: .bottom, of: lastView, withOffset: 20)
inviteButton.autoPinLeadingToSuperviewMargin(withInset: 55)
inviteButton.autoPinTrailingToSuperviewMargin(withInset: 55)
lastView = inviteButton
case .nonSystemContact:
// Show no action buttons for non-system contacts.
break
case .noPhoneNumber:
// Show no action buttons for contacts without a phone number.
break
case .unknown:
let activityIndicator = UIActivityIndicatorView(style: .whiteLarge)
topView.addSubview(activityIndicator)
activityIndicator.autoPinEdge(.top, to: .bottom, of: lastView, withOffset: 10)
activityIndicator.autoHCenterInSuperview()
lastView = activityIndicator
break
}
// Always show "add to contacts" button.
let addToContactsButton = createLargePillButton(text: NSLocalizedString("CONVERSATION_VIEW_ADD_TO_CONTACTS_OFFER",
comment: "Message shown in conversation view that offers to add an unknown user to your phone's contacts."),
actionBlock: { [weak self] in
guard let strongSelf = self else { return }
strongSelf.didPressAddToContacts()
})
topView.addSubview(addToContactsButton)
addToContactsButton.autoPinEdge(.top, to: .bottom, of: lastView, withOffset: 20)
addToContactsButton.autoPinLeadingToSuperviewMargin(withInset: 55)
addToContactsButton.autoPinTrailingToSuperviewMargin(withInset: 55)
lastView = addToContactsButton
lastView.autoPinEdge(toSuperviewEdge: .bottom, withInset: 15)
return topView
}
private func createFieldsView() -> UIView {
AssertIsOnMainThread()
var rows = [UIView]()
// TODO: Not designed yet.
// if viewMode == .systemContactWithSignal ||
// viewMode == .systemContactWithoutSignal {
// addRow(createActionRow(labelText:NSLocalizedString("ACTION_SHARE_CONTACT",
// comment:"Label for 'share contact' button."),
// action:#selector(didPressShareContact)))
// }
if let organizationName = contactShare.name.organizationName?.ows_stripped() {
if (contactShare.name.hasAnyNamePart() &&
organizationName.count > 0) {
rows.append(ContactFieldView.contactFieldView(forOrganizationName: organizationName,
layoutMargins: UIEdgeInsets(top: 5, left: hMargin, bottom: 5, right: hMargin)))
}
}
for phoneNumber in contactShare.phoneNumbers {
rows.append(ContactFieldView.contactFieldView(forPhoneNumber: phoneNumber,
layoutMargins: UIEdgeInsets(top: 5, left: hMargin, bottom: 5, right: hMargin),
actionBlock: { [weak self] in
guard let strongSelf = self else { return }
strongSelf.didPressPhoneNumber(phoneNumber: phoneNumber)
}))
}
for email in contactShare.emails {
rows.append(ContactFieldView.contactFieldView(forEmail: email,
layoutMargins: UIEdgeInsets(top: 5, left: hMargin, bottom: 5, right: hMargin),
actionBlock: { [weak self] in
guard let strongSelf = self else { return }
strongSelf.didPressEmail(email: email)
}))
}
for address in contactShare.addresses {
rows.append(ContactFieldView.contactFieldView(forAddress: address,
layoutMargins: UIEdgeInsets(top: 5, left: hMargin, bottom: 5, right: hMargin),
actionBlock: { [weak self] in
guard let strongSelf = self else { return }
strongSelf.didPressAddress(address: address)
}))
}
return ContactFieldView(rows: rows, hMargin: hMargin)
}
private let hMargin = CGFloat(16)
private func createActionRow(labelText: String, action: Selector) -> UIView {
let row = UIView()
row.layoutMargins.left = 0
row.layoutMargins.right = 0
row.isUserInteractionEnabled = true
row.addGestureRecognizer(UITapGestureRecognizer(target: self, action: action))
let label = UILabel()
label.text = labelText
label.font = UIFont.ows_dynamicTypeBody
label.textColor = UIColor.ows_materialBlue
label.lineBreakMode = .byTruncatingTail
row.addSubview(label)
label.autoPinTopToSuperviewMargin()
label.autoPinBottomToSuperviewMargin()
label.autoPinLeadingToSuperviewMargin(withInset: hMargin)
label.autoPinTrailingToSuperviewMargin(withInset: hMargin)
return row
}
// TODO: Use real assets.
private func createCircleActionButton(text: String, imageName: String, actionBlock : @escaping () -> Void) -> UIView {
let buttonSize = CGFloat(50)
let button = TappableView(actionBlock: actionBlock)
button.layoutMargins = .zero
button.autoSetDimension(.width, toSize: buttonSize, relation: .greaterThanOrEqual)
let circleView = UIView()
circleView.backgroundColor = Theme.backgroundColor
circleView.autoSetDimension(.width, toSize: buttonSize)
circleView.autoSetDimension(.height, toSize: buttonSize)
circleView.layer.cornerRadius = buttonSize * 0.5
button.addSubview(circleView)
circleView.autoPinEdge(toSuperviewEdge: .top)
circleView.autoHCenterInSuperview()
guard let image = UIImage(named: imageName) else {
owsFailDebug("missing image.")
return button
}
let imageView = UIImageView(image: image.withRenderingMode(.alwaysTemplate))
imageView.tintColor = Theme.primaryColor.withAlphaComponent(0.6)
circleView.addSubview(imageView)
imageView.autoCenterInSuperview()
let label = UILabel()
label.text = text
label.font = UIFont.ows_dynamicTypeCaption2
label.textColor = Theme.primaryColor
label.lineBreakMode = .byTruncatingTail
label.textAlignment = .center
button.addSubview(label)
label.autoPinEdge(.top, to: .bottom, of: circleView, withOffset: 3)
label.autoPinEdge(toSuperviewEdge: .bottom)
label.autoPinLeadingToSuperviewMargin()
label.autoPinTrailingToSuperviewMargin()
return button
}
private func createLargePillButton(text: String, actionBlock : @escaping () -> Void) -> UIView {
let button = TappableView(actionBlock: actionBlock)
button.backgroundColor = Theme.backgroundColor
button.layoutMargins = .zero
button.autoSetDimension(.height, toSize: 45)
button.layer.cornerRadius = 5
let label = UILabel()
label.text = text
label.font = UIFont.ows_dynamicTypeBody
label.textColor = UIColor.ows_materialBlue
label.lineBreakMode = .byTruncatingTail
label.textAlignment = .center
button.addSubview(label)
label.autoPinLeadingToSuperviewMargin(withInset: 20)
label.autoPinTrailingToSuperviewMargin(withInset: 20)
label.autoVCenterInSuperview()
label.autoPinEdge(toSuperviewEdge: .top, withInset: 0, relation: .greaterThanOrEqual)
label.autoPinEdge(toSuperviewEdge: .bottom, withInset: 0, relation: .greaterThanOrEqual)
return button
}
func didPressShareContact(sender: UIGestureRecognizer) {
Logger.info("")
guard sender.state == .recognized else {
return
}
// TODO:
}
func didPressSendMessage() {
Logger.info("")
self.contactShareViewHelper.sendMessage(contactShare: self.contactShare, fromViewController: self)
}
func didPressAudioCall() {
Logger.info("")
self.contactShareViewHelper.audioCall(contactShare: self.contactShare, fromViewController: self)
}
func didPressVideoCall() {
Logger.info("")
self.contactShareViewHelper.videoCall(contactShare: self.contactShare, fromViewController: self)
}
func didPressInvite() {
Logger.info("")
self.contactShareViewHelper.showInviteContact(contactShare: self.contactShare, fromViewController: self)
}
func didPressAddToContacts() {
Logger.info("")
self.contactShareViewHelper.showAddToContacts(contactShare: self.contactShare, fromViewController: self)
}
func didPressDismiss() {
Logger.info("")
guard let navigationController = self.navigationController else {
owsFailDebug("navigationController was unexpectedly nil")
return
}
navigationController.popViewController(animated: true)
}
func didPressPhoneNumber(phoneNumber: OWSContactPhoneNumber) {
Logger.info("")
let actionSheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
if let e164 = phoneNumber.tryToConvertToE164() {
if contactShare.systemContactsWithSignalAccountPhoneNumbers(contactsManager).contains(e164) {
actionSheet.addAction(UIAlertAction(title: NSLocalizedString("ACTION_SEND_MESSAGE",
comment: "Label for 'send message' button in contact view."),
style: .default) { _ in
SignalApp.shared().presentConversation(forRecipientId: e164, action: .compose, animated: true)
})
actionSheet.addAction(UIAlertAction(title: NSLocalizedString("ACTION_AUDIO_CALL",
comment: "Label for 'audio call' button in contact view."),
style: .default) { _ in
SignalApp.shared().presentConversation(forRecipientId: e164, action: .audioCall, animated: true)
})
actionSheet.addAction(UIAlertAction(title: NSLocalizedString("ACTION_VIDEO_CALL",
comment: "Label for 'video call' button in contact view."),
style: .default) { _ in
SignalApp.shared().presentConversation(forRecipientId: e164, action: .videoCall, animated: true)
})
} else {
// TODO: We could offer callPhoneNumberWithSystemCall.
}
}
actionSheet.addAction(UIAlertAction(title: NSLocalizedString("EDIT_ITEM_COPY_ACTION",
comment: "Short name for edit menu item to copy contents of media message."),
style: .default) { _ in
UIPasteboard.general.string = phoneNumber.phoneNumber
})
actionSheet.addAction(OWSAlerts.cancelAction)
presentAlert(actionSheet)
}
func callPhoneNumberWithSystemCall(phoneNumber: OWSContactPhoneNumber) {
Logger.info("")
guard let url = NSURL(string: "tel:\(phoneNumber.phoneNumber)") else {
owsFailDebug("could not open phone number.")
return
}
UIApplication.shared.openURL(url as URL)
}
func didPressEmail(email: OWSContactEmail) {
Logger.info("")
let actionSheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
actionSheet.addAction(UIAlertAction(title: NSLocalizedString("CONTACT_VIEW_OPEN_EMAIL_IN_EMAIL_APP",
comment: "Label for 'open email in email app' button in contact view."),
style: .default) { [weak self] _ in
self?.openEmailInEmailApp(email: email)
})
actionSheet.addAction(UIAlertAction(title: NSLocalizedString("EDIT_ITEM_COPY_ACTION",
comment: "Short name for edit menu item to copy contents of media message."),
style: .default) { _ in
UIPasteboard.general.string = email.email
})
actionSheet.addAction(OWSAlerts.cancelAction)
presentAlert(actionSheet)
}
func openEmailInEmailApp(email: OWSContactEmail) {
Logger.info("")
guard let url = NSURL(string: "mailto:\(email.email)") else {
owsFailDebug("could not open email.")
return
}
UIApplication.shared.openURL(url as URL)
}
func didPressAddress(address: OWSContactAddress) {
Logger.info("")
let actionSheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
actionSheet.addAction(UIAlertAction(title: NSLocalizedString("CONTACT_VIEW_OPEN_ADDRESS_IN_MAPS_APP",
comment: "Label for 'open address in maps app' button in contact view."),
style: .default) { [weak self] _ in
self?.openAddressInMaps(address: address)
})
actionSheet.addAction(UIAlertAction(title: NSLocalizedString("EDIT_ITEM_COPY_ACTION",
comment: "Short name for edit menu item to copy contents of media message."),
style: .default) { [weak self] _ in
guard let strongSelf = self else { return }
UIPasteboard.general.string = strongSelf.formatAddressForQuery(address: address)
})
actionSheet.addAction(OWSAlerts.cancelAction)
presentAlert(actionSheet)
}
func openAddressInMaps(address: OWSContactAddress) {
Logger.info("")
let mapAddress = formatAddressForQuery(address: address)
guard let escapedMapAddress = mapAddress.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else {
owsFailDebug("could not open address.")
return
}
// Note that we use "q" (i.e. query) rather than "address" since we can't assume
// this is a well-formed address.
guard let url = URL(string: "http://maps.apple.com/?q=\(escapedMapAddress)") else {
owsFailDebug("could not open address.")
return
}
UIApplication.shared.openURL(url as URL)
}
func formatAddressForQuery(address: OWSContactAddress) -> String {
Logger.info("")
// Open address in Apple Maps app.
var addressParts = [String]()
let addAddressPart: ((String?) -> Void) = { (part) in
guard let part = part else {
return
}
guard part.count > 0 else {
return
}
addressParts.append(part)
}
addAddressPart(address.street)
addAddressPart(address.neighborhood)
addAddressPart(address.city)
addAddressPart(address.region)
addAddressPart(address.postcode)
addAddressPart(address.country)
return addressParts.joined(separator: ", ")
}
// MARK: - ContactShareViewHelperDelegate
public func didCreateOrEditContact() {
Logger.info("")
updateContent()
self.dismiss(animated: true)
}
}

View File

@ -1,404 +0,0 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
// Originally based on EPContacts
//
// Created by Prabaharan Elangovan on 12/10/15.
// Parts Copyright © 2015 Prabaharan Elangovan. All rights reserved
import UIKit
import Contacts
import SignalUtilitiesKit
@objc
public protocol ContactsPickerDelegate: class {
func contactsPicker(_: ContactsPicker, contactFetchDidFail error: NSError)
func contactsPickerDidCancel(_: ContactsPicker)
func contactsPicker(_: ContactsPicker, didSelectContact contact: Contact)
func contactsPicker(_: ContactsPicker, didSelectMultipleContacts contacts: [Contact])
func contactsPicker(_: ContactsPicker, shouldSelectContact contact: Contact) -> Bool
}
@objc
public enum SubtitleCellValue: Int {
case phoneNumber, email, none
}
@objc
public class ContactsPicker: OWSViewController, UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate {
var tableView: UITableView!
var searchBar: UISearchBar!
// MARK: - Properties
private let contactCellReuseIdentifier = "contactCellReuseIdentifier"
private var contactsManager: OWSContactsManager {
return Environment.shared.contactsManager
}
// HACK: Though we don't have an input accessory view, the VC we are presented above (ConversationVC) does.
// If the app is backgrounded and then foregrounded, when OWSWindowManager calls mainWindow.makeKeyAndVisible
// the ConversationVC's inputAccessoryView will appear *above* us unless we'd previously become first responder.
override public var canBecomeFirstResponder: Bool {
Logger.debug("")
return true
}
override public func becomeFirstResponder() -> Bool {
Logger.debug("")
return super.becomeFirstResponder()
}
override public func resignFirstResponder() -> Bool {
Logger.debug("")
return super.resignFirstResponder()
}
private let collation = UILocalizedIndexedCollation.current()
public var collationForTests: UILocalizedIndexedCollation {
get {
return collation
}
}
private let contactStore = CNContactStore()
// Data Source State
private lazy var sections = [[CNContact]]()
private lazy var filteredSections = [[CNContact]]()
private lazy var selectedContacts = [Contact]()
// Configuration
@objc
public weak var contactsPickerDelegate: ContactsPickerDelegate?
private let subtitleCellType: SubtitleCellValue
private let allowsMultipleSelection: Bool
private let allowedContactKeys: [CNKeyDescriptor] = ContactsFrameworkContactStoreAdaptee.allowedContactKeys
// MARK: - Initializers
@objc
required public init(allowsMultipleSelection: Bool, subtitleCellType: SubtitleCellValue) {
self.allowsMultipleSelection = allowsMultipleSelection
self.subtitleCellType = subtitleCellType
super.init(nibName: nil, bundle: nil)
}
required public init?(coder aDecoder: NSCoder) {
notImplemented()
}
// MARK: - Lifecycle Methods
override public func loadView() {
self.view = UIView()
let tableView = UITableView()
self.tableView = tableView
self.tableView.separatorColor = Theme.cellSeparatorColor
view.addSubview(tableView)
tableView.autoPinEdge(toSuperviewEdge: .top)
tableView.autoPinEdge(toSuperviewEdge: .bottom)
tableView.autoPinEdge(toSuperviewSafeArea: .leading)
tableView.autoPinEdge(toSuperviewSafeArea: .trailing)
tableView.delegate = self
tableView.dataSource = self
let searchBar = OWSSearchBar()
self.searchBar = searchBar
searchBar.delegate = self
searchBar.sizeToFit()
tableView.tableHeaderView = searchBar
}
override open func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = Theme.backgroundColor
self.tableView.backgroundColor = Theme.backgroundColor
searchBar.placeholder = NSLocalizedString("INVITE_FRIENDS_PICKER_SEARCHBAR_PLACEHOLDER", comment: "Search")
// Auto size cells for dynamic type
tableView.estimatedRowHeight = 60.0
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 60
tableView.allowsMultipleSelection = allowsMultipleSelection
tableView.separatorInset = UIEdgeInsets(top: 0, left: ContactCell.kSeparatorHInset, bottom: 0, right: 16)
registerContactCell()
initializeBarButtons()
reloadContacts()
updateSearchResults(searchText: "")
NotificationCenter.default.addObserver(self, selector: #selector(self.didChangePreferredContentSize), name: UIContentSizeCategory.didChangeNotification, object: nil)
}
@objc
public func didChangePreferredContentSize() {
self.tableView.reloadData()
}
private func initializeBarButtons() {
let cancelButton = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(onTouchCancelButton))
self.navigationItem.leftBarButtonItem = cancelButton
if allowsMultipleSelection {
let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(onTouchDoneButton))
self.navigationItem.rightBarButtonItem = doneButton
}
}
private func registerContactCell() {
tableView.register(ContactCell.self, forCellReuseIdentifier: contactCellReuseIdentifier)
}
// MARK: - Contact Operations
private func reloadContacts() {
getContacts( onError: { error in
Logger.error("failed to reload contacts with error:\(error)")
})
}
private func getContacts(onError errorHandler: @escaping (_ error: Error) -> Void) {
switch CNContactStore.authorizationStatus(for: CNEntityType.contacts) {
case CNAuthorizationStatus.denied, CNAuthorizationStatus.restricted:
let title = NSLocalizedString("INVITE_FLOW_REQUIRES_CONTACT_ACCESS_TITLE", comment: "Alert title when contacts disabled while trying to invite contacts to signal")
let body = NSLocalizedString("INVITE_FLOW_REQUIRES_CONTACT_ACCESS_BODY", comment: "Alert body when contacts disabled while trying to invite contacts to signal")
let alert = UIAlertController(title: title, message: body, preferredStyle: .alert)
let dismissText = CommonStrings.cancelButton
let cancelAction = UIAlertAction(title: dismissText, style: .cancel, handler: { _ in
let error = NSError(domain: "contactsPickerErrorDomain", code: 1, userInfo: [NSLocalizedDescriptionKey: "No Contacts Access"])
self.contactsPickerDelegate?.contactsPicker(self, contactFetchDidFail: error)
errorHandler(error)
})
alert.addAction(cancelAction)
let settingsText = CommonStrings.openSettingsButton
let openSettingsAction = UIAlertAction(title: settingsText, style: .default, handler: { (_) in
UIApplication.shared.openSystemSettings()
})
alert.addAction(openSettingsAction)
self.presentAlert(alert)
case CNAuthorizationStatus.notDetermined:
//This case means the user is prompted for the first time for allowing contacts
contactStore.requestAccess(for: CNEntityType.contacts) { (granted, error) -> Void in
//At this point an alert is provided to the user to provide access to contacts. This will get invoked if a user responds to the alert
if granted {
self.getContacts(onError: errorHandler)
} else {
errorHandler(error!)
}
}
case CNAuthorizationStatus.authorized:
//Authorization granted by user for this app.
var contacts = [CNContact]()
do {
let contactFetchRequest = CNContactFetchRequest(keysToFetch: allowedContactKeys)
contactFetchRequest.sortOrder = .userDefault
try contactStore.enumerateContacts(with: contactFetchRequest) { (contact, _) -> Void in
contacts.append(contact)
}
self.sections = collatedContacts(contacts)
} catch let error as NSError {
Logger.error("Failed to fetch contacts with error:\(error)")
}
}
}
func collatedContacts(_ contacts: [CNContact]) -> [[CNContact]] {
let selector: Selector = #selector(getter: CNContact.nameForCollating)
var collated = Array(repeating: [CNContact](), count: collation.sectionTitles.count)
for contact in contacts {
let sectionNumber = collation.section(for: contact, collationStringSelector: selector)
collated[sectionNumber].append(contact)
}
return collated
}
// MARK: - Table View DataSource
open func numberOfSections(in tableView: UITableView) -> Int {
return self.collation.sectionTitles.count
}
open func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let dataSource = filteredSections
guard section < dataSource.count else {
return 0
}
return dataSource[section].count
}
// MARK: - Table View Delegates
open func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: contactCellReuseIdentifier, for: indexPath) as? ContactCell else {
owsFailDebug("cell had unexpected type")
return UITableViewCell()
}
let dataSource = filteredSections
let cnContact = dataSource[indexPath.section][indexPath.row]
let contact = Contact(systemContact: cnContact)
cell.configure(contact: contact, subtitleType: subtitleCellType, showsWhenSelected: self.allowsMultipleSelection, contactsManager: self.contactsManager)
let isSelected = selectedContacts.contains(where: { $0.uniqueId == contact.uniqueId })
cell.isSelected = isSelected
// Make sure we preserve selection across tableView.reloadData which happens when toggling between
// search controller
if (isSelected) {
self.tableView.selectRow(at: indexPath, animated: false, scrollPosition: .none)
} else {
self.tableView.deselectRow(at: indexPath, animated: false)
}
return cell
}
open func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath) as! ContactCell
let deselectedContact = cell.contact!
selectedContacts = selectedContacts.filter {
return $0.uniqueId != deselectedContact.uniqueId
}
}
open func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
Logger.verbose("")
let cell = tableView.cellForRow(at: indexPath) as! ContactCell
let selectedContact = cell.contact!
guard (contactsPickerDelegate == nil || contactsPickerDelegate!.contactsPicker(self, shouldSelectContact: selectedContact)) else {
self.tableView.deselectRow(at: indexPath, animated: false)
return
}
selectedContacts.append(selectedContact)
if !allowsMultipleSelection {
// Single selection code
self.contactsPickerDelegate?.contactsPicker(self, didSelectContact: selectedContact)
}
}
open func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String, at index: Int) -> Int {
return collation.section(forSectionIndexTitle: index)
}
open func sectionIndexTitles(for tableView: UITableView) -> [String]? {
return collation.sectionIndexTitles
}
open func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
let dataSource = filteredSections
guard section < dataSource.count else {
return nil
}
// Don't show empty sections
if dataSource[section].count > 0 {
guard section < collation.sectionTitles.count else {
return nil
}
return collation.sectionTitles[section]
} else {
return nil
}
}
// MARK: - Button Actions
@objc func onTouchCancelButton() {
contactsPickerDelegate?.contactsPickerDidCancel(self)
}
@objc func onTouchDoneButton() {
contactsPickerDelegate?.contactsPicker(self, didSelectMultipleContacts: selectedContacts)
}
// MARK: - Search Actions
open func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
updateSearchResults(searchText: searchText)
}
open func updateSearchResults(searchText: String) {
let predicate: NSPredicate
if searchText.isEmpty {
filteredSections = sections
} else {
do {
predicate = CNContact.predicateForContacts(matchingName: searchText)
let filteredContacts = try contactStore.unifiedContacts(matching: predicate, keysToFetch: allowedContactKeys)
filteredSections = collatedContacts(filteredContacts)
} catch let error as NSError {
Logger.error("updating search results failed with error: \(error)")
}
}
self.tableView.reloadData()
}
}
let ContactSortOrder = computeSortOrder()
func computeSortOrder() -> CNContactSortOrder {
let comparator = CNContact.comparator(forNameSortOrder: .userDefault)
let contact0 = CNMutableContact()
contact0.givenName = "A"
contact0.familyName = "Z"
let contact1 = CNMutableContact()
contact1.givenName = "Z"
contact1.familyName = "A"
let result = comparator(contact0, contact1)
if result == .orderedAscending {
return .givenName
} else {
return .familyName
}
}
fileprivate extension CNContact {
/**
* Sorting Key used by collation
*/
@objc var nameForCollating: String {
get {
if self.familyName.isEmpty && self.givenName.isEmpty {
return self.emailAddresses.first?.value as String? ?? ""
}
let compositeName: String
if ContactSortOrder == .familyName {
compositeName = "\(self.familyName) \(self.givenName)"
} else {
compositeName = "\(self.givenName) \(self.familyName)"
}
return compositeName.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
}
}
}

View File

@ -1,97 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
import Foundation
@objc
class ConversationConfigurationSyncOperation: OWSOperation {
enum ColorSyncOperationError: Error {
case assertionError(description: String)
}
private var dbConnection: YapDatabaseConnection {
return OWSPrimaryStorage.shared().dbReadConnection
}
private var messageSenderJobQueue: MessageSenderJobQueue {
return SSKEnvironment.shared.messageSenderJobQueue
}
private var contactsManager: OWSContactsManager {
return Environment.shared.contactsManager
}
private var syncManager: OWSSyncManagerProtocol {
return SSKEnvironment.shared.syncManager
}
private let thread: TSThread
@objc
public init(thread: TSThread) {
self.thread = thread
super.init()
}
override public func run() {
if let contactThread = thread as? TSContactThread {
sync(contactThread: contactThread)
} else if let groupThread = thread as? TSGroupThread {
sync(groupThread: groupThread)
} else {
self.reportAssertionError(description: "unknown thread type")
}
}
private func reportAssertionError(description: String) {
let error = ColorSyncOperationError.assertionError(description: description)
self.reportError(error)
}
private func sync(contactThread: TSContactThread) {
guard let signalAccount: SignalAccount = self.contactsManager.fetchSignalAccount(forRecipientId: contactThread.contactIdentifier()) else {
reportAssertionError(description: "unable to find signalAccount")
return
}
syncManager.syncContacts(for: [signalAccount]).retainUntilComplete()
}
private func sync(groupThread: TSGroupThread) {
// TODO sync only the affected group
// The current implementation works, but seems wasteful.
// Does desktop handle single group sync correctly?
// What does Android do?
let syncMessage: OWSSyncGroupsMessage = OWSSyncGroupsMessage(groupThread: groupThread)
var dataSource: DataSource?
self.dbConnection.read { transaction in
guard let messageData: Data = syncMessage.buildPlainTextAttachmentData(with: transaction) else {
owsFailDebug("could not serialize sync groups data")
return
}
dataSource = DataSourceValue.dataSource(withSyncMessageData: messageData)
}
guard let attachmentDataSource = dataSource else {
self.reportAssertionError(description: "unable to build attachment data source")
return
}
self.sendConfiguration(attachmentDataSource: attachmentDataSource, syncMessage: syncMessage)
}
private func sendConfiguration(attachmentDataSource: DataSource, syncMessage: OWSOutgoingSyncMessage) {
self.messageSenderJobQueue.add(mediaMessage: syncMessage,
dataSource: attachmentDataSource,
contentType: OWSMimeTypeApplicationOctetStream,
sourceFilename: nil,
caption: nil,
albumMessageId: nil,
isTemporaryAttachment: true)
self.reportSuccess()
}
}

View File

@ -35,23 +35,9 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark - System Cell
- (void)tappedNonBlockingIdentityChangeForRecipientId:(nullable NSString *)signalId;
- (void)tappedInvalidIdentityKeyErrorMessage:(TSInvalidIdentityKeyErrorMessage *)errorMessage;
- (void)tappedCorruptedMessage:(TSErrorMessage *)message;
- (void)resendGroupUpdateForErrorMessage:(TSErrorMessage *)message;
- (void)showFingerprintWithRecipientId:(NSString *)recipientId;
- (void)showConversationSettings;
- (void)handleCallTap:(TSCall *)call;
#pragma mark - Offers
- (void)tappedUnknownContactBlockOfferMessage:(OWSContactOffersInteraction *)interaction;
- (void)tappedAddToContactsOfferMessage:(OWSContactOffersInteraction *)interaction;
- (void)tappedAddToProfileWhitelistOfferMessage:(OWSContactOffersInteraction *)interaction;
#pragma mark - Formatting
- (NSAttributedString *)attributedContactOrProfileNameForPhoneIdentifier:(NSString *)recipientId;
#pragma mark - Caching
@ -61,10 +47,6 @@ NS_ASSUME_NONNULL_BEGIN
- (void)didTapFailedOutgoingMessage:(TSOutgoingMessage *)message;
#pragma mark - Contacts
- (OWSContactsManager *)contactsManager;
@end
#pragma mark -

View File

@ -1,19 +0,0 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "ConversationViewCell.h"
NS_ASSUME_NONNULL_BEGIN
@class OWSContactOffersInteraction;
#pragma mark -
@interface OWSContactOffersCell : ConversationViewCell
+ (NSString *)cellReuseIdentifier;
@end
NS_ASSUME_NONNULL_END

View File

@ -1,263 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "OWSContactOffersCell.h"
#import "ConversationViewItem.h"
#import "Session-Swift.h"
#import <SignalUtilitiesKit/OWSContactOffersInteraction.h>
#import <SignalUtilitiesKit/UIColor+OWS.h>
#import <SignalUtilitiesKit/UIFont+OWS.h>
#import <SignalUtilitiesKit/UIView+OWS.h>
NS_ASSUME_NONNULL_BEGIN
@interface OWSContactOffersCell ()
@property (nonatomic) UILabel *titleLabel;
@property (nonatomic) UIButton *addToContactsButton;
@property (nonatomic) UIButton *addToProfileWhitelistButton;
@property (nonatomic) UIButton *blockButton;
@property (nonatomic) NSArray<NSLayoutConstraint *> *layoutConstraints;
@property (nonatomic) UIStackView *stackView;
@property (nonatomic) UIStackView *buttonStackView;
@end
#pragma mark -
@implementation OWSContactOffersCell
// `[UIView init]` invokes `[self initWithFrame:...]`.
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
[self commontInit];
}
return self;
}
- (void)commontInit
{
OWSAssertDebug(!self.titleLabel);
self.layoutMargins = UIEdgeInsetsZero;
self.contentView.layoutMargins = UIEdgeInsetsZero;
self.layoutConstraints = @[];
self.titleLabel = [UILabel new];
self.titleLabel.text = NSLocalizedString(@"CONVERSATION_VIEW_CONTACTS_OFFER_TITLE",
@"Title for the group of buttons show for unknown contacts offering to add them to contacts, etc.");
self.titleLabel.lineBreakMode = NSLineBreakByTruncatingTail;
self.titleLabel.textAlignment = NSTextAlignmentCenter;
self.addToContactsButton = [self
createButtonWithTitle:
NSLocalizedString(@"CONVERSATION_VIEW_ADD_TO_CONTACTS_OFFER",
@"Message shown in conversation view that offers to add an unknown user to your phone's contacts.")
selector:@selector(addToContacts)];
self.addToProfileWhitelistButton = [self
createButtonWithTitle:NSLocalizedString(@"CONVERSATION_VIEW_ADD_USER_TO_PROFILE_WHITELIST_OFFER",
@"Message shown in conversation view that offers to share your profile with a user.")
selector:@selector(addToProfileWhitelist)];
self.blockButton =
[self createButtonWithTitle:NSLocalizedString(@"CONVERSATION_VIEW_UNKNOWN_CONTACT_BLOCK_OFFER",
@"Message shown in conversation view that offers to block an unknown user.")
selector:@selector(block)];
UIStackView *buttonStackView = [[UIStackView alloc] initWithArrangedSubviews:self.buttons];
buttonStackView.axis = UILayoutConstraintAxisVertical;
buttonStackView.spacing = self.vSpacing;
self.buttonStackView = buttonStackView;
self.stackView = [[UIStackView alloc] initWithArrangedSubviews:@[
self.titleLabel,
buttonStackView,
]];
self.stackView.axis = UILayoutConstraintAxisVertical;
self.stackView.spacing = self.vSpacing;
self.stackView.alignment = UIStackViewAlignmentCenter;
[self.contentView addSubview:self.stackView];
}
- (void)configureFonts
{
self.titleLabel.font = UIFont.ows_dynamicTypeSubheadlineFont;
UIFont *buttonFont = UIFont.ows_dynamicTypeSubheadlineFont.ows_mediumWeight;
self.addToContactsButton.titleLabel.font = buttonFont;
self.addToProfileWhitelistButton.titleLabel.font = buttonFont;
self.blockButton.titleLabel.font = buttonFont;
}
- (UIButton *)createButtonWithTitle:(NSString *)title selector:(SEL)selector
{
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
[button setTitle:title forState:UIControlStateNormal];
button.titleLabel.textAlignment = NSTextAlignmentCenter;
button.layer.cornerRadius = 4.f;
[button addTarget:self action:selector forControlEvents:UIControlEventTouchUpInside];
button.contentEdgeInsets = UIEdgeInsetsMake(0, 10.f, 0, 10.f);
return button;
}
+ (NSString *)cellReuseIdentifier
{
return NSStringFromClass([self class]);
}
- (void)loadForDisplay
{
OWSAssertDebug(self.conversationStyle);
OWSAssertDebug(self.conversationStyle.viewWidth > 0);
OWSAssertDebug(self.viewItem);
OWSAssertDebug([self.viewItem.interaction isKindOfClass:[OWSContactOffersInteraction class]]);
self.backgroundColor = [Theme backgroundColor];
[self configureFonts];
self.titleLabel.textColor = Theme.secondaryColor;
for (UIButton *button in self.buttons) {
[button setTitleColor:[UIColor ows_signalBlueColor] forState:UIControlStateNormal];
[button setBackgroundColor:Theme.conversationButtonBackgroundColor];
}
OWSContactOffersInteraction *interaction = (OWSContactOffersInteraction *)self.viewItem.interaction;
OWSAssertDebug(
interaction.hasBlockOffer || interaction.hasAddToContactsOffer || interaction.hasAddToProfileWhitelistOffer);
self.addToContactsButton.hidden = !interaction.hasAddToContactsOffer;
self.addToProfileWhitelistButton.hidden = !interaction.hasAddToProfileWhitelistOffer;
self.blockButton.hidden = !interaction.hasBlockOffer;
[NSLayoutConstraint deactivateConstraints:self.layoutConstraints];
self.layoutConstraints = @[
[self.addToContactsButton autoSetDimension:ALDimensionHeight toSize:self.buttonHeight],
[self.addToProfileWhitelistButton autoSetDimension:ALDimensionHeight toSize:self.buttonHeight],
[self.blockButton autoSetDimension:ALDimensionHeight toSize:self.buttonHeight],
[self.stackView autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:self.topVMargin],
[self.stackView autoPinEdgeToSuperviewEdge:ALEdgeBottom withInset:self.bottomVMargin],
[self.stackView autoPinEdgeToSuperviewEdge:ALEdgeLeading
withInset:self.conversationStyle.fullWidthGutterLeading],
[self.stackView autoPinEdgeToSuperviewEdge:ALEdgeTrailing
withInset:self.conversationStyle.fullWidthGutterTrailing],
];
// This hack fixes a bug that I don't understand.
//
// On an iPhone 5C running iOS 10.3.3,
//
// * Alice is a contact for which we should show some but not all contact offer buttons.
// * Delete thread with Alice.
// * Send yourself a message from Alice.
// * Open conversation with Alice.
//
// Expected: Some (but not all) offer buttons are displayed.
// Observed: All offer buttons are displayed, in a cramped layout.
for (UIButton *button in self.buttons) {
[button removeFromSuperview];
}
for (UIButton *button in self.buttons) {
if (!button.hidden) {
[self.buttonStackView addArrangedSubview:button];
}
}
}
- (NSArray<UIButton *> *)buttons
{
return @[
self.addToContactsButton,
self.addToProfileWhitelistButton,
self.blockButton,
];
}
- (CGFloat)topVMargin
{
return 0.f;
}
- (CGFloat)bottomVMargin
{
return 0.f;
}
- (CGFloat)vSpacing
{
return 8.f;
}
- (CGFloat)buttonHeight
{
return (24.f + self.addToContactsButton.titleLabel.font.lineHeight);
}
- (CGSize)cellSize
{
OWSAssertDebug(self.conversationStyle);
OWSAssertDebug(self.conversationStyle.viewWidth > 0);
OWSAssertDebug(self.viewItem);
OWSAssertDebug([self.viewItem.interaction isKindOfClass:[OWSContactOffersInteraction class]]);
[self configureFonts];
OWSContactOffersInteraction *interaction = (OWSContactOffersInteraction *)self.viewItem.interaction;
CGSize result = CGSizeMake(self.conversationStyle.viewWidth, 0);
result.height += self.topVMargin;
result.height += self.bottomVMargin;
result.height += ceil([self.titleLabel sizeThatFits:CGSizeZero].height);
int buttonCount = ((interaction.hasBlockOffer ? 1 : 0) + (interaction.hasAddToContactsOffer ? 1 : 0)
+ (interaction.hasAddToProfileWhitelistOffer ? 1 : 0));
result.height += buttonCount * (self.vSpacing + self.buttonHeight);
return result;
}
#pragma mark - Events
- (nullable OWSContactOffersInteraction *)interaction
{
OWSAssertDebug(self.viewItem);
OWSAssertDebug(self.viewItem.interaction);
if (![self.viewItem.interaction isKindOfClass:[OWSContactOffersInteraction class]]) {
OWSFailDebug(@"expected OWSContactOffersInteraction but found: %@", self.viewItem.interaction);
return nil;
}
return (OWSContactOffersInteraction *)self.viewItem.interaction;
}
- (void)addToContacts
{
OWSAssertDebug(self.delegate);
OWSAssertDebug(self.interaction);
[self.delegate tappedAddToContactsOfferMessage:self.interaction];
}
- (void)addToProfileWhitelist
{
OWSAssertDebug(self.delegate);
OWSAssertDebug(self.interaction);
[self.delegate tappedAddToProfileWhitelistOfferMessage:self.interaction];
}
- (void)block
{
OWSAssertDebug(self.delegate);
OWSAssertDebug(self.interaction);
[self.delegate tappedUnknownContactBlockOfferMessage:self.interaction];
}
@end
NS_ASSUME_NONNULL_END

View File

@ -1,33 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
NS_ASSUME_NONNULL_BEGIN
@class ContactShareViewModel;
@protocol OWSContactShareButtonsViewDelegate <NSObject>
- (void)didTapSendMessageToContactShare:(ContactShareViewModel *)contactShare;
- (void)didTapSendInviteToContactShare:(ContactShareViewModel *)contactShare;
- (void)didTapShowAddToContactUIForContactShare:(ContactShareViewModel *)contactShare;
@end
#pragma mark -
@interface OWSContactShareButtonsView : UIView
- (instancetype)initWithContactShare:(ContactShareViewModel *)contactShare
delegate:(id<OWSContactShareButtonsViewDelegate>)delegate;
+ (CGFloat)bubbleHeight;
// Returns YES IFF the tap was handled.
- (BOOL)handleTapGesture:(UITapGestureRecognizer *)sender;
+ (BOOL)hasAnyButton:(ContactShareViewModel *)contactShare;
@end
NS_ASSUME_NONNULL_END

View File

@ -1,169 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "OWSContactShareButtonsView.h"
#import "Session-Swift.h"
#import "UIColor+OWS.h"
#import "UIFont+OWS.h"
#import "UIView+OWS.h"
#import <SignalUtilitiesKit/Environment.h>
#import <SignalUtilitiesKit/SignalUtilitiesKit-Swift.h>
#import <SignalUtilitiesKit/UIColor+OWS.h>
#import <SignalUtilitiesKit/OWSContact.h>
NS_ASSUME_NONNULL_BEGIN
@interface OWSContactShareButtonsView ()
@property (nonatomic, readonly) ContactShareViewModel *contactShare;
@property (nonatomic, weak) id<OWSContactShareButtonsViewDelegate> delegate;
@property (nonatomic, readonly) OWSContactsManager *contactsManager;
@property (nonatomic, nullable) UIView *buttonView;
@end
#pragma mark -
@implementation OWSContactShareButtonsView
- (instancetype)initWithContactShare:(ContactShareViewModel *)contactShare
delegate:(id<OWSContactShareButtonsViewDelegate>)delegate
{
self = [super init];
if (self) {
_delegate = delegate;
_contactShare = contactShare;
_contactsManager = Environment.shared.contactsManager;
[self createContents];
}
return self;
}
#pragma mark -
+ (BOOL)hasSendTextButton:(ContactShareViewModel *)contactShare contactsManager:(OWSContactsManager *)contactsManager
{
OWSAssertDebug(contactShare);
OWSAssertDebug(contactsManager);
return [contactShare systemContactsWithSignalAccountPhoneNumbers:contactsManager].count > 0;
}
+ (BOOL)hasInviteButton:(ContactShareViewModel *)contactShare contactsManager:(OWSContactsManager *)contactsManager
{
OWSAssertDebug(contactShare);
OWSAssertDebug(contactsManager);
return [contactShare systemContactPhoneNumbers:contactsManager].count > 0;
}
+ (BOOL)hasAddToContactsButton:(ContactShareViewModel *)contactShare
{
OWSAssertDebug(contactShare);
return [contactShare e164PhoneNumbers].count > 0;
}
+ (BOOL)hasAnyButton:(ContactShareViewModel *)contactShare
{
OWSAssertDebug(contactShare);
OWSContactsManager *contactsManager = Environment.shared.contactsManager;
return [self hasAnyButton:contactShare contactsManager:contactsManager];
}
+ (BOOL)hasAnyButton:(ContactShareViewModel *)contactShare contactsManager:(OWSContactsManager *)contactsManager
{
OWSAssertDebug(contactShare);
return ([self hasSendTextButton:contactShare contactsManager:contactsManager] ||
[self hasInviteButton:contactShare contactsManager:contactsManager] ||
[self hasAddToContactsButton:contactShare]);
}
+ (CGFloat)bubbleHeight
{
return self.buttonHeight;
}
+ (CGFloat)buttonHeight
{
return MAX(44.f, self.buttonFont.lineHeight + self.buttonVMargin * 2);
}
+ (UIFont *)buttonFont
{
return [UIFont ows_dynamicTypeBodyFont].ows_mediumWeight;
}
+ (CGFloat)buttonVMargin
{
return 5;
}
- (void)createContents
{
OWSAssertDebug([OWSContactShareButtonsView hasAnyButton:self.contactShare contactsManager:self.contactsManager]);
self.layoutMargins = UIEdgeInsetsZero;
self.backgroundColor = Theme.conversationButtonBackgroundColor;
UILabel *label = [UILabel new];
self.buttonView = label;
if ([OWSContactShareButtonsView hasSendTextButton:self.contactShare contactsManager:self.contactsManager]) {
label.text
= NSLocalizedString(@"ACTION_SEND_MESSAGE", @"Label for button that lets you send a message to a contact.");
} else if ([OWSContactShareButtonsView hasInviteButton:self.contactShare contactsManager:self.contactsManager]) {
label.text = NSLocalizedString(@"ACTION_INVITE", @"Label for 'invite' button in contact view.");
} else if ([OWSContactShareButtonsView hasAddToContactsButton:self.contactShare]) {
label.text = NSLocalizedString(@"CONVERSATION_VIEW_ADD_TO_CONTACTS_OFFER",
@"Message shown in conversation view that offers to add an unknown user to your phone's contacts.");
} else {
OWSFailDebug(@"unexpected button state.");
}
label.font = OWSContactShareButtonsView.buttonFont;
label.textColor = (Theme.isDarkThemeEnabled ? UIColor.ows_whiteColor : UIColor.ows_materialBlueColor);
label.textAlignment = NSTextAlignmentCenter;
[self addSubview:label];
[label ows_autoPinToSuperviewEdges];
[label autoSetDimension:ALDimensionHeight toSize:OWSContactShareButtonsView.buttonHeight];
self.userInteractionEnabled = YES;
UITapGestureRecognizer *tap =
[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapGesture:)];
[self addGestureRecognizer:tap];
}
- (BOOL)handleTapGesture:(UITapGestureRecognizer *)sender
{
if (!self.buttonView) {
return NO;
}
CGPoint location = [sender locationInView:self.buttonView];
if (!CGRectContainsPoint(self.buttonView.bounds, location)) {
return NO;
}
if ([OWSContactShareButtonsView hasSendTextButton:self.contactShare contactsManager:self.contactsManager]) {
[self.delegate didTapSendMessageToContactShare:self.contactShare];
} else if ([OWSContactShareButtonsView hasInviteButton:self.contactShare contactsManager:self.contactsManager]) {
[self.delegate didTapSendInviteToContactShare:self.contactShare];
} else if ([OWSContactShareButtonsView hasAddToContactsButton:self.contactShare]) {
[self.delegate didTapShowAddToContactUIForContactShare:self.contactShare];
} else {
OWSFailDebug(@"unexpected button tap.");
}
return YES;
}
@end
NS_ASSUME_NONNULL_END

View File

@ -1,22 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
NS_ASSUME_NONNULL_BEGIN
@class ContactShareViewModel;
@class ConversationStyle;
@interface OWSContactShareView : UIView
- (instancetype)initWithContactShare:(ContactShareViewModel *)contactShare
isIncoming:(BOOL)isIncoming
conversationStyle:(ConversationStyle *)conversationStyle;
- (void)createContents;
+ (CGFloat)bubbleHeight;
@end
NS_ASSUME_NONNULL_END

View File

@ -1,165 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "OWSContactShareView.h"
#import "OWSContactAvatarBuilder.h"
#import "Session-Swift.h"
#import "UIColor+OWS.h"
#import "UIFont+OWS.h"
#import "UIView+OWS.h"
#import <SignalUtilitiesKit/Environment.h>
#import <SignalUtilitiesKit/SignalUtilitiesKit-Swift.h>
#import <SignalUtilitiesKit/UIColor+OWS.h>
#import <SignalUtilitiesKit/OWSContact.h>
NS_ASSUME_NONNULL_BEGIN
@interface OWSContactShareView ()
@property (nonatomic, readonly) ContactShareViewModel *contactShare;
@property (nonatomic, readonly) BOOL isIncoming;
@property (nonatomic, readonly) ConversationStyle *conversationStyle;
@property (nonatomic, readonly) OWSContactsManager *contactsManager;
@end
#pragma mark -
@implementation OWSContactShareView
- (instancetype)initWithContactShare:(ContactShareViewModel *)contactShare
isIncoming:(BOOL)isIncoming
conversationStyle:(ConversationStyle *)conversationStyle
{
self = [super init];
if (self) {
_contactShare = contactShare;
_isIncoming = isIncoming;
_conversationStyle = conversationStyle;
_contactsManager = Environment.shared.contactsManager;
}
return self;
}
#pragma mark -
- (CGFloat)hMargin
{
return 12.f;
}
+ (CGFloat)vMargin
{
return 0.f;
}
- (CGFloat)iconHSpacing
{
return 8.f;
}
+ (CGFloat)bubbleHeight
{
return self.contentHeight;
}
+ (CGFloat)contentHeight
{
CGFloat labelsHeight = (self.nameFont.lineHeight + self.labelsVSpacing + self.subtitleFont.lineHeight);
CGFloat contentHeight = MAX(self.iconSize, labelsHeight);
contentHeight += OWSContactShareView.vMargin * 2;
return contentHeight;
}
+ (CGFloat)iconSize
{
return kStandardAvatarSize;
}
- (CGFloat)iconSize
{
return [OWSContactShareView iconSize];
}
+ (UIFont *)nameFont
{
return [UIFont ows_dynamicTypeBodyFont];
}
+ (UIFont *)subtitleFont
{
return [UIFont ows_dynamicTypeCaption1Font];
}
+ (CGFloat)labelsVSpacing
{
return 2;
}
- (void)createContents
{
self.layoutMargins = UIEdgeInsetsZero;
UIColor *textColor = [self.conversationStyle bubbleTextColorWithIsIncoming:self.isIncoming];
AvatarImageView *avatarView = [AvatarImageView new];
avatarView.image =
[self.contactShare getAvatarImageWithDiameter:self.iconSize contactsManager:self.contactsManager];
[avatarView autoSetDimension:ALDimensionWidth toSize:self.iconSize];
[avatarView autoSetDimension:ALDimensionHeight toSize:self.iconSize];
[avatarView setCompressionResistanceHigh];
[avatarView setContentHuggingHigh];
UILabel *topLabel = [UILabel new];
topLabel.text = self.contactShare.displayName;
topLabel.textColor = textColor;
topLabel.lineBreakMode = NSLineBreakByTruncatingTail;
topLabel.font = OWSContactShareView.nameFont;
UIStackView *labelsView = [UIStackView new];
labelsView.axis = UILayoutConstraintAxisVertical;
labelsView.spacing = OWSContactShareView.labelsVSpacing;
[labelsView addArrangedSubview:topLabel];
NSString *_Nullable firstPhoneNumber =
[self.contactShare systemContactsWithSignalAccountPhoneNumbers:self.contactsManager].firstObject;
if (firstPhoneNumber.length > 0) {
UILabel *bottomLabel = [UILabel new];
bottomLabel.text = [PhoneNumber bestEffortLocalizedPhoneNumberWithE164:firstPhoneNumber];
bottomLabel.textColor = [self.conversationStyle bubbleSecondaryTextColorWithIsIncoming:self.isIncoming];
bottomLabel.lineBreakMode = NSLineBreakByTruncatingTail;
bottomLabel.font = OWSContactShareView.subtitleFont;
[labelsView addArrangedSubview:bottomLabel];
}
UIImage *disclosureImage =
[UIImage imageNamed:(CurrentAppContext().isRTL ? @"small_chevron_left" : @"small_chevron_right")];
OWSAssertDebug(disclosureImage);
UIImageView *disclosureImageView = [UIImageView new];
disclosureImageView.image = [disclosureImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
disclosureImageView.tintColor = textColor;
[disclosureImageView setCompressionResistanceHigh];
[disclosureImageView setContentHuggingHigh];
UIStackView *hStackView = [UIStackView new];
hStackView.axis = UILayoutConstraintAxisHorizontal;
hStackView.spacing = self.iconHSpacing;
hStackView.alignment = UIStackViewAlignmentCenter;
hStackView.layoutMarginsRelativeArrangement = YES;
hStackView.layoutMargins
= UIEdgeInsetsMake(OWSContactShareView.vMargin, self.hMargin, OWSContactShareView.vMargin, self.hMargin);
[hStackView addArrangedSubview:avatarView];
[hStackView addArrangedSubview:labelsView];
[hStackView addArrangedSubview:disclosureImageView];
[self addSubview:hStackView];
[hStackView ows_autoPinToSuperviewEdges];
}
@end
NS_ASSUME_NONNULL_END

View File

@ -7,7 +7,7 @@
#import "Session-Swift.h"
#import "UIFont+OWS.h"
#import "UIView+OWS.h"
#import "ViewControllerUtils.h"
#import <SignalUtilitiesKit/OWSFormat.h>
#import <SignalUtilitiesKit/UIColor+OWS.h>
#import <SignalUtilitiesKit/MimeTypeUtil.h>
@ -91,7 +91,7 @@ NS_ASSUME_NONNULL_BEGIN
- (CGFloat)iconHeight
{
return kStandardAvatarSize;
return 48.0f;
}
- (void)createContentsWithConversationStyle:(ConversationStyle *)conversationStyle

View File

@ -49,15 +49,6 @@ typedef NS_ENUM(NSUInteger, OWSMessageGestureLocation) {
- (void)didTapConversationItem:(id<ConversationViewItem>)viewItem linkPreview:(OWSLinkPreview *)linkPreview;
- (void)didTapContactShareViewItem:(id<ConversationViewItem>)viewItem;
- (void)didTapSendMessageToContactShare:(ContactShareViewModel *)contactShare
NS_SWIFT_NAME(didTapSendMessage(toContactShare:));
- (void)didTapSendInviteToContactShare:(ContactShareViewModel *)contactShare
NS_SWIFT_NAME(didTapSendInvite(toContactShare:));
- (void)didTapShowAddToContactUIForContactShare:(ContactShareViewModel *)contactShare
NS_SWIFT_NAME(didTapShowAddToContactUI(forContactShare:));
@property (nonatomic, readonly, nullable) NSString *lastSearchedText;
@end

View File

@ -7,8 +7,6 @@
#import "ConversationViewItem.h"
#import "OWSBubbleShapeView.h"
#import "OWSBubbleView.h"
#import "OWSContactShareButtonsView.h"
#import "OWSContactShareView.h"
#import "OWSGenericAttachmentView.h"
#import "OWSLabel.h"
#import "OWSMessageFooterView.h"
@ -20,7 +18,7 @@
NS_ASSUME_NONNULL_BEGIN
@interface OWSMessageBubbleView () <OWSQuotedMessageViewDelegate, OWSContactShareButtonsViewDelegate>
@interface OWSMessageBubbleView () <OWSQuotedMessageViewDelegate>
@property (nonatomic) OWSBubbleView *bubbleView;
@ -48,8 +46,6 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic) OWSMessageFooterView *footerView;
@property (nonatomic, nullable) OWSContactShareButtonsView *contactShareButtonsView;
@end
#pragma mark -
@ -267,9 +263,6 @@ NS_ASSUME_NONNULL_BEGIN
case OWSMessageCellType_GenericAttachment:
bodyMediaView = [self loadViewForGenericAttachment];
break;
case OWSMessageCellType_ContactShare:
bodyMediaView = [self loadViewForContactShare];
break;
case OWSMessageCellType_MediaMessage:
bodyMediaView = [self loadViewForMediaAlbum];
break;
@ -300,24 +293,6 @@ NS_ASSUME_NONNULL_BEGIN
if (self.hasBodyMediaWithThumbnail) {
[self.stackView addArrangedSubview:bodyMediaView];
} else {
OWSAssertDebug(self.cellType == OWSMessageCellType_ContactShare);
if (self.contactShareHasSpacerTop) {
UIView *spacerView = [UIView containerView];
[spacerView autoSetDimension:ALDimensionHeight toSize:self.contactShareVSpacing];
[spacerView setCompressionResistanceHigh];
[self.stackView addArrangedSubview:spacerView];
}
[self.stackView addArrangedSubview:bodyMediaView];
if (self.contactShareHasSpacerBottom) {
UIView *spacerView = [UIView containerView];
[spacerView autoSetDimension:ALDimensionHeight toSize:self.contactShareVSpacing];
[spacerView setCompressionResistanceHigh];
[self.stackView addArrangedSubview:spacerView];
}
}
} else {
[textViews addObject:bodyMediaView];
@ -414,86 +389,11 @@ NS_ASSUME_NONNULL_BEGIN
addObject:[bodyMediaView autoSetDimension:ALDimensionHeight toSize:bodyMediaSize.CGSizeValue.height]];
}
[self insertContactShareButtonsIfNecessary];
[self updateBubbleColor];
[self configureBubbleRounding];
}
- (void)insertContactShareButtonsIfNecessary
{
if (self.cellType != OWSMessageCellType_ContactShare) {
return;
}
if (![OWSContactShareButtonsView hasAnyButton:self.viewItem.contactShare]) {
return;
}
OWSAssertDebug(self.viewItem.contactShare);
OWSContactShareButtonsView *buttonsView =
[[OWSContactShareButtonsView alloc] initWithContactShare:self.viewItem.contactShare delegate:self];
NSValue *_Nullable actionButtonsSize = [self actionButtonsSize];
OWSAssertDebug(actionButtonsSize);
[self.viewConstraints addObjectsFromArray:@[
[buttonsView autoSetDimension:ALDimensionHeight toSize:actionButtonsSize.CGSizeValue.height],
]];
// The "contact share" view casts a shadow "downward" onto adjacent views,
// so we use a "proxy" view to take its place within the v-stack
// view and then insert the "contact share" view above its proxy so that
// it floats above the other content of the bubble view.
UIView *proxyView = [UIView new];
[self.stackView addArrangedSubview:proxyView];
OWSBubbleShapeView *shadowView = [[OWSBubbleShapeView alloc] initShadow];
OWSBubbleShapeView *clipView = [[OWSBubbleShapeView alloc] initClip];
[self addSubview:shadowView];
[self addSubview:clipView];
[self.viewConstraints addObjectsFromArray:[shadowView autoPinToEdgesOfView:proxyView]];
[self.viewConstraints addObjectsFromArray:[clipView autoPinToEdgesOfView:proxyView]];
[clipView addSubview:buttonsView];
[self.viewConstraints addObjectsFromArray:[buttonsView ows_autoPinToSuperviewEdges]];
[self.bubbleView addPartnerView:shadowView];
[self.bubbleView addPartnerView:clipView];
// Prevent the layer from animating changes.
[CATransaction begin];
[CATransaction setDisableActions:YES];
OWSAssertDebug(buttonsView.backgroundColor);
shadowView.fillColor = buttonsView.backgroundColor;
shadowView.layer.shadowColor = Theme.boldColor.CGColor;
shadowView.layer.shadowOpacity = 0.12f;
shadowView.layer.shadowOffset = CGSizeZero;
shadowView.layer.shadowRadius = 1.f;
[CATransaction commit];
}
- (BOOL)contactShareHasSpacerTop
{
return (self.cellType == OWSMessageCellType_ContactShare && (self.isQuotedReply || !self.shouldShowSenderName));
}
- (BOOL)contactShareHasSpacerBottom
{
return (self.cellType == OWSMessageCellType_ContactShare && !self.hasBottomFooter);
}
- (CGFloat)contactShareVSpacing
{
return 12.f;
}
- (CGFloat)senderNameBottomSpacing
{
return 0.f;
@ -557,7 +457,6 @@ NS_ASSUME_NONNULL_BEGIN
case OWSMessageCellType_TextOnlyMessage:
case OWSMessageCellType_Audio:
case OWSMessageCellType_GenericAttachment:
case OWSMessageCellType_ContactShare:
case OWSMessageCellType_OversizeTextDownloading:
return NO;
case OWSMessageCellType_MediaMessage:
@ -572,7 +471,6 @@ NS_ASSUME_NONNULL_BEGIN
return NO;
case OWSMessageCellType_Audio:
case OWSMessageCellType_GenericAttachment:
case OWSMessageCellType_ContactShare:
case OWSMessageCellType_MediaMessage:
case OWSMessageCellType_OversizeTextDownloading:
return YES;
@ -581,8 +479,7 @@ NS_ASSUME_NONNULL_BEGIN
- (BOOL)hasFullWidthMediaView
{
return (self.hasBodyMediaWithThumbnail || self.cellType == OWSMessageCellType_ContactShare
|| self.cellType == OWSMessageCellType_MediaMessage);
return (self.hasBodyMediaWithThumbnail || self.cellType == OWSMessageCellType_MediaMessage);
}
- (BOOL)canFooterOverlayMedia
@ -876,26 +773,6 @@ NS_ASSUME_NONNULL_BEGIN
return attachmentView;
}
- (UIView *)loadViewForContactShare
{
OWSAssertDebug(self.viewItem.contactShare);
OWSContactShareView *contactShareView = [[OWSContactShareView alloc] initWithContactShare:self.viewItem.contactShare
isIncoming:self.isIncoming
conversationStyle:self.conversationStyle];
[contactShareView createContents];
// TODO: Should we change appearance if contact avatar is uploading?
self.loadCellContentBlock = ^{
// Do nothing.
};
self.unloadCellContentBlock = ^{
// Do nothing.
};
return contactShareView;
}
- (UIView *)loadViewForOversizeTextDownload
{
// We can use an empty view. The progress views will display download
@ -1079,11 +956,6 @@ NS_ASSUME_NONNULL_BEGIN
result = [attachmentView measureSizeWithMaxMessageWidth:maxMessageWidth];
break;
}
case OWSMessageCellType_ContactShare:
OWSAssertDebug(self.viewItem.contactShare);
result = CGSizeMake(maxMessageWidth, [OWSContactShareView bubbleHeight]);
break;
case OWSMessageCellType_MediaMessage:
result = [OWSMediaAlbumCellView layoutSizeForMaxMessageWidth:maxMessageWidth
items:self.viewItem.mediaAlbumItems];
@ -1186,23 +1058,6 @@ NS_ASSUME_NONNULL_BEGIN
return [NSValue valueWithCGSize:result];
}
- (nullable NSValue *)actionButtonsSize
{
OWSAssertDebug(self.conversationStyle);
OWSAssertDebug(self.conversationStyle.maxMessageWidth > 0);
if (self.cellType == OWSMessageCellType_ContactShare) {
OWSAssertDebug(self.viewItem.contactShare);
if ([OWSContactShareButtonsView hasAnyButton:self.viewItem.contactShare]) {
CGSize buttonsSize = CGSizeCeil(
CGSizeMake(self.conversationStyle.maxMessageWidth, [OWSContactShareButtonsView bubbleHeight]));
return [NSValue valueWithCGSize:buttonsSize];
}
}
return nil;
}
- (CGSize)measureSize
{
OWSAssertDebug(self.conversationStyle);
@ -1239,13 +1094,6 @@ NS_ASSUME_NONNULL_BEGIN
[textViewSizes addObject:bodyMediaSize];
bodyMediaSize = nil;
}
if (self.contactShareHasSpacerTop) {
cellSize.height += self.contactShareVSpacing;
}
if (self.contactShareHasSpacerBottom) {
cellSize.height += self.contactShareVSpacing;
}
}
if (bodyMediaSize || quotedMessageSize) {
@ -1296,12 +1144,6 @@ NS_ASSUME_NONNULL_BEGIN
cellSize.height += self.tapForMoreHeight + self.textViewVSpacing;
}
NSValue *_Nullable actionButtonsSize = [self actionButtonsSize];
if (actionButtonsSize) {
cellSize.width = MAX(cellSize.width, actionButtonsSize.CGSizeValue.width);
cellSize.height += actionButtonsSize.CGSizeValue.height;
}
cellSize = CGSizeCeil(cellSize);
OWSAssertDebug(cellSize.width <= self.conversationStyle.maxMessageWidth);
@ -1395,9 +1237,6 @@ NS_ASSUME_NONNULL_BEGIN
}
}
[self.contactShareButtonsView removeFromSuperview];
self.contactShareButtonsView = nil;
[self.linkPreviewView removeFromSuperview];
self.linkPreviewView.state = nil;
}
@ -1430,12 +1269,6 @@ NS_ASSUME_NONNULL_BEGIN
}
}
if (self.contactShareButtonsView) {
if ([self.contactShareButtonsView handleTapGesture:sender]) {
return;
}
}
CGPoint locationInMessageBubble = [sender locationInView:self];
switch ([self gestureLocationForLocation:locationInMessageBubble]) {
case OWSMessageGestureLocation_Default:
@ -1488,9 +1321,6 @@ NS_ASSUME_NONNULL_BEGIN
[AttachmentSharing showShareUIForAttachment:self.viewItem.attachmentStream];
}
break;
case OWSMessageCellType_ContactShare:
[self.delegate didTapContactShareViewItem:self.viewItem];
break;
case OWSMessageCellType_MediaMessage: {
OWSAssertDebug(self.bodyMediaView);
OWSAssertDebug(self.viewItem.mediaAlbumItems.count > 0);
@ -1603,32 +1433,6 @@ NS_ASSUME_NONNULL_BEGIN
OWSFailDebug(@"Sent quoted replies should not be cancellable.");
}
#pragma mark - OWSContactShareButtonsViewDelegate
- (void)didTapSendMessageToContactShare:(ContactShareViewModel *)contactShare
{
OWSAssertIsOnMainThread();
OWSAssertDebug(contactShare);
[self.delegate didTapSendMessageToContactShare:contactShare];
}
- (void)didTapSendInviteToContactShare:(ContactShareViewModel *)contactShare
{
OWSAssertIsOnMainThread();
OWSAssertDebug(contactShare);
[self.delegate didTapSendInviteToContactShare:contactShare];
}
- (void)didTapShowAddToContactUIForContactShare:(ContactShareViewModel *)contactShare
{
OWSAssertIsOnMainThread();
OWSAssertDebug(contactShare);
[self.delegate didTapShowAddToContactUIForContactShare:contactShare];
}
@end
NS_ASSUME_NONNULL_END

View File

@ -3,7 +3,6 @@
//
#import "OWSMessageCell.h"
#import "OWSContactAvatarBuilder.h"
#import "OWSMessageBubbleView.h"
#import "OWSMessageHeaderView.h"
#import "Session-Swift.h"
@ -286,7 +285,7 @@ NS_ASSUME_NONNULL_BEGIN
[self.avatarView update];
// Loki: Show the moderator icon if needed
if (self.viewItem.isGroupThread && !self.viewItem.isRSSFeed) { // FIXME: This logic also shouldn't apply to closed groups
if (self.viewItem.isGroupThread) { // FIXME: This logic also shouldn't apply to closed groups
__block SNOpenGroup *publicChat;
[OWSPrimaryStorage.sharedManager.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
publicChat = [LKDatabaseUtilities getPublicChatForThreadID:self.viewItem.interaction.uniqueThreadId transaction: transaction];

View File

@ -122,7 +122,8 @@ NS_ASSUME_NONNULL_BEGIN
[OWSPrimaryStorage.sharedManager.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
TSContactThread *thread = [outgoingMessage.thread as:TSContactThread.class];
if (thread != nil) {
isNoteToSelf = [LKDatabaseUtilities isUserLinkedDevice:thread.contactIdentifier in:transaction];
NSString *userPublicKey = OWSIdentityManager.sharedManager.identityKeyPair.hexEncodedPublicKey;
isNoteToSelf = ([thread.contactIdentifier isEqual:userPublicKey]);
}
}];

View File

@ -9,7 +9,7 @@
#import "UIView+OWS.h"
#import <QuartzCore/QuartzCore.h>
#import <SignalCoreKit/NSDate+OWS.h>
#import <SignalUtilitiesKit/NSTimer+OWS.h>
#import <SessionUtilitiesKit/NSTimer+Proxying.h>
NS_ASSUME_NONNULL_BEGIN

View File

@ -8,7 +8,7 @@
#import "OWSBubbleView.h"
#import "Session-Swift.h"
#import <SignalCoreKit/NSString+OWS.h>
#import <SignalUtilitiesKit/OWSContactsManager.h>
#import <SignalUtilitiesKit/SignalUtilitiesKit-Swift.h>
#import <SignalUtilitiesKit/UIColor+OWS.h>
#import <SignalUtilitiesKit/UIView+OWS.h>
@ -550,8 +550,7 @@ const CGFloat kRemotelySourcedContentRowSpacing = 4;
quotedAuthorText = NSLocalizedString(@"You", @"");
}
} else {
OWSContactsManager *contactsManager = Environment.shared.contactsManager;
__block NSString *quotedAuthor = [SSKEnvironment.shared.profileManager profileNameForRecipientWithID:self.quotedMessage.authorId] ?: [contactsManager contactOrProfileNameForPhoneIdentifier:self.quotedMessage.authorId];
__block NSString *quotedAuthor = [SSKEnvironment.shared.profileManager profileNameForRecipientWithID:self.quotedMessage.authorId];
if (quotedAuthor == self.quotedMessage.authorId) {
[OWSPrimaryStorage.sharedManager.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {

View File

@ -9,9 +9,8 @@
#import "UIColor+OWS.h"
#import "UIFont+OWS.h"
#import "UIView+OWS.h"
#import <SignalUtilitiesKit/OWSDisappearingConfigurationUpdateInfoMessage.h>
#import <SignalUtilitiesKit/Environment.h>
#import <SignalUtilitiesKit/OWSContactsManager.h>
#import <SignalUtilitiesKit/OWSVerificationStateChangeMessage.h>
#import <SignalUtilitiesKit/TSCall.h>
#import <SignalUtilitiesKit/TSErrorMessage.h>
#import <SignalUtilitiesKit/TSInfoMessage.h>
@ -290,17 +289,6 @@ typedef void (^SystemMessageActionBlock)(void);
: [UIImage imageNamed:@"system_message_disappearing_messages_disabled"]);
break;
}
case TSInfoMessageVerificationStateChange:
OWSAssertDebug([interaction isKindOfClass:[OWSVerificationStateChangeMessage class]]);
if ([interaction isKindOfClass:[OWSVerificationStateChangeMessage class]]) {
OWSVerificationStateChangeMessage *message = (OWSVerificationStateChangeMessage *)interaction;
BOOL isVerified = message.verificationState == OWSVerificationStateVerified;
if (!isVerified) {
return nil;
}
}
result = [UIImage imageNamed:@"system_message_verified"];
break;
}
} else if ([interaction isKindOfClass:[TSCall class]]) {
result = [UIImage imageNamed:@"system_message_call"];
@ -398,8 +386,6 @@ typedef void (^SystemMessageActionBlock)(void);
return [self actionForErrorMessage:(TSErrorMessage *)interaction];
} else if ([interaction isKindOfClass:[TSInfoMessage class]]) {
return [self actionForInfoMessage:(TSInfoMessage *)interaction];
} else if ([interaction isKindOfClass:[TSCall class]]) {
return [self actionForCall:(TSCall *)interaction];
} else {
OWSFailDebug(@"Tap for system messages of unknown type: %@", [interaction class]);
return nil;
@ -414,21 +400,6 @@ typedef void (^SystemMessageActionBlock)(void);
switch (message.errorType) {
case TSErrorMessageInvalidKeyException:
return nil;
case TSErrorMessageNonBlockingIdentityChange:
return [SystemMessageAction
actionWithTitle:NSLocalizedString(@"SYSTEM_MESSAGE_ACTION_VERIFY_SAFETY_NUMBER",
@"Label for button to verify a user's safety number.")
block:^{
[weakSelf.delegate tappedNonBlockingIdentityChangeForRecipientId:message.recipientId];
}];
case TSErrorMessageWrongTrustedIdentityKey:
return [SystemMessageAction
actionWithTitle:NSLocalizedString(@"SYSTEM_MESSAGE_ACTION_VERIFY_SAFETY_NUMBER",
@"Label for button to verify a user's safety number.")
block:^{
[weakSelf.delegate
tappedInvalidIdentityKeyErrorMessage:(TSInvalidIdentityKeyErrorMessage *)message];
}];
case TSErrorMessageMissingKeyId:
case TSErrorMessageNoSession:
case TSErrorMessageInvalidMessage:
@ -486,48 +457,12 @@ typedef void (^SystemMessageActionBlock)(void);
block:^{
[weakSelf.delegate showConversationSettings];
}];
case TSInfoMessageVerificationStateChange:
return [SystemMessageAction
actionWithTitle:NSLocalizedString(@"SHOW_SAFETY_NUMBER_ACTION", @"Action sheet item")
block:^{
[weakSelf.delegate
showFingerprintWithRecipientId:((OWSVerificationStateChangeMessage *)message)
.recipientId];
}];
}
OWSLogInfo(@"Unhandled tap for info message: %@", message);
return nil;
}
- (nullable SystemMessageAction *)actionForCall:(TSCall *)call
{
OWSAssertDebug(call);
__weak OWSSystemMessageCell *weakSelf = self;
switch (call.callType) {
case RPRecentCallTypeIncoming:
case RPRecentCallTypeIncomingMissed:
case RPRecentCallTypeIncomingMissedBecauseOfChangedIdentity:
case RPRecentCallTypeIncomingDeclined:
return
[SystemMessageAction actionWithTitle:NSLocalizedString(@"CALLBACK_BUTTON_TITLE", @"notification action")
block:^{
[weakSelf.delegate handleCallTap:call];
}];
case RPRecentCallTypeOutgoing:
case RPRecentCallTypeOutgoingMissed:
return [SystemMessageAction actionWithTitle:NSLocalizedString(@"CALL_AGAIN_BUTTON_TITLE",
@"Label for button that lets users call a contact again.")
block:^{
[weakSelf.delegate handleCallTap:call];
}];
case RPRecentCallTypeOutgoingIncomplete:
case RPRecentCallTypeIncomingIncomplete:
return nil;
}
}
#pragma mark - Events
- (void)handleLongPressGesture:(UILongPressGestureRecognizer *)longPress

View File

@ -19,7 +19,7 @@ public class TypingIndicatorCell: ConversationViewCell {
private let kAvatarSize: CGFloat = 36
private let kAvatarHSpacing: CGFloat = 8
private let avatarView = AvatarImageView()
// private let avatarView = AvatarImageView()
private let bubbleView = OWSBubbleView()
private let typingIndicatorView = TypingIndicatorView()
private var viewConstraints = [NSLayoutConstraint]()
@ -39,8 +39,8 @@ public class TypingIndicatorCell: ConversationViewCell {
bubbleView.addSubview(typingIndicatorView)
contentView.addSubview(bubbleView)
avatarView.autoSetDimension(.width, toSize: kAvatarSize)
avatarView.autoSetDimension(.height, toSize: kAvatarSize)
// avatarView.autoSetDimension(.width, toSize: kAvatarSize)
// avatarView.autoSetDimension(.height, toSize: kAvatarSize)
}
@objc
@ -65,16 +65,16 @@ public class TypingIndicatorCell: ConversationViewCell {
typingIndicatorView.autoPinBottomToSuperviewMargin(withInset: conversationStyle.textInsetBottom)
])
if let avatarView = configureAvatarView() {
contentView.addSubview(avatarView)
viewConstraints.append(contentsOf: [
bubbleView.autoPinLeading(toTrailingEdgeOf: avatarView, offset: kAvatarHSpacing),
bubbleView.autoAlignAxis(.horizontal, toSameAxisOf: avatarView)
])
} else {
avatarView.removeFromSuperview()
}
// if let avatarView = configureAvatarView() {
// contentView.addSubview(avatarView)
// viewConstraints.append(contentsOf: [
// bubbleView.autoPinLeading(toTrailingEdgeOf: avatarView, offset: kAvatarHSpacing),
// bubbleView.autoAlignAxis(.horizontal, toSameAxisOf: avatarView)
// ])
//
// } else {
// avatarView.removeFromSuperview()
// }
}
private func configureAvatarView() -> UIView? {
@ -93,15 +93,16 @@ public class TypingIndicatorCell: ConversationViewCell {
owsFailDebug("Missing authorConversationColorName")
return nil
}
guard let authorAvatarImage =
OWSContactAvatarBuilder(signalId: typingIndicators.recipientId,
colorName: ConversationColorName(rawValue: colorName),
diameter: UInt(kAvatarSize)).build() else {
owsFailDebug("Could build avatar image")
return nil
}
avatarView.image = authorAvatarImage
return avatarView
// guard let authorAvatarImage =
// OWSContactAvatarBuilder(signalId: typingIndicators.recipientId,
// colorName: ConversationColorName(rawValue: colorName),
// diameter: UInt(kAvatarSize)).build() else {
// owsFailDebug("Could build avatar image")
// return nil
// }
// avatarView.image = authorAvatarImage
// return avatarView
return UIView()
}
private func shouldShowAvatar() -> Bool {
@ -140,8 +141,8 @@ public class TypingIndicatorCell: ConversationViewCell {
NSLayoutConstraint.deactivate(viewConstraints)
viewConstraints = [NSLayoutConstraint]()
avatarView.image = nil
avatarView.removeFromSuperview()
// avatarView.image = nil
// avatarView.removeFromSuperview()
typingIndicatorView.stopAnimation()
}

View File

@ -1,131 +0,0 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
import Foundation
@objc
public protocol ConversationHeaderViewDelegate {
func didTapConversationHeaderView(_ conversationHeaderView: ConversationHeaderView)
}
@objc
public class ConversationHeaderView: UIStackView {
@objc
public weak var delegate: ConversationHeaderViewDelegate?
@objc
public var attributedTitle: NSAttributedString? {
get {
return self.titleLabel.attributedText
}
set {
self.titleLabel.attributedText = newValue
}
}
@objc
public var attributedSubtitle: NSAttributedString? {
get {
return self.subtitleLabel.attributedText
}
set {
self.subtitleLabel.attributedText = newValue
self.subtitleLabel.isHidden = newValue == nil
}
}
public var avatarImage: UIImage? {
get {
return self.avatarView.image
}
set {
self.avatarView.image = newValue
}
}
@objc
public let titlePrimaryFont: UIFont = UIFont.ows_boldFont(withSize: 17)
@objc
public let titleSecondaryFont: UIFont = UIFont.ows_regularFont(withSize: 9)
@objc
public let subtitleFont: UIFont = UIFont.ows_regularFont(withSize: 12)
private let titleLabel: UILabel
private let subtitleLabel: UILabel
private let avatarView: ConversationAvatarImageView
@objc
public required init(thread: TSThread, contactsManager: OWSContactsManager) {
let avatarView = ConversationAvatarImageView(thread: thread, diameter: 36, contactsManager: contactsManager)
self.avatarView = avatarView
titleLabel = UILabel()
titleLabel.textColor = Theme.navbarTitleColor
titleLabel.lineBreakMode = .byTruncatingTail
titleLabel.font = titlePrimaryFont
titleLabel.setContentHuggingHigh()
subtitleLabel = UILabel()
subtitleLabel.textColor = Theme.navbarTitleColor
subtitleLabel.lineBreakMode = .byTruncatingTail
subtitleLabel.font = subtitleFont
subtitleLabel.setContentHuggingHigh()
let textRows = UIStackView(arrangedSubviews: [titleLabel, subtitleLabel])
textRows.axis = .vertical
textRows.alignment = .leading
textRows.distribution = .fillProportionally
textRows.spacing = 0
textRows.layoutMargins = UIEdgeInsets(top: 0, left: 8, bottom: 0, right: 8)
textRows.isLayoutMarginsRelativeArrangement = true
// low content hugging so that the text rows push container to the right bar button item(s)
textRows.setContentHuggingLow()
super.init(frame: .zero)
self.layoutMargins = UIEdgeInsets(top: 4, left: 2, bottom: 4, right: 2)
self.isLayoutMarginsRelativeArrangement = true
self.axis = .horizontal
self.alignment = .center
self.spacing = 0
self.addArrangedSubview(avatarView)
self.addArrangedSubview(textRows)
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(didTapView))
self.addGestureRecognizer(tapGesture)
}
required public init(coder: NSCoder) {
notImplemented()
}
required public override init(frame: CGRect) {
notImplemented()
}
public override var intrinsicContentSize: CGSize {
// Grow to fill as much of the navbar as possible.
return UIView.layoutFittingExpandedSize
}
@objc
public func updateAvatar() {
self.avatarView.updateImage()
}
// MARK: Delegate Methods
@objc func didTapView(tapGesture: UITapGestureRecognizer) {
guard tapGesture.state == .recognized else {
return
}
self.delegate?.didTapConversationHeaderView(self)
}
}

View File

@ -5,17 +5,14 @@
#import "ConversationInputToolbar.h"
#import "ConversationInputTextView.h"
#import "Environment.h"
#import "OWSContactsManager.h"
#import "OWSMath.h"
#import "Session-Swift.h"
#import "UIColor+OWS.h"
#import "UIFont+OWS.h"
#import "ViewControllerUtils.h"
#import <PromiseKit/AnyPromise.h>
#import <SignalUtilitiesKit/OWSFormat.h>
#import <SignalUtilitiesKit/SignalUtilitiesKit-Swift.h>
#import <SignalUtilitiesKit/UIView+OWS.h>
#import <SignalUtilitiesKit/NSTimer+OWS.h>
#import <SignalUtilitiesKit/TSQuotedMessage.h>
NS_ASSUME_NONNULL_BEGIN

File diff suppressed because it is too large Load Diff

View File

@ -12,7 +12,6 @@ typedef NS_ENUM(NSInteger, OWSMessageCellType) {
OWSMessageCellType_TextOnlyMessage,
OWSMessageCellType_Audio,
OWSMessageCellType_GenericAttachment,
OWSMessageCellType_ContactShare,
OWSMessageCellType_MediaMessage,
OWSMessageCellType_OversizeTextDownloading,
};
@ -67,7 +66,6 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType);
@property (nonatomic, readonly, nullable) OWSQuotedReplyModel *quotedReply;
@property (nonatomic, readonly) BOOL isGroupThread;
@property (nonatomic, readonly) BOOL isRSSFeed;
@property (nonatomic, readonly) BOOL userCanDeleteGroupMessage;
@property (nonatomic, readonly) BOOL hasBodyText;
@ -163,7 +161,6 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType);
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithInteraction:(TSInteraction *)interaction
isGroupThread:(BOOL)isGroupThread
isRSSFeed:(BOOL)isRSSFeed
transaction:(YapDatabaseReadTransaction *)transaction
conversationStyle:(ConversationStyle *)conversationStyle;

View File

@ -4,7 +4,7 @@
#import <CoreServices/CoreServices.h>
#import "ConversationViewItem.h"
#import "OWSContactOffersCell.h"
#import "OWSMessageCell.h"
#import "OWSMessageHeaderView.h"
#import "OWSSystemMessageCell.h"
@ -13,7 +13,7 @@
#import <SignalUtilitiesKit/OWSUnreadIndicator.h>
#import <SignalUtilitiesKit/NSData+Image.h>
#import <SignalUtilitiesKit/NSString+SSK.h>
#import <SignalUtilitiesKit/OWSContact.h>
#import <SignalUtilitiesKit/TSInteraction.h>
#import <SignalUtilitiesKit/SSKEnvironment.h>
#import <SignalUtilitiesKit/SignalUtilitiesKit-Swift.h>
@ -31,8 +31,6 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
return @"OWSMessageCellType_GenericAttachment";
case OWSMessageCellType_Unknown:
return @"OWSMessageCellType_Unknown";
case OWSMessageCellType_ContactShare:
return @"OWSMessageCellType_ContactShare";
case OWSMessageCellType_MediaMessage:
return @"OWSMessageCellType_MediaMessage";
case OWSMessageCellType_OversizeTextDownloading:
@ -119,7 +117,6 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
@synthesize interaction = _interaction;
@synthesize isFirstInCluster = _isFirstInCluster;
@synthesize isGroupThread = _isGroupThread;
@synthesize isRSSFeed = _isRSSFeed;
@synthesize isLastInCluster = _isLastInCluster;
@synthesize lastAudioMessageView = _lastAudioMessageView;
@synthesize senderName = _senderName;
@ -127,7 +124,6 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
- (instancetype)initWithInteraction:(TSInteraction *)interaction
isGroupThread:(BOOL)isGroupThread
isRSSFeed:(BOOL)isRSSFeed
transaction:(YapDatabaseReadTransaction *)transaction
conversationStyle:(ConversationStyle *)conversationStyle
{
@ -143,11 +139,8 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
_interaction = interaction;
_isGroupThread = isGroupThread;
_isRSSFeed = isRSSFeed;
_conversationStyle = conversationStyle;
[self updateAuthorConversationColorNameWithTransaction:transaction];
[self ensureViewState:transaction];
return self;
@ -173,37 +166,11 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
self.linkPreview = nil;
self.linkPreviewAttachment = nil;
[self updateAuthorConversationColorNameWithTransaction:transaction];
[self clearCachedLayoutState];
[self ensureViewState:transaction];
}
- (void)updateAuthorConversationColorNameWithTransaction:(YapDatabaseReadTransaction *)transaction
{
OWSAssertDebug(transaction);
switch (self.interaction.interactionType) {
case OWSInteractionType_TypingIndicator: {
OWSTypingIndicatorInteraction *typingIndicator = (OWSTypingIndicatorInteraction *)self.interaction;
_authorConversationColorName =
[TSContactThread conversationColorNameForRecipientId:typingIndicator.recipientId
transaction:transaction];
break;
}
case OWSInteractionType_IncomingMessage: {
TSIncomingMessage *incomingMessage = (TSIncomingMessage *)self.interaction;
_authorConversationColorName =
[TSContactThread conversationColorNameForRecipientId:incomingMessage.authorId transaction:transaction];
break;
}
default:
_authorConversationColorName = nil;
break;
}
}
- (OWSPrimaryStorage *)primaryStorage
{
return SSKEnvironment.shared.primaryStorage;
@ -385,9 +352,6 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
case OWSInteractionType_Call:
measurementCell = [OWSSystemMessageCell new];
break;
case OWSInteractionType_Offer:
measurementCell = [OWSContactOffersCell new];
break;
case OWSInteractionType_TypingIndicator:
measurementCell = [OWSTypingIndicatorCell new];
break;
@ -445,10 +409,6 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
case OWSInteractionType_Call:
return [collectionView dequeueReusableCellWithReuseIdentifier:[OWSSystemMessageCell cellReuseIdentifier]
forIndexPath:indexPath];
case OWSInteractionType_Offer:
return [collectionView dequeueReusableCellWithReuseIdentifier:[OWSContactOffersCell cellReuseIdentifier]
forIndexPath:indexPath];
case OWSInteractionType_TypingIndicator:
return [collectionView dequeueReusableCellWithReuseIdentifier:[OWSTypingIndicatorCell cellReuseIdentifier]
forIndexPath:indexPath];
@ -609,12 +569,6 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
self.hasViewState = YES;
TSMessage *message = (TSMessage *)self.interaction;
if (message.contactShare) {
self.contactShare =
[[ContactShareViewModel alloc] initWithContactShareRecord:message.contactShare transaction:transaction];
self.messageCellType = OWSMessageCellType_ContactShare;
return;
}
// Check for quoted replies _before_ media album handling,
// since that logic may exit early.
@ -804,35 +758,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
}
case OWSInteractionType_Info: {
TSInfoMessage *infoMessage = (TSInfoMessage *)self.interaction;
if ([infoMessage isKindOfClass:[OWSVerificationStateChangeMessage class]]) {
OWSVerificationStateChangeMessage *verificationMessage
= (OWSVerificationStateChangeMessage *)infoMessage;
BOOL isVerified = verificationMessage.verificationState == OWSVerificationStateVerified;
NSString *displayName =
[Environment.shared.contactsManager displayNameForPhoneIdentifier:verificationMessage.recipientId];
NSString *titleFormat = (isVerified
? (verificationMessage.isLocalChange
? NSLocalizedString(@"VERIFICATION_STATE_CHANGE_FORMAT_VERIFIED_LOCAL",
@"Format for info message indicating that the verification state was verified "
@"on "
@"this device. Embeds {{user's name or phone number}}.")
: NSLocalizedString(@"VERIFICATION_STATE_CHANGE_FORMAT_VERIFIED_OTHER_DEVICE",
@"Format for info message indicating that the verification state was verified "
@"on "
@"another device. Embeds {{user's name or phone number}}."))
: (verificationMessage.isLocalChange
? NSLocalizedString(@"VERIFICATION_STATE_CHANGE_FORMAT_NOT_VERIFIED_LOCAL",
@"Format for info message indicating that the verification state was "
@"unverified on "
@"this device. Embeds {{user's name or phone number}}.")
: NSLocalizedString(@"VERIFICATION_STATE_CHANGE_FORMAT_NOT_VERIFIED_OTHER_DEVICE",
@"Format for info message indicating that the verification state was "
@"unverified on "
@"another device. Embeds {{user's name or phone number}}.")));
return [NSString stringWithFormat:titleFormat, displayName];
} else {
return [infoMessage previewTextWithTransaction:transaction];
}
return [infoMessage previewTextWithTransaction:transaction];
}
case OWSInteractionType_Call: {
TSCall *call = (TSCall *)self.interaction;
@ -921,11 +847,6 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
OWSFailDebug(@"No text to copy");
break;
}
case OWSMessageCellType_ContactShare: {
// TODO: Implement copy contact.
OWSFailDebug(@"Not implemented yet");
break;
}
case OWSMessageCellType_OversizeTextDownloading:
OWSFailDebug(@"Can't copy not-yet-downloaded attachment");
return;
@ -942,10 +863,6 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
switch (self.messageCellType) {
case OWSMessageCellType_Unknown:
case OWSMessageCellType_TextOnlyMessage:
case OWSMessageCellType_ContactShare: {
OWSFailDebug(@"No media to copy");
break;
}
case OWSMessageCellType_Audio:
case OWSMessageCellType_GenericAttachment: {
[self copyAttachmentToPasteboard:self.attachmentStream];
@ -996,9 +913,6 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
switch (self.messageCellType) {
case OWSMessageCellType_Unknown:
case OWSMessageCellType_TextOnlyMessage:
case OWSMessageCellType_ContactShare:
OWSFailDebug(@"No media to share.");
break;
case OWSMessageCellType_Audio:
case OWSMessageCellType_GenericAttachment:
[AttachmentSharing showShareUIForAttachment:self.attachmentStream];
@ -1035,8 +949,6 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
switch (self.messageCellType) {
case OWSMessageCellType_Unknown:
case OWSMessageCellType_TextOnlyMessage:
case OWSMessageCellType_ContactShare:
return NO;
case OWSMessageCellType_Audio:
return NO;
case OWSMessageCellType_GenericAttachment:
@ -1064,8 +976,6 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
switch (self.messageCellType) {
case OWSMessageCellType_Unknown:
case OWSMessageCellType_TextOnlyMessage:
case OWSMessageCellType_ContactShare:
return NO;
case OWSMessageCellType_Audio:
return NO;
case OWSMessageCellType_GenericAttachment:
@ -1104,9 +1014,6 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
switch (self.messageCellType) {
case OWSMessageCellType_Unknown:
case OWSMessageCellType_TextOnlyMessage:
case OWSMessageCellType_ContactShare:
OWSFailDebug(@"Cannot save text data.");
break;
case OWSMessageCellType_Audio:
OWSFailDebug(@"Cannot save media data.");
break;
@ -1177,7 +1084,6 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
if (self.isGroupThread) {
// Skip if the thread is an RSS feed
TSGroupThread *groupThread = (TSGroupThread *)self.interaction.thread;
if (groupThread.isRSSFeed) return;
// Only allow deletion on incoming and outgoing messages
OWSInteractionType interationType = self.interaction.interactionType;
@ -1217,8 +1123,6 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
switch (self.messageCellType) {
case OWSMessageCellType_Unknown:
case OWSMessageCellType_TextOnlyMessage:
case OWSMessageCellType_ContactShare:
return NO;
case OWSMessageCellType_Audio:
case OWSMessageCellType_GenericAttachment:
return self.attachmentStream != nil;
@ -1252,7 +1156,6 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
// Ensure the thread is a public chat and not an RSS feed
TSGroupThread *groupThread = (TSGroupThread *)self.interaction.thread;
if (groupThread.isRSSFeed) return false;
// Only allow deletion on incoming and outgoing messages
OWSInteractionType interationType = self.interaction.interactionType;

View File

@ -98,12 +98,10 @@ typedef NS_ENUM(NSUInteger, ConversationUpdateItemType) {
@property (nonatomic, readonly) ConversationViewState *viewState;
@property (nonatomic, nullable) NSString *focusMessageIdOnOpen;
@property (nonatomic, readonly, nullable) ThreadDynamicInteractions *dynamicInteractions;
@property (nonatomic, readonly) BOOL isRSSFeed;
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithThread:(TSThread *)thread
focusMessageIdOnOpen:(nullable NSString *)focusMessageIdOnOpen
isRSSFeed:(BOOL)isRSSFeed
delegate:(id<ConversationViewModelDelegate>)delegate NS_DESIGNATED_INITIALIZER;
- (void)ensureDynamicInteractionsAndUpdateIfNecessary:(BOOL)updateIfNecessary;

View File

@ -10,10 +10,10 @@
#import "Session-Swift.h"
#import <SignalCoreKit/NSDate+OWS.h>
#import <SignalUtilitiesKit/OWSContactOffersInteraction.h>
#import <SignalUtilitiesKit/OWSContactsManager.h>
#import <SignalUtilitiesKit/OWSUnreadIndicator.h>
#import <SignalUtilitiesKit/SignalUtilitiesKit-Swift.h>
#import <SignalUtilitiesKit/ThreadUtil.h>
#import <SignalUtilitiesKit/OWSBlockingManager.h>
#import <SignalUtilitiesKit/OWSPrimaryStorage.h>
#import <SignalUtilitiesKit/SSKEnvironment.h>
@ -226,7 +226,6 @@ static const int kYapDatabaseRangeMaxLength = 25000;
- (instancetype)initWithThread:(TSThread *)thread
focusMessageIdOnOpen:(nullable NSString *)focusMessageIdOnOpen
isRSSFeed:(BOOL)isRSSFeed
delegate:(id<ConversationViewModelDelegate>)delegate
{
self = [super init];
@ -242,7 +241,6 @@ static const int kYapDatabaseRangeMaxLength = 25000;
_persistedViewItems = @[];
_unsavedOutgoingMessages = @[];
self.focusMessageIdOnOpen = focusMessageIdOnOpen;
_isRSSFeed = isRSSFeed;
_viewState = [[ConversationViewState alloc] initWithViewItems:@[]];
[self configure];
@ -269,11 +267,6 @@ static const int kYapDatabaseRangeMaxLength = 25000;
return self.primaryStorage.dbReadWriteConnection;
}
- (OWSContactsManager *)contactsManager
{
return (OWSContactsManager *)SSKEnvironment.shared.contactsManager;
}
- (OWSBlockingManager *)blockingManager
{
return OWSBlockingManager.sharedManager;
@ -304,10 +297,6 @@ static const int kYapDatabaseRangeMaxLength = 25000;
selector:@selector(applicationDidEnterBackground:)
name:OWSApplicationDidEnterBackgroundNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(signalAccountsDidChange:)
name:OWSContactsManagerSignalAccountsDidChangeNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(typingIndicatorStateDidChange:)
name:[OWSTypingIndicatorsImpl typingIndicatorStateDidChange]
@ -530,7 +519,6 @@ static const int kYapDatabaseRangeMaxLength = 25000;
ThreadDynamicInteractions *dynamicInteractions =
[ThreadUtil ensureDynamicInteractionsForThread:self.thread
contactsManager:self.contactsManager
blockingManager:self.blockingManager
dbConnection:self.editingDatabaseConnection
hideUnreadMessagesIndicator:self.hasClearedUnreadMessagesIndicator
@ -1023,24 +1011,10 @@ static const int kYapDatabaseRangeMaxLength = 25000;
return;
}
// Many OWSProfileManager methods aren't safe to call from inside a database
// transaction, so do this work now.
//
// TODO: It'd be nice if these methods took a transaction.
BOOL hasLocalProfile = [self.profileManager hasLocalProfile];
BOOL isThreadInProfileWhitelist = [self.profileManager isThreadInProfileWhitelist:self.thread];
BOOL hasUnwhitelistedMember = NO;
for (NSString *recipientId in self.thread.recipientIdentifiers) {
if (![self.profileManager isUserInProfileWhitelist:recipientId]) {
hasUnwhitelistedMember = YES;
break;
}
}
ConversationProfileState *conversationProfileState = [ConversationProfileState new];
conversationProfileState.hasLocalProfile = hasLocalProfile;
conversationProfileState.isThreadInProfileWhitelist = isThreadInProfileWhitelist;
conversationProfileState.hasUnwhitelistedMember = hasUnwhitelistedMember;
conversationProfileState.hasLocalProfile = YES;
conversationProfileState.isThreadInProfileWhitelist = YES;
conversationProfileState.hasUnwhitelistedMember = NO;
self.conversationProfileState = conversationProfileState;
}
@ -1136,15 +1110,6 @@ static const int kYapDatabaseRangeMaxLength = 25000;
// Don't create profile whitelist offers for users which are not already blocked.
shouldHaveAddToProfileWhitelistOffer = NO;
}
if ([self.contactsManager hasSignalAccountForRecipientId:recipientId]) {
// Only create "add to contacts" offers for non-contacts.
shouldHaveAddToContactsOffer = NO;
// Only create block offers for non-contacts.
shouldHaveBlockOffer = NO;
// Don't create profile whitelist offers for non-contacts.
shouldHaveAddToProfileWhitelistOffer = NO;
}
}
if (hasTooManyOutgoingMessagesToBlock) {
@ -1214,7 +1179,6 @@ static const int kYapDatabaseRangeMaxLength = 25000;
NSArray<NSString *> *loadedUniqueIds = [self.messageMapping loadedUniqueIds];
BOOL isGroupThread = self.thread.isGroupThread;
BOOL isRSSFeed = self.isRSSFeed;
ConversationStyle *conversationStyle = self.delegate.conversationStyle;
[self ensureConversationProfileState];
@ -1228,7 +1192,6 @@ static const int kYapDatabaseRangeMaxLength = 25000;
if (!viewItem) {
viewItem = [[ConversationInteractionViewItem alloc] initWithInteraction:interaction
isGroupThread:isGroupThread
isRSSFeed:isRSSFeed
transaction:transaction
conversationStyle:conversationStyle];
}
@ -1236,7 +1199,7 @@ static const int kYapDatabaseRangeMaxLength = 25000;
viewItemCache[interaction.uniqueId] = viewItem;
[viewItems addObject:viewItem];
TSMessage *message = (TSMessage *)viewItem.interaction;
if (message.hasAttachmentsInNSE) {
if (message.hasUnfetchedAttachmentsFromPN) {
[SSKEnvironment.shared.attachmentDownloads downloadAttachmentsForMessage:message
transaction:transaction
success:^(NSArray<TSAttachmentStream *> *attachmentStreams) {
@ -1534,8 +1497,7 @@ static const int kYapDatabaseRangeMaxLength = 25000;
}
if (shouldShowSenderName) {
senderName = [self.contactsManager attributedContactOrProfileNameForPhoneIdentifier:incomingSenderId primaryAttributes:[OWSMessageBubbleView senderNamePrimaryAttributes]
secondaryAttributes:[OWSMessageBubbleView senderNameSecondaryAttributes]];
senderName = [[NSAttributedString alloc] initWithString:[SSKEnvironment.shared.profileManager profileNameForRecipientWithID:incomingSenderId avoidingWriteTransaction:YES]];
if ([self.thread isKindOfClass:[TSGroupThread class]]) {
TSGroupThread *groupThread = (TSGroupThread *)self.thread;
@ -1558,9 +1520,7 @@ static const int kYapDatabaseRangeMaxLength = 25000;
// the next message has the same sender avatar and
// no "date break" separates us.
shouldShowSenderAvatar = YES;
if (viewItem.isRSSFeed) {
shouldShowSenderAvatar = NO;
} else if (previousViewItem && previousViewItem.interaction.interactionType == interactionType) {
if (previousViewItem && previousViewItem.interaction.interactionType == interactionType) {
shouldShowSenderAvatar = (![NSObject isNullableObject:previousIncomingSenderId equalTo:incomingSenderId]);
}
}

View File

@ -1,13 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import <SignalUtilitiesKit/OWSViewController.h>
NS_ASSUME_NONNULL_BEGIN
@interface DomainFrontingCountryViewController : OWSViewController
@end
NS_ASSUME_NONNULL_END

View File

@ -1,98 +0,0 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
#import "DomainFrontingCountryViewController.h"
#import "OWSCountryMetadata.h"
#import "OWSTableViewController.h"
#import "UIColor+OWS.h"
#import "UIFont+OWS.h"
#import "UIView+OWS.h"
#import <SignalUtilitiesKit/Theme.h>
#import <SignalUtilitiesKit/OWSSignalService.h>
NS_ASSUME_NONNULL_BEGIN
#pragma mark -
@interface DomainFrontingCountryViewController ()
@property (nonatomic, readonly) OWSTableViewController *tableViewController;
@end
#pragma mark -
@implementation DomainFrontingCountryViewController
- (void)loadView
{
[super loadView];
self.title = NSLocalizedString(
@"CENSORSHIP_CIRCUMVENTION_COUNTRY_VIEW_TITLE", @"Title for the 'censorship circumvention country' view.");
self.view.backgroundColor = Theme.backgroundColor;
[self createViews];
}
- (void)createViews
{
_tableViewController = [OWSTableViewController new];
[self.view addSubview:self.tableViewController.view];
[self.tableViewController.view autoPinEdgeToSuperviewSafeArea:ALEdgeLeading];
[self.tableViewController.view autoPinEdgeToSuperviewSafeArea:ALEdgeTrailing];
[_tableViewController.view autoPinEdge:ALEdgeTop toEdge:ALEdgeTop ofView:self.view withOffset:0.0f];
[_tableViewController.view autoPinEdge:ALEdgeBottom toEdge:ALEdgeBottom ofView:self.view withOffset:0.0f];
[self updateTableContents];
}
#pragma mark - Table Contents
- (void)updateTableContents
{
OWSTableContents *contents = [OWSTableContents new];
NSString *currentCountryCode = OWSSignalService.sharedInstance.manualCensorshipCircumventionCountryCode;
__weak DomainFrontingCountryViewController *weakSelf = self;
OWSTableSection *section = [OWSTableSection new];
section.headerTitle = NSLocalizedString(
@"DOMAIN_FRONTING_COUNTRY_VIEW_SECTION_HEADER", @"Section title for the 'domain fronting country' view.");
for (OWSCountryMetadata *countryMetadata in [OWSCountryMetadata allCountryMetadatas]) {
[section addItem:[OWSTableItem
itemWithCustomCellBlock:^{
UITableViewCell *cell = [OWSTableItem newCell];
[OWSTableItem configureCell:cell];
cell.textLabel.text = countryMetadata.localizedCountryName;
if ([countryMetadata.countryCode isEqualToString:currentCountryCode]) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
}
return cell;
}
actionBlock:^{
[weakSelf selectCountry:countryMetadata];
}]];
}
[contents addSection:section];
self.tableViewController.contents = contents;
}
- (void)selectCountry:(OWSCountryMetadata *)countryMetadata
{
OWSAssertDebug(countryMetadata);
OWSSignalService.sharedInstance.manualCensorshipCircumventionCountryCode = countryMetadata.countryCode;
[self.navigationController popViewControllerAnimated:YES];
}
@end
NS_ASSUME_NONNULL_END

View File

@ -1,15 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import <SignalUtilitiesKit/OWSViewController.h>
NS_ASSUME_NONNULL_BEGIN
@interface FingerprintViewController : OWSViewController
+ (void)presentFromViewController:(UIViewController *)viewController recipientId:(NSString *)recipientId;
@end
NS_ASSUME_NONNULL_END

View File

@ -1,558 +0,0 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
#import "FingerprintViewController.h"
#import "FingerprintViewScanController.h"
#import "OWSBezierPathView.h"
#import "Session-Swift.h"
#import "UIColor+OWS.h"
#import "UIFont+OWS.h"
#import "UIView+OWS.h"
#import <SignalCoreKit/NSDate+OWS.h>
#import <SignalUtilitiesKit/Environment.h>
#import <SignalUtilitiesKit/OWSContactsManager.h>
#import <SignalUtilitiesKit/UIUtil.h>
#import <SignalUtilitiesKit/OWSError.h>
#import <SignalUtilitiesKit/OWSFingerprint.h>
#import <SignalUtilitiesKit/OWSFingerprintBuilder.h>
#import <SignalUtilitiesKit/OWSIdentityManager.h>
#import <SignalUtilitiesKit/OWSPrimaryStorage+SessionStore.h>
#import <SignalUtilitiesKit/TSAccountManager.h>
#import <SignalUtilitiesKit/TSInfoMessage.h>
NS_ASSUME_NONNULL_BEGIN
typedef void (^CustomLayoutBlock)(void);
@interface CustomLayoutView : UIView
@property (nonatomic) CustomLayoutBlock layoutBlock;
@end
#pragma mark -
@implementation CustomLayoutView
- (instancetype)init
{
if (self = [super init]) {
self.translatesAutoresizingMaskIntoConstraints = NO;
}
return self;
}
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
self.translatesAutoresizingMaskIntoConstraints = NO;
}
return self;
}
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder
{
if (self = [super initWithCoder:aDecoder]) {
self.translatesAutoresizingMaskIntoConstraints = NO;
}
return self;
}
- (void)layoutSubviews
{
[super layoutSubviews];
self.layoutBlock();
}
@end
#pragma mark -
@interface FingerprintViewController () <OWSCompareSafetyNumbersActivityDelegate>
@property (nonatomic) NSString *recipientId;
@property (nonatomic) NSData *identityKey;
@property (nonatomic) TSAccountManager *accountManager;
@property (nonatomic) OWSFingerprint *fingerprint;
@property (nonatomic) NSString *contactName;
@property (nonatomic) UIBarButtonItem *shareButton;
@property (nonatomic) UILabel *verificationStateLabel;
@property (nonatomic) UILabel *verifyUnverifyButtonLabel;
@end
#pragma mark -
@implementation FingerprintViewController
+ (void)presentFromViewController:(UIViewController *)viewController recipientId:(NSString *)recipientId
{
OWSAssertDebug(recipientId.length > 0);
OWSRecipientIdentity *_Nullable recipientIdentity =
[[OWSIdentityManager sharedManager] recipientIdentityForRecipientId:recipientId];
if (!recipientIdentity) {
[OWSAlerts showAlertWithTitle:NSLocalizedString(@"CANT_VERIFY_IDENTITY_ALERT_TITLE",
@"Title for alert explaining that a user cannot be verified.")
message:NSLocalizedString(@"CANT_VERIFY_IDENTITY_ALERT_MESSAGE",
@"Message for alert explaining that a user cannot be verified.")];
return;
}
FingerprintViewController *fingerprintViewController = [FingerprintViewController new];
[fingerprintViewController configureWithRecipientId:recipientId];
OWSNavigationController *navigationController =
[[OWSNavigationController alloc] initWithRootViewController:fingerprintViewController];
[viewController presentViewController:navigationController animated:YES completion:nil];
}
- (instancetype)init
{
self = [super init];
if (!self) {
return self;
}
_accountManager = [TSAccountManager sharedInstance];
[self observeNotifications];
return self;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)observeNotifications
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(identityStateDidChange:)
name:kNSNotificationName_IdentityStateDidChange
object:nil];
}
- (void)configureWithRecipientId:(NSString *)recipientId
{
OWSAssertDebug(recipientId.length > 0);
self.recipientId = recipientId;
OWSContactsManager *contactsManager = Environment.shared.contactsManager;
self.contactName = [contactsManager displayNameForPhoneIdentifier:recipientId];
OWSRecipientIdentity *_Nullable recipientIdentity =
[[OWSIdentityManager sharedManager] recipientIdentityForRecipientId:recipientId];
OWSAssertDebug(recipientIdentity);
// By capturing the identity key when we enter these views, we prevent the edge case
// where the user verifies a key that we learned about while this view was open.
self.identityKey = recipientIdentity.identityKey;
OWSFingerprintBuilder *builder =
[[OWSFingerprintBuilder alloc] initWithAccountManager:self.accountManager contactsManager:contactsManager];
self.fingerprint =
[builder fingerprintWithTheirSignalId:recipientId theirIdentityKey:recipientIdentity.identityKey];
}
- (void)loadView
{
[super loadView];
self.title = NSLocalizedString(@"PRIVACY_VERIFICATION_TITLE", @"Navbar title");
self.navigationItem.leftBarButtonItem =
[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemStop
target:self
action:@selector(closeButton)
accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME(self, @"stop")];
self.shareButton =
[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAction
target:self
action:@selector(didTapShareButton)
accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME(self, @"share")];
self.navigationItem.rightBarButtonItem = self.shareButton;
[self createViews];
}
- (void)createViews
{
self.view.backgroundColor = Theme.backgroundColor;
// Verify/Unverify Button
UIView *verifyUnverifyButton = [UIView new];
[verifyUnverifyButton
addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self
action:@selector(verifyUnverifyButtonTapped:)]];
[self.view addSubview:verifyUnverifyButton];
[verifyUnverifyButton autoPinWidthToSuperview];
[verifyUnverifyButton autoPinEdge:ALEdgeBottom toEdge:ALEdgeBottom ofView:self.view withOffset:0.0f];
SET_SUBVIEW_ACCESSIBILITY_IDENTIFIER(self, verifyUnverifyButton);
UIView *verifyUnverifyPillbox = [UIView new];
verifyUnverifyPillbox.backgroundColor = [UIColor ows_materialBlueColor];
verifyUnverifyPillbox.layer.cornerRadius = 3.f;
verifyUnverifyPillbox.clipsToBounds = YES;
[verifyUnverifyButton addSubview:verifyUnverifyPillbox];
[verifyUnverifyPillbox autoHCenterInSuperview];
[verifyUnverifyPillbox autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:ScaleFromIPhone5To7Plus(10.f, 15.f)];
[verifyUnverifyPillbox autoPinEdgeToSuperviewEdge:ALEdgeBottom withInset:ScaleFromIPhone5To7Plus(10.f, 20.f)];
UILabel *verifyUnverifyButtonLabel = [UILabel new];
self.verifyUnverifyButtonLabel = verifyUnverifyButtonLabel;
verifyUnverifyButtonLabel.font = [UIFont ows_mediumFontWithSize:ScaleFromIPhone5To7Plus(14.f, 20.f)];
verifyUnverifyButtonLabel.textColor = [UIColor whiteColor];
verifyUnverifyButtonLabel.textAlignment = NSTextAlignmentCenter;
[verifyUnverifyPillbox addSubview:verifyUnverifyButtonLabel];
[verifyUnverifyButtonLabel autoPinWidthToSuperviewWithMargin:ScaleFromIPhone5To7Plus(50.f, 50.f)];
[verifyUnverifyButtonLabel autoPinHeightToSuperviewWithMargin:ScaleFromIPhone5To7Plus(8.f, 8.f)];
// Learn More
UIView *learnMoreButton = [UIView new];
[learnMoreButton
addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self
action:@selector(learnMoreButtonTapped:)]];
[self.view addSubview:learnMoreButton];
[learnMoreButton autoPinWidthToSuperview];
[learnMoreButton autoPinEdge:ALEdgeBottom toEdge:ALEdgeTop ofView:verifyUnverifyButton withOffset:0];
SET_SUBVIEW_ACCESSIBILITY_IDENTIFIER(self, learnMoreButton);
UILabel *learnMoreLabel = [UILabel new];
learnMoreLabel.attributedText = [[NSAttributedString alloc]
initWithString:NSLocalizedString(@"PRIVACY_SAFETY_NUMBERS_LEARN_MORE",
@"Label for a link to more information about safety numbers and verification.")
attributes:@{
NSUnderlineStyleAttributeName : @(NSUnderlineStyleSingle | NSUnderlinePatternSolid),
}];
learnMoreLabel.font = [UIFont ows_regularFontWithSize:ScaleFromIPhone5To7Plus(13.f, 16.f)];
learnMoreLabel.textColor = [UIColor ows_materialBlueColor];
learnMoreLabel.textAlignment = NSTextAlignmentCenter;
[learnMoreButton addSubview:learnMoreLabel];
[learnMoreLabel autoPinWidthToSuperviewWithMargin:16.f];
[learnMoreLabel autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:ScaleFromIPhone5To7Plus(5.f, 10.f)];
[learnMoreLabel autoPinEdgeToSuperviewEdge:ALEdgeBottom withInset:ScaleFromIPhone5To7Plus(5.f, 10.f)];
// Instructions
NSString *instructionsFormat = NSLocalizedString(@"PRIVACY_VERIFICATION_INSTRUCTIONS",
@"Paragraph(s) shown alongside the safety number when verifying privacy with {{contact name}}");
UILabel *instructionsLabel = [UILabel new];
instructionsLabel.text = [NSString stringWithFormat:instructionsFormat, self.contactName];
instructionsLabel.font = [UIFont ows_regularFontWithSize:ScaleFromIPhone5To7Plus(11.f, 14.f)];
instructionsLabel.textColor = Theme.secondaryColor;
instructionsLabel.textAlignment = NSTextAlignmentCenter;
instructionsLabel.numberOfLines = 0;
instructionsLabel.lineBreakMode = NSLineBreakByWordWrapping;
[self.view addSubview:instructionsLabel];
[instructionsLabel autoPinWidthToSuperviewWithMargin:16.f];
[instructionsLabel autoPinEdge:ALEdgeBottom toEdge:ALEdgeTop ofView:learnMoreButton withOffset:0];
// Fingerprint Label
UILabel *fingerprintLabel = [UILabel new];
fingerprintLabel.text = self.fingerprint.displayableText;
fingerprintLabel.font = [UIFont fontWithName:@"Menlo-Regular" size:ScaleFromIPhone5To7Plus(20.f, 23.f)];
fingerprintLabel.textColor = Theme.secondaryColor;
fingerprintLabel.numberOfLines = 3;
fingerprintLabel.lineBreakMode = NSLineBreakByTruncatingTail;
fingerprintLabel.adjustsFontSizeToFitWidth = YES;
[fingerprintLabel
addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self
action:@selector(fingerprintLabelTapped:)]];
fingerprintLabel.userInteractionEnabled = YES;
[self.view addSubview:fingerprintLabel];
[fingerprintLabel autoPinWidthToSuperviewWithMargin:ScaleFromIPhone5To7Plus(50.f, 60.f)];
[fingerprintLabel autoPinEdge:ALEdgeBottom
toEdge:ALEdgeTop
ofView:instructionsLabel
withOffset:-ScaleFromIPhone5To7Plus(8.f, 15.f)];
SET_SUBVIEW_ACCESSIBILITY_IDENTIFIER(self, fingerprintLabel);
// Fingerprint Image
CustomLayoutView *fingerprintView = [CustomLayoutView new];
[self.view addSubview:fingerprintView];
[fingerprintView autoPinWidthToSuperview];
[fingerprintView autoPinEdge:ALEdgeBottom
toEdge:ALEdgeTop
ofView:fingerprintLabel
withOffset:-ScaleFromIPhone5To7Plus(10.f, 15.f)];
[fingerprintView
addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self
action:@selector(fingerprintViewTapped:)]];
fingerprintView.userInteractionEnabled = YES;
SET_SUBVIEW_ACCESSIBILITY_IDENTIFIER(self, fingerprintView);
OWSBezierPathView *fingerprintCircle = [OWSBezierPathView new];
[fingerprintCircle setConfigureShapeLayerBlock:^(CAShapeLayer *layer, CGRect bounds) {
layer.fillColor = Theme.offBackgroundColor.CGColor;
CGFloat size = MIN(bounds.size.width, bounds.size.height);
CGRect circle = CGRectMake((bounds.size.width - size) * 0.5f, (bounds.size.height - size) * 0.5f, size, size);
layer.path = [UIBezierPath bezierPathWithOvalInRect:circle].CGPath;
}];
[fingerprintView addSubview:fingerprintCircle];
[fingerprintCircle ows_autoPinToSuperviewEdges];
UIImageView *fingerprintImageView = [UIImageView new];
fingerprintImageView.image = self.fingerprint.image;
// Don't antialias QR Codes.
fingerprintImageView.layer.magnificationFilter = kCAFilterNearest;
fingerprintImageView.layer.minificationFilter = kCAFilterNearest;
[fingerprintView addSubview:fingerprintImageView];
UILabel *scanLabel = [UILabel new];
scanLabel.text = NSLocalizedString(@"PRIVACY_TAP_TO_SCAN", @"Button that shows the 'scan with camera' view.");
scanLabel.font = [UIFont ows_mediumFontWithSize:ScaleFromIPhone5To7Plus(14.f, 16.f)];
scanLabel.textColor = Theme.secondaryColor;
[scanLabel sizeToFit];
[fingerprintView addSubview:scanLabel];
fingerprintView.layoutBlock = ^{
CGFloat size = round(MIN(fingerprintView.width, fingerprintView.height) * 0.675f);
fingerprintImageView.frame = CGRectMake(
round((fingerprintView.width - size) * 0.5f), round((fingerprintView.height - size) * 0.5f), size, size);
CGFloat scanY = round(fingerprintImageView.bottom
+ ((fingerprintView.height - fingerprintImageView.bottom) - scanLabel.height) * 0.33f);
scanLabel.frame = CGRectMake(
round((fingerprintView.width - scanLabel.width) * 0.5f), scanY, scanLabel.width, scanLabel.height);
};
// Verification State
UILabel *verificationStateLabel = [UILabel new];
self.verificationStateLabel = verificationStateLabel;
verificationStateLabel.font = [UIFont ows_mediumFontWithSize:ScaleFromIPhone5To7Plus(16.f, 20.f)];
verificationStateLabel.textColor = Theme.secondaryColor;
verificationStateLabel.textAlignment = NSTextAlignmentCenter;
verificationStateLabel.numberOfLines = 0;
verificationStateLabel.lineBreakMode = NSLineBreakByWordWrapping;
[self.view addSubview:verificationStateLabel];
[verificationStateLabel autoPinWidthToSuperviewWithMargin:16.f];
// Bind height of label to height of two lines of text.
// This should always be sufficient, and will prevent the view's
// layout from changing if the user is marked as verified or not
// verified.
[verificationStateLabel autoSetDimension:ALDimensionHeight
toSize:round(verificationStateLabel.font.lineHeight * 2.25f)];
[verificationStateLabel autoPinEdge:ALEdgeTop toEdge:ALEdgeTop ofView:self.view withOffset:ScaleFromIPhone5To7Plus(15.f, 20.f)];
[verificationStateLabel autoPinEdge:ALEdgeBottom
toEdge:ALEdgeTop
ofView:fingerprintView
withOffset:-ScaleFromIPhone5To7Plus(10.f, 15.f)];
[self updateVerificationStateLabel];
}
- (void)updateVerificationStateLabel
{
OWSAssertDebug(self.recipientId.length > 0);
BOOL isVerified = [[OWSIdentityManager sharedManager] verificationStateForRecipientId:self.recipientId]
== OWSVerificationStateVerified;
if (isVerified) {
NSMutableAttributedString *labelText = [NSMutableAttributedString new];
if (isVerified) {
// Show a "checkmark" if this user is verified.
[labelText
appendAttributedString:[[NSAttributedString alloc]
initWithString:LocalizationNotNeeded(@"\uf00c ")
attributes:@{
NSFontAttributeName : [UIFont
ows_fontAwesomeFont:self.verificationStateLabel.font.pointSize],
}]];
}
[labelText
appendAttributedString:
[[NSAttributedString alloc]
initWithString:[NSString stringWithFormat:NSLocalizedString(@"PRIVACY_IDENTITY_IS_VERIFIED_FORMAT",
@"Label indicating that the user is verified. Embeds "
@"{{the user's name or phone number}}."),
self.contactName]]];
self.verificationStateLabel.attributedText = labelText;
self.verifyUnverifyButtonLabel.text = NSLocalizedString(
@"PRIVACY_UNVERIFY_BUTTON", @"Button that lets user mark another user's identity as unverified.");
} else {
self.verificationStateLabel.text = [NSString
stringWithFormat:NSLocalizedString(@"PRIVACY_IDENTITY_IS_NOT_VERIFIED_FORMAT",
@"Label indicating that the user is not verified. Embeds {{the user's name or phone "
@"number}}."),
self.contactName];
NSMutableAttributedString *buttonText = [NSMutableAttributedString new];
// Show a "checkmark" if this user is not verified.
[buttonText
appendAttributedString:[[NSAttributedString alloc]
initWithString:LocalizationNotNeeded(@"\uf00c ")
attributes:@{
NSFontAttributeName : [UIFont
ows_fontAwesomeFont:self.verifyUnverifyButtonLabel.font.pointSize],
}]];
[buttonText appendAttributedString:
[[NSAttributedString alloc]
initWithString:NSLocalizedString(@"PRIVACY_VERIFY_BUTTON",
@"Button that lets user mark another user's identity as verified.")]];
self.verifyUnverifyButtonLabel.attributedText = buttonText;
}
[self.view setNeedsLayout];
}
#pragma mark -
- (void)showSharingActivityWithCompletion:(nullable void (^)(void))completionHandler
{
OWSLogDebug(@"Sharing safety numbers");
OWSCompareSafetyNumbersActivity *compareActivity = [[OWSCompareSafetyNumbersActivity alloc] initWithDelegate:self];
NSString *shareFormat = NSLocalizedString(
@"SAFETY_NUMBER_SHARE_FORMAT", @"Snippet to share {{safety number}} with a friend. sent e.g. via SMS");
NSString *shareString = [NSString stringWithFormat:shareFormat, self.fingerprint.displayableText];
UIActivityViewController *activityController =
[[UIActivityViewController alloc] initWithActivityItems:@[ shareString ]
applicationActivities:@[ compareActivity ]];
activityController.completionWithItemsHandler = ^void(UIActivityType __nullable activityType,
BOOL completed,
NSArray *__nullable returnedItems,
NSError *__nullable activityError) {
if (completionHandler) {
completionHandler();
}
};
// This value was extracted by inspecting `activityType` in the activityController.completionHandler
NSString *const iCloudActivityType = @"com.apple.CloudDocsUI.AddToiCloudDrive";
activityController.excludedActivityTypes = @[
UIActivityTypePostToFacebook,
UIActivityTypePostToWeibo,
UIActivityTypeAirDrop,
UIActivityTypePostToTwitter,
iCloudActivityType // This isn't being excluded. RADAR https://openradar.appspot.com/27493621
];
[self presentViewController:activityController animated:YES completion:nil];
}
#pragma mark - OWSCompareSafetyNumbersActivityDelegate
- (void)compareSafetyNumbersActivitySucceededWithActivity:(OWSCompareSafetyNumbersActivity *)activity
{
[self showVerificationSucceeded];
}
- (void)compareSafetyNumbersActivity:(OWSCompareSafetyNumbersActivity *)activity failedWithError:(NSError *)error
{
[self showVerificationFailedWithError:error];
}
- (void)showVerificationSucceeded
{
[FingerprintViewScanController showVerificationSucceeded:self
identityKey:self.identityKey
recipientId:self.recipientId
contactName:self.contactName
tag:self.logTag];
}
- (void)showVerificationFailedWithError:(NSError *)error
{
[FingerprintViewScanController showVerificationFailedWithError:error
viewController:self
retryBlock:nil
cancelBlock:^{
// Do nothing.
}
tag:self.logTag];
}
#pragma mark - Action
- (void)closeButton
{
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void)didTapShareButton
{
[self showSharingActivityWithCompletion:nil];
}
- (void)showScanner
{
FingerprintViewScanController *scanView = [FingerprintViewScanController new];
[scanView configureWithRecipientId:self.recipientId];
[self.navigationController pushViewController:scanView animated:YES];
}
- (void)learnMoreButtonTapped:(UIGestureRecognizer *)gestureRecognizer
{
if (gestureRecognizer.state == UIGestureRecognizerStateRecognized) {
NSString *learnMoreURL = @"https://support.signal.org/hc/en-us/articles/"
@"213134107";
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:learnMoreURL]];
}
}
- (void)fingerprintLabelTapped:(UIGestureRecognizer *)gestureRecognizer
{
if (gestureRecognizer.state == UIGestureRecognizerStateRecognized) {
[self showSharingActivityWithCompletion:nil];
}
}
- (void)fingerprintViewTapped:(UIGestureRecognizer *)gestureRecognizer
{
if (gestureRecognizer.state == UIGestureRecognizerStateRecognized) {
[self showScanner];
}
}
- (void)verifyUnverifyButtonTapped:(UIGestureRecognizer *)gestureRecognizer
{
if (gestureRecognizer.state == UIGestureRecognizerStateRecognized) {
[LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
BOOL isVerified = [[OWSIdentityManager sharedManager] verificationStateForRecipientId:self.recipientId
transaction:transaction]
== OWSVerificationStateVerified;
OWSVerificationState newVerificationState
= (isVerified ? OWSVerificationStateDefault : OWSVerificationStateVerified);
[[OWSIdentityManager sharedManager] setVerificationState:newVerificationState
identityKey:self.identityKey
recipientId:self.recipientId
isUserInitiatedChange:YES
transaction:transaction];
}];
[self dismissViewControllerAnimated:YES completion:nil];
}
}
#pragma mark - Notifications
- (void)identityStateDidChange:(NSNotification *)notification
{
OWSAssertIsOnMainThread();
[self updateVerificationStateLabel];
}
#pragma mark - Orientation
- (UIInterfaceOrientationMask)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskPortrait;
}
@end
NS_ASSUME_NONNULL_END

View File

@ -1,27 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import <SignalUtilitiesKit/OWSViewController.h>
NS_ASSUME_NONNULL_BEGIN
@interface FingerprintViewScanController : OWSViewController
- (void)configureWithRecipientId:(NSString *)recipientId NS_SWIFT_NAME(configure(recipientId:));
+ (void)showVerificationSucceeded:(UIViewController *)viewController
identityKey:(NSData *)identityKey
recipientId:(NSString *)recipientId
contactName:(NSString *)contactName
tag:(NSString *)tag;
+ (void)showVerificationFailedWithError:(NSError *)error
viewController:(UIViewController *)viewController
retryBlock:(void (^_Nullable)(void))retryBlock
cancelBlock:(void (^_Nonnull)(void))cancelBlock
tag:(NSString *)tag;
@end
NS_ASSUME_NONNULL_END

View File

@ -1,258 +0,0 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
#import "FingerprintViewScanController.h"
#import "OWSQRCodeScanningViewController.h"
#import "Session-Swift.h"
#import "UIColor+OWS.h"
#import "UIFont+OWS.h"
#import "UIView+OWS.h"
#import "UIViewController+Permissions.h"
#import <SignalUtilitiesKit/Environment.h>
#import <SignalUtilitiesKit/OWSContactsManager.h>
#import <SignalUtilitiesKit/UIUtil.h>
#import <SignalUtilitiesKit/OWSError.h>
#import <SignalUtilitiesKit/OWSFingerprint.h>
#import <SignalUtilitiesKit/OWSFingerprintBuilder.h>
#import <SignalUtilitiesKit/OWSIdentityManager.h>
NS_ASSUME_NONNULL_BEGIN
@interface FingerprintViewScanController () <OWSQRScannerDelegate>
@property (nonatomic) TSAccountManager *accountManager;
@property (nonatomic) NSString *recipientId;
@property (nonatomic) NSData *identityKey;
@property (nonatomic) OWSFingerprint *fingerprint;
@property (nonatomic) NSString *contactName;
@property (nonatomic) OWSQRCodeScanningViewController *qrScanningController;
@end
#pragma mark -
@implementation FingerprintViewScanController
- (void)configureWithRecipientId:(NSString *)recipientId
{
OWSAssertDebug(recipientId.length > 0);
self.recipientId = recipientId;
self.accountManager = [TSAccountManager sharedInstance];
OWSContactsManager *contactsManager = Environment.shared.contactsManager;
self.contactName = [contactsManager displayNameForPhoneIdentifier:recipientId];
OWSRecipientIdentity *_Nullable recipientIdentity =
[[OWSIdentityManager sharedManager] recipientIdentityForRecipientId:recipientId];
OWSAssertDebug(recipientIdentity);
// By capturing the identity key when we enter these views, we prevent the edge case
// where the user verifies a key that we learned about while this view was open.
self.identityKey = recipientIdentity.identityKey;
OWSFingerprintBuilder *builder =
[[OWSFingerprintBuilder alloc] initWithAccountManager:self.accountManager contactsManager:contactsManager];
self.fingerprint =
[builder fingerprintWithTheirSignalId:recipientId theirIdentityKey:recipientIdentity.identityKey];
}
- (void)loadView
{
[super loadView];
self.title = NSLocalizedString(@"SCAN_QR_CODE_VIEW_TITLE", @"Title for the 'scan QR code' view.");
[self createViews];
}
- (void)createViews
{
self.view.backgroundColor = UIColor.blackColor;
self.qrScanningController = [OWSQRCodeScanningViewController new];
self.qrScanningController.scanDelegate = self;
[self.view addSubview:self.qrScanningController.view];
[self.qrScanningController.view autoPinWidthToSuperview];
[self.qrScanningController.view autoPinEdge:ALEdgeTop toEdge:ALEdgeTop ofView:self.view withOffset:0.0f];
UIView *footer = [UIView new];
footer.backgroundColor = [UIColor colorWithWhite:0.25f alpha:1.f];
[self.view addSubview:footer];
[footer autoPinWidthToSuperview];
[footer autoPinEdgeToSuperviewEdge:ALEdgeBottom];
[footer autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:self.qrScanningController.view];
UILabel *cameraInstructionLabel = [UILabel new];
cameraInstructionLabel.text
= NSLocalizedString(@"SCAN_CODE_INSTRUCTIONS", @"label presented once scanning (camera) view is visible.");
cameraInstructionLabel.font = [UIFont ows_regularFontWithSize:ScaleFromIPhone5To7Plus(14.f, 18.f)];
cameraInstructionLabel.textColor = [UIColor whiteColor];
cameraInstructionLabel.textAlignment = NSTextAlignmentCenter;
cameraInstructionLabel.numberOfLines = 0;
cameraInstructionLabel.lineBreakMode = NSLineBreakByWordWrapping;
[footer addSubview:cameraInstructionLabel];
[cameraInstructionLabel autoPinWidthToSuperviewWithMargin:ScaleFromIPhone5To7Plus(16.f, 30.f)];
CGFloat instructionsVMargin = ScaleFromIPhone5To7Plus(10.f, 20.f);
[cameraInstructionLabel autoPinEdge:ALEdgeBottom toEdge:ALEdgeBottom ofView:self.view withOffset:instructionsVMargin];
[cameraInstructionLabel autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:instructionsVMargin];
}
#pragma mark - Action
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self ows_askForCameraPermissions:^(BOOL granted) {
if (granted) {
// Camera stops capturing when "sharing" while in capture mode.
// Also, it's less obvious whats being "shared" at this point,
// so just disable sharing when in capture mode.
OWSLogInfo(@"Showing Scanner");
[self.qrScanningController startCapture];
} else {
[self.navigationController popViewControllerAnimated:YES];
}
}];
}
#pragma mark - OWSQRScannerDelegate
- (void)controller:(OWSQRCodeScanningViewController *)controller didDetectQRCodeWithData:(NSData *)data
{
[self verifyCombinedFingerprintData:data];
}
- (void)verifyCombinedFingerprintData:(NSData *)combinedFingerprintData
{
NSError *error;
if ([self.fingerprint matchesLogicalFingerprintsData:combinedFingerprintData error:&error]) {
[self showVerificationSucceeded];
} else {
[self showVerificationFailedWithError:error];
}
}
- (void)showVerificationSucceeded
{
[self.class showVerificationSucceeded:self
identityKey:self.identityKey
recipientId:self.recipientId
contactName:self.contactName
tag:self.logTag];
}
- (void)showVerificationFailedWithError:(NSError *)error
{
[self.class showVerificationFailedWithError:error
viewController:self
retryBlock:^{
[self.qrScanningController startCapture];
}
cancelBlock:^{
[self.navigationController popViewControllerAnimated:YES];
}
tag:self.logTag];
}
+ (void)showVerificationSucceeded:(UIViewController *)viewController
identityKey:(NSData *)identityKey
recipientId:(NSString *)recipientId
contactName:(NSString *)contactName
tag:(NSString *)tag
{
OWSAssertDebug(viewController);
OWSAssertDebug(identityKey.length > 0);
OWSAssertDebug(recipientId.length > 0);
OWSAssertDebug(contactName.length > 0);
OWSAssertDebug(tag.length > 0);
OWSLogInfo(@"%@ Successfully verified safety numbers.", tag);
NSString *successTitle = NSLocalizedString(@"SUCCESSFUL_VERIFICATION_TITLE", nil);
NSString *descriptionFormat = NSLocalizedString(
@"SUCCESSFUL_VERIFICATION_DESCRIPTION", @"Alert body after verifying privacy with {{other user's name}}");
NSString *successDescription = [NSString stringWithFormat:descriptionFormat, contactName];
UIAlertController *alert = [UIAlertController alertControllerWithTitle:successTitle
message:successDescription
preferredStyle:UIAlertControllerStyleAlert];
[alert
addAction:[UIAlertAction
actionWithTitle:NSLocalizedString(@"FINGERPRINT_SCAN_VERIFY_BUTTON",
@"Button that marks user as verified after a successful fingerprint scan.")
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) {
[OWSIdentityManager.sharedManager setVerificationState:OWSVerificationStateVerified
identityKey:identityKey
recipientId:recipientId
isUserInitiatedChange:YES];
[viewController dismissViewControllerAnimated:true completion:nil];
}]];
UIAlertAction *dismissAction =
[UIAlertAction actionWithTitle:CommonStrings.dismissButton
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) {
[viewController dismissViewControllerAnimated:true completion:nil];
}];
[alert addAction:dismissAction];
[viewController presentAlert:alert];
}
+ (void)showVerificationFailedWithError:(NSError *)error
viewController:(UIViewController *)viewController
retryBlock:(void (^_Nullable)(void))retryBlock
cancelBlock:(void (^_Nonnull)(void))cancelBlock
tag:(NSString *)tag
{
OWSAssertDebug(viewController);
OWSAssertDebug(cancelBlock);
OWSAssertDebug(tag.length > 0);
OWSLogInfo(@"%@ Failed to verify safety numbers.", tag);
NSString *_Nullable failureTitle;
if (error.code != OWSErrorCodeUserError) {
failureTitle = NSLocalizedString(@"FAILED_VERIFICATION_TITLE", @"alert title");
} // else no title. We don't want to show a big scary "VERIFICATION FAILED" when it's just user error.
UIAlertController *alert = [UIAlertController alertControllerWithTitle:failureTitle
message:error.localizedDescription
preferredStyle:UIAlertControllerStyleAlert];
if (retryBlock) {
[alert addAction:[UIAlertAction actionWithTitle:[CommonStrings retryButton]
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) {
retryBlock();
}]];
}
[alert addAction:[OWSAlerts cancelAction]];
[viewController presentAlert:alert];
OWSLogWarn(@"%@ Identity verification failed with error: %@", tag, error);
}
- (void)dismissViewControllerAnimated:(BOOL)animated completion:(nullable void (^)(void))completion
{
self.qrScanningController.view.hidden = YES;
[super dismissViewControllerAnimated:animated completion:completion];
}
#pragma mark - Orientation
- (UIInterfaceOrientationMask)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskPortrait;
}
@end
NS_ASSUME_NONNULL_END

View File

@ -35,7 +35,6 @@ class GifPickerViewController: OWSViewController, UISearchBarDelegate, UICollect
public weak var delegate: GifPickerViewControllerDelegate?
let thread: TSThread
let messageSender: MessageSender
let searchBar: SearchBar
let layout: GifPickerLayout
@ -60,9 +59,8 @@ class GifPickerViewController: OWSViewController, UISearchBarDelegate, UICollect
}
@objc
required init(thread: TSThread, messageSender: MessageSender) {
required init(thread: TSThread) {
self.thread = thread
self.messageSender = messageSender
self.searchBar = SearchBar()
self.layout = GifPickerLayout()

View File

@ -7,15 +7,9 @@ import SignalUtilitiesKit
@objc class GroupTableViewCell: UITableViewCell {
// MARK: - Dependencies
private var contactsManager: OWSContactsManager {
return Environment.shared.contactsManager
}
// MARK: -
private let avatarView = AvatarImageView()
// private let avatarView = AvatarImageView()
private let nameLabel = UILabel()
private let subtitleLabel = UILabel()
@ -30,14 +24,14 @@ import SignalUtilitiesKit
// Layout
avatarView.autoSetDimension(.width, toSize: CGFloat(kStandardAvatarSize))
avatarView.autoPinToSquareAspectRatio()
// avatarView.autoSetDimension(.width, toSize: CGFloat(kStandardAvatarSize))
// avatarView.autoPinToSquareAspectRatio()
let textRows = UIStackView(arrangedSubviews: [nameLabel, subtitleLabel])
textRows.axis = .vertical
textRows.alignment = .leading
let columns = UIStackView(arrangedSubviews: [avatarView, textRows])
let columns = UIStackView(arrangedSubviews: [ textRows ])
columns.axis = .horizontal
columns.alignment = .center
columns.spacing = kContactCellAvatarTextMargin
@ -62,11 +56,11 @@ import SignalUtilitiesKit
let groupMemberIds: [String] = thread.groupModel.groupMemberIds
let groupMemberNames = groupMemberIds.map { (recipientId: String) in
contactsManager.displayName(forPhoneIdentifier: recipientId)
SSKEnvironment.shared.profileManager.profileNameForRecipient(withID: recipientId, avoidingWriteTransaction: true)!
}.joined(separator: ", ")
self.subtitleLabel.text = groupMemberNames
self.avatarView.image = OWSAvatarBuilder.buildImage(thread: thread, diameter: kStandardAvatarSize)
// self.avatarView.image = OWSAvatarBuilder.buildImage(thread: thread, diameter: kStandardAvatarSize)
}
}

View File

@ -1,271 +0,0 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
import Foundation
import Social
import ContactsUI
import MessageUI
import SignalUtilitiesKit
@objc(OWSInviteFlow)
class InviteFlow: NSObject, MFMessageComposeViewControllerDelegate, MFMailComposeViewControllerDelegate, ContactsPickerDelegate {
enum Channel {
case message, mail, twitter
}
let installUrl = "https://signal.org/install/"
let homepageUrl = "https://signal.org"
@objc
let actionSheetController: UIAlertController
@objc
let presentingViewController: UIViewController
var channel: Channel?
@objc
required init(presentingViewController: UIViewController) {
self.presentingViewController = presentingViewController
actionSheetController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
super.init()
actionSheetController.addAction(dismissAction())
if let messageAction = messageAction() {
actionSheetController.addAction(messageAction)
}
if let mailAction = mailAction() {
actionSheetController.addAction(mailAction)
}
if let tweetAction = tweetAction() {
actionSheetController.addAction(tweetAction)
}
}
deinit {
Logger.verbose("[InviteFlow] deinit")
}
// MARK: Twitter
func canTweet() -> Bool {
return SLComposeViewController.isAvailable(forServiceType: SLServiceTypeTwitter)
}
func tweetAction() -> UIAlertAction? {
guard canTweet() else {
Logger.info("Twitter not supported.")
return nil
}
guard let twitterViewController = SLComposeViewController(forServiceType: SLServiceTypeTwitter) else {
Logger.error("unable to build twitter controller.")
return nil
}
let tweetString = NSLocalizedString("SETTINGS_INVITE_TWITTER_TEXT", comment: "content of tweet when inviting via twitter - please do not translate URL")
twitterViewController.setInitialText(tweetString)
let tweetUrl = URL(string: installUrl)
twitterViewController.add(tweetUrl)
twitterViewController.add(#imageLiteral(resourceName: "twitter_sharing_image"))
let tweetTitle = NSLocalizedString("SHARE_ACTION_TWEET", comment: "action sheet item")
return UIAlertAction(title: tweetTitle, style: .default) { _ in
Logger.debug("Chose tweet")
self.presentingViewController.present(twitterViewController, animated: true, completion: nil)
}
}
func dismissAction() -> UIAlertAction {
return UIAlertAction(title: CommonStrings.dismissButton, style: .cancel)
}
// MARK: ContactsPickerDelegate
func contactsPicker(_: ContactsPicker, didSelectMultipleContacts contacts: [Contact]) {
Logger.debug("didSelectContacts:\(contacts)")
guard let inviteChannel = channel else {
Logger.error("unexpected nil channel after returning from contact picker.")
self.presentingViewController.dismiss(animated: true)
return
}
switch inviteChannel {
case .message:
let phoneNumbers: [String] = contacts.map { $0.userTextPhoneNumbers.first }.filter { $0 != nil }.map { $0! }
dismissAndSendSMSTo(phoneNumbers: phoneNumbers)
case .mail:
let recipients: [String] = contacts.map { $0.emails.first }.filter { $0 != nil }.map { $0! }
sendMailTo(emails: recipients)
default:
Logger.error("unexpected channel after returning from contact picker: \(inviteChannel)")
}
}
func contactsPicker(_: ContactsPicker, shouldSelectContact contact: Contact) -> Bool {
guard let inviteChannel = channel else {
Logger.error("unexpected nil channel in contact picker.")
return true
}
switch inviteChannel {
case .message:
return contact.userTextPhoneNumbers.count > 0
case .mail:
return contact.emails.count > 0
default:
Logger.error("unexpected channel after returning from contact picker: \(inviteChannel)")
}
return true
}
func contactsPicker(_: ContactsPicker, contactFetchDidFail error: NSError) {
Logger.error("with error: \(error)")
self.presentingViewController.dismiss(animated: true) {
OWSAlerts.showErrorAlert(message: NSLocalizedString("ERROR_COULD_NOT_FETCH_CONTACTS", comment: "Error indicating that the phone's contacts could not be retrieved."))
}
}
func contactsPickerDidCancel(_: ContactsPicker) {
Logger.debug("")
self.presentingViewController.dismiss(animated: true)
}
func contactsPicker(_: ContactsPicker, didSelectContact contact: Contact) {
owsFailDebug("InviteFlow only supports multi-select")
self.presentingViewController.dismiss(animated: true)
}
// MARK: SMS
func messageAction() -> UIAlertAction? {
guard MFMessageComposeViewController.canSendText() else {
Logger.info("Device cannot send text")
return nil
}
let messageTitle = NSLocalizedString("SHARE_ACTION_MESSAGE", comment: "action sheet item to open native messages app")
return UIAlertAction(title: messageTitle, style: .default) { _ in
Logger.debug("Chose message.")
self.channel = .message
let picker = ContactsPicker(allowsMultipleSelection: true, subtitleCellType: .phoneNumber)
picker.contactsPickerDelegate = self
picker.title = NSLocalizedString("INVITE_FRIENDS_PICKER_TITLE", comment: "Navbar title")
let navigationController = OWSNavigationController(rootViewController: picker)
self.presentingViewController.present(navigationController, animated: true)
}
}
public func dismissAndSendSMSTo(phoneNumbers: [String]) {
self.presentingViewController.dismiss(animated: true) {
if phoneNumbers.count > 1 {
let warning = UIAlertController(title: nil,
message: NSLocalizedString("INVITE_WARNING_MULTIPLE_INVITES_BY_TEXT",
comment: "Alert warning that sending an invite to multiple users will create a group message whose recipients will be able to see each other."),
preferredStyle: .alert)
warning.addAction(UIAlertAction(title: NSLocalizedString("BUTTON_CONTINUE",
comment: "Label for 'continue' button."),
style: .default, handler: { _ in
self.sendSMSTo(phoneNumbers: phoneNumbers)
}))
warning.addAction(OWSAlerts.cancelAction)
self.presentingViewController.presentAlert(warning)
} else {
self.sendSMSTo(phoneNumbers: phoneNumbers)
}
}
}
@objc
public func sendSMSTo(phoneNumbers: [String]) {
let messageComposeViewController = MFMessageComposeViewController()
messageComposeViewController.messageComposeDelegate = self
messageComposeViewController.recipients = phoneNumbers
let inviteText = NSLocalizedString("SMS_INVITE_BODY", comment: "body sent to contacts when inviting to Install Signal")
messageComposeViewController.body = inviteText.appending(" \(self.installUrl)")
self.presentingViewController.present(messageComposeViewController, animated: true)
}
// MARK: MessageComposeViewControllerDelegate
func messageComposeViewController(_ controller: MFMessageComposeViewController, didFinishWith result: MessageComposeResult) {
self.presentingViewController.dismiss(animated: true) {
switch result {
case .failed:
let warning = UIAlertController(title: nil, message: NSLocalizedString("SEND_INVITE_FAILURE", comment: "Alert body after invite failed"), preferredStyle: .alert)
warning.addAction(UIAlertAction(title: CommonStrings.dismissButton, style: .default, handler: nil))
self.presentingViewController.present(warning, animated: true, completion: nil)
case .sent:
Logger.debug("user successfully invited their friends via SMS.")
case .cancelled:
Logger.debug("user cancelled message invite")
}
}
}
// MARK: Mail
func mailAction() -> UIAlertAction? {
guard MFMailComposeViewController.canSendMail() else {
Logger.info("Device cannot send mail")
return nil
}
let mailActionTitle = NSLocalizedString("SHARE_ACTION_MAIL", comment: "action sheet item to open native mail app")
return UIAlertAction(title: mailActionTitle, style: .default) { _ in
Logger.debug("Chose mail.")
self.channel = .mail
let picker = ContactsPicker(allowsMultipleSelection: true, subtitleCellType: .email)
picker.contactsPickerDelegate = self
picker.title = NSLocalizedString("INVITE_FRIENDS_PICKER_TITLE", comment: "Navbar title")
let navigationController = OWSNavigationController(rootViewController: picker)
self.presentingViewController.present(navigationController, animated: true)
}
}
func sendMailTo(emails recipientEmails: [String]) {
let mailComposeViewController = MFMailComposeViewController()
mailComposeViewController.mailComposeDelegate = self
mailComposeViewController.setBccRecipients(recipientEmails)
let subject = NSLocalizedString("EMAIL_INVITE_SUBJECT", comment: "subject of email sent to contacts when inviting to install Signal")
let bodyFormat = NSLocalizedString("EMAIL_INVITE_BODY", comment: "body of email sent to contacts when inviting to install Signal. Embeds {{link to install Signal}} and {{link to the Signal home page}}")
let body = String.init(format: bodyFormat, installUrl, homepageUrl)
mailComposeViewController.setSubject(subject)
mailComposeViewController.setMessageBody(body, isHTML: false)
self.presentingViewController.dismiss(animated: true) {
self.presentingViewController.present(mailComposeViewController, animated: true)
}
}
// MARK: MailComposeViewControllerDelegate
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
self.presentingViewController.dismiss(animated: true) {
switch result {
case .failed:
let warning = UIAlertController(title: nil, message: NSLocalizedString("SEND_INVITE_FAILURE", comment: "Alert body after invite failed"), preferredStyle: .alert)
warning.addAction(UIAlertAction(title: CommonStrings.dismissButton, style: .default, handler: nil))
self.presentingViewController.present(warning, animated: true, completion: nil)
case .sent:
Logger.debug("user successfully invited their friends via mail.")
case .saved:
Logger.debug("user saved mail invite.")
case .cancelled:
Logger.debug("user cancelled mail invite.")
}
}
}
}

View File

@ -176,7 +176,7 @@ extension LegacyNotificationPresenterAdaptee: NotificationPresenterAdaptee {
}
let checkForCancel = category == .incomingMessage
if checkForCancel && hasReceivedSyncMessageRecently {
if checkForCancel {
assert(userInfo[AppNotificationUserInfoKey.threadId] != nil)
notification.fireDate = Date(timeIntervalSinceNow: kNotificationDelayForRemoteRead)
notification.timeZone = NSTimeZone.local

View File

@ -587,7 +587,6 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou
let conversationStyle = ConversationStyle(thread: thread)
fetchedItem = ConversationInteractionViewItem(interaction: message,
isGroupThread: thread.isGroupThread(),
isRSSFeed: false,
transaction: transaction,
conversationStyle: conversationStyle)
}
@ -670,10 +669,6 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou
// MARK: Dynamic Header
private var contactsManager: OWSContactsManager {
return Environment.shared.contactsManager
}
private func senderName(message: TSMessage) -> String {
switch message {
case let incomingMessage as TSIncomingMessage:

View File

@ -93,9 +93,8 @@ class ConversationViewItemActions: NSObject {
var actions: [MenuAction] = []
let isGroup = conversationViewItem.isGroupThread;
let isRSSFeed = conversationViewItem.isRSSFeed;
if shouldAllowReply && !isRSSFeed {
if shouldAllowReply {
let replyAction = MessageActionBuilder.reply(conversationViewItem: conversationViewItem, delegate: delegate)
actions.append(replyAction)
}
@ -105,7 +104,7 @@ class ConversationViewItemActions: NSObject {
actions.append(copyTextAction)
}
if isGroup && !isRSSFeed && conversationViewItem.interaction is TSIncomingMessage {
if isGroup && conversationViewItem.interaction is TSIncomingMessage {
let copyPublicKeyAction = MessageActionBuilder.copyPublicKey(conversationViewItem: conversationViewItem, delegate: delegate)
actions.append(copyPublicKeyAction)
}
@ -132,9 +131,8 @@ class ConversationViewItemActions: NSObject {
var actions: [MenuAction] = []
let isGroup = conversationViewItem.isGroupThread;
let isRSSFeed = conversationViewItem.isRSSFeed;
if shouldAllowReply && !isRSSFeed {
if shouldAllowReply {
let replyAction = MessageActionBuilder.reply(conversationViewItem: conversationViewItem, delegate: delegate)
actions.append(replyAction)
}
@ -150,7 +148,7 @@ class ConversationViewItemActions: NSObject {
}
}
if isGroup && !isRSSFeed && conversationViewItem.interaction is TSIncomingMessage {
if isGroup && conversationViewItem.interaction is TSIncomingMessage {
let copyPublicKeyAction = MessageActionBuilder.copyPublicKey(conversationViewItem: conversationViewItem, delegate: delegate)
actions.append(copyPublicKeyAction)
}
@ -182,9 +180,8 @@ class ConversationViewItemActions: NSObject {
}
let isGroup = conversationViewItem.isGroupThread;
let isRSSFeed = conversationViewItem.isRSSFeed;
if isGroup && !isRSSFeed && conversationViewItem.interaction is TSIncomingMessage {
if isGroup && conversationViewItem.interaction is TSIncomingMessage {
let copyPublicKeyAction = MessageActionBuilder.copyPublicKey(conversationViewItem: conversationViewItem, delegate: delegate)
actions.append(copyPublicKeyAction)
}

View File

@ -18,7 +18,7 @@ protocol MessageDetailViewDelegate: AnyObject {
}
@objc
class MessageDetailViewController: OWSViewController, MediaGalleryDataSourceDelegate, OWSMessageBubbleViewDelegate, ContactShareViewHelperDelegate {
class MessageDetailViewController: OWSViewController, MediaGalleryDataSourceDelegate, OWSMessageBubbleViewDelegate {
@objc
weak var delegate: MessageDetailViewDelegate?
@ -52,18 +52,12 @@ class MessageDetailViewController: OWSViewController, MediaGalleryDataSourceDele
var conversationStyle: ConversationStyle
private var contactShareViewHelper: ContactShareViewHelper!
// MARK: Dependencies
var preferences: OWSPreferences {
return Environment.shared.preferences
}
var contactsManager: OWSContactsManager {
return Environment.shared.contactsManager
}
// MARK: Initializers
@available(*, unavailable, message:"use other constructor instead.")
@ -86,8 +80,6 @@ class MessageDetailViewController: OWSViewController, MediaGalleryDataSourceDele
override func viewDidLoad() {
super.viewDidLoad()
self.contactShareViewHelper = ContactShareViewHelper(contactsManager: contactsManager)
contactShareViewHelper.delegate = self
do {
try updateMessageToLatest()
@ -614,27 +606,6 @@ class MessageDetailViewController: OWSViewController, MediaGalleryDataSourceDele
mediaGallery.presentDetailView(fromViewController: self, mediaAttachment: attachmentStream, replacingView: imageView)
}
func didTapContactShare(_ viewItem: ConversationViewItem) {
guard let contactShare = viewItem.contactShare else {
owsFailDebug("missing contact.")
return
}
let contactViewController = ContactViewController(contactShare: contactShare)
self.navigationController?.pushViewController(contactViewController, animated: true)
}
func didTapSendMessage(toContactShare contactShare: ContactShareViewModel) {
contactShareViewHelper.sendMessage(contactShare: contactShare, fromViewController: self)
}
func didTapSendInvite(toContactShare contactShare: ContactShareViewModel) {
contactShareViewHelper.showInviteContact(contactShare: contactShare, fromViewController: self)
}
func didTapShowAddToContactUI(forContactShare contactShare: ContactShareViewModel) {
contactShareViewHelper.showAddToContacts(contactShare: contactShare, fromViewController: self)
}
var audioAttachmentPlayer: OWSAudioPlayer?
func didTapAudioViewItem(_ viewItem: ConversationViewItem, attachmentStream: TSAttachmentStream) {

View File

@ -1,175 +0,0 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
import Foundation
import PromiseKit
import SignalUtilitiesKit
@objc(OWSMessageFetcherJob)
public class MessageFetcherJob: NSObject {
private var timer: Timer?
@objc
public override init() {
super.init()
SwiftSingletons.register(self)
}
// MARK: Singletons
private var networkManager: TSNetworkManager {
return SSKEnvironment.shared.networkManager
}
private var messageReceiver: OWSMessageReceiver {
return SSKEnvironment.shared.messageReceiver
}
private var signalService: OWSSignalService {
return OWSSignalService.sharedInstance()
}
// MARK:
@discardableResult
public func run() -> Promise<Void> {
let promise = fetchUndeliveredMessages().then { promises -> Promise<Void> in
let promises = promises.map { promise -> Promise<Void> in
return promise.then { envelopes -> Promise<Void> in
for envelope in envelopes {
Logger.info("Envelope received.")
do {
let envelopeData = try envelope.serializedData()
self.messageReceiver.handleReceivedEnvelopeData(envelopeData)
} catch {
owsFailDebug("Failed to serialize envelope.")
}
self.acknowledgeDelivery(envelope: envelope)
}
return Promise.value(())
}
}
return when(resolved: promises).asVoid()
}
promise.retainUntilComplete()
return promise
}
@objc
@discardableResult
public func run() -> AnyPromise {
return AnyPromise(run() as Promise)
}
// use in DEBUG or wherever you can't receive push notifications to poll for messages.
// Do not use in production.
public func startRunLoop(timeInterval: Double) {
Logger.error("Starting message fetch polling. This should not be used in production.")
timer = WeakTimer.scheduledTimer(timeInterval: timeInterval, target: self, userInfo: nil, repeats: true) {[weak self] _ in
let _: Promise<Void>? = self?.run()
return
}
}
public func stopRunLoop() {
timer?.invalidate()
timer = nil
}
private func parseMessagesResponse(responseObject: Any?) -> (envelopes: [SSKProtoEnvelope], more: Bool)? {
guard let responseObject = responseObject else {
Logger.error("response object was surpringly nil")
return nil
}
guard let responseDict = responseObject as? [String: Any] else {
Logger.error("response object was not a dictionary")
return nil
}
guard let messageDicts = responseDict["messages"] as? [[String: Any]] else {
Logger.error("messages object was not a list of dictionaries")
return nil
}
let moreMessages = { () -> Bool in
if let responseMore = responseDict["more"] as? Bool {
return responseMore
} else {
Logger.warn("more object was not a bool. Assuming no more")
return false
}
}()
let envelopes: [SSKProtoEnvelope] = messageDicts.compactMap { buildEnvelope(messageDict: $0) }
return (
envelopes: envelopes,
more: moreMessages
)
}
private func buildEnvelope(messageDict: [String: Any]) -> SSKProtoEnvelope? {
do {
let params = ParamParser(dictionary: messageDict)
let typeInt: Int32 = try params.required(key: "type")
guard let type: SSKProtoEnvelope.SSKProtoEnvelopeType = SSKProtoEnvelope.SSKProtoEnvelopeType(rawValue: typeInt) else {
Logger.error("`type` was invalid: \(typeInt)")
throw ParamParser.ParseError.invalidFormat("type")
}
guard let timestamp: UInt64 = try params.required(key: "timestamp") else {
Logger.error("`timestamp` was invalid: \(typeInt)")
throw ParamParser.ParseError.invalidFormat("timestamp")
}
let builder = SSKProtoEnvelope.builder(type: type, timestamp: timestamp)
if let source: String = try params.optional(key: "source") {
builder.setSource(source)
}
if let sourceDevice: UInt32 = try params.optional(key: "sourceDevice") {
builder.setSourceDevice(sourceDevice)
}
if let legacyMessage = try params.optionalBase64EncodedData(key: "message") {
builder.setLegacyMessage(legacyMessage)
}
if let content = try params.optionalBase64EncodedData(key: "content") {
builder.setContent(content)
}
if let serverTimestamp: UInt64 = try params.optional(key: "serverTimestamp") {
builder.setServerTimestamp(serverTimestamp)
}
if let serverGuid: String = try params.optional(key: "guid") {
builder.setServerGuid(serverGuid)
}
return try builder.build()
} catch {
owsFailDebug("error building envelope: \(error)")
return nil
}
}
private func fetchUndeliveredMessages() -> Promise<Set<Promise<[SSKProtoEnvelope]>>> {
let userPublickKey = getUserHexEncodedPublicKey() // Can be missing in rare cases
guard !userPublickKey.isEmpty else { return Promise.value(Set()) }
return SnodeAPI.getMessages(for: userPublickKey).map2 { promises -> Set<Promise<[SSKProtoEnvelope]>> in
return Set(promises.map { promise -> Promise<[SSKProtoEnvelope]> in
return promise.map2 { rawMessages -> [SSKProtoEnvelope] in
return rawMessages.compactMap { SSKProtoEnvelope.from($0) }
}
})
}
}
private func acknowledgeDelivery(envelope: SSKProtoEnvelope) {
// Do nothing
}
}

View File

@ -110,10 +110,6 @@ public class MessageRecipientStatusUtils: NSObject {
// Use the "long" version of this message here.
return (.failed, NSLocalizedString("MESSAGE_STATUS_FAILED", comment: "status message for failed messages"))
case .sending:
if outgoingMessage.isCalculatingPoW {
return (.calculatingPoW, NSLocalizedString("Calculating proof of work", comment: ""))
}
if outgoingMessage.hasAttachments() {
return (.uploading, NSLocalizedString("MESSAGE_STATUS_UPLOADING",
comment: "status message while attachment is uploading"))

View File

@ -1,15 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "OWSTableViewController.h"
NS_ASSUME_NONNULL_BEGIN
@interface OWSAddToContactViewController : OWSTableViewController
- (void)configureWithRecipientId:(NSString *)recipientId;
@end
NS_ASSUME_NONNULL_END

View File

@ -1,212 +0,0 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
#import "OWSAddToContactViewController.h"
#import <SignalUtilitiesKit/ContactsViewHelper.h>
#import <SignalUtilitiesKit/Environment.h>
#import <SignalUtilitiesKit/OWSContactsManager.h>
#import <SignalUtilitiesKit/UIUtil.h>
@import ContactsUI;
NS_ASSUME_NONNULL_BEGIN
@interface OWSAddToContactViewController () <ContactEditingDelegate, ContactsViewHelperDelegate>
@property (nonatomic) NSString *recipientId;
@property (nonatomic, readonly) OWSContactsManager *contactsManager;
@property (nonatomic, readonly) ContactsViewHelper *contactsViewHelper;
@end
#pragma mark -
@implementation OWSAddToContactViewController
- (instancetype)init
{
self = [super init];
if (!self) {
return self;
}
[self commonInit];
return self;
}
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (!self) {
return self;
}
[self commonInit];
return self;
}
- (instancetype)initWithNibName:(nullable NSString *)nibNameOrNil bundle:(nullable NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (!self) {
return self;
}
[self commonInit];
return self;
}
- (void)commonInit
{
_contactsManager = Environment.shared.contactsManager;
_contactsViewHelper = [[ContactsViewHelper alloc] initWithDelegate:self];
}
- (void)configureWithRecipientId:(NSString *)recipientId
{
OWSAssertDebug(recipientId.length > 0);
_recipientId = recipientId;
}
#pragma mark - ContactEditingDelegate
- (void)didFinishEditingContact
{
OWSLogDebug(@"");
[self dismissViewControllerAnimated:NO
completion:^{
[self.navigationController popViewControllerAnimated:YES];
}];
}
#pragma mark - CNContactViewControllerDelegate
- (void)contactViewController:(CNContactViewController *)viewController
didCompleteWithContact:(nullable CNContact *)contact
{
if (contact) {
// Saving normally returns you to the "Show Contact" view
// which we're not interested in, so we skip it here. There is
// an unfortunate blip of the "Show Contact" view on slower devices.
OWSLogDebug(@"completed editing contact.");
[self dismissViewControllerAnimated:NO
completion:^{
[self.navigationController popViewControllerAnimated:YES];
}];
} else {
OWSLogDebug(@"canceled editing contact.");
[self dismissViewControllerAnimated:YES
completion:^{
[self.navigationController popViewControllerAnimated:YES];
}];
}
}
#pragma mark - ContactsViewHelperDelegate
- (void)contactsViewHelperDidUpdateContacts
{
[self updateTableContents];
}
#pragma mark - View Lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
self.title = NSLocalizedString(@"CONVERSATION_SETTINGS_ADD_TO_EXISTING_CONTACT",
@"Label for 'new contact' button in conversation settings view.");
[self updateTableContents];
}
- (nullable NSString *)displayNameForContact:(Contact *)contact
{
OWSAssertDebug(contact);
if (contact.fullName.length > 0) {
return contact.fullName;
}
for (NSString *email in contact.emails) {
if (email.length > 0) {
return email;
}
}
for (NSString *phoneNumber in contact.userTextPhoneNumbers) {
if (phoneNumber.length > 0) {
return phoneNumber;
}
}
return nil;
}
- (void)updateTableContents
{
OWSTableContents *contents = [OWSTableContents new];
contents.title = NSLocalizedString(@"CONVERSATION_SETTINGS", @"title for conversation settings screen");
__weak OWSAddToContactViewController *weakSelf = self;
OWSTableSection *section = [OWSTableSection new];
section.headerTitle = NSLocalizedString(
@"EDIT_GROUP_CONTACTS_SECTION_TITLE", @"a title for the contacts section of the 'new/update group' view.");
for (Contact *contact in self.contactsViewHelper.contactsManager.allContacts) {
NSString *_Nullable displayName = [self displayNameForContact:contact];
if (displayName.length < 1) {
continue;
}
// TODO: Confirm with nancy if this will work.
NSString *cellName = [NSString stringWithFormat:@"contact.%@", NSUUID.UUID.UUIDString];
[section addItem:[OWSTableItem disclosureItemWithText:displayName
accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME(self, cellName)
actionBlock:^{
[weakSelf presentContactViewControllerForContact:contact];
}]];
}
[contents addSection:section];
self.contents = contents;
[self.tableView reloadData];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
}
#pragma mark - Actions
- (void)presentContactViewControllerForContact:(Contact *)contact
{
OWSAssertDebug(contact);
OWSAssertDebug(self.recipientId);
if (!self.contactsManager.supportsContactEditing) {
OWSFailDebug(@"Contact editing not supported");
return;
}
CNContact *_Nullable cnContact = [self.contactsManager cnContactWithId:contact.cnContactId];
if (!cnContact) {
OWSFailDebug(@"Could not load system contact.");
return;
}
[self.contactsViewHelper presentContactViewControllerForRecipientId:self.recipientId
fromViewController:self
editImmediately:YES
addToExistingCnContact:cnContact];
}
@end
NS_ASSUME_NONNULL_END

View File

@ -1,22 +0,0 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
import Foundation
import SignalUtilitiesKit
func FormatAnalyticsLocation(file: String, function: String) -> NSString {
return "\((file as NSString).lastPathComponent):\(function)" as NSString
}
func OWSProdError(_ eventName: String, file: String, function: String, line: Int32) {
let location = FormatAnalyticsLocation(file: file, function: function)
OWSAnalytics
.logEvent(eventName, severity: .error, parameters: nil, location: location.utf8String!, line:line)
}
func OWSProdInfo(_ eventName: String, file: String, function: String, line: Int32) {
let location = FormatAnalyticsLocation(file: file, function: function)
OWSAnalytics
.logEvent(eventName, severity: .info, parameters: nil, location: location.utf8String!, line:line)
}

View File

@ -411,25 +411,7 @@ NS_ASSUME_NONNULL_BEGIN
self.backupIO = [[OWSBackupIO alloc] initWithJobTempDirPath:self.jobTempDirPath];
// We need to verify that we have a valid account.
// Otherwise, if we re-register on another device, we
// continue to backup on our old device, overwriting
// backups from the new device.
//
// We use an arbitrary request that requires authentication
// to verify our account state.
return [AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
TSRequest *currentSignedPreKey = [OWSRequestFactory currentSignedPreKeyRequest];
[[TSNetworkManager sharedManager] makeRequest:currentSignedPreKey
success:^(NSURLSessionDataTask *task, NSDictionary *responseObject) {
resolve(@(1));
}
failure:^(NSURLSessionDataTask *task, NSError *error) {
// TODO: We may want to surface this in the UI.
OWSLogError(@"could not verify account status: %@.", error);
resolve(error);
}];
}];
return [AnyPromise promiseWithValue:@(1)];
}
- (AnyPromise *)fetchAllRecords

View File

@ -5,7 +5,7 @@
#import "OWSBackupSettingsViewController.h"
#import "OWSBackup.h"
#import "Session-Swift.h"
#import "ThreadUtil.h"
#import <PromiseKit/AnyPromise.h>
#import <SignalUtilitiesKit/AttachmentSharing.h>
#import <SignalUtilitiesKit/Environment.h>

View File

@ -4,22 +4,19 @@
#import "OWSConversationSettingsViewController.h"
#import "BlockListUIUtils.h"
#import "ContactsViewHelper.h"
#import "FingerprintViewController.h"
#import "OWSAddToContactViewController.h"
#import "OWSBlockingManager.h"
#import "OWSSoundSettingsViewController.h"
#import "PhoneNumber.h"
#import "ShowGroupMembersViewController.h"
#import "Session-Swift.h"
#import "UIFont+OWS.h"
#import "UIView+OWS.h"
#import "UpdateGroupViewController.h"
#import <Curve25519Kit/Curve25519.h>
#import <SignalCoreKit/NSDate+OWS.h>
#import <SignalUtilitiesKit/Environment.h>
#import <SignalUtilitiesKit/OWSAvatarBuilder.h>
#import <SignalUtilitiesKit/OWSContactsManager.h>
#import <SignalUtilitiesKit/OWSProfileManager.h>
#import <SignalUtilitiesKit/OWSSounds.h>
#import <SignalUtilitiesKit/OWSUserProfile.h>
@ -27,7 +24,7 @@
#import <SignalUtilitiesKit/UIUtil.h>
#import <SignalUtilitiesKit/OWSDisappearingConfigurationUpdateInfoMessage.h>
#import <SignalUtilitiesKit/OWSDisappearingMessagesConfiguration.h>
#import <SignalUtilitiesKit/OWSMessageSender.h>
#import <SignalUtilitiesKit/OWSPrimaryStorage.h>
#import <SignalUtilitiesKit/TSGroupThread.h>
#import <SignalUtilitiesKit/TSOutgoingMessage.h>
@ -42,8 +39,7 @@ NS_ASSUME_NONNULL_BEGIN
const CGFloat kIconViewLength = 24;
@interface OWSConversationSettingsViewController () <ContactEditingDelegate,
ContactsViewHelperDelegate,
@interface OWSConversationSettingsViewController () <
#ifdef SHOW_COLOR_PICKER
ColorPickerDelegate,
#endif
@ -107,7 +103,6 @@ const CGFloat kIconViewLength = 24;
- (void)commonInit
{
_contactsViewHelper = [[ContactsViewHelper alloc] initWithDelegate:self];
[self observeNotifications];
}
@ -119,11 +114,6 @@ const CGFloat kIconViewLength = 24;
#pragma mark - Dependencies
- (SSKMessageSenderJobQueue *)messageSenderJobQueue
{
return SSKEnvironment.shared.messageSenderJobQueue;
}
- (TSAccountManager *)tsAccountManager
{
OWSAssertDebug(SSKEnvironment.shared.tsAccountManager);
@ -131,16 +121,6 @@ const CGFloat kIconViewLength = 24;
return SSKEnvironment.shared.tsAccountManager;
}
- (OWSContactsManager *)contactsManager
{
return Environment.shared.contactsManager;
}
- (OWSMessageSender *)messageSender
{
return SSKEnvironment.shared.messageSender;
}
- (OWSBlockingManager *)blockingManager
{
return [OWSBlockingManager sharedManager];
@ -174,7 +154,7 @@ const CGFloat kIconViewLength = 24;
{
NSString *threadName = self.thread.name;
if (self.thread.contactIdentifier) {
return [self.contactsManager profileNameForRecipientId:self.thread.contactIdentifier];
return [SSKEnvironment.shared.profileManager profileNameForRecipientWithID:self.thread.contactIdentifier avoidingWriteTransaction:YES];
} else if (threadName.length == 0 && [self isGroupThread]) {
threadName = [MessageStrings newGroupDefaultTitle];
}
@ -186,20 +166,20 @@ const CGFloat kIconViewLength = 24;
return [self.thread isKindOfClass:[TSGroupThread class]];
}
- (BOOL)isOpenGroupChat
- (BOOL)isOpenGroup
{
if ([self isGroupThread]) {
TSGroupThread *thread = (TSGroupThread *)self.thread;
return thread.isPublicChat;
return thread.isOpenGroup;
}
return false;
}
-(BOOL)isPrivateGroupChat
-(BOOL)isClosedGroup
{
if (self.isGroupThread) {
TSGroupThread *thread = (TSGroupThread *)self.thread;
return !thread.isRSSFeed && !thread.isPublicChat;
return thread.groupModel.groupType == closedGroup;
}
return false;
}
@ -209,31 +189,6 @@ const CGFloat kIconViewLength = 24;
OWSAssertDebug(thread);
self.thread = thread;
self.uiDatabaseConnection = uiDatabaseConnection;
[self updateEditButton];
}
- (void)updateEditButton
{
OWSAssertDebug(self.thread);
if ([self.thread isKindOfClass:[TSContactThread class]] && self.contactsManager.supportsContactEditing
&& self.hasExistingContact) {
self.navigationItem.rightBarButtonItem =
[[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"EDIT_TXT", nil)
style:UIBarButtonItemStylePlain
target:self
action:@selector(didTapEditButton)
accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME(self, @"edit")];
}
}
- (BOOL)hasExistingContact
{
OWSAssertDebug([self.thread isKindOfClass:[TSContactThread class]]);
TSContactThread *contactThread = (TSContactThread *)self.thread;
NSString *recipientId = contactThread.contactIdentifier;
return [self.contactsManager hasSignalAccountForRecipientId:recipientId];
}
#pragma mark - ContactEditingDelegate
@ -265,16 +220,6 @@ const CGFloat kIconViewLength = 24;
}
}
#pragma mark - ContactsViewHelperDelegate
- (void)contactsViewHelperDidUpdateContacts
{
// Loki: Original code
// ========
// [self updateTableContents];
// ========
}
#pragma mark - View Lifecycle
- (void)viewDidLoad
@ -322,8 +267,6 @@ const CGFloat kIconViewLength = 24;
self.showVerificationOnAppear = NO;
if (self.isGroupThread) {
[self showGroupMembersView];
} else {
[self showVerificationView];
}
}
}
@ -350,49 +293,6 @@ const CGFloat kIconViewLength = 24;
mainSection.customHeaderHeight = isSmallScreen ? @(201.f) : @(208.f);
}
/**
* Loki: Original code
* ========
if ([self.thread isKindOfClass:[TSContactThread class]] && self.contactsManager.supportsContactEditing
&& !self.hasExistingContact) {
[mainSection
addItem:[OWSTableItem
itemWithCustomCellBlock:^{
return [weakSelf
disclosureCellWithName:
NSLocalizedString(@"CONVERSATION_SETTINGS_NEW_CONTACT",
@"Label for 'new contact' button in conversation settings view.")
iconName:@"table_ic_new_contact"
accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME(
OWSConversationSettingsViewController, @"new_contact")];
}
actionBlock:^{
[weakSelf presentContactViewController];
}]];
[mainSection addItem:[OWSTableItem
itemWithCustomCellBlock:^{
return [weakSelf
disclosureCellWithName:
NSLocalizedString(@"CONVERSATION_SETTINGS_ADD_TO_EXISTING_CONTACT",
@"Label for 'new contact' button in conversation settings view.")
iconName:@"table_ic_add_to_existing_contact"
accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME(
OWSConversationSettingsViewController,
@"add_to_existing_contact")];
}
actionBlock:^{
OWSConversationSettingsViewController *strongSelf = weakSelf;
OWSCAssertDebug(strongSelf);
TSContactThread *contactThread = (TSContactThread *)strongSelf.thread;
NSString *recipientId = contactThread.contactIdentifier;
[strongSelf presentAddToContactViewControllerWithRecipientId:recipientId];
}]];
}
if (SSKFeatureFlags.conversationSearch) {
* ========
*/
if ([self.thread isKindOfClass:TSContactThread.class]) {
[mainSection addItem:[OWSTableItem
itemWithCustomCellBlock:^{
@ -433,79 +333,8 @@ const CGFloat kIconViewLength = 24;
actionBlock:^{
[weakSelf tappedConversationSearch];
}]];
/*
}
if (!isNoteToSelf && !self.isGroupThread && self.thread.hasSafetyNumbers) {
[mainSection
addItem:[OWSTableItem
itemWithCustomCellBlock:^{
return [weakSelf
disclosureCellWithName:NSLocalizedString(@"VERIFY_PRIVACY",
@"Label for button or row which allows users to verify the "
@"safety number of another user.")
iconName:@"table_ic_not_verified"
accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME(
OWSConversationSettingsViewController, @"safety_numbers")];
}
actionBlock:^{
[weakSelf showVerificationView];
}]];
}
if (isNoteToSelf) {
// Skip the profile whitelist.
} else if ([self.profileManager isThreadInProfileWhitelist:self.thread]) {
[mainSection
addItem:[OWSTableItem
itemWithCustomCellBlock:^{
OWSConversationSettingsViewController *strongSelf = weakSelf;
OWSCAssertDebug(strongSelf);
return [strongSelf
labelCellWithName:
(strongSelf.isGroupThread
? NSLocalizedString(
@"CONVERSATION_SETTINGS_VIEW_PROFILE_IS_SHARED_WITH_GROUP",
@"Indicates that user's profile has been shared with a group.")
: NSLocalizedString(
@"CONVERSATION_SETTINGS_VIEW_PROFILE_IS_SHARED_WITH_USER",
@"Indicates that user's profile has been shared with a user."))
iconName:@"table_ic_share_profile"
accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME(
OWSConversationSettingsViewController,
@"profile_is_shared")];
}
actionBlock:nil]];
} else {
[mainSection
addItem:[OWSTableItem
itemWithCustomCellBlock:^{
OWSConversationSettingsViewController *strongSelf = weakSelf;
OWSCAssertDebug(strongSelf);
UITableViewCell *cell = [strongSelf
disclosureCellWithName:
(strongSelf.isGroupThread
? NSLocalizedString(@"CONVERSATION_SETTINGS_VIEW_SHARE_PROFILE_WITH_GROUP",
@"Action that shares user profile with a group.")
: NSLocalizedString(@"CONVERSATION_SETTINGS_VIEW_SHARE_PROFILE_WITH_USER",
@"Action that shares user profile with a user."))
iconName:@"table_ic_share_profile"
accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME(
OWSConversationSettingsViewController, @"share_profile")];
cell.userInteractionEnabled = !strongSelf.hasLeftGroup;
return cell;
}
actionBlock:^{
[weakSelf showShareProfileAlert];
}]];
}
* =======
*/
if (![self isOpenGroupChat]) {
if (![self isOpenGroup]) {
[mainSection addItem:[OWSTableItem
itemWithCustomCellBlock:^{
UITableViewCell *cell = [OWSTableItem newCell];
@ -652,12 +481,10 @@ const CGFloat kIconViewLength = 24;
__block BOOL isUserMember = NO;
if (self.isGroupThread) {
NSString *userPublicKey = OWSIdentityManager.sharedManager.identityKeyPair.hexEncodedPublicKey;
[LKStorage readWithBlock:^(YapDatabaseReadTransaction *transaction) {
isUserMember = [(TSGroupThread *)self.thread isUserMemberInGroup:userPublicKey transaction:transaction];
}];
isUserMember = [(TSGroupThread *)self.thread isUserMemberInGroup:userPublicKey];
}
if (self.isGroupThread && self.isPrivateGroupChat && isUserMember) {
if (self.isGroupThread && self.isClosedGroup && isUserMember) {
if (((TSGroupThread *)self.thread).usesSharedSenderKeys) {
[mainSection addItem:[OWSTableItem
itemWithCustomCellBlock:^{
@ -675,20 +502,6 @@ const CGFloat kIconViewLength = 24;
}]
];
}
// [mainSection addItem:[OWSTableItem
// itemWithCustomCellBlock:^{
// UITableViewCell *cell =
// [weakSelf disclosureCellWithName:NSLocalizedString(@"LIST_GROUP_MEMBERS_ACTION",
// @"table cell label in conversation settings")
// iconName:@"table_ic_group_members"
// accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME(
// OWSConversationSettingsViewController, @"group_members")];
// return cell;
// }
// actionBlock:^{
// [weakSelf showGroupMembersView];
// }]
// ];
[mainSection addItem:[OWSTableItem
itemWithCustomCellBlock:^{
UITableViewCell *cell =
@ -711,11 +524,6 @@ const CGFloat kIconViewLength = 24;
// Mute thread section.
if (!isNoteToSelf) {
// OWSTableSection *notificationsSection = [OWSTableSection new];
// We need a section header to separate the notifications UI from the group settings UI.
// notificationsSection.headerTitle = NSLocalizedString(
// @"SETTINGS_SECTION_NOTIFICATIONS", @"Label for the notifications section of conversation settings view.");
[mainSection
addItem:[OWSTableItem
itemWithCustomCellBlock:^{
@ -825,9 +633,6 @@ const CGFloat kIconViewLength = 24;
actionBlock:^{
[weakSelf showMuteUnmuteActionSheet];
}]];
// mainSection.footerTitle = NSLocalizedString(
// @"MUTE_BEHAVIOR_EXPLANATION", @"An explanation of the consequences of muting a thread.");
// [contents addSection:notificationsSection];
}
// Block Conversation section.
@ -885,19 +690,6 @@ const CGFloat kIconViewLength = 24;
return 12.f;
}
- (UITableViewCell *)cellWithName:(NSString *)name
iconName:(NSString *)iconName
disclosureIconColor:(UIColor *)disclosureIconColor
{
UITableViewCell *cell = [self cellWithName:name iconName:iconName];
OWSColorPickerAccessoryView *accessoryView =
[[OWSColorPickerAccessoryView alloc] initWithColor:disclosureIconColor];
[accessoryView sizeToFit];
cell.accessoryView = accessoryView;
return cell;
}
- (UITableViewCell *)cellWithName:(NSString *)name iconName:(NSString *)iconName
{
OWSAssertDebug(iconName.length > 0);
@ -1031,24 +823,6 @@ static CGRect oldframe;
return stackView;
}
- (void)conversationNameTouched:(UIGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateRecognized) {
if (self.isGroupThread) {
CGPoint location = [sender locationInView:self.avatarView];
if (CGRectContainsPoint(self.avatarView.bounds, location)) {
[self showUpdateGroupView:UpdateGroupMode_EditGroupAvatar];
} else {
[self showUpdateGroupView:UpdateGroupMode_EditGroupName];
}
} else {
if (self.contactsManager.supportsContactEditing) {
[self presentContactViewController];
}
}
}
}
- (UIImageView *)viewForIconWithName:(NSString *)iconName
{
UIImage *icon = [UIImage imageNamed:iconName];
@ -1102,11 +876,15 @@ static CGRect oldframe;
createdInExistingGroup:NO];
[infoMessage saveWithTransaction:transaction];
// TODO TODO TODO
/*
OWSDisappearingMessagesConfigurationMessage *message = [[OWSDisappearingMessagesConfigurationMessage alloc]
initWithConfiguration:self.disappearingMessagesConfiguration
thread:self.thread];
[self.messageSenderJobQueue addMessage:message transaction:transaction];
*/
}];
}
}
@ -1122,14 +900,6 @@ static CGRect oldframe;
}];
}
- (void)showVerificationView
{
NSString *recipientId = self.thread.contactIdentifier;
OWSAssertDebug(recipientId.length > 0);
[FingerprintViewController presentFromViewController:self recipientId:recipientId];
}
- (void)showGroupMembersView
{
TSGroupThread *thread = (TSGroupThread *)self.thread;
@ -1137,57 +907,6 @@ static CGRect oldframe;
[self.navigationController pushViewController:groupMembersVC animated:YES];
}
- (void)showUpdateGroupView:(UpdateGroupMode)mode
{
OWSAssertDebug(self.conversationSettingsViewDelegate);
UpdateGroupViewController *updateGroupViewController = [UpdateGroupViewController new];
updateGroupViewController.conversationSettingsViewDelegate = self.conversationSettingsViewDelegate;
updateGroupViewController.thread = (TSGroupThread *)self.thread;
updateGroupViewController.mode = mode;
[self.navigationController pushViewController:updateGroupViewController animated:YES];
}
- (void)presentContactViewController
{
if (!self.contactsManager.supportsContactEditing) {
OWSFailDebug(@"Contact editing not supported");
return;
}
if (![self.thread isKindOfClass:[TSContactThread class]]) {
OWSFailDebug(@"unexpected thread: %@", [self.thread class]);
return;
}
TSContactThread *contactThread = (TSContactThread *)self.thread;
[self.contactsViewHelper presentContactViewControllerForRecipientId:contactThread.contactIdentifier
fromViewController:self
editImmediately:YES];
}
- (void)presentAddToContactViewControllerWithRecipientId:(NSString *)recipientId
{
if (!self.contactsManager.supportsContactEditing) {
// Should not expose UI that lets the user get here.
OWSFailDebug(@"Contact editing not supported.");
return;
}
if (!self.contactsManager.isSystemContactsAuthorized) {
[self.contactsViewHelper presentMissingContactAccessAlertControllerFromViewController:self];
return;
}
OWSAddToContactViewController *viewController = [OWSAddToContactViewController new];
[viewController configureWithRecipientId:recipientId];
[self.navigationController pushViewController:viewController animated:YES];
}
- (void)didTapEditButton
{
[self presentContactViewController];
}
- (void)editGroup
{
LKEditClosedGroupVC *editClosedGroupVC = [[LKEditClosedGroupVC alloc] initWithThreadID:self.thread.uniqueId];
@ -1218,7 +937,7 @@ static CGRect oldframe;
{
if (self.isGroupThread) {
TSGroupThread *groupThread = (TSGroupThread *)self.thread;
return !groupThread.isLocalUserInGroup;
return !groupThread.isCurrentUserInGroup;
}
return NO;
@ -1233,14 +952,6 @@ static CGRect oldframe;
[LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) {
[[LKClosedGroupsProtocol leaveGroupWithPublicKey:groupPublicKey transaction:transaction] retainUntilComplete];
}];
} else {
TSOutgoingMessage *message =
[TSOutgoingMessage outgoingMessageInThread:gThread groupMetaMessage:TSGroupMetaMessageQuit expiresInSeconds:0];
[LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) {
[self.messageSenderJobQueue addMessage:message transaction:transaction];
[gThread leaveGroupWithTransaction:transaction];
}];
}
[self.navigationController popViewControllerAnimated:YES];
@ -1273,8 +984,6 @@ static CGRect oldframe;
[BlockListUIUtils showBlockThreadActionSheet:self.thread
fromViewController:self
blockingManager:self.blockingManager
contactsManager:self.contactsManager
messageSender:self.messageSender
completionBlock:^(BOOL isBlocked) {
// Update switch state if user cancels action.
blockConversationSwitch.on = isBlocked;
@ -1290,7 +999,6 @@ static CGRect oldframe;
[BlockListUIUtils showUnblockThreadActionSheet:self.thread
fromViewController:self
blockingManager:self.blockingManager
contactsManager:self.contactsManager
completionBlock:^(BOOL isBlocked) {
// Update switch state if user cancels action.
blockConversationSwitch.on = isBlocked;
@ -1498,7 +1206,6 @@ static CGRect oldframe;
[alert addAction:[UIAlertAction actionWithTitle:@"Reset" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
dispatch_async(dispatch_get_main_queue(), ^{
[LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[thread addSessionRestoreDevice:thread.contactIdentifier transaction:transaction];
[LKSessionManagementProtocol startSessionResetInThread:thread transaction:transaction];
}];
[weakSelf.navigationController popViewControllerAnimated:YES];

View File

@ -1,20 +0,0 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
#import <SignalUtilitiesKit/OWSDevice.h>
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface OWSDeviceTableViewCell : UITableViewCell
@property (nonatomic) UILabel *nameLabel;
@property (nonatomic) UILabel *linkedLabel;
@property (nonatomic) UILabel *lastSeenLabel;
- (void)configureWithDevice:(OWSDevice *)device;
@end
NS_ASSUME_NONNULL_END

View File

@ -1,85 +0,0 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
#import "OWSDeviceTableViewCell.h"
#import "DateUtil.h"
#import <SignalUtilitiesKit/OWSTableViewController.h>
#import <SignalUtilitiesKit/Theme.h>
#import <SignalUtilitiesKit/UIFont+OWS.h>
#import <SignalUtilitiesKit/UIView+OWS.h>
NS_ASSUME_NONNULL_BEGIN
@implementation OWSDeviceTableViewCell
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(nullable NSString *)reuseIdentifier
{
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
[self configure];
}
return self;
}
- (void)configure
{
self.preservesSuperviewLayoutMargins = YES;
self.contentView.preservesSuperviewLayoutMargins = YES;
self.nameLabel = [UILabel new];
self.linkedLabel = [UILabel new];
self.lastSeenLabel = [UILabel new];
UIStackView *stackView = [[UIStackView alloc] initWithArrangedSubviews:@[
self.nameLabel,
self.linkedLabel,
self.lastSeenLabel,
]];
stackView.axis = UILayoutConstraintAxisVertical;
stackView.alignment = UIStackViewAlignmentLeading;
stackView.spacing = 2;
[self.contentView addSubview:stackView];
[stackView ows_autoPinToSuperviewMargins];
}
- (void)configureWithDevice:(OWSDevice *)device
{
OWSAssertDebug(device);
[OWSTableItem configureCell:self];
self.nameLabel.font = UIFont.ows_dynamicTypeBodyFont;
self.linkedLabel.font = UIFont.ows_dynamicTypeCaption1Font;
self.lastSeenLabel.font = UIFont.ows_dynamicTypeCaption1Font;
self.nameLabel.textColor = Theme.primaryColor;
self.linkedLabel.textColor = Theme.secondaryColor;
self.lastSeenLabel.textColor = Theme.secondaryColor;
self.nameLabel.text = device.displayName;
NSString *linkedFormatString
= NSLocalizedString(@"DEVICE_LINKED_AT_LABEL", @"{{Short Date}} when device was linked.");
self.linkedLabel.text =
[NSString stringWithFormat:linkedFormatString, [DateUtil.dateFormatter stringFromDate:device.createdAt]];
NSString *lastSeenFormatString = NSLocalizedString(
@"DEVICE_LAST_ACTIVE_AT_LABEL", @"{{Short Date}} when device last communicated with Signal Server.");
NSDate *displayedLastSeenAt;
// lastSeenAt is stored at day granularity. At midnight UTC.
// Making it likely that when you first link a device it will
// be "last seen" the day before it was created, which looks broken.
if ([device.lastSeenAt compare:device.createdAt] == NSOrderedDescending) {
displayedLastSeenAt = device.lastSeenAt;
} else {
displayedLastSeenAt = device.createdAt;
}
self.lastSeenLabel.text =
[NSString stringWithFormat:lastSeenFormatString, [DateUtil.dateFormatter stringFromDate:displayedLastSeenAt]];
}
@end
NS_ASSUME_NONNULL_END

View File

@ -9,7 +9,7 @@
#import <SignalUtilitiesKit/OWSUserProfile.h>
#import <SignalUtilitiesKit/AppReadiness.h>
#import <SignalUtilitiesKit/AppVersion.h>
#import <SignalUtilitiesKit/OWSContact.h>
#import <SignalUtilitiesKit/OWSFileSystem.h>
#import <SignalUtilitiesKit/OWSPrimaryStorage.h>
#import <SignalUtilitiesKit/TSAttachmentStream.h>

View File

@ -1,18 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
NS_ASSUME_NONNULL_BEGIN
typedef void (^SubmitDebugLogsCompletion)(void);
@interface Pastelog : NSObject
- (instancetype)init NS_UNAVAILABLE;
+ (void)submitLogs;
+ (void)submitLogsWithCompletion:(nullable SubmitDebugLogsCompletion)completion;
@end
NS_ASSUME_NONNULL_END

View File

@ -1,647 +0,0 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
#import "Pastelog.h"
#import "Session-Swift.h"
#import "ThreadUtil.h"
#import "zlib.h"
#import <AFNetworking/AFNetworking.h>
#import <SSZipArchive/SSZipArchive.h>
#import <SignalCoreKit/Threading.h>
#import <SignalUtilitiesKit/AttachmentSharing.h>
#import <SignalUtilitiesKit/DebugLogger.h>
#import <SignalUtilitiesKit/Environment.h>
#import <SignalUtilitiesKit/AppContext.h>
#import <SignalUtilitiesKit/MimeTypeUtil.h>
#import <SignalUtilitiesKit/OWSPrimaryStorage.h>
#import <SignalUtilitiesKit/TSAccountManager.h>
#import <SignalUtilitiesKit/TSContactThread.h>
#import <sys/sysctl.h>
NS_ASSUME_NONNULL_BEGIN
typedef void (^UploadDebugLogsSuccess)(NSURL *url);
typedef void (^UploadDebugLogsFailure)(NSString *localizedErrorMessage);
#pragma mark -
@class DebugLogUploader;
typedef void (^DebugLogUploadSuccess)(DebugLogUploader *uploader, NSURL *url);
typedef void (^DebugLogUploadFailure)(DebugLogUploader *uploader, NSError *error);
@interface DebugLogUploader : NSObject
@property (nonatomic) NSURL *fileUrl;
@property (nonatomic) NSString *mimeType;
@property (nonatomic, nullable) DebugLogUploadSuccess success;
@property (nonatomic, nullable) DebugLogUploadFailure failure;
@end
#pragma mark -
@implementation DebugLogUploader
- (void)dealloc
{
OWSLogVerbose(@"");
}
- (void)uploadFileWithURL:(NSURL *)fileUrl
mimeType:(NSString *)mimeType
success:(DebugLogUploadSuccess)success
failure:(DebugLogUploadFailure)failure
{
OWSAssertDebug(fileUrl);
OWSAssertDebug(mimeType.length > 0);
OWSAssertDebug(success);
OWSAssertDebug(failure);
self.fileUrl = fileUrl;
self.mimeType = mimeType;
self.success = success;
self.failure = failure;
[self getUploadParameters];
}
- (void)getUploadParameters
{
__weak DebugLogUploader *weakSelf = self;
NSURLSessionConfiguration *sessionConf = NSURLSessionConfiguration.ephemeralSessionConfiguration;
AFHTTPSessionManager *sessionManager =
[[AFHTTPSessionManager alloc] initWithBaseURL:nil sessionConfiguration:sessionConf];
sessionManager.requestSerializer = [AFHTTPRequestSerializer serializer];
sessionManager.responseSerializer = [AFJSONResponseSerializer serializer];
NSString *urlString = @"https://debuglogs.org/";
[sessionManager GET:urlString
parameters:nil
headers:nil
progress:nil
success:^(NSURLSessionDataTask *task, id _Nullable responseObject) {
DebugLogUploader *strongSelf = weakSelf;
if (!strongSelf) {
return;
}
if (![responseObject isKindOfClass:[NSDictionary class]]) {
OWSLogError(@"Invalid response: %@, %@", urlString, responseObject);
[strongSelf
failWithError:OWSErrorWithCodeDescription(OWSErrorCodeDebugLogUploadFailed, @"Invalid response")];
return;
}
NSString *uploadUrl = responseObject[@"url"];
if (![uploadUrl isKindOfClass:[NSString class]] || uploadUrl.length < 1) {
OWSLogError(@"Invalid response: %@, %@", urlString, responseObject);
[strongSelf
failWithError:OWSErrorWithCodeDescription(OWSErrorCodeDebugLogUploadFailed, @"Invalid response")];
return;
}
NSDictionary *fields = responseObject[@"fields"];
if (![fields isKindOfClass:[NSDictionary class]] || fields.count < 1) {
OWSLogError(@"Invalid response: %@, %@", urlString, responseObject);
[strongSelf
failWithError:OWSErrorWithCodeDescription(OWSErrorCodeDebugLogUploadFailed, @"Invalid response")];
return;
}
for (NSString *fieldName in fields) {
NSString *fieldValue = fields[fieldName];
if (![fieldName isKindOfClass:[NSString class]] || fieldName.length < 1
|| ![fieldValue isKindOfClass:[NSString class]] || fieldValue.length < 1) {
OWSLogError(@"Invalid response: %@, %@", urlString, responseObject);
[strongSelf failWithError:OWSErrorWithCodeDescription(
OWSErrorCodeDebugLogUploadFailed, @"Invalid response")];
return;
}
}
NSString *_Nullable uploadKey = fields[@"key"];
if (![uploadKey isKindOfClass:[NSString class]] || uploadKey.length < 1) {
OWSLogError(@"Invalid response: %@, %@", urlString, responseObject);
[strongSelf
failWithError:OWSErrorWithCodeDescription(OWSErrorCodeDebugLogUploadFailed, @"Invalid response")];
return;
}
// Add a file extension to the upload's key.
NSString *fileExtension = strongSelf.fileUrl.lastPathComponent.pathExtension;
if (fileExtension.length < 1) {
OWSLogError(@"Invalid file url: %@, %@", urlString, responseObject);
[strongSelf
failWithError:OWSErrorWithCodeDescription(OWSErrorCodeDebugLogUploadFailed, @"Invalid file url")];
return;
}
uploadKey = [uploadKey stringByAppendingPathExtension:fileExtension];
NSMutableDictionary *updatedFields = [fields mutableCopy];
updatedFields[@"key"] = uploadKey;
[strongSelf uploadFileWithUploadUrl:uploadUrl fields:updatedFields uploadKey:uploadKey];
}
failure:^(NSURLSessionDataTask *_Nullable task, NSError *error) {
OWSLogError(@"failed: %@", urlString);
[weakSelf failWithError:error];
}];
}
- (void)uploadFileWithUploadUrl:(NSString *)uploadUrl fields:(NSDictionary *)fields uploadKey:(NSString *)uploadKey
{
OWSAssertDebug(uploadUrl.length > 0);
OWSAssertDebug(fields);
OWSAssertDebug(uploadKey.length > 0);
__weak DebugLogUploader *weakSelf = self;
NSURLSessionConfiguration *sessionConf = NSURLSessionConfiguration.ephemeralSessionConfiguration;
AFHTTPSessionManager *sessionManager =
[[AFHTTPSessionManager alloc] initWithBaseURL:nil sessionConfiguration:sessionConf];
sessionManager.requestSerializer = [AFHTTPRequestSerializer serializer];
sessionManager.responseSerializer = [AFHTTPResponseSerializer serializer];
[sessionManager POST:uploadUrl
parameters:@{}
headers:nil
constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
for (NSString *fieldName in fields) {
NSString *fieldValue = fields[fieldName];
[formData appendPartWithFormData:[fieldValue dataUsingEncoding:NSUTF8StringEncoding] name:fieldName];
}
[formData appendPartWithFormData:[weakSelf.mimeType dataUsingEncoding:NSUTF8StringEncoding]
name:@"content-type"];
NSError *error;
BOOL success = [formData appendPartWithFileURL:weakSelf.fileUrl
name:@"file"
fileName:weakSelf.fileUrl.lastPathComponent
mimeType:weakSelf.mimeType
error:&error];
if (!success || error) {
OWSLogError(@"failed: %@, error: %@", uploadUrl, error);
}
}
progress:nil
success:^(NSURLSessionDataTask *task, id _Nullable responseObject) {
OWSLogVerbose(@"Response: %@, %@", uploadUrl, responseObject);
NSString *urlString = [NSString stringWithFormat:@"https://debuglogs.org/%@", uploadKey];
[self succeedWithUrl:[NSURL URLWithString:urlString]];
}
failure:^(NSURLSessionDataTask *_Nullable task, NSError *error) {
OWSLogError(@"upload: %@ failed with error: %@", uploadUrl, error);
[weakSelf failWithError:error];
}];
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
NSInteger statusCode = httpResponse.statusCode;
// We'll accept any 2xx status code.
NSInteger statusCodeClass = statusCode - (statusCode % 100);
if (statusCodeClass != 200) {
OWSLogError(@"statusCode: %zd, %zd", statusCode, statusCodeClass);
OWSLogError(@"headers: %@", httpResponse.allHeaderFields);
[self failWithError:[NSError errorWithDomain:@"PastelogKit"
code:10001
userInfo:@{ NSLocalizedDescriptionKey : @"Invalid response code." }]];
}
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
OWSLogVerbose(@"");
[self failWithError:error];
}
- (void)failWithError:(NSError *)error
{
OWSAssertDebug(error);
OWSLogError(@"%@", error);
DispatchMainThreadSafe(^{
// Call the completions exactly once.
if (self.failure) {
self.failure(self, error);
}
self.success = nil;
self.failure = nil;
});
}
- (void)succeedWithUrl:(NSURL *)url
{
OWSAssertDebug(url);
OWSLogVerbose(@"%@", url);
DispatchMainThreadSafe(^{
// Call the completions exactly once.
if (self.success) {
self.success(self, url);
}
self.success = nil;
self.failure = nil;
});
}
@end
#pragma mark -
@interface Pastelog () <UIAlertViewDelegate>
@property (nonatomic) UIAlertController *loadingAlert;
@property (nonatomic) DebugLogUploader *currentUploader;
@end
#pragma mark -
@implementation Pastelog
+ (instancetype)sharedManager
{
static Pastelog *sharedMyManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedMyManager = [[self alloc] initDefault];
});
return sharedMyManager;
}
- (instancetype)initDefault
{
self = [super init];
if (!self) {
return self;
}
OWSSingletonAssert();
return self;
}
#pragma mark - Dependencies
- (YapDatabaseConnection *)dbConnection
{
return SSKEnvironment.shared.primaryStorage.dbReadWriteConnection;
}
- (TSAccountManager *)tsAccountManager
{
OWSAssertDebug(SSKEnvironment.shared.tsAccountManager);
return SSKEnvironment.shared.tsAccountManager;
}
#pragma mark -
+ (void)submitLogs
{
[self submitLogsWithCompletion:nil];
}
+ (void)submitLogsWithCompletion:(nullable SubmitDebugLogsCompletion)completionParam
{
SubmitDebugLogsCompletion completion = ^{
if (completionParam) {
// Wait a moment. If PasteLog opens a URL, it needs a moment to complete.
dispatch_after(
dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC), dispatch_get_main_queue(), completionParam);
}
};
[[self sharedManager] uploadLogsWithUIWithSuccess:^(NSURL *url) {
UIAlertController *alert = [UIAlertController
alertControllerWithTitle:NSLocalizedString(@"DEBUG_LOG_ALERT_TITLE", @"Title of the debug log alert.")
message:NSLocalizedString(@"DEBUG_LOG_ALERT_MESSAGE", @"Message of the debug log alert.")
preferredStyle:UIAlertControllerStyleAlert];
[alert
addAction:[UIAlertAction
actionWithTitle:NSLocalizedString(@"DEBUG_LOG_ALERT_OPTION_EMAIL",
@"Label for the 'email debug log' option of the debug log alert.")
accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME(self, @"send_email")
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) {
[Pastelog.sharedManager submitEmail:url];
completion();
}]];
[alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"DEBUG_LOG_ALERT_OPTION_COPY_LINK",
@"Label for the 'copy link' option of the debug log alert.")
accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME(self, @"copy_link")
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) {
UIPasteboard *pb = [UIPasteboard generalPasteboard];
[pb setString:url.absoluteString];
completion();
}]];
#ifdef DEBUG
[alert
addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"DEBUG_LOG_ALERT_OPTION_SEND_TO_SELF",
@"Label for the 'send to self' option of the debug log alert.")
accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME(self, @"send_to_self")
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) {
[Pastelog.sharedManager sendToSelf:url];
}]];
[alert addAction:[UIAlertAction actionWithTitle:
NSLocalizedString(@"DEBUG_LOG_ALERT_OPTION_SEND_TO_LAST_THREAD",
@"Label for the 'send to last thread' option of the debug log alert.")
accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME(self, @"send_to_last_thread")
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) {
[Pastelog.sharedManager sendToMostRecentThread:url];
}]];
#endif
[alert
addAction:
[UIAlertAction actionWithTitle:NSLocalizedString(@"DEBUG_LOG_ALERT_OPTION_BUG_REPORT",
@"Label for the 'Open a Bug Report' option of the debug log alert.")
accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME(self, @"submit_bug_report")
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) {
[Pastelog.sharedManager prepareRedirection:url completion:completion];
}]];
[alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"DEBUG_LOG_ALERT_OPTION_SHARE",
@"Label for the 'Share' option of the debug log alert.")
accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME(self, @"share")
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) {
[AttachmentSharing showShareUIForText:url.absoluteString
completion:completion];
}]];
[alert addAction:[OWSAlerts cancelAction]];
UIViewController *presentingViewController
= UIApplication.sharedApplication.frontmostViewControllerIgnoringAlerts;
[presentingViewController presentAlert:alert animated:NO];
}];
}
- (void)uploadLogsWithUIWithSuccess:(UploadDebugLogsSuccess)successParam {
OWSAssertIsOnMainThread();
[ModalActivityIndicatorViewController
presentFromViewController:UIApplication.sharedApplication.frontmostViewControllerIgnoringAlerts
canCancel:YES
backgroundBlock:^(ModalActivityIndicatorViewController *modalActivityIndicator) {
[self
uploadLogsWithSuccess:^(NSURL *url) {
OWSAssertIsOnMainThread();
if (modalActivityIndicator.wasCancelled) {
return;
}
[modalActivityIndicator dismissWithCompletion:^{
OWSAssertIsOnMainThread();
successParam(url);
}];
}
failure:^(NSString *localizedErrorMessage) {
OWSAssertIsOnMainThread();
if (modalActivityIndicator.wasCancelled) {
return;
}
[modalActivityIndicator dismissWithCompletion:^{
OWSAssertIsOnMainThread();
[Pastelog showFailureAlertWithMessage:localizedErrorMessage];
}];
}];
}];
}
- (void)uploadLogsWithSuccess:(UploadDebugLogsSuccess)successParam failure:(UploadDebugLogsFailure)failureParam {
OWSAssertDebug(successParam);
OWSAssertDebug(failureParam);
// Ensure that we call the completions on the main thread.
UploadDebugLogsSuccess success = ^(NSURL *url) {
DispatchMainThreadSafe(^{
successParam(url);
});
};
UploadDebugLogsFailure failure = ^(NSString *localizedErrorMessage) {
DispatchMainThreadSafe(^{
failureParam(localizedErrorMessage);
});
};
// Phase 1. Make a local copy of all of the log files.
NSDateFormatter *dateFormatter = [NSDateFormatter new];
[dateFormatter setLocale:[NSLocale currentLocale]];
[dateFormatter setDateFormat:@"yyyy.MM.dd hh.mm.ss"];
NSString *dateString = [dateFormatter stringFromDate:[NSDate new]];
NSString *logsName = [[dateString stringByAppendingString:@" "] stringByAppendingString:NSUUID.UUID.UUIDString];
NSString *tempDirectory = OWSTemporaryDirectory();
NSString *zipFilePath =
[tempDirectory stringByAppendingPathComponent:[logsName stringByAppendingPathExtension:@"zip"]];
NSString *zipDirPath = [tempDirectory stringByAppendingPathComponent:logsName];
[OWSFileSystem ensureDirectoryExists:zipDirPath];
NSArray<NSString *> *logFilePaths = DebugLogger.sharedLogger.allLogFilePaths;
if (logFilePaths.count < 1) {
failure(NSLocalizedString(@"DEBUG_LOG_ALERT_NO_LOGS", @"Error indicating that no debug logs could be found."));
return;
}
for (NSString *logFilePath in logFilePaths) {
NSString *copyFilePath = [zipDirPath stringByAppendingPathComponent:logFilePath.lastPathComponent];
NSError *error;
[[NSFileManager defaultManager] copyItemAtPath:logFilePath toPath:copyFilePath error:&error];
if (error) {
failure(NSLocalizedString(
@"DEBUG_LOG_ALERT_COULD_NOT_COPY_LOGS", @"Error indicating that the debug logs could not be copied."));
return;
}
[OWSFileSystem protectFileOrFolderAtPath:copyFilePath];
}
// Phase 2. Zip up the log files.
BOOL zipSuccess = [SSZipArchive createZipFileAtPath:zipFilePath
withContentsOfDirectory:zipDirPath
keepParentDirectory:YES
compressionLevel:Z_DEFAULT_COMPRESSION
password:nil
AES:NO
progressHandler:nil];
if (!zipSuccess) {
failure(NSLocalizedString(
@"DEBUG_LOG_ALERT_COULD_NOT_PACKAGE_LOGS", @"Error indicating that the debug logs could not be packaged."));
return;
}
[OWSFileSystem protectFileOrFolderAtPath:zipFilePath];
[OWSFileSystem deleteFile:zipDirPath];
// Phase 3. Upload the log files.
__weak Pastelog *weakSelf = self;
self.currentUploader = [DebugLogUploader new];
[self.currentUploader uploadFileWithURL:[NSURL fileURLWithPath:zipFilePath]
mimeType:OWSMimeTypeApplicationZip
success:^(DebugLogUploader *uploader, NSURL *url) {
if (uploader != weakSelf.currentUploader) {
// Ignore events from obsolete uploaders.
return;
}
[OWSFileSystem deleteFile:zipFilePath];
success(url);
}
failure:^(DebugLogUploader *uploader, NSError *error) {
if (uploader != weakSelf.currentUploader) {
// Ignore events from obsolete uploaders.
return;
}
[OWSFileSystem deleteFile:zipFilePath];
failure(NSLocalizedString(
@"DEBUG_LOG_ALERT_ERROR_UPLOADING_LOG", @"Error indicating that a debug log could not be uploaded."));
}];
}
+ (void)showFailureAlertWithMessage:(NSString *)message
{
UIAlertController *alert = [UIAlertController
alertControllerWithTitle:NSLocalizedString(@"DEBUG_LOG_ALERT_TITLE",
@"Title of the alert shown for failures while uploading debug logs.")
message:message
preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"OK", @"")
accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME(self, @"ok")
style:UIAlertActionStyleDefault
handler:nil]];
UIViewController *presentingViewController = UIApplication.sharedApplication.frontmostViewControllerIgnoringAlerts;
[presentingViewController presentAlert:alert animated:NO];
}
#pragma mark Logs submission
- (void)submitEmail:(NSURL *)url
{
NSString *emailAddress = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"LOGS_EMAIL"];
NSMutableString *body = [NSMutableString new];
[body appendFormat:@"Tell us about the issue: \n\n\n"];
size_t size;
sysctlbyname("hw.machine", NULL, &size, NULL, 0);
char *machine = malloc(size);
sysctlbyname("hw.machine", machine, &size, NULL, 0);
NSString *platform = [NSString stringWithUTF8String:machine];
free(machine);
[body appendFormat:@"Device: %@ (%@)\n", UIDevice.currentDevice.model, platform];
[body appendFormat:@"iOS Version: %@ \n", [UIDevice currentDevice].systemVersion];
[body appendFormat:@"Signal Version: %@ \n", [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]];
[body appendFormat:@"Log URL: %@ \n", url];
NSString *escapedBody =
[body stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLQueryAllowedCharacterSet];
NSString *urlString =
[NSString stringWithFormat:@"mailto:%@?subject=iOS%%20Debug%%20Log&body=%@", emailAddress, escapedBody];
BOOL success = [UIApplication.sharedApplication openURL:[NSURL URLWithString:urlString]];
if (!success) {
OWSLogError(@"Could not open Email app.");
[OWSAlerts showErrorAlertWithMessage:NSLocalizedString(@"DEBUG_LOG_COULD_NOT_EMAIL",
@"Error indicating that the app could not launch the Email app.")];
}
}
- (void)prepareRedirection:(NSURL *)url completion:(SubmitDebugLogsCompletion)completion
{
OWSAssertDebug(completion);
UIPasteboard *pb = [UIPasteboard generalPasteboard];
[pb setString:url.absoluteString];
UIAlertController *alert =
[UIAlertController alertControllerWithTitle:NSLocalizedString(@"DEBUG_LOG_GITHUB_ISSUE_ALERT_TITLE",
@"Title of the alert before redirecting to GitHub Issues.")
message:NSLocalizedString(@"DEBUG_LOG_GITHUB_ISSUE_ALERT_MESSAGE",
@"Message of the alert before redirecting to GitHub Issues.")
preferredStyle:UIAlertControllerStyleAlert];
[alert
addAction:[UIAlertAction
actionWithTitle:NSLocalizedString(@"OK", @"")
accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME(self, @"ok")
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) {
[UIApplication.sharedApplication
openURL:[NSURL
URLWithString:[[NSBundle mainBundle]
objectForInfoDictionaryKey:@"LOGS_URL"]]];
completion();
}]];
UIViewController *presentingViewController = UIApplication.sharedApplication.frontmostViewControllerIgnoringAlerts;
[presentingViewController presentAlert:alert animated:NO];
}
- (void)sendToSelf:(NSURL *)url
{
if (![self.tsAccountManager isRegistered]) {
return;
}
NSString *recipientId = [TSAccountManager localNumber];
DispatchMainThreadSafe(^{
__block TSThread *thread = nil;
[LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
thread = [TSContactThread getOrCreateThreadWithContactId:recipientId transaction:transaction];
}];
[LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) {
[ThreadUtil enqueueMessageWithText:url.absoluteString
inThread:thread
quotedReplyModel:nil
linkPreviewDraft:nil
transaction:transaction];
}];
});
// Also copy to pasteboard.
[[UIPasteboard generalPasteboard] setString:url.absoluteString];
}
- (void)sendToMostRecentThread:(NSURL *)url
{
if (![self.tsAccountManager isRegistered]) {
return;
}
__block TSThread *thread = nil;
[OWSPrimaryStorage.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
thread = [[transaction ext:TSThreadDatabaseViewExtensionName] firstObjectInGroup:TSInboxGroup];
}];
DispatchMainThreadSafe(^{
if (thread) {
[LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) {
[ThreadUtil enqueueMessageWithText:url.absoluteString
inThread:thread
quotedReplyModel:nil
linkPreviewDraft:nil
transaction:transaction];
}];
} else {
[Pastelog showFailureAlertWithMessage:@"Could not find last thread."];
}
});
// Also copy to pasteboard.
[[UIPasteboard generalPasteboard] setString:url.absoluteString];
}
@end
NS_ASSUME_NONNULL_END

View File

@ -1,32 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
NS_ASSUME_NONNULL_BEGIN
@class PinEntryView;
@protocol PinEntryViewDelegate <NSObject>
- (void)pinEntryView:(PinEntryView *)entryView submittedPinCode:(NSString *)pinCode;
- (void)pinEntryViewForgotPinLinkTapped:(PinEntryView *)entryView;
@optional
- (void)pinEntryView:(PinEntryView *)entryView pinCodeDidChange:(NSString *)pinCode;
@end
@interface PinEntryView : UIView
@property (nonatomic, weak, nullable) id<PinEntryViewDelegate> delegate;
@property (nonatomic, readonly) BOOL hasValidPin;
@property (nullable, nonatomic) NSString *instructionsText;
@property (nullable, nonatomic) NSAttributedString *attributedInstructionsText;
@property (nonatomic, readonly) UIFont *boldLabelFont;
- (void)clearText;
- (BOOL)makePinTextFieldFirstResponder;
@end
NS_ASSUME_NONNULL_END

View File

@ -1,227 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "PinEntryView.h"
#import "Session-Swift.h"
#import <SignalUtilitiesKit/UIColor+OWS.h>
#import <SignalUtilitiesKit/UIFont+OWS.h>
#import <SignalUtilitiesKit/UIView+OWS.h>
#import <SignalUtilitiesKit/ViewControllerUtils.h>
NS_ASSUME_NONNULL_BEGIN
@interface PinEntryView () <UITextFieldDelegate>
@property (nonatomic) UITextField *pinTextfield;
@property (nonatomic) OWSFlatButton *submitButton;
@property (nonatomic) UILabel *instructionsLabel;
@end
@implementation PinEntryView : UIView
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (!self) {
return self;
}
[self createContents];
return self;
}
#pragma mark - view creation
- (UIFont *)labelFont
{
return [UIFont ows_regularFontWithSize:ScaleFromIPhone5To7Plus(14.f, 16.f)];
}
- (UIFont *)boldLabelFont
{
return [UIFont ows_boldFontWithSize:ScaleFromIPhone5To7Plus(14.f, 16.f)];
}
- (UILabel *)createLabelWithText:(nullable NSString *)text
{
UILabel *label = [UILabel new];
label.textColor = [Theme primaryColor];
label.text = text;
label.font = self.labelFont;
label.numberOfLines = 0;
label.lineBreakMode = NSLineBreakByWordWrapping;
label.textAlignment = NSTextAlignmentCenter;
[self addSubview:label];
return label;
}
- (void)createPinTextfield
{
if (UIDevice.currentDevice.isShorterThanIPhone5) {
self.pinTextfield = [DismissableTextField new];
} else {
self.pinTextfield = [OWSTextField new];
}
self.pinTextfield.textColor = [Theme primaryColor];
self.pinTextfield.font = [UIFont ows_mediumFontWithSize:ScaleFromIPhone5To7Plus(30.f, 36.f)];
self.pinTextfield.textAlignment = NSTextAlignmentCenter;
self.pinTextfield.keyboardType = UIKeyboardTypeNumberPad;
self.pinTextfield.delegate = self;
self.pinTextfield.secureTextEntry = YES;
self.pinTextfield.textAlignment = NSTextAlignmentCenter;
[self addSubview:self.pinTextfield];
}
- (UILabel *)createForgotLink
{
UILabel *label = [UILabel new];
label.textColor = [UIColor ows_materialBlueColor];
NSString *text = NSLocalizedString(
@"REGISTER_2FA_FORGOT_PIN", @"Label for 'I forgot my PIN' link in the 2FA registration view.");
label.attributedText = [[NSAttributedString alloc]
initWithString:text
attributes:@{
NSForegroundColorAttributeName : [UIColor ows_materialBlueColor],
NSUnderlineStyleAttributeName : @(NSUnderlineStyleSingle | NSUnderlinePatternSolid)
}];
label.font = [UIFont ows_regularFontWithSize:ScaleFromIPhone5To7Plus(14.f, 16.f)];
label.numberOfLines = 0;
label.lineBreakMode = NSLineBreakByWordWrapping;
label.textAlignment = NSTextAlignmentCenter;
label.userInteractionEnabled = YES;
[label addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self
action:@selector(forgotPinLinkTapped:)]];
[self addSubview:label];
return label;
}
- (void)createSubmitButton
{
const CGFloat kSubmitButtonHeight = 47.f;
// NOTE: We use ows_signalBrandBlueColor instead of ows_materialBlueColor
// throughout the onboarding flow to be consistent with the headers.
OWSFlatButton *submitButton =
[OWSFlatButton buttonWithTitle:NSLocalizedString(@"REGISTER_2FA_SUBMIT_BUTTON",
@"Label for 'submit' button in the 2FA registration view.")
font:[OWSFlatButton fontForHeight:kSubmitButtonHeight]
titleColor:[UIColor whiteColor]
backgroundColor:[UIColor ows_signalBrandBlueColor]
target:self
selector:@selector(submitButtonWasPressed)];
self.submitButton = submitButton;
[self addSubview:submitButton];
[self.submitButton autoSetDimension:ALDimensionHeight toSize:kSubmitButtonHeight];
}
- (nullable NSString *)instructionsText
{
return self.instructionsLabel.text;
}
- (void)setInstructionsText:(nullable NSString *)instructionsText
{
self.instructionsLabel.text = instructionsText;
}
- (nullable NSAttributedString *)attributedInstructionsText
{
return self.instructionsLabel.attributedText;
}
- (void)setAttributedInstructionsText:(nullable NSAttributedString *)attributedInstructionsText
{
self.instructionsLabel.attributedText = attributedInstructionsText;
}
- (void)createContents
{
const CGFloat kVSpacing = ScaleFromIPhone5To7Plus(12, 30);
UILabel *instructionsLabel = [self createLabelWithText:nil];
self.instructionsLabel = instructionsLabel;
[instructionsLabel autoPinTopToSuperviewMarginWithInset:kVSpacing];
[instructionsLabel autoPinWidthToSuperview];
UILabel *createForgotLink = [self createForgotLink];
[createForgotLink autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:instructionsLabel withOffset:5];
[createForgotLink autoPinWidthToSuperview];
[self createPinTextfield];
[self.pinTextfield autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:createForgotLink withOffset:kVSpacing];
[self.pinTextfield autoPinWidthToSuperview];
UIView *underscoreView = [UIView new];
underscoreView.backgroundColor = [UIColor colorWithWhite:0.5 alpha:1.f];
[self addSubview:underscoreView];
[underscoreView autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:self.pinTextfield withOffset:3];
[underscoreView autoPinWidthToSuperview];
[underscoreView autoSetDimension:ALDimensionHeight toSize:1.f];
[self createSubmitButton];
[self.submitButton autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:underscoreView withOffset:kVSpacing];
[self.submitButton autoPinWidthToSuperview];
[self updateIsSubmitEnabled];
}
#pragma mark - UITextFieldDelegate
- (BOOL)textField:(UITextField *)textField
shouldChangeCharactersInRange:(NSRange)range
replacementString:(NSString *)insertionText
{
[ViewControllerUtils ows2FAPINTextField:textField
shouldChangeCharactersInRange:range
replacementString:insertionText];
[self updateIsSubmitEnabled];
if (self.delegate && [self.delegate respondsToSelector:@selector(pinEntryView:pinCodeDidChange:)]) {
[self.delegate pinEntryView:self pinCodeDidChange:textField.text];
}
return NO;
}
- (void)updateIsSubmitEnabled;
{
[self.submitButton setEnabled:self.hasValidPin];
}
- (BOOL)makePinTextFieldFirstResponder
{
return [self.pinTextfield becomeFirstResponder];
}
- (BOOL)hasValidPin
{
return self.pinTextfield.text.length >= kMin2FAPinLength;
}
- (void)clearText
{
self.pinTextfield.text = @"";
[self updateIsSubmitEnabled];
}
#pragma mark - Events
- (void)submitButtonWasPressed
{
[self.delegate pinEntryView:self submittedPinCode:self.pinTextfield.text];
}
- (void)forgotPinLinkTapped:(UIGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateRecognized) {
[self.delegate pinEntryViewForgotPinLinkTapped:self];
}
}
@end
NS_ASSUME_NONNULL_END

View File

@ -3,16 +3,16 @@
//
#import "PrivacySettingsTableViewController.h"
#import "BlockListViewController.h"
#import "Session-Swift.h"
#import <SignalCoreKit/NSString+OWS.h>
#import <SignalUtilitiesKit/Environment.h>
#import <SignalUtilitiesKit/OWSPreferences.h>
#import <SignalUtilitiesKit/ThreadUtil.h>
#import <SignalUtilitiesKit/UIColor+OWS.h>
#import <SignalUtilitiesKit/SignalUtilitiesKit-Swift.h>
#import <SignalUtilitiesKit/NSString+SSK.h>
#import <SignalUtilitiesKit/OWS2FAManager.h>
#import <SignalUtilitiesKit/ThreadUtil.h>
#import <SignalUtilitiesKit/OWSReadReceiptManager.h>
#import <SignalUtilitiesKit/SignalUtilitiesKit-Swift.h>
@ -83,22 +83,7 @@ static NSString *const kSealedSenderInfoURL = @"https://signal.org/blog/sealed-s
OWSTableContents *contents = [OWSTableContents new];
__weak PrivacySettingsTableViewController *weakSelf = self;
// Loki: Original code
// ========
// OWSTableSection *blocklistSection = [OWSTableSection new];
// blocklistSection.headerTitle
// = NSLocalizedString(@"SETTINGS_BLOCK_LIST_TITLE", @"Label for the block list section of the settings view");
// [blocklistSection
// addItem:[OWSTableItem disclosureItemWithText:NSLocalizedString(@"SETTINGS_BLOCK_LIST_TITLE",
// @"Label for the block list section of the settings view")
// accessibilityIdentifier:[NSString stringWithFormat:@"settings.privacy.%@", @"blocklist"]
// actionBlock:^{
// [weakSelf showBlocklist];
// }]];
// [contents addSection:blocklistSection];
//
// ========
OWSTableSection *readReceiptsSection = [OWSTableSection new];
readReceiptsSection.headerTitle
= NSLocalizedString(@"SETTINGS_READ_RECEIPT", @"Label for the 'read receipts' setting.");
@ -189,104 +174,6 @@ static NSString *const kSealedSenderInfoURL = @"https://signal.org/blog/sealed-s
selector:@selector(didToggleScreenSecuritySwitch:)]];
[contents addSection:screenSecuritySection];
// Loki: Original code
// ========
// // Allow calls to connect directly vs. using TURN exclusively
// OWSTableSection *callingSection = [OWSTableSection new];
// callingSection.headerTitle
// = NSLocalizedString(@"SETTINGS_SECTION_TITLE_CALLING", @"settings topic header for table section");
// callingSection.footerTitle = NSLocalizedString(@"SETTINGS_CALLING_HIDES_IP_ADDRESS_PREFERENCE_TITLE_DETAIL",
// @"User settings section footer, a detailed explanation");
// [callingSection addItem:[OWSTableItem switchItemWithText:NSLocalizedString(
// @"SETTINGS_CALLING_HIDES_IP_ADDRESS_PREFERENCE_TITLE",
// @"Table cell label")
// accessibilityIdentifier:[NSString stringWithFormat:@"settings.privacy.%@",
// @"calling_hide_ip_address"]
// isOnBlock:^{
// return [Environment.shared.preferences doCallsHideIPAddress];
// }
// isEnabledBlock:^{
// return YES;
// }
// target:weakSelf
// selector:@selector(didToggleCallsHideIPAddressSwitch:)]];
// [contents addSection:callingSection];
//
// if (CallUIAdapter.isCallkitDisabledForLocale) {
// // Hide all CallKit-related prefs; CallKit is disabled.
// } else if (@available(iOS 11, *)) {
// OWSTableSection *callKitSection = [OWSTableSection new];
// [callKitSection
// addItem:[OWSTableItem switchItemWithText:NSLocalizedString(
// @"SETTINGS_PRIVACY_CALLKIT_SYSTEM_CALL_LOG_PREFERENCE_TITLE",
// @"Short table cell label")
// accessibilityIdentifier:[NSString stringWithFormat:@"settings.privacy.%@", @"callkit_history"]
// isOnBlock:^{
// return [Environment.shared.preferences isSystemCallLogEnabled];
// }
// isEnabledBlock:^{
// return YES;
// }
// target:weakSelf
// selector:@selector(didToggleEnableSystemCallLogSwitch:)]];
// callKitSection.footerTitle = NSLocalizedString(
// @"SETTINGS_PRIVACY_CALLKIT_SYSTEM_CALL_LOG_PREFERENCE_DESCRIPTION", @"Settings table section footer.");
// [contents addSection:callKitSection];
// } else if (@available(iOS 10, *)) {
// OWSTableSection *callKitSection = [OWSTableSection new];
// callKitSection.footerTitle
// = NSLocalizedString(@"SETTINGS_SECTION_CALL_KIT_DESCRIPTION", @"Settings table section footer.");
// [callKitSection
// addItem:[OWSTableItem switchItemWithText:NSLocalizedString(
// @"SETTINGS_PRIVACY_CALLKIT_TITLE", @"Short table cell label")
// accessibilityIdentifier:[NSString stringWithFormat:@"settings.privacy.%@", @"callkit"]
// isOnBlock:^{
// return [Environment.shared.preferences isCallKitEnabled];
// }
// isEnabledBlock:^{
// return YES;
// }
// target:weakSelf
// selector:@selector(didToggleEnableCallKitSwitch:)]];
// if (self.preferences.isCallKitEnabled) {
// [callKitSection
// addItem:[OWSTableItem switchItemWithText:NSLocalizedString(@"SETTINGS_PRIVACY_CALLKIT_PRIVACY_TITLE",
// @"Label for 'CallKit privacy' preference")
// accessibilityIdentifier:[NSString
// stringWithFormat:@"settings.privacy.%@", @"callkit_privacy"]
// isOnBlock:^{
// return (BOOL) ![Environment.shared.preferences isCallKitPrivacyEnabled];
// }
// isEnabledBlock:^{
// return YES;
// }
// target:weakSelf
// selector:@selector(didToggleEnableCallKitPrivacySwitch:)]];
// }
// [contents addSection:callKitSection];
// }
//
// OWSTableSection *twoFactorAuthSection = [OWSTableSection new];
// twoFactorAuthSection.headerTitle = NSLocalizedString(
// @"SETTINGS_TWO_FACTOR_AUTH_TITLE", @"Title for the 'two factor auth' section of the privacy settings.");
// [twoFactorAuthSection
// addItem:
// [OWSTableItem
// disclosureItemWithText:NSLocalizedString(@"SETTINGS_TWO_FACTOR_AUTH_ITEM",
// @"Label for the 'two factor auth' item of the privacy settings.")
// detailText:
// ([OWS2FAManager.sharedManager is2FAEnabled]
// ? NSLocalizedString(@"SETTINGS_TWO_FACTOR_AUTH_ENABLED",
// @"Indicates that 'two factor auth' is enabled in the privacy settings.")
// : NSLocalizedString(@"SETTINGS_TWO_FACTOR_AUTH_DISABLED",
// @"Indicates that 'two factor auth' is disabled in the privacy settings."))
// accessibilityIdentifier:[NSString stringWithFormat:@"settings.privacy.%@", @"2fa"]
// actionBlock:^{
// [weakSelf show2FASettings];
// }]];
// [contents addSection:twoFactorAuthSection];
// ========
OWSTableSection *historyLogsSection = [OWSTableSection new];
historyLogsSection.headerTitle = NSLocalizedString(@"SETTINGS_HISTORYLOG_TITLE", @"Section header");
[historyLogsSection
@ -297,99 +184,6 @@ static NSString *const kSealedSenderInfoURL = @"https://signal.org/blog/sealed-s
}]];
[contents addSection:historyLogsSection];
// Loki: Original code
// ========
// OWSTableSection *unidentifiedDeliveryIndicatorsSection = [OWSTableSection new];
// unidentifiedDeliveryIndicatorsSection.headerTitle
// = NSLocalizedString(@"SETTINGS_UNIDENTIFIED_DELIVERY_SECTION_TITLE", @"table section label");
// [unidentifiedDeliveryIndicatorsSection
// addItem:[OWSTableItem
// itemWithCustomCellBlock:^UITableViewCell * {
// UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1
// reuseIdentifier:@"UITableViewCellStyleValue1"];
// [OWSTableItem configureCell:cell];
// cell.preservesSuperviewLayoutMargins = YES;
// cell.contentView.preservesSuperviewLayoutMargins = YES;
// cell.selectionStyle = UITableViewCellSelectionStyleNone;
//
// UILabel *label = [UILabel new];
// label.text
// = NSLocalizedString(@"SETTINGS_UNIDENTIFIED_DELIVERY_SHOW_INDICATORS", @"switch label");
// label.font = [UIFont ows_regularFontWithSize:18.f];
// label.textColor = [Theme primaryColor];
// [label setContentHuggingHorizontalHigh];
//
// UIImage *icon = [UIImage imageNamed:@"ic_secret_sender_indicator"];
// UIImageView *iconView = [[UIImageView alloc]
// initWithImage:[icon imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]];
// iconView.tintColor = Theme.secondaryColor;
// [iconView setContentHuggingHorizontalHigh];
//
// UIView *spacer = [UIView new];
// [spacer setContentHuggingHorizontalLow];
//
// UISwitch *cellSwitch = [UISwitch new];
// cell.accessoryView = cellSwitch;
// [cellSwitch setOn:Environment.shared.preferences.shouldShowUnidentifiedDeliveryIndicators];
// [cellSwitch addTarget:weakSelf
// action:@selector(didToggleUDShowIndicatorsSwitch:)
// forControlEvents:UIControlEventValueChanged];
// [cellSwitch setContentHuggingHorizontalHigh];
// cellSwitch.accessibilityIdentifier =
// [NSString stringWithFormat:@"settings.privacy.%@", @"sealed_sender"];
//
// UIStackView *stackView =
// [[UIStackView alloc] initWithArrangedSubviews:@[ label, iconView, spacer, cellSwitch ]];
// stackView.axis = UILayoutConstraintAxisHorizontal;
// stackView.spacing = 10;
// stackView.alignment = UIStackViewAlignmentCenter;
//
// [cell.contentView addSubview:stackView];
// [stackView ows_autoPinToSuperviewMargins];
// return cell;
// }
// customRowHeight:UITableViewAutomaticDimension
// actionBlock:^{
// NSURL *url = [NSURL URLWithString:kSealedSenderInfoURL];
// OWSCAssertDebug(url);
// [UIApplication.sharedApplication openURL:url];
// }]];
//
// unidentifiedDeliveryIndicatorsSection.footerTitle
// = NSLocalizedString(@"SETTINGS_UNIDENTIFIED_DELIVERY_SHOW_INDICATORS_FOOTER", @"table section footer");
// [contents addSection:unidentifiedDeliveryIndicatorsSection];
//
// OWSTableSection *unidentifiedDeliveryUnrestrictedSection = [OWSTableSection new];
// OWSTableItem *unrestrictedAccessItem = [OWSTableItem
// switchItemWithText:NSLocalizedString(@"SETTINGS_UNIDENTIFIED_DELIVERY_UNRESTRICTED_ACCESS", @"switch label")
// accessibilityIdentifier:[NSString stringWithFormat:@"settings.privacy.%@", @"sealed_sender_unrestricted"]
// isOnBlock:^{
// return [SSKEnvironment.shared.udManager shouldAllowUnrestrictedAccessLocal];
// }
// isEnabledBlock:^{
// return YES;
// }
// target:weakSelf
// selector:@selector(didToggleUDUnrestrictedAccessSwitch:)];
// [unidentifiedDeliveryUnrestrictedSection addItem:unrestrictedAccessItem];
// unidentifiedDeliveryUnrestrictedSection.footerTitle
// = NSLocalizedString(@"SETTINGS_UNIDENTIFIED_DELIVERY_UNRESTRICTED_ACCESS_FOOTER", @"table section footer");
// [contents addSection:unidentifiedDeliveryUnrestrictedSection];
//
// OWSTableSection *unidentifiedDeliveryLearnMoreSection = [OWSTableSection new];
// [unidentifiedDeliveryLearnMoreSection
// addItem:[OWSTableItem disclosureItemWithText:NSLocalizedString(@"SETTINGS_UNIDENTIFIED_DELIVERY_LEARN_MORE",
// @"Label for a link to more info about unidentified delivery.")
// accessibilityIdentifier:[NSString stringWithFormat:@"settings.privacy.%@",
// @"sealed_sender_learn_more"]
// actionBlock:^{
// NSURL *url = [NSURL URLWithString:kSealedSenderInfoURL];
// OWSCAssertDebug(url);
// [UIApplication.sharedApplication openURL:url];
// }]];
// [contents addSection:unidentifiedDeliveryLearnMoreSection];
// ========
OWSTableSection *linkPreviewsSection = [OWSTableSection new];
[linkPreviewsSection
addItem:[OWSTableItem switchItemWithText:NSLocalizedString(@"SETTINGS_LINK_PREVIEWS",
@ -414,12 +208,6 @@ static NSString *const kSealedSenderInfoURL = @"https://signal.org/blog/sealed-s
#pragma mark - Events
- (void)showBlocklist
{
BlockListViewController *vc = [BlockListViewController new];
[self.navigationController pushViewController:vc animated:YES];
}
- (void)clearHistoryLogs
{
UIAlertController *alert =

View File

@ -17,14 +17,10 @@ public enum PushRegistrationError: Error {
/**
* Singleton used to integrate with push notification services - registration and routing received remote notifications.
*/
@objc public class PushRegistrationManager: NSObject, PKPushRegistryDelegate {
@objc public class PushRegistrationManager: NSObject {
// MARK: - Dependencies
private var messageFetcherJob: MessageFetcherJob {
return AppEnvironment.shared.messageFetcherJob
}
private var notificationPresenter: NotificationPresenter {
return AppEnvironment.shared.notificationPresenter
}
@ -91,34 +87,6 @@ public enum PushRegistrationError: Error {
vanillaTokenResolver.reject(error)
}
// MARK: PKPushRegistryDelegate - voIP Push Token
public func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType) {
Logger.info("")
assert(type == .voIP)
AppReadiness.runNowOrWhenAppDidBecomeReady {
(self.messageFetcherJob.run() as Promise<Void>).retainUntilComplete()
}
}
public func pushRegistry(_ registry: PKPushRegistry, didUpdate credentials: PKPushCredentials, for type: PKPushType) {
Logger.info("")
assert(type == .voIP)
assert(credentials.type == .voIP)
guard let voipTokenResolver = self.voipTokenResolver else {
owsFailDebug("fulfillVoipTokenPromise was unexpectedly nil")
return
}
voipTokenResolver.fulfill(credentials.token)
}
public func pushRegistry(_ registry: PKPushRegistry, didInvalidatePushTokenFor type: PKPushType) {
// It's not clear when this would happen. We've never previously handled it, but we should at
// least start learning if it happens.
owsFailDebug("Invalid state")
}
// MARK: helpers
// User notification settings must be registered *before* AppDelegate will
@ -200,55 +168,6 @@ public enum PushRegistrationError: Error {
self.vanillaTokenPromise = nil
}
}
private func registerForVoipPushToken() -> Promise<String> {
AssertIsOnMainThread()
Logger.info("")
guard self.voipTokenPromise == nil else {
let promise = self.voipTokenPromise!
assert(promise.isPending)
return promise.map { $0.hexEncodedString }
}
// No pending voip token yet. Create a new promise
let (promise, resolver) = Promise<Data>.pending()
self.voipTokenPromise = promise
self.voipTokenResolver = resolver
if self.voipRegistry == nil {
// We don't create the voip registry in init, because it immediately requests the voip token,
// potentially before we're ready to handle it.
let voipRegistry = PKPushRegistry(queue: nil)
self.voipRegistry = voipRegistry
voipRegistry.desiredPushTypes = [.voIP]
voipRegistry.delegate = self
}
guard let voipRegistry = self.voipRegistry else {
owsFailDebug("failed to initialize voipRegistry")
resolver.reject(PushRegistrationError.assertionError(description: "failed to initialize voipRegistry"))
return promise.map { _ in
// coerce expected type of returned promise - we don't really care about the value,
// since this promise has been rejected. In practice this shouldn't happen
String()
}
}
// If we've already completed registering for a voip token, resolve it immediately,
// rather than waiting for the delegate method to be called.
if let voipTokenData = voipRegistry.pushToken(for: .voIP) {
Logger.info("using pre-registered voIP token")
resolver.fulfill(voipTokenData)
}
return promise.map { (voipTokenData: Data) -> String in
Logger.info("successfully registered for voip push notifications")
return voipTokenData.hexEncodedString
}.ensure {
self.voipTokenPromise = nil
}
}
}
// We transmit pushToken data as hex encoded string to the server

View File

@ -1,117 +0,0 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
import Foundation
import SignalUtilitiesKit
@objc
public class SafetyNumberConfirmationAlert: NSObject {
private let contactsManager: OWSContactsManager
private let primaryStorage: OWSPrimaryStorage
init(contactsManager: OWSContactsManager) {
self.contactsManager = contactsManager
self.primaryStorage = OWSPrimaryStorage.shared()
}
@objc
public class func presentAlertIfNecessary(recipientId: String, confirmationText: String, contactsManager: OWSContactsManager, completion: @escaping (Bool) -> Void) -> Bool {
return self.presentAlertIfNecessary(recipientIds: [recipientId], confirmationText: confirmationText, contactsManager: contactsManager, completion: completion, beforePresentationHandler: nil)
}
@objc
public class func presentAlertIfNecessary(recipientId: String, confirmationText: String, contactsManager: OWSContactsManager, completion: @escaping (Bool) -> Void, beforePresentationHandler: (() -> Void)? = nil) -> Bool {
return self.presentAlertIfNecessary(recipientIds: [recipientId], confirmationText: confirmationText, contactsManager: contactsManager, completion: completion, beforePresentationHandler: beforePresentationHandler)
}
@objc
public class func presentAlertIfNecessary(recipientIds: [String], confirmationText: String, contactsManager: OWSContactsManager, completion: @escaping (Bool) -> Void) -> Bool {
return self.presentAlertIfNecessary(recipientIds: recipientIds, confirmationText: confirmationText, contactsManager: contactsManager, completion: completion, beforePresentationHandler: nil)
}
@objc
public class func presentAlertIfNecessary(recipientIds: [String], confirmationText: String, contactsManager: OWSContactsManager, completion: @escaping (Bool) -> Void, beforePresentationHandler: (() -> Void)? = nil) -> Bool {
return SafetyNumberConfirmationAlert(contactsManager: contactsManager).presentIfNecessary(recipientIds: recipientIds,
confirmationText: confirmationText,
completion: completion,
beforePresentationHandler: beforePresentationHandler)
}
/**
* Shows confirmation dialog if at least one of the recipient id's is not confirmed.
*
* @returns true if an alert was shown
* false if there were no unconfirmed identities
*/
public func presentIfNecessary(recipientIds: [String], confirmationText: String, completion: @escaping (Bool) -> Void, beforePresentationHandler: (() -> Void)? = nil) -> Bool {
guard let untrustedIdentity = untrustedIdentityForSending(recipientIds: recipientIds) else {
// No identities to confirm, no alert to present.
return false
}
let displayName = contactsManager.displayName(forPhoneIdentifier: untrustedIdentity.recipientId)
let titleFormat = NSLocalizedString("CONFIRM_SENDING_TO_CHANGED_IDENTITY_TITLE_FORMAT",
comment: "Action sheet title presented when a user's SN has recently changed. Embeds {{contact's name or phone number}}")
let title = String(format: titleFormat, displayName)
let bodyFormat = NSLocalizedString("CONFIRM_SENDING_TO_CHANGED_IDENTITY_BODY_FORMAT",
comment: "Action sheet body presented when a user's SN has recently changed. Embeds {{contact's name or phone number}}")
let body = String(format: bodyFormat, displayName)
let actionSheet = UIAlertController(title: title, message: body, preferredStyle: .actionSheet)
let confirmAction = UIAlertAction(title: confirmationText, style: .default) { _ in
Logger.info("Confirmed identity: \(untrustedIdentity)")
self.primaryStorage.newDatabaseConnection().asyncReadWrite { (transaction) in
OWSIdentityManager.shared().setVerificationState(.default, identityKey: untrustedIdentity.identityKey, recipientId: untrustedIdentity.recipientId, isUserInitiatedChange: true, transaction: transaction)
DispatchQueue.main.async {
completion(true)
}
}
}
actionSheet.addAction(confirmAction)
let showSafetyNumberAction = UIAlertAction(title: NSLocalizedString("VERIFY_PRIVACY", comment: "Label for button or row which allows users to verify the safety number of another user."), style: .default) { _ in
Logger.info("Opted to show Safety Number for identity: \(untrustedIdentity)")
self.presentSafetyNumberViewController(theirIdentityKey: untrustedIdentity.identityKey,
theirRecipientId: untrustedIdentity.recipientId,
theirDisplayName: displayName,
completion: { completion(false) })
}
actionSheet.addAction(showSafetyNumberAction)
// We can't use the default `OWSAlerts.cancelAction` because we need to specify that the completion
// handler is called.
let cancelAction = UIAlertAction(title: CommonStrings.cancelButton, style: .cancel) { _ in
Logger.info("user canceled.")
completion(false)
}
actionSheet.addAction(cancelAction)
beforePresentationHandler?()
UIApplication.shared.frontmostViewController?.presentAlert(actionSheet)
return true
}
public func presentSafetyNumberViewController(theirIdentityKey: Data, theirRecipientId: String, theirDisplayName: String, completion: (() -> Void)? = nil) {
guard let fromViewController = UIApplication.shared.frontmostViewController else {
Logger.info("Missing frontmostViewController")
return
}
FingerprintViewController.present(from: fromViewController, recipientId: theirRecipientId)
}
private func untrustedIdentityForSending(recipientIds: [String]) -> OWSRecipientIdentity? {
return recipientIds.compactMap {
OWSIdentityManager.shared().untrustedIdentityForSending(toRecipientId: $0)
}.first
}
}

View File

@ -97,10 +97,6 @@ public class SessionResetOperation: OWSOperation, DurableOperation {
return SSKEnvironment.shared.primaryStorage
}
var messageSender: MessageSender {
return SSKEnvironment.shared.messageSender
}
// MARK:
var firstAttempt = true
@ -108,19 +104,7 @@ public class SessionResetOperation: OWSOperation, DurableOperation {
override public func run() {
assert(self.durableOperationDelegate != nil)
/* Loki: Original code
* We don't want to delete the session. Ref: SignalServiceKit/Loki/Docs/SessionReset.md
* ================
if firstAttempt {
Storage.writeSync { transaction in
Logger.info("deleting sessions for recipient: \(self.recipientId)")
self.primaryStorage.deleteAllSessions(forContact: self.recipientId, protocolContext: transaction)
}
firstAttempt = false
}
* ================
*/
/*
let endSessionMessage = EndSessionMessage(timestamp: NSDate.ows_millisecondTimeStamp(), in: self.contactThread)
firstly {
@ -157,6 +141,7 @@ public class SessionResetOperation: OWSOperation, DurableOperation {
Logger.error("sending error: \(error.localizedDescription)")
self.reportError(error)
}.retainUntilComplete()
*/
}
override public func didSucceed() {

View File

@ -1,13 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "OWSTableViewController.h"
@class TSGroupThread;
@interface ShowGroupMembersViewController : OWSTableViewController
- (void)configWithThread:(TSGroupThread *)thread;
@end

View File

@ -1,478 +0,0 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
#import "ShowGroupMembersViewController.h"
#import "Session-Swift.h"
#import "SignalApp.h"
#import "ViewControllerUtils.h"
#import <SignalUtilitiesKit/BlockListUIUtils.h>
#import <SignalUtilitiesKit/ContactTableViewCell.h>
#import <SignalUtilitiesKit/ContactsViewHelper.h>
#import <SignalUtilitiesKit/Environment.h>
#import <SignalUtilitiesKit/OWSContactsManager.h>
#import <SignalUtilitiesKit/UIUtil.h>
#import <SignalUtilitiesKit/OWSBlockingManager.h>
#import <SignalUtilitiesKit/SignalAccount.h>
#import <SignalUtilitiesKit/TSGroupModel.h>
#import <SignalUtilitiesKit/TSGroupThread.h>
@import ContactsUI;
NS_ASSUME_NONNULL_BEGIN
@interface ShowGroupMembersViewController () <ContactsViewHelperDelegate, ContactEditingDelegate>
@property (nonatomic, readonly) TSGroupThread *thread;
@property (nonatomic, readonly) ContactsViewHelper *contactsViewHelper;
@property (nonatomic, nullable) NSSet<NSString *> *memberRecipientIds;
@end
#pragma mark -
@implementation ShowGroupMembersViewController
- (instancetype)init
{
self = [super init];
if (!self) {
return self;
}
[self commonInit];
return self;
}
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (!self) {
return self;
}
[self commonInit];
return self;
}
- (void)commonInit
{
_contactsViewHelper = [[ContactsViewHelper alloc] initWithDelegate:self];
self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedRowHeight = 60;
[self observeNotifications];
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)observeNotifications
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(identityStateDidChange:)
name:kNSNotificationName_IdentityStateDidChange
object:nil];
}
- (void)configWithThread:(TSGroupThread *)thread
{
_thread = thread;
OWSAssertDebug(self.thread);
OWSAssertDebug(self.thread.groupModel);
OWSAssertDebug(self.thread.groupModel.groupMemberIds);
self.memberRecipientIds = [NSSet setWithArray:self.thread.groupModel.groupMemberIds];
}
- (void)viewDidLoad
{
[super viewDidLoad];
OWSAssertDebug([self.navigationController isKindOfClass:[OWSNavigationController class]]);
self.title = _thread.groupModel.groupName;
self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedRowHeight = 45;
[self updateTableContents];
}
#pragma mark - Table Contents
- (void)updateTableContents
{
OWSAssertDebug(self.thread);
OWSTableContents *contents = [OWSTableContents new];
__weak ShowGroupMembersViewController *weakSelf = self;
ContactsViewHelper *helper = self.contactsViewHelper;
OWSTableSection *membersSection = [OWSTableSection new];
// Group Members
// If there are "no longer verified" members of the group,
// highlight them in a special section.
NSArray<NSString *> *noLongerVerifiedRecipientIds = [self noLongerVerifiedRecipientIds];
if (noLongerVerifiedRecipientIds.count > 0) {
OWSTableSection *noLongerVerifiedSection = [OWSTableSection new];
noLongerVerifiedSection.headerTitle = NSLocalizedString(@"GROUP_MEMBERS_SECTION_TITLE_NO_LONGER_VERIFIED",
@"Title for the 'no longer verified' section of the 'group members' view.");
membersSection.headerTitle = NSLocalizedString(
@"GROUP_MEMBERS_SECTION_TITLE_MEMBERS", @"Title for the 'members' section of the 'group members' view.");
[noLongerVerifiedSection
addItem:[OWSTableItem disclosureItemWithText:NSLocalizedString(@"GROUP_MEMBERS_RESET_NO_LONGER_VERIFIED",
@"Label for the button that clears all verification "
@"errors in the 'group members' view.")
accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME(self, @"no_longer_verified")
customRowHeight:UITableViewAutomaticDimension
actionBlock:^{
[weakSelf offerResetAllNoLongerVerified];
}]];
[self addMembers:noLongerVerifiedRecipientIds toSection:noLongerVerifiedSection useVerifyAction:YES];
[contents addSection:noLongerVerifiedSection];
}
NSMutableSet *memberRecipientIds = [self.memberRecipientIds mutableCopy];
[memberRecipientIds removeObject:[helper localNumber]];
[self addMembers:memberRecipientIds.allObjects toSection:membersSection useVerifyAction:NO];
[contents addSection:membersSection];
self.contents = contents;
}
- (void)addMembers:(NSArray<NSString *> *)recipientIds
toSection:(OWSTableSection *)section
useVerifyAction:(BOOL)useVerifyAction
{
OWSAssertDebug(recipientIds);
OWSAssertDebug(section);
__weak ShowGroupMembersViewController *weakSelf = self;
ContactsViewHelper *helper = self.contactsViewHelper;
// Sort the group members using contacts manager.
NSArray<NSString *> *sortedRecipientIds = [recipientIds sortedArrayUsingComparator:^NSComparisonResult(
NSString *recipientIdA, NSString *recipientIdB) {
SignalAccount *signalAccountA = [helper.contactsManager fetchOrBuildSignalAccountForRecipientId:recipientIdA];
SignalAccount *signalAccountB = [helper.contactsManager fetchOrBuildSignalAccountForRecipientId:recipientIdB];
return [helper.contactsManager compareSignalAccount:signalAccountA withSignalAccount:signalAccountB];
}];
for (NSString *recipientId in sortedRecipientIds) {
[section addItem:[OWSTableItem
itemWithCustomCellBlock:^{
ShowGroupMembersViewController *strongSelf = weakSelf;
OWSCAssertDebug(strongSelf);
ContactTableViewCell *cell = [ContactTableViewCell new];
OWSVerificationState verificationState =
[[OWSIdentityManager sharedManager] verificationStateForRecipientId:recipientId];
BOOL isVerified = verificationState == OWSVerificationStateVerified;
BOOL isNoLongerVerified = verificationState == OWSVerificationStateNoLongerVerified;
BOOL isBlocked = [helper isRecipientIdBlocked:recipientId];
if (isNoLongerVerified) {
cell.accessoryMessage = NSLocalizedString(@"CONTACT_CELL_IS_NO_LONGER_VERIFIED",
@"An indicator that a contact is no longer verified.");
} else if (isBlocked) {
cell.accessoryMessage = NSLocalizedString(
@"CONTACT_CELL_IS_BLOCKED", @"An indicator that a contact has been blocked.");
}
[cell configureWithRecipientId:recipientId];
if (isVerified) {
[cell setAttributedSubtitle:cell.verifiedSubtitle];
} else {
[cell setAttributedSubtitle:nil];
}
NSString *cellName = [NSString stringWithFormat:@"user.%@", recipientId];
cell.accessibilityIdentifier
= ACCESSIBILITY_IDENTIFIER_WITH_NAME(ShowGroupMembersViewController, cellName);
return cell;
}
customRowHeight:UITableViewAutomaticDimension
actionBlock:^{
if (useVerifyAction) {
[weakSelf showSafetyNumberView:recipientId];
} else {
[weakSelf didSelectRecipientId:recipientId];
}
}]];
}
}
- (void)offerResetAllNoLongerVerified
{
OWSAssertIsOnMainThread();
UIAlertController *actionSheet = [UIAlertController
alertControllerWithTitle:nil
message:NSLocalizedString(@"GROUP_MEMBERS_RESET_NO_LONGER_VERIFIED_ALERT_MESSAGE",
@"Label for the 'reset all no-longer-verified group members' confirmation alert.")
preferredStyle:UIAlertControllerStyleAlert];
__weak ShowGroupMembersViewController *weakSelf = self;
UIAlertAction *verifyAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"OK", nil)
accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME(self, @"ok")
style:UIAlertActionStyleDestructive
handler:^(UIAlertAction *_Nonnull action) {
[weakSelf resetAllNoLongerVerified];
}];
[actionSheet addAction:verifyAction];
[actionSheet addAction:[OWSAlerts cancelAction]];
[self presentAlert:actionSheet];
}
- (void)resetAllNoLongerVerified
{
OWSAssertIsOnMainThread();
OWSIdentityManager *identityManger = [OWSIdentityManager sharedManager];
NSArray<NSString *> *recipientIds = [self noLongerVerifiedRecipientIds];
for (NSString *recipientId in recipientIds) {
OWSVerificationState verificationState = [identityManger verificationStateForRecipientId:recipientId];
if (verificationState == OWSVerificationStateNoLongerVerified) {
NSData *identityKey = [identityManger identityKeyForRecipientId:recipientId];
if (identityKey.length < 1) {
OWSFailDebug(@"Missing identity key for: %@", recipientId);
continue;
}
[identityManger setVerificationState:OWSVerificationStateDefault
identityKey:identityKey
recipientId:recipientId
isUserInitiatedChange:YES];
}
}
[self updateTableContents];
}
// Returns a collection of the group members who are "no longer verified".
- (NSArray<NSString *> *)noLongerVerifiedRecipientIds
{
NSMutableArray<NSString *> *result = [NSMutableArray new];
for (NSString *recipientId in self.thread.recipientIdentifiers) {
if ([[OWSIdentityManager sharedManager] verificationStateForRecipientId:recipientId]
== OWSVerificationStateNoLongerVerified) {
[result addObject:recipientId];
}
}
return [result copy];
}
- (void)didSelectRecipientId:(NSString *)recipientId
{
OWSAssertDebug(recipientId.length > 0);
ContactsViewHelper *helper = self.contactsViewHelper;
SignalAccount *_Nullable signalAccount = [helper fetchSignalAccountForRecipientId:recipientId];
UIAlertController *actionSheet =
[UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet];
if (self.contactsViewHelper.contactsManager.supportsContactEditing) {
NSString *contactInfoTitle = signalAccount
? NSLocalizedString(@"GROUP_MEMBERS_VIEW_CONTACT_INFO", @"Button label for the 'show contact info' button")
: NSLocalizedString(
@"GROUP_MEMBERS_ADD_CONTACT_INFO", @"Button label to add information to an unknown contact");
[actionSheet
addAction:[UIAlertAction actionWithTitle:contactInfoTitle
accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME(self, @"show_contact_info")
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *_Nonnull action) {
[self showContactInfoViewForRecipientId:recipientId];
}]];
}
BOOL isBlocked;
if (signalAccount) {
isBlocked = [helper isRecipientIdBlocked:signalAccount.recipientId];
if (isBlocked) {
[actionSheet
addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"BLOCK_LIST_UNBLOCK_BUTTON",
@"Button label for the 'unblock' button")
accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME(self, @"unblock")
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *_Nonnull action) {
[BlockListUIUtils
showUnblockSignalAccountActionSheet:signalAccount
fromViewController:self
blockingManager:helper.blockingManager
contactsManager:helper.contactsManager
completionBlock:^(BOOL ignore) {
[self updateTableContents];
}];
}]];
} else {
[actionSheet
addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"BLOCK_LIST_BLOCK_BUTTON",
@"Button label for the 'block' button")
accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME(self, @"block")
style:UIAlertActionStyleDestructive
handler:^(UIAlertAction *_Nonnull action) {
[BlockListUIUtils
showBlockSignalAccountActionSheet:signalAccount
fromViewController:self
blockingManager:helper.blockingManager
contactsManager:helper.contactsManager
completionBlock:^(BOOL ignore) {
[self updateTableContents];
}];
}]];
}
} else {
isBlocked = [helper isRecipientIdBlocked:recipientId];
if (isBlocked) {
[actionSheet
addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"BLOCK_LIST_UNBLOCK_BUTTON",
@"Button label for the 'unblock' button")
accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME(self, @"unblock")
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *_Nonnull action) {
[BlockListUIUtils
showUnblockPhoneNumberActionSheet:recipientId
fromViewController:self
blockingManager:helper.blockingManager
contactsManager:helper.contactsManager
completionBlock:^(BOOL ignore) {
[self updateTableContents];
}];
}]];
} else {
[actionSheet
addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"BLOCK_LIST_BLOCK_BUTTON",
@"Button label for the 'block' button")
accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME(self, @"block")
style:UIAlertActionStyleDestructive
handler:^(UIAlertAction *_Nonnull action) {
[BlockListUIUtils
showBlockPhoneNumberActionSheet:recipientId
fromViewController:self
blockingManager:helper.blockingManager
contactsManager:helper.contactsManager
completionBlock:^(BOOL ignore) {
[self updateTableContents];
}];
}]];
}
}
if (!isBlocked) {
[actionSheet
addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"GROUP_MEMBERS_SEND_MESSAGE",
@"Button label for the 'send message to group member' button")
accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME(self, @"send_message")
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *_Nonnull action) {
[self showConversationViewForRecipientId:recipientId];
}]];
[actionSheet addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"GROUP_MEMBERS_CALL",
@"Button label for the 'call group member' button")
accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME(self, @"call")
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *_Nonnull action) {
[self callMember:recipientId];
}]];
[actionSheet
addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"VERIFY_PRIVACY",
@"Label for button or row which allows users to verify the "
@"safety number of another user.")
accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME(self, @"safety_numbers")
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *_Nonnull action) {
[self showSafetyNumberView:recipientId];
}]];
}
[actionSheet addAction:[OWSAlerts cancelAction]];
[self presentAlert:actionSheet];
}
- (void)showContactInfoViewForRecipientId:(NSString *)recipientId
{
OWSAssertDebug(recipientId.length > 0);
[self.contactsViewHelper presentContactViewControllerForRecipientId:recipientId
fromViewController:self
editImmediately:NO];
}
- (void)showConversationViewForRecipientId:(NSString *)recipientId
{
OWSAssertDebug(recipientId.length > 0);
[SignalApp.sharedApp presentConversationForRecipientId:recipientId
action:ConversationViewActionCompose
animated:YES];
}
- (void)callMember:(NSString *)recipientId
{
[SignalApp.sharedApp presentConversationForRecipientId:recipientId
action:ConversationViewActionAudioCall
animated:YES];
}
- (void)showSafetyNumberView:(NSString *)recipientId
{
OWSAssertDebug(recipientId.length > 0);
[FingerprintViewController presentFromViewController:self recipientId:recipientId];
}
#pragma mark - ContactsViewHelperDelegate
- (void)contactsViewHelperDidUpdateContacts
{
[self updateTableContents];
}
- (BOOL)shouldHideLocalNumber
{
return YES;
}
#pragma mark - ContactEditingDelegate
- (void)didFinishEditingContact
{
OWSLogDebug(@"");
[self dismissViewControllerAnimated:YES completion:nil];
}
#pragma mark - CNContactViewControllerDelegate
- (void)contactViewController:(CNContactViewController *)viewController
didCompleteWithContact:(nullable CNContact *)contact
{
OWSLogDebug(@"done editing contact.");
[self dismissViewControllerAnimated:YES completion:nil];
}
#pragma mark - Notifications
- (void)identityStateDidChange:(NSNotification *)notification
{
OWSAssertIsOnMainThread();
[self updateTableContents];
}
@end
NS_ASSUME_NONNULL_END

View File

@ -6,7 +6,6 @@
#import "AppDelegate.h"
#import "ConversationViewController.h"
#import "Session-Swift.h"
#import "SignalsNavigationController.h"
#import <SignalCoreKit/Threading.h>
#import <SignalUtilitiesKit/DebugLogger.h>
#import <SignalUtilitiesKit/Environment.h>
@ -178,8 +177,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)showHomeView
{
HomeVC *homeView = [HomeVC new];
SignalsNavigationController *navigationController =
[[SignalsNavigationController alloc] initWithRootViewController:homeView];
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:homeView];
AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
appDelegate.window.rootViewController = navigationController;
OWSAssertDebug([navigationController.topViewController isKindOfClass:[HomeVC class]]);

View File

@ -1,9 +0,0 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "OWSNavigationController.h"
@interface SignalsNavigationController : OWSNavigationController
@end

View File

@ -1,123 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "SignalsNavigationController.h"
#import "Session-Swift.h"
#import <SignalUtilitiesKit/UIUtil.h>
#import <SignalUtilitiesKit/NSTimer+OWS.h>
#import <SignalUtilitiesKit/OWSSignalService.h>
#import <SignalUtilitiesKit/TSSocketManager.h>
static double const STALLED_PROGRESS = 0.9;
@interface SignalsNavigationController ()
@property (nonatomic) UIProgressView *socketStatusView;
@property (nonatomic) NSTimer *updateStatusTimer;
@end
#pragma mark -
@implementation SignalsNavigationController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
[self initializeObserver];
[self updateSocketStatusView];
}
- (void)initializeSocketStatusBar {
if (!_socketStatusView) {
_socketStatusView = [[UIProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleDefault];
}
CGRect bar = self.navigationBar.frame;
_socketStatusView.frame = CGRectMake(0, bar.size.height - 1.0f, self.view.frame.size.width, 1.0f);
_socketStatusView.progress = 0.0f;
_socketStatusView.progressTintColor = [UIColor ows_fadedBlueColor];
/** Loki: Original code
if (![_socketStatusView superview]) {
[self.navigationBar addSubview:_socketStatusView];
}
*/
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
#pragma mark - Socket Status Notifications
- (void)initializeObserver {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(OWSWebSocketStateDidChange)
name:kNSNotification_OWSWebSocketStateDidChange
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(isCensorshipCircumventionActiveDidChange:)
name:kNSNotificationName_IsCensorshipCircumventionActiveDidChange
object:nil];
}
- (void)isCensorshipCircumventionActiveDidChange:(NSNotification *)notification
{
OWSAssertIsOnMainThread();
[self updateSocketStatusView];
}
- (void)OWSWebSocketStateDidChange
{
OWSAssertIsOnMainThread();
[self updateSocketStatusView];
}
- (void)updateSocketStatusView {
OWSAssertIsOnMainThread();
if ([OWSSignalService sharedInstance].isCensorshipCircumventionActive) {
[_updateStatusTimer invalidate];
[_socketStatusView removeFromSuperview];
_socketStatusView = nil;
return;
}
switch (TSSocketManager.shared.highestSocketState) {
case OWSWebSocketStateClosed:
if (_socketStatusView == nil) {
[self initializeSocketStatusBar];
[_updateStatusTimer invalidate];
_updateStatusTimer = [NSTimer weakScheduledTimerWithTimeInterval:0.5
target:self
selector:@selector(updateProgress)
userInfo:nil
repeats:YES];
} else if (_socketStatusView.progress >= STALLED_PROGRESS) {
[_updateStatusTimer invalidate];
}
break;
case OWSWebSocketStateConnecting:
// Do nothing.
break;
case OWSWebSocketStateOpen:
[_updateStatusTimer invalidate];
[_socketStatusView removeFromSuperview];
_socketStatusView = nil;
break;
}
}
- (void)updateProgress {
double progress = _socketStatusView.progress + 0.05;
_socketStatusView.progress = (float) MIN(progress, STALLED_PROGRESS);
}
@end

View File

@ -1,29 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "OWSConversationSettingsViewDelegate.h"
#import <SignalUtilitiesKit/OWSViewController.h>
NS_ASSUME_NONNULL_BEGIN
@class TSGroupThread;
typedef NS_ENUM(NSUInteger, UpdateGroupMode) {
UpdateGroupMode_Default = 0,
UpdateGroupMode_EditGroupName,
UpdateGroupMode_EditGroupAvatar,
};
@interface UpdateGroupViewController : OWSViewController
@property (nonatomic, weak) id<OWSConversationSettingsViewDelegate> conversationSettingsViewDelegate;
// This property _must_ be set before the view is presented.
@property (nonatomic) TSGroupThread *thread;
@property (nonatomic) UpdateGroupMode mode;
@end
NS_ASSUME_NONNULL_END

View File

@ -1,558 +0,0 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
#import "UpdateGroupViewController.h"
#import "AddToGroupViewController.h"
#import "AvatarViewHelper.h"
#import "OWSNavigationController.h"
#import "Session-Swift.h"
#import "ViewControllerUtils.h"
#import <SignalCoreKit/NSDate+OWS.h>
#import <SignalCoreKit/NSString+OWS.h>
#import <SignalUtilitiesKit/BlockListUIUtils.h>
#import <SignalUtilitiesKit/ContactTableViewCell.h>
#import <SignalUtilitiesKit/ContactsViewHelper.h>
#import <SignalUtilitiesKit/Environment.h>
#import <SignalUtilitiesKit/OWSContactsManager.h>
#import <SignalUtilitiesKit/OWSTableViewController.h>
#import <SignalUtilitiesKit/SignalKeyingStorage.h>
#import <SignalUtilitiesKit/UIUtil.h>
#import <SignalUtilitiesKit/UIView+OWS.h>
#import <SignalUtilitiesKit/UIViewController+OWS.h>
#import <SignalUtilitiesKit/NSString+SSK.h>
#import <SignalUtilitiesKit/OWSMessageSender.h>
#import <SignalUtilitiesKit/SignalAccount.h>
#import <SignalUtilitiesKit/TSGroupModel.h>
#import <SignalUtilitiesKit/TSGroupThread.h>
#import <SignalUtilitiesKit/TSOutgoingMessage.h>
NS_ASSUME_NONNULL_BEGIN
@interface UpdateGroupViewController () <UIImagePickerControllerDelegate,
UITextFieldDelegate,
ContactsViewHelperDelegate,
AvatarViewHelperDelegate,
AddToGroupViewControllerDelegate,
OWSTableViewControllerDelegate,
UINavigationControllerDelegate,
OWSNavigationView>
@property (nonatomic, readonly) OWSMessageSender *messageSender;
@property (nonatomic, readonly) ContactsViewHelper *contactsViewHelper;
@property (nonatomic, readonly) AvatarViewHelper *avatarViewHelper;
@property (nonatomic, readonly) OWSTableViewController *tableViewController;
@property (nonatomic, readonly) AvatarImageView *avatarView;
@property (nonatomic, readonly) UITextField *groupNameTextField;
@property (nonatomic, nullable) UIImage *groupAvatar;
@property (nonatomic, nullable) NSSet<NSString *> *previousMemberRecipientIds;
@property (nonatomic) NSMutableSet<NSString *> *memberRecipientIds;
@property (nonatomic) NSMutableSet<NSString *> *removedRecipientIds;
@property (nonatomic) BOOL hasUnsavedChanges;
@end
#pragma mark -
@implementation UpdateGroupViewController
- (instancetype)init
{
self = [super init];
if (!self) {
return self;
}
[self commonInit];
return self;
}
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (!self) {
return self;
}
[self commonInit];
return self;
}
- (void)commonInit
{
_messageSender = SSKEnvironment.shared.messageSender;
_contactsViewHelper = [[ContactsViewHelper alloc] initWithDelegate:self];
_avatarViewHelper = [AvatarViewHelper new];
_avatarViewHelper.delegate = self;
self.memberRecipientIds = [NSMutableSet new];
self.removedRecipientIds = [NSMutableSet new];
}
#pragma mark - View Lifecycle
- (void)loadView
{
[super loadView];
OWSAssertDebug(self.thread);
OWSAssertDebug(self.thread.groupModel);
OWSAssertDebug(self.thread.groupModel.groupMemberIds);
self.view.backgroundColor = Theme.backgroundColor;
[self.memberRecipientIds addObjectsFromArray:self.thread.groupModel.groupMemberIds];
self.previousMemberRecipientIds = [NSSet setWithArray:self.thread.groupModel.groupMemberIds];
self.title = NSLocalizedString(@"EDIT_GROUP_DEFAULT_TITLE", @"The navbar title for the 'update group' view.");
// First section.
UIView *firstSection = [self firstSectionHeader];
[self.view addSubview:firstSection];
[firstSection autoSetDimension:ALDimensionHeight toSize:100.f];
[firstSection autoPinWidthToSuperview];
[firstSection autoPinEdge:ALEdgeTop toEdge:ALEdgeTop ofView:self.view withOffset:0.0f];
_tableViewController = [OWSTableViewController new];
_tableViewController.delegate = self;
[self.view addSubview:self.tableViewController.view];
[self.tableViewController.view autoPinEdgeToSuperviewSafeArea:ALEdgeLeading];
[self.tableViewController.view autoPinEdgeToSuperviewSafeArea:ALEdgeTrailing];
[_tableViewController.view autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:firstSection];
[self autoPinViewToBottomOfViewControllerOrKeyboard:self.tableViewController.view avoidNotch:NO];
self.tableViewController.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableViewController.tableView.estimatedRowHeight = 60;
[self updateTableContents];
}
- (void)setHasUnsavedChanges:(BOOL)hasUnsavedChanges
{
_hasUnsavedChanges = hasUnsavedChanges;
[self updateNavigationBar];
}
- (void)updateNavigationBar
{
self.navigationItem.rightBarButtonItem = (self.hasUnsavedChanges
? [[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"EDIT_GROUP_UPDATE_BUTTON",
@"The title for the 'update group' button.")
style:UIBarButtonItemStylePlain
target:self
action:@selector(updateGroupPressed)
accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME(self, @"update")]
: nil);
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
switch (self.mode) {
case UpdateGroupMode_EditGroupName:
[self.groupNameTextField becomeFirstResponder];
break;
case UpdateGroupMode_EditGroupAvatar:
[self showChangeAvatarUI];
break;
default:
break;
}
// Only perform these actions the first time the view appears.
self.mode = UpdateGroupMode_Default;
}
- (UIView *)firstSectionHeader
{
OWSAssertDebug(self.thread);
OWSAssertDebug(self.thread.groupModel);
UIView *firstSectionHeader = [UIView new];
firstSectionHeader.userInteractionEnabled = YES;
[firstSectionHeader
addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(headerWasTapped:)]];
firstSectionHeader.backgroundColor = [Theme backgroundColor];
UIView *threadInfoView = [UIView new];
[firstSectionHeader addSubview:threadInfoView];
[threadInfoView autoPinWidthToSuperviewWithMargin:16.f];
[threadInfoView autoPinHeightToSuperviewWithMargin:16.f];
AvatarImageView *avatarView = [AvatarImageView new];
_avatarView = avatarView;
[threadInfoView addSubview:avatarView];
[avatarView autoVCenterInSuperview];
[avatarView autoPinLeadingToSuperviewMargin];
[avatarView autoSetDimension:ALDimensionWidth toSize:kLargeAvatarSize];
[avatarView autoSetDimension:ALDimensionHeight toSize:kLargeAvatarSize];
_groupAvatar = self.thread.groupModel.groupImage;
[self updateAvatarView];
UITextField *groupNameTextField = [OWSTextField new];
_groupNameTextField = groupNameTextField;
self.groupNameTextField.text = [self.thread.groupModel.groupName ows_stripped];
groupNameTextField.textColor = [Theme primaryColor];
groupNameTextField.font = [UIFont ows_dynamicTypeTitle2Font];
groupNameTextField.placeholder
= NSLocalizedString(@"NEW_GROUP_NAMEGROUP_REQUEST_DEFAULT", @"Placeholder text for group name field");
groupNameTextField.delegate = self;
[groupNameTextField addTarget:self
action:@selector(groupNameDidChange:)
forControlEvents:UIControlEventEditingChanged];
[threadInfoView addSubview:groupNameTextField];
[groupNameTextField autoVCenterInSuperview];
[groupNameTextField autoPinTrailingToSuperviewMargin];
[groupNameTextField autoPinLeadingToTrailingEdgeOfView:avatarView offset:16.f];
SET_SUBVIEW_ACCESSIBILITY_IDENTIFIER(self, groupNameTextField);
[avatarView
addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(avatarTouched:)]];
avatarView.userInteractionEnabled = YES;
SET_SUBVIEW_ACCESSIBILITY_IDENTIFIER(self, avatarView);
return firstSectionHeader;
}
- (void)headerWasTapped:(UIGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateRecognized) {
[self.groupNameTextField becomeFirstResponder];
}
}
- (void)avatarTouched:(UIGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateRecognized) {
[self showChangeAvatarUI];
}
}
#pragma mark - Table Contents
- (void)updateTableContents
{
OWSAssertDebug(self.thread);
OWSTableContents *contents = [OWSTableContents new];
__weak UpdateGroupViewController *weakSelf = self;
ContactsViewHelper *contactsViewHelper = self.contactsViewHelper;
// Group Members
OWSTableSection *section = [OWSTableSection new];
section.headerTitle = NSLocalizedString(
@"EDIT_GROUP_MEMBERS_SECTION_TITLE", @"a title for the members section of the 'new/update group' view.");
[section addItem:[OWSTableItem
disclosureItemWithText:NSLocalizedString(@"EDIT_GROUP_MEMBERS_ADD_MEMBER",
@"Label for the cell that lets you add a new member to a group.")
customRowHeight:UITableViewAutomaticDimension
actionBlock:^{
AddToGroupViewController *viewController = [AddToGroupViewController new];
viewController.addToGroupDelegate = weakSelf;
[weakSelf.navigationController pushViewController:viewController animated:YES];
}]];
NSMutableSet *memberRecipientIds = [self.memberRecipientIds mutableCopy];
[memberRecipientIds removeObject:[contactsViewHelper localNumber]];
for (NSString *recipientId in [memberRecipientIds.allObjects sortedArrayUsingSelector:@selector(compare:)]) {
[section
addItem:[OWSTableItem
itemWithCustomCellBlock:^{
UpdateGroupViewController *strongSelf = weakSelf;
OWSCAssertDebug(strongSelf);
ContactTableViewCell *cell = [ContactTableViewCell new];
BOOL isPreviousMember = [strongSelf.previousMemberRecipientIds containsObject:recipientId];
BOOL isBlocked = [contactsViewHelper isRecipientIdBlocked:recipientId];
if (isPreviousMember) {
if (isBlocked) {
cell.accessoryMessage = NSLocalizedString(
@"CONTACT_CELL_IS_BLOCKED", @"An indicator that a contact has been blocked.");
} else {
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
} else {
// In the "members" section, we label "new" members as such when editing an existing
// group.
//
// The only way a "new" member could be blocked is if we blocked them on a linked device
// while in this dialog. We don't need to worry about that edge case.
cell.accessoryMessage = NSLocalizedString(@"EDIT_GROUP_NEW_MEMBER_LABEL",
@"An indicator that a user is a new member of the group.");
}
[cell configureWithRecipientId:recipientId];
// TODO: Confirm with nancy if this will work.
NSString *cellName = [NSString stringWithFormat:@"member.%@", recipientId];
cell.accessibilityIdentifier
= ACCESSIBILITY_IDENTIFIER_WITH_NAME(UpdateGroupViewController, cellName);
return cell;
}
customRowHeight:UITableViewAutomaticDimension
actionBlock:^{
SignalAccount *_Nullable signalAccount =
[contactsViewHelper fetchSignalAccountForRecipientId:recipientId];
BOOL isPreviousMember = [weakSelf.previousMemberRecipientIds containsObject:recipientId];
BOOL isBlocked = [contactsViewHelper isRecipientIdBlocked:recipientId];
if (isPreviousMember) {
if (isBlocked) {
if (signalAccount) {
[weakSelf showUnblockAlertForSignalAccount:signalAccount];
} else {
[weakSelf showUnblockAlertForRecipientId:recipientId];
}
} else {
[OWSAlerts
showAlertWithTitle:
NSLocalizedString(@"UPDATE_GROUP_CANT_REMOVE_MEMBERS_ALERT_TITLE",
@"Title for alert indicating that group members can't be removed.")
message:NSLocalizedString(
@"UPDATE_GROUP_CANT_REMOVE_MEMBERS_ALERT_MESSAGE",
@"Title for alert indicating that group members can't "
@"be removed.")];
}
} else {
[weakSelf removeRecipientId:recipientId];
}
}]];
}
[contents addSection:section];
self.tableViewController.contents = contents;
}
- (void)showUnblockAlertForSignalAccount:(SignalAccount *)signalAccount
{
OWSAssertDebug(signalAccount);
__weak UpdateGroupViewController *weakSelf = self;
[BlockListUIUtils showUnblockSignalAccountActionSheet:signalAccount
fromViewController:self
blockingManager:self.contactsViewHelper.blockingManager
contactsManager:self.contactsViewHelper.contactsManager
completionBlock:^(BOOL isBlocked) {
if (!isBlocked) {
[weakSelf updateTableContents];
}
}];
}
- (void)showUnblockAlertForRecipientId:(NSString *)recipientId
{
OWSAssertDebug(recipientId.length > 0);
__weak UpdateGroupViewController *weakSelf = self;
[BlockListUIUtils showUnblockPhoneNumberActionSheet:recipientId
fromViewController:self
blockingManager:self.contactsViewHelper.blockingManager
contactsManager:self.contactsViewHelper.contactsManager
completionBlock:^(BOOL isBlocked) {
if (!isBlocked) {
[weakSelf updateTableContents];
}
}];
}
- (void)removeRecipientId:(NSString *)recipientId
{
OWSAssertDebug(recipientId.length > 0);
[self.memberRecipientIds removeObject:recipientId];
[self.removedRecipientIds addObject:recipientId];
[self updateTableContents];
}
#pragma mark - Methods
- (void)updateGroup
{
OWSAssertDebug(self.conversationSettingsViewDelegate);
NSString *groupName = [self.groupNameTextField.text ows_stripped];
TSGroupModel *groupModel = [[TSGroupModel alloc] initWithTitle:groupName
memberIds:self.memberRecipientIds.allObjects
image:self.groupAvatar
groupId:self.thread.groupModel.groupId
groupType:self.thread.groupModel.groupType
adminIds:self.thread.groupModel.groupAdminIds];
[self.conversationSettingsViewDelegate groupWasUpdated:groupModel];
}
#pragma mark - Group Avatar
- (void)showChangeAvatarUI
{
[self.groupNameTextField resignFirstResponder];
[self.avatarViewHelper showChangeAvatarUI];
}
- (void)setGroupAvatar:(nullable UIImage *)groupAvatar
{
OWSAssertIsOnMainThread();
_groupAvatar = groupAvatar;
self.hasUnsavedChanges = YES;
[self updateAvatarView];
}
- (void)updateAvatarView
{
UIImage *_Nullable groupAvatar = self.groupAvatar;
if (!groupAvatar) {
groupAvatar = [[[OWSGroupAvatarBuilder alloc] initWithThread:self.thread diameter:kLargeAvatarSize] build];
}
self.avatarView.image = groupAvatar;
}
#pragma mark - Event Handling
- (void)backButtonPressed
{
[self.groupNameTextField resignFirstResponder];
if (!self.hasUnsavedChanges) {
// If user made no changes, return to conversation settings view.
[self.navigationController popViewControllerAnimated:YES];
return;
}
UIAlertController *alert = [UIAlertController
alertControllerWithTitle:NSLocalizedString(@"EDIT_GROUP_VIEW_UNSAVED_CHANGES_TITLE",
@"The alert title if user tries to exit update group view without saving changes.")
message:
NSLocalizedString(@"EDIT_GROUP_VIEW_UNSAVED_CHANGES_MESSAGE",
@"The alert message if user tries to exit update group view without saving changes.")
preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ALERT_SAVE",
@"The label for the 'save' button in action sheets.")
accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME(self, @"save")
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) {
OWSAssertDebug(self.conversationSettingsViewDelegate);
[self updateGroup];
[self.conversationSettingsViewDelegate
popAllConversationSettingsViewsWithCompletion:nil];
}]];
[alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ALERT_DONT_SAVE",
@"The label for the 'don't save' button in action sheets.")
accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME(self, @"dont_save")
style:UIAlertActionStyleDestructive
handler:^(UIAlertAction *action) {
[self.navigationController popViewControllerAnimated:YES];
}]];
[self presentAlert:alert];
}
- (void)updateGroupPressed
{
OWSAssertDebug(self.conversationSettingsViewDelegate);
[self updateGroup];
[self.conversationSettingsViewDelegate popAllConversationSettingsViewsWithCompletion:nil];
}
- (void)groupNameDidChange:(id)sender
{
self.hasUnsavedChanges = YES;
}
#pragma mark - Text Field Delegate
- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
[self.groupNameTextField resignFirstResponder];
return NO;
}
#pragma mark - OWSTableViewControllerDelegate
- (void)tableViewWillBeginDragging
{
[self.groupNameTextField resignFirstResponder];
}
#pragma mark - ContactsViewHelperDelegate
- (void)contactsViewHelperDidUpdateContacts
{
[self updateTableContents];
}
- (BOOL)shouldHideLocalNumber
{
return YES;
}
#pragma mark - AvatarViewHelperDelegate
- (nullable NSString *)avatarActionSheetTitle
{
return NSLocalizedString(
@"NEW_GROUP_ADD_PHOTO_ACTION", @"Action Sheet title prompting the user for a group avatar");
}
- (void)avatarDidChange:(UIImage *)image
{
OWSAssertIsOnMainThread();
OWSAssertDebug(image);
self.groupAvatar = image;
}
- (UIViewController *)fromViewController
{
return self;
}
- (BOOL)hasClearAvatarAction
{
return NO;
}
#pragma mark - AddToGroupViewControllerDelegate
- (void)recipientIdWasAdded:(NSString *)recipientId
{
[self.memberRecipientIds addObject:recipientId];
self.hasUnsavedChanges = YES;
[self updateTableContents];
}
- (BOOL)isRecipientGroupMember:(NSString *)recipientId
{
OWSAssertDebug(recipientId.length > 0);
return [self.memberRecipientIds containsObject:recipientId];
}
#pragma mark - OWSNavigationView
- (BOOL)shouldCancelNavigationBack
{
BOOL result = self.hasUnsavedChanges;
if (result) {
[self backButtonPressed];
}
return result;
}
@end
NS_ASSUME_NONNULL_END

View File

@ -125,7 +125,7 @@ extension UserNotificationPresenterAdaptee: NotificationPresenterAdaptee {
let trigger: UNNotificationTrigger?
let checkForCancel = category == .incomingMessage
if checkForCancel && hasReceivedSyncMessageRecently {
if checkForCancel {
assert(userInfo[AppNotificationUserInfoKey.threadId] != nil)
trigger = UNTimeIntervalNotificationTrigger(timeInterval: kNotificationDelayForRemoteRead, repeats: false)
} else {

View File

@ -21,61 +21,8 @@ public class WebRTCCallMessageHandler: NSObject/*, OWSCallMessageHandler*/ {
// MARK: - Dependencies
private var messageSender : MessageSender
{
return SSKEnvironment.shared.messageSender
}
private var accountManager : AccountManager
{
return AppEnvironment.shared.accountManager
}
// private var callService : CallService
// {
// return AppEnvironment.shared.callService
// }
// MARK: - Call Handlers
// public func receivedOffer(_ offer: SSKProtoCallMessageOffer, from callerId: String) {
// AssertIsOnMainThread()
//
// let thread = TSContactThread.getOrCreateThread(contactId: callerId)
// self.callService.handleReceivedOffer(thread: thread, callId: offer.id, sessionDescription: offer.sessionDescription)
// }
//
// public func receivedAnswer(_ answer: SSKProtoCallMessageAnswer, from callerId: String) {
// AssertIsOnMainThread()
//
// let thread = TSContactThread.getOrCreateThread(contactId: callerId)
// self.callService.handleReceivedAnswer(thread: thread, callId: answer.id, sessionDescription: answer.sessionDescription)
// }
//
// public func receivedIceUpdate(_ iceUpdate: SSKProtoCallMessageIceUpdate, from callerId: String) {
// AssertIsOnMainThread()
//
// let thread = TSContactThread.getOrCreateThread(contactId: callerId)
//
// // Discrepency between our protobuf's sdpMlineIndex, which is unsigned,
// // while the RTC iOS API requires a signed int.
// let lineIndex = Int32(iceUpdate.sdpMlineIndex)
//
// self.callService.handleRemoteAddedIceCandidate(thread: thread, callId: iceUpdate.id, sdp: iceUpdate.sdp, lineIndex: lineIndex, mid: iceUpdate.sdpMid)
// }
//
// public func receivedHangup(_ hangup: SSKProtoCallMessageHangup, from callerId: String) {
// AssertIsOnMainThread()
//
// let thread = TSContactThread.getOrCreateThread(contactId: callerId)
// self.callService.handleRemoteHangup(thread: thread, callId: hangup.id)
// }
//
// public func receivedBusy(_ busy: SSKProtoCallMessageBusy, from callerId: String) {
// AssertIsOnMainThread()
//
// let thread = TSContactThread.getOrCreateThread(contactId: callerId)
// self.callService.handleRemoteBusy(thread: thread, callId: busy.id)
// }
}

View File

@ -9,7 +9,8 @@ public final class BackgroundPoller : NSObject {
@objc(pollWithCompletionHandler:)
public static func poll(completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
var promises: [Promise<Void>] = []
promises.append(AppEnvironment.shared.messageFetcherJob.run()) // FIXME: It'd be nicer to just use Poller directly
// TODO TODO TODO
// promises.append(AppEnvironment.shared.messageFetcherJob.run()) // FIXME: It'd be nicer to just use Poller directly
closedGroupPoller = ClosedGroupPoller()
promises.append(contentsOf: closedGroupPoller.pollOnce())
var openGroups: [String:OpenGroup] = [:]

View File

@ -24,7 +24,6 @@ class BaseVC : UIViewController {
override func viewDidLoad() {
setNeedsStatusBarAppearanceUpdate()
NotificationCenter.default.addObserver(self, selector: #selector(handleUnexpectedDeviceLinkRequestReceivedNotification(_:)), name: .unexpectedDeviceLinkRequestReceived, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(handleAppModeChangedNotification(_:)), name: .appModeChanged, object: nil)
}
@ -71,15 +70,6 @@ class BaseVC : UIViewController {
NotificationCenter.default.removeObserver(self)
}
@objc private func handleUnexpectedDeviceLinkRequestReceivedNotification(_ notification: Notification) {
guard DeviceLinkingUtilities.shouldShowUnexpectedDeviceLinkRequestReceivedAlert else { return }
DispatchQueue.main.async {
let alert = UIAlertController(title: "Device Link Request Received", message: "Open the device link screen by going to \"Settings\" > \"Devices\" > \"Link a Device\" to link your devices.", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
// TODO: Post an appModeChanged notification?
}

View File

@ -1,256 +0,0 @@
import NVActivityIndicatorView
@objc(LKDeviceLinkingModal)
final class DeviceLinkingModal : Modal, DeviceLinkingSessionDelegate {
private let mode: Mode
private let delegate: DeviceLinkingModalDelegate?
private var deviceLink: DeviceLink?
private var hasAuthorizedDeviceLink = false
// MARK: Types
enum Mode : String { case master, slave }
// MARK: Components
private lazy var spinner = NVActivityIndicatorView(frame: CGRect.zero, type: .circleStrokeSpin, color: Colors.text, padding: nil)
private lazy var qrCodeImageViewContainer: UIView = {
let result = UIView()
result.addSubview(qrCodeImageView)
qrCodeImageView.pin(.top, to: .top, of: result)
qrCodeImageView.pin(.bottom, to: .bottom, of: result)
qrCodeImageView.center(.horizontal, in: result)
return result
}()
private lazy var qrCodeImageView: UIImageView = {
let result = UIImageView()
result.contentMode = .scaleAspectFit
let size: CGFloat = 128
result.set(.width, to: size)
result.set(.height, to: size)
return result
}()
private lazy var titleLabel: UILabel = {
let result = UILabel()
result.textColor = Colors.text
result.font = .boldSystemFont(ofSize: Values.mediumFontSize)
result.numberOfLines = 0
result.lineBreakMode = .byWordWrapping
result.textAlignment = .center
return result
}()
private lazy var subtitleLabel: UILabel = {
let result = UILabel()
result.textColor = Colors.text
result.font = .systemFont(ofSize: Values.smallFontSize)
result.numberOfLines = 0
result.lineBreakMode = .byWordWrapping
result.textAlignment = .center
return result
}()
private lazy var mnemonicLabel: UILabel = {
let result = UILabel()
result.textColor = Colors.text
result.font = .systemFont(ofSize: Values.smallFontSize)
result.numberOfLines = 0
result.lineBreakMode = .byWordWrapping
result.textAlignment = .center
return result
}()
private lazy var buttonStackView: UIStackView = {
let result = UIStackView(arrangedSubviews: [ cancelButton, authorizeButton ])
result.axis = .horizontal
result.spacing = Values.mediumSpacing
result.distribution = .fillEqually
return result
}()
private lazy var authorizeButton: UIButton = {
let result = UIButton()
result.set(.height, to: Values.mediumButtonHeight)
result.layer.cornerRadius = Values.modalButtonCornerRadius
result.backgroundColor = Colors.accent
result.titleLabel!.font = .systemFont(ofSize: Values.smallFontSize)
result.setTitleColor(Colors.text, for: UIControl.State.normal)
result.setTitle(NSLocalizedString("modal_link_device_master_mode_authorize_button_title", comment: ""), for: UIControl.State.normal)
return result
}()
private lazy var mainStackView: UIStackView = {
let result = UIStackView(arrangedSubviews: [ titleLabel, subtitleLabel, mnemonicLabel, buttonStackView ])
result.spacing = Values.largeSpacing
result.axis = .vertical
return result
}()
// MARK: Lifecycle
init(mode: Mode, delegate: DeviceLinkingModalDelegate?) {
self.mode = mode
if mode == .slave {
guard delegate != nil else { preconditionFailure("Missing delegate for device linking modal in slave mode.") }
}
self.delegate = delegate
super.init(nibName: nil, bundle: nil)
}
@objc(initWithMode:delegate:)
convenience init(modeAsString: String, delegate: DeviceLinkingModalDelegate?) {
guard let mode = Mode(rawValue: modeAsString) else { preconditionFailure("Invalid mode: \(modeAsString).") }
self.init(mode: mode, delegate: delegate)
}
required init?(coder: NSCoder) { preconditionFailure() }
override init(nibName: String?, bundle: Bundle?) { preconditionFailure() }
override func viewDidLoad() {
super.viewDidLoad()
switch mode {
case .master: let _ = DeviceLinkingSession.startListeningForLinkingRequests(with: self)
case .slave: let _ = DeviceLinkingSession.startListeningForLinkingAuthorization(with: self)
}
}
override func populateContentView() {
switch mode {
case .master: mainStackView.insertArrangedSubview(qrCodeImageViewContainer, at: 0)
case .slave: mainStackView.insertArrangedSubview(spinner, at: 0)
}
contentView.addSubview(mainStackView)
switch mode {
case .master:
let hexEncodedPublicKey = getUserHexEncodedPublicKey()
qrCodeImageView.image = QRCode.generate(for: hexEncodedPublicKey, hasBackground: true)
case .slave:
spinner.set(.height, to: 64)
spinner.startAnimating()
}
titleLabel.text = {
switch mode {
case .master: return NSLocalizedString("modal_link_device_master_mode_title_1", comment: "")
case .slave: return NSLocalizedString("modal_link_device_slave_mode_title_1", comment: "")
}
}()
subtitleLabel.text = {
switch mode {
case .master: return NSLocalizedString("modal_link_device_master_mode_explanation_1", comment: "")
case .slave: return NSLocalizedString("modal_link_device_slave_mode_explanation_1", comment: "")
}
}()
mnemonicLabel.isHidden = (mode == .master)
if mode == .slave {
let hexEncodedPublicKey = getUserHexEncodedPublicKey().removing05PrefixIfNeeded()
mnemonicLabel.text = Mnemonic.hash(hexEncodedString: hexEncodedPublicKey)
}
authorizeButton.addTarget(self, action: #selector(authorizeDeviceLink), for: UIControl.Event.touchUpInside)
authorizeButton.isHidden = true
mainStackView.pin(.leading, to: .leading, of: contentView, withInset: Values.largeSpacing)
mainStackView.pin(.top, to: .top, of: contentView, withInset: Values.largeSpacing)
contentView.pin(.trailing, to: .trailing, of: mainStackView, withInset: Values.largeSpacing)
contentView.pin(.bottom, to: .bottom, of: mainStackView, withInset: Values.largeSpacing)
}
// MARK: Device Linking
func requestUserAuthorization(for deviceLink: DeviceLink) {
self.deviceLink = deviceLink
qrCodeImageViewContainer.isHidden = true
titleLabel.text = NSLocalizedString("modal_link_device_master_mode_title_2", comment: "")
subtitleLabel.text = NSLocalizedString("modal_link_device_master_mode_explanation_2", comment: "")
let hexEncodedPublicKey = deviceLink.slave.publicKey.removing05PrefixIfNeeded()
mnemonicLabel.text = Mnemonic.hash(hexEncodedString: hexEncodedPublicKey)
mnemonicLabel.isHidden = false
authorizeButton.isHidden = false
}
@objc private func authorizeDeviceLink() {
guard !hasAuthorizedDeviceLink else { return }
hasAuthorizedDeviceLink = true
mainStackView.removeArrangedSubview(qrCodeImageViewContainer)
mainStackView.insertArrangedSubview(spinner, at: 0)
spinner.set(.height, to: 64)
spinner.startAnimating()
titleLabel.text = NSLocalizedString("modal_link_device_master_mode_title_3", comment: "")
subtitleLabel.text = NSLocalizedString("modal_link_device_master_mode_explanation_3", comment: "")
mnemonicLabel.isHidden = true
buttonStackView.isHidden = true
let deviceLink = self.deviceLink!
DeviceLinkingSession.current!.markLinkingRequestAsProcessed()
DeviceLinkingSession.current!.stopListeningForLinkingRequests()
let linkingAuthorizationMessage = DeviceLinkingUtilities.getLinkingAuthorizationMessage(for: deviceLink)
let master = DeviceLink.Device(publicKey: deviceLink.master.publicKey, signature: linkingAuthorizationMessage.masterSignature)
let signedDeviceLink = DeviceLink(between: master, and: deviceLink.slave)
FileServerAPI.addDeviceLink(signedDeviceLink).done(on: DispatchQueue.main) { [weak self] in
SSKEnvironment.shared.messageSender.send(linkingAuthorizationMessage, success: {
let slavePublicKey = deviceLink.slave.publicKey
Storage.writeSync { transaction in
let thread = TSContactThread.getOrCreateThread(withContactId: slavePublicKey, transaction: transaction)
thread.save(with: transaction)
}
let _ = SSKEnvironment.shared.syncManager.syncAllGroups().ensure {
// Closed groups first because we prefer the session request mechanism
// to the AFR mechanism
let _ = SSKEnvironment.shared.syncManager.syncAllContacts()
}
let _ = SSKEnvironment.shared.syncManager.syncAllOpenGroups()
DispatchQueue.main.async {
self?.dismiss(animated: true, completion: nil)
self?.delegate?.handleDeviceLinkAuthorized(signedDeviceLink)
}
}, failure: { error in
print("[Loki] Failed to send device link authorization message.")
let _ = FileServerAPI.removeDeviceLink(signedDeviceLink) // Attempt to roll back
DispatchQueue.main.async {
self?.close()
let alert = UIAlertController(title: "Device Linking Failed", message: "Please check your internet connection and try again", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default, handler: nil))
self?.presentingViewController?.present(alert, animated: true, completion: nil)
}
})
}.catch { [weak self] error in
print("[Loki] Failed to add device link due to error: \(error).")
DispatchQueue.main.async {
self?.close() // TODO: Show a message to the user
let alert = UIAlertController(title: "Device Linking Failed", message: "Please check your internet connection and try again", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default, handler: nil))
self?.presentingViewController?.present(alert, animated: true, completion: nil)
}
}
}
func handleDeviceLinkAuthorized(_ deviceLink: DeviceLink) {
let session = DeviceLinkingSession.current!
session.stopListeningForLinkingAuthorization()
spinner.stopAnimating()
spinner.isHidden = true
titleLabel.text = NSLocalizedString("modal_link_device_slave_mode_title_2", comment: "")
subtitleLabel.text = NSLocalizedString("modal_link_device_slave_mode_explanation_2", comment: "")
mnemonicLabel.isHidden = true
buttonStackView.isHidden = true
FileServerAPI.addDeviceLink(deviceLink).catch { error in
print("[Loki] Failed to add device link due to error: \(error).")
}
Timer.scheduledTimer(withTimeInterval: 2, repeats: false) { _ in
self.dismiss(animated: true) {
self.delegate?.handleDeviceLinkAuthorized(deviceLink)
}
}
}
@objc override func close() {
guard let session = DeviceLinkingSession.current else {
return print("[Loki] Device linking session missing.") // Should never occur
}
session.stopListeningForLinkingRequests()
session.markLinkingRequestAsProcessed() // Only relevant in master mode
delegate?.handleDeviceLinkingModalDismissed() // Only relevant in slave mode
if let deviceLink = deviceLink {
Storage.writeSync { transaction in
OWSPrimaryStorage.shared().removePreKeyBundle(forContact: deviceLink.slave.publicKey, transaction: transaction)
}
}
dismiss(animated: true, completion: nil)
}
}

View File

@ -1,12 +0,0 @@
@objc protocol DeviceLinkingModalDelegate {
func handleDeviceLinkAuthorized(_ deviceLink: DeviceLink)
func handleDeviceLinkingModalDismissed()
}
extension DeviceLinkingModalDelegate {
func handleDeviceLinkAuthorized(_ deviceLink: DeviceLink) { /* Do nothing */ }
func handleDeviceLinkingModalDismissed() { /* Do nothing */ }
}

View File

@ -1,234 +0,0 @@
// MARK: - Device Links View Controller
@objc(LKDeviceLinksVC)
final class DeviceLinksVC : BaseVC, UITableViewDataSource, UITableViewDelegate, DeviceLinkingModalDelegate, DeviceNameModalDelegate {
private var deviceLinks: [DeviceLink] = [] { didSet { updateUI() } }
// MARK: Components
private lazy var tableView: UITableView = {
let result = UITableView()
result.dataSource = self
result.delegate = self
result.register(Cell.self, forCellReuseIdentifier: "Cell")
result.separatorStyle = .none
result.backgroundColor = .clear
return result
}()
private lazy var callToActionView : UIStackView = {
let explanationLabel = UILabel()
explanationLabel.textColor = Colors.text
explanationLabel.font = .systemFont(ofSize: Values.smallFontSize)
explanationLabel.numberOfLines = 0
explanationLabel.lineBreakMode = .byWordWrapping
explanationLabel.textAlignment = .center
explanationLabel.text = NSLocalizedString("vc_linked_devices_empty_state_message", comment: "")
let linkNewDeviceButton = Button(style: .prominentOutline, size: .large)
linkNewDeviceButton.setTitle(NSLocalizedString("vc_linked_devices_empty_state_button_title", comment: ""), for: UIControl.State.normal)
linkNewDeviceButton.addTarget(self, action: #selector(linkNewDevice), for: UIControl.Event.touchUpInside)
linkNewDeviceButton.set(.width, to: 196)
let result = UIStackView(arrangedSubviews: [ explanationLabel, linkNewDeviceButton ])
result.axis = .vertical
result.spacing = Values.mediumSpacing
result.alignment = .center
return result
}()
// MARK: Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
setUpGradientBackground()
setUpNavBarStyle()
setNavBarTitle(NSLocalizedString("vc_linked_devices_title", comment: ""))
// Set up link new device button
let linkNewDeviceButton = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(linkNewDevice))
linkNewDeviceButton.tintColor = Colors.text
navigationItem.rightBarButtonItem = linkNewDeviceButton
// Set up constraints
view.addSubview(tableView)
tableView.pin(to: view)
view.addSubview(callToActionView)
callToActionView.center(.horizontal, in: view)
let verticalCenteringConstraint = callToActionView.center(.vertical, in: view)
verticalCenteringConstraint.constant = -16 // Makes things appear centered visually
// Perform initial update
updateDeviceLinks()
}
// MARK: Data
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return deviceLinks.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! Cell
let selectedBackgroundView = UIView()
selectedBackgroundView.backgroundColor = Colors.cellSelected
cell.selectedBackgroundView = selectedBackgroundView
let device = deviceLinks[indexPath.row].other
cell.device = device
return cell
}
// MARK: Updating
private func updateDeviceLinks() {
let storage = OWSPrimaryStorage.shared()
let userHexEncodedPublicKey = getUserHexEncodedPublicKey()
var deviceLinks: [DeviceLink] = []
storage.dbReadConnection.read { transaction in
deviceLinks = storage.getDeviceLinks(for: userHexEncodedPublicKey, in: transaction).sorted { lhs, rhs in
return lhs.other.publicKey > rhs.other.publicKey
}
}
self.deviceLinks = deviceLinks
}
private func updateUI() {
tableView.reloadData()
UIView.animate(withDuration: 0.25) {
self.callToActionView.isHidden = !self.deviceLinks.isEmpty
}
}
func handleDeviceLinkAuthorized(_ deviceLink: DeviceLink) {
// The modal already dismisses itself
updateDeviceLinks()
}
func handleDeviceLinkingModalDismissed() {
// Do nothing
}
// MARK: Interaction
@objc private func linkNewDevice() {
if deviceLinks.isEmpty {
let deviceLinkingModal = DeviceLinkingModal(mode: .master, delegate: self)
deviceLinkingModal.modalPresentationStyle = .overFullScreen
deviceLinkingModal.modalTransitionStyle = .crossDissolve
present(deviceLinkingModal, animated: true, completion: nil)
} else {
let alert = UIAlertController(title: NSLocalizedString("vc_linked_devices_multi_device_limit_reached_modal_title", comment: ""), message: NSLocalizedString("It's currently not allowed to link more than one device.", comment: ""), preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default, handler: nil))
present(alert, animated: true, completion: nil)
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
defer { tableView.deselectRow(at: indexPath, animated: true) }
let deviceLink = deviceLinks[indexPath.row]
let sheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
sheet.addAction(UIAlertAction(title: NSLocalizedString("vc_device_list_bottom_sheet_change_name_button_title", comment: ""), style: .default) { [weak self] _ in
guard let self = self else { return }
let deviceNameModal = DeviceNameModal()
deviceNameModal.device = deviceLink.other
deviceNameModal.delegate = self
deviceNameModal.modalPresentationStyle = .overFullScreen
deviceNameModal.modalTransitionStyle = .crossDissolve
self.present(deviceNameModal, animated: true, completion: nil)
})
sheet.addAction(UIAlertAction(title: NSLocalizedString("vc_device_list_bottom_sheet_unlink_device_button_title", comment: ""), style: .destructive) { [weak self] _ in
self?.removeDeviceLink(deviceLink)
})
sheet.addAction(UIAlertAction(title: NSLocalizedString("cancel", comment: ""), style: .cancel) { _ in })
present(sheet, animated: true, completion: nil)
}
@objc func handleDeviceNameChanged(to name: String, for device: DeviceLink.Device) {
dismiss(animated: true, completion: nil)
updateUI()
}
private func removeDeviceLink(_ deviceLink: DeviceLink) {
FileServerAPI.removeDeviceLink(deviceLink).done { [weak self] in
let linkedDevicePublicKey = deviceLink.other.publicKey
guard let thread = TSContactThread.fetch(uniqueId: TSContactThread.threadId(fromContactId: linkedDevicePublicKey)) else { return }
let unlinkDeviceMessage = UnlinkDeviceMessage(thread: thread)
SSKEnvironment.shared.messageSender.send(unlinkDeviceMessage, success: {
let storage = OWSPrimaryStorage.shared()
Storage.writeSync { transaction in
storage.removePreKeyBundle(forContact: linkedDevicePublicKey, transaction: transaction)
storage.deleteAllSessions(forContact: linkedDevicePublicKey, protocolContext: transaction)
for groupPublicKey in Storage.getUserClosedGroupPublicKeys() {
// TODO: Possibly re-implement in the future
// ClosedGroupsProtocol.removeMembers([ linkedDevicePublicKey ], from: groupPublicKey, using: transaction)
}
}
}, failure: { _ in
print("[Loki] Failed to send unlink device message.")
let storage = OWSPrimaryStorage.shared()
Storage.writeSync { transaction in
storage.removePreKeyBundle(forContact: linkedDevicePublicKey, transaction: transaction)
storage.deleteAllSessions(forContact: linkedDevicePublicKey, protocolContext: transaction)
for groupPublicKey in Storage.getUserClosedGroupPublicKeys() {
// TODO: Possibly re-implement in the future
// ClosedGroupsProtocol.removeMembers([ linkedDevicePublicKey ], from: groupPublicKey, using: transaction)
}
}
})
self?.updateDeviceLinks()
}.catch { [weak self] _ in
let alert = UIAlertController(title: "Couldn't Unlink Device", message: "Please check your internet connection and try again", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), accessibilityIdentifier: nil, style: .default, handler: nil))
self?.present(alert, animated: true, completion: nil)
}
}
}
// MARK: - Cell
private extension DeviceLinksVC {
final class Cell : UITableViewCell {
var device: DeviceLink.Device! { didSet { update() } }
// MARK: Components
private lazy var titleLabel: UILabel = {
let result = UILabel()
result.textColor = Colors.text
result.font = .boldSystemFont(ofSize: Values.mediumFontSize)
result.lineBreakMode = .byTruncatingTail
return result
}()
private lazy var subtitleLabel: UILabel = {
let result = UILabel()
result.textColor = Colors.text
result.font = .systemFont(ofSize: Values.smallFontSize)
result.lineBreakMode = .byTruncatingTail
return result
}()
// MARK: Initialization
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setUpViewHierarchy()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setUpViewHierarchy()
}
private func setUpViewHierarchy() {
backgroundColor = Colors.cellBackground
let stackView = UIStackView(arrangedSubviews: [ titleLabel, subtitleLabel ])
stackView.axis = .vertical
stackView.distribution = .equalCentering
stackView.spacing = Values.verySmallSpacing
stackView.set(.height, to: 44)
contentView.addSubview(stackView)
stackView.pin(.leading, to: .leading, of: contentView, withInset: Values.largeSpacing)
stackView.pin(.top, to: .top, of: contentView, withInset: 12)
contentView.pin(.trailing, to: .trailing, of: stackView, withInset: Values.largeSpacing)
contentView.pin(.bottom, to: .bottom, of: stackView, withInset: 12)
stackView.set(.width, to: UIScreen.main.bounds.width - 2 * Values.largeSpacing)
}
// MARK: Updating
private func update() {
titleLabel.text = device.displayName
subtitleLabel.text = Mnemonic.hash(hexEncodedString: device.publicKey.removing05PrefixIfNeeded())
}
}
}

View File

@ -1,103 +0,0 @@
@objc(LKDeviceNameModal)
final class DeviceNameModal : Modal {
@objc public var device: DeviceLink.Device!
@objc public var delegate: DeviceNameModalDelegate?
// MARK: Components
private lazy var nameTextField: UITextField = {
let result = UITextField()
result.textColor = Colors.text
result.font = .systemFont(ofSize: Values.mediumFontSize)
result.textAlignment = .center
let placeholder = NSMutableAttributedString(string: NSLocalizedString("modal_edit_device_name_text_field_hint", comment: ""))
placeholder.addAttribute(.foregroundColor, value: Colors.text.withAlphaComponent(Values.unimportantElementOpacity), range: NSRange(location: 0, length: placeholder.length))
result.attributedPlaceholder = placeholder
result.tintColor = Colors.accent
result.keyboardAppearance = .dark
return result
}()
// MARK: Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self, selector: #selector(handleKeyboardWillChangeFrameNotification(_:)), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
notificationCenter.addObserver(self, selector: #selector(handleKeyboardWillHideNotification(_:)), name: UIResponder.keyboardWillHideNotification, object: nil)
}
override func populateContentView() {
// Set up title label
let titleLabel = UILabel()
titleLabel.textColor = Colors.text
titleLabel.font = .boldSystemFont(ofSize: Values.mediumFontSize)
titleLabel.text = "Change Device Name"
titleLabel.numberOfLines = 0
titleLabel.lineBreakMode = .byWordWrapping
titleLabel.textAlignment = .center
// Set up explanation label
let explanationLabel = UILabel()
explanationLabel.textColor = Colors.text
explanationLabel.font = .systemFont(ofSize: Values.smallFontSize)
explanationLabel.text = "Enter the new display name for your device below"
explanationLabel.numberOfLines = 0
explanationLabel.textAlignment = .center
explanationLabel.lineBreakMode = .byWordWrapping
// Set up OK button
let okButton = UIButton()
okButton.set(.height, to: Values.mediumButtonHeight)
okButton.layer.cornerRadius = Values.modalButtonCornerRadius
okButton.backgroundColor = Colors.accent
okButton.titleLabel!.font = .systemFont(ofSize: Values.smallFontSize)
okButton.setTitleColor(Colors.text, for: UIControl.State.normal)
okButton.setTitle(NSLocalizedString("OK", comment: ""), for: UIControl.State.normal)
okButton.addTarget(self, action: #selector(changeName), for: UIControl.Event.touchUpInside)
// Set up button stack view
let buttonStackView = UIStackView(arrangedSubviews: [ cancelButton, okButton ])
buttonStackView.axis = .horizontal
buttonStackView.spacing = Values.mediumSpacing
buttonStackView.distribution = .fillEqually
// Set up main stack view
let stackView = UIStackView(arrangedSubviews: [ titleLabel, explanationLabel, nameTextField, buttonStackView ])
stackView.axis = .vertical
stackView.spacing = Values.largeSpacing
contentView.addSubview(stackView)
stackView.pin(.leading, to: .leading, of: contentView, withInset: Values.largeSpacing)
stackView.pin(.top, to: .top, of: contentView, withInset: Values.largeSpacing)
contentView.pin(.trailing, to: .trailing, of: stackView, withInset: Values.largeSpacing)
contentView.pin(.bottom, to: .bottom, of: stackView, withInset: Values.largeSpacing)
}
deinit {
NotificationCenter.default.removeObserver(self)
}
// MARK: Updating
@objc private func handleKeyboardWillChangeFrameNotification(_ notification: Notification) {
guard let newHeight = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue.size.height else { return }
verticalCenteringConstraint.constant = -(newHeight / 2)
UIView.animate(withDuration: 0.25) {
self.view.layoutIfNeeded()
}
}
@objc private func handleKeyboardWillHideNotification(_ notification: Notification) {
verticalCenteringConstraint.constant = 0
UIView.animate(withDuration: 0.25) {
self.view.layoutIfNeeded()
}
}
// MARK: Interaction
@objc private func changeName() {
let name = nameTextField.text!.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
if !name.isEmpty {
UserDefaults.standard[.slaveDeviceName(device.publicKey)] = name
delegate?.handleDeviceNameChanged(to: name, for: device)
} else {
let alert = UIAlertController(title: "Error", message: "Please pick a name", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), accessibilityIdentifier: nil, style: .default, handler: nil))
present(alert, animated: true, completion: nil)
}
}
}

View File

@ -1,5 +0,0 @@
@objc protocol DeviceNameModalDelegate {
func handleDeviceNameChanged(to name: String, for device: DeviceLink.Device)
}

View File

@ -96,8 +96,7 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, UIScrol
// Set up seed reminder view if needed
let userDefaults = UserDefaults.standard
let hasViewedSeed = userDefaults[.hasViewedSeed]
let isMasterDevice = userDefaults.isMasterDevice
if !hasViewedSeed && isMasterDevice {
if !hasViewedSeed {
view.addSubview(seedReminderView)
seedReminderView.pin(.leading, to: .leading, of: view)
seedReminderView.pin(.top, to: .top, of: view)
@ -108,7 +107,7 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, UIScrol
tableView.delegate = self
view.addSubview(tableView)
tableView.pin(.leading, to: .leading, of: view)
if !hasViewedSeed && isMasterDevice {
if !hasViewedSeed {
tableViewTopConstraint = tableView.pin(.top, to: .bottom, of: seedReminderView)
} else {
tableViewTopConstraint = tableView.pin(.top, to: .top, of: view, withInset: Values.smallSpacing)
@ -283,13 +282,7 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, UIScrol
let profilePictureSize = Values.verySmallProfilePictureSize
let profilePictureView = ProfilePictureView()
profilePictureView.size = profilePictureSize
let userHexEncodedPublicKey: String
if let masterHexEncodedPublicKey = UserDefaults.standard[.masterHexEncodedPublicKey] {
userHexEncodedPublicKey = masterHexEncodedPublicKey
} else {
userHexEncodedPublicKey = getUserHexEncodedPublicKey()
}
profilePictureView.hexEncodedPublicKey = userHexEncodedPublicKey
profilePictureView.hexEncodedPublicKey = getUserHexEncodedPublicKey()
profilePictureView.update()
profilePictureView.set(.width, to: profilePictureSize)
profilePictureView.set(.height, to: profilePictureSize)
@ -413,10 +406,7 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, UIScrol
}
delete.backgroundColor = Colors.destructive
if thread is TSContactThread {
var publicKey: String!
Storage.read { transaction in
publicKey = OWSPrimaryStorage.shared().getMasterHexEncodedPublicKey(for: thread.contactIdentifier()!, in: transaction) ?? thread.contactIdentifier()!
}
let publicKey = thread.contactIdentifier()!
let blockingManager = SSKEnvironment.shared.blockingManager
let isBlocked = blockingManager.isRecipientIdBlocked(publicKey)
let block = UITableViewRowAction(style: .normal, title: NSLocalizedString("BLOCK_LIST_BLOCK_BUTTON", comment: "")) { _, _ in

View File

@ -133,7 +133,7 @@ final class JoinPublicChatVC : BaseVC, UIPageViewControllerDataSource, UIPageVie
isJoining = true
let channelID: UInt64 = 1
let urlAsString = url.absoluteString
let userPublicKey = UserDefaults.standard[.masterHexEncodedPublicKey] ?? getUserHexEncodedPublicKey()
let userPublicKey = getUserHexEncodedPublicKey()
let profileManager = OWSProfileManager.shared()
let displayName = profileManager.profileNameForRecipient(withID: userPublicKey)
let profilePictureURL = profileManager.profilePictureURL()
@ -148,8 +148,6 @@ final class JoinPublicChatVC : BaseVC, UIPageViewControllerDataSource, UIPageVie
let _ = OpenGroupAPI.setDisplayName(to: displayName, on: urlAsString)
let _ = OpenGroupAPI.setProfilePictureURL(to: profilePictureURL, using: profileKey, on: urlAsString)
let _ = OpenGroupAPI.join(channelID, on: urlAsString)
let syncManager = SSKEnvironment.shared.syncManager
let _ = syncManager.syncAllOpenGroups()
self?.presentingViewController!.dismiss(animated: true, completion: nil)
}
.catch(on: DispatchQueue.main) { [weak self] error in

View File

@ -1,5 +1,5 @@
final class LandingVC : BaseVC, LinkDeviceVCDelegate, DeviceLinkingModalDelegate {
final class LandingVC : BaseVC {
private var fakeChatViewContentOffset: CGPoint!
// MARK: Components
@ -25,14 +25,6 @@ final class LandingVC : BaseVC, LinkDeviceVCDelegate, DeviceLinkingModalDelegate
return result
}()
private lazy var linkButton: Button = {
let result = Button(style: .regularBorderless, size: .small)
result.setTitle(NSLocalizedString("vc_landing_link_button_title", comment: ""), for: UIControl.State.normal)
result.titleLabel!.font = .systemFont(ofSize: Values.smallFontSize)
result.addTarget(self, action: #selector(linkDevice), for: UIControl.Event.touchUpInside)
return result
}()
// MARK: Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
@ -59,11 +51,6 @@ final class LandingVC : BaseVC, LinkDeviceVCDelegate, DeviceLinkingModalDelegate
// Set up link button container
let linkButtonContainer = UIView()
linkButtonContainer.set(.height, to: Values.onboardingButtonBottomOffset)
// linkButtonContainer.addSubview(linkButton)
// linkButton.pin(.leading, to: .leading, of: linkButtonContainer, withInset: Values.massiveSpacing)
// linkButton.pin(.top, to: .top, of: linkButtonContainer)
// linkButtonContainer.pin(.trailing, to: .trailing, of: linkButton, withInset: Values.massiveSpacing)
// linkButtonContainer.pin(.bottom, to: .bottom, of: linkButton, withInset: isIPhone5OrSmaller ? 6 : 10)
// Set up button stack view
let buttonStackView = UIStackView(arrangedSubviews: [ registerButton, restoreButton ])
buttonStackView.axis = .vertical
@ -83,13 +70,6 @@ final class LandingVC : BaseVC, LinkDeviceVCDelegate, DeviceLinkingModalDelegate
view.addSubview(mainStackView)
mainStackView.pin(to: view)
topSpacer.heightAnchor.constraint(equalTo: bottomSpacer.heightAnchor, multiplier: 1).isActive = true
// Show device unlinked alert if needed
if UserDefaults.standard[.wasUnlinked] {
let alert = UIAlertController(title: "Device Unlinked", message: NSLocalizedString("vc_landing_device_unlinked_modal_title", comment: ""), preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), accessibilityIdentifier: nil, style: .default, handler: nil))
present(alert, animated: true, completion: nil)
UserDefaults.removeAll()
}
}
override func viewDidDisappear(_ animated: Bool) {
@ -118,58 +98,9 @@ final class LandingVC : BaseVC, LinkDeviceVCDelegate, DeviceLinkingModalDelegate
navigationController!.pushViewController(restoreVC, animated: true)
}
@objc private func linkDevice() {
let linkDeviceVC = LinkDeviceVC()
linkDeviceVC.delegate = self
let navigationController = OWSNavigationController(rootViewController: linkDeviceVC)
present(navigationController, animated: true, completion: nil)
}
// MARK: Device Linking
func requestDeviceLink(with hexEncodedPublicKey: String) {
guard ECKeyPair.isValidHexEncodedPublicKey(candidate: hexEncodedPublicKey) else {
let alert = UIAlertController(title: NSLocalizedString("invalid_session_id", comment: ""), message: "Please make sure the Session ID you entered is correct and try again.", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), accessibilityIdentifier: nil, style: .default, handler: nil))
return present(alert, animated: true, completion: nil)
}
let seed = Randomness.generateRandomBytes(16)
preconditionFailure("This code path shouldn't be invoked.")
let keyPair = Curve25519.generateKeyPair()
let identityManager = OWSIdentityManager.shared()
let databaseConnection = identityManager.value(forKey: "dbConnection") as! YapDatabaseConnection
databaseConnection.setObject(seed.toHexString(), forKey: "LKLokiSeed", inCollection: OWSPrimaryStorageIdentityKeyStoreCollection)
databaseConnection.setObject(keyPair, forKey: OWSPrimaryStorageIdentityKeyStoreIdentityKey, inCollection: OWSPrimaryStorageIdentityKeyStoreCollection)
TSAccountManager.sharedInstance().phoneNumberAwaitingVerification = keyPair.hexEncodedPublicKey
TSAccountManager.sharedInstance().didRegister()
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.startPollerIfNeeded()
let deviceLinkingModal = DeviceLinkingModal(mode: .slave, delegate: self)
deviceLinkingModal.modalPresentationStyle = .overFullScreen
deviceLinkingModal.modalTransitionStyle = .crossDissolve
self.present(deviceLinkingModal, animated: true, completion: nil)
let linkingRequestMessage = DeviceLinkingUtilities.getLinkingRequestMessage(for: hexEncodedPublicKey)
ThreadUtil.enqueue(linkingRequestMessage)
}
func handleDeviceLinkAuthorized(_ deviceLink: DeviceLink) {
UserDefaults.standard[.masterHexEncodedPublicKey] = deviceLink.master.publicKey
fakeChatViewContentOffset = fakeChatView.contentOffset
DispatchQueue.main.async {
self.fakeChatView.contentOffset = self.fakeChatViewContentOffset
}
let homeVC = HomeVC()
navigationController!.setViewControllers([ homeVC ], animated: true)
}
func handleDeviceLinkingModalDismissed() {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.stopPoller()
TSAccountManager.sharedInstance().resetForReregistration()
}
// MARK: Convenience
private func setUserInteractionEnabled(_ isEnabled: Bool) {
[ registerButton, restoreButton, linkButton ].forEach {
[ registerButton, restoreButton ].forEach {
$0.isUserInteractionEnabled = isEnabled
}
}

View File

@ -149,14 +149,6 @@ final class NewClosedGroupVC : BaseVC, UITableViewDataSource, UITableViewDelegat
}
@objc private func createClosedGroup() {
if ClosedGroupsProtocol.isSharedSenderKeysEnabled {
createSSKClosedGroup()
} else {
createLegacyClosedGroup()
}
}
private func createSSKClosedGroup() {
func showError(title: String, message: String = "") {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default, handler: nil))
@ -176,16 +168,15 @@ final class NewClosedGroupVC : BaseVC, UITableViewDataSource, UITableViewDelegat
}
let selectedContacts = self.selectedContacts
ModalActivityIndicatorViewController.present(fromViewController: navigationController!, canCancel: false) { [weak self] _ in
FileServerAPI.getDeviceLinks(associatedWith: selectedContacts).then2 { _ -> Promise<TSGroupThread> in
var promise: Promise<TSGroupThread>!
Storage.writeSync { transaction in
promise = ClosedGroupsProtocol.createClosedGroup(name: name, members: selectedContacts, transaction: transaction)
}
return promise
}.done(on: DispatchQueue.main) { thread in
var promise: Promise<TSGroupThread>!
Storage.writeSync { transaction in
promise = ClosedGroupsProtocol.createClosedGroup(name: name, members: selectedContacts, transaction: transaction)
}
let _ = promise.done(on: DispatchQueue.main) { thread in
self?.presentingViewController?.dismiss(animated: true, completion: nil)
SignalApp.shared().presentConversation(for: thread, action: .compose, animated: false)
}.catch(on: DispatchQueue.main) { _ in
}
promise.catch(on: DispatchQueue.main) { _ in
self?.dismiss(animated: true, completion: nil) // Dismiss the loader
let title = "Couldn't Create Group"
let message = "Please check your internet connection and try again."
@ -195,57 +186,6 @@ final class NewClosedGroupVC : BaseVC, UITableViewDataSource, UITableViewDelegat
}
}
}
private func createLegacyClosedGroup() {
func showError(title: String, message: String = "") {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default, handler: nil))
presentAlert(alert)
}
guard let name = nameTextField.text?.trimmingCharacters(in: .whitespacesAndNewlines), name.count > 0 else {
return showError(title: NSLocalizedString("vc_create_closed_group_group_name_missing_error", comment: ""))
}
guard name.count < 64 else {
return showError(title: NSLocalizedString("vc_create_closed_group_group_name_too_long_error", comment: ""))
}
guard selectedContacts.count >= 1 else {
return showError(title: "Please pick at least 1 group member")
}
guard selectedContacts.count < 10 else { // Minus one because we're going to include self later
return showError(title: NSLocalizedString("vc_create_closed_group_too_many_group_members_error", comment: ""))
}
let userPublicKey = getUserHexEncodedPublicKey()
let storage = OWSPrimaryStorage.shared()
var masterPublicKey = ""
storage.dbReadConnection.read { transaction in
masterPublicKey = storage.getMasterHexEncodedPublicKey(for: userPublicKey, in: transaction) ?? userPublicKey
}
let members = selectedContacts + [ masterPublicKey ]
let admins = [ masterPublicKey ]
let groupID = LKGroupUtilities.getEncodedClosedGroupIDAsData(Randomness.generateRandomBytes(kGroupIdLength).toHexString())
let group = TSGroupModel(title: name, memberIds: members, image: nil, groupId: groupID, groupType: .closedGroup, adminIds: admins)
let thread = TSGroupThread.getOrCreateThread(with: group)
OWSProfileManager.shared().addThread(toProfileWhitelist: thread)
ModalActivityIndicatorViewController.present(fromViewController: navigationController!, canCancel: false) { [weak self] modalActivityIndicator in
let message = TSOutgoingMessage(in: thread, groupMetaMessage: .new, expiresInSeconds: 0)
message.update(withCustomMessage: "Closed group created")
DispatchQueue.main.async {
SSKEnvironment.shared.messageSender.send(message, success: {
DispatchQueue.main.async {
self?.presentingViewController?.dismiss(animated: true, completion: nil)
SignalApp.shared().presentConversation(for: thread, action: .compose, animated: false)
}
}, failure: { error in
let message = TSErrorMessage(timestamp: NSDate.ows_millisecondTimeStamp(), in: thread, failedMessageType: .groupCreationFailed)
message.save()
DispatchQueue.main.async {
self?.presentingViewController?.dismiss(animated: true, completion: nil)
SignalApp.shared().presentConversation(for: thread, action: .compose, animated: false)
}
})
}
}
}
@objc private func createNewPrivateChat() {
presentingViewController?.dismiss(animated: true, completion: nil)

View File

@ -147,14 +147,6 @@ final class NewPrivateChatVC : BaseVC, UIPageViewControllerDataSource, UIPageVie
private final class EnterPublicKeyVC : UIViewController {
weak var newPrivateChatVC: NewPrivateChatVC!
private lazy var userPublicKey: String = {
if let masterHexEncodedPublicKey = UserDefaults.standard[.masterHexEncodedPublicKey] {
return masterHexEncodedPublicKey
} else {
return getUserHexEncodedPublicKey()
}
}()
// MARK: Components
private let publicKeyTextView = TextView(placeholder: NSLocalizedString("vc_enter_public_key_text_field_hint", comment: ""))
@ -186,7 +178,7 @@ private final class EnterPublicKeyVC : UIViewController {
userPublicKeyLabel.numberOfLines = 0
userPublicKeyLabel.textAlignment = .center
userPublicKeyLabel.lineBreakMode = .byCharWrapping
userPublicKeyLabel.text = userPublicKey
userPublicKeyLabel.text = getUserHexEncodedPublicKey()
// Set up share button
let shareButton = Button(style: .unimportant, size: .medium)
shareButton.setTitle(NSLocalizedString("share", comment: ""), for: UIControl.State.normal)
@ -239,7 +231,7 @@ private final class EnterPublicKeyVC : UIViewController {
// MARK: Interaction
@objc private func copyPublicKey() {
UIPasteboard.general.string = userPublicKey
UIPasteboard.general.string = getUserHexEncodedPublicKey()
copyButton.isUserInteractionEnabled = false
UIView.transition(with: copyButton, duration: 0.25, options: .transitionCrossDissolve, animations: {
self.copyButton.setTitle("Copied", for: UIControl.State.normal)
@ -248,7 +240,7 @@ private final class EnterPublicKeyVC : UIViewController {
}
@objc private func sharePublicKey() {
let shareVC = UIActivityViewController(activityItems: [ userPublicKey ], applicationActivities: nil)
let shareVC = UIActivityViewController(activityItems: [ getUserHexEncodedPublicKey() ], applicationActivities: nil)
newPrivateChatVC.navigationController!.present(shareVC, animated: true, completion: nil)
}

View File

@ -137,14 +137,6 @@ private final class ViewMyQRCodeVC : UIViewController {
weak var qrCodeVC: QRCodeVC!
private var bottomConstraint: NSLayoutConstraint!
private lazy var userHexEncodedPublicKey: String = {
if let masterHexEncodedPublicKey = UserDefaults.standard[.masterHexEncodedPublicKey] {
return masterHexEncodedPublicKey
} else {
return getUserHexEncodedPublicKey()
}
}()
// MARK: Lifecycle
override func viewDidLoad() {
// Remove background color
@ -160,7 +152,7 @@ private final class ViewMyQRCodeVC : UIViewController {
titleLabel.set(.height, to: isIPhone5OrSmaller ? CGFloat(40) : Values.massiveFontSize)
// Set up QR code image view
let qrCodeImageView = UIImageView()
let qrCode = QRCode.generate(for: userHexEncodedPublicKey, hasBackground: true)
let qrCode = QRCode.generate(for: getUserHexEncodedPublicKey(), hasBackground: true)
qrCodeImageView.image = qrCode
qrCodeImageView.contentMode = .scaleAspectFit
qrCodeImageView.set(.height, to: isIPhone5OrSmaller ? 180 : 240)
@ -218,7 +210,7 @@ private final class ViewMyQRCodeVC : UIViewController {
// MARK: Interaction
@objc private func shareQRCode() {
let qrCode = QRCode.generate(for: userHexEncodedPublicKey, hasBackground: true)
let qrCode = QRCode.generate(for: getUserHexEncodedPublicKey(), hasBackground: true)
let shareVC = UIActivityViewController(activityItems: [ qrCode ], applicationActivities: nil)
qrCodeVC.navigationController!.present(shareVC, animated: true, completion: nil)
}

View File

@ -4,14 +4,6 @@ final class SettingsVC : BaseVC, AvatarViewHelperDelegate {
private var displayNameToBeUploaded: String?
private var isEditingDisplayName = false { didSet { handleIsEditingDisplayNameChanged() } }
private lazy var userHexEncodedPublicKey: String = {
if let masterHexEncodedPublicKey = UserDefaults.standard[.masterHexEncodedPublicKey] {
return masterHexEncodedPublicKey
} else {
return getUserHexEncodedPublicKey()
}
}()
// MARK: Components
private lazy var profilePictureView: ProfilePictureView = {
let result = ProfilePictureView()
@ -71,10 +63,10 @@ final class SettingsVC : BaseVC, AvatarViewHelperDelegate {
// Set up profile picture view
let profilePictureTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(showEditProfilePictureUI))
profilePictureView.addGestureRecognizer(profilePictureTapGestureRecognizer)
profilePictureView.hexEncodedPublicKey = userHexEncodedPublicKey
profilePictureView.hexEncodedPublicKey = getUserHexEncodedPublicKey()
profilePictureView.update()
// Set up display name label
displayNameLabel.text = OWSProfileManager.shared().profileNameForRecipient(withID: userHexEncodedPublicKey)
displayNameLabel.text = OWSProfileManager.shared().profileNameForRecipient(withID: getUserHexEncodedPublicKey())
// Set up display name container
let displayNameContainer = UIView()
displayNameContainer.addSubview(displayNameLabel)
@ -99,7 +91,7 @@ final class SettingsVC : BaseVC, AvatarViewHelperDelegate {
publicKeyLabel.numberOfLines = 0
publicKeyLabel.textAlignment = .center
publicKeyLabel.lineBreakMode = .byCharWrapping
publicKeyLabel.text = userHexEncodedPublicKey
publicKeyLabel.text = getUserHexEncodedPublicKey()
// Set up share button
let shareButton = Button(style: .regular, size: .medium)
shareButton.setTitle(NSLocalizedString("share", comment: ""), for: UIControl.State.normal)
@ -178,23 +170,19 @@ final class SettingsVC : BaseVC, AvatarViewHelperDelegate {
button.set(.height, to: Values.settingButtonHeight)
return button
}
var result = [
return [
getSeparator(),
getSettingButton(withTitle: NSLocalizedString("vc_settings_privacy_button_title", comment: ""), color: Colors.text, action: #selector(showPrivacySettings)),
getSeparator(),
getSettingButton(withTitle: NSLocalizedString("vc_settings_notifications_button_title", comment: ""), color: Colors.text, action: #selector(showNotificationSettings)),
getSeparator(),
getSettingButton(withTitle: "Invite", color: Colors.text, action: #selector(sendInvitation))
getSettingButton(withTitle: "Invite", color: Colors.text, action: #selector(sendInvitation)),
getSeparator(),
getSettingButton(withTitle: NSLocalizedString("vc_settings_recovery_phrase_button_title", comment: ""), color: Colors.text, action: #selector(showSeed)),
getSeparator(),
getSettingButton(withTitle: NSLocalizedString("vc_settings_clear_all_data_button_title", comment: ""), color: Colors.destructive, action: #selector(clearAllData)),
getSeparator()
]
let isMasterDevice = UserDefaults.standard.isMasterDevice
if isMasterDevice {
result.append(getSeparator())
result.append(getSettingButton(withTitle: NSLocalizedString("vc_settings_recovery_phrase_button_title", comment: ""), color: Colors.text, action: #selector(showSeed)))
}
result.append(getSeparator())
result.append(getSettingButton(withTitle: NSLocalizedString("vc_settings_clear_all_data_button_title", comment: ""), color: Colors.destructive, action: #selector(clearAllData)))
result.append(getSeparator())
return result
}
// MARK: General
@ -283,8 +271,8 @@ final class SettingsVC : BaseVC, AvatarViewHelperDelegate {
}
private func updateProfile(isUpdatingDisplayName: Bool, isUpdatingProfilePicture: Bool) {
let displayName = displayNameToBeUploaded ?? OWSProfileManager.shared().profileNameForRecipient(withID: userHexEncodedPublicKey)
let profilePicture = profilePictureToBeUploaded ?? OWSProfileManager.shared().profileAvatar(forRecipientId: userHexEncodedPublicKey)
let displayName = displayNameToBeUploaded ?? OWSProfileManager.shared().profileNameForRecipient(withID: getUserHexEncodedPublicKey())
let profilePicture = profilePictureToBeUploaded ?? OWSProfileManager.shared().profileAvatar(forRecipientId: getUserHexEncodedPublicKey())
ModalActivityIndicatorViewController.present(fromViewController: navigationController!, canCancel: false) { [weak self] modalActivityIndicator in
OWSProfileManager.shared().updateLocalProfileName(displayName, avatarImage: profilePicture, success: {
DispatchQueue.main.async {
@ -372,7 +360,7 @@ final class SettingsVC : BaseVC, AvatarViewHelperDelegate {
}
@objc private func copyPublicKey() {
UIPasteboard.general.string = userHexEncodedPublicKey
UIPasteboard.general.string = getUserHexEncodedPublicKey()
copyButton.isUserInteractionEnabled = false
UIView.transition(with: copyButton, duration: 0.25, options: .transitionCrossDissolve, animations: {
self.copyButton.setTitle("Copied", for: UIControl.State.normal)
@ -381,7 +369,7 @@ final class SettingsVC : BaseVC, AvatarViewHelperDelegate {
}
@objc private func sharePublicKey() {
let shareVC = UIActivityViewController(activityItems: [ userHexEncodedPublicKey ], applicationActivities: nil)
let shareVC = UIActivityViewController(activityItems: [ getUserHexEncodedPublicKey() ], applicationActivities: nil)
navigationController!.present(shareVC, animated: true, completion: nil)
}
@ -394,14 +382,9 @@ final class SettingsVC : BaseVC, AvatarViewHelperDelegate {
let notificationSettingsVC = NotificationSettingsViewController()
navigationController!.pushViewController(notificationSettingsVC, animated: true)
}
@objc private func showLinkedDevices() {
let deviceLinksVC = DeviceLinksVC()
navigationController!.pushViewController(deviceLinksVC, animated: true)
}
@objc private func sendInvitation() {
let invitation = "Hey, I've been using Session to chat with complete privacy and security. Come join me! Download it at https://getsession.org/. My Session ID is \(userHexEncodedPublicKey)!"
let invitation = "Hey, I've been using Session to chat with complete privacy and security. Come join me! Download it at https://getsession.org/. My Session ID is \(getUserHexEncodedPublicKey())!"
let shareVC = UIActivityViewController(activityItems: [ invitation ], applicationActivities: nil)
navigationController!.present(shareVC, animated: true, completion: nil)
}

Some files were not shown because too many files have changed in this diff Show More