diff --git a/Session/Conversations/ConversationMessageMapping.swift b/Session/Conversations/ConversationMessageMapping.swift index f48503948..7075edc57 100644 --- a/Session/Conversations/ConversationMessageMapping.swift +++ b/Session/Conversations/ConversationMessageMapping.swift @@ -229,7 +229,7 @@ public class ConversationMessageMapping: NSObject { let indexPtr: UnsafeMutablePointer = UnsafeMutablePointer.allocate(capacity: 1) let wasFound = view.getGroup(nil, index: indexPtr, forKey: uniqueId, inCollection: TSInteraction.collection()) guard wasFound else { - owsFailDebug("Could not find interaction.") + SNLog("Could not find interaction.") return nil } let index = indexPtr.pointee diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index 3aa4e499d..432167228 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -3,6 +3,7 @@ import CoreServices import Photos import PhotosUI import Sodium +import PromiseKit import SessionUtilitiesKit import SignalUtilitiesKit @@ -11,6 +12,16 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc ConversationTitleViewDelegate { func handleTitleViewTapped() { + // Don't take the user to settings for message requests + guard + let contactThread: TSContactThread = thread as? TSContactThread, + let contact: Contact = Storage.shared.getContact(with: contactThread.contactSessionID()), + contact.isApproved, + contact.didApproveMe + else { + return + } + openSettings() } @@ -253,71 +264,109 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc let linkPreviewDraft = snInputView.linkPreviewInfo?.draft let tsMessage = TSOutgoingMessage.from(message, associatedWith: thread) - viewModel.appendUnsavedOutgoingTextMessage(tsMessage) - Storage.write(with: { transaction in - message.linkPreview = VisibleMessage.LinkPreview.from(linkPreviewDraft, using: transaction) - }, completion: { [weak self] in - tsMessage.linkPreview = OWSLinkPreview.from(message.linkPreview) - - Storage.shared.write( - with: { transaction in - tsMessage.save(with: transaction as! YapDatabaseReadWriteTransaction) - }, - completion: { [weak self] in - // At this point the TSOutgoingMessage should have its link preview set, so we can scroll to the bottom knowing - // the height of the new message cell - self?.scrollToBottom(isAnimated: false) - } + let promise: Promise = self.approveMessageRequestIfNeeded( + for: self.thread, + with: transaction, + isNewThread: !oldThreadShouldBeVisible, + timestamp: (sentTimestamp - 1) // Set 1ms earlier as this is used for sorting ) - - Storage.shared.write( - with: { transaction in - self?.approveMessageRequestIfNeeded( - for: self?.thread, - with: (transaction as! YapDatabaseReadWriteTransaction), - isNewThread: !oldThreadShouldBeVisible, - timestamp: (sentTimestamp - 1) // Set 1ms earlier as this is used for sorting + .map { [weak self] _ in + self?.viewModel.appendUnsavedOutgoingTextMessage(tsMessage) + + Storage.write(with: { transaction in + message.linkPreview = VisibleMessage.LinkPreview.from(linkPreviewDraft, using: transaction) + }, completion: { [weak self] in + tsMessage.linkPreview = OWSLinkPreview.from(message.linkPreview) + + Storage.shared.write( + with: { transaction in + tsMessage.save(with: transaction as! YapDatabaseReadWriteTransaction) + }, + completion: { [weak self] in + // At this point the TSOutgoingMessage should have its link preview set, so we can scroll to the bottom knowing + // the height of the new message cell + self?.scrollToBottom(isAnimated: false) + } ) - }, - completion: { [weak self] in + Storage.shared.write { transaction in MessageSender.send(message, with: [], in: thread, using: transaction as! YapDatabaseReadWriteTransaction) } self?.handleMessageSent() - } - ) + }) + } + + // Show an error indicating that approving the thread failed + promise.catch(on: DispatchQueue.main) { [weak self] _ in + let alert = UIAlertController(title: "Session", message: "An error occurred when trying to accept this message request", preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) + self?.present(alert, animated: true, completion: nil) + } + + promise.retainUntilComplete() }) } func sendAttachments(_ attachments: [SignalAttachment], with text: String, onComplete: (() -> ())? = nil) { guard !showBlockedModalIfNeeded() else { return } + for attachment in attachments { if attachment.hasError { return showErrorAlert(for: attachment, onDismiss: onComplete) } } + let thread = self.thread + let sentTimestamp: UInt64 = NSDate.millisecondTimestamp() let message = VisibleMessage() - message.sentTimestamp = NSDate.millisecondTimestamp() + message.sentTimestamp = sentTimestamp message.text = replaceMentions(in: text) + + // Note: 'shouldBeVisible' is set to true the first time a thread is saved so we can + // use it to determine if the user is creating a new thread and update the 'isApproved' + // flags appropriately + let oldThreadShouldBeVisible: Bool = thread.shouldBeVisible let tsMessage = TSOutgoingMessage.from(message, associatedWith: thread) + Storage.write(with: { transaction in - tsMessage.save(with: transaction) - // The new message cell is inserted at this point, but the TSOutgoingMessage doesn't have its attachment yet - }, completion: { [weak self] in - Storage.write(with: { transaction in - MessageSender.send(message, with: attachments, in: thread, using: transaction) - }, completion: { [weak self] in - // At this point the TSOutgoingMessage should have its attachments set, so we can scroll to the bottom knowing - // the height of the new message cell - self?.scrollToBottom(isAnimated: false) - }) - self?.handleMessageSent() + let promise: Promise = self.approveMessageRequestIfNeeded( + for: self.thread, + with: transaction, + isNewThread: !oldThreadShouldBeVisible, + timestamp: (sentTimestamp - 1) // Set 1ms earlier as this is used for sorting + ) + .map { [weak self] _ in + Storage.write( + with: { transaction in + tsMessage.save(with: transaction) + // The new message cell is inserted at this point, but the TSOutgoingMessage doesn't have its attachment yet + }, + completion: { [weak self] in + Storage.write(with: { transaction in + MessageSender.send(message, with: attachments, in: thread, using: transaction) + }, completion: { [weak self] in + // At this point the TSOutgoingMessage should have its attachments set, so we can scroll to the bottom knowing + // the height of the new message cell + self?.scrollToBottom(isAnimated: false) + }) + self?.handleMessageSent() + + // Attachment successfully sent - dismiss the screen + onComplete?() + } + ) + } + + // Show an error indicating that approving the thread failed + promise.catch(on: DispatchQueue.main) { [weak self] _ in + let alert = UIAlertController(title: "Session", message: "An error occurred when trying to accept this message request", preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) + self?.present(alert, animated: true, completion: nil) + } - // Attachment successfully sent - dismiss the screen - onComplete?() + promise.retainUntilComplete() }) } @@ -1178,8 +1227,8 @@ extension ConversationVC { navigationController?.popToViewController(viewControllers[messageRequestsIndex - 1], animated: true) } - fileprivate func approveMessageRequestIfNeeded(for thread: TSThread?, with transaction: YapDatabaseReadWriteTransaction, isNewThread: Bool, timestamp: UInt64) { - guard let contactThread: TSContactThread = thread as? TSContactThread else { return } + fileprivate func approveMessageRequestIfNeeded(for thread: TSThread?, with transaction: YapDatabaseReadWriteTransaction, isNewThread: Bool, timestamp: UInt64) -> Promise { + guard let contactThread: TSContactThread = thread as? TSContactThread else { return Promise.value(()) } // If the contact doesn't exist then we should create it so we can store the 'isApproved' state // (it'll be updated with correct profile info if they accept the message request so this @@ -1187,64 +1236,93 @@ extension ConversationVC { let sessionId: String = contactThread.contactSessionID() let contact: Contact = (Storage.shared.getContact(with: sessionId) ?? Contact(sessionID: sessionId)) - if !contact.isApproved { - // Default 'didApproveMe' to true for the person approving the message request - contact.isApproved = true - contact.didApproveMe = (contact.didApproveMe || !isNewThread) - Storage.shared.setContact(contact, using: transaction) - - // If we aren't creating a new thread (ie. sending a message request) then send a - // messageRequestResponse back to the sender (this allows the sender to know that - // they have been approved and can now use this contact in closed groups) - if !isNewThread { + guard !contact.isApproved else { return Promise.value(()) } + + return Promise.value(()) + .then { [weak self] _ -> Promise in + guard !isNewThread else { return Promise.value(()) } + guard let strongSelf = self else { return Promise(error: MessageSender.Error.noThread) } + + // If we aren't creating a new thread (ie. sending a message request) then send a + // messageRequestResponse back to the sender (this allows the sender to know that + // they have been approved and can now use this contact in closed groups) + let (promise, seal) = Promise.pending() let messageRequestResponse: MessageRequestResponse = MessageRequestResponse( isApproved: true ) messageRequestResponse.sentTimestamp = timestamp - MessageSender.send(messageRequestResponse, in: contactThread, using: transaction) + // Show a loading indicator + ModalActivityIndicatorViewController.present(fromViewController: strongSelf, canCancel: false) { _ in + seal.fulfill(()) + } + + return promise + .then { MessageSender.sendNonDurably(messageRequestResponse, in: contactThread, using: transaction) } + .map { _ in + if self?.presentedViewController is ModalActivityIndicatorViewController { + self?.dismiss(animated: true, completion: nil) // Dismiss the loader + } + } } - - // Hide the 'messageRequestView' since the request has been approved and force a config - // sync to propagate the contact approval state (both must run on the main thread) - DispatchQueue.main.async { [weak self] in - let messageRequestViewWasVisible: Bool = (self?.messageRequestView.isHidden == false) + .map { _ in + // Default 'didApproveMe' to true for the person approving the message request + contact.isApproved = true + contact.didApproveMe = (contact.didApproveMe || !isNewThread) + Storage.shared.setContact(contact, using: transaction) - UIView.animate(withDuration: 0.3) { - self?.messageRequestView.isHidden = true - self?.scrollButtonMessageRequestsBottomConstraint?.isActive = false - self?.scrollButtonBottomConstraint?.isActive = true + // Hide the 'messageRequestView' since the request has been approved and force a config + // sync to propagate the contact approval state (both must run on the main thread) + DispatchQueue.main.async { [weak self] in + let messageRequestViewWasVisible: Bool = (self?.messageRequestView.isHidden == false) - // Update the table content inset and offset to account for the dissapearance of - // the messageRequestsView - if messageRequestViewWasVisible { - let messageRequestsOffset: CGFloat = ((self?.messageRequestView.bounds.height ?? 0) + 16) - let oldContentInset: UIEdgeInsets = (self?.messagesTableView.contentInset ?? UIEdgeInsets.zero) - self?.messagesTableView.contentInset = UIEdgeInsets( - top: 0, - leading: 0, - bottom: max(oldContentInset.bottom - messageRequestsOffset, 0), - trailing: 0 - ) + UIView.animate(withDuration: 0.3) { + self?.messageRequestView.isHidden = true + self?.scrollButtonMessageRequestsBottomConstraint?.isActive = false + self?.scrollButtonBottomConstraint?.isActive = true + + // Update the table content inset and offset to account for the dissapearance of + // the messageRequestsView + if messageRequestViewWasVisible { + let messageRequestsOffset: CGFloat = ((self?.messageRequestView.bounds.height ?? 0) + 16) + let oldContentInset: UIEdgeInsets = (self?.messagesTableView.contentInset ?? UIEdgeInsets.zero) + self?.messagesTableView.contentInset = UIEdgeInsets( + top: 0, + leading: 0, + bottom: max(oldContentInset.bottom - messageRequestsOffset, 0), + trailing: 0 + ) + } + } + + // Update UI + self?.updateNavBarButtons() + + // Send a sync message with the details of the contact + if let appDelegate = UIApplication.shared.delegate as? AppDelegate { + appDelegate.forceSyncConfigurationNowIfNeeded(with: transaction).retainUntilComplete() } - } - - // Send a sync message with the details of the contact - if let appDelegate = UIApplication.shared.delegate as? AppDelegate { - appDelegate.forceSyncConfigurationNowIfNeeded(with: transaction).retainUntilComplete() } } - } } @objc func acceptMessageRequest() { - Storage.write { [weak self] transaction in - self?.approveMessageRequestIfNeeded( - for: self?.thread, + Storage.write { transaction in + let promise: Promise = self.approveMessageRequestIfNeeded( + for: self.thread, with: transaction, isNewThread: false, timestamp: NSDate.millisecondTimestamp() ) + + // Show an error indicating that approving the thread failed + promise.catch(on: DispatchQueue.main) { [weak self] _ in + let alert = UIAlertController(title: "Session", message: NSLocalizedString("MESSAGE_REQUESTS_APPROVAL_ERROR_MESSAGE", comment: ""), preferredStyle: .alert) + alert.addAction(UIAlertAction(title: NSLocalizedString("BUTTON_OK", comment: ""), style: .default, handler: nil)) + self?.present(alert, animated: true, completion: nil) + } + + promise.retainUntilComplete() } } diff --git a/Session/Conversations/ConversationVC.swift b/Session/Conversations/ConversationVC.swift index d8d8dc079..4d3bad017 100644 --- a/Session/Conversations/ConversationVC.swift +++ b/Session/Conversations/ConversationVC.swift @@ -1,6 +1,7 @@ import UIKit import SessionUIKit import SessionMessagingKit +import UIKit // TODO: // • Slight paging glitch when scrolling up and loading more content @@ -474,30 +475,47 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat // MARK: Updating func updateNavBarButtons() { + navigationItem.hidesBackButton = isShowingSearchUI + if isShowingSearchUI { navigationItem.leftBarButtonItem = nil navigationItem.rightBarButtonItems = [] - } else { + } + else { navigationItem.leftBarButtonItem = UIViewController.createOWSBackButton(withTarget: self, selector: #selector(handleBackPressed)) - let rightBarButtonItem: UIBarButtonItem - if thread is TSContactThread { - let size = Values.verySmallProfilePictureSize - let profilePictureView = ProfilePictureView() - profilePictureView.accessibilityLabel = "Settings button" - profilePictureView.size = size - profilePictureView.update(for: thread) - profilePictureView.set(.width, to: size) - profilePictureView.set(.height, to: size) - let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(openSettings)) - profilePictureView.addGestureRecognizer(tapGestureRecognizer) - rightBarButtonItem = UIBarButtonItem(customView: profilePictureView) - } else { - rightBarButtonItem = UIBarButtonItem(image: UIImage(named: "Gear"), style: .plain, target: self, action: #selector(openSettings)) + if let contactThread: TSContactThread = thread as? TSContactThread { + // Don't show the settings button for message requests + if let contact: Contact = Storage.shared.getContact(with: contactThread.contactSessionID()), contact.isApproved, contact.didApproveMe { + let size = Values.verySmallProfilePictureSize + let profilePictureView = ProfilePictureView() + profilePictureView.size = size + profilePictureView.update(for: thread) + profilePictureView.set(.width, to: size) + profilePictureView.set(.height, to: size) + + let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(openSettings)) + profilePictureView.addGestureRecognizer(tapGestureRecognizer) + + let rightBarButtonItem: UIBarButtonItem = UIBarButtonItem(customView: profilePictureView) + rightBarButtonItem.accessibilityLabel = "Settings button" + rightBarButtonItem.isAccessibilityElement = true + + navigationItem.rightBarButtonItem = rightBarButtonItem + } + else { + // Note: Adding an empty button because without it the title alignment is busted (Note: The size was + // taken from the layout inspector for the back button in Xcode + navigationItem.rightBarButtonItem = UIBarButtonItem(customView: UIView(frame: CGRect(x: 0, y: 0, width: 37, height: 44))) + } + } + else { + let rightBarButtonItem: UIBarButtonItem = UIBarButtonItem(image: UIImage(named: "Gear"), style: .plain, target: self, action: #selector(openSettings)) + rightBarButtonItem.accessibilityLabel = "Settings button" + rightBarButtonItem.isAccessibilityElement = true + + navigationItem.rightBarButtonItem = rightBarButtonItem } - rightBarButtonItem.accessibilityLabel = "Settings button" - rightBarButtonItem.isAccessibilityElement = true - navigationItem.rightBarButtonItem = rightBarButtonItem } } @@ -806,9 +824,12 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat } func markAllAsRead() { - guard !thread.isMessageRequest() else { return } guard let lastSortID = viewItems.last?.interaction.sortId else { return } - OWSReadReceiptManager.shared().markAsReadLocally(beforeSortId: lastSortID, thread: thread) + OWSReadReceiptManager.shared().markAsReadLocally( + beforeSortId: lastSortID, + thread: thread, + trySendReadReceipt: !thread.isMessageRequest() + ) SSKEnvironment.shared.disappearingMessagesJob.cleanupMessagesWhichFailedToStartExpiringFromNow() } @@ -912,7 +933,7 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat let searchBar = searchController.uiSearchController.searchBar searchBar.searchBarStyle = .minimal searchBar.barStyle = .black - searchBar.tintColor = Colors.accent + searchBar.tintColor = Colors.text let searchIcon = UIImage(named: "searchbar_search")!.asTintedImage(color: Colors.searchBarPlaceholder) searchBar.setImage(searchIcon, for: .search, state: UIControl.State.normal) let clearIcon = UIImage(named: "searchbar_clear")!.asTintedImage(color: Colors.searchBarPlaceholder) diff --git a/Session/Conversations/Settings/OWSConversationSettingsViewController.m b/Session/Conversations/Settings/OWSConversationSettingsViewController.m index f8a4f1b5e..b7a4d1978 100644 --- a/Session/Conversations/Settings/OWSConversationSettingsViewController.m +++ b/Session/Conversations/Settings/OWSConversationSettingsViewController.m @@ -277,7 +277,7 @@ CGFloat kIconViewLength = 24; contents.title = NSLocalizedString(@"CONVERSATION_SETTINGS", @"title for conversation settings screen"); BOOL isNoteToSelf = self.thread.isNoteToSelf; - + __weak OWSConversationSettingsViewController *weakSelf = self; OWSTableSection *section = [OWSTableSection new]; @@ -332,7 +332,7 @@ CGFloat kIconViewLength = 24; } actionBlock:^{ [weakSelf tappedConversationSearch]; }]]; - + // Disappearing messages if (![self isOpenGroup]) { [section addItem:[OWSTableItem itemWithCustomCellBlock:^{ @@ -358,13 +358,6 @@ CGFloat kIconViewLength = 24; switchView.on = strongSelf.disappearingMessagesConfiguration.isEnabled; [switchView addTarget:strongSelf action:@selector(disappearingMessagesSwitchValueDidChange:) forControlEvents:UIControlEventValueChanged]; - - // Disable Disappearing Messages if the conversation hasn't been approved - if (!self.thread.isGroupThread) { - TSContactThread *thread = (TSContactThread *)self.thread; - SNContact *contact = [LKStorage.shared getContactWithSessionID:thread.contactSessionID]; - [switchView setEnabled:(contact.isApproved && contact.didApproveMe)]; - } UIStackView *topRow = [[UIStackView alloc] initWithArrangedSubviews:@[ iconView, rowLabel, switchView ]]; @@ -438,13 +431,6 @@ CGFloat kIconViewLength = 24; [slider autoPinTrailingToSuperviewMargin]; [slider autoPinBottomToSuperviewMargin]; - // Disable Disappearing Messages slider if the conversation hasn't been approved (just in case) - if (!self.thread.isGroupThread) { - TSContactThread *thread = (TSContactThread *)self.thread; - SNContact *contact = [LKStorage.shared getContactWithSessionID:thread.contactSessionID]; - [slider setEnabled:(contact.isApproved && contact.didApproveMe)]; - } - cell.userInteractionEnabled = !strongSelf.hasLeftGroup; cell.accessibilityIdentifier = ACCESSIBILITY_IDENTIFIER_WITH_NAME( diff --git a/Session/Conversations/Views & Modals/ConversationTitleView.swift b/Session/Conversations/Views & Modals/ConversationTitleView.swift index e8693e87b..e5ae03b9d 100644 --- a/Session/Conversations/Views & Modals/ConversationTitleView.swift +++ b/Session/Conversations/Views & Modals/ConversationTitleView.swift @@ -73,13 +73,17 @@ final class ConversationTitleView : UIView { private func getTitle() -> String { if let thread = thread as? TSGroupThread { return thread.groupModel.groupName! - } else if thread.isNoteToSelf() { + } + else if thread.isNoteToSelf() { return "Note to Self" - } else { + } + else { let sessionID = (thread as! TSContactThread).contactSessionID() var result = sessionID Storage.read { transaction in - result = Storage.shared.getContact(with: sessionID)?.displayName(for: .regular) ?? "Anonymous" + let displayName: String = ((Storage.shared.getContact(with: sessionID)?.displayName(for: .regular)) ?? sessionID) + let middleTruncatedHexKey: String = "\(sessionID.prefix(4))...\(sessionID.suffix(4))" + result = (displayName == sessionID ? middleTruncatedHexKey : displayName) } return result } diff --git a/Session/Home/HomeVC.swift b/Session/Home/HomeVC.swift index b3ab3c629..4c140aea9 100644 --- a/Session/Home/HomeVC.swift +++ b/Session/Home/HomeVC.swift @@ -6,6 +6,7 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, NewConv private var threads: YapDatabaseViewMappings! private var threadViewModelCache: [String:ThreadViewModel] = [:] // Thread ID to ThreadViewModel private var tableViewTopConstraint: NSLayoutConstraint! + private var unreadMessageRequestCount: UInt = 0 private var messageRequestCount: UInt { threads.numberOfItems(inGroup: TSMessageRequestGroup) @@ -196,7 +197,7 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, NewConv switch indexPath.section { case 0: let cell = tableView.dequeueReusableCell(withIdentifier: MessageRequestsCell.reuseIdentifier) as! MessageRequestsCell - cell.update(with: Int(messageRequestCount)) + cell.update(with: Int(unreadMessageRequestCount)) return cell default: @@ -263,6 +264,14 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, NewConv } } + // Update the number of unread message requests + unreadMessageRequestCount = OWSMessageUtils.sharedManager().unreadMessageRequestCount() + + // If there are no unread message requests then hide the message request banner + if unreadMessageRequestCount == 0 { + CurrentAppContext().appUserDefaults()[.hasHiddenMessageRequests] = true + } + return reload() } } @@ -286,11 +295,21 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, NewConv // If we need to unhide the message request row and then re-insert it if !messageRequestChanges.isEmpty { - if tableView.numberOfRows(inSection: 0) == 1 && Int(messageRequestCount) <= 0 { + // Update the number of unread message requests + unreadMessageRequestCount = OWSMessageUtils.sharedManager().unreadMessageRequestCount() + + // If there are no unread message requests then hide the message request banner + if unreadMessageRequestCount == 0 && tableView.numberOfRows(inSection: 0) == 1 { + CurrentAppContext().appUserDefaults()[.hasHiddenMessageRequests] = true tableView.deleteRows(at: [IndexPath(row: 0, section: 0)], with: .automatic) } - else if tableView.numberOfRows(inSection: 0) == 0 && Int(messageRequestCount) > 0 && !CurrentAppContext().appUserDefaults()[.hasHiddenMessageRequests] { - tableView.insertRows(at: [IndexPath(row: 0, section: 0)], with: .automatic) + else { + if tableView.numberOfRows(inSection: 0) == 1 && Int(messageRequestCount) <= 0 { + tableView.deleteRows(at: [IndexPath(row: 0, section: 0)], with: .automatic) + } + else if tableView.numberOfRows(inSection: 0) == 0 && Int(messageRequestCount) > 0 && !CurrentAppContext().appUserDefaults()[.hasHiddenMessageRequests] { + tableView.insertRows(at: [IndexPath(row: 0, section: 0)], with: .automatic) + } } } @@ -393,11 +412,13 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, NewConv profilePictureViewContainer.addSubview(pathStatusView) pathStatusView.pin(.trailing, to: .trailing, of: profilePictureViewContainer) pathStatusView.pin(.bottom, to: .bottom, of: profilePictureViewContainer) + // Left bar button item let leftBarButtonItem = UIBarButtonItem(customView: profilePictureViewContainer) leftBarButtonItem.accessibilityLabel = "Settings button" leftBarButtonItem.isAccessibilityElement = true navigationItem.leftBarButtonItem = leftBarButtonItem + // Right bar button item - search button let rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .search, target: self, action: #selector(showSearchUI)) rightBarButtonItem.accessibilityLabel = "Search button" @@ -542,6 +563,7 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, NewConv @objc private func openSettings() { let settingsVC = SettingsVC() let navigationController = OWSNavigationController(rootViewController: settingsVC) + navigationController.modalPresentationStyle = .fullScreen present(navigationController, animated: true, completion: nil) } diff --git a/Session/Home/Message Requests/MessageRequestsViewController.swift b/Session/Home/Message Requests/MessageRequestsViewController.swift index dfb4e1b73..32e1fc46e 100644 --- a/Session/Home/Message Requests/MessageRequestsViewController.swift +++ b/Session/Home/Message Requests/MessageRequestsViewController.swift @@ -63,23 +63,16 @@ class MessageRequestsViewController: BaseVC, UITableViewDelegate, UITableViewDat return result }() - private lazy var clearAllButton: UIButton = { - let result: UIButton = UIButton() + private lazy var clearAllButton: Button = { + let result: Button = Button(style: .destructiveOutline, size: .large) result.translatesAutoresizingMaskIntoConstraints = false - result.clipsToBounds = true - result.titleLabel?.font = UIFont.boldSystemFont(ofSize: 18) result.setTitle(NSLocalizedString("MESSAGE_REQUESTS_CLEAR_ALL", comment: ""), for: .normal) - result.setTitleColor(Colors.destructive, for: .normal) result.setBackgroundImage( Colors.destructive .withAlphaComponent(isDarkMode ? 0.2 : 0.06) .toImage(isDarkMode: isDarkMode), for: .highlighted ) - result.isHidden = true - result.layer.cornerRadius = (NewConversationButtonSet.collapsedButtonSize / 2) - result.layer.borderColor = Colors.destructive.cgColor - result.layer.borderWidth = 1.5 result.addTarget(self, action: #selector(clearAllTapped), for: .touchUpInside) return result @@ -163,10 +156,11 @@ class MessageRequestsViewController: BaseVC, UITableViewDelegate, UITableViewDat clearAllButton.centerXAnchor.constraint(equalTo: view.centerXAnchor), clearAllButton.bottomAnchor.constraint( - equalTo: view.bottomAnchor, - constant: -Values.newConversationButtonBottomOffset // Negative due to how the constraint is set up + equalTo: view.safeAreaLayoutGuide.bottomAnchor, + constant: -Values.largeSpacing ), - clearAllButton.widthAnchor.constraint(equalToConstant: 155), + // Note: The '182' is to match the 'Next' button on the New DM page (which doesn't have a fixed width) + clearAllButton.widthAnchor.constraint(equalToConstant: 182), clearAllButton.heightAnchor.constraint(equalToConstant: NewConversationButtonSet.collapsedButtonSize) ]) } diff --git a/Session/Home/Views/MessageRequestsCell.swift b/Session/Home/Views/MessageRequestsCell.swift index 5229a9540..54002f721 100644 --- a/Session/Home/Views/MessageRequestsCell.swift +++ b/Session/Home/Views/MessageRequestsCell.swift @@ -76,7 +76,7 @@ class MessageRequestsCell: UITableViewCell { }() private func setUpViewHierarchy() { - backgroundColor = Colors.cellBackground + backgroundColor = Colors.cellPinned selectedBackgroundView = UIView() selectedBackgroundView?.backgroundColor = Colors.cellSelected diff --git a/Session/Meta/Translations/de.lproj/Localizable.strings b/Session/Meta/Translations/de.lproj/Localizable.strings index 862b11959..191df72c7 100644 --- a/Session/Meta/Translations/de.lproj/Localizable.strings +++ b/Session/Meta/Translations/de.lproj/Localizable.strings @@ -610,6 +610,7 @@ "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_TITLE" = "Are you sure you want to clear all message requests?"; "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear"; "MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?"; +"MESSAGE_REQUESTS_APPROVAL_ERROR_MESSAGE" = "An error occurred when trying to accept this message request"; "MESSAGE_REQUESTS_INFO" = "Sending a message to this user will automatically accept their message request."; "MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted."; "MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request"; diff --git a/Session/Meta/Translations/en.lproj/Localizable.strings b/Session/Meta/Translations/en.lproj/Localizable.strings index 40e98c4d4..4152f23ad 100644 --- a/Session/Meta/Translations/en.lproj/Localizable.strings +++ b/Session/Meta/Translations/en.lproj/Localizable.strings @@ -620,6 +620,7 @@ "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_TITLE" = "Are you sure you want to clear all message requests?"; "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear"; "MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?"; +"MESSAGE_REQUESTS_APPROVAL_ERROR_MESSAGE" = "An error occurred when trying to accept this message request"; "MESSAGE_REQUESTS_INFO" = "Sending a message to this user will automatically accept their message request."; "MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted."; "MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request"; diff --git a/Session/Meta/Translations/es.lproj/Localizable.strings b/Session/Meta/Translations/es.lproj/Localizable.strings index 72f8761b1..882168f97 100644 --- a/Session/Meta/Translations/es.lproj/Localizable.strings +++ b/Session/Meta/Translations/es.lproj/Localizable.strings @@ -610,6 +610,7 @@ "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_TITLE" = "Are you sure you want to clear all message requests?"; "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear"; "MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?"; +"MESSAGE_REQUESTS_APPROVAL_ERROR_MESSAGE" = "An error occurred when trying to accept this message request"; "MESSAGE_REQUESTS_INFO" = "Sending a message to this user will automatically accept their message request."; "MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted."; "MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request"; diff --git a/Session/Meta/Translations/fa.lproj/Localizable.strings b/Session/Meta/Translations/fa.lproj/Localizable.strings index ff280c80c..5d203e3f1 100644 --- a/Session/Meta/Translations/fa.lproj/Localizable.strings +++ b/Session/Meta/Translations/fa.lproj/Localizable.strings @@ -610,6 +610,7 @@ "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_TITLE" = "Are you sure you want to clear all message requests?"; "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear"; "MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?"; +"MESSAGE_REQUESTS_APPROVAL_ERROR_MESSAGE" = "An error occurred when trying to accept this message request"; "MESSAGE_REQUESTS_INFO" = "Sending a message to this user will automatically accept their message request."; "MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted."; "MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request"; diff --git a/Session/Meta/Translations/fi.lproj/Localizable.strings b/Session/Meta/Translations/fi.lproj/Localizable.strings index d30d9643a..a964f7b10 100644 --- a/Session/Meta/Translations/fi.lproj/Localizable.strings +++ b/Session/Meta/Translations/fi.lproj/Localizable.strings @@ -610,6 +610,7 @@ "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_TITLE" = "Are you sure you want to clear all message requests?"; "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear"; "MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?"; +"MESSAGE_REQUESTS_APPROVAL_ERROR_MESSAGE" = "An error occurred when trying to accept this message request"; "MESSAGE_REQUESTS_INFO" = "Sending a message to this user will automatically accept their message request."; "MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted."; "MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request"; diff --git a/Session/Meta/Translations/fr.lproj/Localizable.strings b/Session/Meta/Translations/fr.lproj/Localizable.strings index 91de28b47..a46436ab6 100644 --- a/Session/Meta/Translations/fr.lproj/Localizable.strings +++ b/Session/Meta/Translations/fr.lproj/Localizable.strings @@ -610,6 +610,7 @@ "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_TITLE" = "Are you sure you want to clear all message requests?"; "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear"; "MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?"; +"MESSAGE_REQUESTS_APPROVAL_ERROR_MESSAGE" = "An error occurred when trying to accept this message request"; "MESSAGE_REQUESTS_INFO" = "Sending a message to this user will automatically accept their message request."; "MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted."; "MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request"; diff --git a/Session/Meta/Translations/hi.lproj/Localizable.strings b/Session/Meta/Translations/hi.lproj/Localizable.strings index a56748e22..50dc8836e 100644 --- a/Session/Meta/Translations/hi.lproj/Localizable.strings +++ b/Session/Meta/Translations/hi.lproj/Localizable.strings @@ -610,6 +610,7 @@ "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_TITLE" = "Are you sure you want to clear all message requests?"; "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear"; "MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?"; +"MESSAGE_REQUESTS_APPROVAL_ERROR_MESSAGE" = "An error occurred when trying to accept this message request"; "MESSAGE_REQUESTS_INFO" = "Sending a message to this user will automatically accept their message request."; "MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted."; "MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request"; diff --git a/Session/Meta/Translations/hr.lproj/Localizable.strings b/Session/Meta/Translations/hr.lproj/Localizable.strings index 2e58d55f4..4b3cc4f92 100644 --- a/Session/Meta/Translations/hr.lproj/Localizable.strings +++ b/Session/Meta/Translations/hr.lproj/Localizable.strings @@ -610,6 +610,7 @@ "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_TITLE" = "Are you sure you want to clear all message requests?"; "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear"; "MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?"; +"MESSAGE_REQUESTS_APPROVAL_ERROR_MESSAGE" = "An error occurred when trying to accept this message request"; "MESSAGE_REQUESTS_INFO" = "Sending a message to this user will automatically accept their message request."; "MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted."; "MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request"; diff --git a/Session/Meta/Translations/id-ID.lproj/Localizable.strings b/Session/Meta/Translations/id-ID.lproj/Localizable.strings index 3c4e80f9b..d37981455 100644 --- a/Session/Meta/Translations/id-ID.lproj/Localizable.strings +++ b/Session/Meta/Translations/id-ID.lproj/Localizable.strings @@ -610,6 +610,7 @@ "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_TITLE" = "Are you sure you want to clear all message requests?"; "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear"; "MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?"; +"MESSAGE_REQUESTS_APPROVAL_ERROR_MESSAGE" = "An error occurred when trying to accept this message request"; "MESSAGE_REQUESTS_INFO" = "Sending a message to this user will automatically accept their message request."; "MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted."; "MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request"; diff --git a/Session/Meta/Translations/it.lproj/Localizable.strings b/Session/Meta/Translations/it.lproj/Localizable.strings index b0d2d5f95..84830091a 100644 --- a/Session/Meta/Translations/it.lproj/Localizable.strings +++ b/Session/Meta/Translations/it.lproj/Localizable.strings @@ -610,6 +610,7 @@ "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_TITLE" = "Are you sure you want to clear all message requests?"; "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear"; "MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?"; +"MESSAGE_REQUESTS_APPROVAL_ERROR_MESSAGE" = "An error occurred when trying to accept this message request"; "MESSAGE_REQUESTS_INFO" = "Sending a message to this user will automatically accept their message request."; "MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted."; "MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request"; diff --git a/Session/Meta/Translations/ja.lproj/Localizable.strings b/Session/Meta/Translations/ja.lproj/Localizable.strings index ecffbb724..a9ab4de29 100644 --- a/Session/Meta/Translations/ja.lproj/Localizable.strings +++ b/Session/Meta/Translations/ja.lproj/Localizable.strings @@ -610,6 +610,7 @@ "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_TITLE" = "Are you sure you want to clear all message requests?"; "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear"; "MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?"; +"MESSAGE_REQUESTS_APPROVAL_ERROR_MESSAGE" = "An error occurred when trying to accept this message request"; "MESSAGE_REQUESTS_INFO" = "Sending a message to this user will automatically accept their message request."; "MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted."; "MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request"; diff --git a/Session/Meta/Translations/nl.lproj/Localizable.strings b/Session/Meta/Translations/nl.lproj/Localizable.strings index cec15e1e0..f1dd572ba 100644 --- a/Session/Meta/Translations/nl.lproj/Localizable.strings +++ b/Session/Meta/Translations/nl.lproj/Localizable.strings @@ -610,6 +610,7 @@ "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_TITLE" = "Are you sure you want to clear all message requests?"; "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear"; "MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?"; +"MESSAGE_REQUESTS_APPROVAL_ERROR_MESSAGE" = "An error occurred when trying to accept this message request"; "MESSAGE_REQUESTS_INFO" = "Sending a message to this user will automatically accept their message request."; "MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted."; "MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request"; diff --git a/Session/Meta/Translations/pl.lproj/Localizable.strings b/Session/Meta/Translations/pl.lproj/Localizable.strings index a2cf9fa5b..6b7fc5195 100644 --- a/Session/Meta/Translations/pl.lproj/Localizable.strings +++ b/Session/Meta/Translations/pl.lproj/Localizable.strings @@ -610,6 +610,7 @@ "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_TITLE" = "Are you sure you want to clear all message requests?"; "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear"; "MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?"; +"MESSAGE_REQUESTS_APPROVAL_ERROR_MESSAGE" = "An error occurred when trying to accept this message request"; "MESSAGE_REQUESTS_INFO" = "Sending a message to this user will automatically accept their message request."; "MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted."; "MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request"; diff --git a/Session/Meta/Translations/pt_BR.lproj/Localizable.strings b/Session/Meta/Translations/pt_BR.lproj/Localizable.strings index 5ba950332..17d977882 100644 --- a/Session/Meta/Translations/pt_BR.lproj/Localizable.strings +++ b/Session/Meta/Translations/pt_BR.lproj/Localizable.strings @@ -610,6 +610,7 @@ "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_TITLE" = "Are you sure you want to clear all message requests?"; "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear"; "MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?"; +"MESSAGE_REQUESTS_APPROVAL_ERROR_MESSAGE" = "An error occurred when trying to accept this message request"; "MESSAGE_REQUESTS_INFO" = "Sending a message to this user will automatically accept their message request."; "MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted."; "MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request"; diff --git a/Session/Meta/Translations/ru.lproj/Localizable.strings b/Session/Meta/Translations/ru.lproj/Localizable.strings index 35aff56e6..1694788c1 100644 --- a/Session/Meta/Translations/ru.lproj/Localizable.strings +++ b/Session/Meta/Translations/ru.lproj/Localizable.strings @@ -610,6 +610,7 @@ "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_TITLE" = "Are you sure you want to clear all message requests?"; "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear"; "MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?"; +"MESSAGE_REQUESTS_APPROVAL_ERROR_MESSAGE" = "An error occurred when trying to accept this message request"; "MESSAGE_REQUESTS_INFO" = "Sending a message to this user will automatically accept their message request."; "MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted."; "MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request"; diff --git a/Session/Meta/Translations/si.lproj/Localizable.strings b/Session/Meta/Translations/si.lproj/Localizable.strings index af3895055..ec4d12dd6 100644 --- a/Session/Meta/Translations/si.lproj/Localizable.strings +++ b/Session/Meta/Translations/si.lproj/Localizable.strings @@ -611,6 +611,7 @@ "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_TITLE" = "Are you sure you want to clear all message requests?"; "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear"; "MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?"; +"MESSAGE_REQUESTS_APPROVAL_ERROR_MESSAGE" = "An error occurred when trying to accept this message request"; "MESSAGE_REQUESTS_INFO" = "Sending a message to this user will automatically accept their message request."; "MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted."; "MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request"; diff --git a/Session/Meta/Translations/sk.lproj/Localizable.strings b/Session/Meta/Translations/sk.lproj/Localizable.strings index 689e43c70..3aea597db 100644 --- a/Session/Meta/Translations/sk.lproj/Localizable.strings +++ b/Session/Meta/Translations/sk.lproj/Localizable.strings @@ -610,6 +610,7 @@ "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_TITLE" = "Are you sure you want to clear all message requests?"; "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear"; "MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?"; +"MESSAGE_REQUESTS_APPROVAL_ERROR_MESSAGE" = "An error occurred when trying to accept this message request"; "MESSAGE_REQUESTS_INFO" = "Sending a message to this user will automatically accept their message request."; "MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted."; "MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request"; diff --git a/Session/Meta/Translations/sv.lproj/Localizable.strings b/Session/Meta/Translations/sv.lproj/Localizable.strings index 5dc2ab6b3..dd873fce6 100644 --- a/Session/Meta/Translations/sv.lproj/Localizable.strings +++ b/Session/Meta/Translations/sv.lproj/Localizable.strings @@ -610,6 +610,7 @@ "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_TITLE" = "Are you sure you want to clear all message requests?"; "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear"; "MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?"; +"MESSAGE_REQUESTS_APPROVAL_ERROR_MESSAGE" = "An error occurred when trying to accept this message request"; "MESSAGE_REQUESTS_INFO" = "Sending a message to this user will automatically accept their message request."; "MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted."; "MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request"; diff --git a/Session/Meta/Translations/th.lproj/Localizable.strings b/Session/Meta/Translations/th.lproj/Localizable.strings index 582c9294a..b39834963 100644 --- a/Session/Meta/Translations/th.lproj/Localizable.strings +++ b/Session/Meta/Translations/th.lproj/Localizable.strings @@ -610,6 +610,7 @@ "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_TITLE" = "Are you sure you want to clear all message requests?"; "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear"; "MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?"; +"MESSAGE_REQUESTS_APPROVAL_ERROR_MESSAGE" = "An error occurred when trying to accept this message request"; "MESSAGE_REQUESTS_INFO" = "Sending a message to this user will automatically accept their message request."; "MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted."; "MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request"; diff --git a/Session/Meta/Translations/vi-VN.lproj/Localizable.strings b/Session/Meta/Translations/vi-VN.lproj/Localizable.strings index bcb302c87..fa1a72c4d 100644 --- a/Session/Meta/Translations/vi-VN.lproj/Localizable.strings +++ b/Session/Meta/Translations/vi-VN.lproj/Localizable.strings @@ -610,6 +610,7 @@ "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_TITLE" = "Are you sure you want to clear all message requests?"; "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear"; "MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?"; +"MESSAGE_REQUESTS_APPROVAL_ERROR_MESSAGE" = "An error occurred when trying to accept this message request"; "MESSAGE_REQUESTS_INFO" = "Sending a message to this user will automatically accept their message request."; "MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted."; "MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request"; diff --git a/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings b/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings index d806a7c61..871282699 100644 --- a/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings +++ b/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings @@ -610,6 +610,7 @@ "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_TITLE" = "Are you sure you want to clear all message requests?"; "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear"; "MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?"; +"MESSAGE_REQUESTS_APPROVAL_ERROR_MESSAGE" = "An error occurred when trying to accept this message request"; "MESSAGE_REQUESTS_INFO" = "Sending a message to this user will automatically accept their message request."; "MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted."; "MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request"; diff --git a/Session/Meta/Translations/zh_CN.lproj/Localizable.strings b/Session/Meta/Translations/zh_CN.lproj/Localizable.strings index da4f83b72..aa5bc4a74 100644 --- a/Session/Meta/Translations/zh_CN.lproj/Localizable.strings +++ b/Session/Meta/Translations/zh_CN.lproj/Localizable.strings @@ -610,6 +610,7 @@ "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_TITLE" = "Are you sure you want to clear all message requests?"; "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear"; "MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?"; +"MESSAGE_REQUESTS_APPROVAL_ERROR_MESSAGE" = "An error occurred when trying to accept this message request"; "MESSAGE_REQUESTS_INFO" = "Sending a message to this user will automatically accept their message request."; "MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted."; "MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request"; diff --git a/Session/Settings/SettingsVC.swift b/Session/Settings/SettingsVC.swift index 46be1b4d7..9641f53dd 100644 --- a/Session/Settings/SettingsVC.swift +++ b/Session/Settings/SettingsVC.swift @@ -139,9 +139,6 @@ final class SettingsVC : BaseVC, AvatarViewHelperDelegate { setUpNavBarStyle() setNavBarTitle(NSLocalizedString("vc_settings_title", comment: "")) // Navigation bar buttons - let backButton = UIBarButtonItem(title: "Back", style: .plain, target: nil, action: nil) - backButton.tintColor = Colors.text - navigationItem.backBarButtonItem = backButton updateNavigationBarButtons() // Profile picture view let profilePictureTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(showEditProfilePictureUI)) @@ -254,8 +251,6 @@ final class SettingsVC : BaseVC, AvatarViewHelperDelegate { pathStatusView.pin(.leading, to: .trailing, of: pathButton.titleLabel!, withInset: Values.smallSpacing) pathStatusView.autoVCenterInSuperview() - pathButton.titleEdgeInsets = UIEdgeInsets(top: 0, leading: 0, bottom: 0, trailing: Values.smallSpacing) - return [ getSeparator(), pathButton, diff --git a/Session/Shared/BaseVC.swift b/Session/Shared/BaseVC.swift index 1f2ad64d9..b0692e5b7 100644 --- a/Session/Shared/BaseVC.swift +++ b/Session/Shared/BaseVC.swift @@ -46,6 +46,7 @@ class BaseVC : UIViewController { internal func setUpNavBarStyle() { guard let navigationBar = navigationController?.navigationBar else { return } + if #available(iOS 15.0, *) { let appearance = UINavigationBarAppearance() appearance.configureWithOpaqueBackground() @@ -59,6 +60,11 @@ class BaseVC : UIViewController { navigationBar.isTranslucent = false navigationBar.barTintColor = Colors.navigationBarBackground } + + // Back button (to appear on pushed screen) + let backButton = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil) + backButton.tintColor = Colors.text + navigationItem.backBarButtonItem = backButton } internal func setNavBarTitle(_ title: String, customFontSize: CGFloat? = nil) { diff --git a/Session/Shared/ConversationCell.swift b/Session/Shared/ConversationCell.swift index 3d56f4ecb..1e376ed3d 100644 --- a/Session/Shared/ConversationCell.swift +++ b/Session/Shared/ConversationCell.swift @@ -358,15 +358,20 @@ final class ConversationCell : UITableViewCell { if threadViewModel.isGroupThread { if threadViewModel.name.isEmpty { return "Unknown Group" - } else { + } + else { return threadViewModel.name } - } else { + } + else { if threadViewModel.threadRecord.isNoteToSelf() { return NSLocalizedString("NOTE_TO_SELF", comment: "") - } else { - let hexEncodedPublicKey = threadViewModel.contactSessionID! - return Storage.shared.getContact(with: hexEncodedPublicKey)?.displayName(for: .regular) ?? hexEncodedPublicKey + } + else { + let hexEncodedPublicKey: String = threadViewModel.contactSessionID! + let displayName: String = (Storage.shared.getContact(with: hexEncodedPublicKey)?.displayName(for: .regular) ?? hexEncodedPublicKey) + let middleTruncatedHexKey: String = "\(hexEncodedPublicKey.prefix(4))...\(hexEncodedPublicKey.suffix(4))" + return (displayName == hexEncodedPublicKey ? middleTruncatedHexKey : displayName) } } } diff --git a/SessionMessagingKit/Messages/Signal/TSIncomingMessage.m b/SessionMessagingKit/Messages/Signal/TSIncomingMessage.m index e80bf0bdb..a6578df0c 100644 --- a/SessionMessagingKit/Messages/Signal/TSIncomingMessage.m +++ b/SessionMessagingKit/Messages/Signal/TSIncomingMessage.m @@ -140,16 +140,16 @@ NS_ASSUME_NONNULL_BEGIN return YES; } -- (void)markAsReadNowWithSendReadReceipt:(BOOL)sendReadReceipt +- (void)markAsReadNowWithTrySendReadReceipt:(BOOL)trySendReadReceipt transaction:(YapDatabaseReadWriteTransaction *)transaction; { [self markAsReadAtTimestamp:[NSDate millisecondTimestamp] - sendReadReceipt:sendReadReceipt + trySendReadReceipt:trySendReadReceipt transaction:transaction]; } - (void)markAsReadAtTimestamp:(uint64_t)readTimestamp - sendReadReceipt:(BOOL)sendReadReceipt + trySendReadReceipt:(BOOL)trySendReadReceipt transaction:(YapDatabaseReadWriteTransaction *)transaction; { if (_read && readTimestamp >= self.expireStartedAt) { @@ -174,7 +174,7 @@ NS_ASSUME_NONNULL_BEGIN expirationStartedAt:readTimestamp transaction:transaction]; - if (sendReadReceipt) { + if (trySendReadReceipt) { [OWSReadReceiptManager.sharedManager messageWasReadLocally:self]; } } diff --git a/SessionMessagingKit/Messages/Signal/TSInfoMessage.m b/SessionMessagingKit/Messages/Signal/TSInfoMessage.m index 5785196a6..9d303a1bf 100644 --- a/SessionMessagingKit/Messages/Signal/TSInfoMessage.m +++ b/SessionMessagingKit/Messages/Signal/TSInfoMessage.m @@ -134,7 +134,7 @@ NSUInteger TSInfoMessageSchemaVersion = 1; } - (void)markAsReadAtTimestamp:(uint64_t)readTimestamp - sendReadReceipt:(BOOL)sendReadReceipt + trySendReadReceipt:(BOOL)trySendReadReceipt transaction:(YapDatabaseReadWriteTransaction *)transaction { if (_read) { @@ -144,7 +144,7 @@ NSUInteger TSInfoMessageSchemaVersion = 1; _read = YES; [self saveWithTransaction:transaction]; - // Ignore sendReadReceipt, it doesn't apply to info messages. + // Ignore trySendReadReceipt, it doesn't apply to info messages. } @end diff --git a/SessionMessagingKit/Open Groups/OpenGroupManager.swift b/SessionMessagingKit/Open Groups/OpenGroupManager.swift index dd3e71863..37d5e339a 100644 --- a/SessionMessagingKit/Open Groups/OpenGroupManager.swift +++ b/SessionMessagingKit/Open Groups/OpenGroupManager.swift @@ -488,7 +488,8 @@ public final class OpenGroupManager: NSObject { // https://143.198.213.225:443/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c // 143.198.213.255:80/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c let useTLS = (url.scheme == "https") - let room = String(url.path.dropFirst()) // Drop the leading slash + let updatedPath = (url.path.starts(with: "/r/") ? url.path.substring(from: 2) : url.path) + let room = String(updatedPath.dropFirst()) // Drop the leading slash let queryParts = query.split(separator: "=") guard !room.isEmpty && !room.contains("/"), queryParts.count == 2, queryParts[0] == "public_key" else { return nil } let publicKey = String(queryParts[1]) diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift index 4093628be..55fd0a4d6 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift @@ -394,7 +394,7 @@ extension MessageReceiver { if let tsOutgoingMessage = TSMessage.fetch(uniqueId: tsMessageID, transaction: transaction) as? TSOutgoingMessage, let thread = TSThread.fetch(uniqueId: threadID, transaction: transaction) { // Mark previous messages as read if there is a sync message - OWSReadReceiptManager.shared().markAsReadLocally(beforeSortId: tsOutgoingMessage.sortId, thread: thread) + OWSReadReceiptManager.shared().markAsReadLocally(beforeSortId: tsOutgoingMessage.sortId, thread: thread, trySendReadReceipt: true) } // Update the contact's approval status of the current user if needed (if we are getting messages from diff --git a/SessionMessagingKit/Sending & Receiving/Read Tracking/OWSReadReceiptManager.h b/SessionMessagingKit/Sending & Receiving/Read Tracking/OWSReadReceiptManager.h index d2c07812b..c2dfddec5 100644 --- a/SessionMessagingKit/Sending & Receiving/Read Tracking/OWSReadReceiptManager.h +++ b/SessionMessagingKit/Sending & Receiving/Read Tracking/OWSReadReceiptManager.h @@ -45,7 +45,7 @@ extern NSString *const kIncomingMessageMarkedAsReadNotification; // This method can be called from any thread. - (void)messageWasReadLocally:(TSIncomingMessage *)message; -- (void)markAsReadLocallyBeforeSortId:(uint64_t)sortId thread:(TSThread *)thread; +- (void)markAsReadLocallyBeforeSortId:(uint64_t)sortId thread:(TSThread *)thread trySendReadReceipt:(BOOL)trySendReadReceipt; #pragma mark - Settings diff --git a/SessionMessagingKit/Sending & Receiving/Read Tracking/OWSReadReceiptManager.m b/SessionMessagingKit/Sending & Receiving/Read Tracking/OWSReadReceiptManager.m index 13a1b39c2..4517e2be4 100644 --- a/SessionMessagingKit/Sending & Receiving/Read Tracking/OWSReadReceiptManager.m +++ b/SessionMessagingKit/Sending & Receiving/Read Tracking/OWSReadReceiptManager.m @@ -180,13 +180,13 @@ NSString *const OWSReadReceiptManagerAreReadReceiptsEnabled = @"areReadReceiptsE #pragma mark - Mark as Read Locally -- (void)markAsReadLocallyBeforeSortId:(uint64_t)sortId thread:(TSThread *)thread +- (void)markAsReadLocallyBeforeSortId:(uint64_t)sortId thread:(TSThread *)thread trySendReadReceipt:(BOOL)trySendReadReceipt { [LKStorage writeWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { [self markAsReadBeforeSortId:sortId thread:thread readTimestamp:[NSDate millisecondTimestamp] - wasLocal:YES + trySendReadReceipt:trySendReadReceipt transaction:transaction]; }]; } @@ -254,7 +254,7 @@ NSString *const OWSReadReceiptManagerAreReadReceiptsEnabled = @"areReadReceiptsE - (void)markAsReadBeforeSortId:(uint64_t)sortId thread:(TSThread *)thread readTimestamp:(uint64_t)readTimestamp - wasLocal:(BOOL)wasLocal + trySendReadReceipt:(BOOL)trySendReadReceipt transaction:(YapDatabaseReadWriteTransaction *)transaction { NSMutableArray> *newlyReadList = [NSMutableArray new]; @@ -285,7 +285,7 @@ NSString *const OWSReadReceiptManagerAreReadReceiptsEnabled = @"areReadReceiptsE } for (id readItem in newlyReadList) { - [readItem markAsReadAtTimestamp:readTimestamp sendReadReceipt:wasLocal transaction:transaction]; + [readItem markAsReadAtTimestamp:readTimestamp trySendReadReceipt:trySendReadReceipt transaction:transaction]; } } diff --git a/SessionMessagingKit/Sending & Receiving/Read Tracking/OWSReadTracking.h b/SessionMessagingKit/Sending & Receiving/Read Tracking/OWSReadTracking.h index d8afaaa41..b08071959 100644 --- a/SessionMessagingKit/Sending & Receiving/Read Tracking/OWSReadTracking.h +++ b/SessionMessagingKit/Sending & Receiving/Read Tracking/OWSReadTracking.h @@ -29,7 +29,7 @@ NS_ASSUME_NONNULL_BEGIN * Used both for *responding* to a remote read receipt and in response to the local user's activity. */ - (void)markAsReadAtTimestamp:(uint64_t)readTimestamp - sendReadReceipt:(BOOL)sendReadReceipt + trySendReadReceipt:(BOOL)trySendReadReceipt transaction:(YapDatabaseReadWriteTransaction *)transaction; @end diff --git a/SessionMessagingKit/Threads/TSThread.m b/SessionMessagingKit/Threads/TSThread.m index 91484bba7..00049a305 100644 --- a/SessionMessagingKit/Threads/TSThread.m +++ b/SessionMessagingKit/Threads/TSThread.m @@ -309,7 +309,7 @@ BOOL IsNoteToSelfEnabled(void) - (void)markAllAsReadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction { for (id message in [self unseenMessagesWithTransaction:transaction]) { - [message markAsReadAtTimestamp:[NSDate ows_millisecondTimeStamp] sendReadReceipt:YES transaction:transaction]; + [message markAsReadAtTimestamp:[NSDate ows_millisecondTimeStamp] trySendReadReceipt:YES transaction:transaction]; } } diff --git a/SessionUIKit/Components/Button.swift b/SessionUIKit/Components/Button.swift index 93aaaf6f6..adb45f592 100644 --- a/SessionUIKit/Components/Button.swift +++ b/SessionUIKit/Components/Button.swift @@ -6,7 +6,7 @@ public final class Button : UIButton { private var heightConstraint: NSLayoutConstraint! public enum Style { - case unimportant, regular, prominentOutline, prominentFilled, regularBorderless + case unimportant, regular, prominentOutline, prominentFilled, regularBorderless, destructiveOutline } public enum Size { @@ -41,6 +41,7 @@ public final class Button : UIButton { case .prominentOutline: fillColor = UIColor.clear case .prominentFilled: fillColor = isLightMode ? Colors.text : Colors.accent case .regularBorderless: fillColor = UIColor.clear + case .destructiveOutline: fillColor = UIColor.clear } let borderColor: UIColor switch style { @@ -49,6 +50,7 @@ public final class Button : UIButton { case .prominentOutline: borderColor = isLightMode ? Colors.text : Colors.accent case .prominentFilled: borderColor = isLightMode ? Colors.text : Colors.accent case .regularBorderless: borderColor = UIColor.clear + case .destructiveOutline: borderColor = Colors.destructive } let textColor: UIColor switch style { @@ -57,6 +59,7 @@ public final class Button : UIButton { case .prominentOutline: textColor = isLightMode ? Colors.text : Colors.accent case .prominentFilled: textColor = isLightMode ? UIColor.white : Colors.text case .regularBorderless: textColor = Colors.text + case .destructiveOutline: textColor = Colors.destructive } let height: CGFloat switch size { diff --git a/SignalUtilitiesKit/Messaging/OWSMessageUtils.h b/SignalUtilitiesKit/Messaging/OWSMessageUtils.h index ef693b8dc..3069360bf 100644 --- a/SignalUtilitiesKit/Messaging/OWSMessageUtils.h +++ b/SignalUtilitiesKit/Messaging/OWSMessageUtils.h @@ -16,6 +16,7 @@ NS_ASSUME_NONNULL_BEGIN + (instancetype)sharedManager; - (NSUInteger)unreadMessagesCount; +- (NSUInteger)unreadMessageRequestCount; - (NSUInteger)unreadMessagesCountExcept:(TSThread *)thread; - (void)updateApplicationBadgeCount; diff --git a/SignalUtilitiesKit/Messaging/OWSMessageUtils.m b/SignalUtilitiesKit/Messaging/OWSMessageUtils.m index fe8d0a4d1..b07a80bf8 100644 --- a/SignalUtilitiesKit/Messaging/OWSMessageUtils.m +++ b/SignalUtilitiesKit/Messaging/OWSMessageUtils.m @@ -112,6 +112,38 @@ NS_ASSUME_NONNULL_BEGIN return numberOfItems; } +- (NSUInteger)unreadMessageRequestCount { + __block NSUInteger count = 0; + + [LKStorage readWithBlock:^(YapDatabaseReadTransaction *transaction) { + YapDatabaseViewTransaction *unreadMessages = [transaction ext:TSUnreadDatabaseViewExtensionName]; + NSArray *allGroups = [unreadMessages allGroups]; + // FIXME: Confusingly, `allGroups` includes contact threads as well + for (NSString *groupID in allGroups) { + TSThread *thread = [TSThread fetchObjectWithUniqueID:groupID transaction:transaction]; + + // Only increase the count for message requests + if (!thread.isMessageRequest) { continue; } + + [unreadMessages enumerateKeysAndObjectsInGroup:groupID + usingBlock:^(NSString *collection, NSString *key, id object, NSUInteger index, BOOL *stop) { + if (![object conformsToProtocol:@protocol(OWSReadTracking)]) { + return; + } + id unread = (id)object; + if (unread.read) { + NSLog(@"Found an already read message in the * unread * messages list."); + return; + } + count += 1; + *stop = YES; + }]; + } + }]; + + return count; +} + - (NSUInteger)unreadMessagesCountExcept:(TSThread *)thread { __block NSUInteger numberOfItems; diff --git a/SignalUtilitiesKit/Shared View Controllers/ModalActivityIndicatorViewController.swift b/SignalUtilitiesKit/Shared View Controllers/ModalActivityIndicatorViewController.swift index c99a28310..a38915a40 100644 --- a/SignalUtilitiesKit/Shared View Controllers/ModalActivityIndicatorViewController.swift +++ b/SignalUtilitiesKit/Shared View Controllers/ModalActivityIndicatorViewController.swift @@ -25,8 +25,6 @@ public class ModalActivityIndicatorViewController: OWSViewController { return result }() - var presentTimer: Timer? - var wasDimissed: Bool = false // MARK: Initializers @@ -127,41 +125,7 @@ public class ModalActivityIndicatorViewController: OWSViewController { self.spinner.startAnimating() - // Hide the the modal and wait for a second before revealing it, - // to avoid "blipping" in the modal during short blocking operations. - // - // NOTE: It will still intercept user interactions while hidden, as it - // should. - let kPresentationDelaySeconds = TimeInterval(1) - self.presentTimer?.invalidate() - self.presentTimer = Timer.weakScheduledTimer(withTimeInterval: kPresentationDelaySeconds, target: self, selector: #selector(presentTimerFired), userInfo: nil, repeats: false) - } - - public override func viewWillDisappear(_ animated: Bool) { - super.viewWillDisappear(animated) - - clearTimer() - } - - public override func viewDidDisappear(_ animated: Bool) { - super.viewDidDisappear(animated) - - self.spinner.stopAnimating() - - clearTimer() - } - - private func clearTimer() { - self.presentTimer?.invalidate() - self.presentTimer = nil - } - - @objc func presentTimerFired() { - AssertIsOnMainThread() - - clearTimer() - - // Fade in the modal. + // Fade in the modal UIView.animate(withDuration: 0.35) { self.view.layer.opacity = 1.0 } diff --git a/SignalUtilitiesKit/Utilities/UIViewController+Utilities.swift b/SignalUtilitiesKit/Utilities/UIViewController+Utilities.swift index 582f9d38f..e5a0acb48 100644 --- a/SignalUtilitiesKit/Utilities/UIViewController+Utilities.swift +++ b/SignalUtilitiesKit/Utilities/UIViewController+Utilities.swift @@ -28,7 +28,7 @@ public final class ViewControllerUtilities : NSObject { } // Set up back button if hasCustomBackButton { - let backButton = UIBarButtonItem(title: "Back", style: .plain, target: nil, action: nil) + let backButton = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil) backButton.tintColor = Colors.text vc.navigationItem.backBarButtonItem = backButton }