|
|
|
// 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 _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 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 AnyPublisher {
|
|
|
|
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()
|
|
|
|
}
|
|
|
|
}
|