mirror of
https://github.com/element-hq/element-x-ios.git
synced 2025-03-10 13:37:11 +00:00
Add support for sharing URLs and text. (#3546)
* Add support for sharing URLs and text. * Fix a bug where a stored draft would overwrite the shared text. * Add tests.
This commit is contained in:
parent
3a9f54a9f6
commit
c081e538b4
@ -62,9 +62,18 @@ private enum PresentationAction: Hashable {
|
||||
var focusedEvent: FocusEvent? {
|
||||
switch self {
|
||||
case .eventFocus(let focusEvent):
|
||||
return focusEvent
|
||||
focusEvent
|
||||
default:
|
||||
return nil
|
||||
nil
|
||||
}
|
||||
}
|
||||
|
||||
var sharedText: String? {
|
||||
switch self {
|
||||
case .share(.text(_, let text)):
|
||||
text
|
||||
default:
|
||||
nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -196,11 +205,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
||||
roomScreenCoordinator?.focusOnEvent(.init(eventID: eventID, shouldSetPin: false))
|
||||
}
|
||||
case .share(let payload):
|
||||
guard case let .mediaFile(roomID, _) = payload else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let roomID, roomID == self.roomID else {
|
||||
guard let roomID = payload.roomID, roomID == self.roomID else {
|
||||
fatalError("Navigation route doesn't belong to this room flow.")
|
||||
}
|
||||
|
||||
@ -615,8 +620,10 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
||||
case .eventFocus(let focusedEvent):
|
||||
roomScreenCoordinator?.focusOnEvent(focusedEvent)
|
||||
case .share(.mediaFile(_, let mediaFile)):
|
||||
stateMachine.tryEvent(.presentMediaUploadPreview(fileURL: mediaFile.url))
|
||||
default:
|
||||
stateMachine.tryEvent(.presentMediaUploadPreview(fileURL: mediaFile.url), userInfo: EventUserInfo(animated: animated))
|
||||
case .share(.text(_, let text)):
|
||||
roomScreenCoordinator?.shareText(text)
|
||||
case .none:
|
||||
break
|
||||
}
|
||||
|
||||
@ -624,32 +631,57 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
Task {
|
||||
// Flag the room as read on entering, the timeline will take care of the read receipts
|
||||
await roomProxy.flagAsUnread(false)
|
||||
Task { await roomProxy.flagAsUnread(false) }
|
||||
|
||||
analytics.trackViewRoom(isDM: roomProxy.infoPublisher.value.isDirect, isSpace: roomProxy.infoPublisher.value.isSpace)
|
||||
|
||||
let coordinator = makeRoomScreenCoordinator(presentationAction: presentationAction)
|
||||
roomScreenCoordinator = coordinator
|
||||
|
||||
if !isChildFlow {
|
||||
let animated = UIDevice.current.userInterfaceIdiom == .phone ? animated : false
|
||||
navigationStackCoordinator.setRootCoordinator(coordinator, animated: animated) { [weak self] in
|
||||
self?.stateMachine.tryEvent(.dismissFlow)
|
||||
}
|
||||
} else {
|
||||
if joinRoomScreenCoordinator != nil {
|
||||
navigationStackCoordinator.pop()
|
||||
}
|
||||
|
||||
let userID = userSession.clientProxy.userID
|
||||
navigationStackCoordinator.push(coordinator, animated: animated) { [weak self] in
|
||||
self?.stateMachine.tryEvent(.dismissFlow)
|
||||
}
|
||||
}
|
||||
|
||||
switch presentationAction {
|
||||
case .share(.mediaFile(_, let mediaFile)):
|
||||
stateMachine.tryEvent(.presentMediaUploadPreview(fileURL: mediaFile.url), userInfo: EventUserInfo(animated: animated))
|
||||
case .share(.text), .eventFocus:
|
||||
break // These are both handled in the coordinator's init.
|
||||
case .none:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
private func makeRoomScreenCoordinator(presentationAction: PresentationAction?) -> RoomScreenCoordinator {
|
||||
let userID = userSession.clientProxy.userID
|
||||
let timelineItemFactory = RoomTimelineItemFactory(userID: userID,
|
||||
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
|
||||
stateEventStringBuilder: RoomStateEventStringBuilder(userID: userID))
|
||||
|
||||
let timelineController = roomTimelineControllerFactory.buildRoomTimelineController(roomProxy: roomProxy,
|
||||
initialFocussedEventID: presentationAction?.focusedEvent?.eventID,
|
||||
timelineItemFactory: timelineItemFactory,
|
||||
mediaProvider: userSession.mediaProvider)
|
||||
self.timelineController = timelineController
|
||||
|
||||
analytics.trackViewRoom(isDM: roomProxy.infoPublisher.value.isDirect, isSpace: roomProxy.infoPublisher.value.isSpace)
|
||||
|
||||
let completionSuggestionService = CompletionSuggestionService(roomProxy: roomProxy)
|
||||
|
||||
let composerDraftService = ComposerDraftService(roomProxy: roomProxy, timelineItemfactory: timelineItemFactory)
|
||||
|
||||
let parameters = RoomScreenCoordinatorParameters(clientProxy: userSession.clientProxy,
|
||||
roomProxy: roomProxy,
|
||||
focussedEvent: presentationAction?.focusedEvent,
|
||||
sharedText: presentationAction?.sharedText,
|
||||
timelineController: timelineController,
|
||||
mediaProvider: userSession.mediaProvider,
|
||||
mediaPlayerProvider: MediaPlayerProvider(),
|
||||
@ -697,28 +729,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
roomScreenCoordinator = coordinator
|
||||
if !isChildFlow {
|
||||
let animated = UIDevice.current.userInterfaceIdiom == .phone ? animated : false
|
||||
navigationStackCoordinator.setRootCoordinator(coordinator, animated: animated) { [weak self] in
|
||||
self?.stateMachine.tryEvent(.dismissFlow)
|
||||
}
|
||||
} else {
|
||||
if joinRoomScreenCoordinator != nil {
|
||||
navigationStackCoordinator.pop()
|
||||
}
|
||||
|
||||
navigationStackCoordinator.push(coordinator, animated: animated) { [weak self] in
|
||||
self?.stateMachine.tryEvent(.dismissFlow)
|
||||
}
|
||||
}
|
||||
|
||||
switch presentationAction {
|
||||
case .share(.mediaFile(_, let mediaFile)):
|
||||
stateMachine.tryEvent(.presentMediaUploadPreview(fileURL: mediaFile.url), userInfo: EventUserInfo(animated: animated))
|
||||
default:
|
||||
break
|
||||
}
|
||||
return coordinator
|
||||
}
|
||||
|
||||
private func presentJoinRoomScreen(via: [String], animated: Bool) {
|
||||
|
@ -207,9 +207,7 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
||||
case .settings, .chatBackupSettings:
|
||||
settingsFlowCoordinator.handleAppRoute(appRoute, animated: animated)
|
||||
case .share(let payload):
|
||||
switch payload {
|
||||
case .mediaFile(let roomID, _):
|
||||
if let roomID {
|
||||
if let roomID = payload.roomID {
|
||||
stateMachine.processEvent(.selectRoom(roomID: roomID,
|
||||
via: [],
|
||||
entryPoint: .share(payload)),
|
||||
@ -219,7 +217,6 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func attemptStartingOnboarding() {
|
||||
if onboardingFlowCoordinator.shouldStart {
|
||||
@ -938,6 +935,8 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
||||
let sharePayload = switch sharePayload {
|
||||
case .mediaFile(_, let mediaFile):
|
||||
ShareExtensionPayload.mediaFile(roomID: roomID, mediaFile: mediaFile)
|
||||
case .text(_, let text):
|
||||
ShareExtensionPayload.text(roomID: roomID, text: text)
|
||||
}
|
||||
|
||||
navigationSplitCoordinator.setSheetCoordinator(nil)
|
||||
|
@ -123,6 +123,7 @@ extension JoinedRoomProxyMock {
|
||||
matrixToEventPermalinkReturnValue = .success(.homeDirectory)
|
||||
loadDraftReturnValue = .success(nil)
|
||||
clearDraftReturnValue = .success(())
|
||||
sendTypingNotificationIsTypingReturnValue = .success(())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,7 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
import SwiftUI
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
extension NSItemProvider {
|
||||
@ -15,6 +15,19 @@ extension NSItemProvider {
|
||||
let fileExtension: String
|
||||
}
|
||||
|
||||
func loadTransferable<T: Transferable>(type transferableType: T.Type) async -> T? {
|
||||
try? await withCheckedContinuation { continuation in
|
||||
_ = loadTransferable(type: T.self) { result in
|
||||
continuation.resume(returning: result)
|
||||
}
|
||||
}
|
||||
.get()
|
||||
}
|
||||
|
||||
func loadString() async -> String? {
|
||||
try? await loadItem(forTypeIdentifier: UTType.text.identifier) as? String
|
||||
}
|
||||
|
||||
func storeData() async -> URL? {
|
||||
guard let contentType = preferredContentType else {
|
||||
MXLog.error("Invalid NSItemProvider: \(self)")
|
||||
|
@ -15,6 +15,7 @@ import WysiwygComposer
|
||||
typealias ComposerToolbarViewModelType = StateStoreViewModel<ComposerToolbarViewState, ComposerToolbarViewAction>
|
||||
|
||||
final class ComposerToolbarViewModel: ComposerToolbarViewModelType, ComposerToolbarViewModelProtocol {
|
||||
private var initialText: String?
|
||||
private let wysiwygViewModel: WysiwygComposerViewModel
|
||||
private let completionSuggestionService: CompletionSuggestionServiceProtocol
|
||||
private let analyticsService: AnalyticsService
|
||||
@ -41,12 +42,14 @@ final class ComposerToolbarViewModel: ComposerToolbarViewModelType, ComposerTool
|
||||
|
||||
private var replyLoadingTask: Task<Void, Never>?
|
||||
|
||||
init(wysiwygViewModel: WysiwygComposerViewModel,
|
||||
init(initialText: String? = nil,
|
||||
wysiwygViewModel: WysiwygComposerViewModel,
|
||||
completionSuggestionService: CompletionSuggestionServiceProtocol,
|
||||
mediaProvider: MediaProviderProtocol,
|
||||
mentionDisplayHelper: MentionDisplayHelper,
|
||||
analyticsService: AnalyticsService,
|
||||
composerDraftService: ComposerDraftServiceProtocol) {
|
||||
self.initialText = initialText
|
||||
self.wysiwygViewModel = wysiwygViewModel
|
||||
self.completionSuggestionService = completionSuggestionService
|
||||
self.analyticsService = analyticsService
|
||||
@ -206,6 +209,8 @@ final class ComposerToolbarViewModel: ComposerToolbarViewModelType, ComposerTool
|
||||
} else {
|
||||
set(text: plainText)
|
||||
}
|
||||
case .setFocus:
|
||||
state.bindings.composerFocused = true
|
||||
case .removeFocus:
|
||||
state.bindings.composerFocused = false
|
||||
case .clear:
|
||||
@ -219,8 +224,12 @@ final class ComposerToolbarViewModel: ComposerToolbarViewModelType, ComposerTool
|
||||
}
|
||||
}
|
||||
|
||||
func loadDraft() {
|
||||
Task {
|
||||
func loadDraft() async {
|
||||
if let initialText {
|
||||
set(text: initialText)
|
||||
set(mode: .default)
|
||||
state.bindings.composerFocused = true
|
||||
} else {
|
||||
guard case let .success(draft) = await draftService.loadDraft(),
|
||||
let draft else {
|
||||
return
|
||||
|
@ -15,6 +15,6 @@ protocol ComposerToolbarViewModelProtocol {
|
||||
var keyCommands: [WysiwygKeyCommand] { get }
|
||||
|
||||
func process(timelineAction: TimelineComposerAction)
|
||||
func loadDraft()
|
||||
func loadDraft() async
|
||||
func saveDraft()
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ struct RoomScreenCoordinatorParameters {
|
||||
let clientProxy: ClientProxyProtocol
|
||||
let roomProxy: JoinedRoomProxyProtocol
|
||||
var focussedEvent: FocusEvent?
|
||||
var sharedText: String?
|
||||
let timelineController: RoomTimelineControllerProtocol
|
||||
let mediaProvider: MediaProviderProtocol
|
||||
let mediaPlayerProvider: MediaPlayerProviderProtocol
|
||||
@ -88,7 +89,8 @@ final class RoomScreenCoordinator: CoordinatorProtocol {
|
||||
maxCompressedHeight: ComposerConstant.maxHeight,
|
||||
maxExpandedHeight: ComposerConstant.maxHeight,
|
||||
parserStyle: .elementX)
|
||||
let composerViewModel = ComposerToolbarViewModel(wysiwygViewModel: wysiwygViewModel,
|
||||
let composerViewModel = ComposerToolbarViewModel(initialText: parameters.sharedText,
|
||||
wysiwygViewModel: wysiwygViewModel,
|
||||
completionSuggestionService: parameters.completionSuggestionService,
|
||||
mediaProvider: parameters.mediaProvider,
|
||||
mentionDisplayHelper: ComposerMentionDisplayHelper(timelineContext: timelineViewModel.context),
|
||||
@ -172,7 +174,7 @@ final class RoomScreenCoordinator: CoordinatorProtocol {
|
||||
.store(in: &cancellables)
|
||||
|
||||
// Loading the draft requires the subscriptions to be set up first otherwise the room won't be be able to propagate the information to the composer.
|
||||
composerViewModel.loadDraft()
|
||||
Task { await composerViewModel.loadDraft() }
|
||||
}
|
||||
|
||||
func focusOnEvent(_ focussedEvent: FocusEvent) {
|
||||
@ -183,6 +185,12 @@ final class RoomScreenCoordinator: CoordinatorProtocol {
|
||||
Task { await timelineViewModel.focusOnEvent(eventID: eventID) }
|
||||
}
|
||||
|
||||
func shareText(_ string: String) {
|
||||
composerViewModel.process(timelineAction: .setMode(mode: .default)) // Make sure we're not e.g. replying.
|
||||
composerViewModel.process(timelineAction: .setText(plainText: string, htmlText: nil))
|
||||
composerViewModel.process(timelineAction: .setFocus)
|
||||
}
|
||||
|
||||
func stop() {
|
||||
composerViewModel.saveDraft()
|
||||
timelineViewModel.stop()
|
||||
|
@ -79,6 +79,7 @@ enum TimelineViewAction {
|
||||
enum TimelineComposerAction {
|
||||
case setMode(mode: ComposerMode)
|
||||
case setText(plainText: String, htmlText: String?)
|
||||
case setFocus
|
||||
case removeFocus
|
||||
case clear
|
||||
}
|
||||
|
@ -13,6 +13,15 @@ enum ShareExtensionConstants {
|
||||
|
||||
enum ShareExtensionPayload: Hashable, Codable {
|
||||
case mediaFile(roomID: String?, mediaFile: ShareExtensionMediaFile)
|
||||
case text(roomID: String?, text: String)
|
||||
|
||||
var roomID: String? {
|
||||
switch self {
|
||||
case .mediaFile(let roomID, _),
|
||||
.text(let roomID, _):
|
||||
roomID
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ShareExtensionMediaFile: Hashable, Codable {
|
||||
|
@ -42,14 +42,18 @@ class ShareExtensionViewController: UIViewController {
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let fileURL = await itemProvider.storeData() else {
|
||||
MXLog.error("Failed storing NSItemProvider data \(itemProvider)")
|
||||
return nil
|
||||
}
|
||||
|
||||
let roomID = (extensionContext?.intent as? INSendMessageIntent)?.conversationIdentifier
|
||||
|
||||
if let fileURL = await itemProvider.storeData() {
|
||||
return .mediaFile(roomID: roomID, mediaFile: .init(url: fileURL, suggestedName: fileURL.lastPathComponent))
|
||||
} else if let url = await itemProvider.loadTransferable(type: URL.self) {
|
||||
return .text(roomID: roomID, text: url.absoluteString)
|
||||
} else if let string = await itemProvider.loadString() {
|
||||
return .text(roomID: roomID, text: string)
|
||||
} else {
|
||||
MXLog.error("Failed loading NSItemProvider data: \(itemProvider)")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
private func openMainApp(payload: ShareExtensionPayload) async {
|
||||
|
@ -36,6 +36,10 @@
|
||||
<integer>1</integer>
|
||||
<key>NSExtensionActivationSupportsMovieWithMaxCount</key>
|
||||
<integer>1</integer>
|
||||
<key>NSExtensionActivationSupportsText</key>
|
||||
<true/>
|
||||
<key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
|
||||
<integer>1</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
|
@ -23,17 +23,7 @@ class ComposerToolbarViewModelTests: XCTestCase {
|
||||
AppSettings.resetAllSettings()
|
||||
appSettings = AppSettings()
|
||||
ServiceLocator.shared.register(appSettings: appSettings)
|
||||
wysiwygViewModel = WysiwygComposerViewModel()
|
||||
completionSuggestionServiceMock = CompletionSuggestionServiceMock(configuration: .init())
|
||||
draftServiceMock = ComposerDraftServiceMock()
|
||||
viewModel = ComposerToolbarViewModel(wysiwygViewModel: wysiwygViewModel,
|
||||
completionSuggestionService: completionSuggestionServiceMock,
|
||||
mediaProvider: MediaProviderMock(configuration: .init()),
|
||||
mentionDisplayHelper: ComposerMentionDisplayHelper.mock,
|
||||
analyticsService: ServiceLocator.shared.analytics,
|
||||
composerDraftService: draftServiceMock)
|
||||
|
||||
viewModel.context.composerFormattingEnabled = true
|
||||
setUpViewModel()
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
@ -340,7 +330,7 @@ class ComposerToolbarViewModelTests: XCTestCase {
|
||||
return .success(nil)
|
||||
}
|
||||
|
||||
viewModel.loadDraft()
|
||||
await viewModel.loadDraft()
|
||||
await fulfillment(of: [expectation], timeout: 10)
|
||||
XCTAssertFalse(viewModel.context.composerFormattingEnabled)
|
||||
XCTAssertTrue(viewModel.state.composerEmpty)
|
||||
@ -356,7 +346,7 @@ class ComposerToolbarViewModelTests: XCTestCase {
|
||||
htmlText: nil,
|
||||
draftType: .newMessage))
|
||||
}
|
||||
viewModel.loadDraft()
|
||||
await viewModel.loadDraft()
|
||||
|
||||
await fulfillment(of: [expectation], timeout: 10)
|
||||
XCTAssertFalse(viewModel.context.composerFormattingEnabled)
|
||||
@ -373,7 +363,7 @@ class ComposerToolbarViewModelTests: XCTestCase {
|
||||
htmlText: "<strong>Hello</strong> world!",
|
||||
draftType: .newMessage))
|
||||
}
|
||||
viewModel.loadDraft()
|
||||
await viewModel.loadDraft()
|
||||
|
||||
await fulfillment(of: [expectation], timeout: 10)
|
||||
XCTAssertTrue(viewModel.context.composerFormattingEnabled)
|
||||
@ -391,7 +381,7 @@ class ComposerToolbarViewModelTests: XCTestCase {
|
||||
htmlText: nil,
|
||||
draftType: .edit(eventID: "testID")))
|
||||
}
|
||||
viewModel.loadDraft()
|
||||
await viewModel.loadDraft()
|
||||
|
||||
await fulfillment(of: [expectation], timeout: 10)
|
||||
XCTAssertFalse(viewModel.context.composerFormattingEnabled)
|
||||
@ -424,7 +414,7 @@ class ComposerToolbarViewModelTests: XCTestCase {
|
||||
return .success(.init(details: loadedReply,
|
||||
isThreaded: true))
|
||||
}
|
||||
viewModel.loadDraft()
|
||||
await viewModel.loadDraft()
|
||||
|
||||
await fulfillment(of: [draftExpectation], timeout: 10)
|
||||
XCTAssertFalse(viewModel.context.composerFormattingEnabled)
|
||||
@ -464,7 +454,7 @@ class ComposerToolbarViewModelTests: XCTestCase {
|
||||
return .success(.init(details: loadedReply,
|
||||
isThreaded: true))
|
||||
}
|
||||
viewModel.loadDraft()
|
||||
await viewModel.loadDraft()
|
||||
|
||||
await fulfillment(of: [draftExpectation], timeout: 10)
|
||||
XCTAssertFalse(viewModel.context.composerFormattingEnabled)
|
||||
@ -622,6 +612,45 @@ class ComposerToolbarViewModelTests: XCTestCase {
|
||||
viewModel.process(viewAction: .sendMessage)
|
||||
try await deferred.fulfill()
|
||||
}
|
||||
|
||||
func testRestoreDoesntOverwriteInitialText() async {
|
||||
let sharedText = "Some shared text"
|
||||
let expectation = expectation(description: "Wait for draft to be restored")
|
||||
expectation.isInverted = true
|
||||
setUpViewModel(initialText: sharedText) {
|
||||
defer { expectation.fulfill() }
|
||||
return .success(.init(plainText: "Hello world!",
|
||||
htmlText: nil,
|
||||
draftType: .newMessage))
|
||||
}
|
||||
viewModel.context.composerFormattingEnabled = false
|
||||
await viewModel.loadDraft()
|
||||
|
||||
await fulfillment(of: [expectation], timeout: 1)
|
||||
XCTAssertFalse(viewModel.context.composerFormattingEnabled)
|
||||
XCTAssertEqual(viewModel.state.composerMode, .default)
|
||||
XCTAssertEqual(viewModel.context.plainComposerText, NSAttributedString(string: sharedText))
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
private func setUpViewModel(initialText: String? = nil, loadDraftClosure: (() async -> Result<ComposerDraftProxy?, ComposerDraftServiceError>)? = nil) {
|
||||
wysiwygViewModel = WysiwygComposerViewModel()
|
||||
completionSuggestionServiceMock = CompletionSuggestionServiceMock(configuration: .init())
|
||||
draftServiceMock = ComposerDraftServiceMock()
|
||||
if let loadDraftClosure {
|
||||
draftServiceMock.loadDraftClosure = loadDraftClosure
|
||||
}
|
||||
|
||||
viewModel = ComposerToolbarViewModel(initialText: initialText,
|
||||
wysiwygViewModel: wysiwygViewModel,
|
||||
completionSuggestionService: completionSuggestionServiceMock,
|
||||
mediaProvider: MediaProviderMock(configuration: .init()),
|
||||
mentionDisplayHelper: ComposerMentionDisplayHelper.mock,
|
||||
analyticsService: ServiceLocator.shared.analytics,
|
||||
composerDraftService: draftServiceMock)
|
||||
viewModel.context.composerFormattingEnabled = true
|
||||
}
|
||||
}
|
||||
|
||||
private extension MentionSuggestionItem {
|
||||
|
@ -218,7 +218,7 @@ class RoomFlowCoordinatorTests: XCTestCase {
|
||||
XCTAssert(navigationStackCoordinator.stackCoordinators.first is RoomScreenCoordinator)
|
||||
}
|
||||
|
||||
func testShareRoute() async throws {
|
||||
func testShareMediaRoute() async throws {
|
||||
await setupRoomFlowCoordinator()
|
||||
|
||||
try await process(route: .room(roomID: "1", via: []))
|
||||
@ -243,6 +243,31 @@ class RoomFlowCoordinatorTests: XCTestCase {
|
||||
XCTAssertTrue((navigationStackCoordinator.sheetCoordinator as? NavigationStackCoordinator)?.rootCoordinator is MediaUploadPreviewScreenCoordinator)
|
||||
}
|
||||
|
||||
func testShareTextRoute() async throws {
|
||||
await setupRoomFlowCoordinator()
|
||||
|
||||
try await process(route: .room(roomID: "1", via: []))
|
||||
XCTAssert(navigationStackCoordinator.rootCoordinator is RoomScreenCoordinator)
|
||||
XCTAssertEqual(navigationStackCoordinator.stackCoordinators.count, 0)
|
||||
|
||||
let sharePayload: ShareExtensionPayload = .text(roomID: "1", text: "Important text")
|
||||
try await process(route: .share(sharePayload))
|
||||
|
||||
XCTAssert(navigationStackCoordinator.rootCoordinator is RoomScreenCoordinator)
|
||||
XCTAssertEqual(navigationStackCoordinator.stackCoordinators.count, 0)
|
||||
|
||||
XCTAssertNil(navigationStackCoordinator.sheetCoordinator, "The media upload sheet shouldn't be shown when sharing text.")
|
||||
|
||||
try await process(route: .childRoom(roomID: "2", via: []))
|
||||
XCTAssertNil(navigationStackCoordinator.sheetCoordinator)
|
||||
XCTAssertEqual(navigationStackCoordinator.stackCoordinators.count, 1)
|
||||
|
||||
try await process(route: .share(sharePayload))
|
||||
|
||||
XCTAssertEqual(navigationStackCoordinator.stackCoordinators.count, 0)
|
||||
XCTAssertNil(navigationStackCoordinator.sheetCoordinator, "The media upload sheet shouldn't be shown when sharing text.")
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func process(route: AppRoute) async throws {
|
||||
|
@ -242,7 +242,7 @@ class UserSessionFlowCoordinatorTests: XCTestCase {
|
||||
"A new timeline should be created for the same room ID, so that the screen isn't stale while loading.")
|
||||
}
|
||||
|
||||
func testShareRouteWithoutRoom() async throws {
|
||||
func testShareMediaRouteWithoutRoom() async throws {
|
||||
try await process(route: .settings, expectedState: .settingsScreen(selectedRoomID: nil))
|
||||
XCTAssertTrue((splitCoordinator?.sheetCoordinator as? NavigationStackCoordinator)?.rootCoordinator is SettingsScreenCoordinator)
|
||||
|
||||
@ -253,7 +253,7 @@ class UserSessionFlowCoordinatorTests: XCTestCase {
|
||||
XCTAssertTrue((splitCoordinator?.sheetCoordinator as? NavigationStackCoordinator)?.rootCoordinator is RoomSelectionScreenCoordinator)
|
||||
}
|
||||
|
||||
func testShareRouteWithRoom() async throws {
|
||||
func testShareMediaRouteWithRoom() async throws {
|
||||
try await process(route: .event(eventID: "1", roomID: "1", via: []), expectedState: .roomList(selectedRoomID: "1"))
|
||||
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
|
||||
|
||||
@ -265,6 +265,29 @@ class UserSessionFlowCoordinatorTests: XCTestCase {
|
||||
XCTAssertTrue((splitCoordinator?.sheetCoordinator as? NavigationStackCoordinator)?.rootCoordinator is MediaUploadPreviewScreenCoordinator)
|
||||
}
|
||||
|
||||
func testShareTextRouteWithoutRoom() async throws {
|
||||
try await process(route: .settings, expectedState: .settingsScreen(selectedRoomID: nil))
|
||||
XCTAssertTrue((splitCoordinator?.sheetCoordinator as? NavigationStackCoordinator)?.rootCoordinator is SettingsScreenCoordinator)
|
||||
|
||||
let sharePayload: ShareExtensionPayload = .text(roomID: nil, text: "Important Text")
|
||||
try await process(route: .share(sharePayload),
|
||||
expectedState: .shareExtensionRoomList(sharePayload: sharePayload))
|
||||
|
||||
XCTAssertTrue((splitCoordinator?.sheetCoordinator as? NavigationStackCoordinator)?.rootCoordinator is RoomSelectionScreenCoordinator)
|
||||
}
|
||||
|
||||
func testShareTextRouteWithRoom() async throws {
|
||||
try await process(route: .event(eventID: "1", roomID: "1", via: []), expectedState: .roomList(selectedRoomID: "1"))
|
||||
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
|
||||
|
||||
let sharePayload: ShareExtensionPayload = .text(roomID: "2", text: "Important text")
|
||||
try await process(route: .share(sharePayload),
|
||||
expectedState: .roomList(selectedRoomID: "2"))
|
||||
|
||||
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
|
||||
XCTAssertNil(splitCoordinator?.sheetCoordinator, "The media upload sheet shouldn't be shown when sharing text.")
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func process(route: AppRoute, expectedState: UserSessionFlowCoordinatorStateMachine.State) async throws {
|
||||
|
Loading…
x
Reference in New Issue
Block a user