Merge remote-tracking branch 'upstream/dev' into fix/remaining-reachability-oddities

# Conflicts:
#	Session/Meta/Translations/de.lproj/Localizable.strings
#	Session/Meta/Translations/en.lproj/Localizable.strings
#	Session/Meta/Translations/es.lproj/Localizable.strings
#	Session/Meta/Translations/fa.lproj/Localizable.strings
#	Session/Meta/Translations/fi.lproj/Localizable.strings
#	Session/Meta/Translations/fr.lproj/Localizable.strings
#	Session/Meta/Translations/hi.lproj/Localizable.strings
#	Session/Meta/Translations/hr.lproj/Localizable.strings
#	Session/Meta/Translations/id-ID.lproj/Localizable.strings
#	Session/Meta/Translations/it.lproj/Localizable.strings
#	Session/Meta/Translations/ja.lproj/Localizable.strings
#	Session/Meta/Translations/nl.lproj/Localizable.strings
#	Session/Meta/Translations/pl.lproj/Localizable.strings
#	Session/Meta/Translations/pt_BR.lproj/Localizable.strings
#	Session/Meta/Translations/ru.lproj/Localizable.strings
#	Session/Meta/Translations/si.lproj/Localizable.strings
#	Session/Meta/Translations/sk.lproj/Localizable.strings
#	Session/Meta/Translations/sv.lproj/Localizable.strings
#	Session/Meta/Translations/th.lproj/Localizable.strings
#	Session/Meta/Translations/vi-VN.lproj/Localizable.strings
#	Session/Meta/Translations/zh-Hant.lproj/Localizable.strings
#	Session/Meta/Translations/zh_CN.lproj/Localizable.strings
pull/784/head
Morgan Pretty 2 years ago
commit e4d6400375

@ -6040,7 +6040,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 = 391; CURRENT_PROJECT_VERSION = 392;
DEBUG_INFORMATION_FORMAT = dwarf; DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = SUQ8J2PCT7; DEVELOPMENT_TEAM = SUQ8J2PCT7;
FRAMEWORK_SEARCH_PATHS = "$(inherited)"; FRAMEWORK_SEARCH_PATHS = "$(inherited)";
@ -6065,7 +6065,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 2.2.6; MARKETING_VERSION = 2.2.7;
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)";
@ -6113,7 +6113,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 = 391; CURRENT_PROJECT_VERSION = 392;
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;
@ -6143,7 +6143,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 2.2.6; MARKETING_VERSION = 2.2.7;
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)";
@ -6179,7 +6179,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 = 391; CURRENT_PROJECT_VERSION = 392;
DEBUG_INFORMATION_FORMAT = dwarf; DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = SUQ8J2PCT7; DEVELOPMENT_TEAM = SUQ8J2PCT7;
FRAMEWORK_SEARCH_PATHS = "$(inherited)"; FRAMEWORK_SEARCH_PATHS = "$(inherited)";
@ -6202,7 +6202,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 2.2.6; MARKETING_VERSION = 2.2.7;
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";
@ -6253,7 +6253,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 = 391; CURRENT_PROJECT_VERSION = 392;
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;
@ -6281,7 +6281,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 2.2.6; MARKETING_VERSION = 2.2.7;
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";
@ -7181,7 +7181,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 = 391; CURRENT_PROJECT_VERSION = 392;
DEVELOPMENT_TEAM = SUQ8J2PCT7; DEVELOPMENT_TEAM = SUQ8J2PCT7;
FRAMEWORK_SEARCH_PATHS = ( FRAMEWORK_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
@ -7220,7 +7220,7 @@
"$(SRCROOT)", "$(SRCROOT)",
); );
LLVM_LTO = NO; LLVM_LTO = NO;
MARKETING_VERSION = 2.2.6; MARKETING_VERSION = 2.2.7;
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";
@ -7253,7 +7253,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 = 391; CURRENT_PROJECT_VERSION = 392;
DEVELOPMENT_TEAM = SUQ8J2PCT7; DEVELOPMENT_TEAM = SUQ8J2PCT7;
FRAMEWORK_SEARCH_PATHS = ( FRAMEWORK_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
@ -7292,7 +7292,7 @@
"$(SRCROOT)", "$(SRCROOT)",
); );
LLVM_LTO = NO; LLVM_LTO = NO;
MARKETING_VERSION = 2.2.6; MARKETING_VERSION = 2.2.7;
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;

@ -122,6 +122,7 @@ extension ContextMenuVC {
for cellViewModel: MessageViewModel, for cellViewModel: MessageViewModel,
recentEmojis: [EmojiWithSkinTones], recentEmojis: [EmojiWithSkinTones],
currentUserPublicKey: String, currentUserPublicKey: String,
currentUserBlindedPublicKey: String?,
currentUserIsOpenGroupModerator: Bool, currentUserIsOpenGroupModerator: Bool,
currentThreadIsMessageRequest: Bool, currentThreadIsMessageRequest: Bool,
delegate: ContextMenuActionDelegate? delegate: ContextMenuActionDelegate?
@ -184,6 +185,7 @@ extension ContextMenuVC {
cellViewModel.threadVariant != .openGroup || cellViewModel.threadVariant != .openGroup ||
currentUserIsOpenGroupModerator || currentUserIsOpenGroupModerator ||
cellViewModel.authorId == currentUserPublicKey || cellViewModel.authorId == currentUserPublicKey ||
cellViewModel.authorId == currentUserBlindedPublicKey ||
cellViewModel.state == .failed cellViewModel.state == .failed
) )
let canBan: Bool = ( let canBan: Bool = (

@ -201,6 +201,30 @@ extension ConversationVC:
// MARK: - ExpandingAttachmentsButtonDelegate // MARK: - ExpandingAttachmentsButtonDelegate
func handleGIFButtonTapped() { func handleGIFButtonTapped() {
guard Storage.shared[.isGiphyEnabled] else {
let modal: ConfirmationModal = ConfirmationModal(
info: ConfirmationModal.Info(
title: "GIPHY_PERMISSION_TITLE".localized(),
explanation: "GIPHY_PERMISSION_MESSAGE".localized(),
confirmTitle: "continue_2".localized()
) { [weak self] _ in
Storage.shared.writeAsync(
updates: { db in
db[.isGiphyEnabled] = true
},
completion: { _, _ in
DispatchQueue.main.async {
self?.handleGIFButtonTapped()
}
}
)
}
)
present(modal, animated: true, completion: nil)
return
}
let gifVC = GifPickerViewController() let gifVC = GifPickerViewController()
gifVC.delegate = self gifVC.delegate = self
@ -779,6 +803,7 @@ extension ConversationVC:
for: cellViewModel, for: cellViewModel,
recentEmojis: (self.viewModel.threadData.recentReactionEmoji ?? []).compactMap { EmojiWithSkinTones(rawValue: $0) }, recentEmojis: (self.viewModel.threadData.recentReactionEmoji ?? []).compactMap { EmojiWithSkinTones(rawValue: $0) },
currentUserPublicKey: self.viewModel.threadData.currentUserPublicKey, currentUserPublicKey: self.viewModel.threadData.currentUserPublicKey,
currentUserBlindedPublicKey: self.viewModel.threadData.currentUserBlindedPublicKey,
currentUserIsOpenGroupModerator: OpenGroupManager.isUserModeratorOrAdmin( currentUserIsOpenGroupModerator: OpenGroupManager.isUserModeratorOrAdmin(
self.viewModel.threadData.currentUserPublicKey, self.viewModel.threadData.currentUserPublicKey,
for: self.viewModel.threadData.openGroupRoomToken, for: self.viewModel.threadData.openGroupRoomToken,

@ -115,6 +115,7 @@ public class ConversationViewModel: OWSAudioPlayerDelegate {
} }
) )
) )
.populatingCurrentUserBlindedKey()
/// This is all the data the screen needs to populate itself, please see the following link for tips to help optimise /// This is all the data the screen needs to populate itself, please see the following link for tips to help optimise
/// performance https://github.com/groue/GRDB.swift#valueobservation-performance /// performance https://github.com/groue/GRDB.swift#valueobservation-performance
@ -130,7 +131,7 @@ public class ConversationViewModel: OWSAudioPlayerDelegate {
private func setupObservableThreadData(for threadId: String) -> ValueObservation<ValueReducers.RemoveDuplicates<ValueReducers.Fetch<SessionThreadViewModel?>>> { private func setupObservableThreadData(for threadId: String) -> ValueObservation<ValueReducers.RemoveDuplicates<ValueReducers.Fetch<SessionThreadViewModel?>>> {
return ValueObservation return ValueObservation
.trackingConstantRegion { db -> SessionThreadViewModel? in .trackingConstantRegion { [weak self] db -> SessionThreadViewModel? in
let userPublicKey: String = getUserHexEncodedPublicKey(db) let userPublicKey: String = getUserHexEncodedPublicKey(db)
let recentReactionEmoji: [String] = try Emoji.getRecent(db, withDefaultEmoji: true) let recentReactionEmoji: [String] = try Emoji.getRecent(db, withDefaultEmoji: true)
let threadViewModel: SessionThreadViewModel? = try SessionThreadViewModel let threadViewModel: SessionThreadViewModel? = try SessionThreadViewModel
@ -139,6 +140,11 @@ public class ConversationViewModel: OWSAudioPlayerDelegate {
return threadViewModel return threadViewModel
.map { $0.with(recentReactionEmoji: recentReactionEmoji) } .map { $0.with(recentReactionEmoji: recentReactionEmoji) }
.map { viewModel -> SessionThreadViewModel in
viewModel.populatingCurrentUserBlindedKey(
currentUserBlindedPublicKeyForThisThread: self?.threadData.currentUserBlindedPublicKey
)
}
} }
.removeDuplicates() .removeDuplicates()
} }

@ -607,3 +607,5 @@
"delete_message_for_me_and_my_devices" = "Delete from all of my devices"; "delete_message_for_me_and_my_devices" = "Delete from all of my devices";
"context_menu_resend" = "Resend"; "context_menu_resend" = "Resend";
"context_menu_resync" = "Resync"; "context_menu_resync" = "Resync";
"GIPHY_PERMISSION_TITLE" = "Search GIFs?";
"GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs.";

@ -607,3 +607,5 @@
"delete_message_for_me_and_my_devices" = "Delete from all of my devices"; "delete_message_for_me_and_my_devices" = "Delete from all of my devices";
"context_menu_resend" = "Resend"; "context_menu_resend" = "Resend";
"context_menu_resync" = "Resync"; "context_menu_resync" = "Resync";
"GIPHY_PERMISSION_TITLE" = "Search GIFs?";
"GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs.";

@ -607,3 +607,5 @@
"delete_message_for_me_and_my_devices" = "Delete from all of my devices"; "delete_message_for_me_and_my_devices" = "Delete from all of my devices";
"context_menu_resend" = "Resend"; "context_menu_resend" = "Resend";
"context_menu_resync" = "Resync"; "context_menu_resync" = "Resync";
"GIPHY_PERMISSION_TITLE" = "Search GIFs?";
"GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs.";

@ -607,3 +607,5 @@
"delete_message_for_me_and_my_devices" = "Delete from all of my devices"; "delete_message_for_me_and_my_devices" = "Delete from all of my devices";
"context_menu_resend" = "Resend"; "context_menu_resend" = "Resend";
"context_menu_resync" = "Resync"; "context_menu_resync" = "Resync";
"GIPHY_PERMISSION_TITLE" = "Search GIFs?";
"GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs.";

@ -607,3 +607,5 @@
"delete_message_for_me_and_my_devices" = "Delete from all of my devices"; "delete_message_for_me_and_my_devices" = "Delete from all of my devices";
"context_menu_resend" = "Resend"; "context_menu_resend" = "Resend";
"context_menu_resync" = "Resync"; "context_menu_resync" = "Resync";
"GIPHY_PERMISSION_TITLE" = "Search GIFs?";
"GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs.";

@ -607,3 +607,5 @@
"delete_message_for_me_and_my_devices" = "Delete from all of my devices"; "delete_message_for_me_and_my_devices" = "Delete from all of my devices";
"context_menu_resend" = "Resend"; "context_menu_resend" = "Resend";
"context_menu_resync" = "Resync"; "context_menu_resync" = "Resync";
"GIPHY_PERMISSION_TITLE" = "Search GIFs?";
"GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs.";

@ -607,3 +607,5 @@
"delete_message_for_me_and_my_devices" = "Delete from all of my devices"; "delete_message_for_me_and_my_devices" = "Delete from all of my devices";
"context_menu_resend" = "Resend"; "context_menu_resend" = "Resend";
"context_menu_resync" = "Resync"; "context_menu_resync" = "Resync";
"GIPHY_PERMISSION_TITLE" = "Search GIFs?";
"GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs.";

@ -607,3 +607,5 @@
"delete_message_for_me_and_my_devices" = "Delete from all of my devices"; "delete_message_for_me_and_my_devices" = "Delete from all of my devices";
"context_menu_resend" = "Resend"; "context_menu_resend" = "Resend";
"context_menu_resync" = "Resync"; "context_menu_resync" = "Resync";
"GIPHY_PERMISSION_TITLE" = "Search GIFs?";
"GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs.";

@ -607,3 +607,5 @@
"delete_message_for_me_and_my_devices" = "Delete from all of my devices"; "delete_message_for_me_and_my_devices" = "Delete from all of my devices";
"context_menu_resend" = "Resend"; "context_menu_resend" = "Resend";
"context_menu_resync" = "Resync"; "context_menu_resync" = "Resync";
"GIPHY_PERMISSION_TITLE" = "Search GIFs?";
"GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs.";

@ -607,3 +607,5 @@
"delete_message_for_me_and_my_devices" = "Delete from all of my devices"; "delete_message_for_me_and_my_devices" = "Delete from all of my devices";
"context_menu_resend" = "Resend"; "context_menu_resend" = "Resend";
"context_menu_resync" = "Resync"; "context_menu_resync" = "Resync";
"GIPHY_PERMISSION_TITLE" = "Search GIFs?";
"GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs.";

@ -607,3 +607,5 @@
"delete_message_for_me_and_my_devices" = "Delete from all of my devices"; "delete_message_for_me_and_my_devices" = "Delete from all of my devices";
"context_menu_resend" = "Resend"; "context_menu_resend" = "Resend";
"context_menu_resync" = "Resync"; "context_menu_resync" = "Resync";
"GIPHY_PERMISSION_TITLE" = "Search GIFs?";
"GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs.";

@ -607,3 +607,5 @@
"delete_message_for_me_and_my_devices" = "Delete from all of my devices"; "delete_message_for_me_and_my_devices" = "Delete from all of my devices";
"context_menu_resend" = "Resend"; "context_menu_resend" = "Resend";
"context_menu_resync" = "Resync"; "context_menu_resync" = "Resync";
"GIPHY_PERMISSION_TITLE" = "Search GIFs?";
"GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs.";

@ -607,3 +607,5 @@
"delete_message_for_me_and_my_devices" = "Delete from all of my devices"; "delete_message_for_me_and_my_devices" = "Delete from all of my devices";
"context_menu_resend" = "Resend"; "context_menu_resend" = "Resend";
"context_menu_resync" = "Resync"; "context_menu_resync" = "Resync";
"GIPHY_PERMISSION_TITLE" = "Search GIFs?";
"GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs.";

@ -607,3 +607,5 @@
"delete_message_for_me_and_my_devices" = "Delete from all of my devices"; "delete_message_for_me_and_my_devices" = "Delete from all of my devices";
"context_menu_resend" = "Resend"; "context_menu_resend" = "Resend";
"context_menu_resync" = "Resync"; "context_menu_resync" = "Resync";
"GIPHY_PERMISSION_TITLE" = "Search GIFs?";
"GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs.";

@ -607,3 +607,5 @@
"delete_message_for_me_and_my_devices" = "Delete from all of my devices"; "delete_message_for_me_and_my_devices" = "Delete from all of my devices";
"context_menu_resend" = "Resend"; "context_menu_resend" = "Resend";
"context_menu_resync" = "Resync"; "context_menu_resync" = "Resync";
"GIPHY_PERMISSION_TITLE" = "Search GIFs?";
"GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs.";

@ -607,3 +607,5 @@
"delete_message_for_me_and_my_devices" = "Delete from all of my devices"; "delete_message_for_me_and_my_devices" = "Delete from all of my devices";
"context_menu_resend" = "Resend"; "context_menu_resend" = "Resend";
"context_menu_resync" = "Resync"; "context_menu_resync" = "Resync";
"GIPHY_PERMISSION_TITLE" = "Search GIFs?";
"GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs.";

@ -607,3 +607,5 @@
"delete_message_for_me_and_my_devices" = "Delete from all of my devices"; "delete_message_for_me_and_my_devices" = "Delete from all of my devices";
"context_menu_resend" = "Resend"; "context_menu_resend" = "Resend";
"context_menu_resync" = "Resync"; "context_menu_resync" = "Resync";
"GIPHY_PERMISSION_TITLE" = "Search GIFs?";
"GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs.";

@ -607,3 +607,5 @@
"delete_message_for_me_and_my_devices" = "Delete from all of my devices"; "delete_message_for_me_and_my_devices" = "Delete from all of my devices";
"context_menu_resend" = "Resend"; "context_menu_resend" = "Resend";
"context_menu_resync" = "Resync"; "context_menu_resync" = "Resync";
"GIPHY_PERMISSION_TITLE" = "Search GIFs?";
"GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs.";

@ -607,3 +607,5 @@
"delete_message_for_me_and_my_devices" = "Delete from all of my devices"; "delete_message_for_me_and_my_devices" = "Delete from all of my devices";
"context_menu_resend" = "Resend"; "context_menu_resend" = "Resend";
"context_menu_resync" = "Resync"; "context_menu_resync" = "Resync";
"GIPHY_PERMISSION_TITLE" = "Search GIFs?";
"GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs.";

@ -607,3 +607,5 @@
"delete_message_for_me_and_my_devices" = "Delete from all of my devices"; "delete_message_for_me_and_my_devices" = "Delete from all of my devices";
"context_menu_resend" = "Resend"; "context_menu_resend" = "Resend";
"context_menu_resync" = "Resync"; "context_menu_resync" = "Resync";
"GIPHY_PERMISSION_TITLE" = "Search GIFs?";
"GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs.";

@ -607,3 +607,5 @@
"delete_message_for_me_and_my_devices" = "Delete from all of my devices"; "delete_message_for_me_and_my_devices" = "Delete from all of my devices";
"context_menu_resend" = "Resend"; "context_menu_resend" = "Resend";
"context_menu_resync" = "Resync"; "context_menu_resync" = "Resync";
"GIPHY_PERMISSION_TITLE" = "Search GIFs?";
"GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs.";

@ -607,3 +607,5 @@
"delete_message_for_me_and_my_devices" = "Delete from all of my devices"; "delete_message_for_me_and_my_devices" = "Delete from all of my devices";
"context_menu_resend" = "Resend"; "context_menu_resend" = "Resend";
"context_menu_resync" = "Resync"; "context_menu_resync" = "Resync";
"GIPHY_PERMISSION_TITLE" = "Search GIFs?";
"GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs.";

@ -11,6 +11,7 @@ public enum SyncPushTokensJob: JobExecutor {
public static let maxFailureCount: Int = -1 public static let maxFailureCount: Int = -1
public static let requiresThreadId: Bool = false public static let requiresThreadId: Bool = false
public static let requiresInteractionId: Bool = false public static let requiresInteractionId: Bool = false
private static let maxFrequency: TimeInterval = (12 * 60 * 60)
public static func run( public static func run(
_ job: Job, _ job: Job,
@ -34,45 +35,44 @@ public enum SyncPushTokensJob: JobExecutor {
return return
} }
// Push tokens don't normally change while the app is launched, so checking once during launch is // Push tokens don't normally change while the app is launched, so you would assume checking once
// usually sufficient, but e.g. on iOS11, users who have disabled "Allow Notifications" and disabled // during launch is sufficient, but e.g. on iOS11, users who have disabled "Allow Notifications"
// "Background App Refresh" will not be able to obtain an APN token. Enabling those settings does not // and disabled "Background App Refresh" will not be able to obtain an APN token. Enabling those
// restart the app, so we check every activation for users who haven't yet registered. // settings does not restart the app, so we check every activation for users who haven't yet
guard job.behaviour != .recurringOnActive || !UIApplication.shared.isRegisteredForRemoteNotifications else { // registered.
//
// It's also possible for a device to successfully register for push notifications but fail to
// register with Session
//
// Due to the above we want to re-register at least once every ~12 hours to ensure users will
// continue to receive push notifications
//
// In addition to this if we are custom running the job (eg. by toggling the push notification
// setting) then we should run regardless of the other settings so users have a mechanism to force
// the registration to run
let lastPushNotificationSync: Date = UserDefaults.standard[.lastPushNotificationSync]
.defaulting(to: Date.distantPast)
guard
job.behaviour == .runOnce ||
!UIApplication.shared.isRegisteredForRemoteNotifications ||
Date().timeIntervalSince(lastPushNotificationSync) >= SyncPushTokensJob.maxFrequency
else {
deferred(job) // Don't need to do anything if push notifications are already registered deferred(job) // Don't need to do anything if push notifications are already registered
return return
} }
Logger.info("Retrying remote notification registration since user hasn't registered yet.") Logger.info("Re-registering for remote notifications.")
// Determine if we want to upload only if stale (Note: This should default to true, and be true if
// 'details' isn't provided)
let uploadOnlyIfStale: Bool = ((try? JSONDecoder().decode(Details.self, from: job.details ?? Data()))?.uploadOnlyIfStale ?? true)
// Get the app version info (used to determine if we want to update the push tokens)
let lastAppVersion: String? = AppVersion.sharedInstance().lastAppVersion
let currentAppVersion: String? = AppVersion.sharedInstance().currentAppVersion
// Perform device registration
PushRegistrationManager.shared.requestPushTokens() PushRegistrationManager.shared.requestPushTokens()
.then(on: queue) { (pushToken: String, voipToken: String) -> Promise<Void> in .then(on: queue) { (pushToken: String, voipToken: String) -> Promise<Void> in
let lastPushToken: String? = Storage.shared[.lastRecordedPushToken]
let lastVoipToken: String? = Storage.shared[.lastRecordedVoipToken]
let shouldUploadTokens: Bool = (
!uploadOnlyIfStale || (
lastPushToken != pushToken ||
lastVoipToken != voipToken
) ||
lastAppVersion != currentAppVersion
)
guard shouldUploadTokens else { return Promise.value(()) }
let (promise, seal) = Promise<Void>.pending() let (promise, seal) = Promise<Void>.pending()
SyncPushTokensJob.registerForPushNotifications( SyncPushTokensJob.registerForPushNotifications(
pushToken: pushToken, pushToken: pushToken,
voipToken: voipToken, voipToken: voipToken,
isForcedUpdate: shouldUploadTokens, isForcedUpdate: true,
success: { seal.fulfill(()) }, success: { seal.fulfill(()) },
failure: seal.reject failure: seal.reject
) )
@ -94,6 +94,7 @@ public enum SyncPushTokensJob: JobExecutor {
public static func run(uploadOnlyIfStale: Bool) { public static func run(uploadOnlyIfStale: Bool) {
guard let job: Job = Job( guard let job: Job = Job(
variant: .syncPushTokens, variant: .syncPushTokens,
behaviour: .runOnce,
details: SyncPushTokensJob.Details( details: SyncPushTokensJob.Details(
uploadOnlyIfStale: uploadOnlyIfStale uploadOnlyIfStale: uploadOnlyIfStale
) )

@ -29,49 +29,79 @@ public extension MentionInfo {
let profile: TypedTableAlias<Profile> = TypedTableAlias() let profile: TypedTableAlias<Profile> = TypedTableAlias()
let interaction: TypedTableAlias<Interaction> = TypedTableAlias() let interaction: TypedTableAlias<Interaction> = TypedTableAlias()
let openGroup: TypedTableAlias<OpenGroup> = TypedTableAlias() let openGroup: TypedTableAlias<OpenGroup> = TypedTableAlias()
let groupMember: TypedTableAlias<GroupMember> = TypedTableAlias()
let prefixLiteral: SQL = SQL(stringLiteral: "\(targetPrefix.rawValue)%") let prefixLiteral: SQL = SQL(stringLiteral: "\(targetPrefix.rawValue)%")
let profileFullTextSearch: SQL = SQL(stringLiteral: Profile.fullTextSearchTableName) let profileFullTextSearch: SQL = SQL(stringLiteral: Profile.fullTextSearchTableName)
/// **Note:** The `\(MentionInfo.profileKey).*` value **MUST** be first /// The query needs to differ depending on the thread variant because the behaviour should be different:
let limitSQL: SQL? = (threadVariant == .openGroup ? SQL("LIMIT 20") : nil) ///
/// **Contact:** We should show the profile directly (filtered out if the pattern doesn't match)
/// **Closed Group:** We should show all profiles within the group, filtered by the pattern
/// **Open Group:** We should show only the 20 most recent profiles which match the pattern
let request: SQLRequest<MentionInfo> = { let request: SQLRequest<MentionInfo> = {
guard let pattern: FTS5Pattern = pattern else { let hasValidPattern: Bool = (pattern != nil && pattern?.rawPattern != "\"\"*")
let finalLimitSQL: SQL = (limitSQL ?? "") let targetJoin: SQL = {
guard hasValidPattern else { return "FROM \(Profile.self)" }
return """ return """
SELECT FROM \(profileFullTextSearch)
\(Profile.self).*, JOIN \(Profile.self) ON (
MAX(\(interaction[.timestampMs])), -- Want the newest interaction (for sorting) \(Profile.self).rowid = \(profileFullTextSearch).rowid AND
\(SQL("\(threadVariant) AS \(MentionInfo.threadVariantKey)")), \(SQL("\(profile[.id]) != \(userPublicKey)")) AND (
\(openGroup[.server]) AS \(MentionInfo.openGroupServerKey), \(SQL("\(threadVariant) != \(SessionThread.Variant.openGroup)")) OR
\(openGroup[.roomToken]) AS \(MentionInfo.openGroupRoomTokenKey) \(SQL("\(profile[.id]) LIKE '\(prefixLiteral)'"))
FROM \(Profile.self)
JOIN \(Interaction.self) ON (
\(SQL("\(interaction[.threadId]) = \(threadId)")) AND
\(interaction[.authorId]) = \(profile[.id])
) )
LEFT JOIN \(OpenGroup.self) ON \(SQL("\(openGroup[.threadId]) = \(threadId)")) )
"""
}()
let targetWhere: SQL = {
guard let pattern: FTS5Pattern = pattern, pattern.rawPattern != "\"\"*" else {
return """
WHERE ( WHERE (
\(SQL("\(profile[.id]) != \(userPublicKey)")) AND ( \(SQL("\(profile[.id]) != \(userPublicKey)")) AND (
\(SQL("\(threadVariant) != \(SessionThread.Variant.openGroup)")) OR \(SQL("\(threadVariant) != \(SessionThread.Variant.openGroup)")) OR
\(SQL("\(profile[.id]) LIKE '\(prefixLiteral)'")) \(SQL("\(profile[.id]) LIKE '\(prefixLiteral)'"))
) )
) )
GROUP BY \(profile[.id])
ORDER BY \(interaction[.timestampMs].desc)
\(finalLimitSQL)
""" """
} }
// If we do have a search patern then use FTS
let matchLiteral: SQL = SQL(stringLiteral: "\(Profile.Columns.nickname.name):\(pattern.rawPattern) OR \(Profile.Columns.name.name):\(pattern.rawPattern)") let matchLiteral: SQL = SQL(stringLiteral: "\(Profile.Columns.nickname.name):\(pattern.rawPattern) OR \(Profile.Columns.name.name):\(pattern.rawPattern)")
let finalLimitSQL: SQL = (limitSQL ?? "")
return """ return "WHERE \(profileFullTextSearch) MATCH '\(matchLiteral)'"
}()
switch threadVariant {
case .contact:
return SQLRequest("""
SELECT
\(Profile.self).*,
\(SQL("\(threadVariant) AS \(MentionInfo.threadVariantKey)"))
\(targetJoin)
\(targetWhere) AND \(SQL("\(profile[.id]) = \(threadId)"))
""")
case .closedGroup:
return SQLRequest("""
SELECT
\(Profile.self).*,
\(SQL("\(threadVariant) AS \(MentionInfo.threadVariantKey)"))
\(targetJoin)
JOIN \(GroupMember.self) ON (
\(SQL("\(groupMember[.groupId]) = \(threadId)")) AND
\(groupMember[.profileId]) = \(profile[.id]) AND
\(SQL("\(groupMember[.role]) = \(GroupMember.Role.standard)"))
)
\(targetWhere)
GROUP BY \(profile[.id])
ORDER BY IFNULL(\(profile[.nickname]), \(profile[.name])) ASC
""")
case .openGroup:
return SQLRequest("""
SELECT SELECT
\(Profile.self).*, \(Profile.self).*,
MAX(\(interaction[.timestampMs])), -- Want the newest interaction (for sorting) MAX(\(interaction[.timestampMs])), -- Want the newest interaction (for sorting)
@ -79,25 +109,18 @@ public extension MentionInfo {
\(openGroup[.server]) AS \(MentionInfo.openGroupServerKey), \(openGroup[.server]) AS \(MentionInfo.openGroupServerKey),
\(openGroup[.roomToken]) AS \(MentionInfo.openGroupRoomTokenKey) \(openGroup[.roomToken]) AS \(MentionInfo.openGroupRoomTokenKey)
FROM \(profileFullTextSearch) \(targetJoin)
JOIN \(Profile.self) ON (
\(Profile.self).rowid = \(profileFullTextSearch).rowid AND
\(SQL("\(profile[.id]) != \(userPublicKey)")) AND (
\(SQL("\(threadVariant) != \(SessionThread.Variant.openGroup)")) OR
\(SQL("\(profile[.id]) LIKE '\(prefixLiteral)'"))
)
)
JOIN \(Interaction.self) ON ( JOIN \(Interaction.self) ON (
\(SQL("\(interaction[.threadId]) = \(threadId)")) AND \(SQL("\(interaction[.threadId]) = \(threadId)")) AND
\(interaction[.authorId]) = \(profile[.id]) \(interaction[.authorId]) = \(profile[.id])
) )
LEFT JOIN \(OpenGroup.self) ON \(SQL("\(openGroup[.threadId]) = \(threadId)")) JOIN \(OpenGroup.self) ON \(SQL("\(openGroup[.threadId]) = \(threadId)"))
\(targetWhere)
WHERE \(profileFullTextSearch) MATCH '\(matchLiteral)'
GROUP BY \(profile[.id]) GROUP BY \(profile[.id])
ORDER BY \(interaction[.timestampMs].desc) ORDER BY \(interaction[.timestampMs].desc)
\(finalLimitSQL) LIMIT 20
""" """)
}
}() }()
return request.adapted { db in return request.adapted { db in

@ -33,6 +33,11 @@ public extension Setting.BoolKey {
/// **Note:** Link Previews are only enabled for HTTPS urls /// **Note:** Link Previews are only enabled for HTTPS urls
static let areLinkPreviewsEnabled: Setting.BoolKey = "areLinkPreviewsEnabled" static let areLinkPreviewsEnabled: Setting.BoolKey = "areLinkPreviewsEnabled"
/// Controls whether Giphy search is enabled
///
/// **Note:** Link Previews are only enabled for HTTPS urls
static let isGiphyEnabled: Setting.BoolKey = "isGiphyEnabled"
/// Controls whether Calls are enabled /// Controls whether Calls are enabled
static let areCallsEnabled: Setting.BoolKey = "areCallsEnabled" static let areCallsEnabled: Setting.BoolKey = "areCallsEnabled"

@ -44,6 +44,7 @@ public enum SNUserDefaults {
case lastOpenGroupImageUpdate case lastOpenGroupImageUpdate
case lastOpen case lastOpen
case lastGarbageCollection case lastGarbageCollection
case lastPushNotificationSync
} }
public enum Double: Swift.String { public enum Double: Swift.String {

Loading…
Cancel
Save