mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 06:55:59 +00:00
Pinned Events Timeline actions and differentiation (#3182)
This commit is contained in:
@@ -63,6 +63,7 @@ enum TimelineItemMenuAction: Identifiable, Hashable {
|
||||
case endPoll(pollStartID: String)
|
||||
case pin
|
||||
case unpin
|
||||
case viewInRoomTimeline
|
||||
|
||||
var id: Self { self }
|
||||
|
||||
@@ -106,6 +107,15 @@ enum TimelineItemMenuAction: Identifiable, Hashable {
|
||||
}
|
||||
}
|
||||
|
||||
var canAppearInPinnedEventsTimeline: Bool {
|
||||
switch self {
|
||||
case .viewInRoomTimeline, .pin, .unpin, .forward, .redact:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/// The action's label.
|
||||
@ViewBuilder
|
||||
var label: some View {
|
||||
@@ -139,6 +149,8 @@ enum TimelineItemMenuAction: Identifiable, Hashable {
|
||||
Label(L10n.actionPin, icon: \.pin)
|
||||
case .unpin:
|
||||
Label(L10n.actionUnpin, icon: \.unpin)
|
||||
case .viewInRoomTimeline:
|
||||
Label(L10n.actionViewInTimeline, icon: \.visibilityOn)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+7
-1
@@ -24,6 +24,7 @@ struct TimelineItemMenuActionProvider {
|
||||
let pinnedEventIDs: Set<String>
|
||||
let isDM: Bool
|
||||
let isViewSourceEnabled: Bool
|
||||
let isPinnedEventsTimeline: Bool
|
||||
|
||||
// swiftlint:disable:next cyclomatic_complexity
|
||||
func makeActions() -> TimelineItemMenuActions? {
|
||||
@@ -102,8 +103,13 @@ struct TimelineItemMenuActionProvider {
|
||||
if item.isRedacted {
|
||||
actions = actions.filter(\.canAppearInRedacted)
|
||||
}
|
||||
|
||||
if isPinnedEventsTimeline {
|
||||
actions.insert(.viewInRoomTimeline, at: 0)
|
||||
actions = actions.filter(\.canAppearInPinnedEventsTimeline)
|
||||
}
|
||||
|
||||
return .init(isReactable: item.isReactable, actions: actions, debugActions: debugActions)
|
||||
return .init(isReactable: isPinnedEventsTimeline ? false : item.isReactable, actions: actions, debugActions: debugActions)
|
||||
}
|
||||
|
||||
private func canRedactItem(_ item: EventBasedTimelineItemProtocol) -> Bool {
|
||||
|
||||
@@ -23,21 +23,48 @@ enum PollViewAction {
|
||||
case end
|
||||
}
|
||||
|
||||
enum PollViewState {
|
||||
case preview
|
||||
case full(isEditable: Bool)
|
||||
|
||||
var isPreview: Bool {
|
||||
switch self {
|
||||
case .preview:
|
||||
return true
|
||||
case .full:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
var isEditable: Bool {
|
||||
switch self {
|
||||
case .preview:
|
||||
return false
|
||||
case .full(let isEditable):
|
||||
return isEditable
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct PollView: View {
|
||||
private let feedbackGenerator = UIImpactFeedbackGenerator(style: .heavy)
|
||||
|
||||
let poll: Poll
|
||||
let editable: Bool
|
||||
let state: PollViewState
|
||||
let actionHandler: (PollViewAction) -> Void
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
if state.isPreview {
|
||||
questionView
|
||||
optionsView
|
||||
summaryView
|
||||
toolbarView
|
||||
} else {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
questionView
|
||||
optionsView
|
||||
summaryView
|
||||
toolbarView
|
||||
}
|
||||
.frame(maxWidth: 450)
|
||||
}
|
||||
.frame(maxWidth: 450)
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
@@ -88,7 +115,7 @@ struct PollView: View {
|
||||
Button {
|
||||
toolbarAction()
|
||||
} label: {
|
||||
Text(editable ? L10n.actionEditPoll : L10n.actionEndPoll)
|
||||
Text(state.isEditable ? L10n.actionEditPoll : L10n.actionEndPoll)
|
||||
.lineLimit(2, reservesSpace: false)
|
||||
.font(.compound.bodyLGSemibold)
|
||||
.foregroundColor(.compound.textOnSolidPrimary)
|
||||
@@ -105,7 +132,7 @@ struct PollView: View {
|
||||
}
|
||||
|
||||
private func toolbarAction() {
|
||||
if editable {
|
||||
if state.isEditable {
|
||||
actionHandler(.edit)
|
||||
} else {
|
||||
actionHandler(.end)
|
||||
@@ -146,28 +173,32 @@ private extension Poll {
|
||||
|
||||
struct PollView_Previews: PreviewProvider, TestablePreview {
|
||||
static var previews: some View {
|
||||
PollView(poll: .disclosed(), editable: false) { _ in }
|
||||
PollView(poll: .disclosed(), state: .full(isEditable: false)) { _ in }
|
||||
.padding()
|
||||
.previewDisplayName("Disclosed")
|
||||
|
||||
PollView(poll: .undisclosed(), editable: false) { _ in }
|
||||
PollView(poll: .undisclosed(), state: .full(isEditable: false)) { _ in }
|
||||
.padding()
|
||||
.previewDisplayName("Undisclosed")
|
||||
|
||||
PollView(poll: .endedDisclosed, editable: false) { _ in }
|
||||
PollView(poll: .endedDisclosed, state: .full(isEditable: false)) { _ in }
|
||||
.padding()
|
||||
.previewDisplayName("Ended, Disclosed")
|
||||
|
||||
PollView(poll: .endedUndisclosed, editable: false) { _ in }
|
||||
PollView(poll: .endedUndisclosed, state: .full(isEditable: false)) { _ in }
|
||||
.padding()
|
||||
.previewDisplayName("Ended, Undisclosed")
|
||||
|
||||
PollView(poll: .disclosed(createdByAccountOwner: true), editable: true) { _ in }
|
||||
PollView(poll: .disclosed(createdByAccountOwner: true), state: .full(isEditable: true)) { _ in }
|
||||
.padding()
|
||||
.previewDisplayName("Creator, disclosed")
|
||||
|
||||
PollView(poll: .emptyDisclosed, editable: true) { _ in }
|
||||
PollView(poll: .emptyDisclosed, state: .full(isEditable: true)) { _ in }
|
||||
.padding()
|
||||
.previewDisplayName("Creator, no votes")
|
||||
|
||||
PollView(poll: .emptyDisclosed, state: .preview) { _ in }
|
||||
.padding()
|
||||
.previewDisplayName("Preview")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ struct ReadReceiptsSummaryView_Previews: PreviewProvider, TestablePreview {
|
||||
let roomProxyMock = JoinedRoomProxyMock(.init(name: "Room", members: members))
|
||||
let mock = TimelineViewModel(roomProxy: roomProxyMock,
|
||||
timelineController: MockRoomTimelineController(),
|
||||
isPinnedEventsTimeline: false,
|
||||
mediaProvider: MockMediaProvider(),
|
||||
mediaPlayerProvider: MediaPlayerProviderMock(),
|
||||
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
||||
|
||||
@@ -147,7 +147,8 @@ struct TimelineItemBubbledStylerView<Content: View>: View {
|
||||
canCurrentUserPin: context.viewState.canCurrentUserPin,
|
||||
pinnedEventIDs: context.viewState.pinnedEventIDs,
|
||||
isDM: context.viewState.isEncryptedOneToOneRoom,
|
||||
isViewSourceEnabled: context.viewState.isViewSourceEnabled)
|
||||
isViewSourceEnabled: context.viewState.isViewSourceEnabled,
|
||||
isPinnedEventsTimeline: context.viewState.isPinnedEventsTimeline)
|
||||
TimelineItemMacContextMenu(item: timelineItem, actionProvider: provider) { action in
|
||||
context.send(viewAction: .handleTimelineItemMenuAction(itemID: timelineItem.id, action: action))
|
||||
}
|
||||
|
||||
@@ -32,7 +32,10 @@ struct TimelineItemStatusView: View {
|
||||
|
||||
@ViewBuilder
|
||||
private var mainContent: some View {
|
||||
if context.viewState.showReadReceipts, !timelineItem.properties.orderedReadReceipts.isEmpty {
|
||||
if context.viewState.isPinnedEventsTimeline {
|
||||
// Do not display any status when is a pinned events timeline
|
||||
EmptyView()
|
||||
} else if context.viewState.showReadReceipts, !timelineItem.properties.orderedReadReceipts.isEmpty {
|
||||
readReceipts
|
||||
} else {
|
||||
deliveryStatusBadge
|
||||
|
||||
@@ -92,6 +92,7 @@ struct TimelineReadReceiptsView_Previews: PreviewProvider, TestablePreview {
|
||||
|
||||
static let viewModel = TimelineViewModel(roomProxy: JoinedRoomProxyMock(.init(name: "Test", members: members)),
|
||||
timelineController: MockRoomTimelineController(),
|
||||
isPinnedEventsTimeline: false,
|
||||
mediaProvider: MockMediaProvider(),
|
||||
mediaPlayerProvider: MediaPlayerProviderMock(),
|
||||
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
||||
|
||||
+1
@@ -99,6 +99,7 @@ struct HighlightedTimelineItemTimeline_Previews: PreviewProvider {
|
||||
static let timelineViewModel = TimelineViewModel(roomProxy: roomProxyMock,
|
||||
focussedEventID: focussedEventID,
|
||||
timelineController: MockRoomTimelineController(),
|
||||
isPinnedEventsTimeline: false,
|
||||
mediaProvider: MockMediaProvider(),
|
||||
mediaPlayerProvider: MediaPlayerProviderMock(),
|
||||
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
||||
|
||||
+15
-1
@@ -20,9 +20,18 @@ struct PollRoomTimelineView: View {
|
||||
let timelineItem: PollRoomTimelineItem
|
||||
@EnvironmentObject private var context: TimelineViewModel.Context
|
||||
|
||||
private var state: PollViewState {
|
||||
if context.viewState.isPinnedEventsTimeline {
|
||||
return .preview
|
||||
} else {
|
||||
return .full(isEditable: timelineItem.isEditable)
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
TimelineStyler(timelineItem: timelineItem) {
|
||||
PollView(poll: poll, editable: timelineItem.isEditable) { action in
|
||||
PollView(poll: poll,
|
||||
state: state) { action in
|
||||
switch action {
|
||||
case .selectOption(let optionID):
|
||||
guard let eventID, let option = poll.options.first(where: { $0.id == optionID }), !option.isSelected else { return }
|
||||
@@ -51,6 +60,7 @@ struct PollRoomTimelineView: View {
|
||||
|
||||
struct PollRoomTimelineView_Previews: PreviewProvider, TestablePreview {
|
||||
static let viewModel = TimelineViewModel.mock
|
||||
static let pinnedEventsTimelineViewModel = TimelineViewModel.pinnedEventsTimelineMock
|
||||
|
||||
static var previews: some View {
|
||||
PollRoomTimelineView(timelineItem: .mock(poll: .disclosed(), isOutgoing: false))
|
||||
@@ -76,5 +86,9 @@ struct PollRoomTimelineView_Previews: PreviewProvider, TestablePreview {
|
||||
PollRoomTimelineView(timelineItem: .mock(poll: .emptyDisclosed, isEditable: true))
|
||||
.environmentObject(viewModel.context)
|
||||
.previewDisplayName("Creator, no votes, Bubble")
|
||||
|
||||
PollRoomTimelineView(timelineItem: .mock(poll: .disclosed(), isEditable: true))
|
||||
.environmentObject(pinnedEventsTimelineViewModel.context)
|
||||
.previewDisplayName("Preview")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,6 +84,7 @@ struct TimelineView_Previews: PreviewProvider, TestablePreview {
|
||||
static let roomViewModel = RoomScreenViewModel.mock(roomProxyMock: roomProxyMock)
|
||||
static let timelineViewModel = TimelineViewModel(roomProxy: roomProxyMock,
|
||||
timelineController: MockRoomTimelineController(),
|
||||
isPinnedEventsTimeline: false,
|
||||
mediaProvider: MockMediaProvider(),
|
||||
mediaPlayerProvider: MediaPlayerProviderMock(),
|
||||
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
||||
|
||||
Reference in New Issue
Block a user