mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 21:33:26 +00:00
Fixes #2808 - Switch the message sending failure options from a dialog to an alert
* Fixes #2808 - Switch the message sending failure options from a dialog to an alert - this prevents it from showing up as a popup on the room list on an iPad - it used to be set on the timeline and not on the timeline item because of our use of a UITableView * Cleanup room screen action names
This commit is contained in:
@@ -19,16 +19,19 @@ import UIKit
|
||||
|
||||
enum RoomScreenInteractionHandlerAction {
|
||||
case composer(action: RoomScreenComposerAction)
|
||||
case displayError(RoomScreenErrorType)
|
||||
|
||||
case displayEmojiPicker(itemID: TimelineItemIdentifier, selectedEmojis: Set<String>)
|
||||
case displayReportContent(itemID: TimelineItemIdentifier, senderID: String)
|
||||
case displayMessageForwarding(itemID: TimelineItemIdentifier)
|
||||
case displayMediaUploadPreviewScreen(url: URL)
|
||||
case displayPollForm(mode: PollFormMode)
|
||||
case displayRoomMemberDetails(userID: String)
|
||||
|
||||
case showActionMenu(TimelineItemActionMenuInfo)
|
||||
case showDebugInfo(TimelineItemDebugInfo)
|
||||
case showConfirmationAlert(AlertInfo<UUID>)
|
||||
|
||||
case displayAudioRecorderPermissionError
|
||||
case displayErrorToast(String)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
@@ -85,7 +88,7 @@ class RoomScreenInteractionHandler {
|
||||
|
||||
// MARK: Timeline Item Action Menu
|
||||
|
||||
func showTimelineItemActionMenu(for itemID: TimelineItemIdentifier) {
|
||||
func displayTimelineItemActionMenu(for itemID: TimelineItemIdentifier) {
|
||||
Task {
|
||||
if case let .success(value) = await roomProxy.canUserRedactOther(userID: roomProxy.ownUserID) {
|
||||
canCurrentUserRedactOthers = value
|
||||
@@ -186,7 +189,7 @@ class RoomScreenInteractionHandler {
|
||||
return .init(actions: actions, debugActions: debugActions)
|
||||
}
|
||||
|
||||
func processTimelineItemMenuAction(_ action: TimelineItemMenuAction, itemID: TimelineItemIdentifier) {
|
||||
func handleTimelineItemMenuAction(_ action: TimelineItemMenuAction, itemID: TimelineItemIdentifier) {
|
||||
guard let timelineItem = timelineController.timelineItems.firstUsingStableID(itemID),
|
||||
let eventTimelineItem = timelineItem as? EventBasedTimelineItemProtocol else {
|
||||
return
|
||||
@@ -214,13 +217,13 @@ class RoomScreenInteractionHandler {
|
||||
}
|
||||
case .copyPermalink:
|
||||
guard let eventID = eventTimelineItem.id.eventID else {
|
||||
actionsSubject.send(.displayError(.alert(L10n.errorFailedCreatingThePermalink)))
|
||||
actionsSubject.send(.displayErrorToast(L10n.errorFailedCreatingThePermalink))
|
||||
return
|
||||
}
|
||||
|
||||
Task {
|
||||
guard case let .success(permalinkURL) = await roomProxy.matrixToEventPermalink(eventID) else {
|
||||
actionsSubject.send(.displayError(.alert(L10n.errorFailedCreatingThePermalink)))
|
||||
actionsSubject.send(.displayErrorToast(L10n.errorFailedCreatingThePermalink))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -256,7 +259,7 @@ class RoomScreenInteractionHandler {
|
||||
case .report:
|
||||
actionsSubject.send(.displayReportContent(itemID: itemID, senderID: eventTimelineItem.sender.id))
|
||||
case .react:
|
||||
showEmojiPicker(for: itemID)
|
||||
displayEmojiPicker(for: itemID)
|
||||
case .toggleReaction(let key):
|
||||
guard let eventID = itemID.eventID else { return }
|
||||
Task { await roomProxy.timeline.toggleReaction(key, to: eventID) }
|
||||
@@ -294,7 +297,7 @@ class RoomScreenInteractionHandler {
|
||||
case .success:
|
||||
break
|
||||
case .failure:
|
||||
actionsSubject.send(.displayError(.toast(L10n.errorUnknown)))
|
||||
actionsSubject.send(.displayErrorToast(L10n.errorUnknown))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -307,7 +310,7 @@ class RoomScreenInteractionHandler {
|
||||
case .success:
|
||||
break
|
||||
case .failure:
|
||||
actionsSubject.send(.displayError(.toast(L10n.errorUnknown)))
|
||||
actionsSubject.send(.displayErrorToast(L10n.errorUnknown))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -318,7 +321,7 @@ class RoomScreenInteractionHandler {
|
||||
guard let contentType = provider.preferredContentType,
|
||||
let preferredExtension = contentType.preferredFilenameExtension else {
|
||||
MXLog.error("Invalid NSItemProvider: \(provider)")
|
||||
actionsSubject.send(.displayError(.toast(L10n.screenRoomErrorFailedProcessingMedia)))
|
||||
actionsSubject.send(.displayErrorToast(L10n.screenRoomErrorFailedProcessingMedia))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -334,13 +337,13 @@ class RoomScreenInteractionHandler {
|
||||
}
|
||||
|
||||
if let error {
|
||||
self.actionsSubject.send(.displayError(.toast(L10n.screenRoomErrorFailedProcessingMedia)))
|
||||
self.actionsSubject.send(.displayErrorToast(L10n.screenRoomErrorFailedProcessingMedia))
|
||||
MXLog.error("Failed processing NSItemProvider: \(providerDescription) with error: \(error)")
|
||||
return
|
||||
}
|
||||
|
||||
guard let data else {
|
||||
self.actionsSubject.send(.displayError(.toast(L10n.screenRoomErrorFailedProcessingMedia)))
|
||||
self.actionsSubject.send(.displayErrorToast(L10n.screenRoomErrorFailedProcessingMedia))
|
||||
MXLog.error("Invalid NSItemProvider data: \(providerDescription)")
|
||||
return
|
||||
}
|
||||
@@ -359,7 +362,7 @@ class RoomScreenInteractionHandler {
|
||||
|
||||
self.actionsSubject.send(.displayMediaUploadPreviewScreen(url: url))
|
||||
} catch {
|
||||
self.actionsSubject.send(.displayError(.toast(L10n.screenRoomErrorFailedProcessingMedia)))
|
||||
self.actionsSubject.send(.displayErrorToast(L10n.screenRoomErrorFailedProcessingMedia))
|
||||
MXLog.error("Failed storing NSItemProvider data \(providerDescription) with error: \(error)")
|
||||
}
|
||||
}
|
||||
@@ -381,11 +384,7 @@ class RoomScreenInteractionHandler {
|
||||
switch error {
|
||||
case .audioRecorderError(.recordPermissionNotGranted):
|
||||
MXLog.info("permission to record audio has not been granted.")
|
||||
actionsSubject.send(.showConfirmationAlert(.init(id: .init(),
|
||||
title: L10n.dialogPermissionMicrophoneTitleIos(InfoPlistReader.main.bundleDisplayName),
|
||||
message: L10n.dialogPermissionMicrophoneDescriptionIos,
|
||||
primaryButton: .init(title: L10n.commonSettings, action: { [weak self] in self?.openSystemSettings() }),
|
||||
secondaryButton: .init(title: L10n.actionNotNow, role: .cancel, action: nil))))
|
||||
actionsSubject.send(.displayAudioRecorderPermissionError)
|
||||
default:
|
||||
MXLog.error("failed to start voice message recording. \(error)")
|
||||
actionsSubject.send(.composer(action: .setMode(mode: .default)))
|
||||
@@ -427,7 +426,7 @@ class RoomScreenInteractionHandler {
|
||||
|
||||
func sendCurrentVoiceMessage() async {
|
||||
guard let audioPlayerState = voiceMessageRecorder.previewAudioPlayerState, let recordingURL = voiceMessageRecorder.recordingURL else {
|
||||
actionsSubject.send(.displayError(.alert(L10n.errorFailedUploadingVoiceMessage)))
|
||||
actionsSubject.send(.displayErrorToast(L10n.errorFailedUploadingVoiceMessage))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -445,7 +444,7 @@ class RoomScreenInteractionHandler {
|
||||
case .failure(let error):
|
||||
MXLog.error("failed to send the voice message. \(error)")
|
||||
actionsSubject.send(.composer(action: .setMode(mode: .previewVoiceMessage(state: audioPlayerState, waveform: .url(recordingURL), isUploading: false))))
|
||||
actionsSubject.send(.displayError(.alert(L10n.errorFailedUploadingVoiceMessage)))
|
||||
actionsSubject.send(.displayErrorToast(L10n.errorFailedUploadingVoiceMessage))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -578,7 +577,7 @@ class RoomScreenInteractionHandler {
|
||||
|
||||
// MARK: Other
|
||||
|
||||
func showEmojiPicker(for itemID: TimelineItemIdentifier) {
|
||||
func displayEmojiPicker(for itemID: TimelineItemIdentifier) {
|
||||
guard let timelineItem = timelineController.timelineItems.firstUsingStableID(itemID),
|
||||
timelineItem.isReactable,
|
||||
let eventTimelineItem = timelineItem as? EventBasedTimelineItemProtocol else {
|
||||
@@ -588,7 +587,7 @@ class RoomScreenInteractionHandler {
|
||||
actionsSubject.send(.displayEmojiPicker(itemID: itemID, selectedEmojis: selectedEmojis))
|
||||
}
|
||||
|
||||
func handleTappedUser(userID: String) async {
|
||||
func displayRoomMemberDetails(userID: String) async {
|
||||
actionsSubject.send(.displayRoomMemberDetails(userID: userID))
|
||||
}
|
||||
|
||||
@@ -623,10 +622,6 @@ class RoomScreenInteractionHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private func openSystemSettings() {
|
||||
appMediator.openAppSettings()
|
||||
}
|
||||
|
||||
private func displayMediaActionIfPossible(timelineItem: RoomTimelineItemProtocol) async -> RoomTimelineControllerAction {
|
||||
var source: MediaSourceProxy?
|
||||
var body: String
|
||||
|
||||
@@ -68,14 +68,12 @@ enum RoomScreenViewPollAction {
|
||||
case edit(pollStartID: String, poll: Poll)
|
||||
}
|
||||
|
||||
enum RoomScreenViewAudioAction {
|
||||
enum RoomScreenAudioPlayerAction {
|
||||
case playPause(itemID: TimelineItemIdentifier)
|
||||
case seek(itemID: TimelineItemIdentifier, progress: Double)
|
||||
}
|
||||
|
||||
enum RoomScreenViewAction {
|
||||
case displayRoomDetails
|
||||
|
||||
case itemAppeared(itemID: TimelineItemIdentifier)
|
||||
case itemDisappeared(itemID: TimelineItemIdentifier)
|
||||
|
||||
@@ -86,27 +84,20 @@ enum RoomScreenViewAction {
|
||||
case paginateForwards
|
||||
case scrollToBottom
|
||||
|
||||
case timelineItemMenu(itemID: TimelineItemIdentifier)
|
||||
case timelineItemMenuAction(itemID: TimelineItemIdentifier, action: TimelineItemMenuAction)
|
||||
case displayTimelineItemMenu(itemID: TimelineItemIdentifier)
|
||||
case handleTimelineItemMenuAction(itemID: TimelineItemIdentifier, action: TimelineItemMenuAction)
|
||||
|
||||
case displayRoomDetails
|
||||
case displayRoomMemberDetails(userID: String)
|
||||
case displayReactionSummary(itemID: TimelineItemIdentifier, key: String)
|
||||
case displayEmojiPicker(itemID: TimelineItemIdentifier)
|
||||
case displayMessageSendingFailureAlert(itemID: TimelineItemIdentifier)
|
||||
case displayReadReceipts(itemID: TimelineItemIdentifier)
|
||||
case displayCall
|
||||
|
||||
case handlePasteOrDrop(provider: NSItemProvider)
|
||||
|
||||
case tappedOnUser(userID: String)
|
||||
|
||||
case reactionSummary(itemID: TimelineItemIdentifier, key: String)
|
||||
|
||||
case retrySend(itemID: TimelineItemIdentifier)
|
||||
case cancelSend(itemID: TimelineItemIdentifier)
|
||||
|
||||
case showReadReceipts(itemID: TimelineItemIdentifier)
|
||||
|
||||
case poll(RoomScreenViewPollAction)
|
||||
|
||||
case audio(RoomScreenViewAudioAction)
|
||||
|
||||
case presentCall
|
||||
case handlePollAction(RoomScreenViewPollAction)
|
||||
case handleAudioPlayerAction(RoomScreenAudioPlayerAction)
|
||||
|
||||
/// Focus the timeline onto the specified event ID (switching to a detached timeline if needed).
|
||||
case focusOnEventID(String)
|
||||
@@ -161,18 +152,12 @@ struct RoomScreenViewStateBindings {
|
||||
/// A media item that will be previewed with QuickLook.
|
||||
var mediaPreviewItem: MediaPreviewItem?
|
||||
|
||||
/// Information describing the currently displayed alert.
|
||||
var alertInfo: AlertInfo<RoomScreenErrorType>?
|
||||
|
||||
/// An alert info for confirmation actions (e.g. ending a poll)
|
||||
var confirmationAlertInfo: AlertInfo<UUID>?
|
||||
var alertInfo: AlertInfo<RoomScreenAlertInfoType>?
|
||||
|
||||
var debugInfo: TimelineItemDebugInfo?
|
||||
|
||||
var actionMenuInfo: TimelineItemActionMenuInfo?
|
||||
|
||||
var sendFailedConfirmationDialogInfo: SendFailedConfirmationDialogInfo?
|
||||
|
||||
var reactionSummaryInfo: ReactionSummaryInfo?
|
||||
|
||||
var readReceiptsSummaryInfo: ReadReceiptSummaryInfo?
|
||||
@@ -190,9 +175,7 @@ struct TimelineItemActionMenuInfo: Equatable, Identifiable {
|
||||
}
|
||||
}
|
||||
|
||||
struct SendFailedConfirmationDialogInfo: ConfirmationDialogProtocol {
|
||||
let title = L10n.screenRoomRetrySendMenuTitle
|
||||
|
||||
struct MessageSendingFailureInfo: Hashable {
|
||||
let itemID: TimelineItemIdentifier
|
||||
}
|
||||
|
||||
@@ -210,11 +193,10 @@ struct ReadReceiptSummaryInfo: Identifiable {
|
||||
let id: TimelineItemIdentifier
|
||||
}
|
||||
|
||||
enum RoomScreenErrorType: Hashable {
|
||||
/// A specific error message shown in an alert.
|
||||
case alert(String)
|
||||
/// A specific error message shown in a toast.
|
||||
case toast(String)
|
||||
enum RoomScreenAlertInfoType: Hashable {
|
||||
case audioRecodingPermissionError
|
||||
case pollEndConfirmation(String)
|
||||
case messageSendingFailure(TimelineItemIdentifier)
|
||||
}
|
||||
|
||||
struct RoomMemberState {
|
||||
|
||||
@@ -143,48 +143,51 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
||||
|
||||
override func process(viewAction: RoomScreenViewAction) {
|
||||
switch viewAction {
|
||||
case .displayRoomDetails:
|
||||
actionsSubject.send(.displayRoomDetails)
|
||||
case .itemAppeared(let id):
|
||||
Task { await timelineController.processItemAppearance(id) }
|
||||
case .itemDisappeared(let id):
|
||||
Task { await timelineController.processItemDisappearance(id) }
|
||||
|
||||
case .itemTapped(let id):
|
||||
Task { await handleItemTapped(with: id) }
|
||||
case .toggleReaction(let emoji, let itemId):
|
||||
Task { await timelineController.toggleReaction(emoji, to: itemId) }
|
||||
case .sendReadReceiptIfNeeded(let lastVisibleItemID):
|
||||
Task { await sendReadReceiptIfNeeded(for: lastVisibleItemID) }
|
||||
case .timelineItemMenu(let itemID):
|
||||
roomScreenInteractionHandler.showTimelineItemActionMenu(for: itemID)
|
||||
case .timelineItemMenuAction(let itemID, let action):
|
||||
roomScreenInteractionHandler.processTimelineItemMenuAction(action, itemID: itemID)
|
||||
case .handlePasteOrDrop(let provider):
|
||||
roomScreenInteractionHandler.handlePasteOrDrop(provider)
|
||||
case .tappedOnUser(userID: let userID):
|
||||
Task { await roomScreenInteractionHandler.handleTappedUser(userID: userID) }
|
||||
case .displayEmojiPicker(let itemID):
|
||||
roomScreenInteractionHandler.showEmojiPicker(for: itemID)
|
||||
case .reactionSummary(let itemID, let key):
|
||||
showReactionSummary(for: itemID, selectedKey: key)
|
||||
case .retrySend(let itemID):
|
||||
Task { await timelineController.retrySending(itemID: itemID) }
|
||||
case .cancelSend(let itemID):
|
||||
Task { await timelineController.cancelSending(itemID: itemID) }
|
||||
case .paginateBackwards:
|
||||
paginateBackwards()
|
||||
case .paginateForwards:
|
||||
paginateForwards()
|
||||
case .scrollToBottom:
|
||||
scrollToBottom()
|
||||
case .poll(let pollAction):
|
||||
processPollAction(pollAction)
|
||||
case .audio(let audioAction):
|
||||
processAudioAction(audioAction)
|
||||
case .presentCall:
|
||||
|
||||
case .displayTimelineItemMenu(let itemID):
|
||||
roomScreenInteractionHandler.displayTimelineItemActionMenu(for: itemID)
|
||||
case .handleTimelineItemMenuAction(let itemID, let action):
|
||||
roomScreenInteractionHandler.handleTimelineItemMenuAction(action, itemID: itemID)
|
||||
|
||||
case .displayRoomDetails:
|
||||
actionsSubject.send(.displayRoomDetails)
|
||||
case .displayRoomMemberDetails(userID: let userID):
|
||||
Task { await roomScreenInteractionHandler.displayRoomMemberDetails(userID: userID) }
|
||||
case .displayEmojiPicker(let itemID):
|
||||
roomScreenInteractionHandler.displayEmojiPicker(for: itemID)
|
||||
case .displayReactionSummary(let itemID, let key):
|
||||
displayReactionSummary(for: itemID, selectedKey: key)
|
||||
case .displayMessageSendingFailureAlert(let itemID):
|
||||
displayAlert(.messageSendingFailure(itemID))
|
||||
case .displayReadReceipts(itemID: let itemID):
|
||||
displayReadReceipts(for: itemID)
|
||||
case .displayCall:
|
||||
actionsSubject.send(.displayCallScreen)
|
||||
case .showReadReceipts(itemID: let itemID):
|
||||
showReadReceipts(for: itemID)
|
||||
|
||||
case .handlePasteOrDrop(let provider):
|
||||
roomScreenInteractionHandler.handlePasteOrDrop(provider)
|
||||
case .handlePollAction(let pollAction):
|
||||
handlePollAction(pollAction)
|
||||
case .handleAudioPlayerAction(let audioPlayerAction):
|
||||
handleAudioPlayerAction(audioPlayerAction)
|
||||
|
||||
case .focusOnEventID(let eventID):
|
||||
Task { await focusOnEvent(eventID: eventID) }
|
||||
case .focusLive:
|
||||
@@ -245,9 +248,9 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
||||
MXLog.error("Failed to focus on event \(eventID)")
|
||||
|
||||
if case .eventNotFound = error {
|
||||
displayError(.toast(L10n.errorMessageNotFound))
|
||||
displayErrorToast(L10n.errorMessageNotFound)
|
||||
} else {
|
||||
displayError(.toast(L10n.commonFailed))
|
||||
displayErrorToast(L10n.commonFailed)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -277,7 +280,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
||||
return
|
||||
}
|
||||
|
||||
roomScreenInteractionHandler.processTimelineItemMenuAction(.edit, itemID: item.id)
|
||||
roomScreenInteractionHandler.handleTimelineItemMenuAction(.edit, itemID: item.id)
|
||||
}
|
||||
|
||||
private func attach(_ attachment: ComposerAttachmentType) {
|
||||
@@ -295,22 +298,18 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
||||
}
|
||||
}
|
||||
|
||||
private func processPollAction(_ action: RoomScreenViewPollAction) {
|
||||
private func handlePollAction(_ action: RoomScreenViewPollAction) {
|
||||
switch action {
|
||||
case let .selectOption(pollStartID, optionID):
|
||||
roomScreenInteractionHandler.sendPollResponse(pollStartID: pollStartID, optionID: optionID)
|
||||
case let .end(pollStartID):
|
||||
state.bindings.confirmationAlertInfo = .init(id: .init(),
|
||||
title: L10n.actionEndPoll,
|
||||
message: L10n.commonPollEndConfirmation,
|
||||
primaryButton: .init(title: L10n.actionCancel, role: .cancel, action: nil),
|
||||
secondaryButton: .init(title: L10n.actionOk, action: { self.roomScreenInteractionHandler.endPoll(pollStartID: pollStartID) }))
|
||||
displayAlert(.pollEndConfirmation(pollStartID))
|
||||
case .edit(let pollStartID, let poll):
|
||||
actionsSubject.send(.displayPollForm(mode: .edit(eventID: pollStartID, poll: poll)))
|
||||
}
|
||||
}
|
||||
|
||||
private func processAudioAction(_ action: RoomScreenViewAudioAction) {
|
||||
private func handleAudioPlayerAction(_ action: RoomScreenAudioPlayerAction) {
|
||||
switch action {
|
||||
case .playPause(let itemID):
|
||||
Task { await roomScreenInteractionHandler.playPauseAudio(for: itemID) }
|
||||
@@ -424,8 +423,10 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
||||
switch action {
|
||||
case .composer(let action):
|
||||
actionsSubject.send(.composer(action: action))
|
||||
case .displayError(let type):
|
||||
displayError(type)
|
||||
case .displayAudioRecorderPermissionError:
|
||||
displayAlert(.audioRecodingPermissionError)
|
||||
case .displayErrorToast(let title):
|
||||
displayErrorToast(title)
|
||||
case .displayEmojiPicker(let itemID, let selectedEmojis):
|
||||
actionsSubject.send(.displayEmojiPicker(itemID: itemID, selectedEmojis: selectedEmojis))
|
||||
case .displayMessageForwarding(let itemID):
|
||||
@@ -442,8 +443,6 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
||||
state.bindings.actionMenuInfo = actionMenuInfo
|
||||
case .showDebugInfo(let debugInfo):
|
||||
state.bindings.debugInfo = debugInfo
|
||||
case .showConfirmationAlert(let alertInfo):
|
||||
state.bindings.confirmationAlertInfo = alertInfo
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
@@ -483,7 +482,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
||||
|
||||
switch await timelineController.paginateBackwards(requestSize: Constants.paginationEventLimit) {
|
||||
case .failure:
|
||||
displayError(.toast(L10n.errorFailedLoadingMessages))
|
||||
displayErrorToast(L10n.errorFailedLoadingMessages)
|
||||
default:
|
||||
break
|
||||
}
|
||||
@@ -503,7 +502,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
||||
|
||||
switch await timelineController.paginateForwards(requestSize: Constants.paginationEventLimit) {
|
||||
case .failure:
|
||||
displayError(.toast(L10n.errorFailedLoadingMessages))
|
||||
displayErrorToast(L10n.errorFailedLoadingMessages)
|
||||
default:
|
||||
break
|
||||
}
|
||||
@@ -718,7 +717,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
||||
|
||||
// MARK: - Reactions
|
||||
|
||||
private func showReactionSummary(for itemID: TimelineItemIdentifier, selectedKey: String) {
|
||||
private func displayReactionSummary(for itemID: TimelineItemIdentifier, selectedKey: String) {
|
||||
guard let timelineItem = timelineController.timelineItems.firstUsingStableID(itemID),
|
||||
let eventTimelineItem = timelineItem as? EventBasedTimelineItemProtocol else {
|
||||
return
|
||||
@@ -729,7 +728,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
||||
|
||||
// MARK: - Read Receipts
|
||||
|
||||
private func showReadReceipts(for itemID: TimelineItemIdentifier) {
|
||||
private func displayReadReceipts(for itemID: TimelineItemIdentifier) {
|
||||
guard let timelineItem = timelineController.timelineItems.firstUsingStableID(itemID),
|
||||
let eventTimelineItem = timelineItem as? EventBasedTimelineItemProtocol else {
|
||||
return
|
||||
@@ -737,7 +736,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
||||
|
||||
state.bindings.readReceiptsSummaryInfo = .init(orderedReceipts: eventTimelineItem.properties.orderedReadReceipts, id: eventTimelineItem.id)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Message forwarding
|
||||
|
||||
private func forwardMessage(itemID: TimelineItemIdentifier) async {
|
||||
@@ -758,19 +757,38 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
||||
userIndicatorController.retractIndicatorWithId(Constants.focusTimelineToastIndicatorID)
|
||||
}
|
||||
|
||||
private func displayError(_ type: RoomScreenErrorType) {
|
||||
private func displayAlert(_ type: RoomScreenAlertInfoType) {
|
||||
switch type {
|
||||
case .alert(let message):
|
||||
state.bindings.alertInfo = AlertInfo(id: type,
|
||||
title: L10n.commonError,
|
||||
message: message)
|
||||
case .toast(let message):
|
||||
userIndicatorController.submitIndicator(UserIndicator(id: Constants.toastErrorID,
|
||||
type: .toast,
|
||||
title: message,
|
||||
iconName: "xmark"))
|
||||
case .audioRecodingPermissionError:
|
||||
state.bindings.alertInfo = .init(id: type,
|
||||
title: L10n.dialogPermissionMicrophoneTitleIos(InfoPlistReader.main.bundleDisplayName),
|
||||
message: L10n.dialogPermissionMicrophoneDescriptionIos,
|
||||
primaryButton: .init(title: L10n.commonSettings, action: { [weak self] in self?.appMediator.openAppSettings() }),
|
||||
secondaryButton: .init(title: L10n.actionNotNow, role: .cancel, action: nil))
|
||||
case .pollEndConfirmation(let pollStartID):
|
||||
state.bindings.alertInfo = .init(id: type,
|
||||
title: L10n.actionEndPoll,
|
||||
message: L10n.commonPollEndConfirmation,
|
||||
primaryButton: .init(title: L10n.actionCancel, role: .cancel, action: nil),
|
||||
secondaryButton: .init(title: L10n.actionOk, action: { self.roomScreenInteractionHandler.endPoll(pollStartID: pollStartID) }))
|
||||
case .messageSendingFailure(let itemID):
|
||||
state.bindings.alertInfo = .init(id: type,
|
||||
title: L10n.screenRoomRetrySendMenuTitle,
|
||||
primaryButton: .init(title: L10n.screenRoomRetrySendMenuSendAgainAction) {
|
||||
Task { await self.timelineController.retrySending(itemID: itemID) }
|
||||
},
|
||||
secondaryButton: .init(title: L10n.actionRemove, role: .destructive) {
|
||||
Task { await self.timelineController.cancelSending(itemID: itemID) }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private func displayErrorToast(_ title: String) {
|
||||
userIndicatorController.submitIndicator(UserIndicator(id: Constants.toastErrorID,
|
||||
type: .toast,
|
||||
title: title,
|
||||
iconName: "xmark"))
|
||||
}
|
||||
}
|
||||
|
||||
private extension RoomProxyProtocol {
|
||||
|
||||
@@ -55,7 +55,6 @@ struct RoomScreen: View {
|
||||
.toolbarBackground(.visible, for: .navigationBar) // Fix the toolbar's background.
|
||||
.overlay { loadingIndicator }
|
||||
.alert(item: $context.alertInfo)
|
||||
.alert(item: $context.confirmationAlertInfo)
|
||||
.sheet(item: $context.debugInfo) { TimelineItemDebugView(info: $0) }
|
||||
.sheet(item: $context.actionMenuInfo) { info in
|
||||
context.viewState.timelineItemMenuActionProvider?(info.item.id).map { actions in
|
||||
@@ -82,14 +81,6 @@ struct RoomScreen: View {
|
||||
context.send(viewAction: .handlePasteOrDrop(provider: provider))
|
||||
return true
|
||||
}
|
||||
.confirmationDialog(item: $context.sendFailedConfirmationDialogInfo, titleVisibility: .visible) { info in
|
||||
Button(L10n.screenRoomRetrySendMenuSendAgainAction) {
|
||||
context.send(viewAction: .retrySend(itemID: info.itemID))
|
||||
}
|
||||
Button(L10n.actionRemove, role: .destructive) {
|
||||
context.send(viewAction: .cancelSend(itemID: info.itemID))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var timeline: some View {
|
||||
@@ -168,7 +159,7 @@ struct RoomScreen: View {
|
||||
private var callButton: some View {
|
||||
if context.viewState.hasOngoingCall {
|
||||
Button {
|
||||
context.send(viewAction: .presentCall)
|
||||
context.send(viewAction: .displayCall)
|
||||
} label: {
|
||||
Label(L10n.actionJoin, icon: \.videoCallSolid)
|
||||
.labelStyle(.titleAndIcon)
|
||||
@@ -177,7 +168,7 @@ struct RoomScreen: View {
|
||||
.accessibilityIdentifier(A11yIdentifiers.roomScreen.joinCall)
|
||||
} else {
|
||||
Button {
|
||||
context.send(viewAction: .presentCall)
|
||||
context.send(viewAction: .displayCall)
|
||||
} label: {
|
||||
CompoundIcon(\.videoCallSolid)
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ struct TimelineItemBubbledStylerView<Content: View>: View {
|
||||
// sender info are read inside the `TimelineAccessibilityModifier`
|
||||
.accessibilityHidden(true)
|
||||
.onTapGesture {
|
||||
context.send(viewAction: .tappedOnUser(userID: timelineItem.sender.id))
|
||||
context.send(viewAction: .displayRoomMemberDetails(userID: timelineItem.sender.id))
|
||||
}
|
||||
.padding(.top, 8)
|
||||
}
|
||||
@@ -111,9 +111,9 @@ struct TimelineItemBubbledStylerView<Content: View>: View {
|
||||
messageBubble
|
||||
.timelineItemAccessibility(timelineItem) {
|
||||
if adjustedDeliveryStatus == .sendingFailed {
|
||||
context.sendFailedConfirmationDialogInfo = .init(itemID: timelineItem.id)
|
||||
context.send(viewAction: .displayMessageSendingFailureAlert(itemID: timelineItem.id))
|
||||
} else {
|
||||
context.send(viewAction: .timelineItemMenu(itemID: timelineItem.id))
|
||||
context.send(viewAction: .displayTimelineItemMenu(itemID: timelineItem.id))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,7 +139,7 @@ struct TimelineItemBubbledStylerView<Content: View>: View {
|
||||
// We need a tap gesture before this long one so that it doesn't
|
||||
// steal away the gestures from the scroll view
|
||||
.longPressWithFeedback {
|
||||
context.send(viewAction: .timelineItemMenu(itemID: timelineItem.id))
|
||||
context.send(viewAction: .displayTimelineItemMenu(itemID: timelineItem.id))
|
||||
}
|
||||
.swipeRightAction {
|
||||
SwipeToReplyView(timelineItem: timelineItem)
|
||||
@@ -147,12 +147,12 @@ struct TimelineItemBubbledStylerView<Content: View>: View {
|
||||
context.viewState.timelineItemMenuActionProvider?(timelineItem.id)?.canReply ?? false
|
||||
} action: {
|
||||
let isThread = (timelineItem as? EventBasedMessageTimelineItemProtocol)?.isThreaded ?? false
|
||||
context.send(viewAction: .timelineItemMenuAction(itemID: timelineItem.id, action: .reply(isThread: isThread)))
|
||||
context.send(viewAction: .handleTimelineItemMenuAction(itemID: timelineItem.id, action: .reply(isThread: isThread)))
|
||||
}
|
||||
.contextMenu {
|
||||
TimelineItemMacContextMenu(item: timelineItem,
|
||||
actionProvider: context.viewState.timelineItemMenuActionProvider) { action in
|
||||
context.send(viewAction: .timelineItemMenuAction(itemID: timelineItem.id, action: action))
|
||||
context.send(viewAction: .handleTimelineItemMenuAction(itemID: timelineItem.id, action: action))
|
||||
}
|
||||
}
|
||||
.padding(.top, messageBubbleTopPadding)
|
||||
@@ -180,7 +180,7 @@ struct TimelineItemBubbledStylerView<Content: View>: View {
|
||||
if adjustedDeliveryStatus == .sendingFailed {
|
||||
layoutedLocalizedSendInfo
|
||||
.onTapGesture {
|
||||
context.sendFailedConfirmationDialogInfo = .init(itemID: timelineItem.id)
|
||||
context.send(viewAction: .displayMessageSendingFailureAlert(itemID: timelineItem.id))
|
||||
}
|
||||
} else {
|
||||
layoutedLocalizedSendInfo
|
||||
|
||||
@@ -69,7 +69,7 @@ struct TimelineItemPlainStylerView<Content: View>: View {
|
||||
content()
|
||||
.layoutPriority(1)
|
||||
.timelineItemAccessibility(timelineItem) {
|
||||
context.send(viewAction: .timelineItemMenu(itemID: timelineItem.id))
|
||||
context.send(viewAction: .displayTimelineItemMenu(itemID: timelineItem.id))
|
||||
}
|
||||
}
|
||||
.onTapGesture(count: 2) {
|
||||
@@ -81,7 +81,7 @@ struct TimelineItemPlainStylerView<Content: View>: View {
|
||||
// We need a tap gesture before this long one so that it doesn't
|
||||
// steal away the gestures from the scroll view
|
||||
.longPressWithFeedback {
|
||||
context.send(viewAction: .timelineItemMenu(itemID: timelineItem.id))
|
||||
context.send(viewAction: .displayTimelineItemMenu(itemID: timelineItem.id))
|
||||
}
|
||||
.swipeRightAction {
|
||||
SwipeToReplyView(timelineItem: timelineItem)
|
||||
@@ -89,12 +89,12 @@ struct TimelineItemPlainStylerView<Content: View>: View {
|
||||
context.viewState.timelineItemMenuActionProvider?(timelineItem.id)?.canReply ?? false
|
||||
} action: {
|
||||
let isThread = (timelineItem as? EventBasedMessageTimelineItemProtocol)?.isThreaded ?? false
|
||||
context.send(viewAction: .timelineItemMenuAction(itemID: timelineItem.id, action: .reply(isThread: isThread)))
|
||||
context.send(viewAction: .handleTimelineItemMenuAction(itemID: timelineItem.id, action: .reply(isThread: isThread)))
|
||||
}
|
||||
.contextMenu {
|
||||
TimelineItemMacContextMenu(item: timelineItem,
|
||||
actionProvider: context.viewState.timelineItemMenuActionProvider) { action in
|
||||
context.send(viewAction: .timelineItemMenuAction(itemID: timelineItem.id, action: action))
|
||||
context.send(viewAction: .handleTimelineItemMenuAction(itemID: timelineItem.id, action: action))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -111,7 +111,7 @@ struct TimelineItemPlainStylerView<Content: View>: View {
|
||||
.lineLimit(1)
|
||||
}
|
||||
.onTapGesture {
|
||||
context.send(viewAction: .tappedOnUser(userID: timelineItem.sender.id))
|
||||
context.send(viewAction: .displayRoomMemberDetails(userID: timelineItem.sender.id))
|
||||
}
|
||||
Spacer()
|
||||
Text(timelineItem.timestamp)
|
||||
|
||||
@@ -56,7 +56,7 @@ struct TimelineItemStatusView: View {
|
||||
CompoundIcon(\.error, size: .xSmall, relativeTo: .compound.bodyMD)
|
||||
.foregroundColor(.compound.iconCriticalPrimary)
|
||||
.onTapGesture {
|
||||
context.sendFailedConfirmationDialogInfo = .init(itemID: timelineItem.id)
|
||||
context.send(viewAction: .displayMessageSendingFailureAlert(itemID: timelineItem.id))
|
||||
}
|
||||
.accessibilityLabel(L10n.commonSendingFailed)
|
||||
.accessibilityHint(L10n.actionTapForOptions)
|
||||
|
||||
@@ -57,7 +57,7 @@ struct TimelineReactionsView: View {
|
||||
feedbackGenerator.impactOccurred()
|
||||
context.send(viewAction: .toggleReaction(key: key, itemID: itemID))
|
||||
} showReactionSummary: { key in
|
||||
context.send(viewAction: .reactionSummary(itemID: itemID, key: key))
|
||||
context.send(viewAction: .displayReactionSummary(itemID: itemID, key: key))
|
||||
}
|
||||
.reactionLayoutItem(.reaction)
|
||||
.environment(\.layoutDirection, layoutDirection)
|
||||
|
||||
+1
-1
@@ -46,7 +46,7 @@ struct TimelineReadReceiptsView: View {
|
||||
}
|
||||
}
|
||||
.onTapGesture {
|
||||
context.send(viewAction: .showReadReceipts(itemID: timelineItem.id))
|
||||
context.send(viewAction: .displayReadReceipts(itemID: timelineItem.id))
|
||||
}
|
||||
.accessibilityElement(children: .ignore)
|
||||
.accessibilityLabel(accessibilityLabel)
|
||||
|
||||
@@ -26,13 +26,13 @@ struct PollRoomTimelineView: View {
|
||||
switch action {
|
||||
case .selectOption(let optionID):
|
||||
guard let eventID, let option = poll.options.first(where: { $0.id == optionID }), !option.isSelected else { return }
|
||||
context.send(viewAction: .poll(.selectOption(pollStartID: eventID, optionID: option.id)))
|
||||
context.send(viewAction: .handlePollAction(.selectOption(pollStartID: eventID, optionID: option.id)))
|
||||
case .edit:
|
||||
guard let eventID else { return }
|
||||
context.send(viewAction: .poll(.edit(pollStartID: eventID, poll: poll)))
|
||||
context.send(viewAction: .handlePollAction(.edit(pollStartID: eventID, poll: poll)))
|
||||
case .end:
|
||||
guard let eventID else { return }
|
||||
context.send(viewAction: .poll(.end(pollStartID: eventID)))
|
||||
context.send(viewAction: .handlePollAction(.end(pollStartID: eventID)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -298,7 +298,7 @@ struct TimelineItemMenu: View {
|
||||
dismiss()
|
||||
// Otherwise we might get errors that a sheet is already presented
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||
context.send(viewAction: .timelineItemMenuAction(itemID: item.id, action: action))
|
||||
context.send(viewAction: .handleTimelineItemMenuAction(itemID: item.id, action: action))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+4
-4
@@ -40,22 +40,22 @@ struct VoiceMessageRoomTimelineView: View {
|
||||
}
|
||||
|
||||
private func onPlaybackPlayPause() {
|
||||
context.send(viewAction: .audio(.playPause(itemID: timelineItem.id)))
|
||||
context.send(viewAction: .handleAudioPlayerAction(.playPause(itemID: timelineItem.id)))
|
||||
}
|
||||
|
||||
private func onPlaybackSeek(_ progress: Double) {
|
||||
context.send(viewAction: .audio(.seek(itemID: timelineItem.id, progress: progress)))
|
||||
context.send(viewAction: .handleAudioPlayerAction(.seek(itemID: timelineItem.id, progress: progress)))
|
||||
}
|
||||
|
||||
private func onPlaybackScrubbing(_ dragging: Bool) {
|
||||
if dragging {
|
||||
if playerState.playbackState == .playing {
|
||||
resumePlaybackAfterScrubbing = true
|
||||
context.send(viewAction: .audio(.playPause(itemID: timelineItem.id)))
|
||||
context.send(viewAction: .handleAudioPlayerAction(.playPause(itemID: timelineItem.id)))
|
||||
}
|
||||
} else {
|
||||
if resumePlaybackAfterScrubbing {
|
||||
context.send(viewAction: .audio(.playPause(itemID: timelineItem.id)))
|
||||
context.send(viewAction: .handleAudioPlayerAction(.playPause(itemID: timelineItem.id)))
|
||||
resumePlaybackAfterScrubbing = false
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user