Merge branch 'dev' into multi-device

This commit is contained in:
Niels Andriesse 2021-01-22 13:29:13 +11:00 committed by GitHub
commit 867e9114a6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 288 additions and 241 deletions

View File

@ -5288,7 +5288,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 158;
CURRENT_PROJECT_VERSION = 163;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = SUQ8J2PCT7;
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
@ -5309,7 +5309,7 @@
INFOPLIST_FILE = SessionShareExtension/Meta/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
MARKETING_VERSION = 1.7.3;
MARKETING_VERSION = 1.7.5;
MTL_ENABLE_DEBUG_INFO = YES;
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.ShareExtension";
PRODUCT_NAME = "$(TARGET_NAME)";
@ -5357,7 +5357,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 158;
CURRENT_PROJECT_VERSION = 163;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = SUQ8J2PCT7;
ENABLE_NS_ASSERTIONS = NO;
@ -5383,7 +5383,7 @@
INFOPLIST_FILE = SessionShareExtension/Meta/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
MARKETING_VERSION = 1.7.3;
MARKETING_VERSION = 1.7.5;
MTL_ENABLE_DEBUG_INFO = NO;
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.ShareExtension";
PRODUCT_NAME = "$(TARGET_NAME)";
@ -5418,7 +5418,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 158;
CURRENT_PROJECT_VERSION = 163;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = SUQ8J2PCT7;
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
@ -5437,7 +5437,7 @@
INFOPLIST_FILE = SessionNotificationServiceExtension/Meta/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
MARKETING_VERSION = 1.7.3;
MARKETING_VERSION = 1.7.5;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.NotificationServiceExtension";
@ -5488,7 +5488,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 158;
CURRENT_PROJECT_VERSION = 163;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = SUQ8J2PCT7;
ENABLE_NS_ASSERTIONS = NO;
@ -5512,7 +5512,7 @@
INFOPLIST_FILE = SessionNotificationServiceExtension/Meta/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
MARKETING_VERSION = 1.7.3;
MARKETING_VERSION = 1.7.5;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.NotificationServiceExtension";
@ -6507,7 +6507,7 @@
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CURRENT_PROJECT_VERSION = 158;
CURRENT_PROJECT_VERSION = 163;
DEVELOPMENT_TEAM = SUQ8J2PCT7;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
@ -6543,7 +6543,7 @@
"$(SRCROOT)",
);
LLVM_LTO = NO;
MARKETING_VERSION = 1.7.3;
MARKETING_VERSION = 1.7.5;
OTHER_LDFLAGS = "$(inherited)";
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger";
@ -6575,7 +6575,7 @@
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CURRENT_PROJECT_VERSION = 158;
CURRENT_PROJECT_VERSION = 163;
DEVELOPMENT_TEAM = SUQ8J2PCT7;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
@ -6611,7 +6611,7 @@
"$(SRCROOT)",
);
LLVM_LTO = NO;
MARKETING_VERSION = 1.7.3;
MARKETING_VERSION = 1.7.5;
OTHER_LDFLAGS = "$(inherited)";
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger";
PRODUCT_NAME = Session;

View File

@ -257,6 +257,9 @@ final class EditClosedGroupVC : BaseVC, UITableViewDataSource, UITableViewDelega
return showError(title: "Couldn't Update Group", message: "Can't leave while adding or removing other members.")
}
}
guard members.count <= 100 else {
return showError(title: NSLocalizedString("vc_create_closed_group_too_many_group_members_error", comment: ""))
}
Storage.write(with: { [weak self] transaction in
do {
if !members.contains(getUserHexEncodedPublicKey()) {

View File

@ -163,11 +163,12 @@ final class NewClosedGroupVC : BaseVC, UITableViewDataSource, UITableViewDelegat
guard selectedContacts.count >= 1 else {
return showError(title: "Please pick at least 1 group member")
}
guard selectedContacts.count < 20 else { // Minus one because we're going to include self later
guard selectedContacts.count < 100 else { // Minus one because we're going to include self later
return showError(title: NSLocalizedString("vc_create_closed_group_too_many_group_members_error", comment: ""))
}
let selectedContacts = self.selectedContacts
ModalActivityIndicatorViewController.present(fromViewController: navigationController!, canCancel: false) { [weak self] _ in
let message: String? = (selectedContacts.count > 20) ? "Please wait while the group is created..." : nil
ModalActivityIndicatorViewController.present(fromViewController: navigationController!, message: message) { [weak self] _ in
var promise: Promise<TSGroupThread>!
Storage.writeSync { transaction in
promise = MessageSender.createClosedGroup(name: name, members: selectedContacts, transaction: transaction)

View File

@ -1528,15 +1528,17 @@ typedef enum : NSUInteger {
[self showDetailViewForViewItem:conversationViewItem];
}
- (void)report:(id<ConversationViewItem>)conversationViewItem
- (void)banUser:(id<ConversationViewItem>)conversationViewItem
{
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Report?" message:@"If the message is found to violate the Session Public Chat code of conduct it will be removed." preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
uint64_t messageID = 0;
if ([conversationViewItem.interaction isKindOfClass:TSMessage.class]) {
messageID = ((TSMessage *)conversationViewItem.interaction).openGroupServerMessageID;
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Ban This User?" message:nil preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
NSString* publicKey;
if ([conversationViewItem.interaction isKindOfClass:TSIncomingMessage.class]) {
publicKey = ((TSIncomingMessage *)conversationViewItem.interaction).authorId;
}
[SNOpenGroupAPI reportMessageWithID:messageID inChannel:1 onServer:@"https://chat.getsession.org"];
SNOpenGroup *openGroup = [LKStorage.shared getOpenGroupForThreadID:self.thread.uniqueId];
if (openGroup == nil) return;
[[SNOpenGroupAPI banPublicKey:publicKey fromServer:openGroup.server] retainUntilComplete];
}]];
[alert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleDefault handler:nil]];
[self presentViewController:alert animated:YES completion:nil];
@ -2537,6 +2539,7 @@ typedef enum : NSUInteger {
[ModalActivityIndicatorViewController
presentFromViewController:self
canCancel:YES
message:nil
backgroundBlock:^(ModalActivityIndicatorViewController *modalActivityIndicator) {
DataSource *dataSource =
[DataSourcePath dataSourceWithURL:movieURL shouldDeleteOnDeallocation:NO];

View File

@ -67,6 +67,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType);
@property (nonatomic, readonly) BOOL isGroupThread;
@property (nonatomic, readonly) BOOL userCanDeleteGroupMessage;
@property (nonatomic, readonly) BOOL userHasModerationPermission;
@property (nonatomic, readonly) BOOL hasBodyText;

View File

@ -1162,30 +1162,45 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
- (BOOL)userCanDeleteGroupMessage
{
if (!self.isGroupThread) return false;
// Ensure the thread is a public chat and not an RSS feed
TSGroupThread *groupThread = (TSGroupThread *)self.interaction.thread;
// Only allow deletion on incoming and outgoing messages
OWSInteractionType interationType = self.interaction.interactionType;
if (interationType != OWSInteractionType_OutgoingMessage && interationType != OWSInteractionType_IncomingMessage) return false;
// Make sure it's a public chat message
// Make sure it's an open group message
TSMessage *message = (TSMessage *)self.interaction;
if (!message.isOpenGroupMessage) return true;
// Ensure we have the details needed to contact the server
SNOpenGroup *publicChat = [LKStorage.shared getOpenGroupForThreadID:groupThread.uniqueId];
if (publicChat == nil) return true;
SNOpenGroup *openGroup = [LKStorage.shared getOpenGroupForThreadID:groupThread.uniqueId];
if (openGroup == nil) return true;
if (interationType == OWSInteractionType_IncomingMessage) {
// Only allow deletion on incoming messages if the user has moderation permission
return [SNOpenGroupAPI isUserModerator:[SNGeneralUtilities getUserPublicKey] forChannel:publicChat.channel onServer:publicChat.server];
return [SNOpenGroupAPI isUserModerator:[SNGeneralUtilities getUserPublicKey] forChannel:openGroup.channel onServer:openGroup.server];
} else {
return YES;
}
}
- (BOOL)userHasModerationPermission
{
if (!self.isGroupThread) return false;
TSGroupThread *groupThread = (TSGroupThread *)self.interaction.thread;
// Make sure it's an open group message
TSMessage *message = (TSMessage *)self.interaction;
if (!message.isOpenGroupMessage) return false;
// Ensure we have the details needed to contact the server
SNOpenGroup *openGroup = [LKStorage.shared getOpenGroupForThreadID:groupThread.uniqueId];
if (openGroup == nil) return false;
// Check that we're a moderator
return [SNOpenGroupAPI isUserModerator:[SNGeneralUtilities getUserPublicKey] forChannel:openGroup.channel onServer:openGroup.server];
}
@end
NS_ASSUME_NONNULL_END

View File

@ -311,6 +311,7 @@ class MenuActionSheetView: UIView, MenuActionViewDelegate {
case .failed:
Logger.debug("failed")
unhighlightAllActionViews()
default: break
}
}

View File

@ -6,7 +6,7 @@ import Foundation
@objc
protocol MessageActionsDelegate: class {
func report(_ conversationViewItem: ConversationViewItem)
func banUser(_ conversationViewItem: ConversationViewItem)
func messageActionsShowDetailsForItem(_ conversationViewItem: ConversationViewItem)
func messageActionsReplyToItem(_ conversationViewItem: ConversationViewItem)
func copyPublicKey(for conversationViewItem: ConversationViewItem)
@ -45,14 +45,6 @@ struct MessageActionBuilder {
block: { [weak delegate] _ in delegate?.messageActionsShowDetailsForItem(conversationViewItem) }
)
}
static func report(_ conversationViewItem: ConversationViewItem, delegate: MessageActionsDelegate) -> MenuAction {
return MenuAction(image: #imageLiteral(resourceName: "Flag"),
title: NSLocalizedString("Report", comment: ""),
subtitle: nil,
block: { [weak delegate] _ in delegate?.report(conversationViewItem) }
)
}
static func deleteMessage(conversationViewItem: ConversationViewItem, delegate: MessageActionsDelegate) -> MenuAction {
return MenuAction(image: #imageLiteral(resourceName: "ic_trash"),
@ -61,6 +53,14 @@ struct MessageActionBuilder {
block: { _ in conversationViewItem.deleteAction() }
)
}
static func banUser(conversationViewItem: ConversationViewItem, delegate: MessageActionsDelegate) -> MenuAction {
return MenuAction(image: #imageLiteral(resourceName: "ic_block"),
title: "Ban User",
subtitle: nil,
block: { [weak delegate] _ in delegate?.banUser(conversationViewItem) }
)
}
static func copyMedia(conversationViewItem: ConversationViewItem, delegate: MessageActionsDelegate) -> MenuAction {
return MenuAction(image: #imageLiteral(resourceName: "ic_copy"),
@ -108,10 +108,9 @@ class ConversationViewItemActions: NSObject {
actions.append(deleteAction)
}
if isGroup && conversationViewItem.interaction.thread.name() == "Loki Public Chat"
|| conversationViewItem.interaction.thread.name() == "Session Public Chat" {
let reportAction = MessageActionBuilder.report(conversationViewItem, delegate: delegate)
actions.append(reportAction)
if isGroup && conversationViewItem.interaction is TSIncomingMessage && conversationViewItem.userHasModerationPermission {
let banAction = MessageActionBuilder.banUser(conversationViewItem: conversationViewItem, delegate: delegate)
actions.append(banAction)
}
let showDetailsAction = MessageActionBuilder.showDetails(conversationViewItem: conversationViewItem, delegate: delegate)
@ -152,10 +151,9 @@ class ConversationViewItemActions: NSObject {
actions.append(deleteAction)
}
if isGroup && conversationViewItem.interaction.thread.name() == "Loki Public Chat"
|| conversationViewItem.interaction.thread.name() == "Session Public Chat" {
let reportAction = MessageActionBuilder.report(conversationViewItem, delegate: delegate)
actions.append(reportAction)
if isGroup && conversationViewItem.interaction is TSIncomingMessage && conversationViewItem.userHasModerationPermission {
let banAction = MessageActionBuilder.banUser(conversationViewItem: conversationViewItem, delegate: delegate)
actions.append(banAction)
}
let showDetailsAction = MessageActionBuilder.showDetails(conversationViewItem: conversationViewItem, delegate: delegate)
@ -185,10 +183,9 @@ class ConversationViewItemActions: NSObject {
actions.append(deleteAction)
}
if isGroup && conversationViewItem.interaction.thread.name() == "Loki Public Chat"
|| conversationViewItem.interaction.thread.name() == "Session Public Chat" {
let reportAction = MessageActionBuilder.report(conversationViewItem, delegate: delegate)
actions.append(reportAction)
if isGroup && conversationViewItem.interaction is TSIncomingMessage && conversationViewItem.userHasModerationPermission {
let banAction = MessageActionBuilder.banUser(conversationViewItem: conversationViewItem, delegate: delegate)
actions.append(banAction)
}
let showDetailsAction = MessageActionBuilder.showDetails(conversationViewItem: conversationViewItem, delegate: delegate)

View File

@ -2,7 +2,7 @@
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
public extension CGPoint {
extension CGPoint {
public func offsetBy(dx: CGFloat) -> CGPoint {
return CGPoint(x: x + dx, y: y)
}

View File

@ -1,30 +1,24 @@
final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, UIScrollViewDelegate, UIViewControllerPreviewingDelegate, NewConversationButtonSetDelegate, SeedReminderViewDelegate {
private var threadViewModelCache: [String:ThreadViewModel] = [:]
private var isObservingDatabase = true
private var isViewVisible = false { didSet { updateIsObservingDatabase() } }
// See https://github.com/yapstudios/YapDatabase/wiki/LongLivedReadTransactions and
// https://github.com/yapstudios/YapDatabase/wiki/YapDatabaseModifiedNotification for
// more information on database handling.
final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, UIViewControllerPreviewingDelegate, NewConversationButtonSetDelegate, SeedReminderViewDelegate {
private var threads: YapDatabaseViewMappings!
private var threadViewModelCache: [String:ThreadViewModel] = [:] // Thread ID to ThreadViewModel
private var tableViewTopConstraint: NSLayoutConstraint!
private var wasDatabaseModifiedExternally = false
private var threads: YapDatabaseViewMappings = {
let result = YapDatabaseViewMappings(groups: [ TSInboxGroup ], view: TSThreadDatabaseViewExtensionName)
result.setIsReversed(true, forGroup: TSInboxGroup)
return result
}()
private var threadCount: UInt {
threads.numberOfItems(inGroup: TSInboxGroup)
}
private let uiDatabaseConnection: YapDatabaseConnection = {
private lazy var dbConnection: YapDatabaseConnection = {
let result = OWSPrimaryStorage.shared().newDatabaseConnection()
result.objectCacheLimit = 500
return result
}()
private let editingDatabaseConnection = OWSPrimaryStorage.shared().newDatabaseConnection()
private var threadCount: UInt {
threads.numberOfItems(inGroup: TSInboxGroup)
}
// MARK: Components
// MARK: UI Components
private lazy var seedReminderView: SeedReminderView = {
let result = SeedReminderView(hasContinueButton: true)
let title = "You're almost finished! 80%"
@ -36,9 +30,7 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, UIScrol
result.delegate = self
return result
}()
private lazy var searchBar = SearchBar()
private lazy var tableView: UITableView = {
let result = UITableView()
result.backgroundColor = .clear
@ -86,23 +78,26 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, UIScrol
// MARK: Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
// Threads (part 1)
dbConnection.beginLongLivedReadTransaction() // Freeze the connection for use on the main thread (this gives us a stable data source that doesn't change until we tell it to)
// Preparation
SignalApp.shared().homeViewController = self
// Gradient & nav bar
setUpGradientBackground()
if navigationController?.navigationBar != nil {
setUpNavBarStyle()
}
updateNavigationBarButtons()
updateNavBarButtons()
setNavBarTitle("Messages")
// Set up seed reminder view if needed
let userDefaults = UserDefaults.standard
let hasViewedSeed = userDefaults[.hasViewedSeed]
// Recovery phrase reminder
let hasViewedSeed = UserDefaults.standard[.hasViewedSeed]
if !hasViewedSeed {
view.addSubview(seedReminderView)
seedReminderView.pin(.leading, to: .leading, of: view)
seedReminderView.pin(.top, to: .top, of: view)
seedReminderView.pin(.trailing, to: .trailing, of: view)
}
// Set up table view
// Table view
tableView.dataSource = self
tableView.delegate = self
view.addSubview(tableView)
@ -120,52 +115,59 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, UIScrol
fadeView.pin(.top, to: .top, of: view, withInset: topInset)
fadeView.pin(.trailing, to: .trailing, of: view)
fadeView.pin(.bottom, to: .bottom, of: view)
// Set up empty state view
// Empty state view
view.addSubview(emptyStateView)
emptyStateView.center(.horizontal, in: view)
let verticalCenteringConstraint = emptyStateView.center(.vertical, in: view)
verticalCenteringConstraint.constant = -16 // Makes things appear centered visually
// Set up new conversation button set
// New conversation button set
view.addSubview(newConversationButtonSet)
newConversationButtonSet.center(.horizontal, in: view)
newConversationButtonSet.pin(.bottom, to: .bottom, of: view, withInset: -Values.newConversationButtonBottomOffset) // Negative due to how the constraint is set up
// Set up previewing
// Previewing
if traitCollection.forceTouchCapability == .available {
registerForPreviewing(with: self, sourceView: tableView)
}
// Listen for notifications
// Notifications
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self, selector: #selector(handleYapDatabaseModifiedNotification(_:)), name: .YapDatabaseModified, object: OWSPrimaryStorage.shared().dbNotificationObject)
notificationCenter.addObserver(self, selector: #selector(handleApplicationDidBecomeActiveNotification(_:)), name: .OWSApplicationDidBecomeActive, object: nil)
notificationCenter.addObserver(self, selector: #selector(handleApplicationWillResignActiveNotification(_:)), name: .OWSApplicationWillResignActive, object: nil)
notificationCenter.addObserver(self, selector: #selector(handleProfileDidChangeNotification(_:)), name: NSNotification.Name(rawValue: kNSNotificationName_OtherUsersProfileDidChange), object: nil)
notificationCenter.addObserver(self, selector: #selector(handleLocalProfileDidChangeNotification(_:)), name: Notification.Name(kNSNotificationName_LocalProfileDidChange), object: nil)
notificationCenter.addObserver(self, selector: #selector(handleSeedViewedNotification(_:)), name: .seedViewed, object: nil)
notificationCenter.addObserver(self, selector: #selector(handleBlockedContactsUpdatedNotification(_:)), name: .blockedContactsUpdated, object: nil)
// Set up public chats and RSS feeds if needed
// Threads (part 2)
threads = YapDatabaseViewMappings(groups: [ TSInboxGroup ], view: TSThreadDatabaseViewExtensionName) // The extension should be registered at this point
threads.setIsReversed(true, forGroup: TSInboxGroup)
dbConnection.read { transaction in
self.threads.update(with: transaction) // Perform the initial update
}
// Pollers
if OWSIdentityManager.shared().identityKeyPair() != nil {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.startPollerIfNeeded()
appDelegate.startClosedGroupPollerIfNeeded()
appDelegate.startOpenGroupPollersIfNeeded()
}
// Populate onion request path countries cache
// Onion request path countries cache
DispatchQueue.global(qos: .utility).async {
let _ = IP2Country.shared.populateCacheIfNeeded()
}
// Do initial update
reload()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
isViewVisible = true
reload()
UserDefaults.standard[.hasLaunchedOnce] = true
showKeyPairMigrationNudgeIfNeeded()
showKeyPairMigrationModalIfNeeded()
showKeyPairMigrationSuccessModalIfNeeded()
}
private func showKeyPairMigrationNudgeIfNeeded() {
deinit {
NotificationCenter.default.removeObserver(self)
}
// MARK: Migration
private func showKeyPairMigrationModalIfNeeded() {
guard !KeyPairUtilities.hasV2KeyPair() else { return }
let sheet = KeyPairMigrationSheet()
sheet.modalPresentationStyle = .overFullScreen
@ -183,16 +185,7 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, UIScrol
UserDefaults.standard[.isMigratingToV2KeyPair] = false
}
override func viewWillDisappear(_ animated: Bool) {
isViewVisible = false
super.viewWillDisappear(animated)
}
deinit {
NotificationCenter.default.removeObserver(self)
}
// MARK: Data
// MARK: Table View Data Source
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return Int(threadCount)
}
@ -204,44 +197,29 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, UIScrol
}
// MARK: Updating
private func updateIsObservingDatabase() {
isObservingDatabase = isViewVisible && CurrentAppContext().isAppForegroundAndActive()
}
private func reload() {
AssertIsOnMainThread()
uiDatabaseConnection.beginLongLivedReadTransaction()
uiDatabaseConnection.read { transaction in
dbConnection.beginLongLivedReadTransaction() // Jump to the latest commit
dbConnection.read { transaction in
self.threads.update(with: transaction)
}
threadViewModelCache.removeAll()
tableView.reloadData()
emptyStateView.isHidden = (threadCount != 0)
}
@objc private func handleYapDatabaseModifiedNotification(_ notification: Notification) {
@objc private func handleYapDatabaseModifiedNotification(_ yapDatabase: YapDatabase) {
AssertIsOnMainThread()
let notifications = uiDatabaseConnection.beginLongLivedReadTransaction()
let ext = uiDatabaseConnection.ext(TSThreadDatabaseViewExtensionName) as! YapDatabaseViewConnection
let notifications = dbConnection.beginLongLivedReadTransaction() // Jump to the latest commit
guard !notifications.isEmpty else { return }
let ext = dbConnection.ext(TSThreadDatabaseViewExtensionName) as! YapDatabaseViewConnection
let hasChanges = ext.hasChanges(forGroup: TSInboxGroup, in: notifications)
guard isObservingDatabase else {
wasDatabaseModifiedExternally = hasChanges
return
}
guard hasChanges else {
uiDatabaseConnection.read { transaction in
self.threads.update(with: transaction)
}
return
}
// If changes were made in a different process (e.g. the Notification Service Extension) the thread mapping can be out of date
// at this point, causing the app to crash. The code below prevents that by force syncing the database before proceeding.
if notifications.count > 0 {
if let firstChangeSet = notifications[0].userInfo {
let firstSnapshot = firstChangeSet[YapDatabaseSnapshotKey] as! UInt64
if threads.snapshotOfLastUpdate != firstSnapshot - 1 {
return reload()
}
guard hasChanges else { return }
guard !notifications.isEmpty else { return }
if let firstChangeSet = notifications[0].userInfo {
let firstSnapshot = firstChangeSet[YapDatabaseSnapshotKey] as! UInt64
if threads.snapshotOfLastUpdate != firstSnapshot - 1 {
return reload() // The code below will crash if we try to process multiple commits at once
}
}
var sectionChanges = NSArray()
@ -254,13 +232,10 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, UIScrol
let key = rowChange.collectionKey.key
threadViewModelCache[key] = nil
switch rowChange.type {
case .delete: tableView.deleteRows(at: [ rowChange.indexPath! ], with: UITableView.RowAnimation.fade)
case .insert: tableView.insertRows(at: [ rowChange.newIndexPath! ], with: UITableView.RowAnimation.fade)
case .move:
tableView.deleteRows(at: [ rowChange.indexPath! ], with: UITableView.RowAnimation.fade)
tableView.insertRows(at: [ rowChange.newIndexPath! ], with: UITableView.RowAnimation.fade)
case .update:
tableView.reloadRows(at: [ rowChange.indexPath! ], with: UITableView.RowAnimation.none)
case .delete: tableView.deleteRows(at: [ rowChange.indexPath! ], with: UITableView.RowAnimation.automatic)
case .insert: tableView.insertRows(at: [ rowChange.newIndexPath! ], with: UITableView.RowAnimation.automatic)
case .move: tableView.moveRow(at: rowChange.indexPath!, to: rowChange.newIndexPath!)
case .update: tableView.reloadRows(at: [ rowChange.indexPath! ], with: UITableView.RowAnimation.automatic)
default: break
}
}
@ -268,24 +243,12 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, UIScrol
emptyStateView.isHidden = (threadCount != 0)
}
@objc private func handleApplicationDidBecomeActiveNotification(_ notification: Notification) {
updateIsObservingDatabase()
if wasDatabaseModifiedExternally {
reload()
wasDatabaseModifiedExternally = false
}
}
@objc private func handleApplicationWillResignActiveNotification(_ notification: Notification) {
updateIsObservingDatabase()
}
@objc private func handleProfileDidChangeNotification(_ notification: Notification) {
tableView.reloadData() // TODO: Just reload the affected cell
}
@objc private func handleLocalProfileDidChangeNotification(_ notification: Notification) {
updateNavigationBarButtons()
updateNavBarButtons()
}
@objc private func handleSeedViewedNotification(_ notification: Notification) {
@ -298,7 +261,7 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, UIScrol
self.tableView.reloadData() // TODO: Just reload the affected cell
}
private func updateNavigationBarButtons() {
private func updateNavBarButtons() {
let profilePictureSize = Values.verySmallProfilePictureSize
let profilePictureView = ProfilePictureView()
profilePictureView.accessibilityLabel = "Settings button"
@ -353,10 +316,6 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, UIScrol
present(navigationController, animated: true, completion: nil)
}
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
searchBar.resignFirstResponder()
}
func previewingContext(_ previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? {
guard let indexPath = tableView.indexPathForRow(at: location), let thread = self.thread(at: indexPath.row) else { return nil }
previewingContext.sourceRect = tableView.rectForRow(at: indexPath)
@ -465,8 +424,8 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, UIScrol
}
@objc func joinOpenGroup() {
let joinPublicChatVC = JoinPublicChatVC()
let navigationController = OWSNavigationController(rootViewController: joinPublicChatVC)
let joinOpenGroupVC = JoinPublicChatVC()
let navigationController = OWSNavigationController(rootViewController: joinOpenGroupVC)
present(navigationController, animated: true, completion: nil)
}
@ -485,8 +444,9 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, UIScrol
// MARK: Convenience
private func thread(at index: Int) -> TSThread? {
var thread: TSThread? = nil
uiDatabaseConnection.read { transaction in
thread = ((transaction as YapDatabaseReadTransaction).ext(TSThreadDatabaseViewExtensionName) as! YapDatabaseViewTransaction).object(atRow: UInt(index), inSection: 0, with: self.threads) as! TSThread?
dbConnection.read { transaction in
let ext = transaction.ext(TSThreadDatabaseViewExtensionName) as! YapDatabaseViewTransaction
thread = ext.object(atRow: UInt(index), inSection: 0, with: self.threads) as! TSThread?
}
return thread
}
@ -497,7 +457,7 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, UIScrol
return cachedThreadViewModel
} else {
var threadViewModel: ThreadViewModel? = nil
uiDatabaseConnection.read { transaction in
dbConnection.read { transaction in
threadViewModel = ThreadViewModel(thread: thread, transaction: transaction)
}
threadViewModelCache[thread.uniqueId!] = threadViewModel

View File

@ -599,24 +599,6 @@ class PhotoCaptureOutputAdaptee: NSObject, ImageCaptureOutput {
}
completion()
}
// for legacy (iOS10) devices
func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photoSampleBuffer: CMSampleBuffer?, previewPhoto previewPhotoSampleBuffer: CMSampleBuffer?, resolvedSettings: AVCaptureResolvedPhotoSettings, bracketSettings: AVCaptureBracketedStillImageSettings?, error: Error?) {
if #available(iOS 11, *) {
owsFailDebug("unexpectedly calling legacy method.")
}
guard let photoSampleBuffer = photoSampleBuffer else {
owsFailDebug("sampleBuffer was unexpectedly nil")
return
}
let data = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(photoSampleBuffer)
DispatchQueue.main.async {
self.delegate?.captureOutputDidFinishProcessing(photoData: data, error: error)
}
completion()
}
}
}
@ -687,6 +669,7 @@ extension AVCaptureVideoOrientation: CustomStringConvertible {
return "AVCaptureVideoOrientation.landscapeRight"
case .landscapeLeft:
return "AVCaptureVideoOrientation.landscapeLeft"
default: preconditionFailure()
}
}
}
@ -708,6 +691,7 @@ extension UIDeviceOrientation: CustomStringConvertible {
return "UIDeviceOrientation.faceUp"
case .faceDown:
return "UIDeviceOrientation.faceDown"
default: preconditionFailure()
}
}
}
@ -731,6 +715,7 @@ extension UIImage.Orientation: CustomStringConvertible {
return "UIImageOrientation.leftMirrored"
case .rightMirrored:
return "UIImageOrientation.rightMirrored"
default: preconditionFailure()
}
}
}

View File

@ -330,6 +330,7 @@ class PhotoCaptureViewController: OWSViewController {
imageName = "ic_flash_mode_on"
case .off:
imageName = "ic_flash_mode_off"
default: preconditionFailure()
}
self.flashModeControl.setImage(imageName: imageName)
@ -520,6 +521,7 @@ class CaptureButton: UIView {
self.superview?.layoutIfNeeded()
}
delegate?.didCancelLongPressCaptureButton(self)
default: break
}
}
}

View File

@ -470,6 +470,8 @@ static NSTimeInterval launchStartedAt;
// Only mark the app as ready once
return;
}
[SNConfiguration performMainSetup];
// TODO: Once "app ready" logic is moved into AppSetup, move this line there.
[self.profileManager ensureLocalProfileCached];

View File

@ -65,7 +65,7 @@
<key>NSContactsUsageDescription</key>
<string>Signal uses your contacts to find users you know. We do not store your contacts on the server.</string>
<key>NSFaceIDUsageDescription</key>
<string>Session&apos;s Screen Lock feature uses Face ID.</string>
<string>Session's Screen Lock feature uses Face ID.</string>
<key>NSMicrophoneUsageDescription</key>
<string>Session needs access to your microphone to record media.</string>
<key>NSPhotoLibraryAddUsageDescription</key>

View File

@ -2638,7 +2638,7 @@
"vc_create_closed_group_empty_state_button_title" = "Session starten";
"vc_create_closed_group_group_name_missing_error" = "Bitte geben Sie einen Gruppennamen ein.";
"vc_create_closed_group_group_name_too_long_error" = "Bitte geben Sie einen kürzeren Gruppennamen ein.";
"vc_create_closed_group_too_many_group_members_error" = "Eine geschlossene Gruppe kann maximal 20 Mitglieder haben.";
"vc_create_closed_group_too_many_group_members_error" = "Eine geschlossene Gruppe kann maximal 100 Mitglieder haben.";
"vc_create_closed_group_invalid_session_id_error" = "Ein Mitglied Ihrer Gruppe hat eine ungültige Session ID.";
"vc_join_public_chat_title" = "Offener Gruppe beitreten";

View File

@ -2647,7 +2647,7 @@
"vc_create_closed_group_empty_state_button_title" = "Start a Session";
"vc_create_closed_group_group_name_missing_error" = "Please enter a group name";
"vc_create_closed_group_group_name_too_long_error" = "Please enter a shorter group name";
"vc_create_closed_group_too_many_group_members_error" = "A closed group cannot have more than 20 members";
"vc_create_closed_group_too_many_group_members_error" = "A closed group cannot have more than 100 members";
"vc_create_closed_group_invalid_session_id_error" = "One of the members of your group has an invalid Session ID";
"vc_join_public_chat_title" = "Join Open Group";

View File

@ -2638,7 +2638,7 @@
"vc_create_closed_group_empty_state_button_title" = "Empezar una Session";
"vc_create_closed_group_group_name_missing_error" = "Por favor, ingresa un nombre de grupo";
"vc_create_closed_group_group_name_too_long_error" = "Por favor, ingresa un nombre de grupo más corto";
"vc_create_closed_group_too_many_group_members_error" = "Un grupo cerrado no puede tener más de 20 miembros";
"vc_create_closed_group_too_many_group_members_error" = "Un grupo cerrado no puede tener más de 100 miembros";
"vc_create_closed_group_invalid_session_id_error" = "Uno de los miembros de tu grupo tiene un ID de Session no válido";
"vc_join_public_chat_title" = "Únete al grupo abierto";

View File

@ -2638,7 +2638,7 @@
"vc_create_closed_group_empty_state_button_title" = "شروع Session";
"vc_create_closed_group_group_name_missing_error" = "لطفا یک نام گروه وارد کنید";
"vc_create_closed_group_group_name_too_long_error" = "لطفا نام گروه کوتاه‌تری وارد کنید";
"vc_create_closed_group_too_many_group_members_error" = "یک گروه خصوصی نمی‌تواند بیش از بیست عضو داشته باشد";
"vc_create_closed_group_too_many_group_members_error" = "یک گروه خصوصی نمی‌تواند بیش از یکصد عضو داشته باشد";
"vc_create_closed_group_invalid_session_id_error" = "یکی از اعضای گروه شما دارای شناسه نامعتبر است";
"vc_join_public_chat_title" = "به گروه باز بپیوندید";

View File

@ -2648,7 +2648,7 @@
"vc_create_closed_group_empty_state_button_title" = "Démarrer une session";
"vc_create_closed_group_group_name_missing_error" = "Veuillez saisir un nom de groupe";
"vc_create_closed_group_group_name_too_long_error" = "Veuillez saisir un nom de groupe plus court";
"vc_create_closed_group_too_many_group_members_error" = "Un groupe privé ne peut pas avoir plus de 20 membres";
"vc_create_closed_group_too_many_group_members_error" = "Un groupe privé ne peut pas avoir plus de 100 membres";
"vc_create_closed_group_invalid_session_id_error" = "Un des membres de votre groupe a un Session ID non valide";
"vc_join_public_chat_title" = "Joindre un groupe public";

View File

@ -2639,7 +2639,7 @@
"vc_create_closed_group_group_name_missing_error" = "Masukkan nama grup";
"vc_create_closed_group_group_name_too_long_error" = "Masukkan nama grup yang lebih pendek";
"vc_create_closed_group_not_enough_group_members_error" = "Pilih setidaknya 2 anggota grup";
"vc_create_closed_group_too_many_group_members_error" = "Grup tertutup maksimal berisi 20 anggota";
"vc_create_closed_group_too_many_group_members_error" = "Grup tertutup maksimal berisi 100 anggota";
"vc_create_closed_group_invalid_session_id_error" = "Salah satu anggota di grup memiliki Session ID yang salah";
"vc_join_public_chat_title" = "Gabung ke grup terbuka";

View File

@ -2638,7 +2638,7 @@
"vc_create_closed_group_empty_state_button_title" = "Inizia una sessione";
"vc_create_closed_group_group_name_missing_error" = "Inserisci un nome per il gruppo";
"vc_create_closed_group_group_name_too_long_error" = "Inserisci un nome gruppo più breve";
"vc_create_closed_group_too_many_group_members_error" = "Un gruppo chiuso non può avere più di 20 membri";
"vc_create_closed_group_too_many_group_members_error" = "Un gruppo chiuso non può avere più di 100 membri";
"vc_create_closed_group_invalid_session_id_error" = "Uno dei membri del tuo gruppo ha una Sessione ID non valido";
"vc_join_public_chat_title" = "Unisciti a un gruppo aperto";

View File

@ -2639,7 +2639,7 @@
"vc_create_closed_group_group_name_missing_error" = "グループ名を入力してください";
"vc_create_closed_group_group_name_too_long_error" = "短いグループ名を入力してください";
"vc_create_closed_group_not_enough_group_members_error" = "グループメンバーを少なくとも 2 人選択してください";
"vc_create_closed_group_too_many_group_members_error" = "閉じたグループは 20 人を超えるメンバーを抱えることはできません";
"vc_create_closed_group_too_many_group_members_error" = "閉じたグループは 100 人を超えるメンバーを抱えることはできません";
"vc_create_closed_group_invalid_session_id_error" = "グループのメンバーの 1 人の Session ID が無効です";
"vc_join_public_chat_title" = "オープングループに参加する";

View File

@ -2638,7 +2638,7 @@
"vc_create_closed_group_empty_state_button_title" = "Rozpocznij sesję";
"vc_create_closed_group_group_name_missing_error" = "Wpisz nazwę grupy";
"vc_create_closed_group_group_name_too_long_error" = "Wprowadź krótszą nazwę grupy";
"vc_create_closed_group_too_many_group_members_error" = "Grupa zamknięta nie może mieć więcej niż 20 członków";
"vc_create_closed_group_too_many_group_members_error" = "Grupa zamknięta nie może mieć więcej niż 100 członków";
"vc_create_closed_group_invalid_session_id_error" = "Jeden z członków Twojej grupy ma nieprawidłowy identyfikator Session";
"vc_join_public_chat_title" = "Dołącz do Open Group";

View File

@ -2638,7 +2638,7 @@
"vc_create_closed_group_empty_state_button_title" = "Iniciar uma sessão";
"vc_create_closed_group_group_name_missing_error" = "Digite um nome de grupo";
"vc_create_closed_group_group_name_too_long_error" = "Digite um nome de grupo mais curto";
"vc_create_closed_group_too_many_group_members_error" = "Um grupo fechado não pode ter mais de 20 membros";
"vc_create_closed_group_too_many_group_members_error" = "Um grupo fechado não pode ter mais de 100 membros";
"vc_create_closed_group_invalid_session_id_error" = "Um dos membros do seu grupo tem um ID Session inválido";
"vc_join_public_chat_title" = "Participar em grupo aberto";

View File

@ -2638,7 +2638,7 @@
"vc_create_closed_group_empty_state_button_title" = "Начать Сессию";
"vc_create_closed_group_group_name_missing_error" = "Пожалуйста, введите название группы";
"vc_create_closed_group_group_name_too_long_error" = "Пожалуйста, введите более короткое имя группы";
"vc_create_closed_group_too_many_group_members_error" = "В закрытой группе не может быть больше 20 участников";
"vc_create_closed_group_too_many_group_members_error" = "В закрытой группе не может быть больше 100 участников";
"vc_create_closed_group_invalid_session_id_error" = "Один из участников вашей группы имеет недопустимый Session ID";
"vc_join_public_chat_title" = "Присоединиться к открытой группе";

View File

@ -2648,7 +2648,7 @@
"vc_create_closed_group_group_name_missing_error" = "Vui lòng nhập tên nhóm";
"vc_create_closed_group_group_name_too_long_error" = "Vui lòng nhập một tên nhóm ngắn hơn ";
"vc_create_closed_group_not_enough_group_members_error" = "Vui lòng chọn ít nhất 2 thành viên trong nhóm ";
"vc_create_closed_group_too_many_group_members_error" = "Một nhóm kín không thể có nhiều hơn 20 thành viên ";
"vc_create_closed_group_too_many_group_members_error" = "Một nhóm kín không thể có nhiều hơn 100 thành viên ";
"vc_create_closed_group_invalid_session_id_error" = "Một trong các thành viên trong nhóm của bạn có Session ID không hợp lệ ";
"vc_join_public_chat_title" = "Tham gia nhóm mở";

View File

@ -2638,7 +2638,7 @@
"vc_create_closed_group_empty_state_button_title" = "开始对话";
"vc_create_closed_group_group_name_missing_error" = "请输入群组名称";
"vc_create_closed_group_group_name_too_long_error" = "请输入较短的群组名称";
"vc_create_closed_group_too_many_group_members_error" = "私密群组成员不得超过20个";
"vc_create_closed_group_too_many_group_members_error" = "私密群组成员不得超过100个";
"vc_create_closed_group_invalid_session_id_error" = "您群组中的一位成员的Session ID无效";
"vc_join_public_chat_title" = "加入公开群组";

View File

@ -21,6 +21,7 @@ class AppUpdateNag: NSObject {
public func showAppUpgradeNagIfNecessary() {
return
/*
guard let currentVersion = self.currentVersion else {
owsFailDebug("currentVersion was unexpectedly nil")
return
@ -49,6 +50,7 @@ class AppUpdateNag: NSObject {
}.catch { error in
Logger.error("failed with error: \(error)")
}.retainUntilComplete()
*/
}
// MARK: - Internal
@ -110,7 +112,7 @@ class AppUpdateNag: NSObject {
// Only show nag if we are "at rest" in the home view or registration view without any
// alerts or dialogs showing.
guard let frontmostViewController = UIApplication.shared.frontmostViewController else {
guard UIApplication.shared.frontmostViewController != nil else {
owsFailDebug("frontmostViewController was unexpectedly nil")
return
}

View File

@ -180,14 +180,18 @@ extension Storage {
(transaction as! YapDatabaseReadWriteTransaction).setObject(newValue, forKey: openGroupID, inCollection: Storage.openGroupUserCountCollection)
}
public func getIDForMessage(withServerID serverID: UInt64) -> UInt64? {
var result: UInt64? = nil
public func getIDForMessage(withServerID serverID: UInt64) -> String? {
var result: String? = nil
Storage.read { transaction in
result = transaction.object(forKey: String(serverID), inCollection: Storage.openGroupMessageIDCollection) as? UInt64
result = transaction.object(forKey: String(serverID), inCollection: Storage.openGroupMessageIDCollection) as? String
}
return result
}
public func setIDForMessage(withServerID serverID: UInt64, to messageID: String, using transaction: Any) {
(transaction as! YapDatabaseReadWriteTransaction).setObject(messageID, forKey: String(serverID), inCollection: Storage.openGroupMessageIDCollection)
}
public func setOpenGroupDisplayName(to displayName: String, for publicKey: String, inOpenGroupWithID openGroupID: String, using transaction: Any) {
let collection = openGroupID
(transaction as! YapDatabaseReadWriteTransaction).setObject(displayName, forKey: publicKey, inCollection: collection)

View File

@ -218,13 +218,13 @@ public final class OpenGroupAPI : DotNetAPI {
@objc(deleteMessageWithID:forGroup:onServer:isSentByUser:)
public static func objc_deleteMessage(with messageID: UInt, for group: UInt64, on server: String, isSentByUser: Bool) -> AnyPromise {
return AnyPromise.from(deleteMessage(with: messageID, for: group, on: server, isSentByUser: isSentByUser))
return AnyPromise.from(deleteMessage(with: messageID, for: group, on: server, wasSentByUser: isSentByUser))
}
public static func deleteMessage(with messageID: UInt, for channel: UInt64, on server: String, isSentByUser: Bool) -> Promise<Void> {
let isModerationRequest = !isSentByUser
public static func deleteMessage(with messageID: UInt, for channel: UInt64, on server: String, wasSentByUser: Bool) -> Promise<Void> {
let isModerationRequest = !wasSentByUser
SNLog("Deleting message with ID: \(messageID) for open group channel with ID: \(channel) on server: \(server) (isModerationRequest = \(isModerationRequest)).")
let urlAsString = isSentByUser ? "\(server)/channels/\(channel)/messages/\(messageID)" : "\(server)/loki/v1/moderation/message/\(messageID)"
let urlAsString = wasSentByUser ? "\(server)/channels/\(channel)/messages/\(messageID)" : "\(server)/loki/v1/moderation/message/\(messageID)"
return attempt(maxRetryCount: maxRetryCount, recoveringOn: DispatchQueue.global(qos: .default)) {
getOpenGroupServerPublicKey(for: server).then(on: DispatchQueue.global(qos: .default)) { serverPublicKey in
getAuthToken(for: server).then(on: DispatchQueue.global(qos: .default)) { token -> Promise<Void> in
@ -238,6 +238,33 @@ public final class OpenGroupAPI : DotNetAPI {
}.handlingInvalidAuthTokenIfNeeded(for: server)
}
}
// MARK: Banning
@objc(banPublicKey:fromServer:)
public static func objc_ban(_ publicKey: String, from server: String) -> AnyPromise {
return AnyPromise.from(ban(publicKey, from: server))
}
public static func ban(_ publicKey: String, from server: String) -> Promise<Void> {
SNLog("Banning user with ID: \(publicKey) from server: \(server).")
return attempt(maxRetryCount: maxRetryCount, recoveringOn: DispatchQueue.global(qos: .default)) {
getOpenGroupServerPublicKey(for: server).then(on: DispatchQueue.global(qos: .default)) { serverPublicKey in
getAuthToken(for: server).then(on: DispatchQueue.global(qos: .default)) { token -> Promise<Void> in
let url = URL(string: "\(server)/loki/v1/moderation/blacklist/@\(publicKey)")!
let request = TSRequest(url: url, method: "POST", parameters: [:])
request.allHTTPHeaderFields = [ "Content-Type" : "application/json", "Authorization" : "Bearer \(token)" ]
let promise = OnionRequestAPI.sendOnionRequest(request, to: server, using: serverPublicKey, isJSONRequired: false)
promise.done(on: DispatchQueue.global(qos: .default)) { _ -> Void in
SNLog("Banned user with ID: \(publicKey) from server: \(server).")
}
promise.catch(on: DispatchQueue.main) { error in
print(error)
}
return promise.map { _ in }
}
}.handlingInvalidAuthTokenIfNeeded(for: server)
}
}
// MARK: Display Name & Profile Picture
public static func getDisplayNames(for channel: UInt64, on server: String) -> Promise<Void> {

View File

@ -17,6 +17,12 @@ extension MessageReceiver {
case let message as VisibleMessage: try handleVisibleMessage(message, associatedWithProto: proto, openGroupID: openGroupID, isBackgroundPoll: isBackgroundPoll, using: transaction)
default: fatalError()
}
// Touch the thread to update the home screen preview
let storage = SNMessagingKitConfiguration.shared.storage
guard let threadID = storage.getOrCreateThread(for: message.sender!, groupPublicKey: message.groupPublicKey, openGroupID: openGroupID, using: transaction) else { return }
let transaction = transaction as! YapDatabaseReadWriteTransaction
guard let thread = TSThread.fetch(uniqueId: threadID, transaction: transaction) else { return }
thread.touch(with: transaction)
}
private static func handleReadReceipt(_ message: ReadReceipt, using transaction: Any) {
@ -226,6 +232,10 @@ extension MessageReceiver {
if isMainAppAndActive {
cancelTypingIndicatorsIfNeeded(for: message.sender!)
}
// Keep track of the open group server message ID message ID relationship
if let serverID = message.openGroupServerMessageID {
storage.setIDForMessage(withServerID: serverID, to: tsIncomingMessageID, using: transaction)
}
// Notify the user if needed
guard (isMainAppAndActive || isBackgroundPoll), let tsIncomingMessage = TSMessage.fetch(uniqueId: tsMessageID, transaction: transaction) as? TSIncomingMessage,
let thread = TSThread.fetch(uniqueId: threadID, transaction: transaction) else { return tsMessageID }
@ -289,7 +299,7 @@ extension MessageReceiver {
let group = thread.groupModel
let oldMembers = group.groupMemberIds
// Check that the message isn't from before the group was created
guard Double(message.sentTimestamp!) > thread.creationDate.timeIntervalSince1970 else {
guard Double(message.sentTimestamp!) > thread.creationDate.timeIntervalSince1970 * 1000 else {
return SNLog("Ignoring closed group update from before thread was created.")
}
// Check that the sender is a member of the group (before the update)

View File

@ -159,7 +159,7 @@ public final class MessageSender : NSObject {
// Serialize the protobuf
let plaintext: Data
do {
plaintext = try proto.serializedData()
plaintext = (try proto.serializedData() as NSData).paddedMessageBody()
} catch {
SNLog("Couldn't serialize proto due to error: \(error).")
handleFailure(with: error, using: transaction)
@ -343,15 +343,21 @@ public final class MessageSender : NSObject {
Storage.shared.addReceivedMessageTimestamp(message.sentTimestamp!, using: transaction) // To later ignore self-sends in a multi device context
guard let tsMessage = TSOutgoingMessage.find(withTimestamp: message.sentTimestamp!) else { return }
tsMessage.openGroupServerMessageID = message.openGroupServerMessageID ?? 0
let storage = SNMessagingKitConfiguration.shared.storage
let transaction = transaction as! YapDatabaseReadWriteTransaction
tsMessage.save(with: transaction)
if let serverID = message.openGroupServerMessageID {
storage.setIDForMessage(withServerID: serverID, to: tsMessage.uniqueId!, using: transaction)
}
var recipients = [ message.recipient! ]
if case .closedGroup(_) = destination, let threadID = message.threadID, // threadID should always be set at this point
let thread = TSGroupThread.fetch(uniqueId: threadID, transaction: transaction as! YapDatabaseReadTransaction), thread.isClosedGroup {
let thread = TSGroupThread.fetch(uniqueId: threadID, transaction: transaction), thread.isClosedGroup {
recipients = thread.groupModel.groupMemberIds
}
recipients.forEach { recipient in
tsMessage.update(withSentRecipient: recipient, wasSentByUD: true, transaction: transaction as! YapDatabaseReadWriteTransaction)
tsMessage.update(withSentRecipient: recipient, wasSentByUD: true, transaction: transaction)
}
OWSDisappearingMessagesJob.shared().startAnyExpiration(for: tsMessage, expirationStartedAt: NSDate.millisecondTimestamp(), transaction: transaction as! YapDatabaseReadWriteTransaction)
OWSDisappearingMessagesJob.shared().startAnyExpiration(for: tsMessage, expirationStartedAt: NSDate.millisecondTimestamp(), transaction: transaction)
// Sync the message if:
// it wasn't a self-send
// it was a visible message

View File

@ -19,7 +19,7 @@ public final class OpenGroupPoller : NSObject {
// MARK: Settings
private let pollForNewMessagesInterval: TimeInterval = 4
private let pollForDeletedMessagesInterval: TimeInterval = 60
private let pollForDeletedMessagesInterval: TimeInterval = 30
private let pollForModeratorsInterval: TimeInterval = 10 * 60
// MARK: Lifecycle
@ -193,9 +193,10 @@ public final class OpenGroupPoller : NSObject {
let openGroup = self.openGroup
let _ = OpenGroupAPI.getDeletedMessageServerIDs(for: openGroup.channel, on: openGroup.server).done(on: DispatchQueue.global(qos: .default)) { deletedMessageServerIDs in
let deletedMessageIDs = deletedMessageServerIDs.compactMap { Storage.shared.getIDForMessage(withServerID: UInt64($0)) }
SNMessagingKitConfiguration.shared.storage.writeSync { transaction in
SNMessagingKitConfiguration.shared.storage.write { transaction in
deletedMessageIDs.forEach { messageID in
TSMessage.fetch(uniqueId: String(messageID))?.remove(with: transaction as! YapDatabaseReadWriteTransaction)
let transaction = transaction as! YapDatabaseReadWriteTransaction
TSMessage.fetch(uniqueId: messageID, transaction: transaction)?.remove(with: transaction)
}
}
}

View File

@ -74,7 +74,8 @@ public protocol SessionMessagingKitStorageProtocol {
// MARK: - Open Group Metadata
func setUserCount(to newValue: Int, forOpenGroupWithID openGroupID: String, using transaction: Any)
func getIDForMessage(withServerID serverID: UInt64) -> UInt64?
func getIDForMessage(withServerID serverID: UInt64) -> String?
func setIDForMessage(withServerID serverID: UInt64, to messageID: String, using transaction: Any)
func setOpenGroupDisplayName(to displayName: String, for publicKey: String, inOpenGroupWithID openGroupID: String, using transaction: Any)
func setLastProfilePictureUploadDate(_ date: Date) // Stored in user defaults so no transaction is needed

View File

@ -2,8 +2,6 @@ import UserNotifications
import SessionMessagingKit
import SignalUtilitiesKit
// TODO: Group notifications
public final class NotificationServiceExtension : UNNotificationServiceExtension {
private var didPerformSetup = false
private var areVersionMigrationsComplete = false

View File

@ -180,7 +180,7 @@ public class ShareViewController: UIViewController, ShareViewDelegate, SAEFailed
// Avoid blocking app launch by putting all further possible DB access in async block
DispatchQueue.global().async { [weak self] in
guard let _ = self else { return }
Logger.info("running post launch block for registered user: \(TSAccountManager.localNumber)")
Logger.info("running post launch block for registered user: \(TSAccountManager.localNumber())")
// We don't need to use OWSDisappearingMessagesJob in the SAE.
@ -199,7 +199,7 @@ public class ShareViewController: UIViewController, ShareViewDelegate, SAEFailed
if tsAccountManager.isRegistered() {
DispatchQueue.main.async { [weak self] in
guard let _ = self else { return }
Logger.info("running post launch block for registered user: \(TSAccountManager.localNumber)")
Logger.info("running post launch block for registered user: \(TSAccountManager.localNumber())")
// We don't need to use the TSSocketManager in the SAE.
@ -258,7 +258,7 @@ public class ShareViewController: UIViewController, ShareViewDelegate, SAEFailed
AppReadiness.setAppIsReady()
if tsAccountManager.isRegistered() {
Logger.info("localNumber: \(TSAccountManager.localNumber)")
Logger.info("localNumber: \(TSAccountManager.localNumber())")
// We don't need to use messageFetcherJob in the SAE.
@ -290,7 +290,7 @@ public class ShareViewController: UIViewController, ShareViewDelegate, SAEFailed
Logger.debug("")
if tsAccountManager.isRegistered() {
Logger.info("localNumber: \(TSAccountManager.localNumber)")
Logger.info("localNumber: \(TSAccountManager.localNumber())")
// We don't need to use ExperienceUpgradeFinder in the SAE.
@ -659,12 +659,8 @@ public class ShareViewController: UIViewController, ShareViewDelegate, SAEFailed
var visualMediaItemProviders = [NSItemProvider]()
var hasNonVisualMedia = false
for attachment in attachments {
guard let itemProvider = attachment as? NSItemProvider else {
owsFailDebug("Unexpected attachment type: \(String(describing: attachment))")
continue
}
if isVisualMediaItem(itemProvider: itemProvider) {
visualMediaItemProviders.append(itemProvider)
if isVisualMediaItem(itemProvider: attachment) {
visualMediaItemProviders.append(attachment)
} else {
hasNonVisualMedia = true
}
@ -690,15 +686,11 @@ public class ShareViewController: UIViewController, ShareViewDelegate, SAEFailed
}
return isUrlItem(itemProvider: itemProvider)
}) {
if let itemProvider = preferredAttachment as? NSItemProvider {
return [itemProvider]
} else {
owsFailDebug("Unexpected attachment type: \(String(describing: preferredAttachment))")
}
return [preferredAttachment]
}
// else return whatever is available
if let itemProvider = inputItem.attachments?.first as? NSItemProvider {
if let itemProvider = inputItem.attachments?.first {
return [itemProvider]
} else {
owsFailDebug("Missing attachment.")

View File

@ -10,22 +10,38 @@ FOUNDATION_EXPORT const unsigned char SignalUtilitiesKitVersionString[];
#import <SignalUtilitiesKit/AppSetup.h>
#import <SignalUtilitiesKit/AppVersion.h>
#import <SignalUtilitiesKit/AttachmentSharing.h>
#import <SignalUtilitiesKit/BlockListUIUtils.h>
#import <SignalUtilitiesKit/ByteParser.h>
#import <SignalUtilitiesKit/ContactCellView.h>
#import <SignalUtilitiesKit/ContactTableViewCell.h>
#import <SignalUtilitiesKit/DebugLogger.h>
#import <SignalUtilitiesKit/FunctionalUtil.h>
#import <SignalUtilitiesKit/NSArray+OWS.h>
#import <SignalUtilitiesKit/NSAttributedString+OWS.h>
#import <SignalUtilitiesKit/NSObject+Casting.h>
#import <SignalUtilitiesKit/NSSet+Functional.h>
#import <SignalUtilitiesKit/NSURLSessionDataTask+StatusCode.h>
#import <SignalUtilitiesKit/OWSAnyTouchGestureRecognizer.h>
#import <SignalUtilitiesKit/OWSAttachmentDownloads.h>
#import <SignalUtilitiesKit/OWSDatabaseMigration.h>
#import <SignalUtilitiesKit/OWSDatabaseMigrationRunner.h>
#import <SignalUtilitiesKit/OWSDispatch.h>
#import <SignalUtilitiesKit/OWSError.h>
#import <SignalUtilitiesKit/OWSFailedAttachmentDownloadsJob.h>
#import <SignalUtilitiesKit/OWSFailedMessagesJob.h>
#import <SignalUtilitiesKit/OWSFormat.h>
#import <SignalUtilitiesKit/OWSMessageUtils.h>
#import <SignalUtilitiesKit/OWSNavigationController.h>
#import <SignalUtilitiesKit/OWSOperation.h>
#import <SignalUtilitiesKit/OWSPrimaryStorage+keyFromIntLong.h>
#import <SignalUtilitiesKit/OWSPrimaryStorage+Loki.h>
#import <SignalUtilitiesKit/OWSProfileManager.h>
#import <SignalUtilitiesKit/OWSQueues.h>
#import <SignalUtilitiesKit/OWSResaveCollectionDBMigration.h>
#import <SignalUtilitiesKit/OWSScrubbingLogFormatter.h>
#import <SignalUtilitiesKit/OWSSearchBar.h>
#import <SignalUtilitiesKit/OWSTableViewController.h>
#import <SignalUtilitiesKit/OWSTextField.h>
#import <SignalUtilitiesKit/OWSTextView.h>
#import <SignalUtilitiesKit/OWSUnreadIndicator.h>
@ -37,7 +53,10 @@ FOUNDATION_EXPORT const unsigned char SignalUtilitiesKitVersionString[];
#import <SignalUtilitiesKit/SSKAsserts.h>
#import <SignalUtilitiesKit/Theme.h>
#import <SignalUtilitiesKit/ThreadUtil.h>
#import <SignalUtilitiesKit/ThreadViewHelper.h>
#import <SignalUtilitiesKit/TSConstants.h>
#import <SignalUtilitiesKit/TSStorageHeaders.h>
#import <SignalUtilitiesKit/TSStorageKeys.h>
#import <SignalUtilitiesKit/UIFont+OWS.h>
#import <SignalUtilitiesKit/UIUtil.h>
#import <SignalUtilitiesKit/UIViewController+OWS.h>

View File

@ -4,14 +4,15 @@
import Foundation
import MediaPlayer
import SessionUIKit
// A modal view that be used during blocking interactions (e.g. waiting on response from
// service or on the completion of a long-running local operation).
@objc
public class ModalActivityIndicatorViewController: OWSViewController {
let canCancel: Bool
let message: String?
@objc
public var wasCancelled: Bool = false
@ -29,25 +30,26 @@ public class ModalActivityIndicatorViewController: OWSViewController {
notImplemented()
}
public required init(canCancel: Bool) {
public required init(canCancel: Bool = false, message: String? = nil) {
self.canCancel = canCancel
self.message = message
super.init(nibName: nil, bundle: nil)
}
@objc
public class func present(fromViewController: UIViewController,
canCancel: Bool, backgroundBlock : @escaping (ModalActivityIndicatorViewController) -> Void) {
public class func present(fromViewController: UIViewController, canCancel: Bool = false, message: String? = nil,
backgroundBlock : @escaping (ModalActivityIndicatorViewController) -> Void) {
AssertIsOnMainThread()
let view = ModalActivityIndicatorViewController(canCancel: canCancel)
let view = ModalActivityIndicatorViewController(canCancel: canCancel, message: message)
// Present this modal _over_ the current view contents.
view.modalPresentationStyle = .overFullScreen
view.modalTransitionStyle = .crossDissolve
fromViewController.present(view,
animated: false) {
DispatchQueue.global().async {
backgroundBlock(view)
}
fromViewController.present(view, animated: false) {
DispatchQueue.global().async {
backgroundBlock(view)
}
}
}
@ -70,15 +72,31 @@ public class ModalActivityIndicatorViewController: OWSViewController {
public override func loadView() {
super.loadView()
self.view.backgroundColor = (Theme.isDarkThemeEnabled
? UIColor(white: 0, alpha: 0.5)
: UIColor(white: 0, alpha: 0.5))
self.view.backgroundColor = UIColor(white: 0, alpha: 0.5)
self.view.isOpaque = false
let activityIndicator = UIActivityIndicatorView(style: .whiteLarge)
self.activityIndicator = activityIndicator
self.view.addSubview(activityIndicator)
activityIndicator.autoCenterInSuperview()
if let message = message {
let messageLabel = UILabel()
messageLabel.text = message
messageLabel.font = .systemFont(ofSize: Values.mediumFontSize)
messageLabel.textColor = UIColor.white
messageLabel.numberOfLines = 0
messageLabel.textAlignment = .center
messageLabel.lineBreakMode = .byWordWrapping
messageLabel.set(.width, to: UIScreen.main.bounds.width - 2 * Values.mediumSpacing)
let stackView = UIStackView(arrangedSubviews: [ messageLabel, activityIndicator ])
stackView.axis = .vertical
stackView.spacing = Values.largeSpacing
stackView.alignment = .center
self.view.addSubview(stackView)
stackView.center(in: self.view)
} else {
self.view.addSubview(activityIndicator)
activityIndicator.autoCenterInSuperview()
}
if canCancel {
let cancelButton = UIButton(type: .custom)
@ -152,7 +170,6 @@ public class ModalActivityIndicatorViewController: OWSViewController {
wasCancelled = true
dismiss {
}
dismiss { }
}
}