diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index 4ab2ee3e8..dc3ba51cb 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -2349,6 +2349,8 @@ Tap the + to start adding people."; "poll_timeline_not_closed_subtitle" = "Please try again"; +"poll_timeline_decryption_error" = "Due to decryption errors, some votes may not be counted"; + // MARK: - Location sharing "location_sharing_title" = "Location"; diff --git a/Riot/Categories/Sequence.swift b/Riot/Categories/Sequence.swift new file mode 100644 index 000000000..f3e1dfb15 --- /dev/null +++ b/Riot/Categories/Sequence.swift @@ -0,0 +1,28 @@ +// +// Copyright 2022 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +extension Sequence { + func group(by keyPath: KeyPath) -> [GroupID: [Element]] { + var result: [GroupID: [Element]] = .init() + + for item in self { + let groupId = item[keyPath: keyPath] + result[groupId] = (result[groupId] ?? []) + [item] + } + + return result + } +} diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index fbae6b345..05460082a 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -4839,6 +4839,10 @@ public class VectorL10n: NSObject { public static var pollEditFormUpdateFailureTitle: String { return VectorL10n.tr("Vector", "poll_edit_form_update_failure_title") } + /// Due to decryption errors, some votes may not be counted + public static var pollTimelineDecryptionError: String { + return VectorL10n.tr("Vector", "poll_timeline_decryption_error") + } /// Please try again public static var pollTimelineNotClosedSubtitle: String { return VectorL10n.tr("Vector", "poll_timeline_not_closed_subtitle") diff --git a/RiotSwiftUI/Modules/Room/TimelinePoll/Coordinator/TimelinePollCoordinator.swift b/RiotSwiftUI/Modules/Room/TimelinePoll/Coordinator/TimelinePollCoordinator.swift index 74cfaf65b..3520588e2 100644 --- a/RiotSwiftUI/Modules/Room/TimelinePoll/Coordinator/TimelinePollCoordinator.swift +++ b/RiotSwiftUI/Modules/Room/TimelinePoll/Coordinator/TimelinePollCoordinator.swift @@ -132,7 +132,8 @@ final class TimelinePollCoordinator: Coordinator, Presentable, PollAggregatorDel totalAnswerCount: poll.totalAnswerCount, type: pollKindToTimelinePollType(poll.kind), maxAllowedSelections: poll.maxAllowedSelections, - hasBeenEdited: poll.hasBeenEdited) + hasBeenEdited: poll.hasBeenEdited, + hasDecryptionError: poll.hasDecryptionError) } private func pollKindToTimelinePollType(_ kind: PollKind) -> TimelinePollType { diff --git a/RiotSwiftUI/Modules/Room/TimelinePoll/Test/Unit/TimelinePollViewModelTests.swift b/RiotSwiftUI/Modules/Room/TimelinePoll/Test/Unit/TimelinePollViewModelTests.swift index 0e102dc39..be73d7f04 100644 --- a/RiotSwiftUI/Modules/Room/TimelinePoll/Test/Unit/TimelinePollViewModelTests.swift +++ b/RiotSwiftUI/Modules/Room/TimelinePoll/Test/Unit/TimelinePollViewModelTests.swift @@ -35,7 +35,8 @@ class TimelinePollViewModelTests: XCTestCase { totalAnswerCount: 3, type: .disclosed, maxAllowedSelections: 1, - hasBeenEdited: false) + hasBeenEdited: false, + hasDecryptionError: false) viewModel = TimelinePollViewModel(timelinePollDetails: timelinePoll) context = viewModel.context diff --git a/RiotSwiftUI/Modules/Room/TimelinePoll/TimelinePollModels.swift b/RiotSwiftUI/Modules/Room/TimelinePoll/TimelinePollModels.swift index 528ad7c17..fa0d2ac49 100644 --- a/RiotSwiftUI/Modules/Room/TimelinePoll/TimelinePollModels.swift +++ b/RiotSwiftUI/Modules/Room/TimelinePoll/TimelinePollModels.swift @@ -64,13 +64,15 @@ struct TimelinePollDetails { var type: TimelinePollType var maxAllowedSelections: UInt var hasBeenEdited = true + var hasDecryptionError: Bool init(question: String, answerOptions: [TimelinePollAnswerOption], closed: Bool, totalAnswerCount: UInt, type: TimelinePollType, maxAllowedSelections: UInt, - hasBeenEdited: Bool) { + hasBeenEdited: Bool, + hasDecryptionError: Bool) { self.question = question self.answerOptions = answerOptions self.closed = closed @@ -78,6 +80,7 @@ struct TimelinePollDetails { self.type = type self.maxAllowedSelections = maxAllowedSelections self.hasBeenEdited = hasBeenEdited + self.hasDecryptionError = hasDecryptionError } var hasCurrentUserVoted: Bool { diff --git a/RiotSwiftUI/Modules/Room/TimelinePoll/TimelinePollScreenState.swift b/RiotSwiftUI/Modules/Room/TimelinePoll/TimelinePollScreenState.swift index 01fb82c4a..5a9a9e0a1 100644 --- a/RiotSwiftUI/Modules/Room/TimelinePoll/TimelinePollScreenState.swift +++ b/RiotSwiftUI/Modules/Room/TimelinePoll/TimelinePollScreenState.swift @@ -38,7 +38,8 @@ enum MockTimelinePollScreenState: MockScreenState, CaseIterable { totalAnswerCount: 20, type: self == .closedDisclosed || self == .openDisclosed ? .disclosed : .undisclosed, maxAllowedSelections: 1, - hasBeenEdited: false) + hasBeenEdited: false, + hasDecryptionError: false) let viewModel = TimelinePollViewModel(timelinePollDetails: poll) diff --git a/RiotSwiftUI/Modules/Room/TimelinePoll/View/TimelinePollAnswerOptionButton.swift b/RiotSwiftUI/Modules/Room/TimelinePoll/View/TimelinePollAnswerOptionButton.swift index 2ffa68be9..04451bd92 100644 --- a/RiotSwiftUI/Modules/Room/TimelinePoll/View/TimelinePollAnswerOptionButton.swift +++ b/RiotSwiftUI/Modules/Room/TimelinePoll/View/TimelinePollAnswerOptionButton.swift @@ -152,7 +152,8 @@ struct TimelinePollAnswerOptionButton_Previews: PreviewProvider { totalAnswerCount: 100, type: type, maxAllowedSelections: 1, - hasBeenEdited: false) + hasBeenEdited: false, + hasDecryptionError: false) } static func buildAnswerOption(text: String = "Test", selected: Bool, winner: Bool = false) -> TimelinePollAnswerOption { diff --git a/RiotSwiftUI/Modules/Room/TimelinePoll/View/TimelinePollView.swift b/RiotSwiftUI/Modules/Room/TimelinePoll/View/TimelinePollView.swift index ff2ce2541..cc00f017a 100644 --- a/RiotSwiftUI/Modules/Room/TimelinePoll/View/TimelinePollView.swift +++ b/RiotSwiftUI/Modules/Room/TimelinePoll/View/TimelinePollView.swift @@ -49,6 +49,7 @@ struct TimelinePollView: View { .fixedSize(horizontal: false, vertical: true) Text(totalVotesString) + .lineLimit(2) .font(theme.fonts.footnote) .foregroundColor(theme.colors.tertiaryContent) } @@ -62,6 +63,10 @@ struct TimelinePollView: View { private var totalVotesString: String { let poll = viewModel.viewState.poll + if poll.hasDecryptionError, poll.totalAnswerCount > 0 { + return VectorL10n.pollTimelineDecryptionError + } + if poll.closed { if poll.totalAnswerCount == 1 { return VectorL10n.pollTimelineTotalFinalResultsOneVote diff --git a/changelog.d/pr-7206.change b/changelog.d/pr-7206.change new file mode 100644 index 000000000..89d4c8e36 --- /dev/null +++ b/changelog.d/pr-7206.change @@ -0,0 +1 @@ +Polls: show decryption errors in timeline during aggregations.