// 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 { typealias SectionModel = ArraySection> typealias ObservableData = AnyPublisher<([SectionModel], StagedChangeset<[SectionModel]>), Error> // MARK: - Input private let _isEditing: CurrentValueSubject = CurrentValueSubject(false) lazy var isEditing: AnyPublisher = _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 _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 = PassthroughSubject() lazy var dismissScreen: AnyPublisher = _dismissScreen .shareReplay(0) // MARK: - Content open var title: String { preconditionFailure("abstract class - override in subclass") } open var emptyStateTextPublisher: AnyPublisher { Just(nil).eraseToAnyPublisher() } open var footerView: AnyPublisher { Just(nil).eraseToAnyPublisher() } open var footerButtonInfo: AnyPublisher { 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 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( for viewModel: SessionTableViewModel? ) -> [ArraySection>] where Element == ArraySection> { // 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 AnyPublisher { func mapToSessionTableViewData( for viewModel: SessionTableViewModel ) -> AnyPublisher<(Output, StagedChangeset), Failure> where Output == [ArraySection>] { return self .map { [weak viewModel] updatedData -> (Output, StagedChangeset) 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() } }