Inject TimelinePollDetails in PollListItem

This commit is contained in:
Alfonso Grillo
2023-01-19 17:10:14 +01:00
parent eb64ff11fc
commit 460c5a9682
6 changed files with 131 additions and 83 deletions
@@ -37,7 +37,7 @@ struct PollHistoryViewState: BindableState {
}
var bindings: PollHistoryViewBindings
var polls: [PollListData] = []
var polls: [TimelinePollDetails] = []
}
enum PollHistoryViewAction {
@@ -14,18 +14,15 @@
// limitations under the License.
//
import Combine
import SwiftUI
typealias PollHistoryViewModelType = StateStoreViewModel<PollHistoryViewState, PollHistoryViewAction>
final class PollHistoryViewModel: PollHistoryViewModelType, PollHistoryViewModelProtocol {
private let pollService: PollHistoryServiceProtocol
private var polls: [PollListData] = []
private var fetchingTask: Task<Void, Error>? {
didSet {
oldValue?.cancel()
}
}
private var polls: [TimelinePollDetails] = []
private var subcriptions: Set<AnyCancellable> = .init()
var completion: ((PollHistoryViewModelResult) -> Void)?
@@ -39,7 +36,8 @@ final class PollHistoryViewModel: PollHistoryViewModelType, PollHistoryViewModel
override func process(viewAction: PollHistoryViewAction) {
switch viewAction {
case .viewAppeared:
fetchingTask = fetchPolls()
setupSubscriptions()
pollService.startFetching()
case .segmentDidChange:
updatePolls()
}
@@ -47,29 +45,33 @@ final class PollHistoryViewModel: PollHistoryViewModelType, PollHistoryViewModel
}
private extension PollHistoryViewModel {
func fetchPolls() -> Task<Void, Error> {
Task {
let polls = try await pollService.fetchHistory()
guard Task.isCancelled == false else {
return
func setupSubscriptions() {
subcriptions.removeAll()
pollService
.pollHistory
.sink { [weak self] detail in
self?.polls.append(detail)
self?.updatePolls()
}
await MainActor.run {
self.polls = polls
updatePolls()
.store(in: &subcriptions)
pollService
.error
.sink { detail in
#warning("Handle errors")
}
}
.store(in: &subcriptions)
}
func updatePolls() {
let renderedPolls: [PollListData]
let renderedPolls: [TimelinePollDetails]
switch context.mode {
case .active:
renderedPolls = polls.filter { $0.winningOption == nil }
renderedPolls = polls.filter { $0.closed == false }
case .past:
renderedPolls = polls.filter { $0.winningOption != nil }
renderedPolls = polls.filter { $0.closed == true }
}
state.polls = renderedPolls
@@ -20,20 +20,29 @@ import Combine
final class PollHistoryService: PollHistoryServiceProtocol {
private let room: MXRoom
private let livePolls: PassthroughSubject<TimelinePollDetails, Never> = .init()
private let pollsSubject: PassthroughSubject<TimelinePollDetails, Never> = .init()
private let errorSubject: PassthroughSubject<Error, Never> = .init()
private var listner: Any?
private var timeline: MXEventTimeline?
private var pollAggregators: [String: PollAggregator] = [:]
var pollHistory: AnyPublisher<TimelinePollDetails, Never> {
pollsSubject.eraseToAnyPublisher()
}
var error: AnyPublisher<Error, Never> {
errorSubject.eraseToAnyPublisher()
}
init(room: MXRoom) {
self.room = room
}
func fetchHistory() async throws -> [PollListData] {
func startFetching() {
guard timeline == nil else {
paginate()
return []
return
}
room.liveTimeline { [weak self] timeline in
@@ -48,8 +57,6 @@ final class PollHistoryService: PollHistoryServiceProtocol {
self.setup(timeline: timeline)
self.paginate()
}
return []
}
}
@@ -75,6 +82,7 @@ private extension PollHistoryService {
onlyFromStore: false) { response in
switch response {
case .success:
#warning("Go on with pagination...")
break
case .failure(let error):
#warning("Handle error")
@@ -103,6 +111,7 @@ extension PollHistoryService: PollAggregatorDelegate {
}
func pollAggregatorDidEndLoading(_ aggregator: PollAggregator) {
pollsSubject.send(.init(poll: aggregator.poll, represent: .started))
}
func pollAggregator(_ aggregator: PollAggregator, didFailWithError: Error) {
@@ -14,31 +14,48 @@
// limitations under the License.
//
import Combine
final class MockPollHistoryService: PollHistoryServiceProtocol {
var activePollsData: [PollListData] = (1..<10)
private let polls: PassthroughSubject<TimelinePollDetails, Never> = .init()
var pollHistory: AnyPublisher<TimelinePollDetails, Never> {
polls.eraseToAnyPublisher()
}
var error: AnyPublisher<Error, Never> {
Empty().eraseToAnyPublisher()
}
func startFetching() {
for poll in activePollsData + pastPollsData {
polls.send(poll)
}
}
var activePollsData: [TimelinePollDetails] = (1..<10)
.map { index in
PollListData(
startDate: .init().addingTimeInterval(-CGFloat(index) * 3600),
question: "Do you like the active poll number \(index)?",
numberOfVotes: 30,
winningOption: nil
)
TimelinePollDetails(question: "Do you like the active poll number \(index)?",
answerOptions: [],
closed: false,
totalAnswerCount: 30,
type: .disclosed,
eventType: .started,
maxAllowedSelections: 1,
hasBeenEdited: false,
hasDecryptionError: false)
}
var pastPollsData: [PollListData] = (1..<10)
var pastPollsData: [TimelinePollDetails] = (1..<10)
.map { index in
PollListData(
startDate: .init().addingTimeInterval(-CGFloat(index) * 3600),
question: "Do you like the past poll number \(index)?",
numberOfVotes: 30,
winningOption: .init(id: "id", text: "Yes, of course!", count: 20, winner: true, selected: true)
)
TimelinePollDetails(question: "Do you like the active poll number \(index)?",
answerOptions: [.init(id: "id", text: "Yes, of course!", count: 20, winner: true, selected: true)],
closed: true,
totalAnswerCount: 30,
type: .disclosed,
eventType: .started,
maxAllowedSelections: 1,
hasBeenEdited: false,
hasDecryptionError: false)
}
func fetchHistory() async throws -> [PollListData] {
(activePollsData + pastPollsData)
.sorted { poll1, poll2 in
poll1.startDate > poll2.startDate
}
}
}
@@ -14,6 +14,11 @@
// limitations under the License.
//
import Combine
protocol PollHistoryServiceProtocol {
func fetchHistory() async throws -> [PollListData]
var pollHistory: AnyPublisher<TimelinePollDetails, Never> { get }
var error: AnyPublisher<Error, Never> { get }
func startFetching()
}
@@ -16,26 +16,20 @@
import SwiftUI
struct PollListData {
let startDate: Date
let question: String
let numberOfVotes: UInt
let winningOption: TimelinePollAnswerOption?
}
struct PollListItem: View {
@Environment(\.theme) private var theme
private let pollData: PollListData
private let pollData: TimelinePollDetails
@ScaledMetric private var imageSize = 16
init(pollData: PollListData) {
init(pollData: TimelinePollDetails) {
self.pollData = pollData
}
var body: some View {
VStack(alignment: .leading, spacing: 12) {
Text(pollData.formattedDate)
#warning("fix me")
Text(DateFormatter.shortDateFormatter.string(from: .init()))
.foregroundColor(theme.colors.tertiaryContent)
.font(theme.fonts.caption1)
@@ -50,10 +44,14 @@ struct PollListItem: View {
.lineLimit(2)
.accessibilityLabel("PollListItem.title")
}
.frame(maxWidth: .infinity, alignment: .leading)
if pollData.winningOption != nil {
if pollData.closed {
VStack(alignment: .leading, spacing: 12) {
optionView(winningOption: pollData.winningOption!)
let winningOptions = pollData.answerOptions.filter(\.winner)
ForEach(winningOptions) {
optionView(winningOption: $0)
}
resultView
}
}
@@ -67,7 +65,7 @@ struct PollListItem: View {
private func optionView(winningOption: TimelinePollAnswerOption) -> some View {
VStack(alignment: .leading, spacing: 12.0) {
HStack(alignment: .top, spacing: 8.0) {
Text(pollData.winningOption!.text)
Text(winningOption.text)
.font(theme.fonts.body)
.foregroundColor(theme.colors.primaryContent)
.accessibilityIdentifier("PollListData.winningOption")
@@ -78,7 +76,7 @@ struct PollListItem: View {
}
ProgressView(value: Double(winningOption.count),
total: Double(pollData.numberOfVotes))
total: Double(pollData.totalAnswerCount))
.progressViewStyle(LinearProgressViewStyle())
.scaleEffect(x: 1.0, y: 1.2, anchor: .center)
}
@@ -102,7 +100,7 @@ struct PollListItem: View {
}
private var resultView: some View {
let text = pollData.numberOfVotes == 1 ? VectorL10n.pollTimelineTotalFinalResultsOneVote : VectorL10n.pollTimelineTotalFinalResults(Int(pollData.numberOfVotes))
let text = pollData.totalAnswerCount == 1 ? VectorL10n.pollTimelineTotalFinalResultsOneVote : VectorL10n.pollTimelineTotalFinalResults(Int(pollData.totalAnswerCount))
return Text(text)
.font(theme.fonts.footnote)
@@ -110,12 +108,6 @@ struct PollListItem: View {
}
}
private extension PollListData {
var formattedDate: String {
DateFormatter.shortDateFormatter.string(from: startDate)
}
}
private extension DateFormatter {
static let shortDateFormatter: DateFormatter = {
let formatter = DateFormatter()
@@ -131,22 +123,45 @@ private extension DateFormatter {
struct PollListItem_Previews: PreviewProvider {
static var previews: some View {
Group {
let pollData1 = PollListData(
startDate: .init(),
question: "Do you like polls?",
numberOfVotes: 30,
winningOption: .init(id: "id", text: "Yes, of course!", count: 18, winner: true, selected: true)
)
let pollData1 = TimelinePollDetails(question: "Do you like polls?",
answerOptions: [.init(id: "id", text: "Yes, of course!", count: 18, winner: true, selected: true)],
closed: true,
totalAnswerCount: 30,
type: .disclosed,
eventType: .started,
maxAllowedSelections: 1,
hasBeenEdited: false,
hasDecryptionError: false)
PollListItem(pollData: pollData1)
let pollData2 = PollListData(
startDate: .init(),
question: "Do you like polls?",
numberOfVotes: 30,
winningOption: nil)
let pollData2 = TimelinePollDetails(question: "Do you like polls?",
answerOptions: [.init(id: "id", text: "Yes, of course!", count: 18, winner: true, selected: true)],
closed: false,
totalAnswerCount: 30,
type: .disclosed,
eventType: .started,
maxAllowedSelections: 1,
hasBeenEdited: false,
hasDecryptionError: false)
PollListItem(pollData: pollData2)
let pollData3 = TimelinePollDetails(question: "Do you like polls?",
answerOptions: [
.init(id: "id1", text: "Yes, of course!", count: 15, winner: true, selected: true),
.init(id: "id2", text: "No, I don't :-(", count: 15, winner: true, selected: true)
],
closed: true,
totalAnswerCount: 30,
type: .disclosed,
eventType: .started,
maxAllowedSelections: 1,
hasBeenEdited: false,
hasDecryptionError: false)
let allPollData = Array([pollData1, pollData2, pollData3].enumerated())
ForEach(allPollData, id: \.offset) { _, poll in
PollListItem(pollData: poll)
}
}
.padding()
}