Merge branch 'dev' into feature/session-id-blinding-part-2

# Conflicts:
#	Session/Conversations/ConversationVC+Interaction.swift
#	SessionMessagingKit/Open Groups/OpenGroupManagerV2.swift
pull/592/head
Morgan Pretty 3 years ago
commit cb288ca09c

@ -229,7 +229,7 @@ public class ConversationMessageMapping: NSObject {
let indexPtr: UnsafeMutablePointer<UInt> = UnsafeMutablePointer<UInt>.allocate(capacity: 1) let indexPtr: UnsafeMutablePointer<UInt> = UnsafeMutablePointer<UInt>.allocate(capacity: 1)
let wasFound = view.getGroup(nil, index: indexPtr, forKey: uniqueId, inCollection: TSInteraction.collection()) let wasFound = view.getGroup(nil, index: indexPtr, forKey: uniqueId, inCollection: TSInteraction.collection())
guard wasFound else { guard wasFound else {
owsFailDebug("Could not find interaction.") SNLog("Could not find interaction.")
return nil return nil
} }
let index = indexPtr.pointee let index = indexPtr.pointee

@ -3,6 +3,7 @@ import CoreServices
import Photos import Photos
import PhotosUI import PhotosUI
import Sodium import Sodium
import PromiseKit
import SessionUtilitiesKit import SessionUtilitiesKit
import SignalUtilitiesKit import SignalUtilitiesKit
@ -11,6 +12,16 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc
ConversationTitleViewDelegate { ConversationTitleViewDelegate {
func handleTitleViewTapped() { 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() openSettings()
} }
@ -253,71 +264,109 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc
let linkPreviewDraft = snInputView.linkPreviewInfo?.draft let linkPreviewDraft = snInputView.linkPreviewInfo?.draft
let tsMessage = TSOutgoingMessage.from(message, associatedWith: thread) let tsMessage = TSOutgoingMessage.from(message, associatedWith: thread)
viewModel.appendUnsavedOutgoingTextMessage(tsMessage)
Storage.write(with: { transaction in Storage.write(with: { transaction in
message.linkPreview = VisibleMessage.LinkPreview.from(linkPreviewDraft, using: transaction) let promise: Promise<Void> = self.approveMessageRequestIfNeeded(
}, completion: { [weak self] in for: self.thread,
tsMessage.linkPreview = OWSLinkPreview.from(message.linkPreview) with: transaction,
isNewThread: !oldThreadShouldBeVisible,
Storage.shared.write( timestamp: (sentTimestamp - 1) // Set 1ms earlier as this is used for sorting
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)
}
) )
.map { [weak self] _ in
Storage.shared.write( self?.viewModel.appendUnsavedOutgoingTextMessage(tsMessage)
with: { transaction in
self?.approveMessageRequestIfNeeded( Storage.write(with: { transaction in
for: self?.thread, message.linkPreview = VisibleMessage.LinkPreview.from(linkPreviewDraft, using: transaction)
with: (transaction as! YapDatabaseReadWriteTransaction), }, completion: { [weak self] in
isNewThread: !oldThreadShouldBeVisible, tsMessage.linkPreview = OWSLinkPreview.from(message.linkPreview)
timestamp: (sentTimestamp - 1) // Set 1ms earlier as this is used for sorting
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 Storage.shared.write { transaction in
MessageSender.send(message, with: [], in: thread, using: transaction as! YapDatabaseReadWriteTransaction) MessageSender.send(message, with: [], in: thread, using: transaction as! YapDatabaseReadWriteTransaction)
} }
self?.handleMessageSent() 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) { func sendAttachments(_ attachments: [SignalAttachment], with text: String, onComplete: (() -> ())? = nil) {
guard !showBlockedModalIfNeeded() else { return } guard !showBlockedModalIfNeeded() else { return }
for attachment in attachments { for attachment in attachments {
if attachment.hasError { if attachment.hasError {
return showErrorAlert(for: attachment, onDismiss: onComplete) return showErrorAlert(for: attachment, onDismiss: onComplete)
} }
} }
let thread = self.thread let thread = self.thread
let sentTimestamp: UInt64 = NSDate.millisecondTimestamp()
let message = VisibleMessage() let message = VisibleMessage()
message.sentTimestamp = NSDate.millisecondTimestamp() message.sentTimestamp = sentTimestamp
message.text = replaceMentions(in: text) 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) let tsMessage = TSOutgoingMessage.from(message, associatedWith: thread)
Storage.write(with: { transaction in Storage.write(with: { transaction in
tsMessage.save(with: transaction) let promise: Promise<Void> = self.approveMessageRequestIfNeeded(
// The new message cell is inserted at this point, but the TSOutgoingMessage doesn't have its attachment yet for: self.thread,
}, completion: { [weak self] in with: transaction,
Storage.write(with: { transaction in isNewThread: !oldThreadShouldBeVisible,
MessageSender.send(message, with: attachments, in: thread, using: transaction) timestamp: (sentTimestamp - 1) // Set 1ms earlier as this is used for sorting
}, completion: { [weak self] in )
// At this point the TSOutgoingMessage should have its attachments set, so we can scroll to the bottom knowing .map { [weak self] _ in
// the height of the new message cell Storage.write(
self?.scrollToBottom(isAnimated: false) with: { transaction in
}) tsMessage.save(with: transaction)
self?.handleMessageSent() // 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 promise.retainUntilComplete()
onComplete?()
}) })
} }
@ -1178,8 +1227,8 @@ extension ConversationVC {
navigationController?.popToViewController(viewControllers[messageRequestsIndex - 1], animated: true) navigationController?.popToViewController(viewControllers[messageRequestsIndex - 1], animated: true)
} }
fileprivate func approveMessageRequestIfNeeded(for thread: TSThread?, with transaction: YapDatabaseReadWriteTransaction, isNewThread: Bool, timestamp: UInt64) { fileprivate func approveMessageRequestIfNeeded(for thread: TSThread?, with transaction: YapDatabaseReadWriteTransaction, isNewThread: Bool, timestamp: UInt64) -> Promise<Void> {
guard let contactThread: TSContactThread = thread as? TSContactThread else { return } 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 // 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 // (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 sessionId: String = contactThread.contactSessionID()
let contact: Contact = (Storage.shared.getContact(with: sessionId) ?? Contact(sessionID: sessionId)) let contact: Contact = (Storage.shared.getContact(with: sessionId) ?? Contact(sessionID: sessionId))
if !contact.isApproved { guard !contact.isApproved else { return Promise.value(()) }
// Default 'didApproveMe' to true for the person approving the message request
contact.isApproved = true return Promise.value(())
contact.didApproveMe = (contact.didApproveMe || !isNewThread) .then { [weak self] _ -> Promise<Void> in
Storage.shared.setContact(contact, using: transaction) 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 // If we aren't creating a new thread (ie. sending a message request) then send a
// they have been approved and can now use this contact in closed groups) // messageRequestResponse back to the sender (this allows the sender to know that
if !isNewThread { // they have been approved and can now use this contact in closed groups)
let (promise, seal) = Promise<Void>.pending()
let messageRequestResponse: MessageRequestResponse = MessageRequestResponse( let messageRequestResponse: MessageRequestResponse = MessageRequestResponse(
isApproved: true isApproved: true
) )
messageRequestResponse.sentTimestamp = timestamp 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
}
}
} }
.map { _ in
// Hide the 'messageRequestView' since the request has been approved and force a config // Default 'didApproveMe' to true for the person approving the message request
// sync to propagate the contact approval state (both must run on the main thread) contact.isApproved = true
DispatchQueue.main.async { [weak self] in contact.didApproveMe = (contact.didApproveMe || !isNewThread)
let messageRequestViewWasVisible: Bool = (self?.messageRequestView.isHidden == false) Storage.shared.setContact(contact, using: transaction)
UIView.animate(withDuration: 0.3) { // Hide the 'messageRequestView' since the request has been approved and force a config
self?.messageRequestView.isHidden = true // sync to propagate the contact approval state (both must run on the main thread)
self?.scrollButtonMessageRequestsBottomConstraint?.isActive = false DispatchQueue.main.async { [weak self] in
self?.scrollButtonBottomConstraint?.isActive = true let messageRequestViewWasVisible: Bool = (self?.messageRequestView.isHidden == false)
// Update the table content inset and offset to account for the dissapearance of UIView.animate(withDuration: 0.3) {
// the messageRequestsView self?.messageRequestView.isHidden = true
if messageRequestViewWasVisible { self?.scrollButtonMessageRequestsBottomConstraint?.isActive = false
let messageRequestsOffset: CGFloat = ((self?.messageRequestView.bounds.height ?? 0) + 16) self?.scrollButtonBottomConstraint?.isActive = true
let oldContentInset: UIEdgeInsets = (self?.messagesTableView.contentInset ?? UIEdgeInsets.zero)
self?.messagesTableView.contentInset = UIEdgeInsets( // Update the table content inset and offset to account for the dissapearance of
top: 0, // the messageRequestsView
leading: 0, if messageRequestViewWasVisible {
bottom: max(oldContentInset.bottom - messageRequestsOffset, 0), let messageRequestsOffset: CGFloat = ((self?.messageRequestView.bounds.height ?? 0) + 16)
trailing: 0 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() { @objc func acceptMessageRequest() {
Storage.write { [weak self] transaction in Storage.write { transaction in
self?.approveMessageRequestIfNeeded( let promise: Promise<Void> = self.approveMessageRequestIfNeeded(
for: self?.thread, for: self.thread,
with: transaction, with: transaction,
isNewThread: false, isNewThread: false,
timestamp: NSDate.millisecondTimestamp() 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()
} }
} }

@ -1,6 +1,7 @@
import UIKit import UIKit
import SessionUIKit import SessionUIKit
import SessionMessagingKit import SessionMessagingKit
import UIKit
// TODO: // TODO:
// Slight paging glitch when scrolling up and loading more content // Slight paging glitch when scrolling up and loading more content
@ -474,30 +475,47 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat
// MARK: Updating // MARK: Updating
func updateNavBarButtons() { func updateNavBarButtons() {
navigationItem.hidesBackButton = isShowingSearchUI
if isShowingSearchUI { if isShowingSearchUI {
navigationItem.leftBarButtonItem = nil navigationItem.leftBarButtonItem = nil
navigationItem.rightBarButtonItems = [] navigationItem.rightBarButtonItems = []
} else { }
else {
navigationItem.leftBarButtonItem = UIViewController.createOWSBackButton(withTarget: self, selector: #selector(handleBackPressed)) navigationItem.leftBarButtonItem = UIViewController.createOWSBackButton(withTarget: self, selector: #selector(handleBackPressed))
let rightBarButtonItem: UIBarButtonItem if let contactThread: TSContactThread = thread as? TSContactThread {
if thread is TSContactThread { // Don't show the settings button for message requests
let size = Values.verySmallProfilePictureSize if let contact: Contact = Storage.shared.getContact(with: contactThread.contactSessionID()), contact.isApproved, contact.didApproveMe {
let profilePictureView = ProfilePictureView() let size = Values.verySmallProfilePictureSize
profilePictureView.accessibilityLabel = "Settings button" let profilePictureView = ProfilePictureView()
profilePictureView.size = size profilePictureView.size = size
profilePictureView.update(for: thread) profilePictureView.update(for: thread)
profilePictureView.set(.width, to: size) profilePictureView.set(.width, to: size)
profilePictureView.set(.height, to: size) profilePictureView.set(.height, to: size)
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(openSettings))
profilePictureView.addGestureRecognizer(tapGestureRecognizer) let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(openSettings))
rightBarButtonItem = UIBarButtonItem(customView: profilePictureView) profilePictureView.addGestureRecognizer(tapGestureRecognizer)
} else {
rightBarButtonItem = UIBarButtonItem(image: UIImage(named: "Gear"), style: .plain, target: self, action: #selector(openSettings)) 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() { func markAllAsRead() {
guard !thread.isMessageRequest() else { return }
guard let lastSortID = viewItems.last?.interaction.sortId 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() SSKEnvironment.shared.disappearingMessagesJob.cleanupMessagesWhichFailedToStartExpiringFromNow()
} }
@ -912,7 +933,7 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat
let searchBar = searchController.uiSearchController.searchBar let searchBar = searchController.uiSearchController.searchBar
searchBar.searchBarStyle = .minimal searchBar.searchBarStyle = .minimal
searchBar.barStyle = .black searchBar.barStyle = .black
searchBar.tintColor = Colors.accent searchBar.tintColor = Colors.text
let searchIcon = UIImage(named: "searchbar_search")!.asTintedImage(color: Colors.searchBarPlaceholder) let searchIcon = UIImage(named: "searchbar_search")!.asTintedImage(color: Colors.searchBarPlaceholder)
searchBar.setImage(searchIcon, for: .search, state: UIControl.State.normal) searchBar.setImage(searchIcon, for: .search, state: UIControl.State.normal)
let clearIcon = UIImage(named: "searchbar_clear")!.asTintedImage(color: Colors.searchBarPlaceholder) let clearIcon = UIImage(named: "searchbar_clear")!.asTintedImage(color: Colors.searchBarPlaceholder)

@ -277,7 +277,7 @@ CGFloat kIconViewLength = 24;
contents.title = NSLocalizedString(@"CONVERSATION_SETTINGS", @"title for conversation settings screen"); contents.title = NSLocalizedString(@"CONVERSATION_SETTINGS", @"title for conversation settings screen");
BOOL isNoteToSelf = self.thread.isNoteToSelf; BOOL isNoteToSelf = self.thread.isNoteToSelf;
__weak OWSConversationSettingsViewController *weakSelf = self; __weak OWSConversationSettingsViewController *weakSelf = self;
OWSTableSection *section = [OWSTableSection new]; OWSTableSection *section = [OWSTableSection new];
@ -332,7 +332,7 @@ CGFloat kIconViewLength = 24;
} actionBlock:^{ } actionBlock:^{
[weakSelf tappedConversationSearch]; [weakSelf tappedConversationSearch];
}]]; }]];
// Disappearing messages // Disappearing messages
if (![self isOpenGroup]) { if (![self isOpenGroup]) {
[section addItem:[OWSTableItem itemWithCustomCellBlock:^{ [section addItem:[OWSTableItem itemWithCustomCellBlock:^{
@ -358,13 +358,6 @@ CGFloat kIconViewLength = 24;
switchView.on = strongSelf.disappearingMessagesConfiguration.isEnabled; switchView.on = strongSelf.disappearingMessagesConfiguration.isEnabled;
[switchView addTarget:strongSelf action:@selector(disappearingMessagesSwitchValueDidChange:) [switchView addTarget:strongSelf action:@selector(disappearingMessagesSwitchValueDidChange:)
forControlEvents:UIControlEventValueChanged]; 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 *topRow =
[[UIStackView alloc] initWithArrangedSubviews:@[ iconView, rowLabel, switchView ]]; [[UIStackView alloc] initWithArrangedSubviews:@[ iconView, rowLabel, switchView ]];
@ -438,13 +431,6 @@ CGFloat kIconViewLength = 24;
[slider autoPinTrailingToSuperviewMargin]; [slider autoPinTrailingToSuperviewMargin];
[slider autoPinBottomToSuperviewMargin]; [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.userInteractionEnabled = !strongSelf.hasLeftGroup;
cell.accessibilityIdentifier = ACCESSIBILITY_IDENTIFIER_WITH_NAME( cell.accessibilityIdentifier = ACCESSIBILITY_IDENTIFIER_WITH_NAME(

@ -73,13 +73,17 @@ final class ConversationTitleView : UIView {
private func getTitle() -> String { private func getTitle() -> String {
if let thread = thread as? TSGroupThread { if let thread = thread as? TSGroupThread {
return thread.groupModel.groupName! return thread.groupModel.groupName!
} else if thread.isNoteToSelf() { }
else if thread.isNoteToSelf() {
return "Note to Self" return "Note to Self"
} else { }
else {
let sessionID = (thread as! TSContactThread).contactSessionID() let sessionID = (thread as! TSContactThread).contactSessionID()
var result = sessionID var result = sessionID
Storage.read { transaction in 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 return result
} }

@ -6,6 +6,7 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, NewConv
private var threads: YapDatabaseViewMappings! private var threads: YapDatabaseViewMappings!
private var threadViewModelCache: [String:ThreadViewModel] = [:] // Thread ID to ThreadViewModel private var threadViewModelCache: [String:ThreadViewModel] = [:] // Thread ID to ThreadViewModel
private var tableViewTopConstraint: NSLayoutConstraint! private var tableViewTopConstraint: NSLayoutConstraint!
private var unreadMessageRequestCount: UInt = 0
private var messageRequestCount: UInt { private var messageRequestCount: UInt {
threads.numberOfItems(inGroup: TSMessageRequestGroup) threads.numberOfItems(inGroup: TSMessageRequestGroup)
@ -196,7 +197,7 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, NewConv
switch indexPath.section { switch indexPath.section {
case 0: case 0:
let cell = tableView.dequeueReusableCell(withIdentifier: MessageRequestsCell.reuseIdentifier) as! MessageRequestsCell let cell = tableView.dequeueReusableCell(withIdentifier: MessageRequestsCell.reuseIdentifier) as! MessageRequestsCell
cell.update(with: Int(messageRequestCount)) cell.update(with: Int(unreadMessageRequestCount))
return cell return cell
default: 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() 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 we need to unhide the message request row and then re-insert it
if !messageRequestChanges.isEmpty { 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) tableView.deleteRows(at: [IndexPath(row: 0, section: 0)], with: .automatic)
} }
else if tableView.numberOfRows(inSection: 0) == 0 && Int(messageRequestCount) > 0 && !CurrentAppContext().appUserDefaults()[.hasHiddenMessageRequests] { else {
tableView.insertRows(at: [IndexPath(row: 0, section: 0)], with: .automatic) 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) profilePictureViewContainer.addSubview(pathStatusView)
pathStatusView.pin(.trailing, to: .trailing, of: profilePictureViewContainer) pathStatusView.pin(.trailing, to: .trailing, of: profilePictureViewContainer)
pathStatusView.pin(.bottom, to: .bottom, of: profilePictureViewContainer) pathStatusView.pin(.bottom, to: .bottom, of: profilePictureViewContainer)
// Left bar button item // Left bar button item
let leftBarButtonItem = UIBarButtonItem(customView: profilePictureViewContainer) let leftBarButtonItem = UIBarButtonItem(customView: profilePictureViewContainer)
leftBarButtonItem.accessibilityLabel = "Settings button" leftBarButtonItem.accessibilityLabel = "Settings button"
leftBarButtonItem.isAccessibilityElement = true leftBarButtonItem.isAccessibilityElement = true
navigationItem.leftBarButtonItem = leftBarButtonItem navigationItem.leftBarButtonItem = leftBarButtonItem
// Right bar button item - search button // Right bar button item - search button
let rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .search, target: self, action: #selector(showSearchUI)) let rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .search, target: self, action: #selector(showSearchUI))
rightBarButtonItem.accessibilityLabel = "Search button" rightBarButtonItem.accessibilityLabel = "Search button"
@ -542,6 +563,7 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, NewConv
@objc private func openSettings() { @objc private func openSettings() {
let settingsVC = SettingsVC() let settingsVC = SettingsVC()
let navigationController = OWSNavigationController(rootViewController: settingsVC) let navigationController = OWSNavigationController(rootViewController: settingsVC)
navigationController.modalPresentationStyle = .fullScreen
present(navigationController, animated: true, completion: nil) present(navigationController, animated: true, completion: nil)
} }

@ -63,23 +63,16 @@ class MessageRequestsViewController: BaseVC, UITableViewDelegate, UITableViewDat
return result return result
}() }()
private lazy var clearAllButton: UIButton = { private lazy var clearAllButton: Button = {
let result: UIButton = UIButton() let result: Button = Button(style: .destructiveOutline, size: .large)
result.translatesAutoresizingMaskIntoConstraints = false result.translatesAutoresizingMaskIntoConstraints = false
result.clipsToBounds = true
result.titleLabel?.font = UIFont.boldSystemFont(ofSize: 18)
result.setTitle(NSLocalizedString("MESSAGE_REQUESTS_CLEAR_ALL", comment: ""), for: .normal) result.setTitle(NSLocalizedString("MESSAGE_REQUESTS_CLEAR_ALL", comment: ""), for: .normal)
result.setTitleColor(Colors.destructive, for: .normal)
result.setBackgroundImage( result.setBackgroundImage(
Colors.destructive Colors.destructive
.withAlphaComponent(isDarkMode ? 0.2 : 0.06) .withAlphaComponent(isDarkMode ? 0.2 : 0.06)
.toImage(isDarkMode: isDarkMode), .toImage(isDarkMode: isDarkMode),
for: .highlighted 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) result.addTarget(self, action: #selector(clearAllTapped), for: .touchUpInside)
return result return result
@ -163,10 +156,11 @@ class MessageRequestsViewController: BaseVC, UITableViewDelegate, UITableViewDat
clearAllButton.centerXAnchor.constraint(equalTo: view.centerXAnchor), clearAllButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
clearAllButton.bottomAnchor.constraint( clearAllButton.bottomAnchor.constraint(
equalTo: view.bottomAnchor, equalTo: view.safeAreaLayoutGuide.bottomAnchor,
constant: -Values.newConversationButtonBottomOffset // Negative due to how the constraint is set up 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) clearAllButton.heightAnchor.constraint(equalToConstant: NewConversationButtonSet.collapsedButtonSize)
]) ])
} }

@ -76,7 +76,7 @@ class MessageRequestsCell: UITableViewCell {
}() }()
private func setUpViewHierarchy() { private func setUpViewHierarchy() {
backgroundColor = Colors.cellBackground backgroundColor = Colors.cellPinned
selectedBackgroundView = UIView() selectedBackgroundView = UIView()
selectedBackgroundView?.backgroundColor = Colors.cellSelected selectedBackgroundView?.backgroundColor = Colors.cellSelected

@ -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_TITLE" = "Are you sure you want to clear all message requests?";
"MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear"; "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear";
"MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?"; "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_INFO" = "Sending a message to this user will automatically accept their message request.";
"MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted."; "MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted.";
"MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request"; "MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request";

@ -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_TITLE" = "Are you sure you want to clear all message requests?";
"MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear"; "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear";
"MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?"; "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_INFO" = "Sending a message to this user will automatically accept their message request.";
"MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted."; "MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted.";
"MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request"; "MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request";

@ -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_TITLE" = "Are you sure you want to clear all message requests?";
"MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear"; "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear";
"MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?"; "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_INFO" = "Sending a message to this user will automatically accept their message request.";
"MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted."; "MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted.";
"MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request"; "MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request";

@ -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_TITLE" = "Are you sure you want to clear all message requests?";
"MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear"; "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear";
"MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?"; "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_INFO" = "Sending a message to this user will automatically accept their message request.";
"MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted."; "MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted.";
"MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request"; "MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request";

@ -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_TITLE" = "Are you sure you want to clear all message requests?";
"MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear"; "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear";
"MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?"; "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_INFO" = "Sending a message to this user will automatically accept their message request.";
"MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted."; "MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted.";
"MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request"; "MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request";

@ -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_TITLE" = "Are you sure you want to clear all message requests?";
"MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear"; "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear";
"MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?"; "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_INFO" = "Sending a message to this user will automatically accept their message request.";
"MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted."; "MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted.";
"MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request"; "MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request";

@ -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_TITLE" = "Are you sure you want to clear all message requests?";
"MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear"; "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear";
"MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?"; "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_INFO" = "Sending a message to this user will automatically accept their message request.";
"MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted."; "MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted.";
"MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request"; "MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request";

@ -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_TITLE" = "Are you sure you want to clear all message requests?";
"MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear"; "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear";
"MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?"; "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_INFO" = "Sending a message to this user will automatically accept their message request.";
"MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted."; "MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted.";
"MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request"; "MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request";

@ -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_TITLE" = "Are you sure you want to clear all message requests?";
"MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear"; "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear";
"MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?"; "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_INFO" = "Sending a message to this user will automatically accept their message request.";
"MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted."; "MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted.";
"MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request"; "MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request";

@ -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_TITLE" = "Are you sure you want to clear all message requests?";
"MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear"; "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear";
"MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?"; "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_INFO" = "Sending a message to this user will automatically accept their message request.";
"MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted."; "MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted.";
"MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request"; "MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request";

@ -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_TITLE" = "Are you sure you want to clear all message requests?";
"MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear"; "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear";
"MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?"; "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_INFO" = "Sending a message to this user will automatically accept their message request.";
"MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted."; "MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted.";
"MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request"; "MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request";

@ -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_TITLE" = "Are you sure you want to clear all message requests?";
"MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear"; "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear";
"MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?"; "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_INFO" = "Sending a message to this user will automatically accept their message request.";
"MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted."; "MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted.";
"MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request"; "MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request";

@ -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_TITLE" = "Are you sure you want to clear all message requests?";
"MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear"; "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear";
"MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?"; "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_INFO" = "Sending a message to this user will automatically accept their message request.";
"MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted."; "MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted.";
"MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request"; "MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request";

@ -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_TITLE" = "Are you sure you want to clear all message requests?";
"MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear"; "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear";
"MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?"; "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_INFO" = "Sending a message to this user will automatically accept their message request.";
"MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted."; "MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted.";
"MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request"; "MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request";

@ -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_TITLE" = "Are you sure you want to clear all message requests?";
"MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear"; "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear";
"MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?"; "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_INFO" = "Sending a message to this user will automatically accept their message request.";
"MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted."; "MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted.";
"MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request"; "MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request";

@ -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_TITLE" = "Are you sure you want to clear all message requests?";
"MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear"; "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear";
"MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?"; "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_INFO" = "Sending a message to this user will automatically accept their message request.";
"MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted."; "MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted.";
"MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request"; "MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request";

@ -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_TITLE" = "Are you sure you want to clear all message requests?";
"MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear"; "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear";
"MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?"; "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_INFO" = "Sending a message to this user will automatically accept their message request.";
"MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted."; "MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted.";
"MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request"; "MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request";

@ -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_TITLE" = "Are you sure you want to clear all message requests?";
"MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear"; "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear";
"MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?"; "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_INFO" = "Sending a message to this user will automatically accept their message request.";
"MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted."; "MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted.";
"MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request"; "MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request";

@ -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_TITLE" = "Are you sure you want to clear all message requests?";
"MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear"; "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear";
"MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?"; "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_INFO" = "Sending a message to this user will automatically accept their message request.";
"MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted."; "MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted.";
"MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request"; "MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request";

@ -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_TITLE" = "Are you sure you want to clear all message requests?";
"MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear"; "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear";
"MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?"; "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_INFO" = "Sending a message to this user will automatically accept their message request.";
"MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted."; "MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted.";
"MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request"; "MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request";

@ -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_TITLE" = "Are you sure you want to clear all message requests?";
"MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear"; "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear";
"MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?"; "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_INFO" = "Sending a message to this user will automatically accept their message request.";
"MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted."; "MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted.";
"MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request"; "MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request";

@ -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_TITLE" = "Are you sure you want to clear all message requests?";
"MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear"; "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear";
"MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?"; "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_INFO" = "Sending a message to this user will automatically accept their message request.";
"MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted."; "MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted.";
"MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request"; "MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request";

@ -139,9 +139,6 @@ final class SettingsVC : BaseVC, AvatarViewHelperDelegate {
setUpNavBarStyle() setUpNavBarStyle()
setNavBarTitle(NSLocalizedString("vc_settings_title", comment: "")) setNavBarTitle(NSLocalizedString("vc_settings_title", comment: ""))
// Navigation bar buttons // Navigation bar buttons
let backButton = UIBarButtonItem(title: "Back", style: .plain, target: nil, action: nil)
backButton.tintColor = Colors.text
navigationItem.backBarButtonItem = backButton
updateNavigationBarButtons() updateNavigationBarButtons()
// Profile picture view // Profile picture view
let profilePictureTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(showEditProfilePictureUI)) 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.pin(.leading, to: .trailing, of: pathButton.titleLabel!, withInset: Values.smallSpacing)
pathStatusView.autoVCenterInSuperview() pathStatusView.autoVCenterInSuperview()
pathButton.titleEdgeInsets = UIEdgeInsets(top: 0, leading: 0, bottom: 0, trailing: Values.smallSpacing)
return [ return [
getSeparator(), getSeparator(),
pathButton, pathButton,

@ -46,6 +46,7 @@ class BaseVC : UIViewController {
internal func setUpNavBarStyle() { internal func setUpNavBarStyle() {
guard let navigationBar = navigationController?.navigationBar else { return } guard let navigationBar = navigationController?.navigationBar else { return }
if #available(iOS 15.0, *) { if #available(iOS 15.0, *) {
let appearance = UINavigationBarAppearance() let appearance = UINavigationBarAppearance()
appearance.configureWithOpaqueBackground() appearance.configureWithOpaqueBackground()
@ -59,6 +60,11 @@ class BaseVC : UIViewController {
navigationBar.isTranslucent = false navigationBar.isTranslucent = false
navigationBar.barTintColor = Colors.navigationBarBackground 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) { internal func setNavBarTitle(_ title: String, customFontSize: CGFloat? = nil) {

@ -358,15 +358,20 @@ final class ConversationCell : UITableViewCell {
if threadViewModel.isGroupThread { if threadViewModel.isGroupThread {
if threadViewModel.name.isEmpty { if threadViewModel.name.isEmpty {
return "Unknown Group" return "Unknown Group"
} else { }
else {
return threadViewModel.name return threadViewModel.name
} }
} else { }
else {
if threadViewModel.threadRecord.isNoteToSelf() { if threadViewModel.threadRecord.isNoteToSelf() {
return NSLocalizedString("NOTE_TO_SELF", comment: "") return NSLocalizedString("NOTE_TO_SELF", comment: "")
} else { }
let hexEncodedPublicKey = threadViewModel.contactSessionID! else {
return Storage.shared.getContact(with: hexEncodedPublicKey)?.displayName(for: .regular) ?? hexEncodedPublicKey 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)
} }
} }
} }

@ -140,16 +140,16 @@ NS_ASSUME_NONNULL_BEGIN
return YES; return YES;
} }
- (void)markAsReadNowWithSendReadReceipt:(BOOL)sendReadReceipt - (void)markAsReadNowWithTrySendReadReceipt:(BOOL)trySendReadReceipt
transaction:(YapDatabaseReadWriteTransaction *)transaction; transaction:(YapDatabaseReadWriteTransaction *)transaction;
{ {
[self markAsReadAtTimestamp:[NSDate millisecondTimestamp] [self markAsReadAtTimestamp:[NSDate millisecondTimestamp]
sendReadReceipt:sendReadReceipt trySendReadReceipt:trySendReadReceipt
transaction:transaction]; transaction:transaction];
} }
- (void)markAsReadAtTimestamp:(uint64_t)readTimestamp - (void)markAsReadAtTimestamp:(uint64_t)readTimestamp
sendReadReceipt:(BOOL)sendReadReceipt trySendReadReceipt:(BOOL)trySendReadReceipt
transaction:(YapDatabaseReadWriteTransaction *)transaction; transaction:(YapDatabaseReadWriteTransaction *)transaction;
{ {
if (_read && readTimestamp >= self.expireStartedAt) { if (_read && readTimestamp >= self.expireStartedAt) {
@ -174,7 +174,7 @@ NS_ASSUME_NONNULL_BEGIN
expirationStartedAt:readTimestamp expirationStartedAt:readTimestamp
transaction:transaction]; transaction:transaction];
if (sendReadReceipt) { if (trySendReadReceipt) {
[OWSReadReceiptManager.sharedManager messageWasReadLocally:self]; [OWSReadReceiptManager.sharedManager messageWasReadLocally:self];
} }
} }

@ -134,7 +134,7 @@ NSUInteger TSInfoMessageSchemaVersion = 1;
} }
- (void)markAsReadAtTimestamp:(uint64_t)readTimestamp - (void)markAsReadAtTimestamp:(uint64_t)readTimestamp
sendReadReceipt:(BOOL)sendReadReceipt trySendReadReceipt:(BOOL)trySendReadReceipt
transaction:(YapDatabaseReadWriteTransaction *)transaction transaction:(YapDatabaseReadWriteTransaction *)transaction
{ {
if (_read) { if (_read) {
@ -144,7 +144,7 @@ NSUInteger TSInfoMessageSchemaVersion = 1;
_read = YES; _read = YES;
[self saveWithTransaction:transaction]; [self saveWithTransaction:transaction];
// Ignore sendReadReceipt, it doesn't apply to info messages. // Ignore trySendReadReceipt, it doesn't apply to info messages.
} }
@end @end

@ -488,7 +488,8 @@ public final class OpenGroupManager: NSObject {
// https://143.198.213.225:443/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c // https://143.198.213.225:443/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c
// 143.198.213.255:80/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c // 143.198.213.255:80/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c
let useTLS = (url.scheme == "https") 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: "=") let queryParts = query.split(separator: "=")
guard !room.isEmpty && !room.contains("/"), queryParts.count == 2, queryParts[0] == "public_key" else { return nil } guard !room.isEmpty && !room.contains("/"), queryParts.count == 2, queryParts[0] == "public_key" else { return nil }
let publicKey = String(queryParts[1]) let publicKey = String(queryParts[1])

@ -394,7 +394,7 @@ extension MessageReceiver {
if let tsOutgoingMessage = TSMessage.fetch(uniqueId: tsMessageID, transaction: transaction) as? TSOutgoingMessage, if let tsOutgoingMessage = TSMessage.fetch(uniqueId: tsMessageID, transaction: transaction) as? TSOutgoingMessage,
let thread = TSThread.fetch(uniqueId: threadID, transaction: transaction) { let thread = TSThread.fetch(uniqueId: threadID, transaction: transaction) {
// Mark previous messages as read if there is a sync message // 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 // Update the contact's approval status of the current user if needed (if we are getting messages from

@ -45,7 +45,7 @@ extern NSString *const kIncomingMessageMarkedAsReadNotification;
// This method can be called from any thread. // This method can be called from any thread.
- (void)messageWasReadLocally:(TSIncomingMessage *)message; - (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 #pragma mark - Settings

@ -180,13 +180,13 @@ NSString *const OWSReadReceiptManagerAreReadReceiptsEnabled = @"areReadReceiptsE
#pragma mark - Mark as Read Locally #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) { [LKStorage writeWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[self markAsReadBeforeSortId:sortId [self markAsReadBeforeSortId:sortId
thread:thread thread:thread
readTimestamp:[NSDate millisecondTimestamp] readTimestamp:[NSDate millisecondTimestamp]
wasLocal:YES trySendReadReceipt:trySendReadReceipt
transaction:transaction]; transaction:transaction];
}]; }];
} }
@ -254,7 +254,7 @@ NSString *const OWSReadReceiptManagerAreReadReceiptsEnabled = @"areReadReceiptsE
- (void)markAsReadBeforeSortId:(uint64_t)sortId - (void)markAsReadBeforeSortId:(uint64_t)sortId
thread:(TSThread *)thread thread:(TSThread *)thread
readTimestamp:(uint64_t)readTimestamp readTimestamp:(uint64_t)readTimestamp
wasLocal:(BOOL)wasLocal trySendReadReceipt:(BOOL)trySendReadReceipt
transaction:(YapDatabaseReadWriteTransaction *)transaction transaction:(YapDatabaseReadWriteTransaction *)transaction
{ {
NSMutableArray<id<OWSReadTracking>> *newlyReadList = [NSMutableArray new]; NSMutableArray<id<OWSReadTracking>> *newlyReadList = [NSMutableArray new];
@ -285,7 +285,7 @@ NSString *const OWSReadReceiptManagerAreReadReceiptsEnabled = @"areReadReceiptsE
} }
for (id<OWSReadTracking> readItem in newlyReadList) { for (id<OWSReadTracking> readItem in newlyReadList) {
[readItem markAsReadAtTimestamp:readTimestamp sendReadReceipt:wasLocal transaction:transaction]; [readItem markAsReadAtTimestamp:readTimestamp trySendReadReceipt:trySendReadReceipt transaction:transaction];
} }
} }

@ -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. * Used both for *responding* to a remote read receipt and in response to the local user's activity.
*/ */
- (void)markAsReadAtTimestamp:(uint64_t)readTimestamp - (void)markAsReadAtTimestamp:(uint64_t)readTimestamp
sendReadReceipt:(BOOL)sendReadReceipt trySendReadReceipt:(BOOL)trySendReadReceipt
transaction:(YapDatabaseReadWriteTransaction *)transaction; transaction:(YapDatabaseReadWriteTransaction *)transaction;
@end @end

@ -309,7 +309,7 @@ BOOL IsNoteToSelfEnabled(void)
- (void)markAllAsReadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction - (void)markAllAsReadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
{ {
for (id<OWSReadTracking> message in [self unseenMessagesWithTransaction:transaction]) { for (id<OWSReadTracking> message in [self unseenMessagesWithTransaction:transaction]) {
[message markAsReadAtTimestamp:[NSDate ows_millisecondTimeStamp] sendReadReceipt:YES transaction:transaction]; [message markAsReadAtTimestamp:[NSDate ows_millisecondTimeStamp] trySendReadReceipt:YES transaction:transaction];
} }
} }

@ -6,7 +6,7 @@ public final class Button : UIButton {
private var heightConstraint: NSLayoutConstraint! private var heightConstraint: NSLayoutConstraint!
public enum Style { public enum Style {
case unimportant, regular, prominentOutline, prominentFilled, regularBorderless case unimportant, regular, prominentOutline, prominentFilled, regularBorderless, destructiveOutline
} }
public enum Size { public enum Size {
@ -41,6 +41,7 @@ public final class Button : UIButton {
case .prominentOutline: fillColor = UIColor.clear case .prominentOutline: fillColor = UIColor.clear
case .prominentFilled: fillColor = isLightMode ? Colors.text : Colors.accent case .prominentFilled: fillColor = isLightMode ? Colors.text : Colors.accent
case .regularBorderless: fillColor = UIColor.clear case .regularBorderless: fillColor = UIColor.clear
case .destructiveOutline: fillColor = UIColor.clear
} }
let borderColor: UIColor let borderColor: UIColor
switch style { switch style {
@ -49,6 +50,7 @@ public final class Button : UIButton {
case .prominentOutline: borderColor = isLightMode ? Colors.text : Colors.accent case .prominentOutline: borderColor = isLightMode ? Colors.text : Colors.accent
case .prominentFilled: borderColor = isLightMode ? Colors.text : Colors.accent case .prominentFilled: borderColor = isLightMode ? Colors.text : Colors.accent
case .regularBorderless: borderColor = UIColor.clear case .regularBorderless: borderColor = UIColor.clear
case .destructiveOutline: borderColor = Colors.destructive
} }
let textColor: UIColor let textColor: UIColor
switch style { switch style {
@ -57,6 +59,7 @@ public final class Button : UIButton {
case .prominentOutline: textColor = isLightMode ? Colors.text : Colors.accent case .prominentOutline: textColor = isLightMode ? Colors.text : Colors.accent
case .prominentFilled: textColor = isLightMode ? UIColor.white : Colors.text case .prominentFilled: textColor = isLightMode ? UIColor.white : Colors.text
case .regularBorderless: textColor = Colors.text case .regularBorderless: textColor = Colors.text
case .destructiveOutline: textColor = Colors.destructive
} }
let height: CGFloat let height: CGFloat
switch size { switch size {

@ -16,6 +16,7 @@ NS_ASSUME_NONNULL_BEGIN
+ (instancetype)sharedManager; + (instancetype)sharedManager;
- (NSUInteger)unreadMessagesCount; - (NSUInteger)unreadMessagesCount;
- (NSUInteger)unreadMessageRequestCount;
- (NSUInteger)unreadMessagesCountExcept:(TSThread *)thread; - (NSUInteger)unreadMessagesCountExcept:(TSThread *)thread;
- (void)updateApplicationBadgeCount; - (void)updateApplicationBadgeCount;

@ -112,6 +112,38 @@ NS_ASSUME_NONNULL_BEGIN
return numberOfItems; return numberOfItems;
} }
- (NSUInteger)unreadMessageRequestCount {
__block NSUInteger count = 0;
[LKStorage readWithBlock:^(YapDatabaseReadTransaction *transaction) {
YapDatabaseViewTransaction *unreadMessages = [transaction ext:TSUnreadDatabaseViewExtensionName];
NSArray<NSString *> *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<OWSReadTracking> unread = (id<OWSReadTracking>)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 - (NSUInteger)unreadMessagesCountExcept:(TSThread *)thread
{ {
__block NSUInteger numberOfItems; __block NSUInteger numberOfItems;

@ -25,8 +25,6 @@ public class ModalActivityIndicatorViewController: OWSViewController {
return result return result
}() }()
var presentTimer: Timer?
var wasDimissed: Bool = false var wasDimissed: Bool = false
// MARK: Initializers // MARK: Initializers
@ -127,41 +125,7 @@ public class ModalActivityIndicatorViewController: OWSViewController {
self.spinner.startAnimating() self.spinner.startAnimating()
// Hide the the modal and wait for a second before revealing it, // Fade in the modal
// 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.
UIView.animate(withDuration: 0.35) { UIView.animate(withDuration: 0.35) {
self.view.layer.opacity = 1.0 self.view.layer.opacity = 1.0
} }

@ -28,7 +28,7 @@ public final class ViewControllerUtilities : NSObject {
} }
// Set up back button // Set up back button
if hasCustomBackButton { 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 backButton.tintColor = Colors.text
vc.navigationItem.backBarButtonItem = backButton vc.navigationItem.backBarButtonItem = backButton
} }

Loading…
Cancel
Save