Add polls "creator view" (#1765)
* Add end button in PollRoomTimelineView * Add creator logic * Refine PollRoomTimelineView previews * Add UI tests * Update preview tests
This commit is contained in:
@@ -21,29 +21,33 @@ extension Poll {
|
||||
pollKind: Poll.Kind = .disclosed,
|
||||
options: [Poll.Option],
|
||||
votes: [String: [String]] = [:],
|
||||
ended: Bool = false) -> Self {
|
||||
ended: Bool = false,
|
||||
createdByAccountOwner: Bool = false) -> Self {
|
||||
.init(question: question,
|
||||
kind: pollKind,
|
||||
maxSelections: 1,
|
||||
options: options,
|
||||
votes: votes,
|
||||
endDate: ended ? Date() : nil)
|
||||
endDate: ended ? Date() : nil,
|
||||
createdByAccountOwner: createdByAccountOwner)
|
||||
}
|
||||
|
||||
static var disclosed: Self {
|
||||
static func disclosed(createdByAccountOwner: Bool = false) -> Self {
|
||||
mock(question: "What country do you like most?",
|
||||
pollKind: .disclosed,
|
||||
options: [.mock(text: "Italy 🇮🇹", votes: 5, allVotes: 10, isWinning: true),
|
||||
.mock(text: "China 🇨🇳", votes: 3, allVotes: 10),
|
||||
.mock(text: "USA 🇺🇸", votes: 2, allVotes: 10)])
|
||||
.mock(text: "USA 🇺🇸", votes: 2, allVotes: 10)],
|
||||
createdByAccountOwner: createdByAccountOwner)
|
||||
}
|
||||
|
||||
static var undisclosed: Self {
|
||||
static func undisclosed(createdByAccountOwner: Bool = false) -> Self {
|
||||
mock(question: "What country do you like most?",
|
||||
pollKind: .undisclosed,
|
||||
options: [.mock(text: "Italy 🇮🇹", votes: 5, allVotes: 10, isWinning: true),
|
||||
.mock(text: "China 🇨🇳", votes: 3, allVotes: 10, isSelected: true),
|
||||
.mock(text: "USA 🇺🇸", votes: 2, allVotes: 10)])
|
||||
.mock(text: "USA 🇺🇸", votes: 2, allVotes: 10)],
|
||||
createdByAccountOwner: createdByAccountOwner)
|
||||
}
|
||||
|
||||
static var endedDisclosed: Self {
|
||||
@@ -77,12 +81,12 @@ extension Poll.Option {
|
||||
}
|
||||
|
||||
extension PollRoomTimelineItem {
|
||||
static func mock(poll: Poll) -> Self {
|
||||
.init(id: .random,
|
||||
static func mock(poll: Poll, isOutgoing: Bool = true) -> Self {
|
||||
.init(id: .init(timelineID: UUID().uuidString, eventID: UUID().uuidString),
|
||||
poll: poll,
|
||||
body: "poll",
|
||||
timestamp: "Now",
|
||||
isOutgoing: true,
|
||||
isOutgoing: isOutgoing,
|
||||
isEditable: false,
|
||||
sender: .init(id: "userID"),
|
||||
properties: .init())
|
||||
|
||||
@@ -60,7 +60,8 @@ enum RoomScreenViewAction {
|
||||
case sendReadReceiptIfNeeded(TimelineItemIdentifier)
|
||||
case paginateBackwards
|
||||
case selectedPollOption(pollStartID: String, optionID: String)
|
||||
|
||||
case endPoll(pollStartID: String)
|
||||
|
||||
case timelineItemMenu(itemID: TimelineItemIdentifier)
|
||||
case timelineItemMenuAction(itemID: TimelineItemIdentifier, action: TimelineItemMenuAction)
|
||||
|
||||
|
||||
@@ -154,6 +154,8 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
||||
state.longPressDisabledItemID = nil
|
||||
case .disableLongPress(let itemID):
|
||||
state.longPressDisabledItemID = itemID
|
||||
case let .endPoll(pollStartID):
|
||||
endPoll(pollStartID: pollStartID)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,22 +29,9 @@ struct PollRoomTimelineView: View {
|
||||
TimelineStyler(timelineItem: timelineItem) {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
questionView
|
||||
|
||||
ForEach(poll.options, id: \.id) { option in
|
||||
Button {
|
||||
guard let eventID, !option.isSelected else { return }
|
||||
context.send(viewAction: .selectedPollOption(pollStartID: eventID, optionID: option.id))
|
||||
feedbackGenerator.impactOccurred()
|
||||
} label: {
|
||||
PollOptionView(pollOption: option,
|
||||
showVotes: showVotes,
|
||||
isFinalResult: poll.hasEnded)
|
||||
.foregroundColor(progressBarColor(for: option))
|
||||
}
|
||||
.disabled(poll.hasEnded || eventID == nil)
|
||||
}
|
||||
|
||||
optionsView
|
||||
summaryView
|
||||
toolbarView
|
||||
}
|
||||
.frame(maxWidth: 450)
|
||||
}
|
||||
@@ -74,6 +61,22 @@ struct PollRoomTimelineView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private var optionsView: some View {
|
||||
ForEach(poll.options, id: \.id) { option in
|
||||
Button {
|
||||
guard let eventID, !option.isSelected else { return }
|
||||
context.send(viewAction: .selectedPollOption(pollStartID: eventID, optionID: option.id))
|
||||
feedbackGenerator.impactOccurred()
|
||||
} label: {
|
||||
PollOptionView(pollOption: option,
|
||||
showVotes: showVotes,
|
||||
isFinalResult: poll.hasEnded)
|
||||
.foregroundColor(progressBarColor(for: option))
|
||||
}
|
||||
.disabled(poll.hasEnded || eventID == nil)
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var summaryView: some View {
|
||||
if let summaryText = poll.summaryText {
|
||||
@@ -85,6 +88,28 @@ struct PollRoomTimelineView: View {
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var toolbarView: some View {
|
||||
if !poll.hasEnded, poll.createdByAccountOwner, let eventID {
|
||||
Button {
|
||||
context.send(viewAction: .endPoll(pollStartID: eventID))
|
||||
} label: {
|
||||
Text(L10n.actionEndPoll)
|
||||
.lineLimit(2, reservesSpace: false)
|
||||
.font(.compound.bodyLGSemibold)
|
||||
.foregroundColor(.compound.textOnSolidPrimary)
|
||||
.padding(.horizontal, 24)
|
||||
.padding(.vertical, 10)
|
||||
.frame(maxWidth: .infinity)
|
||||
.background {
|
||||
Capsule()
|
||||
.foregroundColor(.compound.bgActionPrimaryRest)
|
||||
}
|
||||
}
|
||||
.padding(.top, 8)
|
||||
}
|
||||
}
|
||||
|
||||
private func progressBarColor(for option: Poll.Option) -> Color {
|
||||
if poll.hasEnded {
|
||||
return option.isWinning ? .compound.textActionAccent : .compound.textDisabled
|
||||
@@ -121,12 +146,12 @@ struct PollRoomTimelineView_Previews: PreviewProvider, TestablePreview {
|
||||
static let viewModel = RoomScreenViewModel.mock
|
||||
|
||||
static var previews: some View {
|
||||
PollRoomTimelineView(timelineItem: .mock(poll: .disclosed))
|
||||
PollRoomTimelineView(timelineItem: .mock(poll: .disclosed(), isOutgoing: false))
|
||||
.environment(\.timelineStyle, .bubbles)
|
||||
.environmentObject(viewModel.context)
|
||||
.previewDisplayName("Disclosed, Bubble")
|
||||
|
||||
PollRoomTimelineView(timelineItem: .mock(poll: .undisclosed))
|
||||
PollRoomTimelineView(timelineItem: .mock(poll: .undisclosed(), isOutgoing: false))
|
||||
.environment(\.timelineStyle, .bubbles)
|
||||
.environmentObject(viewModel.context)
|
||||
.previewDisplayName("Undisclosed, Bubble")
|
||||
@@ -141,12 +166,17 @@ struct PollRoomTimelineView_Previews: PreviewProvider, TestablePreview {
|
||||
.environmentObject(viewModel.context)
|
||||
.previewDisplayName("Ended, Undisclosed, Bubble")
|
||||
|
||||
PollRoomTimelineView(timelineItem: .mock(poll: .disclosed))
|
||||
PollRoomTimelineView(timelineItem: .mock(poll: .disclosed(createdByAccountOwner: true)))
|
||||
.environment(\.timelineStyle, .bubbles)
|
||||
.environmentObject(viewModel.context)
|
||||
.previewDisplayName("Creator, disclosed, Bubble")
|
||||
|
||||
PollRoomTimelineView(timelineItem: .mock(poll: .disclosed(), isOutgoing: false))
|
||||
.environment(\.timelineStyle, .plain)
|
||||
.environmentObject(viewModel.context)
|
||||
.previewDisplayName("Disclosed, Plain")
|
||||
|
||||
PollRoomTimelineView(timelineItem: .mock(poll: .undisclosed))
|
||||
PollRoomTimelineView(timelineItem: .mock(poll: .undisclosed(), isOutgoing: false))
|
||||
.environment(\.timelineStyle, .plain)
|
||||
.environmentObject(viewModel.context)
|
||||
.previewDisplayName("Undisclosed, Plain")
|
||||
@@ -160,5 +190,10 @@ struct PollRoomTimelineView_Previews: PreviewProvider, TestablePreview {
|
||||
.environment(\.timelineStyle, .plain)
|
||||
.environmentObject(viewModel.context)
|
||||
.previewDisplayName("Ended, Undisclosed, Plain")
|
||||
|
||||
PollRoomTimelineView(timelineItem: .mock(poll: .disclosed(createdByAccountOwner: true)))
|
||||
.environment(\.timelineStyle, .plain)
|
||||
.environmentObject(viewModel.context)
|
||||
.previewDisplayName("Creator, disclosed, Plain")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,14 +221,18 @@ enum RoomTimelineItemFixtures {
|
||||
}
|
||||
|
||||
static var disclosedPolls: [RoomTimelineItemProtocol] {
|
||||
[PollRoomTimelineItem.mock(poll: .disclosed),
|
||||
[PollRoomTimelineItem.mock(poll: .disclosed(), isOutgoing: false),
|
||||
PollRoomTimelineItem.mock(poll: .endedDisclosed)]
|
||||
}
|
||||
|
||||
static var undisclosedPolls: [RoomTimelineItemProtocol] {
|
||||
[PollRoomTimelineItem.mock(poll: .undisclosed),
|
||||
[PollRoomTimelineItem.mock(poll: .undisclosed(), isOutgoing: false),
|
||||
PollRoomTimelineItem.mock(poll: .endedUndisclosed)]
|
||||
}
|
||||
|
||||
static var outgoingPolls: [RoomTimelineItemProtocol] {
|
||||
[PollRoomTimelineItem.mock(poll: .disclosed(createdByAccountOwner: true), isOutgoing: true)]
|
||||
}
|
||||
}
|
||||
|
||||
private extension TextRoomTimelineItem {
|
||||
|
||||
@@ -34,6 +34,8 @@ struct Poll: Equatable {
|
||||
let options: [Option]
|
||||
let votes: [String: [String]]
|
||||
let endDate: Date?
|
||||
/// Whether the poll has been created by the account owner
|
||||
let createdByAccountOwner: Bool
|
||||
|
||||
var hasEnded: Bool {
|
||||
endDate != nil
|
||||
|
||||
@@ -389,7 +389,8 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
||||
maxSelections: Int(maxSelections),
|
||||
options: options,
|
||||
votes: votes,
|
||||
endDate: endTime.map { Date(timeIntervalSince1970: TimeInterval($0 / 1000)) })
|
||||
endDate: endTime.map { Date(timeIntervalSince1970: TimeInterval($0 / 1000)) },
|
||||
createdByAccountOwner: eventItemProxy.sender.id == userID)
|
||||
|
||||
return PollRoomTimelineItem(id: eventItemProxy.id,
|
||||
poll: poll,
|
||||
|
||||
@@ -354,11 +354,39 @@ class MockScreen: Identifiable {
|
||||
|
||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||
return navigationStackCoordinator
|
||||
case .roomWithDisclosedPolls, .roomWithUndisclosedPolls:
|
||||
case .roomWithDisclosedPolls:
|
||||
let navigationStackCoordinator = NavigationStackCoordinator()
|
||||
|
||||
let timelineController = MockRoomTimelineController()
|
||||
timelineController.timelineItems = id == .roomWithDisclosedPolls ? RoomTimelineItemFixtures.disclosedPolls : RoomTimelineItemFixtures.undisclosedPolls
|
||||
timelineController.timelineItems = RoomTimelineItemFixtures.disclosedPolls
|
||||
timelineController.incomingItems = []
|
||||
let parameters = RoomScreenCoordinatorParameters(roomProxy: RoomProxyMock(with: .init(displayName: "Polls timeline", avatarURL: URL.picturesDirectory)),
|
||||
timelineController: timelineController,
|
||||
mediaProvider: MockMediaProvider(),
|
||||
emojiProvider: EmojiProvider())
|
||||
let coordinator = RoomScreenCoordinator(parameters: parameters)
|
||||
|
||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||
return navigationStackCoordinator
|
||||
case .roomWithUndisclosedPolls:
|
||||
let navigationStackCoordinator = NavigationStackCoordinator()
|
||||
|
||||
let timelineController = MockRoomTimelineController()
|
||||
timelineController.timelineItems = RoomTimelineItemFixtures.undisclosedPolls
|
||||
timelineController.incomingItems = []
|
||||
let parameters = RoomScreenCoordinatorParameters(roomProxy: RoomProxyMock(with: .init(displayName: "Polls timeline", avatarURL: URL.picturesDirectory)),
|
||||
timelineController: timelineController,
|
||||
mediaProvider: MockMediaProvider(),
|
||||
emojiProvider: EmojiProvider())
|
||||
let coordinator = RoomScreenCoordinator(parameters: parameters)
|
||||
|
||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||
return navigationStackCoordinator
|
||||
case .roomWithOutgoingPolls:
|
||||
let navigationStackCoordinator = NavigationStackCoordinator()
|
||||
|
||||
let timelineController = MockRoomTimelineController()
|
||||
timelineController.timelineItems = RoomTimelineItemFixtures.outgoingPolls
|
||||
timelineController.incomingItems = []
|
||||
let parameters = RoomScreenCoordinatorParameters(roomProxy: RoomProxyMock(with: .init(displayName: "Polls timeline", avatarURL: URL.picturesDirectory)),
|
||||
timelineController: timelineController,
|
||||
|
||||
@@ -48,6 +48,7 @@ enum UITestsScreenIdentifier: String {
|
||||
case roomLayoutBottom
|
||||
case roomWithDisclosedPolls
|
||||
case roomWithUndisclosedPolls
|
||||
case roomWithOutgoingPolls
|
||||
case sessionVerification
|
||||
case userSessionScreen
|
||||
case userSessionScreenReply
|
||||
|
||||
@@ -173,6 +173,12 @@ class RoomScreenUITests: XCTestCase {
|
||||
try await app.assertScreenshot(.roomWithUndisclosedPolls)
|
||||
}
|
||||
|
||||
func testTimelineOutgoingPolls() async throws {
|
||||
let app = Application.launch(.roomWithOutgoingPolls)
|
||||
|
||||
try await app.assertScreenshot(.roomWithOutgoingPolls)
|
||||
}
|
||||
|
||||
// MARK: - Helper Methods
|
||||
|
||||
private func performOperation(_ operation: UITestsSignal, using client: UITestsSignalling.Client) async throws {
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:01c3b15eb3bf6295e858fe87b5230126ef1992d3ade9be116d3cce59f855ad6a
|
||||
size 170292
|
||||
oid sha256:798631c91c1ac886f0a57e61f641c5504baf00e16ceee2996d47e0069eef1385
|
||||
size 171627
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:081985366ac3a0ebd3b3f88323327833d7fc9ed4d7b6ba96258fd01dc83ad56a
|
||||
size 137324
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:930a7dbbcfb3b0e2bbfefe5308d21b6cb6a07a081a0e523cee892e84d7233926
|
||||
size 167456
|
||||
oid sha256:e342720b6641e9e154611b1993bdcc37ee93d4f55a67746a3ad800b228199153
|
||||
size 168916
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:8a67ab8fd0ba2d90b15533ad7d4c35f0c62c88fccad5f329d701ee1dfb7f9865
|
||||
size 265573
|
||||
oid sha256:ef145da87067888b6024648e290bb78cfbc9e6dd736b5bbe2c0c930f371fc312
|
||||
size 270795
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:14d19152f178e537401600795a610c8ec5eea1ea8da0bc754bb12031cc7c477d
|
||||
size 213593
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:656e9daba343ac33675314ebea48d14835e3b2187c7c13e20d5ca2346931c7c3
|
||||
size 261973
|
||||
oid sha256:3a78111fde3bfda01979e95105cf5fb1f80a851903002aa7acbd1f5769ac634a
|
||||
size 267334
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:11b733e64c639a9b9f4f30b94c38c4eddcaab1b0f0b211edc83990024730d779
|
||||
size 196045
|
||||
oid sha256:435ed9882df8248547173ed9a7a93b223ec10c2b7da12333856b8e7eb6c598b0
|
||||
size 197557
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:eab27d5f2463a29282f71ed3ffa895cfd5c6992a3024838188473aaa75f44705
|
||||
size 151315
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c4b6cb1b16225df4d92969f936af0512fd1a710914e1e43a980e5ba8bad85ede
|
||||
size 184488
|
||||
oid sha256:ce16975bde1aa233d1158ef412743b9ea4906a160dd903a1aa497ef6e73b50c3
|
||||
size 185616
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:ffce58c610ae30b2d5261db4ad651f53bf6adcdeb0b9f1a672012d1def69f7e0
|
||||
size 285052
|
||||
oid sha256:87ee95119185a02df4f50a2252d4b419e4c42ea01a2c9f0dca8d14839a3827c5
|
||||
size 286896
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:17830a2ac15c1d861b406ccb8082597e852c16c662a341ea115d41c1e7527cea
|
||||
size 225141
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:99f8eda0c390e6aefb1e483ba1e4722f1bd26ad74f460a867d5667c896009243
|
||||
size 278396
|
||||
oid sha256:f0ba023c1e140678a5fde885fe3c148dbe4022d2d25596cbd9d11bc152adc3e1
|
||||
size 279580
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:0275975896e24d92b5cba52cf4b535daf7517ff6065b35291e73c73fc5dbf39b
|
||||
size 128303
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d575e7e8074feb52c11bd9b29dde9f25f8cbf2a56a3e0d1b6108dfe8bd7d4b35
|
||||
size 120837
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6b549469c3781eb51da06fb646dfeb37bc8ed993be78237dd31d9024372e8919
|
||||
size 120204
|
||||
oid sha256:ffffaef123fd1c405f0cee3b6a00dd2dfbec6e77cc758857c7e6c292d3561b2a
|
||||
size 122998
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:73a49c9e978534a24c88a76756b252527b1bad4765ddabfdc866cf6ecaf831eb
|
||||
size 115040
|
||||
oid sha256:25519ecd8af6e9f6e66353ca3c0b850101a470685d44c817eabf204d5c46aad8
|
||||
size 117721
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:69e1bcb069e2791f7e2723c09704c8602a2ad3b23ce339be09fc84de7eead896
|
||||
size 109370
|
||||
oid sha256:07f063c03c64d74b94a17408c250f0e049bb1192c68435ba619ce770154872ea
|
||||
size 109241
|
||||
|
||||
Reference in New Issue
Block a user