session-ios/Session/Shared/SessionTableViewModel.swift
Morgan Pretty 977c2051ed Fixed a few bugs uncovered with further testing
Added some more logs to libSession build script and tweaked the stdout location
Added shadow threads to the GarbageCollectionJob
Changed the seed node retries to 2 because it's likely we will swap to another seed node pretty quickly which could resolve the issue
Fixed a bug where the user could get kicked from a draft conversation if they get a contacts update before sending a message
Fixed a bug where message status or media message download statuses would trigger the conversation to jump to the bottom
2023-05-10 17:27:36 +10:00

131 lines
5.4 KiB
Swift

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