Merge branch 'dev' into multi-device
This commit is contained in:
commit
867e9114a6
|
@ -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;
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -311,6 +311,7 @@ class MenuActionSheetView: UIView, MenuActionViewDelegate {
|
|||
case .failed:
|
||||
Logger.debug("failed")
|
||||
unhighlightAllActionViews()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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'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>
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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" = "به گروه باز بپیوندید";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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" = "オープングループに参加する";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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" = "Присоединиться к открытой группе";
|
||||
|
|
|
@ -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ở";
|
||||
|
|
|
@ -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" = "加入公开群组";
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.")
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 { }
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue