diff --git a/Riot.xcodeproj/project.pbxproj b/Riot.xcodeproj/project.pbxproj index 05d756727..c020cac98 100644 --- a/Riot.xcodeproj/project.pbxproj +++ b/Riot.xcodeproj/project.pbxproj @@ -760,6 +760,12 @@ EC3B066A24AC6ADE000DF9BF /* CrossSigningSetupBannerCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = EC3B066624AC6ADD000DF9BF /* CrossSigningSetupBannerCell.xib */; }; EC3B066B24AC6ADE000DF9BF /* CrossSigningBannerPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC3B066724AC6ADD000DF9BF /* CrossSigningBannerPreferences.swift */; }; EC3B066C24AC6ADE000DF9BF /* CrossSigningSetupBannerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC3B066824AC6ADD000DF9BF /* CrossSigningSetupBannerCell.swift */; }; + EC60786F2507AC1100EA75C1 /* SearchableDirectoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC60786D2507AC1100EA75C1 /* SearchableDirectoryViewController.swift */; }; + EC6078702507AC1100EA75C1 /* SearchableDirectoryViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = EC60786E2507AC1100EA75C1 /* SearchableDirectoryViewController.storyboard */; }; + EC6078762507B07E00EA75C1 /* DirectoryRoomTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC6078742507B07E00EA75C1 /* DirectoryRoomTableViewCell.swift */; }; + EC6078772507B07E00EA75C1 /* DirectoryRoomTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = EC6078752507B07E00EA75C1 /* DirectoryRoomTableViewCell.xib */; }; + EC60787B2507B0D200EA75C1 /* DirectoryNetworkTableHeaderFooterView.xib in Resources */ = {isa = PBXBuildFile; fileRef = EC6078792507B0D200EA75C1 /* DirectoryNetworkTableHeaderFooterView.xib */; }; + EC60787D2507D30200EA75C1 /* DirectoryNetworkTableHeaderFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC60787C2507D30200EA75C1 /* DirectoryNetworkTableHeaderFooterView.swift */; }; EC619C1924DAD96000663A80 /* UIScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC619C1824DAD96000663A80 /* UIScrollView.swift */; }; EC711B4624A63B13008F830C /* MXRecoveryService.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC711B4524A63B13008F830C /* MXRecoveryService.swift */; }; EC711B7424A63B37008F830C /* SecretsSetupRecoveryKeyViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC711B4A24A63B36008F830C /* SecretsSetupRecoveryKeyViewModelType.swift */; }; @@ -1888,6 +1894,12 @@ EC3B066624AC6ADD000DF9BF /* CrossSigningSetupBannerCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = CrossSigningSetupBannerCell.xib; sourceTree = ""; }; EC3B066724AC6ADD000DF9BF /* CrossSigningBannerPreferences.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CrossSigningBannerPreferences.swift; sourceTree = ""; }; EC3B066824AC6ADD000DF9BF /* CrossSigningSetupBannerCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CrossSigningSetupBannerCell.swift; sourceTree = ""; }; + EC60786D2507AC1100EA75C1 /* SearchableDirectoryViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchableDirectoryViewController.swift; sourceTree = ""; }; + EC60786E2507AC1100EA75C1 /* SearchableDirectoryViewController.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = SearchableDirectoryViewController.storyboard; sourceTree = ""; }; + EC6078742507B07E00EA75C1 /* DirectoryRoomTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DirectoryRoomTableViewCell.swift; sourceTree = ""; }; + EC6078752507B07E00EA75C1 /* DirectoryRoomTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DirectoryRoomTableViewCell.xib; sourceTree = ""; }; + EC6078792507B0D200EA75C1 /* DirectoryNetworkTableHeaderFooterView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DirectoryNetworkTableHeaderFooterView.xib; sourceTree = ""; }; + EC60787C2507D30200EA75C1 /* DirectoryNetworkTableHeaderFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DirectoryNetworkTableHeaderFooterView.swift; sourceTree = ""; }; EC619C1824DAD96000663A80 /* UIScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIScrollView.swift; sourceTree = ""; }; EC711B4524A63B13008F830C /* MXRecoveryService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MXRecoveryService.swift; sourceTree = ""; }; EC711B4A24A63B36008F830C /* SecretsSetupRecoveryKeyViewModelType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecretsSetupRecoveryKeyViewModelType.swift; sourceTree = ""; }; @@ -3595,6 +3607,7 @@ children = ( B1B556F620EE6C4C00210D55 /* RoomsViewController.h */, B1B556F820EE6C4C00210D55 /* RoomsViewController.m */, + EC60786C2507AC1100EA75C1 /* SearchableDirectory */, B1B5577820EE75ED00210D55 /* DirectoryPicker */, ); path = Rooms; @@ -4644,6 +4657,43 @@ path = Banners; sourceTree = ""; }; + EC60786C2507AC1100EA75C1 /* SearchableDirectory */ = { + isa = PBXGroup; + children = ( + EC60786D2507AC1100EA75C1 /* SearchableDirectoryViewController.swift */, + EC60786E2507AC1100EA75C1 /* SearchableDirectoryViewController.storyboard */, + EC6078712507B04500EA75C1 /* Cells */, + ); + path = SearchableDirectory; + sourceTree = ""; + }; + EC6078712507B04500EA75C1 /* Cells */ = { + isa = PBXGroup; + children = ( + EC6078732507B05600EA75C1 /* Room */, + EC6078722507B04D00EA75C1 /* Directory */, + ); + path = Cells; + sourceTree = ""; + }; + EC6078722507B04D00EA75C1 /* Directory */ = { + isa = PBXGroup; + children = ( + EC60787C2507D30200EA75C1 /* DirectoryNetworkTableHeaderFooterView.swift */, + EC6078792507B0D200EA75C1 /* DirectoryNetworkTableHeaderFooterView.xib */, + ); + path = Directory; + sourceTree = ""; + }; + EC6078732507B05600EA75C1 /* Room */ = { + isa = PBXGroup; + children = ( + EC6078742507B07E00EA75C1 /* DirectoryRoomTableViewCell.swift */, + EC6078752507B07E00EA75C1 /* DirectoryRoomTableViewCell.xib */, + ); + path = Room; + sourceTree = ""; + }; EC711B4724A63B36008F830C /* Secrets */ = { isa = PBXGroup; children = ( @@ -5321,6 +5371,7 @@ EC711BAA24A63B58008F830C /* SecureBackupSetupIntroCell.xib in Resources */, F083BDE71E7009ED00A9B29C /* callend.mp3 in Resources */, B1B558EF20EF768F00210D55 /* RoomMembershipCollapsedWithPaginationTitleBubbleCell.xib in Resources */, + EC6078772507B07E00EA75C1 /* DirectoryRoomTableViewCell.xib in Resources */, EC711B7C24A63B37008F830C /* SecretsSetupRecoveryPassphraseViewController.storyboard in Resources */, B1B5573420EE6C4D00210D55 /* GroupDetailsViewController.xib in Resources */, B1B557E420EF60B900210D55 /* MessagesSearchResultAttachmentBubbleCell.xib in Resources */, @@ -5359,6 +5410,7 @@ B1E5368F21FB7258001F3AFF /* KeyBackupRecoverFromPassphraseViewController.storyboard in Resources */, B1B5590420EF768F00210D55 /* RoomOutgoingAttachmentBubbleCell.xib in Resources */, B1B558F120EF768F00210D55 /* RoomIncomingAttachmentWithPaginationTitleBubbleCell.xib in Resources */, + EC6078702507AC1100EA75C1 /* SearchableDirectoryViewController.storyboard in Resources */, B1B557CB20EF5D8000210D55 /* DirectoryServerTableViewCell.xib in Resources */, B1B558EC20EF768F00210D55 /* RoomMembershipCollapsedBubbleCell.xib in Resources */, EC711BAD24A63B58008F830C /* SecureBackupSetupIntroViewController.storyboard in Resources */, @@ -5452,6 +5504,7 @@ B1B5578520EF564900210D55 /* GroupTableViewCellWithSwitch.xib in Resources */, B1B557B320EF5AEF00210D55 /* EventDetailsView.xib in Resources */, B1963B30228F1C4900CBA17F /* BubbleReactionsView.xib in Resources */, + EC60787B2507B0D200EA75C1 /* DirectoryNetworkTableHeaderFooterView.xib in Resources */, B1B557DD20EF5FBB00210D55 /* FilesSearchTableViewCell.xib in Resources */, B1B5590320EF768F00210D55 /* RoomSelectedStickerBubbleCell.xib in Resources */, B1B5573F20EE6C4D00210D55 /* SegmentedViewController.xib in Resources */, @@ -6156,6 +6209,7 @@ B1B558CF20EF768F00210D55 /* RoomIncomingEncryptedTextMsgWithPaginationTitleBubbleCell.m in Sources */, B140B4A221F87F7100E3F5FE /* OperationQueue.swift in Sources */, B183226C23F59F810035B2E8 /* CloseButton.swift in Sources */, + EC6078762507B07E00EA75C1 /* DirectoryRoomTableViewCell.swift in Sources */, EC711B7F24A63B37008F830C /* SecretsSetupRecoveryPassphraseViewController.swift in Sources */, B1B5575120EE6C4D00210D55 /* AuthenticationViewController.m in Sources */, B1CE83BA2422815C00D07506 /* KeyVerificationService.swift in Sources */, @@ -6244,6 +6298,7 @@ EC3B066B24AC6ADE000DF9BF /* CrossSigningBannerPreferences.swift in Sources */, ECB101322477CFDB00CF8C11 /* UIDevice.swift in Sources */, B169330320F3C98900746532 /* RoomBubbleCellData.m in Sources */, + EC60786F2507AC1100EA75C1 /* SearchableDirectoryViewController.swift in Sources */, EC1CA86224C1DEC400DE9EBF /* EnterPinCodeViewState.swift in Sources */, EC711B8A24A63B37008F830C /* SecretsRecoveryWithPassphraseViewController.swift in Sources */, B1B336C3242B933700F95EC4 /* KeyVerificationSelfVerifyStartCoordinator.swift in Sources */, @@ -6335,6 +6390,7 @@ B157FAA523264AE900EBFBD4 /* SettingsDiscoveryThreePidDetailsViewModel.swift in Sources */, EC711B8624A63B37008F830C /* SecretsRecoveryWithPassphraseCoordinatorType.swift in Sources */, B1C562E8228C7CF20037F12A /* ContextualMenuItemView.swift in Sources */, + EC60787D2507D30200EA75C1 /* DirectoryNetworkTableHeaderFooterView.swift in Sources */, B14F143022144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyCoordinatorType.swift in Sources */, B1E5368921FB1E20001F3AFF /* UIButton.swift in Sources */, B1DCC63422E72C1B00625807 /* UISearchBar.swift in Sources */, diff --git a/Riot/Generated/Storyboards.swift b/Riot/Generated/Storyboards.swift index 32b8cc4d6..f0bd06b03 100644 --- a/Riot/Generated/Storyboards.swift +++ b/Riot/Generated/Storyboards.swift @@ -137,6 +137,13 @@ internal enum StoryboardScene { internal static let initialScene = InitialSceneType(storyboard: RoomContextualMenuViewController.self) } + internal enum SearchableDirectoryViewController: StoryboardType { + internal static let storyboardName = "SearchableDirectoryViewController" + + internal static let initialScene = InitialSceneType(storyboard: SearchableDirectoryViewController.self) + + internal static let searchableDirectoryViewController = SceneType(storyboard: SearchableDirectoryViewController.self, identifier: "SearchableDirectoryViewController") + } internal enum SecretsRecoveryWithKeyViewController: StoryboardType { internal static let storyboardName = "SecretsRecoveryWithKeyViewController" diff --git a/Riot/Modules/Rooms/SearchableDirectory/Cells/Directory/DirectoryNetworkTableHeaderFooterView.swift b/Riot/Modules/Rooms/SearchableDirectory/Cells/Directory/DirectoryNetworkTableHeaderFooterView.swift new file mode 100644 index 000000000..e15d5ce77 --- /dev/null +++ b/Riot/Modules/Rooms/SearchableDirectory/Cells/Directory/DirectoryNetworkTableHeaderFooterView.swift @@ -0,0 +1,62 @@ +// +// Copyright 2020 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import UIKit +import Reusable + +struct DirectoryNetworkVM { + var title: String? +} + +@objc protocol DirectoryNetworkTableHeaderFooterViewDelegate: NSObjectProtocol { + func directoryNetworkTableHeaderFooterViewDidTapSwitch(_ view: DirectoryNetworkTableHeaderFooterView) +} + +class DirectoryNetworkTableHeaderFooterView: UITableViewHeaderFooterView { + + @IBOutlet private weak var titleLabel: UILabel! + @IBOutlet private weak var switchButton: UIButton! { + didSet { + switchButton.setTitle(VectorL10n.switch, for: .normal) + } + } + + weak var delegate: DirectoryNetworkTableHeaderFooterViewDelegate? + + func configure(withViewModel viewModel: DirectoryNetworkVM) { + titleLabel.text = viewModel.title + } + + @IBAction private func switchButtonTapped(_ sender: UIButton) { + delegate?.directoryNetworkTableHeaderFooterViewDidTapSwitch(self) + } +} + +extension DirectoryNetworkTableHeaderFooterView: NibReusable {} + +extension DirectoryNetworkTableHeaderFooterView: Themable { + + func update(theme: Theme) { + // bg + let view = UIView() + view.backgroundColor = theme.backgroundColor + backgroundView = view + + titleLabel.textColor = theme.textSecondaryColor + theme.applyStyle(onButton: switchButton) + } + +} diff --git a/Riot/Modules/Rooms/SearchableDirectory/Cells/Directory/DirectoryNetworkTableHeaderFooterView.xib b/Riot/Modules/Rooms/SearchableDirectory/Cells/Directory/DirectoryNetworkTableHeaderFooterView.xib new file mode 100644 index 000000000..734d264f2 --- /dev/null +++ b/Riot/Modules/Rooms/SearchableDirectory/Cells/Directory/DirectoryNetworkTableHeaderFooterView.xib @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/Rooms/SearchableDirectory/Cells/Room/DirectoryRoomTableViewCell.swift b/Riot/Modules/Rooms/SearchableDirectory/Cells/Room/DirectoryRoomTableViewCell.swift new file mode 100644 index 000000000..96f07b723 --- /dev/null +++ b/Riot/Modules/Rooms/SearchableDirectory/Cells/Room/DirectoryRoomTableViewCell.swift @@ -0,0 +1,135 @@ +// +// Copyright 2020 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import UIKit +import Reusable + +class DirectoryRoomTableViewCell: UITableViewCell { + + @IBOutlet private weak var avatarImageView: MXKImageView! { + didSet { + avatarImageView.layer.cornerRadius = avatarImageView.frame.width/2 + avatarImageView.clipsToBounds = true + } + } + @IBOutlet private weak var displaynameLabel: UILabel! + @IBOutlet private weak var numberOfUsersLabel: UILabel! + @IBOutlet private weak var topicLabel: UILabel! + @IBOutlet private weak var joinButton: UIButton! + @IBOutlet private weak var joinActivityIndicator: UIActivityIndicatorView! + + private weak var room: MXPublicRoom! + private weak var session: MXSession! + private var isJoined: Bool = false { + didSet { + joinButton.setTitle(isJoined ? VectorL10n.joined : VectorL10n.join, for: .normal) + joinButton.isUserInteractionEnabled = !isJoined + joinActivityIndicator.isHidden = true + update(theme: ThemeService.shared().theme) + } + } + + func configure(withRoom room: MXPublicRoom, session: MXSession) { + self.room = room + self.session = session + + displaynameLabel.text = room.name + if displaynameLabel.text == nil { + displaynameLabel.text = room.aliases?.first + } + + if room.numJoinedMembers > 0 { + numberOfUsersLabel.isHidden = false + numberOfUsersLabel.text = String(room.numJoinedMembers) + } else { + numberOfUsersLabel.isHidden = true + } + + if let topic = room.topic { + topicLabel.text = MXTools.stripNewlineCharacters(topic) + topicLabel.isHidden = false + } else { + topicLabel.isHidden = true + } + + let avatarImage = AvatarGenerator.generateAvatar(forMatrixItem: room.roomId, withDisplayName: displaynameLabel.text) + + if let avatarUrl = room.avatarUrl { + avatarImageView.enableInMemoryCache = true + + avatarImageView.setImageURI(avatarUrl, + withType: nil, + andImageOrientation: .up, + toFitViewSize: avatarImageView.frame.size, + with: MXThumbnailingMethodCrop, + previewImage: avatarImage, + mediaManager: session.mediaManager) + } else { + avatarImageView.image = avatarImage + } + + avatarImageView.contentMode = .scaleAspectFill + + guard let summary = session.roomSummary(withRoomId: room.roomId) else { + isJoined = false + return + } + isJoined = summary.membership == .join + joinActivityIndicator.isHidden = true + } + + @IBAction private func joinButtonTapped(_ sender: UIButton) { + sender.setTitle(nil, for: .normal) + joinActivityIndicator.isHidden = false + session.joinRoom(room.roomId) { [weak self] (response) in + guard let self = self else { return } + switch response { + case .success: + self.isJoined = true + default: + self.isJoined = false + } + } + } + +} + +extension DirectoryRoomTableViewCell: NibReusable {} + +extension DirectoryRoomTableViewCell: Themable { + + func update(theme: Theme) { + backgroundView = UIView() + backgroundView?.backgroundColor = theme.backgroundColor + + displaynameLabel.textColor = theme.textPrimaryColor + numberOfUsersLabel.textColor = theme.textSecondaryColor + topicLabel.textColor = theme.textSecondaryColor + + if isJoined { + joinButton.backgroundColor = theme.backgroundColor + joinButton.tintColor = theme.textSecondaryColor + joinButton.layer.borderWidth = 1.0 + joinButton.layer.borderColor = theme.textSecondaryColor.cgColor + } else { + joinButton.backgroundColor = theme.tintColor + joinButton.tintColor = .white + joinButton.layer.borderWidth = 0.0 + joinButton.layer.borderColor = nil + } + } + +} diff --git a/Riot/Modules/Rooms/SearchableDirectory/Cells/Room/DirectoryRoomTableViewCell.xib b/Riot/Modules/Rooms/SearchableDirectory/Cells/Room/DirectoryRoomTableViewCell.xib new file mode 100644 index 000000000..80701b235 --- /dev/null +++ b/Riot/Modules/Rooms/SearchableDirectory/Cells/Room/DirectoryRoomTableViewCell.xib @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/Rooms/SearchableDirectory/SearchableDirectoryViewController.storyboard b/Riot/Modules/Rooms/SearchableDirectory/SearchableDirectoryViewController.storyboard new file mode 100644 index 000000000..8030405b1 --- /dev/null +++ b/Riot/Modules/Rooms/SearchableDirectory/SearchableDirectoryViewController.storyboard @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/Rooms/SearchableDirectory/SearchableDirectoryViewController.swift b/Riot/Modules/Rooms/SearchableDirectory/SearchableDirectoryViewController.swift new file mode 100644 index 000000000..784728163 --- /dev/null +++ b/Riot/Modules/Rooms/SearchableDirectory/SearchableDirectoryViewController.swift @@ -0,0 +1,328 @@ +// File created from simpleScreenTemplate +// $ createSimpleScreen.sh Rooms2 SearchableDirectory +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import UIKit + +@objc protocol SearchableDirectoryViewControllerDelegate: NSObjectProtocol { + func searchableDirectoryViewControllerDidCancel(_ viewController: SearchableDirectoryViewController) + func searchableDirectoryViewControllerDidSelect(_ viewController: SearchableDirectoryViewController, room: MXPublicRoom) + func searchableDirectoryViewControllerDidTapCreateNewRoom(_ viewController: SearchableDirectoryViewController) +} + +final class SearchableDirectoryViewController: MXKViewController { + + // MARK: - Properties + + // MARK: Outlets + + @IBOutlet private weak var mainTableView: UITableView! + @IBOutlet private weak var createRoomButton: UIButton! { + didSet { + createRoomButton.setTitle(VectorL10n.searchableDirectoryCreateNewRoom, for: .normal) + } + } + + // MARK: Private + + private var theme: Theme! + private var dataSource: PublicRoomsDirectoryDataSource! + private lazy var footerSpinnerView: UIActivityIndicatorView = { + let spinner = UIActivityIndicatorView(style: .whiteLarge) + spinner.transform = CGAffineTransform(scaleX: 0.75, y: 0.75) + spinner.color = .darkGray + spinner.hidesWhenStopped = false + spinner.backgroundColor = .clear + spinner.startAnimating() + return spinner + }() + private lazy var mainSearchBar: UISearchBar = { + let bar = UISearchBar(frame: CGRect(origin: .zero, size: CGSize(width: 600, height: 44))) + bar.autoresizingMask = .flexibleWidth + bar.showsCancelButton = false + bar.placeholder = VectorL10n.searchDefaultPlaceholder + bar.setBackgroundImage(UIImage.vc_image(from: .clear), for: .any, barMetrics: .default) + bar.delegate = self + return bar + }() + + // MARK: Public + + @objc weak var delegate: SearchableDirectoryViewControllerDelegate? + + @objc func display(withDataSource dataSource: PublicRoomsDirectoryDataSource) { + self.dataSource = dataSource + self.dataSource.delegate = self + if isViewLoaded { + self.mainTableView.reloadData() + } + } + + // MARK: - Setup + + @objc class func instantiate(withSession session: MXSession) -> SearchableDirectoryViewController { + let viewController = StoryboardScene.SearchableDirectoryViewController.initialScene.instantiate() + viewController.theme = ThemeService.shared().theme + viewController.addMatrixSession(session) + return viewController + } + + // MARK: - Life cycle + + override func viewDidLoad() { + super.viewDidLoad() + + // Do any additional setup after loading the view. + + self.title = "Template" + self.vc_removeBackTitle() + + self.setupViews() + self.registerThemeServiceDidChangeThemeNotification() + self.update(theme: self.theme) + + self.mainTableView.tableFooterView = UIView() + + triggerPagination() + } + + override var preferredStatusBarStyle: UIStatusBarStyle { + return self.theme.statusBarStyle + } + + // MARK: - Private + + private func setupViews() { + self.mainTableView.register(headerFooterViewType: DirectoryNetworkTableHeaderFooterView.self) + self.mainTableView.register(cellType: DirectoryRoomTableViewCell.self) + self.mainTableView.rowHeight = 76 + + let cancelBarButtonItem = MXKBarButtonItem(title: VectorL10n.cancel, style: .plain) { [weak self] in + self?.cancelButtonAction() + } + self.navigationItem.rightBarButtonItem = cancelBarButtonItem + + self.navigationItem.titleView = mainSearchBar + } + + private func update(theme: Theme) { + self.theme = theme + + self.view.backgroundColor = theme.backgroundColor + self.mainTableView.backgroundColor = theme.backgroundColor + self.mainTableView.separatorColor = theme.lineBreakColor + + if let navigationBar = self.navigationController?.navigationBar { + theme.applyStyle(onNavigationBar: navigationBar) + } + + theme.applyStyle(onSearchBar: mainSearchBar) + theme.applyStyle(onButton: createRoomButton) + + self.mainTableView.reloadData() + } + + private func registerThemeServiceDidChangeThemeNotification() { + NotificationCenter.default.addObserver(self, selector: #selector(themeDidChange), name: .themeServiceDidChangeTheme, object: nil) + } + + private func addSpinnerFooterView() { + footerSpinnerView.startAnimating() + self.mainTableView.tableFooterView = footerSpinnerView + } + + private func removeSpinnerFooterView() { + footerSpinnerView.stopAnimating() + self.mainTableView.tableFooterView = UIView() + } + + private func triggerPagination(force: Bool = false) { + if !force && (dataSource.hasReachedPaginationEnd || footerSpinnerView.superview != nil) { + // We got all public rooms or we are already paginating + // Do nothing + return + } + + self.addSpinnerFooterView() + + dataSource.paginate({ [weak self] (roomsAdded) in + guard let self = self else { return } + if roomsAdded > 0 { + self.mainTableView.reloadData() + } + self.removeSpinnerFooterView() + }, failure: { [weak self] (error) in + guard let self = self else { return } + self.removeSpinnerFooterView() + }) + } + + // MARK: - Override + + override func addMatrixSession(_ mxSession: MXSession!) { + super.addMatrixSession(mxSession) + + if dataSource == nil { + display(withDataSource: PublicRoomsDirectoryDataSource(matrixSession: mxSession)) + } + } + + // MARK: - Actions + + @objc private func themeDidChange() { + self.update(theme: ThemeService.shared().theme) + } + + private func cancelButtonAction() { + self.delegate?.searchableDirectoryViewControllerDidCancel(self) + } + + @IBAction private func createRoomButtonTapped(_ sender: UIButton) { + self.delegate?.searchableDirectoryViewControllerDidTapCreateNewRoom(self) + } +} + +// MARK: - UITableViewDataSource + +extension SearchableDirectoryViewController: UITableViewDataSource { + + func numberOfSections(in tableView: UITableView) -> Int { + return 1 + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return Int(dataSource?.roomsCount ?? 0) + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell: DirectoryRoomTableViewCell = tableView.dequeueReusableCell(for: indexPath) + if let room = dataSource.room(at: indexPath) { + cell.configure(withRoom: room, session: dataSource.mxSession) + } + cell.update(theme: self.theme) + return cell + } + +} + +// MARK: - UITableViewDataDelegate + +extension SearchableDirectoryViewController: UITableViewDelegate { + + func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { + cell.backgroundColor = theme.backgroundColor + + // Update the selected background view + cell.selectedBackgroundView = UIView() + cell.selectedBackgroundView?.backgroundColor = theme.selectedBackgroundColor + } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + + guard let room = dataSource.room(at: indexPath) else { return } + delegate?.searchableDirectoryViewControllerDidSelect(self, room: room) + } + + func scrollViewDidScroll(_ scrollView: UIScrollView) { + // Trigger inconspicuous pagination when user scrolls down + if (scrollView.contentSize.height - scrollView.contentOffset.y - scrollView.frame.size.height) < 300 { + self.triggerPagination() + } + } + + func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + guard let view: DirectoryNetworkTableHeaderFooterView = tableView.dequeueReusableHeaderFooterView() else { + return nil + } + if let name = self.dataSource.directoryServerDisplayname { + let title = VectorL10n.searchableDirectoryXNetwork(name) + view.configure(withViewModel: DirectoryNetworkVM(title: title)) + } + view.update(theme: self.theme) + view.delegate = self + return view + } + + func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { + return 30 + } + +} + +// MARK: - UISearchBarDelegate + +extension SearchableDirectoryViewController { + + override func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) { + dataSource.searchPattern = searchText + triggerPagination(force: true) + } + +} + +// MARK: - MXKDataSourceDelegate + +extension SearchableDirectoryViewController: MXKDataSourceDelegate { + + func cellViewClass(for cellData: MXKCellData!) -> MXKCellRendering.Type! { + return nil + } + + func cellReuseIdentifier(for cellData: MXKCellData!) -> String! { + return nil + } + + func dataSource(_ dataSource: MXKDataSource!, didCellChange changes: Any!) { + + } + + func dataSource(_ dataSource: MXKDataSource!, didStateChange state: MXKDataSourceState) { + self.mainTableView.reloadData() + } + +} + +// MARK: - DirectoryNetworkTableHeaderFooterViewDelegate + +extension SearchableDirectoryViewController: DirectoryNetworkTableHeaderFooterViewDelegate { + + func directoryNetworkTableHeaderFooterViewDidTapSwitch(_ view: DirectoryNetworkTableHeaderFooterView) { + let controller = DirectoryServerPickerViewController() + let source = MXKDirectoryServersDataSource(matrixSession: self.mainSession) + source?.finalizeInitialization() + source?.roomDirectoryServers = BuildSettings.publicRoomsDirectoryServers + + controller.display(with: source) { [weak self] (cellData) in + guard let self = self else { return } + guard let cellData = cellData else { return } + + if let thirdpartyProtocolInstance = cellData.thirdPartyProtocolInstance { + self.dataSource.thirdpartyProtocolInstance = thirdpartyProtocolInstance + } else if let homeserver = cellData.homeserver { + self.dataSource.includeAllNetworks = cellData.includeAllNetworks + self.dataSource.homeserver = homeserver + } + + self.triggerPagination() + } + + let navController = RiotNavigationController(rootViewController: controller) + self.navigationController?.present(navController, animated: true, completion: nil) + } + +} diff --git a/Riot/SupportingFiles/Riot-Bridging-Header.h b/Riot/SupportingFiles/Riot-Bridging-Header.h index 3d1590625..386d75b28 100644 --- a/Riot/SupportingFiles/Riot-Bridging-Header.h +++ b/Riot/SupportingFiles/Riot-Bridging-Header.h @@ -20,3 +20,4 @@ #import "BubbleReactionsViewSizer.h" #import "RoomEncryptedDataBubbleCell.h" #import "LegacyAppDelegate.h" +#import "DirectoryServerPickerViewController.h"