Skip to content

Commit

Permalink
Merge pull request #7303 from vector-im/alfogrillo/poll_history_load_…
Browse files Browse the repository at this point in the history
…more

Poll history: load more polls (PSG-1093)
  • Loading branch information
Alfonso Grillo committed Jan 26, 2023
2 parents 971a9f0 + fc8ee97 commit 87d8aec
Show file tree
Hide file tree
Showing 14 changed files with 368 additions and 119 deletions.
1 change: 1 addition & 0 deletions Riot/Assets/en.lproj/Vector.strings
Original file line number Diff line number Diff line change
Expand Up @@ -2303,6 +2303,7 @@ Tap the + to start adding people.";
"poll_history_no_active_poll_period_text" = "There are no active polls for the past %@ days. Load more polls to view polls for previous months";
"poll_history_no_past_poll_period_text" = "There are no past polls for the past %@ days. Load more polls to view polls for previous months";
"poll_history_load_more" = "Load more polls";
"poll_history_fetching_error" = "Error fetching polls.";

// MARK: - Polls

Expand Down
4 changes: 4 additions & 0 deletions Riot/Generated/Strings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4819,6 +4819,10 @@ public class VectorL10n: NSObject {
public static var pollHistoryActiveSegmentTitle: String {
return VectorL10n.tr("Vector", "poll_history_active_segment_title")
}
/// Error fetching polls.
public static var pollHistoryFetchingError: String {
return VectorL10n.tr("Vector", "poll_history_fetching_error")
}
/// Load more polls
public static var pollHistoryLoadMore: String {
return VectorL10n.tr("Vector", "poll_history_load_more")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ final class PollHistoryCoordinator: Coordinator, Presentable {

func start() {
MXLog.debug("[PollHistoryCoordinator] did start.")
pollHistoryViewModel.completion = { [weak self] result in
self?.completion?()
pollHistoryViewModel.completion = { _ in

}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,11 @@ enum MockPollHistoryScreenState: MockScreenState, CaseIterable {
// mock that screen.
case active
case past
case activeEmpty
case pastEmpty
case activeNoMoreContent
case contentLoading
case empty
case emptyLoading
case emptyNoMoreContent
case loading

/// The associated screen
Expand All @@ -37,34 +40,40 @@ enum MockPollHistoryScreenState: MockScreenState, CaseIterable {

/// Generate the view struct for the screen state.
var screenView: ([Any], AnyView) {
let pollHistoryMode: PollHistoryMode
var pollHistoryMode: PollHistoryMode = .active
let pollService = MockPollHistoryService()

switch self {
case .active:
pollHistoryMode = .active
case .activeNoMoreContent:
pollHistoryMode = .active
pollService.hasNextBatch = false
case .past:
pollHistoryMode = .past
case .activeEmpty:
case .contentLoading:
pollService.nextBatchPublishers.append(MockPollPublisher.loadingPolls)
case .empty:
pollHistoryMode = .active
pollService.nextBatchPublisher = Empty(completeImmediately: true,
outputType: TimelinePollDetails.self,
failureType: Error.self).eraseToAnyPublisher()
case .pastEmpty:
pollHistoryMode = .past
pollService.nextBatchPublisher = Empty(completeImmediately: true,
outputType: TimelinePollDetails.self,
failureType: Error.self).eraseToAnyPublisher()
pollService.nextBatchPublishers = [MockPollPublisher.emptyPolls]
case .emptyLoading:
pollService.nextBatchPublishers = [MockPollPublisher.emptyPolls, MockPollPublisher.loadingPolls]
case .emptyNoMoreContent:
pollService.hasNextBatch = false
pollService.nextBatchPublishers = [MockPollPublisher.emptyPolls]
case .loading:
pollHistoryMode = .active
pollService.nextBatchPublisher = Empty(completeImmediately: false,
outputType: TimelinePollDetails.self,
failureType: Error.self).eraseToAnyPublisher()
pollService.nextBatchPublishers = [MockPollPublisher.loadingPolls]
}

let viewModel = PollHistoryViewModel(mode: pollHistoryMode, pollService: pollService)

// can simulate service and viewModel actions here if needs be.
switch self {
case .contentLoading, .emptyLoading:
viewModel.process(viewAction: .loadMoreContent)
default:
break
}

return (
[pollHistoryMode, viewModel],
Expand All @@ -73,3 +82,17 @@ enum MockPollHistoryScreenState: MockScreenState, CaseIterable {
)
}
}

enum MockPollPublisher {
static var emptyPolls: AnyPublisher<TimelinePollDetails, Error> {
Empty<TimelinePollDetails, Error>(completeImmediately: true).eraseToAnyPublisher()
}

static var loadingPolls: AnyPublisher<TimelinePollDetails, Error> {
Empty<TimelinePollDetails, Error>(completeImmediately: false).eraseToAnyPublisher()
}

static var failure: AnyPublisher<TimelinePollDetails, Error> {
Fail(error: NSError(domain: "fake", code: 1)).eraseToAnyPublisher()
}
}
6 changes: 5 additions & 1 deletion RiotSwiftUI/Modules/Room/PollHistory/PollHistoryModels.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ enum PollHistoryConstants {
}

enum PollHistoryViewModelResult: Equatable {
#warning("e.g. show poll detail")

}

// MARK: View
Expand All @@ -33,6 +33,7 @@ enum PollHistoryMode: CaseIterable {

struct PollHistoryViewBindings {
var mode: PollHistoryMode
var alertInfo: AlertInfo<Bool>?
}

struct PollHistoryViewState: BindableState {
Expand All @@ -44,9 +45,12 @@ struct PollHistoryViewState: BindableState {
var isLoading = false
var canLoadMoreContent = true
var polls: [TimelinePollDetails]?
var syncStartDate: Date = .init()
var syncedUpTo: Date = .distantFuture
}

enum PollHistoryViewAction {
case viewAppeared
case segmentDidChange
case loadMoreContent
}
59 changes: 45 additions & 14 deletions RiotSwiftUI/Modules/Room/PollHistory/PollHistoryViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ final class PollHistoryViewModel: PollHistoryViewModelType, PollHistoryViewModel
init(mode: PollHistoryMode, pollService: PollHistoryServiceProtocol) {
self.pollService = pollService
super.init(initialViewState: PollHistoryViewState(mode: mode))
state.canLoadMoreContent = pollService.hasNextBatch
}

// MARK: - Public
Expand All @@ -37,30 +38,45 @@ final class PollHistoryViewModel: PollHistoryViewModelType, PollHistoryViewModel
switch viewAction {
case .viewAppeared:
setupUpdateSubscriptions()
fetchFirstBatch()
fetchContent()
case .segmentDidChange:
updateViewState()
case .loadMoreContent:
fetchContent()
}
}
}

private extension PollHistoryViewModel {
func fetchFirstBatch() {
func fetchContent() {
state.isLoading = true

pollService
.nextBatch()
.collect()
.sink { [weak self] _ in
#warning("Handle errors")
self?.state.isLoading = false
.sink { [weak self] completion in
self?.handleBatchEnded(completion: completion)
} receiveValue: { [weak self] polls in
self?.polls = polls
self?.updateViewState()
self?.add(polls: polls)
}
.store(in: &subcriptions)
}

func handleBatchEnded(completion: Subscribers.Completion<Error>) {
state.isLoading = false
state.canLoadMoreContent = pollService.hasNextBatch

switch completion {
case .finished:
break
case .failure:
polls = polls ?? []
state.bindings.alertInfo = .init(id: true, title: VectorL10n.pollHistoryFetchingError)
}

updateViewState()
}

func setupUpdateSubscriptions() {
subcriptions.removeAll()

Expand All @@ -73,9 +89,15 @@ private extension PollHistoryViewModel {
.store(in: &subcriptions)

pollService
.pollErrors
.sink { detail in
#warning("Handle errors")
.fetchedUpTo
.weakAssign(to: \.state.syncedUpTo, on: self)
.store(in: &subcriptions)

pollService
.livePolls
.sink { [weak self] livePoll in
self?.add(polls: [livePoll])
self?.updateViewState()
}
.store(in: &subcriptions)
}
Expand All @@ -88,6 +110,10 @@ private extension PollHistoryViewModel {
polls?[pollIndex] = poll
}

func add(polls: [TimelinePollDetails]) {
self.polls = (self.polls ?? []) + polls
}

func updateViewState() {
let renderedPolls: [TimelinePollDetails]?

Expand All @@ -104,17 +130,22 @@ private extension PollHistoryViewModel {

extension PollHistoryViewModel.Context {
var emptyPollsText: String {
let days = PollHistoryConstants.chunkSizeInDays

switch (viewState.bindings.mode, viewState.canLoadMoreContent) {
case (.active, true):
return VectorL10n.pollHistoryNoActivePollPeriodText("\(days)")
return VectorL10n.pollHistoryNoActivePollPeriodText("\(syncedPastDays)")
case (.active, false):
return VectorL10n.pollHistoryNoActivePollText
case (.past, true):
return VectorL10n.pollHistoryNoPastPollPeriodText("\(days)")
return VectorL10n.pollHistoryNoPastPollPeriodText("\(syncedPastDays)")
case (.past, false):
return VectorL10n.pollHistoryNoPastPollText
}
}

var syncedPastDays: Int {
guard let days = Calendar.current.dateComponents([.day], from: viewState.syncedUpTo, to: viewState.syncStartDate).day else {
return 0
}
return max(0, days)
}
}
Loading

0 comments on commit 87d8aec

Please sign in to comment.