mirror of
https://github.com/metabolist/metatext.git
synced 2024-11-21 15:50:59 +00:00
wip
This commit is contained in:
parent
ca2e8cf27e
commit
86b9e4c903
17 changed files with 430 additions and 358 deletions
|
@ -8,7 +8,8 @@ final class CompositionAttachmentsDataSource: UICollectionViewDiffableDataSource
|
|||
private let updateQueue =
|
||||
DispatchQueue(label: "com.metabolist.metatext.composition-attachments-data-source.update-queue")
|
||||
|
||||
init(collectionView: UICollectionView, viewModelProvider: @escaping (IndexPath) -> CompositionAttachmentViewModel) {
|
||||
init(collectionView: UICollectionView,
|
||||
viewModelProvider: @escaping (IndexPath) -> CompositionAttachmentViewModel?) {
|
||||
let registration = UICollectionView.CellRegistration
|
||||
<CompositionAttachmentCollectionViewCell, CompositionAttachmentViewModel> {
|
||||
$0.viewModel = $2
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
// Copyright © 2020 Metabolist. All rights reserved.
|
||||
|
||||
import UIKit
|
||||
import ViewModels
|
||||
|
||||
final class NewStatusDataSource: UICollectionViewDiffableDataSource<Int, CompositionViewModel.Id> {
|
||||
private let updateQueue =
|
||||
DispatchQueue(label: "com.metabolist.metatext.new-status-data-source.update-queue")
|
||||
|
||||
init(collectionView: UICollectionView, viewModelProvider: @escaping (IndexPath) -> CompositionViewModel) {
|
||||
let registration = UICollectionView.CellRegistration<CompositionListCell, CompositionViewModel> {
|
||||
$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, CompositionViewModel.Id>,
|
||||
animatingDifferences: Bool = true,
|
||||
completion: (() -> Void)? = nil) {
|
||||
updateQueue.async {
|
||||
super.apply(snapshot, animatingDifferences: animatingDifferences, completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
51
Extensions/Status+Extensions.swift
Normal file
51
Extensions/Status+Extensions.swift
Normal file
|
@ -0,0 +1,51 @@
|
|||
// Copyright © 2020 Metabolist. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import Mastodon
|
||||
|
||||
extension Status.Visibility {
|
||||
var systemImageName: String {
|
||||
switch self {
|
||||
case .public:
|
||||
return "network"
|
||||
case .unlisted:
|
||||
return "lock.open"
|
||||
case .private:
|
||||
return "lock"
|
||||
case .direct:
|
||||
return "envelope"
|
||||
case .unknown:
|
||||
return "questionmark"
|
||||
}
|
||||
}
|
||||
|
||||
var title: String? {
|
||||
switch self {
|
||||
case .public:
|
||||
return NSLocalizedString("status.visibility.public", comment: "")
|
||||
case .unlisted:
|
||||
return NSLocalizedString("status.visibility.unlisted", comment: "")
|
||||
case .private:
|
||||
return NSLocalizedString("status.visibility.private", comment: "")
|
||||
case .direct:
|
||||
return NSLocalizedString("status.visibility.direct", comment: "")
|
||||
case .unknown:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var description: String? {
|
||||
switch self {
|
||||
case .public:
|
||||
return NSLocalizedString("status.visibility.public.description", comment: "")
|
||||
case .unlisted:
|
||||
return NSLocalizedString("status.visibility.unlisted.description", comment: "")
|
||||
case .private:
|
||||
return NSLocalizedString("status.visibility.private.description", comment: "")
|
||||
case .direct:
|
||||
return NSLocalizedString("status.visibility.direct.description", comment: "")
|
||||
case .unknown:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
|
@ -138,6 +138,7 @@
|
|||
"report.forward-%@" = "Forward report to %@";
|
||||
"share-extension-error.no-account-found" = "No account found";
|
||||
"status.bookmark" = "Bookmark";
|
||||
"status.content-warning-abbreviation" = "CW";
|
||||
"status.reblogged-by" = "%@ boosted";
|
||||
"status.pinned-post" = "Pinned post";
|
||||
"status.show-more" = "Show More";
|
||||
|
@ -146,10 +147,16 @@
|
|||
"status.poll.time-left" = "%@ left";
|
||||
"status.poll.refresh" = "Refresh";
|
||||
"status.poll.closed" = "Closed";
|
||||
"status.spoiler-text-placeholder" = "Write your warning here";
|
||||
"status.unbookmark" = "Unbookmark";
|
||||
"status.visibility.public" = "Public";
|
||||
"status.visibility.unlisted" = "Unlisted";
|
||||
"status.visibility.private" = "Private";
|
||||
"status.visibility.private" = "Followers-only";
|
||||
"status.visibility.direct" = "Direct";
|
||||
"status.visibility.public.description" = "Visible for all, shown in public timelines";
|
||||
"status.visibility.unlisted.description" = "Visible for all, but not in public timelines";
|
||||
"status.visibility.private.description" = "Visible for followers only";
|
||||
"status.visibility.direct.description" = "Visible for mentioned users only";
|
||||
"submit" = "Submit";
|
||||
"timelines.home" = "Home";
|
||||
"timelines.local" = "Local";
|
||||
|
|
|
@ -17,12 +17,21 @@ public extension StatusEndpoint {
|
|||
struct Components {
|
||||
public let inReplyToId: Status.Id?
|
||||
public let text: String
|
||||
public let spoilerText: String
|
||||
public let mediaIds: [Attachment.Id]
|
||||
public let visibility: Status.Visibility
|
||||
|
||||
public init(inReplyToId: Status.Id?, text: String, mediaIds: [Attachment.Id]) {
|
||||
public init(
|
||||
inReplyToId: Status.Id?,
|
||||
text: String,
|
||||
spoilerText: String,
|
||||
mediaIds: [Attachment.Id],
|
||||
visibility: Status.Visibility) {
|
||||
self.inReplyToId = inReplyToId
|
||||
self.text = text
|
||||
self.spoilerText = spoilerText
|
||||
self.mediaIds = mediaIds
|
||||
self.visibility = visibility
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -35,11 +44,16 @@ extension StatusEndpoint.Components {
|
|||
params["status"] = text
|
||||
}
|
||||
|
||||
if !spoilerText.isEmpty {
|
||||
params["spoiler_text"] = spoilerText
|
||||
}
|
||||
|
||||
if !mediaIds.isEmpty {
|
||||
params["media_ids"] = mediaIds
|
||||
}
|
||||
|
||||
params["in_reply_to_id"] = inReplyToId
|
||||
params["visibility"] = visibility.rawValue
|
||||
|
||||
return params
|
||||
}
|
||||
|
|
|
@ -19,12 +19,15 @@
|
|||
D01F41D924F880C400D55A2D /* TouchFallthroughTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01F41D624F880C400D55A2D /* TouchFallthroughTextView.swift */; };
|
||||
D01F41E424F8889700D55A2D /* StatusAttachmentsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01F41E224F8889700D55A2D /* StatusAttachmentsView.swift */; };
|
||||
D02E1F95250B13210071AD56 /* SafariView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02E1F94250B13210071AD56 /* SafariView.swift */; };
|
||||
D036768E2593E6DE005DF15A /* Status+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0849C7E25903C4900A5EBCC /* Status+Extensions.swift */; };
|
||||
D036AA02254B6101009094DF /* NotificationListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D036AA01254B6101009094DF /* NotificationListCell.swift */; };
|
||||
D036AA07254B6118009094DF /* NotificationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D036AA06254B6118009094DF /* NotificationView.swift */; };
|
||||
D036AA0C254B612B009094DF /* NotificationContentConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D036AA0B254B612B009094DF /* NotificationContentConfiguration.swift */; };
|
||||
D036AA17254CA824009094DF /* StatusBodyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D036AA16254CA823009094DF /* StatusBodyView.swift */; };
|
||||
D038273C259EA38F00056E0F /* NewStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FCC10F259C4F20000B67DF /* NewStatusView.swift */; };
|
||||
D03B1B2A253818F3008F964B /* MediaPreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B1B29253818F3008F964B /* MediaPreferencesView.swift */; };
|
||||
D04226FD2546AC0B000980A3 /* StartupAndSyncingPreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04226FC2546AC0B000980A3 /* StartupAndSyncingPreferencesView.swift */; };
|
||||
D04F9E8E259E9C950081B0C9 /* ViewModels in Frameworks */ = {isa = PBXBuildFile; productRef = D04F9E8D259E9C950081B0C9 /* ViewModels */; };
|
||||
D0625E59250F092900502611 /* StatusListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0625E58250F092900502611 /* StatusListCell.swift */; };
|
||||
D0625E5D250F0B5C00502611 /* StatusContentConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0625E5C250F0B5C00502611 /* StatusContentConfiguration.swift */; };
|
||||
D065965B25899DAE0096AC5D /* CompositionAttachmentsDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D065965A25899DAE0096AC5D /* CompositionAttachmentsDataSource.swift */; };
|
||||
|
@ -37,6 +40,7 @@
|
|||
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 */; };
|
||||
D0849C7F25903C4900A5EBCC /* Status+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0849C7E25903C4900A5EBCC /* Status+Extensions.swift */; };
|
||||
D08B8D3D253F929E00B1EBEF /* ImageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08B8D3C253F929E00B1EBEF /* ImageViewController.swift */; };
|
||||
D08B8D42253F92B600B1EBEF /* ImagePageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08B8D41253F92B600B1EBEF /* ImagePageViewController.swift */; };
|
||||
D08B8D4A253FC36500B1EBEF /* ImageNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08B8D49253FC36500B1EBEF /* ImageNavigationController.swift */; };
|
||||
|
@ -50,18 +54,10 @@
|
|||
D08E512125786A6600FA2C5F /* UIButton+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E512025786A6600FA2C5F /* UIButton+Extensions.swift */; };
|
||||
D08E52612579D2E100FA2C5F /* DomainBlocksView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E52602579D2E100FA2C5F /* DomainBlocksView.swift */; };
|
||||
D08E5276257C36CA00FA2C5F /* Share Extension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = D08E526C257C36CA00FA2C5F /* Share Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
D08E5292257C53B600FA2C5F /* NewStatusViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E5291257C53B600FA2C5F /* NewStatusViewController.swift */; };
|
||||
D08E529C257C58D600FA2C5F /* NewStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E529B257C58D600FA2C5F /* NewStatusView.swift */; };
|
||||
D08E52A6257C61C000FA2C5F /* ShareExtensionNavigationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E52A5257C61C000FA2C5F /* ShareExtensionNavigationViewController.swift */; };
|
||||
D08E52B8257C62D500FA2C5F /* ViewModels in Frameworks */ = {isa = PBXBuildFile; productRef = D08E52B7257C62D500FA2C5F /* ViewModels */; };
|
||||
D08E52BD257C635800FA2C5F /* NewStatusViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E5291257C53B600FA2C5F /* NewStatusViewController.swift */; };
|
||||
D08E52C7257C7AEE00FA2C5F /* ShareErrorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E52C6257C7AEE00FA2C5F /* ShareErrorViewController.swift */; };
|
||||
D08E52CC257C80E300FA2C5F /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = D0C7D45724F76169001EBDBB /* Localizable.strings */; };
|
||||
D08E52D2257C811200FA2C5F /* ShareExtensionError+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E52D1257C811200FA2C5F /* ShareExtensionError+Extensions.swift */; };
|
||||
D08E52DC257D742B00FA2C5F /* CompositionListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E52DB257D742B00FA2C5F /* CompositionListCell.swift */; };
|
||||
D08E52DD257D742B00FA2C5F /* CompositionListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E52DB257D742B00FA2C5F /* CompositionListCell.swift */; };
|
||||
D08E52E3257D747400FA2C5F /* CompositionContentConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E52E2257D747400FA2C5F /* CompositionContentConfiguration.swift */; };
|
||||
D08E52E4257D747400FA2C5F /* CompositionContentConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E52E2257D747400FA2C5F /* CompositionContentConfiguration.swift */; };
|
||||
D08E52EE257D757100FA2C5F /* CompositionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E52ED257D757100FA2C5F /* CompositionView.swift */; };
|
||||
D08E52EF257D757100FA2C5F /* CompositionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E52ED257D757100FA2C5F /* CompositionView.swift */; };
|
||||
D08E52F8257D78BE00FA2C5F /* ViewConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0EA59472522B8B600804347 /* ViewConstants.swift */; };
|
||||
|
@ -119,12 +115,13 @@
|
|||
D0F0B126251A90F400942152 /* AccountListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F0B125251A90F400942152 /* AccountListCell.swift */; };
|
||||
D0F0B12E251A97E400942152 /* TableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F0B12D251A97E400942152 /* TableViewController.swift */; };
|
||||
D0F0B136251AA12700942152 /* CollectionItem+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F0B135251AA12700942152 /* CollectionItem+Extensions.swift */; };
|
||||
D0F2D4D1257EE84400986197 /* NewStatusDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F2D4D0257EE84400986197 /* NewStatusDataSource.swift */; };
|
||||
D0F2D4D6257EED6100986197 /* NewStatusDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F2D4D0257EE84400986197 /* NewStatusDataSource.swift */; };
|
||||
D0F2D4DB257F018300986197 /* Array+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01C6FAB252024BD003D0300 /* Array+Extensions.swift */; };
|
||||
D0F2D54025818C4B00986197 /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = D0F2D53F25818C4B00986197 /* Kingfisher */; };
|
||||
D0F2D5452581ABAB00986197 /* KingfisherOptionsInfo+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D46E24F76169001EBDBB /* KingfisherOptionsInfo+Extensions.swift */; };
|
||||
D0F2D54B2581CF7D00986197 /* VisualEffectBlur.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F2D54A2581CF7D00986197 /* VisualEffectBlur.swift */; };
|
||||
D0FCC105259C4E61000B67DF /* NewStatusViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FCC104259C4E61000B67DF /* NewStatusViewController.swift */; };
|
||||
D0FCC106259C4E62000B67DF /* NewStatusViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FCC104259C4E61000B67DF /* NewStatusViewController.swift */; };
|
||||
D0FCC110259C4F20000B67DF /* NewStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FCC10F259C4F20000B67DF /* NewStatusView.swift */; };
|
||||
D0FE1C8F253686F9003EF1EB /* PlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FE1C8E253686F9003EF1EB /* PlayerView.swift */; };
|
||||
D0FE1C9825368A9D003EF1EB /* PlayerCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FE1C9725368A9D003EF1EB /* PlayerCache.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
@ -197,6 +194,7 @@
|
|||
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>"; };
|
||||
D0849C7E25903C4900A5EBCC /* Status+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Status+Extensions.swift"; 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>"; };
|
||||
D08B8D41253F92B600B1EBEF /* ImagePageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePageViewController.swift; sourceTree = "<group>"; };
|
||||
|
@ -213,13 +211,9 @@
|
|||
D08E526C257C36CA00FA2C5F /* Share Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Share Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
D08E5273257C36CA00FA2C5F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
D08E5277257C36CB00FA2C5F /* Share Extension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Share Extension.entitlements"; sourceTree = "<group>"; };
|
||||
D08E5291257C53B600FA2C5F /* NewStatusViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewStatusViewController.swift; sourceTree = "<group>"; };
|
||||
D08E529B257C58D600FA2C5F /* NewStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewStatusView.swift; sourceTree = "<group>"; };
|
||||
D08E52A5257C61C000FA2C5F /* ShareExtensionNavigationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareExtensionNavigationViewController.swift; sourceTree = "<group>"; };
|
||||
D08E52C6257C7AEE00FA2C5F /* ShareErrorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareErrorViewController.swift; sourceTree = "<group>"; };
|
||||
D08E52D1257C811200FA2C5F /* ShareExtensionError+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ShareExtensionError+Extensions.swift"; sourceTree = "<group>"; };
|
||||
D08E52DB257D742B00FA2C5F /* CompositionListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompositionListCell.swift; sourceTree = "<group>"; };
|
||||
D08E52E2257D747400FA2C5F /* CompositionContentConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompositionContentConfiguration.swift; sourceTree = "<group>"; };
|
||||
D08E52ED257D757100FA2C5F /* CompositionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompositionView.swift; sourceTree = "<group>"; };
|
||||
D0A1F4F6252E7D4B004435BF /* TableViewDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewDataSource.swift; sourceTree = "<group>"; };
|
||||
D0A7AC7225748BFF00E4E8AB /* ReportStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportStatusView.swift; sourceTree = "<group>"; };
|
||||
|
@ -280,8 +274,9 @@
|
|||
D0F0B125251A90F400942152 /* AccountListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountListCell.swift; sourceTree = "<group>"; };
|
||||
D0F0B12D251A97E400942152 /* TableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewController.swift; sourceTree = "<group>"; };
|
||||
D0F0B135251AA12700942152 /* CollectionItem+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CollectionItem+Extensions.swift"; sourceTree = "<group>"; };
|
||||
D0F2D4D0257EE84400986197 /* NewStatusDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewStatusDataSource.swift; sourceTree = "<group>"; };
|
||||
D0F2D54A2581CF7D00986197 /* VisualEffectBlur.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisualEffectBlur.swift; sourceTree = "<group>"; };
|
||||
D0FCC104259C4E61000B67DF /* NewStatusViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewStatusViewController.swift; sourceTree = "<group>"; };
|
||||
D0FCC10F259C4F20000B67DF /* NewStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewStatusView.swift; sourceTree = "<group>"; };
|
||||
D0FE1C8E253686F9003EF1EB /* PlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerView.swift; sourceTree = "<group>"; };
|
||||
D0FE1C9725368A9D003EF1EB /* PlayerCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerCache.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
@ -309,7 +304,7 @@
|
|||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D0F2D54025818C4B00986197 /* Kingfisher in Frameworks */,
|
||||
D08E52B8257C62D500FA2C5F /* ViewModels in Frameworks */,
|
||||
D04F9E8E259E9C950081B0C9 /* ViewModels in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -424,7 +419,6 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
D0A1F4F6252E7D4B004435BF /* TableViewDataSource.swift */,
|
||||
D0F2D4D0257EE84400986197 /* NewStatusDataSource.swift */,
|
||||
D065965A25899DAE0096AC5D /* CompositionAttachmentsDataSource.swift */,
|
||||
);
|
||||
path = "Data Sources";
|
||||
|
@ -452,9 +446,7 @@
|
|||
D065966025899E890096AC5D /* CompositionAttachmentCollectionViewCell.swift */,
|
||||
D080413D258D904400AD6139 /* CompositionAttachmentContentConfiguration.swift */,
|
||||
D0804132258D902900AD6139 /* CompositionAttachmentView.swift */,
|
||||
D08E52E2257D747400FA2C5F /* CompositionContentConfiguration.swift */,
|
||||
D0E9F9A9258450B300EF503D /* CompositionInputAccessoryView.swift */,
|
||||
D08E52DB257D742B00FA2C5F /* CompositionListCell.swift */,
|
||||
D08E52ED257D757100FA2C5F /* CompositionView.swift */,
|
||||
D007023D25562A2800F38136 /* ConversationAvatarsView.swift */,
|
||||
D00702352555F4C500F38136 /* ConversationContentConfiguration.swift */,
|
||||
|
@ -471,7 +463,7 @@
|
|||
D0E569DF252931B100FA1D72 /* LoadMoreContentConfiguration.swift */,
|
||||
D0E569DA2529319100FA1D72 /* LoadMoreView.swift */,
|
||||
D03B1B29253818F3008F964B /* MediaPreferencesView.swift */,
|
||||
D08E529B257C58D600FA2C5F /* NewStatusView.swift */,
|
||||
D0FCC10F259C4F20000B67DF /* NewStatusView.swift */,
|
||||
D036AA0B254B612B009094DF /* NotificationContentConfiguration.swift */,
|
||||
D036AA01254B6101009094DF /* NotificationListCell.swift */,
|
||||
D0C7D42D24F76169001EBDBB /* NotificationTypesPreferencesView.swift */,
|
||||
|
@ -505,7 +497,7 @@
|
|||
D08B8D49253FC36500B1EBEF /* ImageNavigationController.swift */,
|
||||
D08B8D41253F92B600B1EBEF /* ImagePageViewController.swift */,
|
||||
D08B8D3C253F929E00B1EBEF /* ImageViewController.swift */,
|
||||
D08E5291257C53B600FA2C5F /* NewStatusViewController.swift */,
|
||||
D0FCC104259C4E61000B67DF /* NewStatusViewController.swift */,
|
||||
D06BC5E525202AD90079541D /* ProfileViewController.swift */,
|
||||
D0F0B12D251A97E400942152 /* TableViewController.swift */,
|
||||
);
|
||||
|
@ -538,6 +530,7 @@
|
|||
D0C7D46E24F76169001EBDBB /* KingfisherOptionsInfo+Extensions.swift */,
|
||||
D0C7D46B24F76169001EBDBB /* NSMutableAttributedString+Extensions.swift */,
|
||||
D0B5FE9A251583DB00478838 /* ProfileCollection+Extensions.swift */,
|
||||
D0849C7E25903C4900A5EBCC /* Status+Extensions.swift */,
|
||||
D0C7D46A24F76169001EBDBB /* String+Extensions.swift */,
|
||||
D08E512025786A6600FA2C5F /* UIButton+Extensions.swift */,
|
||||
D0C7D46C24F76169001EBDBB /* UIColor+Extensions.swift */,
|
||||
|
@ -629,8 +622,8 @@
|
|||
);
|
||||
name = "Share Extension";
|
||||
packageProductDependencies = (
|
||||
D08E52B7257C62D500FA2C5F /* ViewModels */,
|
||||
D0F2D53F25818C4B00986197 /* Kingfisher */,
|
||||
D04F9E8D259E9C950081B0C9 /* ViewModels */,
|
||||
);
|
||||
productName = "Share Extension";
|
||||
productReference = D08E526C257C36CA00FA2C5F /* Share Extension.appex */;
|
||||
|
@ -769,7 +762,6 @@
|
|||
files = (
|
||||
D0C7D4A324F7616A001EBDBB /* TabNavigationView.swift in Sources */,
|
||||
D02E1F95250B13210071AD56 /* SafariView.swift in Sources */,
|
||||
D0F2D4D1257EE84400986197 /* NewStatusDataSource.swift in Sources */,
|
||||
D00702292555E51200F38136 /* ConversationListCell.swift in Sources */,
|
||||
D03B1B2A253818F3008F964B /* MediaPreferencesView.swift in Sources */,
|
||||
D0C7D49C24F7616A001EBDBB /* RootView.swift in Sources */,
|
||||
|
@ -781,7 +773,6 @@
|
|||
D08E52EE257D757100FA2C5F /* CompositionView.swift in Sources */,
|
||||
D0E569E0252931B100FA1D72 /* LoadMoreContentConfiguration.swift in Sources */,
|
||||
D0FE1C9825368A9D003EF1EB /* PlayerCache.swift in Sources */,
|
||||
D08E52E3257D747400FA2C5F /* CompositionContentConfiguration.swift in Sources */,
|
||||
D0F0B136251AA12700942152 /* CollectionItem+Extensions.swift in Sources */,
|
||||
D007023E25562A2800F38136 /* ConversationAvatarsView.swift in Sources */,
|
||||
D0E7AD3925870B13005F5E2D /* UIVIewController+Extensions.swift in Sources */,
|
||||
|
@ -789,7 +780,6 @@
|
|||
D0BEB1F324F8EE8C001B0F04 /* StatusAttachmentView.swift in Sources */,
|
||||
D0C7D49A24F7616A001EBDBB /* TableView.swift in Sources */,
|
||||
D08B8D622540DE3B00B1EBEF /* ZoomTransitionController.swift in Sources */,
|
||||
D08E529C257C58D600FA2C5F /* NewStatusView.swift in Sources */,
|
||||
D0F0B12E251A97E400942152 /* TableViewController.swift in Sources */,
|
||||
D0DD50CB256B1F24004A04F7 /* ReportView.swift in Sources */,
|
||||
D0FE1C8F253686F9003EF1EB /* PlayerView.swift in Sources */,
|
||||
|
@ -800,15 +790,14 @@
|
|||
D01F41D924F880C400D55A2D /* TouchFallthroughTextView.swift in Sources */,
|
||||
D0C7D4D624F7616A001EBDBB /* NSMutableAttributedString+Extensions.swift in Sources */,
|
||||
D0E9F9AA258450B300EF503D /* CompositionInputAccessoryView.swift in Sources */,
|
||||
D0849C7F25903C4900A5EBCC /* Status+Extensions.swift in Sources */,
|
||||
D080413E258D904400AD6139 /* CompositionAttachmentContentConfiguration.swift in Sources */,
|
||||
D0625E59250F092900502611 /* StatusListCell.swift in Sources */,
|
||||
D0E569DB2529319100FA1D72 /* LoadMoreView.swift in Sources */,
|
||||
D0C7D49D24F7616A001EBDBB /* PostingReadingPreferencesView.swift in Sources */,
|
||||
D0B5FE9B251583DB00478838 /* ProfileCollection+Extensions.swift in Sources */,
|
||||
D08E52DC257D742B00FA2C5F /* CompositionListCell.swift in Sources */,
|
||||
D0C7D49E24F7616A001EBDBB /* SecondaryNavigationView.swift in Sources */,
|
||||
D08B8D602540DE3B00B1EBEF /* ZoomAnimator.swift in Sources */,
|
||||
D08E5292257C53B600FA2C5F /* NewStatusViewController.swift in Sources */,
|
||||
D08B8D672540DEB200B1EBEF /* ZoomAnimatableView.swift in Sources */,
|
||||
D08B8D822544D80000B1EBEF /* PollOptionButton.swift in Sources */,
|
||||
D0C7D4DA24F7616A001EBDBB /* View+Extensions.swift in Sources */,
|
||||
|
@ -847,11 +836,13 @@
|
|||
D0A1F4F7252E7D4B004435BF /* TableViewDataSource.swift in Sources */,
|
||||
D0C7D4C424F7616A001EBDBB /* AppDelegate.swift in Sources */,
|
||||
D0C7D49924F7616A001EBDBB /* AddIdentityView.swift in Sources */,
|
||||
D0FCC105259C4E61000B67DF /* NewStatusViewController.swift in Sources */,
|
||||
D0F2D54B2581CF7D00986197 /* VisualEffectBlur.swift in Sources */,
|
||||
D0A7AC7325748BFF00E4E8AB /* ReportStatusView.swift in Sources */,
|
||||
D0C7D4C324F7616A001EBDBB /* MetatextApp.swift in Sources */,
|
||||
D0E1F583251F13EC00D45315 /* WebfingerIndicatorView.swift in Sources */,
|
||||
D0BEB20524FA1107001B0F04 /* FiltersView.swift in Sources */,
|
||||
D0FCC110259C4F20000B67DF /* NewStatusView.swift in Sources */,
|
||||
D0C7D49B24F7616A001EBDBB /* PreferencesView.swift in Sources */,
|
||||
D0C7D4D724F7616A001EBDBB /* UIColor+Extensions.swift in Sources */,
|
||||
);
|
||||
|
@ -869,23 +860,22 @@
|
|||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D08E52A6257C61C000FA2C5F /* ShareExtensionNavigationViewController.swift in Sources */,
|
||||
D08E52BD257C635800FA2C5F /* NewStatusViewController.swift in Sources */,
|
||||
D08E52D2257C811200FA2C5F /* ShareExtensionError+Extensions.swift in Sources */,
|
||||
D0E9F9AB258450B300EF503D /* CompositionInputAccessoryView.swift in Sources */,
|
||||
D0F2D4DB257F018300986197 /* Array+Extensions.swift in Sources */,
|
||||
D08E52DD257D742B00FA2C5F /* CompositionListCell.swift in Sources */,
|
||||
D0E7AD4225870C79005F5E2D /* UIVIewController+Extensions.swift in Sources */,
|
||||
D038273C259EA38F00056E0F /* NewStatusView.swift in Sources */,
|
||||
D08E52EF257D757100FA2C5F /* CompositionView.swift in Sources */,
|
||||
D0CE9F88258B076900E3A6B6 /* AttachmentUploadView.swift in Sources */,
|
||||
D036768E2593E6DE005DF15A /* Status+Extensions.swift in Sources */,
|
||||
D080413F258D904400AD6139 /* CompositionAttachmentContentConfiguration.swift in Sources */,
|
||||
D0F2D5452581ABAB00986197 /* KingfisherOptionsInfo+Extensions.swift in Sources */,
|
||||
D08E52C7257C7AEE00FA2C5F /* ShareErrorViewController.swift in Sources */,
|
||||
D08E52F8257D78BE00FA2C5F /* ViewConstants.swift in Sources */,
|
||||
D0F2D4D6257EED6100986197 /* NewStatusDataSource.swift in Sources */,
|
||||
D065966725899E910096AC5D /* CompositionAttachmentsDataSource.swift in Sources */,
|
||||
D0FCC106259C4E62000B67DF /* NewStatusViewController.swift in Sources */,
|
||||
D0804134258D902900AD6139 /* CompositionAttachmentView.swift in Sources */,
|
||||
D08E52FD257D78CB00FA2C5F /* UIColor+Extensions.swift in Sources */,
|
||||
D08E52E4257D747400FA2C5F /* CompositionContentConfiguration.swift in Sources */,
|
||||
D065966225899E890096AC5D /* CompositionAttachmentCollectionViewCell.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
@ -1293,15 +1283,15 @@
|
|||
/* End XCRemoteSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
D04F9E8D259E9C950081B0C9 /* ViewModels */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = ViewModels;
|
||||
};
|
||||
D06B492224D4611300642749 /* KingfisherSwiftUI */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = D06B492124D4611300642749 /* XCRemoteSwiftPackageReference "Kingfisher" */;
|
||||
productName = KingfisherSwiftUI;
|
||||
};
|
||||
D08E52B7257C62D500FA2C5F /* ViewModels */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = ViewModels;
|
||||
};
|
||||
D0BECB972501C0FC002C1B13 /* Secrets */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = Secrets;
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import Combine
|
||||
import ServiceLayer
|
||||
import UIKit
|
||||
import SwiftUI
|
||||
import ViewModels
|
||||
|
||||
@objc(ShareExtensionNavigationViewController)
|
||||
|
@ -25,7 +25,7 @@ class ShareExtensionNavigationViewController: UINavigationController {
|
|||
}
|
||||
|
||||
setViewControllers(
|
||||
[NewStatusViewController(viewModel: newStatusViewModel, isShareExtension: true)],
|
||||
[UIHostingController(rootView: NewStatusView { newStatusViewModel })],
|
||||
animated: false)
|
||||
}
|
||||
|
||||
|
|
|
@ -6,29 +6,22 @@ import PhotosUI
|
|||
import UIKit
|
||||
import ViewModels
|
||||
|
||||
class NewStatusViewController: UICollectionViewController {
|
||||
final class NewStatusViewController: UIViewController {
|
||||
private let viewModel: NewStatusViewModel
|
||||
private let isShareExtension: Bool
|
||||
private let scrollView = UIScrollView()
|
||||
private let stackView = UIStackView()
|
||||
private let postButton = UIBarButtonItem(
|
||||
title: NSLocalizedString("post", comment: ""),
|
||||
style: .done,
|
||||
target: nil,
|
||||
action: nil)
|
||||
private var attachMediaTo: CompositionViewModel?
|
||||
private let mediaSelections = PassthroughSubject<[PHPickerResult], Never>()
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
||||
private lazy var dataSource: NewStatusDataSource = {
|
||||
.init(collectionView: collectionView, viewModelProvider: viewModel.viewModel(indexPath:))
|
||||
}()
|
||||
|
||||
init(viewModel: NewStatusViewModel, isShareExtension: Bool) {
|
||||
init(viewModel: NewStatusViewModel) {
|
||||
self.viewModel = viewModel
|
||||
self.isShareExtension = isShareExtension
|
||||
|
||||
let configuration = UICollectionLayoutListConfiguration(appearance: .plain)
|
||||
let layout = UICollectionViewCompositionalLayout.list(using: configuration)
|
||||
|
||||
super.init(collectionViewLayout: layout)
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
|
@ -39,15 +32,101 @@ class NewStatusViewController: UICollectionViewController {
|
|||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
collectionView.dataSource = dataSource
|
||||
|
||||
view.backgroundColor = .systemBackground
|
||||
|
||||
view.addSubview(scrollView)
|
||||
scrollView.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
scrollView.addSubview(stackView)
|
||||
stackView.translatesAutoresizingMaskIntoConstraints = false
|
||||
stackView.axis = .vertical
|
||||
stackView.distribution = .equalSpacing
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
||||
scrollView.topAnchor.constraint(equalTo: view.topAnchor),
|
||||
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
||||
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
|
||||
stackView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
|
||||
stackView.topAnchor.constraint(equalTo: scrollView.topAnchor),
|
||||
stackView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
|
||||
stackView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
|
||||
stackView.widthAnchor.constraint(equalTo: scrollView.widthAnchor)
|
||||
])
|
||||
|
||||
postButton.primaryAction = UIAction(title: NSLocalizedString("post", comment: "")) { [weak self] _ in
|
||||
self?.viewModel.post()
|
||||
}
|
||||
|
||||
setupViewModelBindings()
|
||||
}
|
||||
|
||||
override func didMove(toParent parent: UIViewController?) {
|
||||
super.didMove(toParent: parent)
|
||||
|
||||
setupBarButtonItems(identification: viewModel.identification)
|
||||
}
|
||||
}
|
||||
|
||||
extension NewStatusViewController: PHPickerViewControllerDelegate {
|
||||
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
|
||||
mediaSelections.send(results)
|
||||
dismiss(animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
private extension NewStatusViewController {
|
||||
func handle(event: NewStatusViewModel.Event) {
|
||||
switch event {
|
||||
case let .presentMediaPicker(compositionViewModel):
|
||||
presentMediaPicker(compositionViewModel: compositionViewModel)
|
||||
}
|
||||
}
|
||||
|
||||
func dismiss() {
|
||||
if let extensionContext = extensionContext {
|
||||
extensionContext.completeRequest(returningItems: nil)
|
||||
} else {
|
||||
presentingViewController?.dismiss(animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
func setupViewModelBindings() {
|
||||
viewModel.events.sink { [weak self] in self?.handle(event: $0) }.store(in: &cancellables)
|
||||
|
||||
viewModel.$canPost.sink { [weak self] in self?.postButton.isEnabled = $0 }.store(in: &cancellables)
|
||||
|
||||
viewModel.$compositionViewModels.sink { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
||||
let diff = [$0.map(\.id)].snapshot().itemIdentifiers.difference(
|
||||
from: [self.stackView.arrangedSubviews.compactMap { ($0 as? CompositionView)?.id }]
|
||||
.snapshot().itemIdentifiers)
|
||||
|
||||
for insertion in diff.insertions {
|
||||
guard case let .insert(index, id, _) = insertion,
|
||||
let compositionViewModel = $0.first(where: { $0.id == id })
|
||||
else { continue }
|
||||
|
||||
let compositionView = CompositionView(
|
||||
viewModel: compositionViewModel,
|
||||
parentViewModel: self.viewModel)
|
||||
self.stackView.insertArrangedSubview(compositionView, at: index)
|
||||
compositionView.textView.becomeFirstResponder()
|
||||
DispatchQueue.main.async {
|
||||
self.scrollView.scrollRectToVisible(
|
||||
self.scrollView.convert(compositionView.frame, from: self.stackView),
|
||||
animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
for removal in diff.removals {
|
||||
guard case let .remove(_, id, _) = removal else { continue }
|
||||
|
||||
self.stackView.arrangedSubviews.first { ($0 as? CompositionView)?.id == id }?.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
viewModel.$identification
|
||||
.sink { [weak self] in
|
||||
|
@ -57,33 +136,6 @@ class NewStatusViewController: UICollectionViewController {
|
|||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
viewModel.$compositionViewModels.sink { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
||||
let oldSnapshot = self.dataSource.snapshot()
|
||||
let newSnapshot = [$0.map(\.id)].snapshot()
|
||||
let diff = newSnapshot.itemIdentifiers.difference(from: oldSnapshot.itemIdentifiers)
|
||||
|
||||
self.dataSource.apply(newSnapshot) {
|
||||
if case let .insert(_, id, _) = diff.insertions.first,
|
||||
let indexPath = self.dataSource.indexPath(for: id) {
|
||||
self.collectionView.selectItem(at: indexPath, animated: false, scrollPosition: .top)
|
||||
}
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
// Invalidate the collection view layout on anything that could change the height of a cell
|
||||
viewModel.$compositionViewModels
|
||||
.flatMap { Publishers.MergeMany($0.map(\.objectWillChange)) }
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] in self?.collectionView.collectionViewLayout.invalidateLayout() }
|
||||
.store(in: &cancellables)
|
||||
|
||||
viewModel.$canPost.sink { [weak self] in self?.postButton.isEnabled = $0 }.store(in: &cancellables)
|
||||
|
||||
viewModel.events.sink { [weak self] in self?.handle(event: $0) }.store(in: &cancellables)
|
||||
|
||||
viewModel.$alertItem
|
||||
.compactMap { $0 }
|
||||
.receive(on: DispatchQueue.main)
|
||||
|
@ -91,46 +143,37 @@ class NewStatusViewController: UICollectionViewController {
|
|||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
override func didMove(toParent parent: UIViewController?) {
|
||||
super.didMove(toParent: parent)
|
||||
|
||||
setupBarButtonItems(identification: viewModel.identification)
|
||||
}
|
||||
|
||||
func setupBarButtonItems(identification: Identification) {
|
||||
let target = isShareExtension ? self : parent
|
||||
let closeButton = UIBarButtonItem(
|
||||
systemItem: .close,
|
||||
primaryAction: UIAction { [weak self] _ in self?.dismiss() })
|
||||
|
||||
target?.navigationItem.leftBarButtonItem = closeButton
|
||||
target?.navigationItem.titleView = viewModel.canChangeIdentity
|
||||
parent?.navigationItem.leftBarButtonItem = closeButton
|
||||
parent?.navigationItem.titleView = viewModel.canChangeIdentity
|
||||
? changeIdentityButton(identification: identification)
|
||||
: nil
|
||||
target?.navigationItem.rightBarButtonItem = postButton
|
||||
parent?.navigationItem.rightBarButtonItem = postButton
|
||||
}
|
||||
|
||||
func dismiss() {
|
||||
if isShareExtension {
|
||||
extensionContext?.completeRequest(returningItems: nil)
|
||||
} else {
|
||||
presentingViewController?.dismiss(animated: true)
|
||||
func presentMediaPicker(compositionViewModel: CompositionViewModel) {
|
||||
mediaSelections.first().sink { [weak self] results in
|
||||
guard let self = self, let result = results.first else { return }
|
||||
|
||||
self.viewModel.attach(itemProvider: result.itemProvider, to: compositionViewModel)
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
var configuration = PHPickerConfiguration()
|
||||
|
||||
configuration.preferredAssetRepresentationMode = .current
|
||||
|
||||
let picker = PHPickerViewController(configuration: configuration)
|
||||
|
||||
picker.modalPresentationStyle = .overFullScreen
|
||||
picker.delegate = self
|
||||
present(picker, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
extension NewStatusViewController: PHPickerViewControllerDelegate {
|
||||
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
|
||||
dismiss(animated: true)
|
||||
|
||||
guard let result = results.first else { return }
|
||||
|
||||
attachMediaTo?.attach(itemProvider: result.itemProvider)
|
||||
attachMediaTo = nil
|
||||
}
|
||||
}
|
||||
|
||||
private extension NewStatusViewController {
|
||||
func changeIdentityButton(identification: Identification) -> UIButton {
|
||||
let changeIdentityButton = UIButton()
|
||||
let downsampled = KingfisherOptionsInfo.downsampled(
|
||||
|
@ -168,22 +211,4 @@ private extension NewStatusViewController {
|
|||
|
||||
return changeIdentityButton
|
||||
}
|
||||
|
||||
func handle(event: CompositionViewModel.Event) {
|
||||
switch event {
|
||||
case let .presentMediaPicker(compositionViewModel):
|
||||
attachMediaTo = compositionViewModel
|
||||
|
||||
var configuration = PHPickerConfiguration()
|
||||
|
||||
configuration.preferredAssetRepresentationMode = .current
|
||||
|
||||
let picker = PHPickerViewController(configuration: configuration)
|
||||
|
||||
picker.delegate = self
|
||||
present(picker, animated: true)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -90,8 +90,4 @@ public extension DomainBlocksViewModel {
|
|||
static let preview = DomainBlocksViewModel(service: .init(mastodonAPIClient: .preview))
|
||||
}
|
||||
|
||||
public extension NewStatusViewModel {
|
||||
static let preview = RootViewModel.preview.newStatusViewModel(identification: .preview)
|
||||
}
|
||||
|
||||
// swiftlint:enable force_try
|
||||
|
|
|
@ -5,25 +5,26 @@ import Foundation
|
|||
import Mastodon
|
||||
import ServiceLayer
|
||||
|
||||
public final class CompositionViewModel: ObservableObject {
|
||||
public final class CompositionViewModel: ObservableObject, Identifiable {
|
||||
public let id = Id()
|
||||
public var isPosted = false
|
||||
@Published public var text = ""
|
||||
@Published public var contentWarning = ""
|
||||
@Published public var displayContentWarning = false
|
||||
@Published public private(set) var attachmentViewModels = [CompositionAttachmentViewModel]()
|
||||
@Published public private(set) var isPostable = false
|
||||
@Published public private(set) var identification: Identification
|
||||
@Published public private(set) var attachmentUpload: AttachmentUpload?
|
||||
|
||||
private let eventsSubject: PassthroughSubject<Event, Never>
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
||||
init(identification: Identification,
|
||||
identificationPublisher: AnyPublisher<Identification, Never>,
|
||||
eventsSubject: PassthroughSubject<Event, Never>) {
|
||||
self.identification = identification
|
||||
self.eventsSubject = eventsSubject
|
||||
identificationPublisher.assign(to: &$identification)
|
||||
$text.map { !$0.isEmpty }.removeDuplicates().assign(to: &$isPostable)
|
||||
init() {
|
||||
$text.map { !$0.isEmpty }
|
||||
.removeDuplicates()
|
||||
.combineLatest($attachmentViewModels.map { !$0.isEmpty })
|
||||
.map { textPresent, attachmentPresent in
|
||||
textPresent || attachmentPresent
|
||||
}
|
||||
.assign(to: &$isPostable)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -36,49 +37,41 @@ public extension CompositionViewModel {
|
|||
case error(Error)
|
||||
}
|
||||
|
||||
func components(inReplyToId: Status.Id?) -> StatusComponents {
|
||||
func components(inReplyToId: Status.Id?, visibility: Status.Visibility) -> StatusComponents {
|
||||
StatusComponents(
|
||||
inReplyToId: inReplyToId,
|
||||
text: text,
|
||||
mediaIds: attachmentViewModels.map(\.attachment.id))
|
||||
}
|
||||
|
||||
func presentMediaPicker() {
|
||||
eventsSubject.send(.presentMediaPicker(self))
|
||||
}
|
||||
|
||||
func insert() {
|
||||
eventsSubject.send(.insertAfter(self))
|
||||
}
|
||||
|
||||
func attach(itemProvider: NSItemProvider) {
|
||||
let progress = Progress(totalUnitCount: 1)
|
||||
|
||||
MediaProcessingService.dataAndMimeType(itemProvider: itemProvider)
|
||||
.flatMap { [weak self] data, mimeType -> AnyPublisher<Attachment, Error> in
|
||||
guard let self = self else { return Empty().eraseToAnyPublisher() }
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.attachmentUpload = AttachmentUpload(progress: progress, data: data, mimeType: mimeType)
|
||||
}
|
||||
|
||||
return self.identification.service.uploadAttachment(data: data, mimeType: mimeType, progress: progress)
|
||||
}
|
||||
.print()
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] in
|
||||
self?.attachmentUpload = nil
|
||||
|
||||
if case let .failure(error) = $0 {
|
||||
self?.eventsSubject.send(.error(error))
|
||||
}
|
||||
} receiveValue: { [weak self] in
|
||||
self?.attachmentViewModels.append(CompositionAttachmentViewModel(attachment: $0))
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
spoilerText: displayContentWarning ? contentWarning : "",
|
||||
mediaIds: attachmentViewModels.map(\.attachment.id),
|
||||
visibility: visibility)
|
||||
}
|
||||
|
||||
func attachmentViewModel(indexPath: IndexPath) -> CompositionAttachmentViewModel {
|
||||
attachmentViewModels[indexPath.item]
|
||||
}
|
||||
}
|
||||
|
||||
extension CompositionViewModel {
|
||||
func attach(itemProvider: NSItemProvider, service: IdentityService) -> AnyPublisher<Never, Error> {
|
||||
MediaProcessingService.dataAndMimeType(itemProvider: itemProvider)
|
||||
.flatMap { [weak self] data, mimeType -> AnyPublisher<Attachment, Error> in
|
||||
guard let self = self else { return Empty().eraseToAnyPublisher() }
|
||||
|
||||
let progress = Progress(totalUnitCount: 1)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.attachmentUpload = AttachmentUpload(progress: progress, data: data, mimeType: mimeType)
|
||||
}
|
||||
|
||||
return service.uploadAttachment(data: data, mimeType: mimeType, progress: progress)
|
||||
}
|
||||
.receive(on: DispatchQueue.main)
|
||||
.handleEvents(
|
||||
receiveOutput: { [weak self] in
|
||||
self?.attachmentViewModels.append(CompositionAttachmentViewModel(attachment: $0))
|
||||
},
|
||||
receiveCompletion: { [weak self] _ in self?.attachmentUpload = nil })
|
||||
.ignoreOutput()
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,18 +6,19 @@ import Mastodon
|
|||
import ServiceLayer
|
||||
|
||||
public final class NewStatusViewModel: ObservableObject {
|
||||
@Published public private(set) var compositionViewModels = [CompositionViewModel]()
|
||||
@Published public var visibility: Status.Visibility
|
||||
@Published public private(set) var compositionViewModels = [CompositionViewModel()]
|
||||
@Published public private(set) var identification: Identification
|
||||
@Published public private(set) var authenticatedIdentities = [Identity]()
|
||||
@Published public var canPost = false
|
||||
@Published public var canChangeIdentity = true
|
||||
@Published public var alertItem: AlertItem?
|
||||
@Published public private(set) var loading = false
|
||||
public let events: AnyPublisher<CompositionViewModel.Event, Never>
|
||||
public let events: AnyPublisher<Event, Never>
|
||||
|
||||
private let allIdentitiesService: AllIdentitiesService
|
||||
private let environment: AppEnvironment
|
||||
private let eventsSubject = PassthroughSubject<CompositionViewModel.Event, Never>()
|
||||
private let eventsSubject = PassthroughSubject<Event, Never>()
|
||||
private let itemEventsSubject = PassthroughSubject<CompositionViewModel.Event, Never>()
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
||||
|
@ -28,8 +29,7 @@ public final class NewStatusViewModel: ObservableObject {
|
|||
self.identification = identification
|
||||
self.environment = environment
|
||||
events = eventsSubject.eraseToAnyPublisher()
|
||||
compositionViewModels = [newCompositionViewModel()]
|
||||
itemEventsSubject.sink { [weak self] in self?.handle(event: $0) }.store(in: &cancellables)
|
||||
visibility = identification.identity.preferences.postingDefaultVisibility
|
||||
allIdentitiesService.authenticatedIdentitiesPublisher()
|
||||
.assignErrorsToAlertItem(to: \.alertItem, on: self)
|
||||
.assign(to: &$authenticatedIdentities)
|
||||
|
@ -43,8 +43,8 @@ public final class NewStatusViewModel: ObservableObject {
|
|||
}
|
||||
|
||||
public extension NewStatusViewModel {
|
||||
func viewModel(indexPath: IndexPath) -> CompositionViewModel {
|
||||
compositionViewModels[indexPath.row]
|
||||
enum Event {
|
||||
case presentMediaPicker(CompositionViewModel)
|
||||
}
|
||||
|
||||
func setIdentity(_ identity: Identity) {
|
||||
|
@ -66,6 +66,34 @@ public extension NewStatusViewModel {
|
|||
environment: environment)
|
||||
}
|
||||
|
||||
func presentMediaPicker(viewModel: CompositionViewModel) {
|
||||
eventsSubject.send(.presentMediaPicker(viewModel))
|
||||
}
|
||||
|
||||
func insert(after: CompositionViewModel) {
|
||||
guard let index = compositionViewModels.firstIndex(where: { $0 === after })
|
||||
else { return }
|
||||
|
||||
let newViewModel = CompositionViewModel()
|
||||
|
||||
newViewModel.contentWarning = after.contentWarning
|
||||
newViewModel.displayContentWarning = after.displayContentWarning
|
||||
|
||||
if index >= compositionViewModels.count - 1 {
|
||||
compositionViewModels.append(newViewModel)
|
||||
} else {
|
||||
compositionViewModels.insert(newViewModel, at: index + 1)
|
||||
}
|
||||
}
|
||||
|
||||
func attach(itemProvider: NSItemProvider, to compositionViewModel: CompositionViewModel) {
|
||||
compositionViewModel.attach(itemProvider: itemProvider, service: identification.service)
|
||||
.receive(on: DispatchQueue.main)
|
||||
.assignErrorsToAlertItem(to: \.alertItem, on: self)
|
||||
.sink { _ in }
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
func post() {
|
||||
guard let unposted = compositionViewModels.first(where: { !$0.isPosted }) else { return }
|
||||
|
||||
|
@ -74,35 +102,11 @@ public extension NewStatusViewModel {
|
|||
}
|
||||
|
||||
private extension NewStatusViewModel {
|
||||
func newCompositionViewModel() -> CompositionViewModel {
|
||||
CompositionViewModel(
|
||||
identification: identification,
|
||||
identificationPublisher: $identification.eraseToAnyPublisher(),
|
||||
eventsSubject: itemEventsSubject)
|
||||
}
|
||||
|
||||
func handle(event: CompositionViewModel.Event) {
|
||||
switch event {
|
||||
case let .insertAfter(viewModel):
|
||||
guard let index = compositionViewModels.firstIndex(where: { $0 === viewModel }) else { return }
|
||||
|
||||
let newViewModel = newCompositionViewModel()
|
||||
|
||||
if index >= compositionViewModels.count - 1 {
|
||||
compositionViewModels.append(newViewModel)
|
||||
} else {
|
||||
compositionViewModels.insert(newViewModel, at: index + 1)
|
||||
}
|
||||
case let .error(error):
|
||||
alertItem = AlertItem(error: error)
|
||||
default:
|
||||
eventsSubject.send(event)
|
||||
}
|
||||
}
|
||||
|
||||
func post(viewModel: CompositionViewModel, inReplyToId: Status.Id?) {
|
||||
loading = true
|
||||
identification.service.post(statusComponents: viewModel.components(inReplyToId: inReplyToId))
|
||||
identification.service.post(statusComponents: viewModel.components(
|
||||
inReplyToId: inReplyToId,
|
||||
visibility: visibility))
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
// Copyright © 2020 Metabolist. All rights reserved.
|
||||
|
||||
import UIKit
|
||||
import ViewModels
|
||||
|
||||
struct CompositionContentConfiguration {
|
||||
let viewModel: CompositionViewModel
|
||||
}
|
||||
|
||||
extension CompositionContentConfiguration: UIContentConfiguration {
|
||||
func makeContentView() -> UIView & UIContentView {
|
||||
CompositionView(configuration: self)
|
||||
}
|
||||
|
||||
func updated(for state: UIConfigurationState) -> CompositionContentConfiguration {
|
||||
self
|
||||
}
|
||||
}
|
|
@ -1,16 +1,23 @@
|
|||
// Copyright © 2020 Metabolist. All rights reserved.
|
||||
|
||||
import Combine
|
||||
import Mastodon
|
||||
import UIKit
|
||||
import ViewModels
|
||||
|
||||
class CompositionInputAccessoryView: UIView {
|
||||
private let stackView = UIStackView()
|
||||
final class CompositionInputAccessoryView: UIView {
|
||||
let visibilityButton = UIButton()
|
||||
let addButton = UIButton()
|
||||
let contentWarningButton = UIButton(type: .system)
|
||||
|
||||
private let viewModel: CompositionViewModel
|
||||
private let parentViewModel: NewStatusViewModel
|
||||
private let stackView = UIStackView()
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
||||
init(viewModel: CompositionViewModel) {
|
||||
init(viewModel: CompositionViewModel, parentViewModel: NewStatusViewModel) {
|
||||
self.viewModel = viewModel
|
||||
self.parentViewModel = parentViewModel
|
||||
|
||||
super.init(frame: .zero)
|
||||
|
||||
|
@ -28,6 +35,7 @@ class CompositionInputAccessoryView: UIView {
|
|||
}
|
||||
|
||||
private extension CompositionInputAccessoryView {
|
||||
// swiftlint:disable:next function_body_length
|
||||
func initialSetup() {
|
||||
autoresizingMask = .flexibleHeight
|
||||
backgroundColor = .secondarySystemFill
|
||||
|
@ -44,7 +52,12 @@ private extension CompositionInputAccessoryView {
|
|||
systemName: "photo",
|
||||
withConfiguration: UIImage.SymbolConfiguration(scale: .medium)),
|
||||
for: .normal)
|
||||
mediaButton.addAction(UIAction { [weak self] _ in self?.viewModel.presentMediaPicker() }, for: .touchUpInside)
|
||||
mediaButton.addAction(UIAction { [weak self] _ in
|
||||
guard let self = self else { return }
|
||||
|
||||
self.parentViewModel.presentMediaPicker(viewModel: self.viewModel)
|
||||
},
|
||||
for: .touchUpInside)
|
||||
|
||||
let pollButton = UIButton()
|
||||
|
||||
|
@ -55,9 +68,26 @@ private extension CompositionInputAccessoryView {
|
|||
withConfiguration: UIImage.SymbolConfiguration(scale: .medium)),
|
||||
for: .normal)
|
||||
|
||||
stackView.addArrangedSubview(UIView())
|
||||
stackView.addArrangedSubview(visibilityButton)
|
||||
visibilityButton.showsMenuAsPrimaryAction = true
|
||||
visibilityButton.menu = UIMenu(children: Status.Visibility.allCasesExceptUnknown.reversed().map { visibility in
|
||||
UIAction(
|
||||
title: visibility.title ?? "",
|
||||
image: UIImage(systemName: visibility.systemImageName),
|
||||
discoverabilityTitle: visibility.description) { [weak self] _ in
|
||||
self?.parentViewModel.visibility = visibility
|
||||
}
|
||||
})
|
||||
|
||||
let addButton = UIButton()
|
||||
stackView.addArrangedSubview(contentWarningButton)
|
||||
contentWarningButton.setTitle(
|
||||
NSLocalizedString("status.content-warning-abbreviation", comment: ""),
|
||||
for: .normal)
|
||||
contentWarningButton.addAction(
|
||||
UIAction { [weak self] _ in self?.viewModel.displayContentWarning.toggle() },
|
||||
for: .touchUpInside)
|
||||
|
||||
stackView.addArrangedSubview(UIView())
|
||||
|
||||
stackView.addArrangedSubview(addButton)
|
||||
addButton.setImage(
|
||||
|
@ -65,20 +95,32 @@ private extension CompositionInputAccessoryView {
|
|||
systemName: "plus.circle.fill",
|
||||
withConfiguration: UIImage.SymbolConfiguration(scale: .medium)),
|
||||
for: .normal)
|
||||
addButton.addAction(UIAction { [weak self] _ in self?.viewModel.insert() }, for: .touchUpInside)
|
||||
addButton.addAction(UIAction { [weak self] _ in
|
||||
guard let self = self else { return }
|
||||
|
||||
for button in [mediaButton, pollButton, addButton] {
|
||||
self.parentViewModel.insert(after: self.viewModel)
|
||||
}, for: .touchUpInside)
|
||||
|
||||
viewModel.$isPostable
|
||||
.sink { [weak self] in self?.addButton.isEnabled = $0 }
|
||||
.store(in: &cancellables)
|
||||
|
||||
parentViewModel.$visibility
|
||||
.sink { [weak self] in
|
||||
self?.visibilityButton.setImage(UIImage(systemName: $0.systemImageName), for: .normal)
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
for button in [mediaButton, pollButton, visibilityButton, contentWarningButton, addButton] {
|
||||
button.heightAnchor.constraint(greaterThanOrEqualToConstant: .minimumButtonDimension).isActive = true
|
||||
button.widthAnchor.constraint(greaterThanOrEqualToConstant: .minimumButtonDimension).isActive = true
|
||||
}
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
stackView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||
stackView.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor),
|
||||
stackView.topAnchor.constraint(equalTo: topAnchor),
|
||||
stackView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||
stackView.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor),
|
||||
stackView.bottomAnchor.constraint(equalTo: bottomAnchor)
|
||||
])
|
||||
|
||||
viewModel.$isPostable.sink { addButton.isEnabled = $0 }.store(in: &cancellables)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
// Copyright © 2020 Metabolist. All rights reserved.
|
||||
|
||||
import UIKit
|
||||
import ViewModels
|
||||
|
||||
class CompositionListCell: UICollectionViewListCell {
|
||||
var viewModel: CompositionViewModel?
|
||||
|
||||
override func updateConfiguration(using state: UICellConfigurationState) {
|
||||
guard let viewModel = viewModel else { return }
|
||||
|
||||
contentConfiguration = CompositionContentConfiguration(viewModel: viewModel).updated(for: state)
|
||||
backgroundConfiguration = UIBackgroundConfiguration.clear().updated(for: state)
|
||||
}
|
||||
|
||||
override var isSelected: Bool {
|
||||
didSet {
|
||||
if isSelected {
|
||||
(contentView as? CompositionView)?.textView.becomeFirstResponder()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func updateConstraints() {
|
||||
super.updateConstraints()
|
||||
|
||||
separatorLayoutGuide.trailingAnchor.constraint(equalTo: separatorLayoutGuide.leadingAnchor).isActive = true
|
||||
}
|
||||
}
|
|
@ -3,24 +3,29 @@
|
|||
import Combine
|
||||
import Kingfisher
|
||||
import UIKit
|
||||
import ViewModels
|
||||
|
||||
final class CompositionView: UIView {
|
||||
let avatarImageView = UIImageView()
|
||||
let spoilerTextField = UITextField()
|
||||
let textView = UITextView()
|
||||
let attachmentUploadView = AttachmentUploadView()
|
||||
let attachmentsCollectionView: UICollectionView
|
||||
|
||||
private var compositionConfiguration: CompositionContentConfiguration
|
||||
private let viewModel: CompositionViewModel
|
||||
private let parentViewModel: NewStatusViewModel
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
||||
private lazy var attachmentsDataSource: CompositionAttachmentsDataSource = {
|
||||
CompositionAttachmentsDataSource(
|
||||
collectionView: attachmentsCollectionView,
|
||||
viewModelProvider: compositionConfiguration.viewModel.attachmentViewModel(indexPath:))
|
||||
collectionView: attachmentsCollectionView) { [weak self] in
|
||||
self?.viewModel.attachmentViewModel(indexPath: $0)
|
||||
}
|
||||
}()
|
||||
|
||||
init(configuration: CompositionContentConfiguration) {
|
||||
self.compositionConfiguration = configuration
|
||||
init(viewModel: CompositionViewModel, parentViewModel: NewStatusViewModel) {
|
||||
self.viewModel = viewModel
|
||||
self.parentViewModel = parentViewModel
|
||||
|
||||
let itemSize = NSCollectionLayoutSize(
|
||||
widthDimension: .fractionalWidth(0.2),
|
||||
|
@ -42,7 +47,6 @@ final class CompositionView: UIView {
|
|||
super.init(frame: .zero)
|
||||
|
||||
initialSetup()
|
||||
applyCompositionConfiguration()
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
|
@ -51,93 +55,124 @@ final class CompositionView: UIView {
|
|||
}
|
||||
}
|
||||
|
||||
extension CompositionView: UIContentView {
|
||||
var configuration: UIContentConfiguration {
|
||||
get { compositionConfiguration }
|
||||
set {
|
||||
guard let compositionConfiguration = newValue as? CompositionContentConfiguration else { return }
|
||||
|
||||
self.compositionConfiguration = compositionConfiguration
|
||||
|
||||
applyCompositionConfiguration()
|
||||
}
|
||||
}
|
||||
extension CompositionView {
|
||||
var id: CompositionViewModel.Id { viewModel.id }
|
||||
}
|
||||
|
||||
extension CompositionView: UITextViewDelegate {
|
||||
func textViewDidChange(_ textView: UITextView) {
|
||||
compositionConfiguration.viewModel.text = textView.text
|
||||
viewModel.text = textView.text
|
||||
}
|
||||
}
|
||||
|
||||
private extension CompositionView {
|
||||
static let attachmentUploadViewHeight: CGFloat = 100
|
||||
|
||||
// swiftlint:disable:next function_body_length
|
||||
func initialSetup() {
|
||||
tag = viewModel.id.hashValue
|
||||
|
||||
addSubview(avatarImageView)
|
||||
avatarImageView.translatesAutoresizingMaskIntoConstraints = false
|
||||
avatarImageView.layer.cornerRadius = .avatarDimension / 2
|
||||
avatarImageView.clipsToBounds = true
|
||||
|
||||
let stackView = UIStackView()
|
||||
let inputAccessoryView = CompositionInputAccessoryView(viewModel: viewModel, parentViewModel: parentViewModel)
|
||||
|
||||
addSubview(stackView)
|
||||
stackView.translatesAutoresizingMaskIntoConstraints = false
|
||||
stackView.axis = .vertical
|
||||
stackView.spacing = .defaultSpacing
|
||||
|
||||
stackView.addArrangedSubview(spoilerTextField)
|
||||
spoilerTextField.backgroundColor = .secondarySystemBackground
|
||||
spoilerTextField.layer.cornerRadius = .defaultCornerRadius
|
||||
spoilerTextField.adjustsFontForContentSizeCategory = true
|
||||
spoilerTextField.font = .preferredFont(forTextStyle: .body)
|
||||
spoilerTextField.placeholder = NSLocalizedString("status.spoiler-text-placeholder", comment: "")
|
||||
spoilerTextField.inputAccessoryView = inputAccessoryView
|
||||
spoilerTextField.addAction(
|
||||
UIAction { [weak self] _ in
|
||||
guard let self = self, let text = self.spoilerTextField.text else { return }
|
||||
|
||||
self.viewModel.contentWarning = text
|
||||
},
|
||||
for: .editingChanged)
|
||||
|
||||
stackView.addArrangedSubview(textView)
|
||||
textView.backgroundColor = .secondarySystemBackground
|
||||
textView.layer.cornerRadius = .defaultCornerRadius
|
||||
textView.isScrollEnabled = false
|
||||
textView.adjustsFontForContentSizeCategory = true
|
||||
textView.font = .preferredFont(forTextStyle: .body)
|
||||
textView.textContainer.lineFragmentPadding = 0
|
||||
textView.inputAccessoryView = CompositionInputAccessoryView(viewModel: compositionConfiguration.viewModel)
|
||||
// textView.textContainer.lineFragmentPadding = 0
|
||||
textView.inputAccessoryView = inputAccessoryView
|
||||
textView.inputAccessoryView?.sizeToFit()
|
||||
textView.delegate = self
|
||||
textView.setContentHuggingPriority(.required, for: .vertical)
|
||||
|
||||
stackView.addArrangedSubview(attachmentsCollectionView)
|
||||
attachmentsCollectionView.dataSource = attachmentsDataSource
|
||||
|
||||
stackView.addArrangedSubview(attachmentUploadView)
|
||||
|
||||
let constraints = [
|
||||
avatarImageView.heightAnchor.constraint(equalToConstant: .avatarDimension),
|
||||
avatarImageView.widthAnchor.constraint(equalToConstant: .avatarDimension),
|
||||
avatarImageView.topAnchor.constraint(equalTo: readableContentGuide.topAnchor),
|
||||
avatarImageView.leadingAnchor.constraint(equalTo: readableContentGuide.leadingAnchor),
|
||||
avatarImageView.bottomAnchor.constraint(lessThanOrEqualTo: readableContentGuide.bottomAnchor),
|
||||
stackView.leadingAnchor.constraint(equalTo: avatarImageView.trailingAnchor, constant: .defaultSpacing),
|
||||
stackView.topAnchor.constraint(equalTo: readableContentGuide.topAnchor),
|
||||
stackView.trailingAnchor.constraint(equalTo: readableContentGuide.trailingAnchor),
|
||||
stackView.bottomAnchor.constraint(equalTo: readableContentGuide.bottomAnchor),
|
||||
attachmentsCollectionView.heightAnchor.constraint(
|
||||
equalTo: attachmentsCollectionView.widthAnchor,
|
||||
multiplier: 1 / 4),
|
||||
attachmentUploadView.heightAnchor.constraint(equalToConstant: Self.attachmentUploadViewHeight)
|
||||
]
|
||||
textView.text = viewModel.text
|
||||
spoilerTextField.text = viewModel.contentWarning
|
||||
|
||||
for constraint in constraints {
|
||||
constraint.priority = .justBelowMax
|
||||
viewModel.$displayContentWarning
|
||||
.sink { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
||||
if self.spoilerTextField.isHidden && self.textView.isFirstResponder && $0 {
|
||||
self.spoilerTextField.becomeFirstResponder()
|
||||
} else if !self.spoilerTextField.isHidden && self.spoilerTextField.isFirstResponder && !$0 {
|
||||
self.textView.becomeFirstResponder()
|
||||
}
|
||||
|
||||
NSLayoutConstraint.activate(constraints)
|
||||
self.spoilerTextField.isHidden = !$0
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
func applyCompositionConfiguration() {
|
||||
cancellables.removeAll()
|
||||
|
||||
compositionConfiguration.viewModel.$identification.map(\.identity.image)
|
||||
parentViewModel.$identification.map(\.identity.image)
|
||||
.sink { [weak self] in self?.avatarImageView.kf.setImage(with: $0) }
|
||||
.store(in: &cancellables)
|
||||
|
||||
compositionConfiguration.viewModel.$attachmentViewModels
|
||||
viewModel.$attachmentViewModels
|
||||
.receive(on: DispatchQueue.main) // hack to punt to next run loop, consider refactoring
|
||||
.sink { [weak self] in
|
||||
self?.attachmentsDataSource.apply([$0.map(\.attachment)].snapshot())
|
||||
self?.attachmentsCollectionView.isHidden = $0.isEmpty
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
compositionConfiguration.viewModel.$attachmentUpload
|
||||
viewModel.$attachmentUpload
|
||||
.sink { [weak self] in self?.attachmentUploadView.attachmentUpload = $0 }
|
||||
.store(in: &cancellables)
|
||||
|
||||
let guide = UIDevice.current.userInterfaceIdiom == .pad ? readableContentGuide : layoutMarginsGuide
|
||||
let constraints = [
|
||||
avatarImageView.heightAnchor.constraint(equalToConstant: .avatarDimension),
|
||||
avatarImageView.widthAnchor.constraint(equalToConstant: .avatarDimension),
|
||||
avatarImageView.topAnchor.constraint(equalTo: guide.topAnchor),
|
||||
avatarImageView.leadingAnchor.constraint(equalTo: guide.leadingAnchor),
|
||||
avatarImageView.bottomAnchor.constraint(lessThanOrEqualTo: guide.bottomAnchor),
|
||||
stackView.leadingAnchor.constraint(equalTo: avatarImageView.trailingAnchor, constant: .defaultSpacing),
|
||||
stackView.topAnchor.constraint(equalTo: guide.topAnchor),
|
||||
stackView.trailingAnchor.constraint(equalTo: guide.trailingAnchor),
|
||||
stackView.bottomAnchor.constraint(lessThanOrEqualTo: guide.bottomAnchor),
|
||||
attachmentsCollectionView.heightAnchor.constraint(
|
||||
equalTo: attachmentsCollectionView.widthAnchor,
|
||||
multiplier: 1 / 4),
|
||||
attachmentUploadView.heightAnchor.constraint(equalToConstant: Self.attachmentUploadViewHeight)
|
||||
]
|
||||
|
||||
if UIDevice.current.userInterfaceIdiom == .pad {
|
||||
for constraint in constraints {
|
||||
constraint.priority = .justBelowMax
|
||||
}
|
||||
}
|
||||
|
||||
NSLayoutConstraint.activate(constraints)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,16 +7,10 @@ struct NewStatusView: UIViewControllerRepresentable {
|
|||
let viewModelClosure: () -> NewStatusViewModel
|
||||
|
||||
func makeUIViewController(context: Context) -> NewStatusViewController {
|
||||
NewStatusViewController(viewModel: viewModelClosure(), isShareExtension: false)
|
||||
NewStatusViewController(viewModel: viewModelClosure())
|
||||
}
|
||||
|
||||
func updateUIViewController(_ uiViewController: NewStatusViewController, context: Context) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
struct NewStatusView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
NewStatusView { .preview }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,10 +41,7 @@ struct TabNavigationView: View {
|
|||
EmptyView()
|
||||
.fullScreenCover(isPresented: $viewModel.presentingNewStatus) {
|
||||
NavigationView {
|
||||
NewStatusView {
|
||||
rootViewModel.newStatusViewModel(identification: viewModel.identification)
|
||||
}
|
||||
.edgesIgnoringSafeArea(.all)
|
||||
NewStatusView { rootViewModel.newStatusViewModel(identification: viewModel.identification) }
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
}
|
||||
.navigationViewStyle(StackNavigationViewStyle())
|
||||
|
|
Loading…
Reference in a new issue