mirror of
https://github.com/metabolist/metatext.git
synced 2024-11-28 11:01:02 +00:00
wip
This commit is contained in:
parent
94cbbb2f04
commit
e6e1816e11
14 changed files with 240 additions and 58 deletions
|
@ -5,9 +5,28 @@ import UIKit
|
||||||
import ViewModels
|
import ViewModels
|
||||||
|
|
||||||
final class CompositionAttachmentsDataSource: UICollectionViewDiffableDataSource<Int, Attachment> {
|
final class CompositionAttachmentsDataSource: UICollectionViewDiffableDataSource<Int, Attachment> {
|
||||||
// init(collectionView: UICollectionView, composition: Composition) {
|
private let updateQueue =
|
||||||
// super.init(collectionView: collectionView) { collectionView, indexPath, attachment in
|
DispatchQueue(label: "com.metabolist.metatext.composition-attachments-data-source.update-queue")
|
||||||
//
|
|
||||||
// }
|
init(collectionView: UICollectionView, viewModelProvider: @escaping (IndexPath) -> CompositionAttachmentViewModel) {
|
||||||
// }
|
let registration = UICollectionView.CellRegistration
|
||||||
|
<CompositionAttachmentCollectionViewCell, CompositionAttachmentViewModel> {
|
||||||
|
$0.viewModel = $2
|
||||||
|
}
|
||||||
|
|
||||||
|
super.init(collectionView: collectionView) { collectionView, indexPath, _ in
|
||||||
|
collectionView.dequeueConfiguredReusableCell(
|
||||||
|
using: registration,
|
||||||
|
for: indexPath,
|
||||||
|
item: viewModelProvider(indexPath))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func apply(_ snapshot: NSDiffableDataSourceSnapshot<Int, Attachment>,
|
||||||
|
animatingDifferences: Bool = true,
|
||||||
|
completion: (() -> Void)? = nil) {
|
||||||
|
updateQueue.async {
|
||||||
|
super.apply(snapshot, animatingDifferences: animatingDifferences, completion: completion)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,9 +15,15 @@ public enum StatusEndpoint {
|
||||||
|
|
||||||
public extension StatusEndpoint {
|
public extension StatusEndpoint {
|
||||||
struct Components {
|
struct Components {
|
||||||
public var text: String?
|
public let inReplyToId: Status.Id?
|
||||||
|
public let text: String
|
||||||
|
public let mediaIds: [Attachment.Id]
|
||||||
|
|
||||||
public init() {}
|
public init(inReplyToId: Status.Id?, text: String, mediaIds: [Attachment.Id]) {
|
||||||
|
self.inReplyToId = inReplyToId
|
||||||
|
self.text = text
|
||||||
|
self.mediaIds = mediaIds
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,7 +31,15 @@ extension StatusEndpoint.Components {
|
||||||
var jsonBody: [String: Any]? {
|
var jsonBody: [String: Any]? {
|
||||||
var params = [String: Any]()
|
var params = [String: Any]()
|
||||||
|
|
||||||
params["status"] = text
|
if !text.isEmpty {
|
||||||
|
params["status"] = text
|
||||||
|
}
|
||||||
|
|
||||||
|
if !mediaIds.isEmpty {
|
||||||
|
params["media_ids"] = mediaIds
|
||||||
|
}
|
||||||
|
|
||||||
|
params["in_reply_to_id"] = inReplyToId
|
||||||
|
|
||||||
return params
|
return params
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,10 @@
|
||||||
D065966725899E910096AC5D /* CompositionAttachmentsDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D065965A25899DAE0096AC5D /* CompositionAttachmentsDataSource.swift */; };
|
D065966725899E910096AC5D /* CompositionAttachmentsDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D065965A25899DAE0096AC5D /* CompositionAttachmentsDataSource.swift */; };
|
||||||
D06B492324D4611300642749 /* KingfisherSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = D06B492224D4611300642749 /* KingfisherSwiftUI */; };
|
D06B492324D4611300642749 /* KingfisherSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = D06B492224D4611300642749 /* KingfisherSwiftUI */; };
|
||||||
D06BC5E625202AD90079541D /* ProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06BC5E525202AD90079541D /* ProfileViewController.swift */; };
|
D06BC5E625202AD90079541D /* ProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06BC5E525202AD90079541D /* ProfileViewController.swift */; };
|
||||||
|
D0804133258D902900AD6139 /* CompositionAttachmentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0804132258D902900AD6139 /* CompositionAttachmentView.swift */; };
|
||||||
|
D0804134258D902900AD6139 /* CompositionAttachmentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0804132258D902900AD6139 /* CompositionAttachmentView.swift */; };
|
||||||
|
D080413E258D904400AD6139 /* CompositionAttachmentContentConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D080413D258D904400AD6139 /* CompositionAttachmentContentConfiguration.swift */; };
|
||||||
|
D080413F258D904400AD6139 /* CompositionAttachmentContentConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D080413D258D904400AD6139 /* CompositionAttachmentContentConfiguration.swift */; };
|
||||||
D08B8D3D253F929E00B1EBEF /* ImageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08B8D3C253F929E00B1EBEF /* ImageViewController.swift */; };
|
D08B8D3D253F929E00B1EBEF /* ImageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08B8D3C253F929E00B1EBEF /* ImageViewController.swift */; };
|
||||||
D08B8D42253F92B600B1EBEF /* ImagePageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08B8D41253F92B600B1EBEF /* ImagePageViewController.swift */; };
|
D08B8D42253F92B600B1EBEF /* ImagePageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08B8D41253F92B600B1EBEF /* ImagePageViewController.swift */; };
|
||||||
D08B8D4A253FC36500B1EBEF /* ImageNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08B8D49253FC36500B1EBEF /* ImageNavigationController.swift */; };
|
D08B8D4A253FC36500B1EBEF /* ImageNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08B8D49253FC36500B1EBEF /* ImageNavigationController.swift */; };
|
||||||
|
@ -191,6 +195,8 @@
|
||||||
D0666A2124C677B400F3F04B /* Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
D0666A2124C677B400F3F04B /* Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
D0666A2524C677B400F3F04B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
D0666A2524C677B400F3F04B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
D06BC5E525202AD90079541D /* ProfileViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileViewController.swift; sourceTree = "<group>"; };
|
D06BC5E525202AD90079541D /* ProfileViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileViewController.swift; sourceTree = "<group>"; };
|
||||||
|
D0804132258D902900AD6139 /* CompositionAttachmentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompositionAttachmentView.swift; sourceTree = "<group>"; };
|
||||||
|
D080413D258D904400AD6139 /* CompositionAttachmentContentConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompositionAttachmentContentConfiguration.swift; sourceTree = "<group>"; };
|
||||||
D085C3BB25008DEC008A6C5E /* DB */ = {isa = PBXFileReference; lastKnownFileType = folder; path = DB; sourceTree = "<group>"; };
|
D085C3BB25008DEC008A6C5E /* DB */ = {isa = PBXFileReference; lastKnownFileType = folder; path = DB; sourceTree = "<group>"; };
|
||||||
D08B8D3C253F929E00B1EBEF /* ImageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageViewController.swift; sourceTree = "<group>"; };
|
D08B8D3C253F929E00B1EBEF /* ImageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageViewController.swift; sourceTree = "<group>"; };
|
||||||
D08B8D41253F92B600B1EBEF /* ImagePageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePageViewController.swift; sourceTree = "<group>"; };
|
D08B8D41253F92B600B1EBEF /* ImagePageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePageViewController.swift; sourceTree = "<group>"; };
|
||||||
|
@ -444,6 +450,8 @@
|
||||||
D0C7D42424F76169001EBDBB /* AddIdentityView.swift */,
|
D0C7D42424F76169001EBDBB /* AddIdentityView.swift */,
|
||||||
D0CE9F86258B076900E3A6B6 /* AttachmentUploadView.swift */,
|
D0CE9F86258B076900E3A6B6 /* AttachmentUploadView.swift */,
|
||||||
D065966025899E890096AC5D /* CompositionAttachmentCollectionViewCell.swift */,
|
D065966025899E890096AC5D /* CompositionAttachmentCollectionViewCell.swift */,
|
||||||
|
D080413D258D904400AD6139 /* CompositionAttachmentContentConfiguration.swift */,
|
||||||
|
D0804132258D902900AD6139 /* CompositionAttachmentView.swift */,
|
||||||
D08E52E2257D747400FA2C5F /* CompositionContentConfiguration.swift */,
|
D08E52E2257D747400FA2C5F /* CompositionContentConfiguration.swift */,
|
||||||
D0E9F9A9258450B300EF503D /* CompositionInputAccessoryView.swift */,
|
D0E9F9A9258450B300EF503D /* CompositionInputAccessoryView.swift */,
|
||||||
D08E52DB257D742B00FA2C5F /* CompositionListCell.swift */,
|
D08E52DB257D742B00FA2C5F /* CompositionListCell.swift */,
|
||||||
|
@ -767,6 +775,7 @@
|
||||||
D0C7D49C24F7616A001EBDBB /* RootView.swift in Sources */,
|
D0C7D49C24F7616A001EBDBB /* RootView.swift in Sources */,
|
||||||
D0F0B126251A90F400942152 /* AccountListCell.swift in Sources */,
|
D0F0B126251A90F400942152 /* AccountListCell.swift in Sources */,
|
||||||
D0B32F50250B373600311912 /* RegistrationView.swift in Sources */,
|
D0B32F50250B373600311912 /* RegistrationView.swift in Sources */,
|
||||||
|
D0804133258D902900AD6139 /* CompositionAttachmentView.swift in Sources */,
|
||||||
D08B8D612540DE3B00B1EBEF /* ZoomDismissalInteractionController.swift in Sources */,
|
D08B8D612540DE3B00B1EBEF /* ZoomDismissalInteractionController.swift in Sources */,
|
||||||
D036AA07254B6118009094DF /* NotificationView.swift in Sources */,
|
D036AA07254B6118009094DF /* NotificationView.swift in Sources */,
|
||||||
D08E52EE257D757100FA2C5F /* CompositionView.swift in Sources */,
|
D08E52EE257D757100FA2C5F /* CompositionView.swift in Sources */,
|
||||||
|
@ -791,6 +800,7 @@
|
||||||
D01F41D924F880C400D55A2D /* TouchFallthroughTextView.swift in Sources */,
|
D01F41D924F880C400D55A2D /* TouchFallthroughTextView.swift in Sources */,
|
||||||
D0C7D4D624F7616A001EBDBB /* NSMutableAttributedString+Extensions.swift in Sources */,
|
D0C7D4D624F7616A001EBDBB /* NSMutableAttributedString+Extensions.swift in Sources */,
|
||||||
D0E9F9AA258450B300EF503D /* CompositionInputAccessoryView.swift in Sources */,
|
D0E9F9AA258450B300EF503D /* CompositionInputAccessoryView.swift in Sources */,
|
||||||
|
D080413E258D904400AD6139 /* CompositionAttachmentContentConfiguration.swift in Sources */,
|
||||||
D0625E59250F092900502611 /* StatusListCell.swift in Sources */,
|
D0625E59250F092900502611 /* StatusListCell.swift in Sources */,
|
||||||
D0E569DB2529319100FA1D72 /* LoadMoreView.swift in Sources */,
|
D0E569DB2529319100FA1D72 /* LoadMoreView.swift in Sources */,
|
||||||
D0C7D49D24F7616A001EBDBB /* PostingReadingPreferencesView.swift in Sources */,
|
D0C7D49D24F7616A001EBDBB /* PostingReadingPreferencesView.swift in Sources */,
|
||||||
|
@ -867,11 +877,13 @@
|
||||||
D0E7AD4225870C79005F5E2D /* UIVIewController+Extensions.swift in Sources */,
|
D0E7AD4225870C79005F5E2D /* UIVIewController+Extensions.swift in Sources */,
|
||||||
D08E52EF257D757100FA2C5F /* CompositionView.swift in Sources */,
|
D08E52EF257D757100FA2C5F /* CompositionView.swift in Sources */,
|
||||||
D0CE9F88258B076900E3A6B6 /* AttachmentUploadView.swift in Sources */,
|
D0CE9F88258B076900E3A6B6 /* AttachmentUploadView.swift in Sources */,
|
||||||
|
D080413F258D904400AD6139 /* CompositionAttachmentContentConfiguration.swift in Sources */,
|
||||||
D0F2D5452581ABAB00986197 /* KingfisherOptionsInfo+Extensions.swift in Sources */,
|
D0F2D5452581ABAB00986197 /* KingfisherOptionsInfo+Extensions.swift in Sources */,
|
||||||
D08E52C7257C7AEE00FA2C5F /* ShareErrorViewController.swift in Sources */,
|
D08E52C7257C7AEE00FA2C5F /* ShareErrorViewController.swift in Sources */,
|
||||||
D08E52F8257D78BE00FA2C5F /* ViewConstants.swift in Sources */,
|
D08E52F8257D78BE00FA2C5F /* ViewConstants.swift in Sources */,
|
||||||
D0F2D4D6257EED6100986197 /* NewStatusDataSource.swift in Sources */,
|
D0F2D4D6257EED6100986197 /* NewStatusDataSource.swift in Sources */,
|
||||||
D065966725899E910096AC5D /* CompositionAttachmentsDataSource.swift in Sources */,
|
D065966725899E910096AC5D /* CompositionAttachmentsDataSource.swift in Sources */,
|
||||||
|
D0804134258D902900AD6139 /* CompositionAttachmentView.swift in Sources */,
|
||||||
D08E52FD257D78CB00FA2C5F /* UIColor+Extensions.swift in Sources */,
|
D08E52FD257D78CB00FA2C5F /* UIColor+Extensions.swift in Sources */,
|
||||||
D08E52E4257D747400FA2C5F /* CompositionContentConfiguration.swift in Sources */,
|
D08E52E4257D747400FA2C5F /* CompositionContentConfiguration.swift in Sources */,
|
||||||
D065966225899E890096AC5D /* CompositionAttachmentCollectionViewCell.swift in Sources */,
|
D065966225899E890096AC5D /* CompositionAttachmentCollectionViewCell.swift in Sources */,
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import MastodonAPI
|
||||||
|
|
||||||
|
public typealias StatusComponents = StatusEndpoint.Components
|
|
@ -212,30 +212,9 @@ public extension IdentityService {
|
||||||
progress: progress)
|
progress: progress)
|
||||||
}
|
}
|
||||||
|
|
||||||
// func post(compositions: [Composition]) -> AnyPublisher<Never, Error> {
|
func post(statusComponents: StatusComponents) -> AnyPublisher<Status.Id, Error> {
|
||||||
// fatalError()
|
mastodonAPIClient.request(StatusEndpoint.post(statusComponents)).map(\.id).eraseToAnyPublisher()
|
||||||
// guard let composition = compositions.first else { fatalError() }
|
}
|
||||||
|
|
||||||
// guard let attachment = composition.attachments.first else { fatalError() }
|
|
||||||
// return mastodonAPIClient.request(AttachmentEndpoint.create(
|
|
||||||
// data: attachment.data,
|
|
||||||
// mimeType: attachment.mimeType,
|
|
||||||
// description: attachment.description,
|
|
||||||
// focus: attachment.focus))
|
|
||||||
// .print()
|
|
||||||
// .ignoreOutput()
|
|
||||||
// .eraseToAnyPublisher()
|
|
||||||
|
|
||||||
// var components = StatusEndpoint.Components()
|
|
||||||
//
|
|
||||||
// if !composition.text.isEmpty {
|
|
||||||
// components.text = composition.text
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// return mastodonAPIClient.request(StatusEndpoint.post(components))
|
|
||||||
// .ignoreOutput()
|
|
||||||
// .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)
|
||||||
|
|
|
@ -6,7 +6,7 @@ import Mastodon
|
||||||
import ServiceLayer
|
import ServiceLayer
|
||||||
|
|
||||||
public final class CompositionAttachmentViewModel: ObservableObject {
|
public final class CompositionAttachmentViewModel: ObservableObject {
|
||||||
public var attachment: Attachment
|
public let attachment: Attachment
|
||||||
|
|
||||||
init(attachment: Attachment) {
|
init(attachment: Attachment) {
|
||||||
self.attachment = attachment
|
self.attachment = attachment
|
||||||
|
|
|
@ -7,8 +7,9 @@ import ServiceLayer
|
||||||
|
|
||||||
public final class CompositionViewModel: ObservableObject {
|
public final class CompositionViewModel: ObservableObject {
|
||||||
public let id = Id()
|
public let id = Id()
|
||||||
|
public var isPosted = false
|
||||||
@Published public var text = ""
|
@Published public var text = ""
|
||||||
@Published public private(set) var attachments = [Attachment]()
|
@Published public private(set) var attachmentViewModels = [CompositionAttachmentViewModel]()
|
||||||
@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?
|
||||||
|
@ -35,10 +36,11 @@ public extension CompositionViewModel {
|
||||||
case error(Error)
|
case error(Error)
|
||||||
}
|
}
|
||||||
|
|
||||||
struct AttachmentUpload {
|
func components(inReplyToId: Status.Id?) -> StatusComponents {
|
||||||
public let progress: Progress
|
StatusComponents(
|
||||||
public let data: Data
|
inReplyToId: inReplyToId,
|
||||||
public let mimeType: String
|
text: text,
|
||||||
|
mediaIds: attachmentViewModels.map(\.attachment.id))
|
||||||
}
|
}
|
||||||
|
|
||||||
func presentMediaPicker() {
|
func presentMediaPicker() {
|
||||||
|
@ -63,17 +65,20 @@ public extension CompositionViewModel {
|
||||||
return self.identification.service.uploadAttachment(data: data, mimeType: mimeType, progress: progress)
|
return self.identification.service.uploadAttachment(data: data, mimeType: mimeType, progress: progress)
|
||||||
}
|
}
|
||||||
.print()
|
.print()
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
.sink { [weak self] in
|
.sink { [weak self] in
|
||||||
DispatchQueue.main.async {
|
self?.attachmentUpload = nil
|
||||||
self?.attachmentUpload = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if case let .failure(error) = $0 {
|
if case let .failure(error) = $0 {
|
||||||
self?.eventsSubject.send(.error(error))
|
self?.eventsSubject.send(.error(error))
|
||||||
}
|
}
|
||||||
} receiveValue: { [weak self] in
|
} receiveValue: { [weak self] in
|
||||||
self?.attachments.append($0)
|
self?.attachmentViewModels.append(CompositionAttachmentViewModel(attachment: $0))
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func attachmentViewModel(indexPath: IndexPath) -> CompositionAttachmentViewModel {
|
||||||
|
attachmentViewModels[indexPath.item]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public struct AttachmentUpload: Hashable {
|
||||||
|
public let progress: Progress
|
||||||
|
public let data: Data
|
||||||
|
public let mimeType: String
|
||||||
|
}
|
|
@ -67,14 +67,9 @@ public extension NewStatusViewModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
func post() {
|
func post() {
|
||||||
// identification.service.post(compositions: compositionViewModels.map(\.composition))
|
guard let unposted = compositionViewModels.first(where: { !$0.isPosted }) else { return }
|
||||||
// .receive(on: DispatchQueue.main)
|
|
||||||
// .handleEvents(
|
post(viewModel: unposted, inReplyToId: nil)
|
||||||
// receiveSubscription: { [weak self] _ in self?.loading = true },
|
|
||||||
// receiveCompletion: { [weak self] _ in self?.loading = false })
|
|
||||||
// .assignErrorsToAlertItem(to: \.alertItem, on: self)
|
|
||||||
// .sink { _ in }
|
|
||||||
// .store(in: &cancellables)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,4 +99,30 @@ private extension NewStatusViewModel {
|
||||||
eventsSubject.send(event)
|
eventsSubject.send(event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func post(viewModel: CompositionViewModel, inReplyToId: Status.Id?) {
|
||||||
|
loading = true
|
||||||
|
identification.service.post(statusComponents: viewModel.components(inReplyToId: inReplyToId))
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
|
.sink { [weak self] in
|
||||||
|
guard let self = self else { return }
|
||||||
|
|
||||||
|
switch $0 {
|
||||||
|
case .finished:
|
||||||
|
self.loading = self.compositionViewModels.allSatisfy(\.isPosted)
|
||||||
|
case let .failure(error):
|
||||||
|
self.alertItem = AlertItem(error: error)
|
||||||
|
self.loading = false
|
||||||
|
}
|
||||||
|
} receiveValue: { [weak self] in
|
||||||
|
guard let self = self else { return }
|
||||||
|
|
||||||
|
viewModel.isPosted = true
|
||||||
|
|
||||||
|
if let unposted = self.compositionViewModels.first(where: { !$0.isPosted }) {
|
||||||
|
self.post(viewModel: unposted, inReplyToId: $0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ final class AttachmentUploadView: UIView {
|
||||||
let progressView = UIProgressView(progressViewStyle: .default)
|
let progressView = UIProgressView(progressViewStyle: .default)
|
||||||
private var progressCancellable: AnyCancellable?
|
private var progressCancellable: AnyCancellable?
|
||||||
|
|
||||||
var attachmentUpload: CompositionViewModel.AttachmentUpload? {
|
var attachmentUpload: AttachmentUpload? {
|
||||||
didSet {
|
didSet {
|
||||||
if let attachmentUpload = attachmentUpload {
|
if let attachmentUpload = attachmentUpload {
|
||||||
progressCancellable = attachmentUpload.progress.publisher(for: \.fractionCompleted)
|
progressCancellable = attachmentUpload.progress.publisher(for: \.fractionCompleted)
|
||||||
|
|
|
@ -1,7 +1,15 @@
|
||||||
// Copyright © 2020 Metabolist. All rights reserved.
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import ViewModels
|
||||||
|
|
||||||
class CompositionAttachmentCollectionViewCell: UICollectionViewCell {
|
class CompositionAttachmentCollectionViewCell: UICollectionViewCell {
|
||||||
|
var viewModel: CompositionAttachmentViewModel?
|
||||||
|
|
||||||
|
override func updateConfiguration(using state: UICellConfigurationState) {
|
||||||
|
guard let viewModel = viewModel else { return }
|
||||||
|
|
||||||
|
contentConfiguration = CompositionAttachmentContentConfiguration(viewModel: viewModel).updated(for: state)
|
||||||
|
backgroundConfiguration = UIBackgroundConfiguration.clear().updated(for: state)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
18
Views/CompositionAttachmentContentConfiguration.swift
Normal file
18
Views/CompositionAttachmentContentConfiguration.swift
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import ViewModels
|
||||||
|
|
||||||
|
struct CompositionAttachmentContentConfiguration {
|
||||||
|
let viewModel: CompositionAttachmentViewModel
|
||||||
|
}
|
||||||
|
|
||||||
|
extension CompositionAttachmentContentConfiguration: UIContentConfiguration {
|
||||||
|
func makeContentView() -> UIView & UIContentView {
|
||||||
|
CompositionAttachmentView(configuration: self)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updated(for state: UIConfigurationState) -> CompositionAttachmentContentConfiguration {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
59
Views/CompositionAttachmentView.swift
Normal file
59
Views/CompositionAttachmentView.swift
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import Kingfisher
|
||||||
|
import UIKit
|
||||||
|
import ViewModels
|
||||||
|
|
||||||
|
class CompositionAttachmentView: UIView {
|
||||||
|
let imageView = UIImageView()
|
||||||
|
private var compositionAttachmentConfiguration: CompositionAttachmentContentConfiguration
|
||||||
|
|
||||||
|
init(configuration: CompositionAttachmentContentConfiguration) {
|
||||||
|
self.compositionAttachmentConfiguration = configuration
|
||||||
|
|
||||||
|
super.init(frame: .zero)
|
||||||
|
|
||||||
|
initialSetup()
|
||||||
|
applyCompositionAttachmentConfiguration()
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable)
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension CompositionAttachmentView: UIContentView {
|
||||||
|
var configuration: UIContentConfiguration {
|
||||||
|
get { compositionAttachmentConfiguration }
|
||||||
|
set {
|
||||||
|
guard let compositionAttachmentConfiguration = newValue as? CompositionAttachmentContentConfiguration
|
||||||
|
else { return }
|
||||||
|
|
||||||
|
self.compositionAttachmentConfiguration = compositionAttachmentConfiguration
|
||||||
|
|
||||||
|
applyCompositionAttachmentConfiguration()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension CompositionAttachmentView {
|
||||||
|
func initialSetup() {
|
||||||
|
addSubview(imageView)
|
||||||
|
imageView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
imageView.contentMode = .scaleAspectFill
|
||||||
|
imageView.layer.cornerRadius = .defaultCornerRadius
|
||||||
|
imageView.clipsToBounds = true
|
||||||
|
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
imageView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||||
|
imageView.topAnchor.constraint(equalTo: topAnchor),
|
||||||
|
imageView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||||
|
imageView.bottomAnchor.constraint(equalTo: bottomAnchor)
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyCompositionAttachmentConfiguration() {
|
||||||
|
imageView.kf.setImage(with: compositionAttachmentConfiguration.viewModel.attachment.previewUrl)
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,18 +4,41 @@ import Combine
|
||||||
import Kingfisher
|
import Kingfisher
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
class CompositionView: UIView {
|
final class CompositionView: UIView {
|
||||||
let avatarImageView = UIImageView()
|
let avatarImageView = UIImageView()
|
||||||
let textView = UITextView()
|
let textView = UITextView()
|
||||||
let attachmentUploadView = AttachmentUploadView()
|
let attachmentUploadView = AttachmentUploadView()
|
||||||
// let attachmentsCollectionView = UICollectionView()
|
let attachmentsCollectionView: UICollectionView
|
||||||
|
|
||||||
private var compositionConfiguration: CompositionContentConfiguration
|
private var compositionConfiguration: CompositionContentConfiguration
|
||||||
private var cancellables = Set<AnyCancellable>()
|
private var cancellables = Set<AnyCancellable>()
|
||||||
|
|
||||||
|
private lazy var attachmentsDataSource: CompositionAttachmentsDataSource = {
|
||||||
|
CompositionAttachmentsDataSource(
|
||||||
|
collectionView: attachmentsCollectionView,
|
||||||
|
viewModelProvider: compositionConfiguration.viewModel.attachmentViewModel(indexPath:))
|
||||||
|
}()
|
||||||
|
|
||||||
init(configuration: CompositionContentConfiguration) {
|
init(configuration: CompositionContentConfiguration) {
|
||||||
self.compositionConfiguration = configuration
|
self.compositionConfiguration = configuration
|
||||||
|
|
||||||
|
let itemSize = NSCollectionLayoutSize(
|
||||||
|
widthDimension: .fractionalWidth(0.2),
|
||||||
|
heightDimension: .fractionalHeight(1.0))
|
||||||
|
let item = NSCollectionLayoutItem(layoutSize: itemSize)
|
||||||
|
let groupSize = NSCollectionLayoutSize(
|
||||||
|
widthDimension: .fractionalWidth(1.0),
|
||||||
|
heightDimension: .fractionalWidth(0.2))
|
||||||
|
let group = NSCollectionLayoutGroup.horizontal(
|
||||||
|
layoutSize: groupSize,
|
||||||
|
subitems: [item])
|
||||||
|
|
||||||
|
group.interItemSpacing = .fixed(.defaultSpacing)
|
||||||
|
|
||||||
|
let section = NSCollectionLayoutSection(group: group)
|
||||||
|
let attachmentsLayout = UICollectionViewCompositionalLayout(section: section)
|
||||||
|
attachmentsCollectionView = UICollectionView(frame: .zero, collectionViewLayout: attachmentsLayout)
|
||||||
|
|
||||||
super.init(frame: .zero)
|
super.init(frame: .zero)
|
||||||
|
|
||||||
initialSetup()
|
initialSetup()
|
||||||
|
@ -48,7 +71,7 @@ extension CompositionView: UITextViewDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension CompositionView {
|
private extension CompositionView {
|
||||||
static let attachmentsCollectionViewHeight: CGFloat = 100
|
static let attachmentUploadViewHeight: CGFloat = 100
|
||||||
|
|
||||||
func initialSetup() {
|
func initialSetup() {
|
||||||
addSubview(avatarImageView)
|
addSubview(avatarImageView)
|
||||||
|
@ -71,7 +94,8 @@ private extension CompositionView {
|
||||||
textView.inputAccessoryView?.sizeToFit()
|
textView.inputAccessoryView?.sizeToFit()
|
||||||
textView.delegate = self
|
textView.delegate = self
|
||||||
|
|
||||||
// stackView.addArrangedSubview(attachmentsCollectionView)
|
stackView.addArrangedSubview(attachmentsCollectionView)
|
||||||
|
attachmentsCollectionView.dataSource = attachmentsDataSource
|
||||||
|
|
||||||
stackView.addArrangedSubview(attachmentUploadView)
|
stackView.addArrangedSubview(attachmentUploadView)
|
||||||
|
|
||||||
|
@ -85,8 +109,10 @@ private extension CompositionView {
|
||||||
stackView.topAnchor.constraint(equalTo: readableContentGuide.topAnchor),
|
stackView.topAnchor.constraint(equalTo: readableContentGuide.topAnchor),
|
||||||
stackView.trailingAnchor.constraint(equalTo: readableContentGuide.trailingAnchor),
|
stackView.trailingAnchor.constraint(equalTo: readableContentGuide.trailingAnchor),
|
||||||
stackView.bottomAnchor.constraint(equalTo: readableContentGuide.bottomAnchor),
|
stackView.bottomAnchor.constraint(equalTo: readableContentGuide.bottomAnchor),
|
||||||
// attachmentsCollectionView.heightAnchor.constraint(equalToConstant: Self.attachmentsCollectionViewHeight)
|
attachmentsCollectionView.heightAnchor.constraint(
|
||||||
attachmentUploadView.heightAnchor.constraint(equalToConstant: Self.attachmentsCollectionViewHeight)
|
equalTo: attachmentsCollectionView.widthAnchor,
|
||||||
|
multiplier: 1 / 4),
|
||||||
|
attachmentUploadView.heightAnchor.constraint(equalToConstant: Self.attachmentUploadViewHeight)
|
||||||
]
|
]
|
||||||
|
|
||||||
for constraint in constraints {
|
for constraint in constraints {
|
||||||
|
@ -99,6 +125,13 @@ private extension CompositionView {
|
||||||
.sink { [weak self] in self?.avatarImageView.kf.setImage(with: $0) }
|
.sink { [weak self] in self?.avatarImageView.kf.setImage(with: $0) }
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
compositionConfiguration.viewModel.$attachmentViewModels
|
||||||
|
.sink { [weak self] in
|
||||||
|
self?.attachmentsDataSource.apply([$0.map(\.attachment)].snapshot())
|
||||||
|
self?.attachmentsCollectionView.isHidden = $0.isEmpty
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
|
||||||
compositionConfiguration.viewModel.$attachmentUpload
|
compositionConfiguration.viewModel.$attachmentUpload
|
||||||
.sink { [weak self] in self?.attachmentUploadView.attachmentUpload = $0 }
|
.sink { [weak self] in self?.attachmentUploadView.attachmentUpload = $0 }
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
Loading…
Reference in a new issue