diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index 33ef7e6dc..cec8846ca 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -10,6 +10,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() } @@ -292,32 +302,56 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc 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() - - // Attachment successfully sent - dismiss the screen - onComplete?() - }) + + 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.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 + ) + }, + 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?() + } + ) + } + ) } func handleMessageSent() { diff --git a/Session/Conversations/ConversationVC.swift b/Session/Conversations/ConversationVC.swift index 932f2c434..f3fee082f 100644 --- a/Session/Conversations/ConversationVC.swift +++ b/Session/Conversations/ConversationVC.swift @@ -1,5 +1,6 @@ import SessionUIKit import SessionMessagingKit +import UIKit // TODO: // • Slight paging glitch when scrolling up and loading more content @@ -465,30 +466,53 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat // MARK: Updating func updateNavBarButtons() { + // Back button (to appear on pushed screen) + let backButton = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil) + backButton.tintColor = Colors.text + navigationItem.backBarButtonItem = backButton + + navigationItem.hidesBackButton = isShowingSearchUI + if isShowingSearchUI { navigationItem.leftBarButtonItem = nil navigationItem.rightBarButtonItems = [] - } else { + } + else { + navigationItem.hidesBackButton = false 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 } } @@ -819,7 +843,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 5585b155d..fcfb6d588 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 d80912b29..a557e3666 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 f8635f167..01677bf05 100644 --- a/Session/Home/HomeVC.swift +++ b/Session/Home/HomeVC.swift @@ -393,11 +393,18 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, NewConv profilePictureViewContainer.addSubview(pathStatusView) pathStatusView.pin(.trailing, to: .trailing, of: profilePictureViewContainer) pathStatusView.pin(.bottom, to: .bottom, of: profilePictureViewContainer) + + // Back button (to appear on pushed screen) + let backButton = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil) + backButton.tintColor = Colors.text + navigationItem.backBarButtonItem = backButton + // 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" 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/Settings/SettingsVC.swift b/Session/Settings/SettingsVC.swift index 46be1b4d7..72228f5a8 100644 --- a/Session/Settings/SettingsVC.swift +++ b/Session/Settings/SettingsVC.swift @@ -139,7 +139,7 @@ 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) + let backButton = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil) backButton.tintColor = Colors.text navigationItem.backBarButtonItem = backButton updateNavigationBarButtons() @@ -254,8 +254,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/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/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/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 }