Browse Source

Fixed a number of reported bugs, some cleanup, added animated profile support

Added support for animated profile images (no ability to crop/resize)
Updated the message trimming to only remove messages if the open group has 2000 messages or more
Updated the message trimming setting to default to be on
Updated the ContextMenu to fade out the snapshot as well (looked buggy if the device had even minor lag)
Updated the ProfileManager to delete and re-download invalid avatar images (and updated the conversation screen to reload when avatars complete downloading)
Updated the message request notification logic so it will show notifications when receiving a new message request as long as the user has read all the old ones (previously the user had to accept/reject all the old ones)
Fixed a bug where the "trim open group messages" toggle was accessing UI off the main thread
Fixed a bug where the "Chats" settings screen had a close button instead of a back button
Fixed a bug where the 'viewsToMove' for the reply UI was inconsistent in some places
Fixed an issue where the ProfileManager was doing all of it's validation (and writing to disk) within the database write closure which would block database writes unnecessarily
Fixed a bug where a message request wouldn't be identified as such just because it wasn't visible in the conversations list
Fixed a bug where opening a message request notification would result in the message request being in the wrong state (also wouldn't insert the 'MessageRequestsViewController' into the hierarchy)
Fixed a bug where the avatar image wouldn't appear beside incoming closed group message in some situations cases
Removed an error log that was essentially just spam
Remove the logic to delete old profile images when calling save on a Profile (wouldn't get called if the row was modified directly and duplicates GarbageCollection logic)
Remove the logic to send a notification when calling save on a Profile (wouldn't get called if the row was modified directly)
Tweaked the message trimming description to be more accurate
Cleaned up some duplicate logic used to determine if a notification should be shown
Cleaned up some onion request logic (was passing the version info in some cases when not needed)
Moved the push notification notify API call into the PushNotificationAPI class for consistency
pull/612/head
Morgan Pretty 3 months ago
parent
commit
4afddd6fbb
  1. 4
      Session.xcodeproj/project.pbxproj
  2. 1
      Session/Calls/Call Management/SessionCall.swift
  3. 1
      Session/Conversations/Context Menu/ContextMenuVC.swift
  4. 10
      Session/Conversations/ConversationViewModel.swift
  5. 24
      Session/Conversations/Message Cells/VisibleMessageCell.swift
  6. 28
      Session/Conversations/Settings/OWSConversationSettingsViewController.m
  7. 15
      Session/Home/HomeVC.swift
  8. 18
      Session/Meta/AppDelegate.swift
  9. 15
      Session/Meta/SessionApp.swift
  10. 2
      Session/Meta/Translations/de.lproj/Localizable.strings
  11. 2
      Session/Meta/Translations/en.lproj/Localizable.strings
  12. 2
      Session/Meta/Translations/es.lproj/Localizable.strings
  13. 2
      Session/Meta/Translations/fa.lproj/Localizable.strings
  14. 2
      Session/Meta/Translations/fi.lproj/Localizable.strings
  15. 2
      Session/Meta/Translations/fr.lproj/Localizable.strings
  16. 2
      Session/Meta/Translations/hi.lproj/Localizable.strings
  17. 2
      Session/Meta/Translations/hr.lproj/Localizable.strings
  18. 2
      Session/Meta/Translations/id-ID.lproj/Localizable.strings
  19. 2
      Session/Meta/Translations/it.lproj/Localizable.strings
  20. 2
      Session/Meta/Translations/ja.lproj/Localizable.strings
  21. 2
      Session/Meta/Translations/nl.lproj/Localizable.strings
  22. 2
      Session/Meta/Translations/pl.lproj/Localizable.strings
  23. 2
      Session/Meta/Translations/pt_BR.lproj/Localizable.strings
  24. 2
      Session/Meta/Translations/ru.lproj/Localizable.strings
  25. 2
      Session/Meta/Translations/si.lproj/Localizable.strings
  26. 2
      Session/Meta/Translations/sk.lproj/Localizable.strings
  27. 2
      Session/Meta/Translations/sv.lproj/Localizable.strings
  28. 2
      Session/Meta/Translations/th.lproj/Localizable.strings
  29. 2
      Session/Meta/Translations/vi-VN.lproj/Localizable.strings
  30. 2
      Session/Meta/Translations/zh-Hant.lproj/Localizable.strings
  31. 2
      Session/Meta/Translations/zh_CN.lproj/Localizable.strings
  32. 31
      Session/Notifications/AppNotifications.swift
  33. 3
      Session/Onboarding/DisplayNameVC.swift
  34. 7
      Session/Settings/ChatSettingsViewController.swift
  35. 44
      Session/Settings/SettingsVC.swift
  36. 2
      Session/Utilities/AvatarViewHelper.h
  37. 23
      Session/Utilities/AvatarViewHelper.m
  38. 2
      Session/Utilities/UIApplication+OWS.swift
  39. 3
      SessionMessagingKit/Database/Migrations/_003_YDBToGRDBMigration.swift
  40. 5
      SessionMessagingKit/Database/Models/Attachment.swift
  41. 25
      SessionMessagingKit/Database/Models/Profile.swift
  42. 55
      SessionMessagingKit/Database/Models/SessionThread.swift
  43. 21
      SessionMessagingKit/Database/Notification+Contacts.swift
  44. 15
      SessionMessagingKit/Jobs/Types/GarbageCollectionJob.swift
  45. 51
      SessionMessagingKit/Jobs/Types/NotifyPushServerJob.swift
  46. 6
      SessionMessagingKit/Jobs/Types/UpdateProfilePictureJob.swift
  47. 9
      SessionMessagingKit/Sending & Receiving/MessageReceiver.swift
  48. 45
      SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift
  49. 3
      SessionMessagingKit/Shared Models/MessageViewModel.swift
  50. 1
      SessionMessagingKit/Shared Models/SessionThreadViewModel.swift
  51. 211
      SessionMessagingKit/Utilities/ProfileManager.swift
  52. 35
      SessionNotificationServiceExtension/NSENotificationPresenter.swift
  53. 21
      SessionNotificationServiceExtension/NotificationServiceExtension.swift
  54. 35
      SessionSnodeKit/OnionRequestAPI+Encryption.swift
  55. 80
      SessionSnodeKit/OnionRequestAPI.swift
  56. 3
      SessionSnodeKit/SnodeAPI.swift
  57. 1
      SessionUtilitiesKit/Database/Storage.swift
  58. 13
      SignalUtilitiesKit/Profile Pictures/ProfilePictureView.swift

4
Session.xcodeproj/project.pbxproj

@ -261,7 +261,6 @@
B8DE1FB626C22FCB0079C9CE /* CallMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8DE1FB526C22FCB0079C9CE /* CallMessage.swift */; };
B8EB20EE2640F28000773E52 /* VisibleMessage+OpenGroupInvitation.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8EB20ED2640F28000773E52 /* VisibleMessage+OpenGroupInvitation.swift */; };
B8EB20F02640F7F000773E52 /* OpenGroupInvitationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8EB20EF2640F7F000773E52 /* OpenGroupInvitationView.swift */; };
B8F5F56525EC8453003BF8D4 /* Notification+Contacts.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8F5F56425EC8453003BF8D4 /* Notification+Contacts.swift */; };
B8F5F58325EC94A6003BF8D4 /* Collection+Subscripting.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8F5F58225EC94A6003BF8D4 /* Collection+Subscripting.swift */; };
B8F5F60325EDE16F003BF8D4 /* DataExtractionNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8F5F60225EDE16F003BF8D4 /* DataExtractionNotification.swift */; };
B8F5F71A25F1B35C003BF8D4 /* MediaPlaceholderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8F5F71925F1B35C003BF8D4 /* MediaPlaceholderView.swift */; };
@ -1296,7 +1295,6 @@
B8EB20E6263F7E4B00773E52 /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sk; path = sk.lproj/Localizable.strings; sourceTree = "<group>"; };
B8EB20ED2640F28000773E52 /* VisibleMessage+OpenGroupInvitation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "VisibleMessage+OpenGroupInvitation.swift"; sourceTree = "<group>"; };
B8EB20EF2640F7F000773E52 /* OpenGroupInvitationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupInvitationView.swift; sourceTree = "<group>"; };
B8F5F56425EC8453003BF8D4 /* Notification+Contacts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Notification+Contacts.swift"; sourceTree = "<group>"; };
B8F5F58225EC94A6003BF8D4 /* Collection+Subscripting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Collection+Subscripting.swift"; sourceTree = "<group>"; };
B8F5F60225EDE16F003BF8D4 /* DataExtractionNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataExtractionNotification.swift; sourceTree = "<group>"; };
B8F5F71925F1B35C003BF8D4 /* MediaPlaceholderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPlaceholderView.swift; sourceTree = "<group>"; };
@ -2630,7 +2628,6 @@
FD17D79A27F40ADA00122BE0 /* LegacyDatabase */,
FD17D79427F3E03300122BE0 /* Migrations */,
FD09796C27FA6C8B00936362 /* Models */,
B8F5F56425EC8453003BF8D4 /* Notification+Contacts.swift */,
);
path = Database;
sourceTree = "<group>";
@ -5176,7 +5173,6 @@
FDC438C927BB706500C60D73 /* SendDirectMessageRequest.swift in Sources */,
C3A71D1F25589AC30043A11F /* WebSocketResources.pb.swift in Sources */,
FDF0B74B28061F7A004C14C5 /* InteractionAttachment.swift in Sources */,
B8F5F56525EC8453003BF8D4 /* Notification+Contacts.swift in Sources */,
FD09796E27FA6D0000936362 /* Contact.swift in Sources */,
C38D5E8D2575011E00B6A65C /* MessageSender+ClosedGroups.swift in Sources */,
FD5C72F7284F0E560029977D /* MessageReceiver+ReadReceipts.swift in Sources */,

1
Session/Calls/Call Management/SessionCall.swift

@ -152,6 +152,7 @@ public final class SessionCall: CurrentCallProtocol, WebRTCSessionDelegate {
self.contactName = Profile.displayName(db, id: sessionId, threadVariant: .contact)
self.profilePicture = ProfileManager.profileAvatar(db, id: sessionId)
.map { UIImage(data: $0) }
.defaulting(to: Identicon.generatePlaceholderIcon(seed: sessionId, text: self.contactName, size: 300))
WebRTCSession.current = self.webRTCSession

1
Session/Conversations/Context Menu/ContextMenuVC.swift

@ -174,6 +174,7 @@ final class ContextMenuVC: UIViewController {
animations: { [weak self] in
self?.blurView.effect = nil
self?.menuView.alpha = 0
self?.snapshot.alpha = 0
self?.timestampLabel.alpha = 0
},
completion: { [weak self] _ in

10
Session/Conversations/ConversationViewModel.swift

@ -185,6 +185,16 @@ public class ConversationViewModel: OWSAudioPlayerDelegate {
return SQL("LEFT JOIN \(Contact.self) ON \(contact[.id]) = \(interaction[.threadId])")
}()
),
PagedData.ObservedChanges(
table: Profile.self,
columns: [.profilePictureFileName],
joinToPagedType: {
let interaction: TypedTableAlias<Interaction> = TypedTableAlias()
let profile: TypedTableAlias<Profile> = TypedTableAlias()
return SQL("LEFT JOIN \(Profile.self) ON \(profile[.id]) = \(interaction[.authorId])")
}()
)
],
filterSQL: MessageViewModel.filterSQL(threadId: threadId),

24
Session/Conversations/Message Cells/VisibleMessageCell.swift

@ -38,6 +38,15 @@ final class VisibleMessageCell: MessageCell, UITextViewDelegate, BodyTextViewDel
// MARK: - UI Components
private lazy var viewsToMoveForReply: [UIView] = [
bubbleView,
bubbleBackgroundView,
profilePictureView,
replyButton,
timerView,
messageStatusImageView
]
private lazy var profilePictureView: ProfilePictureView = {
let result: ProfilePictureView = ProfilePictureView()
result.set(.height, to: Values.verySmallProfilePictureSize)
@ -619,8 +628,7 @@ final class VisibleMessageCell: MessageCell, UITextViewDelegate, BodyTextViewDel
super.prepareForReuse()
unloadContent?()
let viewsToMove = [ bubbleView, profilePictureView, replyButton, timerView, messageStatusImageView ]
viewsToMove.forEach { $0.transform = .identity }
viewsToMoveForReply.forEach { $0.transform = .identity }
replyButton.alpha = 0
timerView.prepareForReuse()
}
@ -726,9 +734,6 @@ final class VisibleMessageCell: MessageCell, UITextViewDelegate, BodyTextViewDel
@objc private func handlePan(_ gestureRecognizer: UIPanGestureRecognizer) {
guard let cellViewModel: MessageViewModel = self.viewModel else { return }
let viewsToMove: [UIView] = [
bubbleView, bubbleBackgroundView, profilePictureView, replyButton, timerView, messageStatusImageView
]
let translationX = gestureRecognizer.translation(in: self).x.clamp(-CGFloat.greatestFiniteMagnitude, 0)
switch gestureRecognizer.state {
@ -739,7 +744,7 @@ final class VisibleMessageCell: MessageCell, UITextViewDelegate, BodyTextViewDel
let damping: CGFloat = 20
let sign: CGFloat = -1
let x = (damping * (sqrt(abs(translationX)) / sqrt(damping))) * sign
viewsToMove.forEach { $0.transform = CGAffineTransform(translationX: x, y: 0) }
viewsToMoveForReply.forEach { $0.transform = CGAffineTransform(translationX: x, y: 0) }
if timerView.isHidden {
replyButton.alpha = abs(translationX) / VisibleMessageCell.maxBubbleTranslationX
} else {
@ -778,10 +783,9 @@ final class VisibleMessageCell: MessageCell, UITextViewDelegate, BodyTextViewDel
}
private func resetReply() {
let viewsToMove = [ bubbleView, profilePictureView, replyButton, timerView, messageStatusImageView ]
UIView.animate(withDuration: 0.25) {
viewsToMove.forEach { $0.transform = .identity }
self.replyButton.alpha = 0
UIView.animate(withDuration: 0.25) { [weak self] in
self?.viewsToMoveForReply.forEach { $0.transform = .identity }
self?.replyButton.alpha = 0
}
}

28
Session/Conversations/Settings/OWSConversationSettingsViewController.m

@ -53,8 +53,6 @@ CGFloat kIconViewLength = 24;
return self;
}
[self commonInit];
return self;
}
@ -65,8 +63,6 @@ CGFloat kIconViewLength = 24;
return self;
}
[self commonInit];
return self;
}
@ -77,32 +73,11 @@ CGFloat kIconViewLength = 24;
return self;
}
[self commonInit];
return self;
}
- (void)commonInit
{
[self observeNotifications];
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
#pragma mark
- (void)observeNotifications
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(otherUsersProfileDidChange:)
name:NSNotification.otherUsersProfileDidChange
object:nil];
}
- (void)configureWithThreadId:(NSString *)threadId threadName:(NSString *)threadName isClosedGroup:(BOOL)isClosedGroup isOpenGroup:(BOOL)isOpenGroup isNoteToSelf:(BOOL)isNoteToSelf {
self.threadId = threadId;
self.threadName = threadName;
@ -964,9 +939,10 @@ CGFloat kIconViewLength = 24;
#pragma mark - Notifications
// FIXME: When this screen gets refactored, make sure to observe changes for relevant profile image updates
- (void)otherUsersProfileDidChange:(NSNotification *)notification
{
NSString *recipientId = notification.userInfo[NSNotification.profileRecipientIdKey];
NSString *recipientId = @"";//notification.userInfo[NSNotification.profileRecipientIdKey];
OWSAssertDebug(recipientId.length > 0);
if (recipientId.length > 0 && !self.isClosedGroup && !self.isOpenGroup && self.threadId == recipientId) {

15
Session/Home/HomeVC.swift

@ -518,6 +518,7 @@ final class HomeVC: BaseVC, UITableViewDataSource, UITableViewDelegate, NewConve
show(
threadViewModel.threadId,
variant: threadViewModel.threadVariant,
isMessageRequest: (threadViewModel.threadIsMessageRequest == true),
with: .none,
focusedInteractionId: nil,
animated: true
@ -651,6 +652,7 @@ final class HomeVC: BaseVC, UITableViewDataSource, UITableViewDelegate, NewConve
func show(
_ threadId: String,
variant: SessionThread.Variant,
isMessageRequest: Bool,
with action: ConversationViewModel.Action,
focusedInteractionId: Int64?,
animated: Bool
@ -659,8 +661,17 @@ final class HomeVC: BaseVC, UITableViewDataSource, UITableViewDelegate, NewConve
presentedVC.dismiss(animated: false, completion: nil)
}
let conversationVC: ConversationVC = ConversationVC(threadId: threadId, threadVariant: variant, focusedInteractionId: focusedInteractionId)
self.navigationController?.setViewControllers([ self, conversationVC ], animated: animated)
let finalViewControllers: [UIViewController] = [
self,
(isMessageRequest ? MessageRequestsViewController() : nil),
ConversationVC(
threadId: threadId,
threadVariant: variant,
focusedInteractionId: focusedInteractionId
)
].compactMap { $0 }
self.navigationController?.setViewControllers(finalViewControllers, animated: animated)
}
@objc private func openSettings() {

18
Session/Meta/AppDelegate.swift

@ -142,14 +142,16 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
AppReadiness.runNowOrWhenAppDidBecomeReady { [weak self] in
self?.handleActivation()
}
/// Clear all notifications whenever we become active
///
/// **Note:** It looks like when opening the app from a notification, `userNotificationCenter(didReceive)` is
/// no longer always called before we become active so we need to dispatch this to run on the next run loop
DispatchQueue.main.async { [weak self] in
self?.clearAllNotificationsAndRestoreBadgeCount()
/// Clear all notifications whenever we become active once the app is ready
///
/// **Note:** It looks like when opening the app from a notification, `userNotificationCenter(didReceive)` is
/// no longer always called before `applicationDidBecomeActive` we need to trigger the "clear notifications" logic
/// within the `runNowOrWhenAppDidBecomeReady` callback and dispatch to the next run loop to ensure it runs after
/// the notification has actually been handled
DispatchQueue.main.async { [weak self] in
self?.clearAllNotificationsAndRestoreBadgeCount()
}
}
// On every activation, clear old temp directories.

15
Session/Meta/SessionApp.swift

@ -10,15 +10,21 @@ public struct SessionApp {
// MARK: - View Convenience Methods
public static func presentConversation(for threadId: String, action: ConversationViewModel.Action = .none, animated: Bool) {
let maybeThread: SessionThread? = Storage.shared.write { db in
try SessionThread.fetchOrCreate(db, id: threadId, variant: .contact)
let maybeThreadInfo: (thread: SessionThread, isMessageRequest: Bool)? = Storage.shared.write { db in
let thread: SessionThread = try SessionThread.fetchOrCreate(db, id: threadId, variant: .contact)
return (thread, thread.isMessageRequest(db))
}
guard let variant: SessionThread.Variant = maybeThread?.variant else { return }
guard
let variant: SessionThread.Variant = maybeThreadInfo?.thread.variant,
let isMessageRequest: Bool = maybeThreadInfo?.isMessageRequest
else { return }
self.presentConversation(
for: threadId,
threadVariant: variant,
isMessageRequest: isMessageRequest,
action: action,
focusInteractionId: nil,
animated: animated
@ -28,6 +34,7 @@ public struct SessionApp {
public static func presentConversation(
for threadId: String,
threadVariant: SessionThread.Variant,
isMessageRequest: Bool,
action: ConversationViewModel.Action,
focusInteractionId: Int64?,
animated: Bool
@ -37,6 +44,7 @@ public struct SessionApp {
self.presentConversation(
for: threadId,
threadVariant: threadVariant,
isMessageRequest: isMessageRequest,
action: action,
focusInteractionId: focusInteractionId,
animated: animated
@ -48,6 +56,7 @@ public struct SessionApp {
homeViewController.wrappedValue?.show(
threadId,
variant: threadVariant,
isMessageRequest: isMessageRequest,
with: action,
focusedInteractionId: focusInteractionId,
animated: animated

2
Session/Meta/Translations/de.lproj/Localizable.strings

@ -659,4 +659,4 @@
"CHATS_TITLE" = "Chats";
"MESSAGE_TRIMMING_TITLE" = "Message Trimming";
"MESSAGE_TRIMMING_OPEN_GROUP_TITLE" = "Delete Old Open Group Messages";
"MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION" = "Automatically delete open group messages which are older than 6 months when starting the app";
"MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION" = "Automatically delete messages which are older than 6 months from open groups with over 2,000 messages when starting the app";

2
Session/Meta/Translations/en.lproj/Localizable.strings

@ -659,5 +659,5 @@
"CHATS_TITLE" = "Chats";
"MESSAGE_TRIMMING_TITLE" = "Message Trimming";
"MESSAGE_TRIMMING_OPEN_GROUP_TITLE" = "Delete Old Open Group Messages";
"MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION" = "Automatically delete open group messages which are older than 6 months when starting the app";
"MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION" = "Automatically delete messages which are older than 6 months from open groups with over 2,000 messages when starting the app";

2
Session/Meta/Translations/es.lproj/Localizable.strings

@ -659,4 +659,4 @@
"CHATS_TITLE" = "Chats";
"MESSAGE_TRIMMING_TITLE" = "Message Trimming";
"MESSAGE_TRIMMING_OPEN_GROUP_TITLE" = "Delete Old Open Group Messages";
"MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION" = "Automatically delete open group messages which are older than 6 months when starting the app";
"MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION" = "Automatically delete messages which are older than 6 months from open groups with over 2,000 messages when starting the app";

2
Session/Meta/Translations/fa.lproj/Localizable.strings

@ -659,4 +659,4 @@
"CHATS_TITLE" = "Chats";
"MESSAGE_TRIMMING_TITLE" = "Message Trimming";
"MESSAGE_TRIMMING_OPEN_GROUP_TITLE" = "Delete Old Open Group Messages";
"MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION" = "Automatically delete open group messages which are older than 6 months when starting the app";
"MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION" = "Automatically delete messages which are older than 6 months from open groups with over 2,000 messages when starting the app";

2
Session/Meta/Translations/fi.lproj/Localizable.strings

@ -659,4 +659,4 @@
"CHATS_TITLE" = "Chats";
"MESSAGE_TRIMMING_TITLE" = "Message Trimming";
"MESSAGE_TRIMMING_OPEN_GROUP_TITLE" = "Delete Old Open Group Messages";
"MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION" = "Automatically delete open group messages which are older than 6 months when starting the app";
"MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION" = "Automatically delete messages which are older than 6 months from open groups with over 2,000 messages when starting the app";

2
Session/Meta/Translations/fr.lproj/Localizable.strings

@ -659,4 +659,4 @@
"CHATS_TITLE" = "Chats";
"MESSAGE_TRIMMING_TITLE" = "Message Trimming";
"MESSAGE_TRIMMING_OPEN_GROUP_TITLE" = "Delete Old Open Group Messages";
"MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION" = "Automatically delete open group messages which are older than 6 months when starting the app";
"MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION" = "Automatically delete messages which are older than 6 months from open groups with over 2,000 messages when starting the app";

2
Session/Meta/Translations/hi.lproj/Localizable.strings

@ -659,4 +659,4 @@
"CHATS_TITLE" = "Chats";
"MESSAGE_TRIMMING_TITLE" = "Message Trimming";
"MESSAGE_TRIMMING_OPEN_GROUP_TITLE" = "Delete Old Open Group Messages";
"MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION" = "Automatically delete open group messages which are older than 6 months when starting the app";
"MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION" = "Automatically delete messages which are older than 6 months from open groups with over 2,000 messages when starting the app";

2
Session/Meta/Translations/hr.lproj/Localizable.strings

@ -659,4 +659,4 @@
"CHATS_TITLE" = "Chats";
"MESSAGE_TRIMMING_TITLE" = "Message Trimming";
"MESSAGE_TRIMMING_OPEN_GROUP_TITLE" = "Delete Old Open Group Messages";
"MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION" = "Automatically delete open group messages which are older than 6 months when starting the app";
"MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION" = "Automatically delete messages which are older than 6 months from open groups with over 2,000 messages when starting the app";

2
Session/Meta/Translations/id-ID.lproj/Localizable.strings

@ -659,4 +659,4 @@
"CHATS_TITLE" = "Chats";
"MESSAGE_TRIMMING_TITLE" = "Message Trimming";
"MESSAGE_TRIMMING_OPEN_GROUP_TITLE" = "Delete Old Open Group Messages";
"MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION" = "Automatically delete open group messages which are older than 6 months when starting the app";
"MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION" = "Automatically delete messages which are older than 6 months from open groups with over 2,000 messages when starting the app";

2
Session/Meta/Translations/it.lproj/Localizable.strings

@ -659,4 +659,4 @@
"CHATS_TITLE" = "Chats";
"MESSAGE_TRIMMING_TITLE" = "Message Trimming";
"MESSAGE_TRIMMING_OPEN_GROUP_TITLE" = "Delete Old Open Group Messages";
"MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION" = "Automatically delete open group messages which are older than 6 months when starting the app";
"MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION" = "Automatically delete messages which are older than 6 months from open groups with over 2,000 messages when starting the app";

2
Session/Meta/Translations/ja.lproj/Localizable.strings

@ -659,4 +659,4 @@
"CHATS_TITLE" = "Chats";
"MESSAGE_TRIMMING_TITLE" = "Message Trimming";
"MESSAGE_TRIMMING_OPEN_GROUP_TITLE" = "Delete Old Open Group Messages";
"MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION" = "Automatically delete open group messages which are older than 6 months when starting the app";
"MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION" = "Automatically delete messages which are older than 6 months from open groups with over 2,000 messages when starting the app";

2
Session/Meta/Translations/nl.lproj/Localizable.strings

@ -659,4 +659,4 @@
"CHATS_TITLE" = "Chats";
"MESSAGE_TRIMMING_TITLE" = "Message Trimming";
"MESSAGE_TRIMMING_OPEN_GROUP_TITLE" = "Delete Old Open Group Messages";
"MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION" = "Automatically delete open group messages which are older than 6 months when starting the app";
"MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION" = "Automatically delete messages which are older than 6 months from open groups with over 2,000 messages when starting the app";

2
Session/Meta/Translations/pl.lproj/Localizable.strings

@ -659,4 +659,4 @@
"CHATS_TITLE" = "Chats";
"MESSAGE_TRIMMING_TITLE" = "Message Trimming";
"MESSAGE_TRIMMING_OPEN_GROUP_TITLE" = "Delete Old Open Group Messages";
"MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION" = "Automatically delete open group messages which are older than 6 months when starting the app";
"MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION" = "Automatically delete messages which are older than 6 months from open groups with over 2,000 messages when starting the app";

2
Session/Meta/Translations/pt_BR.lproj/Localizable.strings

@ -659,4 +659,4 @@
"CHATS_TITLE" = "Chats";
"MESSAGE_TRIMMING_TITLE" = "Message Trimming";
"MESSAGE_TRIMMING_OPEN_GROUP_TITLE" = "Delete Old Open Group Messages";
"MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION" = "Automatically delete open group messages which are older than 6 months when starting the app";
"MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION" = "Automatically delete messages which are older than 6 months from open groups with over 2,000 messages when starting the app";

2
Session/Meta/Translations/ru.lproj/Localizable.strings

@ -659,4 +659,4 @@
"CHATS_TITLE" = "Chats";
"MESSAGE_TRIMMING_TITLE" = "Message Trimming";
"MESSAGE_TRIMMING_OPEN_GROUP_TITLE" = "Delete Old Open Group Messages";
"MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION" = "Automatically delete open group messages which are older than 6 months when starting the app";
"MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION" = "Automatically delete messages which are older than 6 months from open groups with over 2,000 messages when starting the app";

2
Session/Meta/Translations/si.lproj/Localizable.strings

@ -659,4 +659,4 @@
"CHATS_TITLE" = "Chats";
"MESSAGE_TRIMMING_TITLE" = "Message Trimming";
"MESSAGE_TRIMMING_OPEN_GROUP_TITLE" = "Delete Old Open Group Messages";
"MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION" = "Automatically delete open group messages which are older than 6 months when starting the app";
"MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION" = "Automatically delete messages which are older than 6 months from open groups with over 2,000 messages when starting the app";

2
Session/Meta/Translations/sk.lproj/Localizable.strings

@ -659,4 +659,4 @@
"CHATS_TITLE" = "Chats";
"MESSAGE_TRIMMING_TITLE" = "Message Trimming";
"MESSAGE_TRIMMING_OPEN_GROUP_TITLE" = "Delete Old Open Group Messages";
"MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION" = "Automatically delete open group messages which are older than 6 months when starting the app";
"MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION" = "Automatically delete messages which are older than 6 months from open groups with over 2,000 messages when starting the app";

2
Session/Meta/Translations/sv.lproj/Localizable.strings

@ -659,4 +659,4 @@
"CHATS_TITLE" = "Chats";
"MESSAGE_TRIMMING_TITLE" = "Message Trimming";
"MESSAGE_TRIMMING_OPEN_GROUP_TITLE" = "Delete Old Open Group Messages";
"MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION" = "Automatically delete open group messages which are older than 6 months when starting the app";
"MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION" = "Automatically delete messages which are older than 6 months from open groups with over 2,000 messages when starting the app";

2
Session/Meta/Translations/th.lproj/Localizable.strings

@ -659,4 +659,4 @@
"CHATS_TITLE" = "Chats";
"MESSAGE_TRIMMING_TITLE" = "Message Trimming";
"MESSAGE_TRIMMING_OPEN_GROUP_TITLE" = "Delete Old Open Group Messages";
"MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION" = "Automatically delete open group messages which are older than 6 months when starting the app";
"MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION" = "Automatically delete messages which are older than 6 months from open groups with over 2,000 messages when starting the app";

2
Session/Meta/Translations/vi-VN.lproj/Localizable.strings

@ -659,4 +659,4 @@
"CHATS_TITLE" = "Chats";
"MESSAGE_TRIMMING_TITLE" = "Message Trimming";
"MESSAGE_TRIMMING_OPEN_GROUP_TITLE" = "Delete Old Open Group Messages";
"MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION" = "Automatically delete open group messages which are older than 6 months when starting the app";
"MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION" = "Automatically delete messages which are older than 6 months from open groups with over 2,000 messages when starting the app";

2
Session/Meta/Translations/zh-Hant.lproj/Localizable.strings

@ -659,4 +659,4 @@
"CHATS_TITLE" = "Chats";
"MESSAGE_TRIMMING_TITLE" = "Message Trimming";
"MESSAGE_TRIMMING_OPEN_GROUP_TITLE" = "Delete Old Open Group Messages";
"MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION" = "Automatically delete open group messages which are older than 6 months when starting the app";
"MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION" = "Automatically delete messages which are older than 6 months from open groups with over 2,000 messages when starting the app";

2
Session/Meta/Translations/zh_CN.lproj/Localizable.strings

@ -659,4 +659,4 @@
"CHATS_TITLE" = "Chats";
"MESSAGE_TRIMMING_TITLE" = "Message Trimming";
"MESSAGE_TRIMMING_OPEN_GROUP_TITLE" = "Delete Old Open Group Messages";
"MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION" = "Automatically delete open group messages which are older than 6 months when starting the app";
"MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION" = "Automatically delete messages which are older than 6 months from open groups with over 2,000 messages when starting the app";

31
Session/Notifications/AppNotifications.swift

@ -142,31 +142,11 @@ public class NotificationPresenter: NSObject, NotificationsProtocol {
}
public func notifyUser(_ db: Database, for interaction: Interaction, in thread: SessionThread, isBackgroundPoll: Bool) {
guard Date().timeIntervalSince1970 > (thread.mutedUntilTimestamp ?? 0) else { return }
let userPublicKey: String = getUserHexEncodedPublicKey(db)
let isMessageRequest: Bool = thread.isMessageRequest(db, includeNonVisible: true)
// If the thread is a message request and the user hasn't hidden message requests then we need
// to check if this is the only message request thread (group threads can't be message requests
// so just ignore those and if the user has hidden message requests then we want to show the
// notification regardless of how many message requests there are)
if thread.variant == .contact {
if isMessageRequest && !db[.hasHiddenMessageRequests] {
let numMessageRequestThreads: Int = (try? SessionThread
.messageRequestsQuery(userPublicKey: userPublicKey, includeNonVisible: true)
.fetchCount(db))
.defaulting(to: 0)
// Allow this to show a notification if there are no message requests (ie. this is the first one)
guard numMessageRequestThreads == 0 else { return }
}
else if isMessageRequest && db[.hasHiddenMessageRequests] {
// If there are other interactions on this thread already then don't show the notification
if ((try? thread.interactions.fetchCount(db)) ?? 0) > 1 { return }
db[.hasHiddenMessageRequests] = false
}
// Ensure we should be showing a notification for the thread
guard thread.shouldShowNotification(db, for: interaction, isMessageRequest: isMessageRequest) else {
return
}
let identifier: String = interaction.notificationIdentifier(isBackgroundPoll: isBackgroundPoll)
@ -180,11 +160,6 @@ public class NotificationPresenter: NSObject, NotificationsProtocol {
// see https://developer.apple.com/documentation/uikit/uilocalnotification/1616646-alertbody
// for more details.
let messageText: String? = String.filterNotificationText(rawMessageText)
// Don't fire the notification if the current user isn't mentioned
// and isOnlyNotifyingForMentions is on.
guard !thread.onlyNotifyForMentions || interaction.hasMention else { return }
let notificationTitle: String?
var notificationBody: String?

3
Session/Onboarding/DisplayNameVC.swift

@ -170,7 +170,8 @@ final class DisplayNameVC: BaseVC {
ProfileManager.updateLocal(
queue: DispatchQueue.global(qos: .default),
profileName: displayName,
avatarImage: nil,
image: nil,
imageFilePath: nil,
requiredSync: false
)
let pnModeVC = PNModeVC()

7
Session/Settings/ChatSettingsViewController.swift

@ -14,9 +14,6 @@ class ChatSettingsViewController: OWSTableViewController {
self.updateTableContents()
ViewControllerUtilities.setUpDefaultSessionStyle(for: self, title: "CHATS_TITLE".localized(), hasCustomBackButton: false)
let closeButton: UIBarButtonItem = UIBarButtonItem(image: UIImage(named: "X"), style: .plain, target: self, action: #selector(close(_:)))
self.navigationItem.leftBarButtonItem = closeButton
}
override func viewDidAppear(_ animated: Bool) {
@ -47,9 +44,11 @@ class ChatSettingsViewController: OWSTableViewController {
// MARK: - Actions
@objc private func didToggleTrimOpenGroupsSwitch(_ sender: UISwitch) {
let switchIsOn: Bool = sender.isOn
Storage.shared.writeAsync(
updates: { db in
db[.trimOpenGroupMessagesOlderThanSixMonths] = !sender.isOn
db[.trimOpenGroupMessagesOlderThanSixMonths] = !switchIsOn
},
completion: { [weak self] _, _ in
self?.updateTableContents()

44
Session/Settings/SettingsVC.swift

@ -7,7 +7,6 @@ import SessionMessagingKit
import SignalUtilitiesKit
final class SettingsVC: BaseVC, AvatarViewHelperDelegate {
private var profilePictureToBeUploaded: UIImage?
private var displayNameToBeUploaded: String?
private var isEditingDisplayName = false { didSet { handleIsEditingDisplayNameChanged() } }
@ -419,34 +418,47 @@ final class SettingsVC: BaseVC, AvatarViewHelperDelegate {
}
}
func avatarDidChange(_ image: UIImage) {
let maxSize = Int(ProfileManager.maxAvatarDiameter)
profilePictureToBeUploaded = image.resizedImage(toFillPixelSize: CGSize(width: maxSize, height: maxSize))
updateProfile(isUpdatingDisplayName: false, isUpdatingProfilePicture: true)
func avatarDidChange(_ image: UIImage?, filePath: String?) {
updateProfile(
profilePicture: image,
profilePictureFilePath: filePath,
isUpdatingDisplayName: false,
isUpdatingProfilePicture: true
)
}
func clearAvatar() {
profilePictureToBeUploaded = nil
updateProfile(isUpdatingDisplayName: false, isUpdatingProfilePicture: true)
updateProfile(
profilePicture: nil,
profilePictureFilePath: nil,
isUpdatingDisplayName: false,
isUpdatingProfilePicture: true
)
}
private func updateProfile(isUpdatingDisplayName: Bool, isUpdatingProfilePicture: Bool) {
private func updateProfile(
profilePicture: UIImage?,
profilePictureFilePath: String?,
isUpdatingDisplayName: Bool,
isUpdatingProfilePicture: Bool
) {
let userDefaults = UserDefaults.standard
let name: String? = (displayNameToBeUploaded ?? Profile.fetchOrCreateCurrentUser().name)
let profilePicture: UIImage? = (profilePictureToBeUploaded ?? ProfileManager.profileAvatar(id: getUserHexEncodedPublicKey()))
let imageFilePath: String? = (profilePictureFilePath ?? ProfileManager.profileAvatarFilepath(id: getUserHexEncodedPublicKey()))
ModalActivityIndicatorViewController.present(fromViewController: navigationController!, canCancel: false) { [weak self, displayNameToBeUploaded, profilePictureToBeUploaded] modalActivityIndicator in
ModalActivityIndicatorViewController.present(fromViewController: navigationController!, canCancel: false) { [weak self, displayNameToBeUploaded] modalActivityIndicator in
ProfileManager.updateLocal(
queue: DispatchQueue.global(qos: .default),
profileName: (name ?? ""),
avatarImage: profilePicture,
image: profilePicture,
imageFilePath: imageFilePath,
requiredSync: true,
success: { db, updatedProfile in
if displayNameToBeUploaded != nil {
userDefaults[.lastDisplayNameUpdate] = Date()
}
if profilePictureToBeUploaded != nil {
if isUpdatingProfilePicture {
userDefaults[.lastProfilePictureUpdate] = Date()
}
@ -462,7 +474,6 @@ final class SettingsVC: BaseVC, AvatarViewHelperDelegate {
threadVariant: .contact
)
self?.displayNameLabel.text = name
self?.profilePictureToBeUploaded = nil
self?.displayNameToBeUploaded = nil
}
}
@ -556,7 +567,12 @@ final class SettingsVC: BaseVC, AvatarViewHelperDelegate {
}
isEditingDisplayName = false
displayNameToBeUploaded = displayName
updateProfile(isUpdatingDisplayName: true, isUpdatingProfilePicture: false)
updateProfile(
profilePicture: nil,
profilePictureFilePath: nil,
isUpdatingDisplayName: true,
isUpdatingProfilePicture: false
)
}
@objc private func showEditProfilePictureUI() {

2
Session/Utilities/AvatarViewHelper.h

@ -13,7 +13,7 @@ NS_ASSUME_NONNULL_BEGIN
- (nullable NSString *)avatarActionSheetTitle;
- (void)avatarDidChange:(UIImage *)image;
- (void)avatarDidChange:(nullable UIImage *)image filePath:(nullable NSString *)filePath;
- (UIViewController *)fromViewController;

23
Session/Utilities/AvatarViewHelper.m

@ -123,19 +123,34 @@ NS_ASSUME_NONNULL_BEGIN
[SNAppearance switchToSessionAppearance];
NSURL* imageURL = [info objectForKey:UIImagePickerControllerImageURL];
UIImage *rawAvatar = [info objectForKey:UIImagePickerControllerOriginalImage];
[self.delegate.fromViewController
dismissViewControllerAnimated:YES
completion:^{
OWSAssertIsOnMainThread();
// Check if the user selected an animated image (if so then don't crop, just
// set the avatar directly
NSString *type;
if ([imageURL getResourceValue:&type forKey:NSURLTypeIdentifierKey error:nil]) {
if ([[MIMETypeUtil supportedAnimatedImageUTITypes] containsObject:type]) {
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate avatarDidChange:nil filePath: imageURL.path];
});
return;
}
}
if (rawAvatar) {
OWSAssertIsOnMainThread();
CropScaleImageViewController *vc = [[CropScaleImageViewController alloc]
initWithSrcImage:rawAvatar
successCompletion:^(UIImage *_Nonnull dstImage) {
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate avatarDidChange:dstImage];
[self.delegate avatarDidChange:dstImage filePath:nil];
});
}];
[self.delegate.fromViewController presentViewController:vc

2
Session/Utilities/UIApplication+OWS.swift

@ -17,8 +17,6 @@ import UIKit
internal func findFrontmostViewController(ignoringAlerts: Bool) -> UIViewController? {
guard let window: UIWindow = CurrentAppContext().mainWindow else { return nil }
Logger.error("findFrontmostViewController: \(window)")
guard let viewController: UIViewController = window.rootViewController else {
owsFailDebug("Missing root view controller.")
return nil

3
SessionMessagingKit/Database/Migrations/_003_YDBToGRDBMigration.swift

@ -1465,6 +1465,9 @@ enum _003_YDBToGRDBMigration: Migration {
db[.hasSentAMessage] = (legacyPreferences[SMKLegacy.preferencesKeyHasSentAMessageKey] as? Bool == true)
db[.isReadyForAppExtensions] = CurrentAppContext().appUserDefaults().bool(forKey: SMKLegacy.preferencesKeyIsReadyForAppExtensions)
// We want this setting to be on by default
db[.trimOpenGroupMessagesOlderThanSixMonths] = true
Storage.update(progress: 1, for: self, in: target) // In case this is the last migration
}

5
SessionMessagingKit/Database/Models/Attachment.swift

@ -1049,7 +1049,10 @@ extension Attachment {
uploadPromise
.done(on: queue) { fileId in
// Save the final upload info
/// Save the final upload info
///
/// **Note:** We **MUST** use the `.with` function here to ensure the `isValid` flag is
/// updated correctly
let uploadedAttachment: Attachment? = Storage.shared.write { db in
try updatedAttachment?
.with(

25
SessionMessagingKit/Database/Models/Profile.swift

@ -72,31 +72,6 @@ public struct Profile: Codable, Identifiable, Equatable, Hashable, FetchableReco
)
"""
}
// MARK: - PersistableRecord
public func save(_ db: Database) throws {
let oldProfile: Profile? = try? Profile.fetchOne(db, id: id)
try performSave(db)
db.afterNextTransactionCommit { db in
// Delete old profile picture if needed
if let oldProfilePictureFileName: String = oldProfile?.profilePictureFileName, oldProfilePictureFileName != profilePictureFileName {
let path: String = ProfileManager.profileAvatarFilepath(filename: oldProfilePictureFileName)
DispatchQueue.global(qos: .default).async {
OWSFileSystem.deleteFileIfExists(path)
}
}
// FIXME: Remove this once the OWSConversationSettingsViewController has been refactored and is observing DB changes
if id != getUserHexEncodedPublicKey(db) {
let userInfo = [ Notification.Key.profileRecipientId.rawValue: id ]
NotificationCenter.default.post(name: .otherUsersProfileDidChange, object: nil, userInfo: userInfo)
}
}
}
}
// MARK: - Codable

55
SessionMessagingKit/Database/Models/SessionThread.swift

@ -174,7 +174,12 @@ public extension SessionThread {
(includeNonVisible || shouldBeVisible) &&
variant == .contact &&
id != getUserHexEncodedPublicKey(db) && // Note to self
(try? Contact.fetchOne(db, id: id))?.isApproved != true
(try? Contact
.filter(id: id)
.select(.isApproved)
.asRequest(of: Bool.self)
.fetchOne(db))
.defaulting(to: false) == false
)
}
}
@ -196,7 +201,7 @@ public extension SessionThread {
"""
}
static func unreadMessageRequestsThreadIdQuery(userPublicKey: String) -> SQLRequest<Int64> {
static func unreadMessageRequestsThreadIdQuery(userPublicKey: String, includeNonVisible: Bool = false) -> SQLRequest<String> {
let thread: TypedTableAlias<SessionThread> = TypedTableAlias()
let interaction: TypedTableAlias<Interaction> = TypedTableAlias()
let contact: TypedTableAlias<Contact> = TypedTableAlias()
@ -210,7 +215,7 @@ public extension SessionThread {
)
LEFT JOIN \(Contact.self) ON \(contact[.id]) = \(thread[.id])
WHERE (
\(SessionThread.isMessageRequest(userPublicKey: userPublicKey))
\(SessionThread.isMessageRequest(userPublicKey: userPublicKey, includeNonVisible: includeNonVisible))
)
GROUP BY \(thread[.id])
"""
@ -245,6 +250,50 @@ public extension SessionThread {
)
}
func shouldShowNotification(_ db: Database, for interaction: Interaction, isMessageRequest: Bool) -> Bool {
// Ensure that the thread isn't muted and either the thread isn't only notifying for mentions
// or the user was actually mentioned
guard
Date().timeIntervalSince1970 > (self.mutedUntilTimestamp ?? 0) &&
(
self.variant == .contact ||
!self.onlyNotifyForMentions ||
interaction.hasMention
)
else { return false }
let userPublicKey: String = getUserHexEncodedPublicKey(db)
// No need to notify the user for self-send messages
guard interaction.authorId != userPublicKey else { return false }
// If the thread is a message request then we only want to notify for the first message
if self.variant == .contact && isMessageRequest {
let hasHiddenMessageRequests: Bool = db[.hasHiddenMessageRequests]
// If the user hasn't hidden the message requests section then only show the notification if
// all the other message request threads have been read
if !hasHiddenMessageRequests {
let numUnreadMessageRequestThreads: Int = (try? SessionThread
.unreadMessageRequestsThreadIdQuery(userPublicKey: userPublicKey, includeNonVisible: true)
.fetchCount(db))
.defaulting(to: 1)
guard numUnreadMessageRequestThreads == 1 else { return false }
}
// We only want to show a notification for the first interaction in the thread
guard ((try? self.interactions.fetchCount(db)) ?? 0) <= 1 else { return false }
// Need to re-show the message requests section if it had been hidden
if hasHiddenMessageRequests {
db[.hasHiddenMessageRequests] = false
}
}
return true
}
static func displayName(
threadId: String,
variant: Variant,

21
SessionMessagingKit/Database/Notification+Contacts.swift

@ -1,21 +0,0 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import SessionUtilitiesKit
// FIXME: Remove these extensions once the OWSConversationSettingsViewModel is refactored to swift and uses proper database observation
public extension Notification.Name {
static let otherUsersProfileDidChange = Notification.Name("otherUsersProfileDidChange")
}
@objc public extension NSNotification {
@objc static let otherUsersProfileDidChange = Notification.Name.otherUsersProfileDidChange.rawValue as NSString
}
extension Notification.Key {
static let profileRecipientId = Notification.Key("profileRecipientId")
}
@objc public extension NSNotification {
static let profileRecipientIdKey = Notification.Key.profileRecipientId.rawValue as NSString
}

15
SessionMessagingKit/Jobs/Types/GarbageCollectionJob.swift

@ -16,6 +16,7 @@ public enum GarbageCollectionJob: JobExecutor {
public static var requiresThreadId: Bool = false
public static let requiresInteractionId: Bool = false
public static let approxSixMonthsInSeconds: TimeInterval = (6 * 30 * 24 * 60 * 60)
private static let minInteractionsToTrim: Int = 2000
public static func run(
_ job: Job,
@ -68,6 +69,8 @@ public enum GarbageCollectionJob: JobExecutor {
if typesToCollect.contains(.oldOpenGroupMessages) && db[.trimOpenGroupMessagesOlderThanSixMonths] {
let interaction: TypedTableAlias<Interaction> = TypedTableAlias()
let thread: TypedTableAlias<SessionThread> = TypedTableAlias()
let threadIdLiteral: SQL = SQL(stringLiteral: Interaction.Columns.threadId.name)
let minInteractionsToTrimSql: SQL = SQL("\(GarbageCollectionJob.minInteractionsToTrim)")
try db.execute(literal: """
DELETE FROM \(Interaction.self)
@ -78,7 +81,17 @@ public enum GarbageCollectionJob: JobExecutor {
\(SQL("\(thread[.variant]) = \(SessionThread.Variant.openGroup)")) AND
\(thread[.id]) = \(interaction[.threadId])
)
WHERE \(interaction[.timestampMs]) < \(timestampNow - approxSixMonthsInSeconds)
JOIN (
SELECT
COUNT(\(interaction.alias[Column.rowID])) AS interactionCount,
\(interaction[.threadId])
FROM \(Interaction.self)
GROUP BY \(interaction[.threadId])
) AS interactionInfo ON interactionInfo.\(threadIdLiteral) = \(interaction[.threadId])
WHERE (
\(interaction[.timestampMs]) < \(timestampNow - approxSixMonthsInSeconds) AND
interactionInfo.interactionCount >= \(minInteractionsToTrimSql)
)
)
""")
}

51
SessionMessagingKit/Jobs/Types/NotifyPushServerJob.swift

@ -17,10 +17,7 @@ public enum NotifyPushServerJob: JobExecutor {
failure: @escaping (Job, Error?, Bool) -> (),
deferred: @escaping (Job) -> ()
) {
let server: String = PushNotificationAPI.server
guard
let url: URL = URL(string: "\(server)/notify"),
let detailsData: Data = job.details,
let details: Details = try? JSONDecoder().decode(Details.self, from: detailsData)
else {
@ -28,34 +25,16 @@ public enum NotifyPushServerJob: JobExecutor {
return
}
let requestBody: RequestBody = RequestBody(
data: details.message.data.description,
sendTo: details.message.recipient
)
guard let body: Data = try? JSONEncoder().encode(requestBody) else {
failure(job, HTTP.Error.invalidJSON, true)
return
}
var request: URLRequest = URLRequest(url: url)
request.httpMethod = "POST"
request.allHTTPHeaderFields = [ Header.contentType.rawValue: "application/json" ]
request.httpBody = body
attempt(maxRetryCount: 4, recoveringOn: queue) {
OnionRequestAPI
.sendOnionRequest(
request,
to: server,
using: .v2,
with: PushNotificationAPI.serverPublicKey
)
.map { _ in }
}
.done(on: queue) { _ in success(job, false) }
.catch(on: queue) { error in failure(job, error, false) }
.retainUntilComplete()
PushNotificationAPI
.notify(
recipient: details.message.recipient,
with: details.message.data,
maxRetryCount: 4,
queue: queue
)
.done(on: queue) { _ in success(job, false) }
.catch(on: queue) { error in failure(job, error, false) }
.retainUntilComplete()
}
}
@ -65,14 +44,4 @@ extension NotifyPushServerJob {
public struct Details: Codable {
public let message: SnodeMessage
}
struct RequestBody: Codable {
enum CodingKeys: String, CodingKey {
case data
case sendTo = "send_to"
}
let data: String
let sendTo: String
}
}

6
SessionMessagingKit/Jobs/Types/UpdateProfilePictureJob.swift

@ -34,12 +34,14 @@ public enum UpdateProfilePictureJob: JobExecutor {
// Note: The user defaults flag is updated in ProfileManager
let profile: Profile = Profile.fetchOrCreateCurrentUser()
let profilePicture: UIImage? = ProfileManager.profileAvatar(id: profile.id)
let profileFilePath: String? = profile.profilePictureFileName
.map { ProfileManager.profileAvatarFilepath(filename: $0) }
ProfileManager.updateLocal(
queue: queue,
profileName: profile.name,
avatarImage: profilePicture,
image: nil,
imageFilePath: profileFilePath,
requiredSync: true,
success: { _, _ in success(job, false) },
failure: { error in failure(job, error, false) }

9
SessionMessagingKit/Sending & Receiving/MessageReceiver.swift

@ -224,6 +224,15 @@ public enum MessageReceiver {
default: fatalError()
}
// Perform any required post-handling logic
try MessageReceiver.postHandleMessage(db, message: message, openGroupId: openGroupId)
}
public static func postHandleMessage(
_ db: Database,
message: Message,
openGroupId: String?
) throws {
// When handling any non-typing indicator message we want to make sure the thread becomes
// visible (the only other spot this flag gets set is when sending messages)
switch message {

45
SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift