From 7cfd43ff6b8edd46d3618743e69f088a95878a84 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Wed, 22 Jan 2020 09:53:29 +1100 Subject: [PATCH] Implement group member count, fix QR code scanning & clean --- .../SessionWhite.imageset/Contents.json | 12 +++++ .../SESSION_ICON_WHT.pdf | Bin 0 -> 1802 bytes .../Components/ConversationTitleView.swift | 25 +++++++-- Signal/src/Loki/View Controllers/HomeVC.swift | 3 ++ .../View Controllers/JoinPublicChatVC.swift | 1 + .../OWSQRCodeScanningViewController.m | 7 ++- .../ConversationViewController.m | 17 ++++++- SignalMessaging/Loki/ProfilePictureView.swift | 21 ++------ .../API/Public Chat/LokiPublicChatAPI.swift | 48 ++++++++++++++++++ .../Public Chat/LokiPublicChatManager.swift | 11 ---- .../Database/OWSPrimaryStorage+Loki.swift | 8 +++ 11 files changed, 117 insertions(+), 36 deletions(-) create mode 100644 Signal/Images.xcassets/Loki V2/SessionWhite.imageset/Contents.json create mode 100644 Signal/Images.xcassets/Loki V2/SessionWhite.imageset/SESSION_ICON_WHT.pdf diff --git a/Signal/Images.xcassets/Loki V2/SessionWhite.imageset/Contents.json b/Signal/Images.xcassets/Loki V2/SessionWhite.imageset/Contents.json new file mode 100644 index 000000000..77e922959 --- /dev/null +++ b/Signal/Images.xcassets/Loki V2/SessionWhite.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "SESSION_ICON_WHT.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Signal/Images.xcassets/Loki V2/SessionWhite.imageset/SESSION_ICON_WHT.pdf b/Signal/Images.xcassets/Loki V2/SessionWhite.imageset/SESSION_ICON_WHT.pdf new file mode 100644 index 0000000000000000000000000000000000000000..f448b1dcfafd59d690847d65157c86d3213da9f8 GIT binary patch literal 1802 zcmb7EeNYr-97fWVMJkkYDo1_GD5zlfy}jGx5V5%P5P`Vv@FNpr*;|fd&f8;e4?(7q z85KxDbR78u3P=PZKO)94W=JViOejB+NktpRFh}zX1wYcP_g(Jb6#wa+o!xz(_v88f zp7;Gdp+>zS0!FHc(7o4oKOj_q0zyVM5fcN*B;H|_vVa;vCISFJ8f6V@4Rb7jYC>o8EUK^Sk6moKdV(_i* zkma{p{R?043$PI93EpAxtVe&C8Pnk}B2?3O7t0+M6V8-<1jhTr7Ul#FbiljJ#h3}A3lcnm!r4&?|3AXD?5JRps%l?R~!%A7`4 z))~iM`Z?Um)LLT@KK@^}VI+9{&tyT?anu(+-HKn9Mn!HvgI3%>#1+cusTE>fl z+vyYjFr$omGcuz%MAqSQVrDMi3nu_cbo1Vx4$ac@xi*eB#m8aLldp+)32u?&T>xum z^2rmYjy3f;;;50Zo_54o-Qn;&B4QfS?9Py6l?;NoQbyu@#ZVy?G@kTFW6^kH8M!mO zIdOLgoP6$5h_-NG-Y+o9|K*a-3(bDl$@i~oF6aCIoN>0NC#Uu3eYNGzjk~=MZ6BVhf7I@qGP^JY*P(SOu1z4)i5nGcSvekHJX zZTs^X8%r1LY@iyKcD>=eb2I0e^}*?9v}HckY+{N3E3Om0z zp=jxwQT4;Zp})C^$pZ`H`^LT6{pThHSHE?_wM*IO+BXl5UpB=~TyD@;Ts<_syzmU6 zjlEx7<@fpR_ew%)19E58kQ1ao40AWz&5cEKv|gl)>@F{aTZ z^F%(Apa6sfHPQg1R?}*b2|O57GDvCMfWwPXN(MQ^6Bv97MnPoNPtH?~#Z=lc91v>9 za6qU*?)dnAkfxD)J%OoIKJz4zwb^-5HZ!wrHIMVf?qm`KbYxh!VY^LmWC{Quo9u)x Ti4`STAgI!6h|tjaDF)&{QHM%l literal 0 HcmV?d00001 diff --git a/Signal/src/Loki/Components/ConversationTitleView.swift b/Signal/src/Loki/Components/ConversationTitleView.swift index 9f1f6eed9..2f94bfb65 100644 --- a/Signal/src/Loki/Components/ConversationTitleView.swift +++ b/Signal/src/Loki/Components/ConversationTitleView.swift @@ -1,5 +1,6 @@ -@objc final class ConversationTitleView : UIView { +@objc(LKConversationTitleView) +final class ConversationTitleView : UIView { private let thread: TSThread private var currentStatus: Status? { didSet { updateSubtitleForCurrentStatus() } } @@ -141,7 +142,7 @@ self.currentStatus = nil } - private func updateSubtitleForCurrentStatus() { + @objc func updateSubtitleForCurrentStatus() { DispatchQueue.main.async { self.subtitleLabel.isHidden = false switch self.currentStatus { @@ -159,13 +160,29 @@ dateFormatter.timeStyle = .medium dateFormatter.dateStyle = .medium subtitle.append(NSAttributedString(string: "Muted until " + dateFormatter.string(from: muteEndDate))) - } else if self.thread.isGroupThread() { - subtitle.append(NSAttributedString(string: "26 members")) // TODO: Implement + } else if self.thread.isGroupThread() && !(self.thread as! TSGroupThread).isRSSFeed { + let storage = OWSPrimaryStorage.shared() + var userCount: Int? + storage.dbReadConnection.readWrite { transaction in + if let publicChat = LokiDatabaseUtilities.getPublicChat(for: self.thread.uniqueId!, in: transaction) { + userCount = storage.getUserCount(for: publicChat, in: transaction) + } + } + if let userCount = userCount { + if userCount > 2500 { + subtitle.append(NSAttributedString(string: "2500+ members")) + } else { + subtitle.append(NSAttributedString(string: "\(userCount) members")) + } + } else { + self.subtitleLabel.isHidden = true + } } else { self.subtitleLabel.isHidden = true } self.subtitleLabel.attributedText = subtitle } + self.titleLabel.font = .boldSystemFont(ofSize: self.subtitleLabel.isHidden ? Values.veryLargeFontSize : Values.mediumFontSize) } } diff --git a/Signal/src/Loki/View Controllers/HomeVC.swift b/Signal/src/Loki/View Controllers/HomeVC.swift index 4d000f48e..9213bb1fc 100644 --- a/Signal/src/Loki/View Controllers/HomeVC.swift +++ b/Signal/src/Loki/View Controllers/HomeVC.swift @@ -326,6 +326,9 @@ final class HomeVC : UIViewController, UITableViewDataSource, UITableViewDelegat thread.remove(with: transaction) } NotificationCenter.default.post(name: .threadDeleted, object: nil, userInfo: [ "threadId" : thread.uniqueId! ]) + if let publicChat = publicChat { + let _ = LokiPublicChatAPI.leave(publicChat.channel, on: publicChat.server) + } }) alert.addAction(UIAlertAction(title: NSLocalizedString("TXT_CANCEL_TITLE", comment: ""), style: .default) { _ in }) guard let self = self else { return } diff --git a/Signal/src/Loki/View Controllers/JoinPublicChatVC.swift b/Signal/src/Loki/View Controllers/JoinPublicChatVC.swift index 8bf15a96a..92c8056e7 100644 --- a/Signal/src/Loki/View Controllers/JoinPublicChatVC.swift +++ b/Signal/src/Loki/View Controllers/JoinPublicChatVC.swift @@ -153,6 +153,7 @@ final class JoinPublicChatVC : UIViewController, UIPageViewControllerDataSource, .done(on: .main) { [weak self] _ in let _ = LokiPublicChatAPI.getMessages(for: channelID, on: urlAsString) let _ = LokiPublicChatAPI.setDisplayName(to: displayName, on: urlAsString) + let _ = LokiPublicChatAPI.join(channelID, on: urlAsString) self?.presentingViewController!.dismiss(animated: true, completion: nil) } .catch(on: .main) { [weak self] _ in diff --git a/Signal/src/ViewControllers/AppSettings/OWSQRCodeScanningViewController.m b/Signal/src/ViewControllers/AppSettings/OWSQRCodeScanningViewController.m index fd8b2a485..d0ef000dc 100644 --- a/Signal/src/ViewControllers/AppSettings/OWSQRCodeScanningViewController.m +++ b/Signal/src/ViewControllers/AppSettings/OWSQRCodeScanningViewController.m @@ -14,6 +14,7 @@ NS_ASSUME_NONNULL_BEGIN @property (atomic) ZXCapture *capture; @property (nonatomic) BOOL captureEnabled; @property (nonatomic) UIView *maskingView; +@property (nonatomic) dispatch_queue_t captureQueue; @end @@ -34,6 +35,7 @@ NS_ASSUME_NONNULL_BEGIN } _captureEnabled = NO; + _captureQueue = dispatch_get_main_queue(); return self; } @@ -46,6 +48,7 @@ NS_ASSUME_NONNULL_BEGIN } _captureEnabled = NO; + _captureQueue = dispatch_get_main_queue(); return self; } @@ -102,7 +105,7 @@ NS_ASSUME_NONNULL_BEGIN { self.captureEnabled = YES; if (!self.capture) { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + dispatch_async(self.captureQueue, ^{ self.capture = [[ZXCapture alloc] init]; // self.capture.invert = YES; self.capture.camera = self.capture.back; @@ -124,7 +127,7 @@ NS_ASSUME_NONNULL_BEGIN - (void)stopCapture { self.captureEnabled = NO; - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + dispatch_async(self.captureQueue, ^{ [self.capture stop]; }); } diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m index 89b9881ff..6203e3f9f 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m @@ -160,7 +160,7 @@ typedef enum : NSUInteger { @property (nonatomic, nullable) NSTimer *readTimer; @property (nonatomic) NSCache *cellMediaCache; -@property (nonatomic) ConversationTitleView *headerView; +@property (nonatomic) LKConversationTitleView *headerView; @property (nonatomic, nullable) UIView *bannerView; @property (nonatomic, nullable) OWSDisappearingMessagesConfiguration *disappearingMessagesConfiguration; @@ -662,6 +662,19 @@ typedef enum : NSUInteger { UIBarButtonItem *settingsButton = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"Gear"] style:UIBarButtonItemStylePlain target:self action:@selector(showConversationSettings)]; settingsButton.tintColor = LKColors.text; self.navigationItem.rightBarButtonItem = settingsButton; + + if (self.thread.isGroupThread) { + TSGroupThread *thread = (TSGroupThread *)self.thread; + if (thread.isRSSFeed) { return; } + __block LKPublicChat *publicChat; + [OWSPrimaryStorage.sharedManager.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { + publicChat = [LKDatabaseUtilities getPublicChatForThreadID:thread.uniqueId transaction:transaction]; + }]; + [LKPublicChatAPI getUserCountForGroup:publicChat.channel onServer:publicChat.server] + .thenOn(dispatch_get_main_queue(), ^(id userCount) { + [self.headerView updateSubtitleForCurrentStatus]; + }); + } } - (void)createContents @@ -1410,7 +1423,7 @@ typedef enum : NSUInteger { - (void)createHeaderViews { - ConversationTitleView *headerView = [[ConversationTitleView alloc] initWithThread:self.thread]; + LKConversationTitleView *headerView = [[LKConversationTitleView alloc] initWithThread:self.thread]; self.headerView = headerView; SET_SUBVIEW_ACCESSIBILITY_IDENTIFIER(self, headerView); diff --git a/SignalMessaging/Loki/ProfilePictureView.swift b/SignalMessaging/Loki/ProfilePictureView.swift index 3e8bec9d2..9855cb3e2 100644 --- a/SignalMessaging/Loki/ProfilePictureView.swift +++ b/SignalMessaging/Loki/ProfilePictureView.swift @@ -12,15 +12,6 @@ public final class ProfilePictureView : UIView { private lazy var imageView = getImageView() private lazy var additionalImageView = getImageView() - private lazy var rssLabel: UILabel = { - let result = UILabel() - result.textColor = UIColor(rgbHex: 0xFFFFFF) // Colors.text - result.font = .systemFont(ofSize: 13) // Values.smallFontSize - result.textAlignment = .center - result.text = "RSS" - return result - }() - // MARK: Lifecycle public override init(frame: CGRect) { super.init(frame: frame) @@ -45,12 +36,6 @@ public final class ProfilePictureView : UIView { additionalImageView.set(.width, to: additionalImageViewSize) additionalImageView.set(.height, to: additionalImageViewSize) additionalImageView.layer.cornerRadius = additionalImageViewSize / 2 - // Set up RSS label - addSubview(rssLabel) - rssLabel.pin(.leading, to: .leading, of: self) - rssLabel.pin(.top, to: .top, of: self) - rssLabel.autoPinWidth(toWidthOf: imageView) - rssLabel.autoPinHeight(toHeightOf: imageView) } // MARK: Updating @@ -80,8 +65,10 @@ public final class ProfilePictureView : UIView { imageView.image = isRSSFeed ? nil : getProfilePicture(of: size, for: hexEncodedPublicKey) imageView.backgroundColor = isRSSFeed ? UIColor(rgbHex: 0x353535) : UIColor(rgbHex: 0xD8D8D8) // UIColor(rgbHex: 0xD8D8D8) = Colors.unimportant imageView.layer.cornerRadius = size / 2 - rssLabel.isHidden = !isRSSFeed - rssLabel.font = size == (75) ? .systemFont(ofSize: 20) : .systemFont(ofSize: 13) // Values.largeProfilePictureSize / Values.largeFontSize / Values.smallFontSize + imageView.contentMode = isRSSFeed ? .center : .scaleAspectFit + if isRSSFeed { + imageView.image = #imageLiteral(resourceName: "SessionWhite").resizedImage(to: CGSize(width: (303*24)/337, height: 24)) + } } // MARK: Convenience diff --git a/SignalServiceKit/src/Loki/API/Public Chat/LokiPublicChatAPI.swift b/SignalServiceKit/src/Loki/API/Public Chat/LokiPublicChatAPI.swift index 76a8b7e3d..5e99e451c 100644 --- a/SignalServiceKit/src/Loki/API/Public Chat/LokiPublicChatAPI.swift +++ b/SignalServiceKit/src/Loki/API/Public Chat/LokiPublicChatAPI.swift @@ -242,6 +242,49 @@ public final class LokiPublicChatAPI : LokiDotNetAPI { } } + public static func join(_ channel: UInt64, on server: String) -> Promise { + return getAuthToken(for: server).then(on: DispatchQueue.global()) { token -> Promise in + let url = URL(string: "\(server)/channels/\(channel)/subscribe")! + let request = TSRequest(url: url, method: "POST", parameters: [:]) + request.allHTTPHeaderFields = [ "Content-Type" : "application/json", "Authorization" : "Bearer \(token)" ] + return TSNetworkManager.shared().perform(request, withCompletionQueue: DispatchQueue.global()).done(on: DispatchQueue.global()) { result -> Void in + print("[Loki] Joined channel with ID: \(channel) on server: \(server).") + }.retryingIfNeeded(maxRetryCount: maxRetryCount) + } + } + + public static func leave(_ channel: UInt64, on server: String) -> Promise { + return getAuthToken(for: server).then(on: DispatchQueue.global()) { token -> Promise in + let url = URL(string: "\(server)/channels/\(channel)/subscribe")! + let request = TSRequest(url: url, method: "DELETE", parameters: [:]) + request.allHTTPHeaderFields = [ "Content-Type" : "application/json", "Authorization" : "Bearer \(token)" ] + return TSNetworkManager.shared().perform(request, withCompletionQueue: DispatchQueue.global()).done(on: DispatchQueue.global()) { result -> Void in + print("[Loki] Left channel with ID: \(channel) on server: \(server).") + }.retryingIfNeeded(maxRetryCount: maxRetryCount) + } + } + + public static func getUserCount(for channel: UInt64, on server: String) -> Promise { + return getAuthToken(for: server).then(on: DispatchQueue.global()) { token -> Promise in + let queryParameters = "count=2500" + let url = URL(string: "\(server)/channels/\(channel)/subscribers?\(queryParameters)")! + let request = TSRequest(url: url) + request.allHTTPHeaderFields = [ "Content-Type" : "application/json", "Authorization" : "Bearer \(token)" ] + return TSNetworkManager.shared().perform(request, withCompletionQueue: DispatchQueue.global()).map { $0.responseObject }.map { rawResponse in + guard let json = rawResponse as? JSON, let users = json["data"] as? [JSON] else { + print("[Loki] Couldn't parse user count for public chat channel with ID: \(channel) on server: \(server) from: \(rawResponse).") + throw Error.parsingFailed + } + let userCount = users.count + let storage = OWSPrimaryStorage.shared() + storage.dbReadWriteConnection.readWrite { transaction in + storage.setUserCount(userCount, forPublicChatWithID: "\(server).\(channel)", in: transaction) + } + return userCount + } + } + } + public static func getDisplayNames(for channel: UInt64, on server: String) -> Promise { let publicChatID = "\(server).\(channel)" guard let hexEncodedPublicKeys = displayNameUpdatees[publicChatID] else { return Promise.value(()) } @@ -344,6 +387,11 @@ public final class LokiPublicChatAPI : LokiDotNetAPI { return AnyPromise.from(deleteMessage(with: messageID, for: group, on: server, isSentByUser: isSentByUser)) } + @objc(getUserCountForGroup:onServer:) + public static func objc_getUserCount(for group: UInt64, on server: String) -> AnyPromise { + return AnyPromise.from(getUserCount(for: group, on: server)) + } + @objc(setDisplayName:on:) public static func objc_setDisplayName(to newDisplayName: String?, on server: String) -> AnyPromise { return AnyPromise.from(setDisplayName(to: newDisplayName, on: server)) diff --git a/SignalServiceKit/src/Loki/API/Public Chat/LokiPublicChatManager.swift b/SignalServiceKit/src/Loki/API/Public Chat/LokiPublicChatManager.swift index 074939404..7ee844325 100644 --- a/SignalServiceKit/src/Loki/API/Public Chat/LokiPublicChatManager.swift +++ b/SignalServiceKit/src/Loki/API/Public Chat/LokiPublicChatManager.swift @@ -71,17 +71,6 @@ public final class LokiPublicChatManager : NSObject { // Store the group chat mapping self.storage.dbReadWriteConnection.readWrite { transaction in let thread = TSGroupThread.getOrCreateThread(with: model, transaction: transaction) - - // Mute the thread - if let utc = TimeZone(identifier: "UTC") { - var calendar = Calendar.current - calendar.timeZone = utc - var dateComponents = DateComponents() - dateComponents.setValue(999, for: .year) - if let date = calendar.date(byAdding: dateComponents, to: Date()) { - thread.updateWithMuted(until: date, transaction: transaction) - } - } // Save the group chat LokiDatabaseUtilities.setPublicChat(chat, for: thread.uniqueId!, in: transaction) diff --git a/SignalServiceKit/src/Loki/Database/OWSPrimaryStorage+Loki.swift b/SignalServiceKit/src/Loki/Database/OWSPrimaryStorage+Loki.swift index f8565cd75..007d319c6 100644 --- a/SignalServiceKit/src/Loki/Database/OWSPrimaryStorage+Loki.swift +++ b/SignalServiceKit/src/Loki/Database/OWSPrimaryStorage+Loki.swift @@ -51,4 +51,12 @@ public extension OWSPrimaryStorage { public func getMasterHexEncodedPublicKey(for slaveHexEncodedPublicKey: String, in transaction: YapDatabaseReadTransaction) -> String? { return getDeviceLink(for: slaveHexEncodedPublicKey, in: transaction)?.master.hexEncodedPublicKey } + + public func getUserCount(for publicChat: LokiPublicChat, in transaction: YapDatabaseReadTransaction) -> Int? { + return transaction.object(forKey: publicChat.id, inCollection: "LokiPublicChatUserCountCollection") as? Int + } + + public func setUserCount(_ userCount: Int, forPublicChatWithID publicChatID: String, in transaction: YapDatabaseReadWriteTransaction) { + transaction.setObject(userCount, forKey: publicChatID, inCollection: "LokiPublicChatUserCountCollection") + } }