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_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
CURRENT_PROJECT_VERSION = 158;
|
CURRENT_PROJECT_VERSION = 163;
|
||||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||||
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
|
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
|
||||||
|
@ -5309,7 +5309,7 @@
|
||||||
INFOPLIST_FILE = SessionShareExtension/Meta/Info.plist;
|
INFOPLIST_FILE = SessionShareExtension/Meta/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
|
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;
|
MTL_ENABLE_DEBUG_INFO = YES;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.ShareExtension";
|
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.ShareExtension";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
@ -5357,7 +5357,7 @@
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
CURRENT_PROJECT_VERSION = 158;
|
CURRENT_PROJECT_VERSION = 163;
|
||||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||||
ENABLE_NS_ASSERTIONS = NO;
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
|
@ -5383,7 +5383,7 @@
|
||||||
INFOPLIST_FILE = SessionShareExtension/Meta/Info.plist;
|
INFOPLIST_FILE = SessionShareExtension/Meta/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
|
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_ENABLE_DEBUG_INFO = NO;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.ShareExtension";
|
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.ShareExtension";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
@ -5418,7 +5418,7 @@
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
CURRENT_PROJECT_VERSION = 158;
|
CURRENT_PROJECT_VERSION = 163;
|
||||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||||
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
|
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
|
||||||
|
@ -5437,7 +5437,7 @@
|
||||||
INFOPLIST_FILE = SessionNotificationServiceExtension/Meta/Info.plist;
|
INFOPLIST_FILE = SessionNotificationServiceExtension/Meta/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
|
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_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||||
MTL_FAST_MATH = YES;
|
MTL_FAST_MATH = YES;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.NotificationServiceExtension";
|
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.NotificationServiceExtension";
|
||||||
|
@ -5488,7 +5488,7 @@
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
CURRENT_PROJECT_VERSION = 158;
|
CURRENT_PROJECT_VERSION = 163;
|
||||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||||
ENABLE_NS_ASSERTIONS = NO;
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
|
@ -5512,7 +5512,7 @@
|
||||||
INFOPLIST_FILE = SessionNotificationServiceExtension/Meta/Info.plist;
|
INFOPLIST_FILE = SessionNotificationServiceExtension/Meta/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
|
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_ENABLE_DEBUG_INFO = NO;
|
||||||
MTL_FAST_MATH = YES;
|
MTL_FAST_MATH = YES;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.NotificationServiceExtension";
|
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.NotificationServiceExtension";
|
||||||
|
@ -6507,7 +6507,7 @@
|
||||||
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
CURRENT_PROJECT_VERSION = 158;
|
CURRENT_PROJECT_VERSION = 163;
|
||||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||||
FRAMEWORK_SEARCH_PATHS = (
|
FRAMEWORK_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
|
@ -6543,7 +6543,7 @@
|
||||||
"$(SRCROOT)",
|
"$(SRCROOT)",
|
||||||
);
|
);
|
||||||
LLVM_LTO = NO;
|
LLVM_LTO = NO;
|
||||||
MARKETING_VERSION = 1.7.3;
|
MARKETING_VERSION = 1.7.5;
|
||||||
OTHER_LDFLAGS = "$(inherited)";
|
OTHER_LDFLAGS = "$(inherited)";
|
||||||
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
|
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger";
|
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger";
|
||||||
|
@ -6575,7 +6575,7 @@
|
||||||
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
CURRENT_PROJECT_VERSION = 158;
|
CURRENT_PROJECT_VERSION = 163;
|
||||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||||
FRAMEWORK_SEARCH_PATHS = (
|
FRAMEWORK_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
|
@ -6611,7 +6611,7 @@
|
||||||
"$(SRCROOT)",
|
"$(SRCROOT)",
|
||||||
);
|
);
|
||||||
LLVM_LTO = NO;
|
LLVM_LTO = NO;
|
||||||
MARKETING_VERSION = 1.7.3;
|
MARKETING_VERSION = 1.7.5;
|
||||||
OTHER_LDFLAGS = "$(inherited)";
|
OTHER_LDFLAGS = "$(inherited)";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger";
|
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger";
|
||||||
PRODUCT_NAME = Session;
|
PRODUCT_NAME = Session;
|
||||||
|
|
|
@ -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.")
|
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
|
Storage.write(with: { [weak self] transaction in
|
||||||
do {
|
do {
|
||||||
if !members.contains(getUserHexEncodedPublicKey()) {
|
if !members.contains(getUserHexEncodedPublicKey()) {
|
||||||
|
|
|
@ -163,11 +163,12 @@ final class NewClosedGroupVC : BaseVC, UITableViewDataSource, UITableViewDelegat
|
||||||
guard selectedContacts.count >= 1 else {
|
guard selectedContacts.count >= 1 else {
|
||||||
return showError(title: "Please pick at least 1 group member")
|
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: ""))
|
return showError(title: NSLocalizedString("vc_create_closed_group_too_many_group_members_error", comment: ""))
|
||||||
}
|
}
|
||||||
let selectedContacts = self.selectedContacts
|
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>!
|
var promise: Promise<TSGroupThread>!
|
||||||
Storage.writeSync { transaction in
|
Storage.writeSync { transaction in
|
||||||
promise = MessageSender.createClosedGroup(name: name, members: selectedContacts, transaction: transaction)
|
promise = MessageSender.createClosedGroup(name: name, members: selectedContacts, transaction: transaction)
|
||||||
|
|
|
@ -1528,15 +1528,17 @@ typedef enum : NSUInteger {
|
||||||
[self showDetailViewForViewItem:conversationViewItem];
|
[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];
|
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Ban This User?" message:nil preferredStyle:UIAlertControllerStyleAlert];
|
||||||
[alert addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
|
[alert addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
|
||||||
uint64_t messageID = 0;
|
NSString* publicKey;
|
||||||
if ([conversationViewItem.interaction isKindOfClass:TSMessage.class]) {
|
if ([conversationViewItem.interaction isKindOfClass:TSIncomingMessage.class]) {
|
||||||
messageID = ((TSMessage *)conversationViewItem.interaction).openGroupServerMessageID;
|
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]];
|
[alert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleDefault handler:nil]];
|
||||||
[self presentViewController:alert animated:YES completion:nil];
|
[self presentViewController:alert animated:YES completion:nil];
|
||||||
|
@ -2537,6 +2539,7 @@ typedef enum : NSUInteger {
|
||||||
[ModalActivityIndicatorViewController
|
[ModalActivityIndicatorViewController
|
||||||
presentFromViewController:self
|
presentFromViewController:self
|
||||||
canCancel:YES
|
canCancel:YES
|
||||||
|
message:nil
|
||||||
backgroundBlock:^(ModalActivityIndicatorViewController *modalActivityIndicator) {
|
backgroundBlock:^(ModalActivityIndicatorViewController *modalActivityIndicator) {
|
||||||
DataSource *dataSource =
|
DataSource *dataSource =
|
||||||
[DataSourcePath dataSourceWithURL:movieURL shouldDeleteOnDeallocation:NO];
|
[DataSourcePath dataSourceWithURL:movieURL shouldDeleteOnDeallocation:NO];
|
||||||
|
|
|
@ -67,6 +67,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType);
|
||||||
|
|
||||||
@property (nonatomic, readonly) BOOL isGroupThread;
|
@property (nonatomic, readonly) BOOL isGroupThread;
|
||||||
@property (nonatomic, readonly) BOOL userCanDeleteGroupMessage;
|
@property (nonatomic, readonly) BOOL userCanDeleteGroupMessage;
|
||||||
|
@property (nonatomic, readonly) BOOL userHasModerationPermission;
|
||||||
|
|
||||||
@property (nonatomic, readonly) BOOL hasBodyText;
|
@property (nonatomic, readonly) BOOL hasBodyText;
|
||||||
|
|
||||||
|
|
|
@ -1162,30 +1162,45 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
|
||||||
- (BOOL)userCanDeleteGroupMessage
|
- (BOOL)userCanDeleteGroupMessage
|
||||||
{
|
{
|
||||||
if (!self.isGroupThread) return false;
|
if (!self.isGroupThread) return false;
|
||||||
|
|
||||||
// Ensure the thread is a public chat and not an RSS feed
|
|
||||||
TSGroupThread *groupThread = (TSGroupThread *)self.interaction.thread;
|
TSGroupThread *groupThread = (TSGroupThread *)self.interaction.thread;
|
||||||
|
|
||||||
// Only allow deletion on incoming and outgoing messages
|
// Only allow deletion on incoming and outgoing messages
|
||||||
OWSInteractionType interationType = self.interaction.interactionType;
|
OWSInteractionType interationType = self.interaction.interactionType;
|
||||||
if (interationType != OWSInteractionType_OutgoingMessage && interationType != OWSInteractionType_IncomingMessage) return false;
|
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;
|
TSMessage *message = (TSMessage *)self.interaction;
|
||||||
if (!message.isOpenGroupMessage) return true;
|
if (!message.isOpenGroupMessage) return true;
|
||||||
|
|
||||||
// Ensure we have the details needed to contact the server
|
// Ensure we have the details needed to contact the server
|
||||||
SNOpenGroup *publicChat = [LKStorage.shared getOpenGroupForThreadID:groupThread.uniqueId];
|
SNOpenGroup *openGroup = [LKStorage.shared getOpenGroupForThreadID:groupThread.uniqueId];
|
||||||
if (publicChat == nil) return true;
|
if (openGroup == nil) return true;
|
||||||
|
|
||||||
if (interationType == OWSInteractionType_IncomingMessage) {
|
if (interationType == OWSInteractionType_IncomingMessage) {
|
||||||
// Only allow deletion on incoming messages if the user has moderation permission
|
// 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 {
|
} else {
|
||||||
return YES;
|
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
|
@end
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_END
|
NS_ASSUME_NONNULL_END
|
||||||
|
|
|
@ -311,6 +311,7 @@ class MenuActionSheetView: UIView, MenuActionViewDelegate {
|
||||||
case .failed:
|
case .failed:
|
||||||
Logger.debug("failed")
|
Logger.debug("failed")
|
||||||
unhighlightAllActionViews()
|
unhighlightAllActionViews()
|
||||||
|
default: break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ import Foundation
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
protocol MessageActionsDelegate: class {
|
protocol MessageActionsDelegate: class {
|
||||||
func report(_ conversationViewItem: ConversationViewItem)
|
func banUser(_ conversationViewItem: ConversationViewItem)
|
||||||
func messageActionsShowDetailsForItem(_ conversationViewItem: ConversationViewItem)
|
func messageActionsShowDetailsForItem(_ conversationViewItem: ConversationViewItem)
|
||||||
func messageActionsReplyToItem(_ conversationViewItem: ConversationViewItem)
|
func messageActionsReplyToItem(_ conversationViewItem: ConversationViewItem)
|
||||||
func copyPublicKey(for conversationViewItem: ConversationViewItem)
|
func copyPublicKey(for conversationViewItem: ConversationViewItem)
|
||||||
|
@ -45,14 +45,6 @@ struct MessageActionBuilder {
|
||||||
block: { [weak delegate] _ in delegate?.messageActionsShowDetailsForItem(conversationViewItem) }
|
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 {
|
static func deleteMessage(conversationViewItem: ConversationViewItem, delegate: MessageActionsDelegate) -> MenuAction {
|
||||||
return MenuAction(image: #imageLiteral(resourceName: "ic_trash"),
|
return MenuAction(image: #imageLiteral(resourceName: "ic_trash"),
|
||||||
|
@ -61,6 +53,14 @@ struct MessageActionBuilder {
|
||||||
block: { _ in conversationViewItem.deleteAction() }
|
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 {
|
static func copyMedia(conversationViewItem: ConversationViewItem, delegate: MessageActionsDelegate) -> MenuAction {
|
||||||
return MenuAction(image: #imageLiteral(resourceName: "ic_copy"),
|
return MenuAction(image: #imageLiteral(resourceName: "ic_copy"),
|
||||||
|
@ -108,10 +108,9 @@ class ConversationViewItemActions: NSObject {
|
||||||
actions.append(deleteAction)
|
actions.append(deleteAction)
|
||||||
}
|
}
|
||||||
|
|
||||||
if isGroup && conversationViewItem.interaction.thread.name() == "Loki Public Chat"
|
if isGroup && conversationViewItem.interaction is TSIncomingMessage && conversationViewItem.userHasModerationPermission {
|
||||||
|| conversationViewItem.interaction.thread.name() == "Session Public Chat" {
|
let banAction = MessageActionBuilder.banUser(conversationViewItem: conversationViewItem, delegate: delegate)
|
||||||
let reportAction = MessageActionBuilder.report(conversationViewItem, delegate: delegate)
|
actions.append(banAction)
|
||||||
actions.append(reportAction)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let showDetailsAction = MessageActionBuilder.showDetails(conversationViewItem: conversationViewItem, delegate: delegate)
|
let showDetailsAction = MessageActionBuilder.showDetails(conversationViewItem: conversationViewItem, delegate: delegate)
|
||||||
|
@ -152,10 +151,9 @@ class ConversationViewItemActions: NSObject {
|
||||||
actions.append(deleteAction)
|
actions.append(deleteAction)
|
||||||
}
|
}
|
||||||
|
|
||||||
if isGroup && conversationViewItem.interaction.thread.name() == "Loki Public Chat"
|
if isGroup && conversationViewItem.interaction is TSIncomingMessage && conversationViewItem.userHasModerationPermission {
|
||||||
|| conversationViewItem.interaction.thread.name() == "Session Public Chat" {
|
let banAction = MessageActionBuilder.banUser(conversationViewItem: conversationViewItem, delegate: delegate)
|
||||||
let reportAction = MessageActionBuilder.report(conversationViewItem, delegate: delegate)
|
actions.append(banAction)
|
||||||
actions.append(reportAction)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let showDetailsAction = MessageActionBuilder.showDetails(conversationViewItem: conversationViewItem, delegate: delegate)
|
let showDetailsAction = MessageActionBuilder.showDetails(conversationViewItem: conversationViewItem, delegate: delegate)
|
||||||
|
@ -185,10 +183,9 @@ class ConversationViewItemActions: NSObject {
|
||||||
actions.append(deleteAction)
|
actions.append(deleteAction)
|
||||||
}
|
}
|
||||||
|
|
||||||
if isGroup && conversationViewItem.interaction.thread.name() == "Loki Public Chat"
|
if isGroup && conversationViewItem.interaction is TSIncomingMessage && conversationViewItem.userHasModerationPermission {
|
||||||
|| conversationViewItem.interaction.thread.name() == "Session Public Chat" {
|
let banAction = MessageActionBuilder.banUser(conversationViewItem: conversationViewItem, delegate: delegate)
|
||||||
let reportAction = MessageActionBuilder.report(conversationViewItem, delegate: delegate)
|
actions.append(banAction)
|
||||||
actions.append(reportAction)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let showDetailsAction = MessageActionBuilder.showDetails(conversationViewItem: conversationViewItem, delegate: delegate)
|
let showDetailsAction = MessageActionBuilder.showDetails(conversationViewItem: conversationViewItem, delegate: delegate)
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
|
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
public extension CGPoint {
|
extension CGPoint {
|
||||||
public func offsetBy(dx: CGFloat) -> CGPoint {
|
public func offsetBy(dx: CGFloat) -> CGPoint {
|
||||||
return CGPoint(x: x + dx, y: y)
|
return CGPoint(x: x + dx, y: y)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,30 +1,24 @@
|
||||||
|
|
||||||
final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, UIScrollViewDelegate, UIViewControllerPreviewingDelegate, NewConversationButtonSetDelegate, SeedReminderViewDelegate {
|
// See https://github.com/yapstudios/YapDatabase/wiki/LongLivedReadTransactions and
|
||||||
private var threadViewModelCache: [String:ThreadViewModel] = [:]
|
// https://github.com/yapstudios/YapDatabase/wiki/YapDatabaseModifiedNotification for
|
||||||
private var isObservingDatabase = true
|
// more information on database handling.
|
||||||
private var isViewVisible = false { didSet { updateIsObservingDatabase() } }
|
|
||||||
|
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 tableViewTopConstraint: NSLayoutConstraint!
|
||||||
private var wasDatabaseModifiedExternally = false
|
|
||||||
|
|
||||||
private var threads: YapDatabaseViewMappings = {
|
private var threadCount: UInt {
|
||||||
let result = YapDatabaseViewMappings(groups: [ TSInboxGroup ], view: TSThreadDatabaseViewExtensionName)
|
threads.numberOfItems(inGroup: TSInboxGroup)
|
||||||
result.setIsReversed(true, forGroup: TSInboxGroup)
|
}
|
||||||
return result
|
|
||||||
}()
|
|
||||||
|
|
||||||
private let uiDatabaseConnection: YapDatabaseConnection = {
|
private lazy var dbConnection: YapDatabaseConnection = {
|
||||||
let result = OWSPrimaryStorage.shared().newDatabaseConnection()
|
let result = OWSPrimaryStorage.shared().newDatabaseConnection()
|
||||||
result.objectCacheLimit = 500
|
result.objectCacheLimit = 500
|
||||||
return result
|
return result
|
||||||
}()
|
}()
|
||||||
|
|
||||||
private let editingDatabaseConnection = OWSPrimaryStorage.shared().newDatabaseConnection()
|
// MARK: UI Components
|
||||||
|
|
||||||
private var threadCount: UInt {
|
|
||||||
threads.numberOfItems(inGroup: TSInboxGroup)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: Components
|
|
||||||
private lazy var seedReminderView: SeedReminderView = {
|
private lazy var seedReminderView: SeedReminderView = {
|
||||||
let result = SeedReminderView(hasContinueButton: true)
|
let result = SeedReminderView(hasContinueButton: true)
|
||||||
let title = "You're almost finished! 80%"
|
let title = "You're almost finished! 80%"
|
||||||
|
@ -36,9 +30,7 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, UIScrol
|
||||||
result.delegate = self
|
result.delegate = self
|
||||||
return result
|
return result
|
||||||
}()
|
}()
|
||||||
|
|
||||||
private lazy var searchBar = SearchBar()
|
|
||||||
|
|
||||||
private lazy var tableView: UITableView = {
|
private lazy var tableView: UITableView = {
|
||||||
let result = UITableView()
|
let result = UITableView()
|
||||||
result.backgroundColor = .clear
|
result.backgroundColor = .clear
|
||||||
|
@ -86,23 +78,26 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, UIScrol
|
||||||
// MARK: Lifecycle
|
// MARK: Lifecycle
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.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
|
SignalApp.shared().homeViewController = self
|
||||||
|
// Gradient & nav bar
|
||||||
setUpGradientBackground()
|
setUpGradientBackground()
|
||||||
if navigationController?.navigationBar != nil {
|
if navigationController?.navigationBar != nil {
|
||||||
setUpNavBarStyle()
|
setUpNavBarStyle()
|
||||||
}
|
}
|
||||||
updateNavigationBarButtons()
|
updateNavBarButtons()
|
||||||
setNavBarTitle("Messages")
|
setNavBarTitle("Messages")
|
||||||
// Set up seed reminder view if needed
|
// Recovery phrase reminder
|
||||||
let userDefaults = UserDefaults.standard
|
let hasViewedSeed = UserDefaults.standard[.hasViewedSeed]
|
||||||
let hasViewedSeed = userDefaults[.hasViewedSeed]
|
|
||||||
if !hasViewedSeed {
|
if !hasViewedSeed {
|
||||||
view.addSubview(seedReminderView)
|
view.addSubview(seedReminderView)
|
||||||
seedReminderView.pin(.leading, to: .leading, of: view)
|
seedReminderView.pin(.leading, to: .leading, of: view)
|
||||||
seedReminderView.pin(.top, to: .top, of: view)
|
seedReminderView.pin(.top, to: .top, of: view)
|
||||||
seedReminderView.pin(.trailing, to: .trailing, of: view)
|
seedReminderView.pin(.trailing, to: .trailing, of: view)
|
||||||
}
|
}
|
||||||
// Set up table view
|
// Table view
|
||||||
tableView.dataSource = self
|
tableView.dataSource = self
|
||||||
tableView.delegate = self
|
tableView.delegate = self
|
||||||
view.addSubview(tableView)
|
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(.top, to: .top, of: view, withInset: topInset)
|
||||||
fadeView.pin(.trailing, to: .trailing, of: view)
|
fadeView.pin(.trailing, to: .trailing, of: view)
|
||||||
fadeView.pin(.bottom, to: .bottom, of: view)
|
fadeView.pin(.bottom, to: .bottom, of: view)
|
||||||
// Set up empty state view
|
// Empty state view
|
||||||
view.addSubview(emptyStateView)
|
view.addSubview(emptyStateView)
|
||||||
emptyStateView.center(.horizontal, in: view)
|
emptyStateView.center(.horizontal, in: view)
|
||||||
let verticalCenteringConstraint = emptyStateView.center(.vertical, in: view)
|
let verticalCenteringConstraint = emptyStateView.center(.vertical, in: view)
|
||||||
verticalCenteringConstraint.constant = -16 // Makes things appear centered visually
|
verticalCenteringConstraint.constant = -16 // Makes things appear centered visually
|
||||||
// Set up new conversation button set
|
// New conversation button set
|
||||||
view.addSubview(newConversationButtonSet)
|
view.addSubview(newConversationButtonSet)
|
||||||
newConversationButtonSet.center(.horizontal, in: view)
|
newConversationButtonSet.center(.horizontal, in: view)
|
||||||
newConversationButtonSet.pin(.bottom, to: .bottom, of: view, withInset: -Values.newConversationButtonBottomOffset) // Negative due to how the constraint is set up
|
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 {
|
if traitCollection.forceTouchCapability == .available {
|
||||||
registerForPreviewing(with: self, sourceView: tableView)
|
registerForPreviewing(with: self, sourceView: tableView)
|
||||||
}
|
}
|
||||||
// Listen for notifications
|
// Notifications
|
||||||
let notificationCenter = NotificationCenter.default
|
let notificationCenter = NotificationCenter.default
|
||||||
notificationCenter.addObserver(self, selector: #selector(handleYapDatabaseModifiedNotification(_:)), name: .YapDatabaseModified, object: OWSPrimaryStorage.shared().dbNotificationObject)
|
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(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(handleLocalProfileDidChangeNotification(_:)), name: Notification.Name(kNSNotificationName_LocalProfileDidChange), object: nil)
|
||||||
notificationCenter.addObserver(self, selector: #selector(handleSeedViewedNotification(_:)), name: .seedViewed, object: nil)
|
notificationCenter.addObserver(self, selector: #selector(handleSeedViewedNotification(_:)), name: .seedViewed, object: nil)
|
||||||
notificationCenter.addObserver(self, selector: #selector(handleBlockedContactsUpdatedNotification(_:)), name: .blockedContactsUpdated, 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 {
|
if OWSIdentityManager.shared().identityKeyPair() != nil {
|
||||||
let appDelegate = UIApplication.shared.delegate as! AppDelegate
|
let appDelegate = UIApplication.shared.delegate as! AppDelegate
|
||||||
appDelegate.startPollerIfNeeded()
|
appDelegate.startPollerIfNeeded()
|
||||||
appDelegate.startClosedGroupPollerIfNeeded()
|
appDelegate.startClosedGroupPollerIfNeeded()
|
||||||
appDelegate.startOpenGroupPollersIfNeeded()
|
appDelegate.startOpenGroupPollersIfNeeded()
|
||||||
}
|
}
|
||||||
// Populate onion request path countries cache
|
// Onion request path countries cache
|
||||||
DispatchQueue.global(qos: .utility).async {
|
DispatchQueue.global(qos: .utility).async {
|
||||||
let _ = IP2Country.shared.populateCacheIfNeeded()
|
let _ = IP2Country.shared.populateCacheIfNeeded()
|
||||||
}
|
}
|
||||||
// Do initial update
|
|
||||||
reload()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewDidAppear(_ animated: Bool) {
|
override func viewDidAppear(_ animated: Bool) {
|
||||||
super.viewDidAppear(animated)
|
super.viewDidAppear(animated)
|
||||||
isViewVisible = true
|
reload()
|
||||||
UserDefaults.standard[.hasLaunchedOnce] = true
|
UserDefaults.standard[.hasLaunchedOnce] = true
|
||||||
showKeyPairMigrationNudgeIfNeeded()
|
showKeyPairMigrationModalIfNeeded()
|
||||||
showKeyPairMigrationSuccessModalIfNeeded()
|
showKeyPairMigrationSuccessModalIfNeeded()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func showKeyPairMigrationNudgeIfNeeded() {
|
deinit {
|
||||||
|
NotificationCenter.default.removeObserver(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Migration
|
||||||
|
private func showKeyPairMigrationModalIfNeeded() {
|
||||||
guard !KeyPairUtilities.hasV2KeyPair() else { return }
|
guard !KeyPairUtilities.hasV2KeyPair() else { return }
|
||||||
let sheet = KeyPairMigrationSheet()
|
let sheet = KeyPairMigrationSheet()
|
||||||
sheet.modalPresentationStyle = .overFullScreen
|
sheet.modalPresentationStyle = .overFullScreen
|
||||||
|
@ -183,16 +185,7 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, UIScrol
|
||||||
UserDefaults.standard[.isMigratingToV2KeyPair] = false
|
UserDefaults.standard[.isMigratingToV2KeyPair] = false
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewWillDisappear(_ animated: Bool) {
|
// MARK: Table View Data Source
|
||||||
isViewVisible = false
|
|
||||||
super.viewWillDisappear(animated)
|
|
||||||
}
|
|
||||||
|
|
||||||
deinit {
|
|
||||||
NotificationCenter.default.removeObserver(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: Data
|
|
||||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||||
return Int(threadCount)
|
return Int(threadCount)
|
||||||
}
|
}
|
||||||
|
@ -204,44 +197,29 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, UIScrol
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Updating
|
// MARK: Updating
|
||||||
private func updateIsObservingDatabase() {
|
|
||||||
isObservingDatabase = isViewVisible && CurrentAppContext().isAppForegroundAndActive()
|
|
||||||
}
|
|
||||||
|
|
||||||
private func reload() {
|
private func reload() {
|
||||||
AssertIsOnMainThread()
|
AssertIsOnMainThread()
|
||||||
uiDatabaseConnection.beginLongLivedReadTransaction()
|
dbConnection.beginLongLivedReadTransaction() // Jump to the latest commit
|
||||||
uiDatabaseConnection.read { transaction in
|
dbConnection.read { transaction in
|
||||||
self.threads.update(with: transaction)
|
self.threads.update(with: transaction)
|
||||||
}
|
}
|
||||||
threadViewModelCache.removeAll()
|
threadViewModelCache.removeAll()
|
||||||
tableView.reloadData()
|
tableView.reloadData()
|
||||||
emptyStateView.isHidden = (threadCount != 0)
|
emptyStateView.isHidden = (threadCount != 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func handleYapDatabaseModifiedNotification(_ notification: Notification) {
|
@objc private func handleYapDatabaseModifiedNotification(_ yapDatabase: YapDatabase) {
|
||||||
AssertIsOnMainThread()
|
AssertIsOnMainThread()
|
||||||
let notifications = uiDatabaseConnection.beginLongLivedReadTransaction()
|
let notifications = dbConnection.beginLongLivedReadTransaction() // Jump to the latest commit
|
||||||
let ext = uiDatabaseConnection.ext(TSThreadDatabaseViewExtensionName) as! YapDatabaseViewConnection
|
guard !notifications.isEmpty else { return }
|
||||||
|
let ext = dbConnection.ext(TSThreadDatabaseViewExtensionName) as! YapDatabaseViewConnection
|
||||||
let hasChanges = ext.hasChanges(forGroup: TSInboxGroup, in: notifications)
|
let hasChanges = ext.hasChanges(forGroup: TSInboxGroup, in: notifications)
|
||||||
guard isObservingDatabase else {
|
guard hasChanges else { return }
|
||||||
wasDatabaseModifiedExternally = hasChanges
|
guard !notifications.isEmpty else { return }
|
||||||
return
|
if let firstChangeSet = notifications[0].userInfo {
|
||||||
}
|
let firstSnapshot = firstChangeSet[YapDatabaseSnapshotKey] as! UInt64
|
||||||
guard hasChanges else {
|
if threads.snapshotOfLastUpdate != firstSnapshot - 1 {
|
||||||
uiDatabaseConnection.read { transaction in
|
return reload() // The code below will crash if we try to process multiple commits at once
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var sectionChanges = NSArray()
|
var sectionChanges = NSArray()
|
||||||
|
@ -254,13 +232,10 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, UIScrol
|
||||||
let key = rowChange.collectionKey.key
|
let key = rowChange.collectionKey.key
|
||||||
threadViewModelCache[key] = nil
|
threadViewModelCache[key] = nil
|
||||||
switch rowChange.type {
|
switch rowChange.type {
|
||||||
case .delete: tableView.deleteRows(at: [ rowChange.indexPath! ], with: UITableView.RowAnimation.fade)
|
case .delete: tableView.deleteRows(at: [ rowChange.indexPath! ], with: UITableView.RowAnimation.automatic)
|
||||||
case .insert: tableView.insertRows(at: [ rowChange.newIndexPath! ], with: UITableView.RowAnimation.fade)
|
case .insert: tableView.insertRows(at: [ rowChange.newIndexPath! ], with: UITableView.RowAnimation.automatic)
|
||||||
case .move:
|
case .move: tableView.moveRow(at: rowChange.indexPath!, to: rowChange.newIndexPath!)
|
||||||
tableView.deleteRows(at: [ rowChange.indexPath! ], with: UITableView.RowAnimation.fade)
|
case .update: tableView.reloadRows(at: [ rowChange.indexPath! ], with: UITableView.RowAnimation.automatic)
|
||||||
tableView.insertRows(at: [ rowChange.newIndexPath! ], with: UITableView.RowAnimation.fade)
|
|
||||||
case .update:
|
|
||||||
tableView.reloadRows(at: [ rowChange.indexPath! ], with: UITableView.RowAnimation.none)
|
|
||||||
default: break
|
default: break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -268,24 +243,12 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, UIScrol
|
||||||
emptyStateView.isHidden = (threadCount != 0)
|
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) {
|
@objc private func handleProfileDidChangeNotification(_ notification: Notification) {
|
||||||
tableView.reloadData() // TODO: Just reload the affected cell
|
tableView.reloadData() // TODO: Just reload the affected cell
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func handleLocalProfileDidChangeNotification(_ notification: Notification) {
|
@objc private func handleLocalProfileDidChangeNotification(_ notification: Notification) {
|
||||||
updateNavigationBarButtons()
|
updateNavBarButtons()
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func handleSeedViewedNotification(_ notification: Notification) {
|
@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
|
self.tableView.reloadData() // TODO: Just reload the affected cell
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateNavigationBarButtons() {
|
private func updateNavBarButtons() {
|
||||||
let profilePictureSize = Values.verySmallProfilePictureSize
|
let profilePictureSize = Values.verySmallProfilePictureSize
|
||||||
let profilePictureView = ProfilePictureView()
|
let profilePictureView = ProfilePictureView()
|
||||||
profilePictureView.accessibilityLabel = "Settings button"
|
profilePictureView.accessibilityLabel = "Settings button"
|
||||||
|
@ -353,10 +316,6 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, UIScrol
|
||||||
present(navigationController, animated: true, completion: nil)
|
present(navigationController, animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
|
|
||||||
searchBar.resignFirstResponder()
|
|
||||||
}
|
|
||||||
|
|
||||||
func previewingContext(_ previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? {
|
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 }
|
guard let indexPath = tableView.indexPathForRow(at: location), let thread = self.thread(at: indexPath.row) else { return nil }
|
||||||
previewingContext.sourceRect = tableView.rectForRow(at: indexPath)
|
previewingContext.sourceRect = tableView.rectForRow(at: indexPath)
|
||||||
|
@ -465,8 +424,8 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, UIScrol
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func joinOpenGroup() {
|
@objc func joinOpenGroup() {
|
||||||
let joinPublicChatVC = JoinPublicChatVC()
|
let joinOpenGroupVC = JoinPublicChatVC()
|
||||||
let navigationController = OWSNavigationController(rootViewController: joinPublicChatVC)
|
let navigationController = OWSNavigationController(rootViewController: joinOpenGroupVC)
|
||||||
present(navigationController, animated: true, completion: nil)
|
present(navigationController, animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -485,8 +444,9 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, UIScrol
|
||||||
// MARK: Convenience
|
// MARK: Convenience
|
||||||
private func thread(at index: Int) -> TSThread? {
|
private func thread(at index: Int) -> TSThread? {
|
||||||
var thread: TSThread? = nil
|
var thread: TSThread? = nil
|
||||||
uiDatabaseConnection.read { transaction in
|
dbConnection.read { transaction in
|
||||||
thread = ((transaction as YapDatabaseReadTransaction).ext(TSThreadDatabaseViewExtensionName) as! YapDatabaseViewTransaction).object(atRow: UInt(index), inSection: 0, with: self.threads) as! TSThread?
|
let ext = transaction.ext(TSThreadDatabaseViewExtensionName) as! YapDatabaseViewTransaction
|
||||||
|
thread = ext.object(atRow: UInt(index), inSection: 0, with: self.threads) as! TSThread?
|
||||||
}
|
}
|
||||||
return thread
|
return thread
|
||||||
}
|
}
|
||||||
|
@ -497,7 +457,7 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, UIScrol
|
||||||
return cachedThreadViewModel
|
return cachedThreadViewModel
|
||||||
} else {
|
} else {
|
||||||
var threadViewModel: ThreadViewModel? = nil
|
var threadViewModel: ThreadViewModel? = nil
|
||||||
uiDatabaseConnection.read { transaction in
|
dbConnection.read { transaction in
|
||||||
threadViewModel = ThreadViewModel(thread: thread, transaction: transaction)
|
threadViewModel = ThreadViewModel(thread: thread, transaction: transaction)
|
||||||
}
|
}
|
||||||
threadViewModelCache[thread.uniqueId!] = threadViewModel
|
threadViewModelCache[thread.uniqueId!] = threadViewModel
|
||||||
|
|
|
@ -599,24 +599,6 @@ class PhotoCaptureOutputAdaptee: NSObject, ImageCaptureOutput {
|
||||||
}
|
}
|
||||||
completion()
|
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"
|
return "AVCaptureVideoOrientation.landscapeRight"
|
||||||
case .landscapeLeft:
|
case .landscapeLeft:
|
||||||
return "AVCaptureVideoOrientation.landscapeLeft"
|
return "AVCaptureVideoOrientation.landscapeLeft"
|
||||||
|
default: preconditionFailure()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -708,6 +691,7 @@ extension UIDeviceOrientation: CustomStringConvertible {
|
||||||
return "UIDeviceOrientation.faceUp"
|
return "UIDeviceOrientation.faceUp"
|
||||||
case .faceDown:
|
case .faceDown:
|
||||||
return "UIDeviceOrientation.faceDown"
|
return "UIDeviceOrientation.faceDown"
|
||||||
|
default: preconditionFailure()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -731,6 +715,7 @@ extension UIImage.Orientation: CustomStringConvertible {
|
||||||
return "UIImageOrientation.leftMirrored"
|
return "UIImageOrientation.leftMirrored"
|
||||||
case .rightMirrored:
|
case .rightMirrored:
|
||||||
return "UIImageOrientation.rightMirrored"
|
return "UIImageOrientation.rightMirrored"
|
||||||
|
default: preconditionFailure()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -330,6 +330,7 @@ class PhotoCaptureViewController: OWSViewController {
|
||||||
imageName = "ic_flash_mode_on"
|
imageName = "ic_flash_mode_on"
|
||||||
case .off:
|
case .off:
|
||||||
imageName = "ic_flash_mode_off"
|
imageName = "ic_flash_mode_off"
|
||||||
|
default: preconditionFailure()
|
||||||
}
|
}
|
||||||
|
|
||||||
self.flashModeControl.setImage(imageName: imageName)
|
self.flashModeControl.setImage(imageName: imageName)
|
||||||
|
@ -520,6 +521,7 @@ class CaptureButton: UIView {
|
||||||
self.superview?.layoutIfNeeded()
|
self.superview?.layoutIfNeeded()
|
||||||
}
|
}
|
||||||
delegate?.didCancelLongPressCaptureButton(self)
|
delegate?.didCancelLongPressCaptureButton(self)
|
||||||
|
default: break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -470,6 +470,8 @@ static NSTimeInterval launchStartedAt;
|
||||||
// Only mark the app as ready once
|
// Only mark the app as ready once
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[SNConfiguration performMainSetup];
|
||||||
|
|
||||||
// TODO: Once "app ready" logic is moved into AppSetup, move this line there.
|
// TODO: Once "app ready" logic is moved into AppSetup, move this line there.
|
||||||
[self.profileManager ensureLocalProfileCached];
|
[self.profileManager ensureLocalProfileCached];
|
||||||
|
|
|
@ -65,7 +65,7 @@
|
||||||
<key>NSContactsUsageDescription</key>
|
<key>NSContactsUsageDescription</key>
|
||||||
<string>Signal uses your contacts to find users you know. We do not store your contacts on the server.</string>
|
<string>Signal uses your contacts to find users you know. We do not store your contacts on the server.</string>
|
||||||
<key>NSFaceIDUsageDescription</key>
|
<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>
|
<key>NSMicrophoneUsageDescription</key>
|
||||||
<string>Session needs access to your microphone to record media.</string>
|
<string>Session needs access to your microphone to record media.</string>
|
||||||
<key>NSPhotoLibraryAddUsageDescription</key>
|
<key>NSPhotoLibraryAddUsageDescription</key>
|
||||||
|
|
|
@ -2638,7 +2638,7 @@
|
||||||
"vc_create_closed_group_empty_state_button_title" = "Session starten";
|
"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_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_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_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";
|
"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_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_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_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_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";
|
"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_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_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_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_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";
|
"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_empty_state_button_title" = "شروع Session";
|
||||||
"vc_create_closed_group_group_name_missing_error" = "لطفا یک نام گروه وارد کنید";
|
"vc_create_closed_group_group_name_missing_error" = "لطفا یک نام گروه وارد کنید";
|
||||||
"vc_create_closed_group_group_name_too_long_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_create_closed_group_invalid_session_id_error" = "یکی از اعضای گروه شما دارای شناسه نامعتبر است";
|
||||||
|
|
||||||
"vc_join_public_chat_title" = "به گروه باز بپیوندید";
|
"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_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_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_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_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";
|
"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_missing_error" = "Masukkan nama grup";
|
||||||
"vc_create_closed_group_group_name_too_long_error" = "Masukkan nama grup yang lebih pendek";
|
"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_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_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";
|
"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_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_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_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_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";
|
"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_missing_error" = "グループ名を入力してください";
|
||||||
"vc_create_closed_group_group_name_too_long_error" = "短いグループ名を入力してください";
|
"vc_create_closed_group_group_name_too_long_error" = "短いグループ名を入力してください";
|
||||||
"vc_create_closed_group_not_enough_group_members_error" = "グループメンバーを少なくとも 2 人選択してください";
|
"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_create_closed_group_invalid_session_id_error" = "グループのメンバーの 1 人の Session ID が無効です";
|
||||||
|
|
||||||
"vc_join_public_chat_title" = "オープングループに参加する";
|
"vc_join_public_chat_title" = "オープングループに参加する";
|
||||||
|
|
|
@ -2638,7 +2638,7 @@
|
||||||
"vc_create_closed_group_empty_state_button_title" = "Rozpocznij sesję";
|
"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_missing_error" = "Wpisz nazwę grupy";
|
||||||
"vc_create_closed_group_group_name_too_long_error" = "Wprowadź krótszą 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_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";
|
"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_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_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_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_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";
|
"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_empty_state_button_title" = "Начать Сессию";
|
||||||
"vc_create_closed_group_group_name_missing_error" = "Пожалуйста, введите название группы";
|
"vc_create_closed_group_group_name_missing_error" = "Пожалуйста, введите название группы";
|
||||||
"vc_create_closed_group_group_name_too_long_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_create_closed_group_invalid_session_id_error" = "Один из участников вашей группы имеет недопустимый Session ID";
|
||||||
|
|
||||||
"vc_join_public_chat_title" = "Присоединиться к открытой группе";
|
"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_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_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_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_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ở";
|
"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_empty_state_button_title" = "开始对话";
|
||||||
"vc_create_closed_group_group_name_missing_error" = "请输入群组名称";
|
"vc_create_closed_group_group_name_missing_error" = "请输入群组名称";
|
||||||
"vc_create_closed_group_group_name_too_long_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_create_closed_group_invalid_session_id_error" = "您群组中的一位成员的Session ID无效";
|
||||||
|
|
||||||
"vc_join_public_chat_title" = "加入公开群组";
|
"vc_join_public_chat_title" = "加入公开群组";
|
||||||
|
|
|
@ -21,6 +21,7 @@ class AppUpdateNag: NSObject {
|
||||||
public func showAppUpgradeNagIfNecessary() {
|
public func showAppUpgradeNagIfNecessary() {
|
||||||
return
|
return
|
||||||
|
|
||||||
|
/*
|
||||||
guard let currentVersion = self.currentVersion else {
|
guard let currentVersion = self.currentVersion else {
|
||||||
owsFailDebug("currentVersion was unexpectedly nil")
|
owsFailDebug("currentVersion was unexpectedly nil")
|
||||||
return
|
return
|
||||||
|
@ -49,6 +50,7 @@ class AppUpdateNag: NSObject {
|
||||||
}.catch { error in
|
}.catch { error in
|
||||||
Logger.error("failed with error: \(error)")
|
Logger.error("failed with error: \(error)")
|
||||||
}.retainUntilComplete()
|
}.retainUntilComplete()
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Internal
|
// 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
|
// Only show nag if we are "at rest" in the home view or registration view without any
|
||||||
// alerts or dialogs showing.
|
// alerts or dialogs showing.
|
||||||
guard let frontmostViewController = UIApplication.shared.frontmostViewController else {
|
guard UIApplication.shared.frontmostViewController != nil else {
|
||||||
owsFailDebug("frontmostViewController was unexpectedly nil")
|
owsFailDebug("frontmostViewController was unexpectedly nil")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -180,14 +180,18 @@ extension Storage {
|
||||||
(transaction as! YapDatabaseReadWriteTransaction).setObject(newValue, forKey: openGroupID, inCollection: Storage.openGroupUserCountCollection)
|
(transaction as! YapDatabaseReadWriteTransaction).setObject(newValue, forKey: openGroupID, inCollection: Storage.openGroupUserCountCollection)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func getIDForMessage(withServerID serverID: UInt64) -> UInt64? {
|
public func getIDForMessage(withServerID serverID: UInt64) -> String? {
|
||||||
var result: UInt64? = nil
|
var result: String? = nil
|
||||||
Storage.read { transaction in
|
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
|
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) {
|
public func setOpenGroupDisplayName(to displayName: String, for publicKey: String, inOpenGroupWithID openGroupID: String, using transaction: Any) {
|
||||||
let collection = openGroupID
|
let collection = openGroupID
|
||||||
(transaction as! YapDatabaseReadWriteTransaction).setObject(displayName, forKey: publicKey, inCollection: collection)
|
(transaction as! YapDatabaseReadWriteTransaction).setObject(displayName, forKey: publicKey, inCollection: collection)
|
||||||
|
|
|
@ -218,13 +218,13 @@ public final class OpenGroupAPI : DotNetAPI {
|
||||||
|
|
||||||
@objc(deleteMessageWithID:forGroup:onServer:isSentByUser:)
|
@objc(deleteMessageWithID:forGroup:onServer:isSentByUser:)
|
||||||
public static func objc_deleteMessage(with messageID: UInt, for group: UInt64, on server: String, isSentByUser: Bool) -> AnyPromise {
|
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> {
|
public static func deleteMessage(with messageID: UInt, for channel: UInt64, on server: String, wasSentByUser: Bool) -> Promise<Void> {
|
||||||
let isModerationRequest = !isSentByUser
|
let isModerationRequest = !wasSentByUser
|
||||||
SNLog("Deleting message with ID: \(messageID) for open group channel with ID: \(channel) on server: \(server) (isModerationRequest = \(isModerationRequest)).")
|
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)) {
|
return attempt(maxRetryCount: maxRetryCount, recoveringOn: DispatchQueue.global(qos: .default)) {
|
||||||
getOpenGroupServerPublicKey(for: server).then(on: DispatchQueue.global(qos: .default)) { serverPublicKey in
|
getOpenGroupServerPublicKey(for: server).then(on: DispatchQueue.global(qos: .default)) { serverPublicKey in
|
||||||
getAuthToken(for: server).then(on: DispatchQueue.global(qos: .default)) { token -> Promise<Void> 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)
|
}.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
|
// MARK: Display Name & Profile Picture
|
||||||
public static func getDisplayNames(for channel: UInt64, on server: String) -> Promise<Void> {
|
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)
|
case let message as VisibleMessage: try handleVisibleMessage(message, associatedWithProto: proto, openGroupID: openGroupID, isBackgroundPoll: isBackgroundPoll, using: transaction)
|
||||||
default: fatalError()
|
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) {
|
private static func handleReadReceipt(_ message: ReadReceipt, using transaction: Any) {
|
||||||
|
@ -226,6 +232,10 @@ extension MessageReceiver {
|
||||||
if isMainAppAndActive {
|
if isMainAppAndActive {
|
||||||
cancelTypingIndicatorsIfNeeded(for: message.sender!)
|
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
|
// Notify the user if needed
|
||||||
guard (isMainAppAndActive || isBackgroundPoll), let tsIncomingMessage = TSMessage.fetch(uniqueId: tsMessageID, transaction: transaction) as? TSIncomingMessage,
|
guard (isMainAppAndActive || isBackgroundPoll), let tsIncomingMessage = TSMessage.fetch(uniqueId: tsMessageID, transaction: transaction) as? TSIncomingMessage,
|
||||||
let thread = TSThread.fetch(uniqueId: threadID, transaction: transaction) else { return tsMessageID }
|
let thread = TSThread.fetch(uniqueId: threadID, transaction: transaction) else { return tsMessageID }
|
||||||
|
@ -289,7 +299,7 @@ extension MessageReceiver {
|
||||||
let group = thread.groupModel
|
let group = thread.groupModel
|
||||||
let oldMembers = group.groupMemberIds
|
let oldMembers = group.groupMemberIds
|
||||||
// Check that the message isn't from before the group was created
|
// 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.")
|
return SNLog("Ignoring closed group update from before thread was created.")
|
||||||
}
|
}
|
||||||
// Check that the sender is a member of the group (before the update)
|
// 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
|
// Serialize the protobuf
|
||||||
let plaintext: Data
|
let plaintext: Data
|
||||||
do {
|
do {
|
||||||
plaintext = try proto.serializedData()
|
plaintext = (try proto.serializedData() as NSData).paddedMessageBody()
|
||||||
} catch {
|
} catch {
|
||||||
SNLog("Couldn't serialize proto due to error: \(error).")
|
SNLog("Couldn't serialize proto due to error: \(error).")
|
||||||
handleFailure(with: error, using: transaction)
|
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
|
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 }
|
guard let tsMessage = TSOutgoingMessage.find(withTimestamp: message.sentTimestamp!) else { return }
|
||||||
tsMessage.openGroupServerMessageID = message.openGroupServerMessageID ?? 0
|
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! ]
|
var recipients = [ message.recipient! ]
|
||||||
if case .closedGroup(_) = destination, let threadID = message.threadID, // threadID should always be set at this point
|
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 = thread.groupModel.groupMemberIds
|
||||||
}
|
}
|
||||||
recipients.forEach { recipient in
|
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:
|
// Sync the message if:
|
||||||
// • it wasn't a self-send
|
// • it wasn't a self-send
|
||||||
// • it was a visible message
|
// • it was a visible message
|
||||||
|
|
|
@ -19,7 +19,7 @@ public final class OpenGroupPoller : NSObject {
|
||||||
|
|
||||||
// MARK: Settings
|
// MARK: Settings
|
||||||
private let pollForNewMessagesInterval: TimeInterval = 4
|
private let pollForNewMessagesInterval: TimeInterval = 4
|
||||||
private let pollForDeletedMessagesInterval: TimeInterval = 60
|
private let pollForDeletedMessagesInterval: TimeInterval = 30
|
||||||
private let pollForModeratorsInterval: TimeInterval = 10 * 60
|
private let pollForModeratorsInterval: TimeInterval = 10 * 60
|
||||||
|
|
||||||
// MARK: Lifecycle
|
// MARK: Lifecycle
|
||||||
|
@ -193,9 +193,10 @@ public final class OpenGroupPoller : NSObject {
|
||||||
let openGroup = self.openGroup
|
let openGroup = self.openGroup
|
||||||
let _ = OpenGroupAPI.getDeletedMessageServerIDs(for: openGroup.channel, on: openGroup.server).done(on: DispatchQueue.global(qos: .default)) { deletedMessageServerIDs in
|
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)) }
|
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
|
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
|
// MARK: - Open Group Metadata
|
||||||
|
|
||||||
func setUserCount(to newValue: Int, forOpenGroupWithID openGroupID: String, using transaction: Any)
|
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 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
|
func setLastProfilePictureUploadDate(_ date: Date) // Stored in user defaults so no transaction is needed
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,6 @@ import UserNotifications
|
||||||
import SessionMessagingKit
|
import SessionMessagingKit
|
||||||
import SignalUtilitiesKit
|
import SignalUtilitiesKit
|
||||||
|
|
||||||
// TODO: Group notifications
|
|
||||||
|
|
||||||
public final class NotificationServiceExtension : UNNotificationServiceExtension {
|
public final class NotificationServiceExtension : UNNotificationServiceExtension {
|
||||||
private var didPerformSetup = false
|
private var didPerformSetup = false
|
||||||
private var areVersionMigrationsComplete = 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
|
// Avoid blocking app launch by putting all further possible DB access in async block
|
||||||
DispatchQueue.global().async { [weak self] in
|
DispatchQueue.global().async { [weak self] in
|
||||||
guard let _ = self else { return }
|
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.
|
// We don't need to use OWSDisappearingMessagesJob in the SAE.
|
||||||
|
|
||||||
|
@ -199,7 +199,7 @@ public class ShareViewController: UIViewController, ShareViewDelegate, SAEFailed
|
||||||
if tsAccountManager.isRegistered() {
|
if tsAccountManager.isRegistered() {
|
||||||
DispatchQueue.main.async { [weak self] in
|
DispatchQueue.main.async { [weak self] in
|
||||||
guard let _ = self else { return }
|
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.
|
// We don't need to use the TSSocketManager in the SAE.
|
||||||
|
|
||||||
|
@ -258,7 +258,7 @@ public class ShareViewController: UIViewController, ShareViewDelegate, SAEFailed
|
||||||
AppReadiness.setAppIsReady()
|
AppReadiness.setAppIsReady()
|
||||||
|
|
||||||
if tsAccountManager.isRegistered() {
|
if tsAccountManager.isRegistered() {
|
||||||
Logger.info("localNumber: \(TSAccountManager.localNumber)")
|
Logger.info("localNumber: \(TSAccountManager.localNumber())")
|
||||||
|
|
||||||
// We don't need to use messageFetcherJob in the SAE.
|
// We don't need to use messageFetcherJob in the SAE.
|
||||||
|
|
||||||
|
@ -290,7 +290,7 @@ public class ShareViewController: UIViewController, ShareViewDelegate, SAEFailed
|
||||||
Logger.debug("")
|
Logger.debug("")
|
||||||
|
|
||||||
if tsAccountManager.isRegistered() {
|
if tsAccountManager.isRegistered() {
|
||||||
Logger.info("localNumber: \(TSAccountManager.localNumber)")
|
Logger.info("localNumber: \(TSAccountManager.localNumber())")
|
||||||
|
|
||||||
// We don't need to use ExperienceUpgradeFinder in the SAE.
|
// We don't need to use ExperienceUpgradeFinder in the SAE.
|
||||||
|
|
||||||
|
@ -659,12 +659,8 @@ public class ShareViewController: UIViewController, ShareViewDelegate, SAEFailed
|
||||||
var visualMediaItemProviders = [NSItemProvider]()
|
var visualMediaItemProviders = [NSItemProvider]()
|
||||||
var hasNonVisualMedia = false
|
var hasNonVisualMedia = false
|
||||||
for attachment in attachments {
|
for attachment in attachments {
|
||||||
guard let itemProvider = attachment as? NSItemProvider else {
|
if isVisualMediaItem(itemProvider: attachment) {
|
||||||
owsFailDebug("Unexpected attachment type: \(String(describing: attachment))")
|
visualMediaItemProviders.append(attachment)
|
||||||
continue
|
|
||||||
}
|
|
||||||
if isVisualMediaItem(itemProvider: itemProvider) {
|
|
||||||
visualMediaItemProviders.append(itemProvider)
|
|
||||||
} else {
|
} else {
|
||||||
hasNonVisualMedia = true
|
hasNonVisualMedia = true
|
||||||
}
|
}
|
||||||
|
@ -690,15 +686,11 @@ public class ShareViewController: UIViewController, ShareViewDelegate, SAEFailed
|
||||||
}
|
}
|
||||||
return isUrlItem(itemProvider: itemProvider)
|
return isUrlItem(itemProvider: itemProvider)
|
||||||
}) {
|
}) {
|
||||||
if let itemProvider = preferredAttachment as? NSItemProvider {
|
return [preferredAttachment]
|
||||||
return [itemProvider]
|
|
||||||
} else {
|
|
||||||
owsFailDebug("Unexpected attachment type: \(String(describing: preferredAttachment))")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// else return whatever is available
|
// else return whatever is available
|
||||||
if let itemProvider = inputItem.attachments?.first as? NSItemProvider {
|
if let itemProvider = inputItem.attachments?.first {
|
||||||
return [itemProvider]
|
return [itemProvider]
|
||||||
} else {
|
} else {
|
||||||
owsFailDebug("Missing attachment.")
|
owsFailDebug("Missing attachment.")
|
||||||
|
|
|
@ -10,22 +10,38 @@ FOUNDATION_EXPORT const unsigned char SignalUtilitiesKitVersionString[];
|
||||||
|
|
||||||
#import <SignalUtilitiesKit/AppSetup.h>
|
#import <SignalUtilitiesKit/AppSetup.h>
|
||||||
#import <SignalUtilitiesKit/AppVersion.h>
|
#import <SignalUtilitiesKit/AppVersion.h>
|
||||||
|
#import <SignalUtilitiesKit/AttachmentSharing.h>
|
||||||
#import <SignalUtilitiesKit/BlockListUIUtils.h>
|
#import <SignalUtilitiesKit/BlockListUIUtils.h>
|
||||||
|
#import <SignalUtilitiesKit/ByteParser.h>
|
||||||
|
#import <SignalUtilitiesKit/ContactCellView.h>
|
||||||
|
#import <SignalUtilitiesKit/ContactTableViewCell.h>
|
||||||
#import <SignalUtilitiesKit/DebugLogger.h>
|
#import <SignalUtilitiesKit/DebugLogger.h>
|
||||||
|
#import <SignalUtilitiesKit/FunctionalUtil.h>
|
||||||
|
#import <SignalUtilitiesKit/NSArray+OWS.h>
|
||||||
#import <SignalUtilitiesKit/NSAttributedString+OWS.h>
|
#import <SignalUtilitiesKit/NSAttributedString+OWS.h>
|
||||||
|
#import <SignalUtilitiesKit/NSObject+Casting.h>
|
||||||
|
#import <SignalUtilitiesKit/NSSet+Functional.h>
|
||||||
#import <SignalUtilitiesKit/NSURLSessionDataTask+StatusCode.h>
|
#import <SignalUtilitiesKit/NSURLSessionDataTask+StatusCode.h>
|
||||||
#import <SignalUtilitiesKit/OWSAnyTouchGestureRecognizer.h>
|
#import <SignalUtilitiesKit/OWSAnyTouchGestureRecognizer.h>
|
||||||
#import <SignalUtilitiesKit/OWSAttachmentDownloads.h>
|
#import <SignalUtilitiesKit/OWSAttachmentDownloads.h>
|
||||||
#import <SignalUtilitiesKit/OWSDatabaseMigration.h>
|
#import <SignalUtilitiesKit/OWSDatabaseMigration.h>
|
||||||
|
#import <SignalUtilitiesKit/OWSDatabaseMigrationRunner.h>
|
||||||
#import <SignalUtilitiesKit/OWSDispatch.h>
|
#import <SignalUtilitiesKit/OWSDispatch.h>
|
||||||
#import <SignalUtilitiesKit/OWSError.h>
|
#import <SignalUtilitiesKit/OWSError.h>
|
||||||
|
#import <SignalUtilitiesKit/OWSFailedAttachmentDownloadsJob.h>
|
||||||
|
#import <SignalUtilitiesKit/OWSFailedMessagesJob.h>
|
||||||
#import <SignalUtilitiesKit/OWSFormat.h>
|
#import <SignalUtilitiesKit/OWSFormat.h>
|
||||||
#import <SignalUtilitiesKit/OWSMessageUtils.h>
|
#import <SignalUtilitiesKit/OWSMessageUtils.h>
|
||||||
#import <SignalUtilitiesKit/OWSNavigationController.h>
|
#import <SignalUtilitiesKit/OWSNavigationController.h>
|
||||||
#import <SignalUtilitiesKit/OWSOperation.h>
|
#import <SignalUtilitiesKit/OWSOperation.h>
|
||||||
|
#import <SignalUtilitiesKit/OWSPrimaryStorage+keyFromIntLong.h>
|
||||||
#import <SignalUtilitiesKit/OWSPrimaryStorage+Loki.h>
|
#import <SignalUtilitiesKit/OWSPrimaryStorage+Loki.h>
|
||||||
#import <SignalUtilitiesKit/OWSProfileManager.h>
|
#import <SignalUtilitiesKit/OWSProfileManager.h>
|
||||||
|
#import <SignalUtilitiesKit/OWSQueues.h>
|
||||||
|
#import <SignalUtilitiesKit/OWSResaveCollectionDBMigration.h>
|
||||||
|
#import <SignalUtilitiesKit/OWSScrubbingLogFormatter.h>
|
||||||
#import <SignalUtilitiesKit/OWSSearchBar.h>
|
#import <SignalUtilitiesKit/OWSSearchBar.h>
|
||||||
|
#import <SignalUtilitiesKit/OWSTableViewController.h>
|
||||||
#import <SignalUtilitiesKit/OWSTextField.h>
|
#import <SignalUtilitiesKit/OWSTextField.h>
|
||||||
#import <SignalUtilitiesKit/OWSTextView.h>
|
#import <SignalUtilitiesKit/OWSTextView.h>
|
||||||
#import <SignalUtilitiesKit/OWSUnreadIndicator.h>
|
#import <SignalUtilitiesKit/OWSUnreadIndicator.h>
|
||||||
|
@ -37,7 +53,10 @@ FOUNDATION_EXPORT const unsigned char SignalUtilitiesKitVersionString[];
|
||||||
#import <SignalUtilitiesKit/SSKAsserts.h>
|
#import <SignalUtilitiesKit/SSKAsserts.h>
|
||||||
#import <SignalUtilitiesKit/Theme.h>
|
#import <SignalUtilitiesKit/Theme.h>
|
||||||
#import <SignalUtilitiesKit/ThreadUtil.h>
|
#import <SignalUtilitiesKit/ThreadUtil.h>
|
||||||
|
#import <SignalUtilitiesKit/ThreadViewHelper.h>
|
||||||
#import <SignalUtilitiesKit/TSConstants.h>
|
#import <SignalUtilitiesKit/TSConstants.h>
|
||||||
|
#import <SignalUtilitiesKit/TSStorageHeaders.h>
|
||||||
|
#import <SignalUtilitiesKit/TSStorageKeys.h>
|
||||||
#import <SignalUtilitiesKit/UIFont+OWS.h>
|
#import <SignalUtilitiesKit/UIFont+OWS.h>
|
||||||
#import <SignalUtilitiesKit/UIUtil.h>
|
#import <SignalUtilitiesKit/UIUtil.h>
|
||||||
#import <SignalUtilitiesKit/UIViewController+OWS.h>
|
#import <SignalUtilitiesKit/UIViewController+OWS.h>
|
||||||
|
|
|
@ -4,14 +4,15 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import MediaPlayer
|
import MediaPlayer
|
||||||
|
import SessionUIKit
|
||||||
|
|
||||||
// A modal view that be used during blocking interactions (e.g. waiting on response from
|
// 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).
|
// service or on the completion of a long-running local operation).
|
||||||
@objc
|
@objc
|
||||||
public class ModalActivityIndicatorViewController: OWSViewController {
|
public class ModalActivityIndicatorViewController: OWSViewController {
|
||||||
|
|
||||||
let canCancel: Bool
|
let canCancel: Bool
|
||||||
|
|
||||||
|
let message: String?
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
public var wasCancelled: Bool = false
|
public var wasCancelled: Bool = false
|
||||||
|
@ -29,25 +30,26 @@ public class ModalActivityIndicatorViewController: OWSViewController {
|
||||||
notImplemented()
|
notImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
public required init(canCancel: Bool) {
|
public required init(canCancel: Bool = false, message: String? = nil) {
|
||||||
self.canCancel = canCancel
|
self.canCancel = canCancel
|
||||||
|
self.message = message
|
||||||
super.init(nibName: nil, bundle: nil)
|
super.init(nibName: nil, bundle: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
public class func present(fromViewController: UIViewController,
|
public class func present(fromViewController: UIViewController, canCancel: Bool = false, message: String? = nil,
|
||||||
canCancel: Bool, backgroundBlock : @escaping (ModalActivityIndicatorViewController) -> Void) {
|
backgroundBlock : @escaping (ModalActivityIndicatorViewController) -> Void) {
|
||||||
AssertIsOnMainThread()
|
AssertIsOnMainThread()
|
||||||
|
|
||||||
let view = ModalActivityIndicatorViewController(canCancel: canCancel)
|
let view = ModalActivityIndicatorViewController(canCancel: canCancel, message: message)
|
||||||
// Present this modal _over_ the current view contents.
|
// Present this modal _over_ the current view contents.
|
||||||
view.modalPresentationStyle = .overFullScreen
|
view.modalPresentationStyle = .overFullScreen
|
||||||
view.modalTransitionStyle = .crossDissolve
|
view.modalTransitionStyle = .crossDissolve
|
||||||
fromViewController.present(view,
|
fromViewController.present(view, animated: false) {
|
||||||
animated: false) {
|
DispatchQueue.global().async {
|
||||||
DispatchQueue.global().async {
|
backgroundBlock(view)
|
||||||
backgroundBlock(view)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,15 +72,31 @@ public class ModalActivityIndicatorViewController: OWSViewController {
|
||||||
public override func loadView() {
|
public override func loadView() {
|
||||||
super.loadView()
|
super.loadView()
|
||||||
|
|
||||||
self.view.backgroundColor = (Theme.isDarkThemeEnabled
|
self.view.backgroundColor = UIColor(white: 0, alpha: 0.5)
|
||||||
? UIColor(white: 0, alpha: 0.5)
|
|
||||||
: UIColor(white: 0, alpha: 0.5))
|
|
||||||
self.view.isOpaque = false
|
self.view.isOpaque = false
|
||||||
|
|
||||||
let activityIndicator = UIActivityIndicatorView(style: .whiteLarge)
|
let activityIndicator = UIActivityIndicatorView(style: .whiteLarge)
|
||||||
self.activityIndicator = activityIndicator
|
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 {
|
if canCancel {
|
||||||
let cancelButton = UIButton(type: .custom)
|
let cancelButton = UIButton(type: .custom)
|
||||||
|
@ -152,7 +170,6 @@ public class ModalActivityIndicatorViewController: OWSViewController {
|
||||||
|
|
||||||
wasCancelled = true
|
wasCancelled = true
|
||||||
|
|
||||||
dismiss {
|
dismiss { }
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue