// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import UIKit.UIImage import Combine import GRDB import DifferenceKit import SessionUIKit import SessionMessagingKit import SessionUtilitiesKit class SessionTableViewModel<NavItemId: Equatable, Section: SessionTableSection, SettingItem: Hashable & Differentiable> { typealias SectionModel = ArraySection<Section, SessionCell.Info<SettingItem>> typealias ObservableData = AnyPublisher<([SectionModel], StagedChangeset<[SectionModel]>), Error> // MARK: - Input private let _isEditing: CurrentValueSubject<Bool, Never> = CurrentValueSubject(false) lazy var isEditing: AnyPublisher<Bool, Never> = _isEditing .removeDuplicates() .shareReplay(1) private let _textChanged: PassthroughSubject<(text: String?, item: SettingItem), Never> = PassthroughSubject() lazy var textChanged: AnyPublisher<(text: String?, item: SettingItem), Never> = _textChanged .eraseToAnyPublisher() // MARK: - Navigation open var leftNavItems: AnyPublisher<[NavItem]?, Never> { Just(nil).eraseToAnyPublisher() } open var rightNavItems: AnyPublisher<[NavItem]?, Never> { Just(nil).eraseToAnyPublisher() } private let _forcedRefresh: PassthroughSubject<Void, Never> = PassthroughSubject() lazy var forcedRefresh: AnyPublisher<Void, Never> = _forcedRefresh .shareReplay(0) private let _showToast: PassthroughSubject<(String, ThemeValue), Never> = PassthroughSubject() lazy var showToast: AnyPublisher<(String, ThemeValue), Never> = _showToast .shareReplay(0) private let _transitionToScreen: PassthroughSubject<(UIViewController, TransitionType), Never> = PassthroughSubject() lazy var transitionToScreen: AnyPublisher<(UIViewController, TransitionType), Never> = _transitionToScreen .shareReplay(0) private let _dismissScreen: PassthroughSubject<DismissType, Never> = PassthroughSubject() lazy var dismissScreen: AnyPublisher<DismissType, Never> = _dismissScreen .shareReplay(0) // MARK: - Content open var title: String { preconditionFailure("abstract class - override in subclass") } open var emptyStateTextPublisher: AnyPublisher<String?, Never> { Just(nil).eraseToAnyPublisher() } open var footerView: AnyPublisher<UIView?, Never> { Just(nil).eraseToAnyPublisher() } open var footerButtonInfo: AnyPublisher<SessionButton.Info?, Never> { Just(nil).eraseToAnyPublisher() } fileprivate var hasEmittedInitialData: Bool = false public private(set) var tableData: [SectionModel] = [] open var observableTableData: ObservableData { preconditionFailure("abstract class - override in subclass") } open var pagedDataObserver: TransactionObserver? { nil } func updateTableData(_ updatedData: [SectionModel]) { self.tableData = updatedData } func loadPageBefore() { preconditionFailure("abstract class - override in subclass") } func loadPageAfter() { preconditionFailure("abstract class - override in subclass") } // MARK: - Functions func forceRefresh() { _forcedRefresh.send(()) } func setIsEditing(_ isEditing: Bool) { _isEditing.send(isEditing) } func textChanged(_ text: String?, for item: SettingItem) { _textChanged.send((text, item)) } func showToast(text: String, backgroundColor: ThemeValue = .backgroundPrimary) { _showToast.send((text, backgroundColor)) } func dismissScreen(type: DismissType = .auto) { _dismissScreen.send(type) } func transitionToScreen(_ viewController: UIViewController, transitionType: TransitionType = .push) { _transitionToScreen.send((viewController, transitionType)) } } // MARK: - Convenience extension Array { func mapToSessionTableViewData<Nav, Section, Item>( for viewModel: SessionTableViewModel<Nav, Section, Item>? ) -> [ArraySection<Section, SessionCell.Info<Item>>] where Element == ArraySection<Section, SessionCell.Info<Item>> { // Update the data to include the proper position for each element return self.map { section in ArraySection( model: section.model, elements: section.elements.enumerated().map { index, element in element.updatedPosition(for: index, count: section.elements.count) } ) } } } extension Publisher { func mapToSessionTableViewData<Nav, Section, Item>( for viewModel: SessionTableViewModel<Nav, Section, Item> ) -> AnyPublisher<(Output, StagedChangeset<Output>), Failure> where Output == [ArraySection<Section, SessionCell.Info<Item>>] { return self .map { [weak viewModel] updatedData -> (Output, StagedChangeset<Output>) in let updatedDataWithPositions: Output = updatedData .mapToSessionTableViewData(for: viewModel) // Generate an updated changeset let changeset = StagedChangeset( source: (viewModel?.tableData ?? []), target: updatedDataWithPositions ) return (updatedDataWithPositions, changeset) } .filter { [weak viewModel] _, changeset in viewModel?.hasEmittedInitialData == false || // Always emit at least once !changeset.isEmpty // Do nothing if there were no changes } .handleEvents(receiveOutput: { [weak viewModel] _ in viewModel?.hasEmittedInitialData = true }) .eraseToAnyPublisher() } }