Hook up suggestion grid

This commit is contained in:
Niels Andriesse 2021-03-26 13:28:40 +11:00
parent 053f581bb2
commit a7dd7e1bf0
7 changed files with 164 additions and 42 deletions

View File

@ -153,6 +153,8 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, NewConv
DispatchQueue.global(qos: .utility).async {
let _ = IP2Country.shared.populateCacheIfNeeded()
}
// Get default open group rooms if needed
OpenGroupAPIV2.getDefaultRoomsIfNeeded()
}
override func viewDidAppear(_ animated: Bool) {

View File

@ -396,7 +396,7 @@ static NSTimeInterval launchStartedAt;
[self startClosedGroupPollerIfNeeded];
[self startOpenGroupPollersIfNeeded];
// Loki: Update profile picture if needed
// Update profile picture if needed
NSUserDefaults *userDefaults = NSUserDefaults.standardUserDefaults;
NSDate *now = [NSDate new];
NSDate *lastProfilePictureUpload = (NSDate *)[userDefaults objectForKey:@"lastProfilePictureUpload"];
@ -410,6 +410,10 @@ static NSTimeInterval launchStartedAt;
// Do nothing
} requiresSync:YES];
}
if (CurrentAppContext().isMainApp) {
[SNOpenGroupAPIV2 getDefaultRoomsIfNeeded];
}
if (![UIApplication sharedApplication].isRegisteredForRemoteNotifications) {
OWSLogInfo(@"Retrying remote notification registration since user hasn't registered yet.");

View File

@ -149,6 +149,28 @@ final class JoinOpenGroupVC : BaseVC, UIPageViewControllerDataSource, UIPageView
}
}
fileprivate func join(_ room: String, on server: String, with publicKey: String) {
guard !isJoining else { return }
isJoining = true
ModalActivityIndicatorViewController.present(fromViewController: navigationController!, canCancel: false) { [weak self] _ in
Storage.shared.write { transaction in
OpenGroupManagerV2.shared.add(room: room, server: server, publicKey: publicKey, using: transaction)
.done(on: DispatchQueue.main) { [weak self] _ in
self?.presentingViewController!.dismiss(animated: true, completion: nil)
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.forceSyncConfigurationNowIfNeeded().retainUntilComplete() // FIXME: It's probably cleaner to do this inside addOpenGroup(...)
}
.catch(on: DispatchQueue.main) { [weak self] error in
self?.dismiss(animated: true, completion: nil) // Dismiss the loader
let title = "Couldn't Join"
let message = error.localizedDescription
self?.isJoining = false
self?.showError(title: title, message: message)
}
}
}
}
// MARK: Convenience
private func showError(title: String, message: String = "") {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
@ -157,7 +179,7 @@ final class JoinOpenGroupVC : BaseVC, UIPageViewControllerDataSource, UIPageView
}
}
private final class EnterURLVC : UIViewController {
private final class EnterURLVC : UIViewController, OpenGroupSuggestionGridDelegate {
weak var joinOpenGroupVC: JoinOpenGroupVC!
// MARK: Components
@ -170,13 +192,22 @@ private final class EnterURLVC : UIViewController {
private lazy var suggestionGrid: OpenGroupSuggestionGrid = {
let maxWidth = UIScreen.main.bounds.width - Values.largeSpacing * 2
return OpenGroupSuggestionGrid(maxWidth: maxWidth)
let result = OpenGroupSuggestionGrid(maxWidth: maxWidth)
result.delegate = self
return result
}()
// MARK: Lifecycle
override func viewDidLoad() {
// Remove background color
view.backgroundColor = .clear
// Suggestion grid title label
let suggestionGridTitleLabel = UILabel()
suggestionGridTitleLabel.textColor = Colors.text
suggestionGridTitleLabel.font = .boldSystemFont(ofSize: Values.largeFontSize)
suggestionGridTitleLabel.text = "Or join one of these..."
suggestionGridTitleLabel.numberOfLines = 0
suggestionGridTitleLabel.lineBreakMode = .byWordWrapping
// Next button
let nextButton = Button(style: .prominentOutline, size: .large)
nextButton.setTitle(NSLocalizedString("next", comment: ""), for: UIControl.State.normal)
@ -187,11 +218,9 @@ private final class EnterURLVC : UIViewController {
nextButton.pin(.top, to: .top, of: nextButtonContainer)
nextButtonContainer.pin(.trailing, to: .trailing, of: nextButton, withInset: 80)
nextButtonContainer.pin(.bottom, to: .bottom, of: nextButton)
// Spacers
let spacer1 = UIView.vStretchingSpacer()
let spacer2 = UIView.vStretchingSpacer()
// Stack view
let stackView = UIStackView(arrangedSubviews: [ urlTextField, spacer1, suggestionGrid, spacer2, nextButtonContainer ])
let stackView = UIStackView(arrangedSubviews: [ urlTextField, UIView.spacer(withHeight: Values.mediumSpacing), suggestionGridTitleLabel,
UIView.spacer(withHeight: Values.mediumSpacing), suggestionGrid, UIView.vStretchingSpacer(), nextButtonContainer ])
stackView.axis = .vertical
stackView.alignment = .fill
stackView.layoutMargins = UIEdgeInsets(uniform: Values.largeSpacing)
@ -200,7 +229,6 @@ private final class EnterURLVC : UIViewController {
stackView.pin(to: view)
// Constraints
view.set(.width, to: UIScreen.main.bounds.width)
spacer1.heightAnchor.constraint(equalTo: spacer2.heightAnchor).isActive = true
// Dismiss keyboard on tap
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
view.addGestureRecognizer(tapGestureRecognizer)
@ -216,6 +244,10 @@ private final class EnterURLVC : UIViewController {
}
// MARK: Interaction
func join(_ room: OpenGroupAPIV2.Info) {
joinOpenGroupVC.join(room.id, on: OpenGroupAPIV2.defaultServer, with: OpenGroupAPIV2.defaultServerPublicKey)
}
@objc private func joinOpenGroupIfPossible() {
let url = urlTextField.text?.trimmingCharacters(in: .whitespaces) ?? ""
joinOpenGroupVC.joinOpenGroupIfPossible(with: url)

View File

@ -1,8 +1,11 @@
import PromiseKit
import NVActivityIndicatorView
final class OpenGroupSuggestionGrid : UIView, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
private let maxWidth: CGFloat
private var rooms: [OpenGroupAPIV2.Info] = [] { didSet { reload() } }
private var rooms: [OpenGroupAPIV2.Info] = [] { didSet { update() } }
private var heightConstraint: NSLayoutConstraint!
var delegate: OpenGroupSuggestionGridDelegate?
// MARK: UI Components
private lazy var layout: UICollectionViewFlowLayout = {
@ -22,6 +25,13 @@ final class OpenGroupSuggestionGrid : UIView, UICollectionViewDataSource, UIColl
return result
}()
private lazy var spinner: NVActivityIndicatorView = {
let result = NVActivityIndicatorView(frame: CGRect.zero, type: .circleStrokeSpin, color: Colors.text, padding: nil)
result.set(.width, to: 64)
result.set(.height, to: 64)
return result
}()
// MARK: Settings
private static let cellHeight: CGFloat = 40
private static let separatorWidth = 1 / UIScreen.main.scale
@ -44,43 +54,59 @@ final class OpenGroupSuggestionGrid : UIView, UICollectionViewDataSource, UIColl
private func initialize() {
addSubview(collectionView)
collectionView.pin(to: self)
heightConstraint = set(.height, to: 0)
attempt(maxRetryCount: 8, recoveringOn: DispatchQueue.main) {
return OpenGroupAPIV2.getAllRooms(from: "https://sessionopengroup.com").done { [weak self] rooms in
self?.rooms = rooms
}
}.retainUntilComplete()
addSubview(spinner)
spinner.pin([ UIView.HorizontalEdge.left, UIView.VerticalEdge.top ], to: self)
spinner.startAnimating()
heightConstraint = set(.height, to: 64)
widthAnchor.constraint(greaterThanOrEqualToConstant: 64).isActive = true
let _ = OpenGroupAPIV2.getDefaultRoomsPromise?.done { [weak self] rooms in
self?.rooms = rooms
}
}
// MARK: Updating
private func reload() {
let height = OpenGroupSuggestionGrid.cellHeight * ceil(CGFloat(rooms.count) / 3)
private func update() {
spinner.stopAnimating()
spinner.isHidden = true
let height = OpenGroupSuggestionGrid.cellHeight * ceil(CGFloat(rooms.count) / 2)
heightConstraint.constant = height
collectionView.reloadData()
}
// MARK: Layout
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: maxWidth / 3, height: OpenGroupSuggestionGrid.cellHeight)
return CGSize(width: maxWidth / 2, height: OpenGroupSuggestionGrid.cellHeight)
}
// MARK: Data Source
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return min(rooms.count, 12) // Cap to a maximum of 12 (4 rows of 3)
return min(rooms.count, 8) // Cap to a maximum of 8 (4 rows of 2)
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: Cell.identifier, for: indexPath) as! Cell
cell.showRightSeparator = (indexPath.row % 2 != 0) || (indexPath.row % 2 == 0 && indexPath.row == rooms.count - 1)
cell.showBottomSeparator = (indexPath.row >= rooms.count - 2)
cell.room = rooms[indexPath.item]
return cell
}
// MARK: Interaction
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let room = rooms[indexPath.item]
delegate?.join(room)
}
}
// MARK: Cell
extension OpenGroupSuggestionGrid {
fileprivate final class Cell : UICollectionViewCell {
var showRightSeparator = false
var showBottomSeparator = false
var room: OpenGroupAPIV2.Info? { didSet { update() } }
private var rightSeparator: UIView!
private var bottomSeparator: UIView!
static let identifier = "OpenGroupSuggestionGridCell"
@ -107,11 +133,47 @@ extension OpenGroupSuggestionGrid {
label.center(in: self)
label.leadingAnchor.constraint(greaterThanOrEqualTo: leadingAnchor, constant: Values.smallSpacing).isActive = true
trailingAnchor.constraint(greaterThanOrEqualTo: label.trailingAnchor, constant: Values.smallSpacing).isActive = true
setUpSeparators()
}
private func setUpSeparators() {
func getVSeparator() -> UIView {
let separator = UIView()
separator.backgroundColor = Colors.separator
separator.set(.height, to: 1 / UIScreen.main.scale)
return separator
}
func getHSeparator() -> UIView {
let separator = UIView()
separator.backgroundColor = Colors.separator
separator.set(.width, to: 1 / UIScreen.main.scale)
return separator
}
let leftSeparator = getHSeparator()
addSubview(leftSeparator)
leftSeparator.pin([ UIView.HorizontalEdge.left, UIView.VerticalEdge.top, UIView.VerticalEdge.bottom ], to: self)
let topSeparator = getVSeparator()
addSubview(topSeparator)
topSeparator.pin([ UIView.HorizontalEdge.left, UIView.VerticalEdge.top, UIView.HorizontalEdge.right ], to: self)
rightSeparator = getHSeparator()
addSubview(rightSeparator)
rightSeparator.pin([ UIView.VerticalEdge.top, UIView.HorizontalEdge.right, UIView.VerticalEdge.bottom ], to: self)
bottomSeparator = getVSeparator()
addSubview(bottomSeparator)
bottomSeparator.pin([ UIView.HorizontalEdge.left, UIView.VerticalEdge.bottom, UIView.HorizontalEdge.right ], to: self)
}
private func update() {
guard let room = room else { return }
label.text = room.name
rightSeparator.alpha = showRightSeparator ? 1 :0
bottomSeparator.alpha = showBottomSeparator ? 1 :0
}
}
}
// MARK: Delegate
protocol OpenGroupSuggestionGridDelegate {
func join(_ room: OpenGroupAPIV2.Info)
}

View File

@ -11,4 +11,9 @@ extension OpenGroupAPIV2 {
public static func objc_isUserModerator(_ publicKey: String, for room: String, on server: String) -> Bool {
return isUserModerator(publicKey, for: room, on: server)
}
@objc(getDefaultRoomsIfNeeded)
public static func objc_getDefaultRoomsIfNeeded() {
return getDefaultRoomsIfNeeded()
}
}

View File

@ -8,6 +8,9 @@ import SessionSnodeKit
@objc(SNOpenGroupAPIV2)
public final class OpenGroupAPIV2 : NSObject {
private static var moderators: [String:[String:Set<String>]] = [:] // Server URL to room ID to set of moderator IDs
public static let defaultServer = "https://sessionopengroup.com"
public static let defaultServerPublicKey = "658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231b"
public static var getDefaultRoomsPromise: Promise<[Info]>?
// MARK: Error
public enum Error : LocalizedError {
@ -280,6 +283,16 @@ public final class OpenGroupAPIV2 : NSObject {
}
// MARK: General
public static func getDefaultRoomsIfNeeded() {
Storage.shared.write(with: { transaction in
Storage.shared.setOpenGroupPublicKey(for: defaultServer, to: defaultServerPublicKey, using: transaction)
}, completion: {
getDefaultRoomsPromise = attempt(maxRetryCount: 8, recoveringOn: DispatchQueue.main) {
OpenGroupAPIV2.getAllRooms(from: defaultServer)
}
})
}
public static func getInfo(for room: String, on server: String) -> Promise<Info> {
let request = Request(verb: .get, room: room, server: server, endpoint: "rooms/\(room)", isAuthRequired: false)
let promise: Promise<Info> = send(request).map(on: DispatchQueue.global(qos: .userInitiated)) { json in

View File

@ -33,30 +33,34 @@ public final class OpenGroupManagerV2 : NSObject {
let storage = Storage.shared
storage.removeLastMessageServerID(for: room, on: server, using: transaction)
storage.removeLastDeletionServerID(for: room, on: server, using: transaction)
storage.setOpenGroupPublicKey(for: server, to: publicKey, using: transaction)
storage.removeAuthToken(for: room, on: server, using: transaction)
let (promise, seal) = Promise<Void>.pending()
OpenGroupAPIV2.getInfo(for: room, on: server).done(on: DispatchQueue.global(qos: .default)) { info in
let openGroup = OpenGroupV2(server: server, room: room, name: info.name, imageID: info.imageID)
let groupID = LKGroupUtilities.getEncodedOpenGroupIDAsData(openGroup.id)
let model = TSGroupModel(title: openGroup.name, memberIds: [ getUserHexEncodedPublicKey() ], image: nil, groupId: groupID, groupType: .openGroup, adminIds: [])
storage.write(with: { transaction in
let transaction = transaction as! YapDatabaseReadWriteTransaction
let thread = TSGroupThread.getOrCreateThread(with: model, transaction: transaction)
thread.shouldThreadBeVisible = true
thread.save(with: transaction)
storage.setV2OpenGroup(openGroup, for: thread.uniqueId!, using: transaction)
}, completion: {
if let poller = OpenGroupManagerV2.shared.pollers[openGroup.id] {
poller.stop()
OpenGroupManagerV2.shared.pollers[openGroup.id] = nil
}
let poller = OpenGroupPollerV2(for: openGroup)
poller.startIfNeeded()
OpenGroupManagerV2.shared.pollers[openGroup.id] = poller
seal.fulfill(())
})
}.catch(on: DispatchQueue.global(qos: .default)) { error in
seal.reject(error)
let transaction = transaction as! YapDatabaseReadWriteTransaction
transaction.addCompletionQueue(DispatchQueue.global(qos: .default)) {
storage.setOpenGroupPublicKey(for: server, to: publicKey, using: transaction)
OpenGroupAPIV2.getInfo(for: room, on: server).done(on: DispatchQueue.global(qos: .default)) { info in
let openGroup = OpenGroupV2(server: server, room: room, name: info.name, imageID: info.imageID)
let groupID = LKGroupUtilities.getEncodedOpenGroupIDAsData(openGroup.id)
let model = TSGroupModel(title: openGroup.name, memberIds: [ getUserHexEncodedPublicKey() ], image: nil, groupId: groupID, groupType: .openGroup, adminIds: [])
storage.write(with: { transaction in
let transaction = transaction as! YapDatabaseReadWriteTransaction
let thread = TSGroupThread.getOrCreateThread(with: model, transaction: transaction)
thread.shouldThreadBeVisible = true
thread.save(with: transaction)
storage.setV2OpenGroup(openGroup, for: thread.uniqueId!, using: transaction)
}, completion: {
if let poller = OpenGroupManagerV2.shared.pollers[openGroup.id] {
poller.stop()
OpenGroupManagerV2.shared.pollers[openGroup.id] = nil
}
let poller = OpenGroupPollerV2(for: openGroup)
poller.startIfNeeded()
OpenGroupManagerV2.shared.pollers[openGroup.id] = poller
seal.fulfill(())
})
}.catch(on: DispatchQueue.global(qos: .default)) { error in
seal.reject(error)
}
}
return promise
}