diff --git a/Riot/Modules/Threads/ThreadList/ThreadListCoordinator.swift b/Riot/Modules/Threads/ThreadList/ThreadListCoordinator.swift index 1dabc738a..a870d8808 100644 --- a/Riot/Modules/Threads/ThreadList/ThreadListCoordinator.swift +++ b/Riot/Modules/Threads/ThreadList/ThreadListCoordinator.swift @@ -69,6 +69,10 @@ extension ThreadListCoordinator: ThreadListViewModelCoordinatorDelegate { self.delegate?.threadListCoordinatorDidSelectThread(self, thread: thread) } + func threadListViewModelDidSelectThreadViewInRoom(_ viewModel: ThreadListViewModelProtocol, thread: MXThread) { + self.delegate?.threadListCoordinatorDidSelectRoom(self, roomId: thread.roomId, eventId: thread.id) + } + func threadListViewModelDidCancel(_ viewModel: ThreadListViewModelProtocol) { self.delegate?.threadListCoordinatorDidCancel(self) } diff --git a/Riot/Modules/Threads/ThreadList/ThreadListCoordinatorProtocol.swift b/Riot/Modules/Threads/ThreadList/ThreadListCoordinatorProtocol.swift index a4227b909..e15c70f35 100644 --- a/Riot/Modules/Threads/ThreadList/ThreadListCoordinatorProtocol.swift +++ b/Riot/Modules/Threads/ThreadList/ThreadListCoordinatorProtocol.swift @@ -21,6 +21,7 @@ import Foundation protocol ThreadListCoordinatorDelegate: AnyObject { func threadListCoordinatorDidLoadThreads(_ coordinator: ThreadListCoordinatorProtocol) func threadListCoordinatorDidSelectThread(_ coordinator: ThreadListCoordinatorProtocol, thread: MXThread) + func threadListCoordinatorDidSelectRoom(_ coordinator: ThreadListCoordinatorProtocol, roomId: String, eventId: String) func threadListCoordinatorDidCancel(_ coordinator: ThreadListCoordinatorProtocol) } diff --git a/Riot/Modules/Threads/ThreadList/ThreadListViewAction.swift b/Riot/Modules/Threads/ThreadList/ThreadListViewAction.swift index 629e430e8..a03d0332f 100644 --- a/Riot/Modules/Threads/ThreadList/ThreadListViewAction.swift +++ b/Riot/Modules/Threads/ThreadList/ThreadListViewAction.swift @@ -25,5 +25,9 @@ enum ThreadListViewAction { case showFilterTypes case selectFilterType(_ type: ThreadListFilterType) case selectThread(_ index: Int) + case longPressThread(_ index: Int) + case actionViewInRoom + case actionCopyLinkToThread + case actionShare case cancel } diff --git a/Riot/Modules/Threads/ThreadList/ThreadListViewController.storyboard b/Riot/Modules/Threads/ThreadList/ThreadListViewController.storyboard index ddfb9060c..c56d0f29d 100644 --- a/Riot/Modules/Threads/ThreadList/ThreadListViewController.storyboard +++ b/Riot/Modules/Threads/ThreadList/ThreadListViewController.storyboard @@ -20,9 +20,11 @@ + + @@ -52,6 +54,11 @@ + + + + + diff --git a/Riot/Modules/Threads/ThreadList/ThreadListViewController.swift b/Riot/Modules/Threads/ThreadList/ThreadListViewController.swift index 9b9b55bc8..1e7da734c 100644 --- a/Riot/Modules/Threads/ThreadList/ThreadListViewController.swift +++ b/Riot/Modules/Threads/ThreadList/ThreadListViewController.swift @@ -147,15 +147,19 @@ final class ThreadListViewController: UIViewController { case .idle: break case .loading: - self.renderLoading() + renderLoading() case .loaded: - self.renderLoaded() + renderLoaded() case .empty(let viewModel): - self.renderEmptyView(withViewModel: viewModel) + renderEmptyView(withViewModel: viewModel) case .showingFilterTypes: - self.renderShowingFilterTypes() + renderShowingFilterTypes() + case .showingLongPressActions: + renderShowingLongPressActions() + case .share(let string): + renderShare(string) case .error(let error): - self.render(error: error) + render(error: error) } } @@ -214,6 +218,44 @@ final class ThreadListViewController: UIViewController { self.present(alertController, animated: true, completion: nil) } + private func renderShowingLongPressActions() { + let controller = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) + + controller.addAction(UIAlertAction(title: VectorL10n.roomEventActionViewInRoom, + style: .default, + handler: { [weak self] action in + guard let self = self else { return } + self.viewModel.process(viewAction: .actionViewInRoom) + })) + + controller.addAction(UIAlertAction(title: VectorL10n.threadCopyLinkToThread, + style: .default, + handler: { [weak self] action in + guard let self = self else { return } + self.viewModel.process(viewAction: .actionCopyLinkToThread) + })) + + controller.addAction(UIAlertAction(title: VectorL10n.roomEventActionShare, + style: .default, + handler: { [weak self] action in + guard let self = self else { return } + self.viewModel.process(viewAction: .actionShare) + })) + + controller.addAction(UIAlertAction(title: VectorL10n.cancel, + style: .cancel, + handler: nil)) + + self.present(controller, animated: true, completion: nil) + } + + private func renderShare(_ string: String) { + let activityVC = UIActivityViewController(activityItems: [string], + applicationActivities: nil) + activityVC.modalTransitionStyle = .coverVertical + present(activityVC, animated: true, completion: nil) + } + private func render(error: Error) { self.activityPresenter.removeCurrentActivityIndicator(animated: true) self.errorPresenter.presentError(from: self, forError: error, animated: true, handler: nil) @@ -225,6 +267,22 @@ final class ThreadListViewController: UIViewController { private func filterButtonTapped(_ sender: UIBarButtonItem) { self.viewModel.process(viewAction: .showFilterTypes) } + + @IBAction private func longPressed(_ sender: UILongPressGestureRecognizer) { + guard sender.state == .began else { + return + } + let point = sender.location(in: threadsTableView) + guard let indexPath = threadsTableView.indexPathForRow(at: point) else { + return + } + guard let cell = threadsTableView.cellForRow(at: indexPath) else { + return + } + if cell.isHighlighted { + viewModel.process(viewAction: .longPressThread(indexPath.row)) + } + } } diff --git a/Riot/Modules/Threads/ThreadList/ThreadListViewModel.swift b/Riot/Modules/Threads/ThreadList/ThreadListViewModel.swift index 4f03e3ed1..884f45711 100644 --- a/Riot/Modules/Threads/ThreadList/ThreadListViewModel.swift +++ b/Riot/Modules/Threads/ThreadList/ThreadListViewModel.swift @@ -31,6 +31,7 @@ final class ThreadListViewModel: ThreadListViewModelProtocol { private var roomState: MXRoomState? private var currentOperation: MXHTTPOperation? + private var longPressedThread: MXThread? // MARK: Public @@ -73,6 +74,14 @@ final class ThreadListViewModel: ThreadListViewModelProtocol { loadData() case .selectThread(let index): selectThread(index) + case .longPressThread(let index): + longPressThread(index) + case .actionViewInRoom: + actionViewInRoom() + case .actionCopyLinkToThread: + actionCopyLinkToThread() + case .actionShare: + actionShare() case .cancel: cancelOperations() coordinatorDelegate?.threadListViewModelDidCancel(self) @@ -264,6 +273,42 @@ final class ThreadListViewModel: ThreadListViewModelProtocol { coordinatorDelegate?.threadListViewModelDidSelectThread(self, thread: thread) } + private func longPressThread(_ index: Int) { + guard index < threads.count else { + return + } + longPressedThread = threads[index] + viewState = .showingLongPressActions + } + + private func actionViewInRoom() { + guard let thread = longPressedThread else { + return + } + coordinatorDelegate?.threadListViewModelDidSelectThreadViewInRoom(self, thread: thread) + longPressedThread = nil + } + + private func actionCopyLinkToThread() { + guard let thread = longPressedThread else { + return + } + if let permalink = MXTools.permalink(toEvent: thread.id, inRoom: thread.roomId) { + MXKPasteboardManager.shared.pasteboard.string = permalink + } + longPressedThread = nil + } + + private func actionShare() { + guard let thread = longPressedThread else { + return + } + if let permalink = MXTools.permalink(toEvent: thread.id, inRoom: thread.roomId) { + viewState = .share(permalink) + } + longPressedThread = nil + } + private func cancelOperations() { self.currentOperation?.cancel() } diff --git a/Riot/Modules/Threads/ThreadList/ThreadListViewModelProtocol.swift b/Riot/Modules/Threads/ThreadList/ThreadListViewModelProtocol.swift index be0892c47..457cf39d5 100644 --- a/Riot/Modules/Threads/ThreadList/ThreadListViewModelProtocol.swift +++ b/Riot/Modules/Threads/ThreadList/ThreadListViewModelProtocol.swift @@ -25,6 +25,7 @@ protocol ThreadListViewModelViewDelegate: AnyObject { protocol ThreadListViewModelCoordinatorDelegate: AnyObject { func threadListViewModelDidLoadThreads(_ viewModel: ThreadListViewModelProtocol) func threadListViewModelDidSelectThread(_ viewModel: ThreadListViewModelProtocol, thread: MXThread) + func threadListViewModelDidSelectThreadViewInRoom(_ viewModel: ThreadListViewModelProtocol, thread: MXThread) func threadListViewModelDidCancel(_ viewModel: ThreadListViewModelProtocol) } diff --git a/Riot/Modules/Threads/ThreadList/ThreadListViewState.swift b/Riot/Modules/Threads/ThreadList/ThreadListViewState.swift index 073c70645..765e67fa2 100644 --- a/Riot/Modules/Threads/ThreadList/ThreadListViewState.swift +++ b/Riot/Modules/Threads/ThreadList/ThreadListViewState.swift @@ -25,5 +25,7 @@ enum ThreadListViewState { case loaded case empty(_ viewModel: ThreadListEmptyViewModel) case showingFilterTypes + case showingLongPressActions + case share(_ string: String) case error(Error) } diff --git a/Riot/Modules/Threads/ThreadsCoordinator.swift b/Riot/Modules/Threads/ThreadsCoordinator.swift index 807d6d125..9e6e63e8f 100644 --- a/Riot/Modules/Threads/ThreadsCoordinator.swift +++ b/Riot/Modules/Threads/ThreadsCoordinator.swift @@ -151,6 +151,10 @@ extension ThreadsCoordinator: ThreadListCoordinatorDelegate { self.add(childCoordinator: roomCoordinator) } + func threadListCoordinatorDidSelectRoom(_ coordinator: ThreadListCoordinatorProtocol, roomId: String, eventId: String) { + self.delegate?.threadsCoordinatorDidSelect(self, roomId: roomId, eventId: eventId) + } + func threadListCoordinatorDidCancel(_ coordinator: ThreadListCoordinatorProtocol) { self.delegate?.threadsCoordinatorDidComplete(self) }