This commit is contained in:
Justin Mazzocchi 2020-12-17 16:17:17 -08:00
parent 0c5a3de66b
commit d86bbda4c2
No known key found for this signature in database
GPG key ID: E223E6937AAFB01C
11 changed files with 27 additions and 76 deletions

View file

@ -1,9 +0,0 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
import GRDB
struct CompositionRecord: Codable, FetchableRecord, PersistableRecord {
let id: Composition.Id
let text: String
}

View file

@ -1,28 +0,0 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Combine
import Foundation
import GRDB
import Mastodon
public class Composition {
public let id: Id
@Published public var text: String
@Published public var attachments: [Attachment]
public init(id: Id, text: String) {
self.id = id
self.text = text
attachments = []
}
}
public extension Composition {
typealias Id = UUID
}
extension Composition {
convenience init(record: CompositionRecord) {
self.init(id: record.id, text: record.text)
}
}

View file

@ -3,7 +3,7 @@
import UIKit import UIKit
import ViewModels import ViewModels
final class NewStatusDataSource: UICollectionViewDiffableDataSource<Int, Composition.Id> { final class NewStatusDataSource: UICollectionViewDiffableDataSource<Int, CompositionViewModel.Id> {
private let updateQueue = private let updateQueue =
DispatchQueue(label: "com.metabolist.metatext.new-status-data-source.update-queue") DispatchQueue(label: "com.metabolist.metatext.new-status-data-source.update-queue")
@ -20,7 +20,7 @@ final class NewStatusDataSource: UICollectionViewDiffableDataSource<Int, Composi
} }
} }
override func apply(_ snapshot: NSDiffableDataSourceSnapshot<Int, Composition.Id>, override func apply(_ snapshot: NSDiffableDataSourceSnapshot<Int, CompositionViewModel.Id>,
animatingDifferences: Bool = true, animatingDifferences: Bool = true,
completion: (() -> Void)? = nil) { completion: (() -> Void)? = nil) {
updateQueue.async { updateQueue.async {

View file

@ -8,14 +8,13 @@ import Mastodon
import UserNotifications import UserNotifications
public struct AppEnvironment { public struct AppEnvironment {
public let uuid: () -> UUID
let session: URLSession let session: URLSession
let webAuthSessionType: WebAuthSession.Type let webAuthSessionType: WebAuthSession.Type
let keychain: Keychain.Type let keychain: Keychain.Type
let userDefaults: UserDefaults let userDefaults: UserDefaults
let userNotificationClient: UserNotificationClient let userNotificationClient: UserNotificationClient
let reduceMotion: () -> Bool let reduceMotion: () -> Bool
let uuid: () -> UUID
let inMemoryContent: Bool let inMemoryContent: Bool
let fixtureDatabase: IdentityDatabase? let fixtureDatabase: IdentityDatabase?

View file

@ -1,5 +0,0 @@
// Copyright © 2020 Metabolist. All rights reserved.
import DB
public typealias Composition = DB.Composition

View file

@ -212,8 +212,8 @@ public extension IdentityService {
progress: progress) progress: progress)
} }
func post(compositions: [Composition]) -> AnyPublisher<Never, Error> { // func post(compositions: [Composition]) -> AnyPublisher<Never, Error> {
fatalError() // fatalError()
// guard let composition = compositions.first else { fatalError() } // guard let composition = compositions.first else { fatalError() }
// guard let attachment = composition.attachments.first else { fatalError() } // guard let attachment = composition.attachments.first else { fatalError() }
@ -235,7 +235,7 @@ public extension IdentityService {
// return mastodonAPIClient.request(StatusEndpoint.post(components)) // return mastodonAPIClient.request(StatusEndpoint.post(components))
// .ignoreOutput() // .ignoreOutput()
// .eraseToAnyPublisher() // .eraseToAnyPublisher()
} // }
func service(timeline: Timeline) -> TimelineService { func service(timeline: Timeline) -> TimelineService {
TimelineService(timeline: timeline, mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase) TimelineService(timeline: timeline, mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)

View file

@ -61,7 +61,7 @@ class NewStatusViewController: UICollectionViewController {
guard let self = self else { return } guard let self = self else { return }
let oldSnapshot = self.dataSource.snapshot() let oldSnapshot = self.dataSource.snapshot()
let newSnapshot = [$0.map(\.composition.id)].snapshot() let newSnapshot = [$0.map(\.id)].snapshot()
let diff = newSnapshot.itemIdentifiers.difference(from: oldSnapshot.itemIdentifiers) let diff = newSnapshot.itemIdentifiers.difference(from: oldSnapshot.itemIdentifiers)
self.dataSource.apply(newSnapshot) { self.dataSource.apply(newSnapshot) {
@ -75,11 +75,8 @@ class NewStatusViewController: UICollectionViewController {
// Invalidate the collection view layout on anything that could change the height of a cell // Invalidate the collection view layout on anything that could change the height of a cell
viewModel.$compositionViewModels viewModel.$compositionViewModels
.flatMap { Publishers.MergeMany($0.map(\.composition.$text)) }
.map { _ in () }
.merge(with: viewModel.$compositionViewModels
.flatMap { Publishers.MergeMany($0.map(\.objectWillChange)) } .flatMap { Publishers.MergeMany($0.map(\.objectWillChange)) }
.map { _ in () }) .receive(on: DispatchQueue.main)
.sink { [weak self] in self?.collectionView.collectionViewLayout.invalidateLayout() } .sink { [weak self] in self?.collectionView.collectionViewLayout.invalidateLayout() }
.store(in: &cancellables) .store(in: &cancellables)
@ -89,6 +86,7 @@ class NewStatusViewController: UICollectionViewController {
viewModel.$alertItem viewModel.$alertItem
.compactMap { $0 } .compactMap { $0 }
.receive(on: DispatchQueue.main)
.sink { [weak self] in self?.present(alertItem: $0) } .sink { [weak self] in self?.present(alertItem: $0) }
.store(in: &cancellables) .store(in: &cancellables)
} }

View file

@ -6,7 +6,9 @@ import Mastodon
import ServiceLayer import ServiceLayer
public final class CompositionViewModel: ObservableObject { public final class CompositionViewModel: ObservableObject {
public let composition: Composition public let id = Id()
@Published public var text = ""
@Published public private(set) var attachments = [Attachment]()
@Published public private(set) var isPostable = false @Published public private(set) var isPostable = false
@Published public private(set) var identification: Identification @Published public private(set) var identification: Identification
@Published public private(set) var attachmentUpload: AttachmentUpload? @Published public private(set) var attachmentUpload: AttachmentUpload?
@ -14,19 +16,19 @@ public final class CompositionViewModel: ObservableObject {
private let eventsSubject: PassthroughSubject<Event, Never> private let eventsSubject: PassthroughSubject<Event, Never>
private var cancellables = Set<AnyCancellable>() private var cancellables = Set<AnyCancellable>()
init(composition: Composition, init(identification: Identification,
identification: Identification,
identificationPublisher: AnyPublisher<Identification, Never>, identificationPublisher: AnyPublisher<Identification, Never>,
eventsSubject: PassthroughSubject<Event, Never>) { eventsSubject: PassthroughSubject<Event, Never>) {
self.composition = composition
self.identification = identification self.identification = identification
self.eventsSubject = eventsSubject self.eventsSubject = eventsSubject
identificationPublisher.assign(to: &$identification) identificationPublisher.assign(to: &$identification)
composition.$text.map { !$0.isEmpty }.removeDuplicates().assign(to: &$isPostable) $text.map { !$0.isEmpty }.removeDuplicates().assign(to: &$isPostable)
} }
} }
public extension CompositionViewModel { public extension CompositionViewModel {
typealias Id = UUID
enum Event { enum Event {
case insertAfter(CompositionViewModel) case insertAfter(CompositionViewModel)
case presentMediaPicker(CompositionViewModel) case presentMediaPicker(CompositionViewModel)
@ -70,7 +72,7 @@ public extension CompositionViewModel {
self?.eventsSubject.send(.error(error)) self?.eventsSubject.send(.error(error))
} }
} receiveValue: { [weak self] in } receiveValue: { [weak self] in
self?.composition.attachments.append($0) self?.attachments.append($0)
} }
.store(in: &cancellables) .store(in: &cancellables)
} }

View file

@ -1,5 +0,0 @@
// Copyright © 2020 Metabolist. All rights reserved.
import ServiceLayer
public typealias Composition = ServiceLayer.Composition

View file

@ -67,21 +67,20 @@ public extension NewStatusViewModel {
} }
func post() { func post() {
identification.service.post(compositions: compositionViewModels.map(\.composition)) // identification.service.post(compositions: compositionViewModels.map(\.composition))
.receive(on: DispatchQueue.main) // .receive(on: DispatchQueue.main)
.handleEvents( // .handleEvents(
receiveSubscription: { [weak self] _ in self?.loading = true }, // receiveSubscription: { [weak self] _ in self?.loading = true },
receiveCompletion: { [weak self] _ in self?.loading = false }) // receiveCompletion: { [weak self] _ in self?.loading = false })
.assignErrorsToAlertItem(to: \.alertItem, on: self) // .assignErrorsToAlertItem(to: \.alertItem, on: self)
.sink { _ in } // .sink { _ in }
.store(in: &cancellables) // .store(in: &cancellables)
} }
} }
private extension NewStatusViewModel { private extension NewStatusViewModel {
func newCompositionViewModel() -> CompositionViewModel { func newCompositionViewModel() -> CompositionViewModel {
CompositionViewModel( CompositionViewModel(
composition: .init(id: environment.uuid(), text: ""),
identification: identification, identification: identification,
identificationPublisher: $identification.eraseToAnyPublisher(), identificationPublisher: $identification.eraseToAnyPublisher(),
eventsSubject: itemEventsSubject) eventsSubject: itemEventsSubject)

View file

@ -43,7 +43,7 @@ extension CompositionView: UIContentView {
extension CompositionView: UITextViewDelegate { extension CompositionView: UITextViewDelegate {
func textViewDidChange(_ textView: UITextView) { func textViewDidChange(_ textView: UITextView) {
compositionConfiguration.viewModel.composition.text = textView.text compositionConfiguration.viewModel.text = textView.text
} }
} }