Merge pull request #533 from oxen-io/dev

1.11.19
pull/611/head 1.11.19
RyanZhao 3 years ago committed by GitHub
commit e12f52c565
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

1
.gitignore vendored

@ -27,6 +27,7 @@ DerivedData
*.ipa *.ipa
*.xcuserstate *.xcuserstate
Index/ Index/
Session-Turn-Server
# CocoaPods # CocoaPods
Pods Pods

@ -5049,7 +5049,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 306; CURRENT_PROJECT_VERSION = 312;
DEBUG_INFORMATION_FORMAT = dwarf; DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = SUQ8J2PCT7; DEVELOPMENT_TEAM = SUQ8J2PCT7;
FRAMEWORK_SEARCH_PATHS = "$(inherited)"; FRAMEWORK_SEARCH_PATHS = "$(inherited)";
@ -5074,7 +5074,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.11.18; MARKETING_VERSION = 1.11.19;
MTL_ENABLE_DEBUG_INFO = YES; MTL_ENABLE_DEBUG_INFO = YES;
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.ShareExtension"; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.ShareExtension";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
@ -5122,7 +5122,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 306; CURRENT_PROJECT_VERSION = 312;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = SUQ8J2PCT7; DEVELOPMENT_TEAM = SUQ8J2PCT7;
ENABLE_NS_ASSERTIONS = NO; ENABLE_NS_ASSERTIONS = NO;
@ -5152,7 +5152,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.11.18; MARKETING_VERSION = 1.11.19;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.ShareExtension"; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.ShareExtension";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
@ -5188,7 +5188,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 306; CURRENT_PROJECT_VERSION = 312;
DEBUG_INFORMATION_FORMAT = dwarf; DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = SUQ8J2PCT7; DEVELOPMENT_TEAM = SUQ8J2PCT7;
FRAMEWORK_SEARCH_PATHS = "$(inherited)"; FRAMEWORK_SEARCH_PATHS = "$(inherited)";
@ -5211,7 +5211,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.11.18; MARKETING_VERSION = 1.11.19;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.NotificationServiceExtension"; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.NotificationServiceExtension";
@ -5262,7 +5262,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 306; CURRENT_PROJECT_VERSION = 312;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = SUQ8J2PCT7; DEVELOPMENT_TEAM = SUQ8J2PCT7;
ENABLE_NS_ASSERTIONS = NO; ENABLE_NS_ASSERTIONS = NO;
@ -5290,7 +5290,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.11.18; MARKETING_VERSION = 1.11.19;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.NotificationServiceExtension"; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.NotificationServiceExtension";
@ -6198,7 +6198,7 @@
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CURRENT_PROJECT_VERSION = 306; CURRENT_PROJECT_VERSION = 312;
DEVELOPMENT_TEAM = SUQ8J2PCT7; DEVELOPMENT_TEAM = SUQ8J2PCT7;
FRAMEWORK_SEARCH_PATHS = ( FRAMEWORK_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
@ -6237,7 +6237,7 @@
"$(SRCROOT)", "$(SRCROOT)",
); );
LLVM_LTO = NO; LLVM_LTO = NO;
MARKETING_VERSION = 1.11.18; MARKETING_VERSION = 1.11.19;
OTHER_LDFLAGS = "$(inherited)"; OTHER_LDFLAGS = "$(inherited)";
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\""; OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger"; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger";
@ -6269,7 +6269,7 @@
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CURRENT_PROJECT_VERSION = 306; CURRENT_PROJECT_VERSION = 312;
DEVELOPMENT_TEAM = SUQ8J2PCT7; DEVELOPMENT_TEAM = SUQ8J2PCT7;
FRAMEWORK_SEARCH_PATHS = ( FRAMEWORK_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
@ -6308,7 +6308,7 @@
"$(SRCROOT)", "$(SRCROOT)",
); );
LLVM_LTO = NO; LLVM_LTO = NO;
MARKETING_VERSION = 1.11.18; MARKETING_VERSION = 1.11.19;
OTHER_LDFLAGS = "$(inherited)"; OTHER_LDFLAGS = "$(inherited)";
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger"; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger";
PRODUCT_NAME = Session; PRODUCT_NAME = Session;

@ -762,6 +762,8 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc
requestMicrophonePermissionIfNeeded() { [weak self] in requestMicrophonePermissionIfNeeded() { [weak self] in
self?.cancelVoiceMessageRecording() self?.cancelVoiceMessageRecording()
} }
// Keep screen on
UIApplication.shared.isIdleTimerDisabled = false
guard AVAudioSession.sharedInstance().recordPermission == .granted else { return } guard AVAudioSession.sharedInstance().recordPermission == .granted else { return }
// Cancel any current audio playback // Cancel any current audio playback
audioPlayer?.stop() audioPlayer?.stop()
@ -810,6 +812,7 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc
} }
func endVoiceMessageRecording() { func endVoiceMessageRecording() {
UIApplication.shared.isIdleTimerDisabled = true
// Hide the UI // Hide the UI
snInputView.hideVoiceMessageUI() snInputView.hideVoiceMessageUI()
// Cancel the timer // Cancel the timer

@ -356,6 +356,23 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, NewConv
self.present(alert, animated: true, completion: nil) self.present(alert, animated: true, completion: nil)
} }
delete.backgroundColor = Colors.destructive delete.backgroundColor = Colors.destructive
let isPinned = thread.isPinned
let pin = UITableViewRowAction(style: .normal, title: NSLocalizedString("PIN_BUTTON_TEXT", comment: "")) { [weak self] _, _ in
thread.isPinned = true
thread.save()
self?.threadViewModelCache.removeValue(forKey: thread.uniqueId!)
tableView.reloadRows(at: [ indexPath ], with: UITableView.RowAnimation.fade)
}
pin.backgroundColor = Colors.pathsBuilding
let unpin = UITableViewRowAction(style: .normal, title: NSLocalizedString("UNPIN_BUTTON_TEXT", comment: "")) { [weak self] _, _ in
thread.isPinned = false
thread.save()
self?.threadViewModelCache.removeValue(forKey: thread.uniqueId!)
tableView.reloadRows(at: [ indexPath ], with: UITableView.RowAnimation.fade)
}
unpin.backgroundColor = Colors.pathsBuilding
if let thread = thread as? TSContactThread { if let thread = thread as? TSContactThread {
let publicKey = thread.contactSessionID() let publicKey = thread.contactSessionID()
let blockingManager = SSKEnvironment.shared.blockingManager let blockingManager = SSKEnvironment.shared.blockingManager
@ -370,9 +387,9 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, NewConv
tableView.reloadRows(at: [ indexPath ], with: UITableView.RowAnimation.fade) tableView.reloadRows(at: [ indexPath ], with: UITableView.RowAnimation.fade)
} }
unblock.backgroundColor = Colors.unimportant unblock.backgroundColor = Colors.unimportant
return [ delete, (isBlocked ? unblock : block) ] return [ delete, (isBlocked ? unblock : block), (isPinned ? unpin : pin) ]
} else { } else {
return [ delete ] return [ delete, (isPinned ? unpin : pin) ]
} }
} }

@ -593,7 +593,11 @@ class PhotoCaptureOutputAdaptee: NSObject, ImageCaptureOutput {
@available(iOS 11.0, *) @available(iOS 11.0, *)
func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) { func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
let data = photo.fileDataRepresentation()! var data = photo.fileDataRepresentation()!
// Call normalized here to fix the orientation
if let srcImage = UIImage(data: data) {
data = srcImage.normalized().jpegData(compressionQuality: 1.0)!
}
DispatchQueue.main.async { DispatchQueue.main.async {
self.delegate?.captureOutputDidFinishProcessing(photoData: data, error: error) self.delegate?.captureOutputDidFinishProcessing(photoData: data, error: error)
} }

@ -740,7 +740,6 @@ static NSTimeInterval launchStartedAt;
- (LKAppMode)getCurrentAppMode - (LKAppMode)getCurrentAppMode
{ {
LKAppMode appMode = [self getAppModeOrSystemDefault]; LKAppMode appMode = [self getAppModeOrSystemDefault];
UIWindow *window = UIApplication.sharedApplication.keyWindow;
return appMode; return appMode;
} }

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "pin.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

@ -572,7 +572,10 @@
"DISMISS_BUTTON_TEXT" = "Dismiss"; "DISMISS_BUTTON_TEXT" = "Dismiss";
/* Button text which opens the settings app */ /* Button text which opens the settings app */
"OPEN_SETTINGS_BUTTON" = "Settings"; "OPEN_SETTINGS_BUTTON" = "Settings";
"APN_Message" = "You've got a new message"; "APN_Message" = "You've got a new message.";
"APN_Collapsed_Messages" = "You've got %@ new messages.";
"system_mode_theme" = "System"; "system_mode_theme" = "System";
"dark_mode_theme" = "Dark"; "dark_mode_theme" = "Dark";
"light_mode_theme" = "Light"; "light_mode_theme" = "Light";
"PIN_BUTTON_TEXT" = "Pin";
"UNPIN_BUTTON_TEXT" = "Unpin";

@ -37,6 +37,7 @@ struct AppNotificationUserInfoKey {
static let threadId = "Signal.AppNotificationsUserInfoKey.threadId" static let threadId = "Signal.AppNotificationsUserInfoKey.threadId"
static let callBackNumber = "Signal.AppNotificationsUserInfoKey.callBackNumber" static let callBackNumber = "Signal.AppNotificationsUserInfoKey.callBackNumber"
static let localCallId = "Signal.AppNotificationsUserInfoKey.localCallId" static let localCallId = "Signal.AppNotificationsUserInfoKey.localCallId"
static let threadNotificationCounter = "Session.AppNotificationsUserInfoKey.threadNotificationCounter"
} }
extension AppNotificationCategory { extension AppNotificationCategory {
@ -80,9 +81,9 @@ extension AppNotificationAction {
} }
} }
// Delay notification of incoming messages when it's likely to be read by a linked device to // Delay notification of incoming messages when it's a background polling to
// avoid notifying a user on their phone while a conversation is actively happening on desktop. // avoid too many notifications fired at the same time
let kNotificationDelayForRemoteRead: TimeInterval = 5 let kNotificationDelayForBackgroumdPoll: TimeInterval = 5
let kAudioNotificationsThrottleCount = 2 let kAudioNotificationsThrottleCount = 2
let kAudioNotificationsThrottleInterval: TimeInterval = 5 let kAudioNotificationsThrottleInterval: TimeInterval = 5
@ -157,9 +158,12 @@ public class NotificationPresenter: NSObject, NotificationsProtocol {
public func notifyUser(for incomingMessage: TSIncomingMessage, in thread: TSThread, transaction: YapDatabaseReadTransaction) { public func notifyUser(for incomingMessage: TSIncomingMessage, in thread: TSThread, transaction: YapDatabaseReadTransaction) {
guard !thread.isMuted else { guard !thread.isMuted else { return }
return guard let threadId = thread.uniqueId else { return }
}
let identifier: String = incomingMessage.notificationIdentifier ?? UUID().uuidString
let isBackgroudPoll = identifier == threadId
// While batch processing, some of the necessary changes have not been commited. // While batch processing, some of the necessary changes have not been commited.
let rawMessageText = incomingMessage.previewText(with: transaction) let rawMessageText = incomingMessage.previewText(with: transaction)
@ -194,13 +198,13 @@ public class NotificationPresenter: NSObject, NotificationsProtocol {
if groupName.count < 1 { if groupName.count < 1 {
groupName = MessageStrings.newGroupDefaultTitle groupName = MessageStrings.newGroupDefaultTitle
} }
notificationTitle = String(format: NotificationStrings.incomingGroupMessageTitleFormat, notificationTitle = isBackgroudPoll ? groupName : String(format: NotificationStrings.incomingGroupMessageTitleFormat, senderName, groupName)
senderName,
groupName)
default: default:
owsFailDebug("unexpected thread: \(thread)") owsFailDebug("unexpected thread: \(thread)")
return return
} }
default:
notificationTitle = "Session"
} }
var notificationBody: String? var notificationBody: String?
@ -209,25 +213,20 @@ public class NotificationPresenter: NSObject, NotificationsProtocol {
notificationBody = NotificationStrings.incomingMessageBody notificationBody = NotificationStrings.incomingMessageBody
case .namePreview: case .namePreview:
notificationBody = messageText notificationBody = messageText
} default:
notificationBody = NotificationStrings.incomingMessageBody
guard let threadId = thread.uniqueId else {
owsFailDebug("threadId was unexpectedly nil")
return
} }
assert((notificationBody ?? notificationTitle) != nil) assert((notificationBody ?? notificationTitle) != nil)
// Don't reply from lockscreen if anyone in this conversation is // Don't reply from lockscreen if anyone in this conversation is
// "no longer verified". // "no longer verified".
var category = AppNotificationCategory.incomingMessage let category = AppNotificationCategory.incomingMessage
let userInfo = [ let userInfo = [
AppNotificationUserInfoKey.threadId: threadId AppNotificationUserInfoKey.threadId: threadId
] ]
let identifier: String = incomingMessage.notificationIdentifier ?? UUID().uuidString
DispatchQueue.main.async { DispatchQueue.main.async {
notificationBody = MentionUtilities.highlightMentions(in: notificationBody!, threadID: thread.uniqueId!) notificationBody = MentionUtilities.highlightMentions(in: notificationBody!, threadID: thread.uniqueId!)
let sound = self.requestSound(thread: thread) let sound = self.requestSound(thread: thread)
@ -247,6 +246,8 @@ public class NotificationPresenter: NSObject, NotificationsProtocol {
notificationTitle = nil notificationTitle = nil
case .nameNoPreview, .namePreview: case .nameNoPreview, .namePreview:
notificationTitle = thread.name() notificationTitle = thread.name()
default:
notificationTitle = nil
} }
let notificationBody = NotificationStrings.failedToSendBody let notificationBody = NotificationStrings.failedToSendBody

@ -96,17 +96,18 @@ extension UserNotificationPresenterAdaptee: NotificationPresenterAdaptee {
let content = UNMutableNotificationContent() let content = UNMutableNotificationContent()
content.categoryIdentifier = category.identifier content.categoryIdentifier = category.identifier
content.userInfo = userInfo content.userInfo = userInfo
let isReplacingNotification = replacingIdentifier != nil
var isBackgroudPoll = false
if let threadIdentifier = userInfo[AppNotificationUserInfoKey.threadId] as? String {
content.threadIdentifier = threadIdentifier
isBackgroudPoll = replacingIdentifier == threadIdentifier
}
let isAppActive = UIApplication.shared.applicationState == .active let isAppActive = UIApplication.shared.applicationState == .active
if let sound = sound, sound != OWSSound.none { if let sound = sound, sound != OWSSound.none {
content.sound = sound.notificationSound(isQuiet: isAppActive) content.sound = sound.notificationSound(isQuiet: isAppActive)
} }
var notificationIdentifier: String = UUID().uuidString let notificationIdentifier = isReplacingNotification ? replacingIdentifier! : UUID().uuidString
if let replacingIdentifier = replacingIdentifier {
notificationIdentifier = replacingIdentifier
Logger.debug("replacing notification with identifier: \(notificationIdentifier)")
cancelNotification(identifier: notificationIdentifier)
}
if shouldPresentNotification(category: category, userInfo: userInfo) { if shouldPresentNotification(category: category, userInfo: userInfo) {
if let displayableTitle = title?.filterForDisplay { if let displayableTitle = title?.filterForDisplay {
@ -120,9 +121,25 @@ extension UserNotificationPresenterAdaptee: NotificationPresenterAdaptee {
Logger.debug("supressing notification body") Logger.debug("supressing notification body")
} }
let request = UNNotificationRequest(identifier: notificationIdentifier, content: content, trigger: nil) let trigger: UNNotificationTrigger?
if isBackgroudPoll {
trigger = UNTimeIntervalNotificationTrigger(timeInterval: kNotificationDelayForBackgroumdPoll, repeats: false)
let numberOfNotifications: Int
if let lastRequest = notifications[notificationIdentifier], let counter = lastRequest.content.userInfo[AppNotificationUserInfoKey.threadNotificationCounter] as? Int {
numberOfNotifications = counter + 1
content.body = String(format: NotificationStrings.incomingCollapsedMessagesBody, "\(numberOfNotifications)")
} else {
numberOfNotifications = 1
}
content.userInfo[AppNotificationUserInfoKey.threadNotificationCounter] = numberOfNotifications
} else {
trigger = nil
}
let request = UNNotificationRequest(identifier: notificationIdentifier, content: content, trigger: trigger)
Logger.debug("presenting notification with identifier: \(notificationIdentifier)") Logger.debug("presenting notification with identifier: \(notificationIdentifier)")
if isReplacingNotification { cancelNotification(identifier: notificationIdentifier) }
notificationCenter.add(request) notificationCenter.add(request)
notifications[notificationIdentifier] = request notifications[notificationIdentifier] = request
} }

@ -90,7 +90,9 @@ class BaseVC : UIViewController {
} }
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
NotificationCenter.default.post(name: .appModeChanged, object: nil) if LKAppModeUtilities.isSystemDefault {
NotificationCenter.default.post(name: .appModeChanged, object: nil)
}
} }
@objc internal func handleAppModeChangedNotification(_ notification: Notification) { @objc internal func handleAppModeChangedNotification(_ notification: Notification) {

@ -58,6 +58,17 @@ final class ConversationCell : UITableViewCell {
return result return result
}() }()
private lazy var isPinnedIcon: UIImageView = {
let result = UIImageView(image: UIImage(named: "Pin")!.withRenderingMode(.alwaysTemplate))
result.contentMode = .scaleAspectFit
let size = ConversationCell.unreadCountViewSize
result.set(.width, to: size)
result.set(.height, to: size)
result.tintColor = Colors.pinIcon
result.layer.masksToBounds = true
return result
}()
private lazy var timestampLabel: UILabel = { private lazy var timestampLabel: UILabel = {
let result = UILabel() let result = UILabel()
result.font = .systemFont(ofSize: Values.smallFontSize) result.font = .systemFont(ofSize: Values.smallFontSize)
@ -124,7 +135,7 @@ final class ConversationCell : UITableViewCell {
hasMentionLabel.pin(to: hasMentionView) hasMentionLabel.pin(to: hasMentionView)
// Label stack view // Label stack view
let topLabelSpacer = UIView.hStretchingSpacer() let topLabelSpacer = UIView.hStretchingSpacer()
let topLabelStackView = UIStackView(arrangedSubviews: [ displayNameLabel, unreadCountView, hasMentionView, topLabelSpacer, timestampLabel ]) let topLabelStackView = UIStackView(arrangedSubviews: [ displayNameLabel, isPinnedIcon, unreadCountView, hasMentionView, topLabelSpacer, timestampLabel ])
topLabelStackView.axis = .horizontal topLabelStackView.axis = .horizontal
topLabelStackView.alignment = .center topLabelStackView.alignment = .center
topLabelStackView.spacing = Values.smallSpacing / 2 // Effectively Values.smallSpacing because there'll be spacing before and after the invisible spacer topLabelStackView.spacing = Values.smallSpacing / 2 // Effectively Values.smallSpacing because there'll be spacing before and after the invisible spacer
@ -182,6 +193,7 @@ final class ConversationCell : UITableViewCell {
private func update() { private func update() {
AssertIsOnMainThread() AssertIsOnMainThread()
guard let thread = threadViewModel?.threadRecord else { return } guard let thread = threadViewModel?.threadRecord else { return }
backgroundColor = threadViewModel.isPinned ? Colors.cellPinned : Colors.cellBackground
let isBlocked: Bool let isBlocked: Bool
if let thread = thread as? TSContactThread { if let thread = thread as? TSContactThread {
isBlocked = SSKEnvironment.shared.blockingManager.isRecipientIdBlocked(thread.contactSessionID()) isBlocked = SSKEnvironment.shared.blockingManager.isRecipientIdBlocked(thread.contactSessionID())
@ -195,6 +207,7 @@ final class ConversationCell : UITableViewCell {
accentLineView.backgroundColor = Colors.accent accentLineView.backgroundColor = Colors.accent
accentLineView.alpha = threadViewModel.hasUnreadMessages ? 1 : 0.0001 // Setting the alpha to exactly 0 causes an issue on iOS 12 accentLineView.alpha = threadViewModel.hasUnreadMessages ? 1 : 0.0001 // Setting the alpha to exactly 0 causes an issue on iOS 12
} }
isPinnedIcon.isHidden = !threadViewModel.isPinned
unreadCountView.isHidden = !threadViewModel.hasUnreadMessages unreadCountView.isHidden = !threadViewModel.hasUnreadMessages
let unreadCount = threadViewModel.unreadCount let unreadCount = threadViewModel.unreadCount
unreadCountLabel.text = unreadCount < 100 ? "\(unreadCount)" : "99+" unreadCountLabel.text = unreadCount < 100 ? "\(unreadCount)" : "99+"

@ -28,6 +28,7 @@ extension Storage {
let thread = TSThread.fetch(uniqueId: threadID, transaction: transaction) else { return nil } let thread = TSThread.fetch(uniqueId: threadID, transaction: transaction) else { return nil }
let tsMessage: TSMessage let tsMessage: TSMessage
if message.sender == getUserPublicKey() { if message.sender == getUserPublicKey() {
if let _ = TSOutgoingMessage.find(withTimestamp: message.sentTimestamp!) { return nil }
let tsOutgoingMessage = TSOutgoingMessage.from(message, associatedWith: thread, using: transaction) let tsOutgoingMessage = TSOutgoingMessage.from(message, associatedWith: thread, using: transaction)
var recipients: [String] = [] var recipients: [String] = []
if let syncTarget = message.syncTarget { if let syncTarget = message.syncTarget {

@ -278,7 +278,10 @@ NSString *const TSLazyRestoreAttachmentsGroup = @"TSLazyRestoreAttachmentsGroup"
TSThread *thread1 = (TSThread *)object1; TSThread *thread1 = (TSThread *)object1;
TSThread *thread2 = (TSThread *)object2; TSThread *thread2 = (TSThread *)object2;
if ([group isEqualToString:TSArchiveGroup] || [group isEqualToString:TSInboxGroup]) { if ([group isEqualToString:TSArchiveGroup] || [group isEqualToString:TSInboxGroup]) {
if (thread1.isPinned != thread2.isPinned) {
if (thread1.isPinned) { return NSOrderedDescending; }
if (thread2.isPinned) { return NSOrderedAscending; }
}
TSInteraction *_Nullable lastInteractionForInbox1 = TSInteraction *_Nullable lastInteractionForInbox1 =
[thread1 lastInteractionForInboxWithTransaction:transaction]; [thread1 lastInteractionForInboxWithTransaction:transaction];
NSDate *lastInteractionForInboxDate1 = lastInteractionForInbox1 ? lastInteractionForInbox1.receivedAtDate : thread1.creationDate; NSDate *lastInteractionForInboxDate1 = lastInteractionForInbox1 ? lastInteractionForInbox1.receivedAtDate : thread1.creationDate;

@ -342,7 +342,9 @@ extension MessageReceiver {
// Notify the user if needed // Notify the user if needed
guard (isMainAppAndActive || isBackgroundPoll), let tsIncomingMessage = TSMessage.fetch(uniqueId: tsMessageID, transaction: transaction) as? TSIncomingMessage, guard (isMainAppAndActive || isBackgroundPoll), let tsIncomingMessage = TSMessage.fetch(uniqueId: tsMessageID, transaction: transaction) as? TSIncomingMessage,
let thread = TSThread.fetch(uniqueId: threadID, transaction: transaction) else { return tsMessageID } let thread = TSThread.fetch(uniqueId: threadID, transaction: transaction) else { return tsMessageID }
tsIncomingMessage.setNotificationIdentifier(UUID().uuidString, transaction: transaction) // Use the same identifier for notifications when in backgroud polling to prevent spam
let notificationIdentifier = isBackgroundPoll ? thread.uniqueId : UUID().uuidString
tsIncomingMessage.setNotificationIdentifier(notificationIdentifier, transaction: transaction)
DispatchQueue.main.async { DispatchQueue.main.async {
Storage.read { transaction in Storage.read { transaction in
SSKEnvironment.shared.notificationsManager!.notifyUser(for: tsIncomingMessage, in: thread, transaction: transaction) SSKEnvironment.shared.notificationsManager!.notifyUser(for: tsIncomingMessage, in: thread, transaction: transaction)

@ -116,8 +116,6 @@ public final class MessageSender : NSObject {
if message.sentTimestamp == nil { // Visible messages will already have their sent timestamp set if message.sentTimestamp == nil { // Visible messages will already have their sent timestamp set
message.sentTimestamp = NSDate.millisecondTimestamp() message.sentTimestamp = NSDate.millisecondTimestamp()
} }
// Ignore future self-sends
Storage.shared.addReceivedMessageTimestamp(message.sentTimestamp!, using: transaction)
message.sender = userPublicKey message.sender = userPublicKey
switch destination { switch destination {
case .contact(let publicKey): message.recipient = publicKey case .contact(let publicKey): message.recipient = publicKey
@ -268,8 +266,6 @@ public final class MessageSender : NSObject {
if message.sentTimestamp == nil { // Visible messages will already have their sent timestamp set if message.sentTimestamp == nil { // Visible messages will already have their sent timestamp set
message.sentTimestamp = NSDate.millisecondTimestamp() message.sentTimestamp = NSDate.millisecondTimestamp()
} }
// Ignore future self-sends
Storage.shared.addReceivedMessageTimestamp(message.sentTimestamp!, using: transaction)
message.sender = storage.getUserPublicKey() message.sender = storage.getUserPublicKey()
switch destination { switch destination {
case .contact(_): preconditionFailure() case .contact(_): preconditionFailure()
@ -366,7 +362,11 @@ public final class MessageSender : NSObject {
let userPublicKey = getUserHexEncodedPublicKey() let userPublicKey = getUserHexEncodedPublicKey()
if case .contact(let publicKey) = destination, !isSyncMessage { if case .contact(let publicKey) = destination, !isSyncMessage {
if let message = message as? VisibleMessage { message.syncTarget = publicKey } if let message = message as? VisibleMessage { message.syncTarget = publicKey }
if let message = message as? ExpirationTimerUpdate { message.syncTarget = publicKey } if let message = message as? ExpirationTimerUpdate {
message.syncTarget = publicKey
// Prevent the same ExpirationTimerUpdate to be handled twice
Storage.shared.addReceivedMessageTimestamp(message.sentTimestamp!, using: transaction)
}
// FIXME: Make this a job // FIXME: Make this a job
sendToSnodeDestination(.contact(publicKey: userPublicKey), message: message, using: transaction, isSyncMessage: true).retainUntilComplete() sendToSnodeDestination(.contact(publicKey: userPublicKey), message: message, using: transaction, isSyncMessage: true).retainUntilComplete()
} }

@ -16,6 +16,7 @@ BOOL IsNoteToSelfEnabled(void);
*/ */
@interface TSThread : TSYapDatabaseObject @interface TSThread : TSYapDatabaseObject
@property (nonatomic) BOOL isPinned;
@property (nonatomic) BOOL shouldBeVisible; @property (nonatomic) BOOL shouldBeVisible;
@property (nonatomic, readonly) NSDate *creationDate; @property (nonatomic, readonly) NSDate *creationDate;
@property (nonatomic, readonly, nullable) NSDate *lastInteractionDate; @property (nonatomic, readonly, nullable) NSDate *lastInteractionDate;

@ -20,6 +20,7 @@ public final class Colors : NSObject {
@objc public static var border: UIColor { UIColor(named: "session_border")! } @objc public static var border: UIColor { UIColor(named: "session_border")! }
@objc public static var cellBackground: UIColor { UIColor(named: "session_cell_background")! } @objc public static var cellBackground: UIColor { UIColor(named: "session_cell_background")! }
@objc public static var cellSelected: UIColor { UIColor(named: "session_cell_selected")! } @objc public static var cellSelected: UIColor { UIColor(named: "session_cell_selected")! }
@objc public static var cellPinned: UIColor { UIColor(named: "session_cell_pinned")! }
@objc public static var navigationBarBackground: UIColor { UIColor(named: "session_navigation_bar_background")! } @objc public static var navigationBarBackground: UIColor { UIColor(named: "session_navigation_bar_background")! }
@objc public static var searchBarPlaceholder: UIColor { UIColor(named: "session_search_bar_placeholder")! } // Also used for the icons @objc public static var searchBarPlaceholder: UIColor { UIColor(named: "session_search_bar_placeholder")! } // Also used for the icons
@objc public static var searchBarBackground: UIColor { UIColor(named: "session_search_bar_background")! } @objc public static var searchBarBackground: UIColor { UIColor(named: "session_search_bar_background")! }
@ -40,4 +41,5 @@ public final class Colors : NSObject {
@objc public static var pnOptionBackground: UIColor { UIColor(named: "session_pn_option_background")! } @objc public static var pnOptionBackground: UIColor { UIColor(named: "session_pn_option_background")! }
@objc public static var pnOptionBorder: UIColor { UIColor(named: "session_pn_option_border")! } @objc public static var pnOptionBorder: UIColor { UIColor(named: "session_pn_option_border")! }
@objc public static var pathsBuilding: UIColor { UIColor(named: "session_paths_building")! } @objc public static var pathsBuilding: UIColor { UIColor(named: "session_paths_building")! }
@objc public static var pinIcon: UIColor { UIColor(named: "session_pin_icon")! }
} }

@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0xF0",
"green" : "0xF0",
"red" : "0xF0"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x40",
"green" : "0x40",
"red" : "0x40"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "96",
"green" : "96",
"red" : "96"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "179",
"green" : "179",
"red" : "179"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

@ -166,7 +166,7 @@ public class ImageEditorCanvasView: UIView {
// of code simplicity. We could modify the image layer's // of code simplicity. We could modify the image layer's
// transform to handle the normalization, which would // transform to handle the normalization, which would
// have perf benefits. // have perf benefits.
return srcImage.normalized() return srcImage
} }
// MARK: - Content // MARK: - Content

@ -14,6 +14,7 @@ public class ThreadViewModel: NSObject {
@objc public let contactSessionID: String? @objc public let contactSessionID: String?
@objc public let name: String @objc public let name: String
@objc public let isMuted: Bool @objc public let isMuted: Bool
@objc public let isPinned: Bool
@objc public let isOnlyNotifyingForMentions: Bool @objc public let isOnlyNotifyingForMentions: Bool
@objc public let hasUnreadMentions: Bool @objc public let hasUnreadMentions: Bool
@ -31,6 +32,7 @@ public class ThreadViewModel: NSObject {
self.isGroupThread = thread.isGroupThread() self.isGroupThread = thread.isGroupThread()
self.name = thread.name() self.name = thread.name()
self.isMuted = thread.isMuted self.isMuted = thread.isMuted
self.isPinned = thread.isPinned
self.lastMessageText = thread.lastMessageText(transaction: transaction) self.lastMessageText = thread.lastMessageText(transaction: transaction)
let lastInteraction = thread.lastInteractionForInbox(transaction: transaction) let lastInteraction = thread.lastInteractionForInbox(transaction: transaction)
self.lastMessageForInbox = lastInteraction self.lastMessageForInbox = lastInteraction

@ -51,6 +51,9 @@ public class NotificationStrings: NSObject {
@objc @objc
static public let incomingMessageBody = NSLocalizedString("APN_Message", comment: "notification body") static public let incomingMessageBody = NSLocalizedString("APN_Message", comment: "notification body")
@objc
static public let incomingCollapsedMessagesBody = NSLocalizedString("APN_Collapsed_Messages", comment: "collapsed notification body for background polling")
@objc @objc
static public let incomingGroupMessageTitleFormat = NSLocalizedString("NEW_GROUP_MESSAGE_NOTIFICATION_TITLE", comment: "notification title. Embeds {{author name}} and {{group name}}") static public let incomingGroupMessageTitleFormat = NSLocalizedString("NEW_GROUP_MESSAGE_NOTIFICATION_TITLE", comment: "notification title. Embeds {{author name}} and {{group name}}")

Loading…
Cancel
Save