mirror of https://github.com/oxen-io/session-ios
				
				
				
			
			You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			131 lines
		
	
	
		
			5.4 KiB
		
	
	
	
		
			Swift
		
	
			
		
		
	
	
			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()
 | |
|     }
 | |
| }
 |