mirror of
https://github.com/metabolist/metatext.git
synced 2024-11-25 01:31:02 +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 =
|
private let updateQueue =
|
||||||
DispatchQueue(label: "com.metabolist.metatext.composition-attachments-data-source.update-queue")
|
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
|
let registration = UICollectionView.CellRegistration
|
||||||
<CompositionAttachmentCollectionViewCell, CompositionAttachmentViewModel> {
|
<CompositionAttachmentCollectionViewCell, CompositionAttachmentViewModel> {
|
||||||
$0.viewModel = $2
|
$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 %@";
|
"report.forward-%@" = "Forward report to %@";
|
||||||
"share-extension-error.no-account-found" = "No account found";
|
"share-extension-error.no-account-found" = "No account found";
|
||||||
"status.bookmark" = "Bookmark";
|
"status.bookmark" = "Bookmark";
|
||||||
|
"status.content-warning-abbreviation" = "CW";
|
||||||
"status.reblogged-by" = "%@ boosted";
|
"status.reblogged-by" = "%@ boosted";
|
||||||
"status.pinned-post" = "Pinned post";
|
"status.pinned-post" = "Pinned post";
|
||||||
"status.show-more" = "Show More";
|
"status.show-more" = "Show More";
|
||||||
|
@ -146,10 +147,16 @@
|
||||||
"status.poll.time-left" = "%@ left";
|
"status.poll.time-left" = "%@ left";
|
||||||
"status.poll.refresh" = "Refresh";
|
"status.poll.refresh" = "Refresh";
|
||||||
"status.poll.closed" = "Closed";
|
"status.poll.closed" = "Closed";
|
||||||
|
"status.spoiler-text-placeholder" = "Write your warning here";
|
||||||
"status.unbookmark" = "Unbookmark";
|
"status.unbookmark" = "Unbookmark";
|
||||||
"status.visibility.public" = "Public";
|
"status.visibility.public" = "Public";
|
||||||
"status.visibility.unlisted" = "Unlisted";
|
"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";
|
"submit" = "Submit";
|
||||||
"timelines.home" = "Home";
|
"timelines.home" = "Home";
|
||||||
"timelines.local" = "Local";
|
"timelines.local" = "Local";
|
||||||
|
|
|
@ -17,12 +17,21 @@ public extension StatusEndpoint {
|
||||||
struct Components {
|
struct Components {
|
||||||
public let inReplyToId: Status.Id?
|
public let inReplyToId: Status.Id?
|
||||||
public let text: String
|
public let text: String
|
||||||
|
public let spoilerText: String
|
||||||
public let mediaIds: [Attachment.Id]
|
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.inReplyToId = inReplyToId
|
||||||
self.text = text
|
self.text = text
|
||||||
|
self.spoilerText = spoilerText
|
||||||
self.mediaIds = mediaIds
|
self.mediaIds = mediaIds
|
||||||
|
self.visibility = visibility
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,11 +44,16 @@ extension StatusEndpoint.Components {
|
||||||
params["status"] = text
|
params["status"] = text
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !spoilerText.isEmpty {
|
||||||
|
params["spoiler_text"] = spoilerText
|
||||||
|
}
|
||||||
|
|
||||||
if !mediaIds.isEmpty {
|
if !mediaIds.isEmpty {
|
||||||
params["media_ids"] = mediaIds
|
params["media_ids"] = mediaIds
|
||||||
}
|
}
|
||||||
|
|
||||||
params["in_reply_to_id"] = inReplyToId
|
params["in_reply_to_id"] = inReplyToId
|
||||||
|
params["visibility"] = visibility.rawValue
|
||||||
|
|
||||||
return params
|
return params
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,12 +19,15 @@
|
||||||
D01F41D924F880C400D55A2D /* TouchFallthroughTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01F41D624F880C400D55A2D /* TouchFallthroughTextView.swift */; };
|
D01F41D924F880C400D55A2D /* TouchFallthroughTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01F41D624F880C400D55A2D /* TouchFallthroughTextView.swift */; };
|
||||||
D01F41E424F8889700D55A2D /* StatusAttachmentsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01F41E224F8889700D55A2D /* StatusAttachmentsView.swift */; };
|
D01F41E424F8889700D55A2D /* StatusAttachmentsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01F41E224F8889700D55A2D /* StatusAttachmentsView.swift */; };
|
||||||
D02E1F95250B13210071AD56 /* SafariView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02E1F94250B13210071AD56 /* SafariView.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 */; };
|
D036AA02254B6101009094DF /* NotificationListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D036AA01254B6101009094DF /* NotificationListCell.swift */; };
|
||||||
D036AA07254B6118009094DF /* NotificationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D036AA06254B6118009094DF /* NotificationView.swift */; };
|
D036AA07254B6118009094DF /* NotificationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D036AA06254B6118009094DF /* NotificationView.swift */; };
|
||||||
D036AA0C254B612B009094DF /* NotificationContentConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D036AA0B254B612B009094DF /* NotificationContentConfiguration.swift */; };
|
D036AA0C254B612B009094DF /* NotificationContentConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D036AA0B254B612B009094DF /* NotificationContentConfiguration.swift */; };
|
||||||
D036AA17254CA824009094DF /* StatusBodyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D036AA16254CA823009094DF /* StatusBodyView.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 */; };
|
D03B1B2A253818F3008F964B /* MediaPreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B1B29253818F3008F964B /* MediaPreferencesView.swift */; };
|
||||||
D04226FD2546AC0B000980A3 /* StartupAndSyncingPreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04226FC2546AC0B000980A3 /* StartupAndSyncingPreferencesView.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 */; };
|
D0625E59250F092900502611 /* StatusListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0625E58250F092900502611 /* StatusListCell.swift */; };
|
||||||
D0625E5D250F0B5C00502611 /* StatusContentConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0625E5C250F0B5C00502611 /* StatusContentConfiguration.swift */; };
|
D0625E5D250F0B5C00502611 /* StatusContentConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0625E5C250F0B5C00502611 /* StatusContentConfiguration.swift */; };
|
||||||
D065965B25899DAE0096AC5D /* CompositionAttachmentsDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D065965A25899DAE0096AC5D /* CompositionAttachmentsDataSource.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 */; };
|
D0804134258D902900AD6139 /* CompositionAttachmentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0804132258D902900AD6139 /* CompositionAttachmentView.swift */; };
|
||||||
D080413E258D904400AD6139 /* CompositionAttachmentContentConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D080413D258D904400AD6139 /* CompositionAttachmentContentConfiguration.swift */; };
|
D080413E258D904400AD6139 /* CompositionAttachmentContentConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D080413D258D904400AD6139 /* CompositionAttachmentContentConfiguration.swift */; };
|
||||||
D080413F258D904400AD6139 /* 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 */; };
|
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 */; };
|
||||||
|
@ -50,18 +54,10 @@
|
||||||
D08E512125786A6600FA2C5F /* UIButton+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E512025786A6600FA2C5F /* UIButton+Extensions.swift */; };
|
D08E512125786A6600FA2C5F /* UIButton+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E512025786A6600FA2C5F /* UIButton+Extensions.swift */; };
|
||||||
D08E52612579D2E100FA2C5F /* DomainBlocksView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E52602579D2E100FA2C5F /* DomainBlocksView.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, ); }; };
|
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 */; };
|
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 */; };
|
D08E52C7257C7AEE00FA2C5F /* ShareErrorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E52C6257C7AEE00FA2C5F /* ShareErrorViewController.swift */; };
|
||||||
D08E52CC257C80E300FA2C5F /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = D0C7D45724F76169001EBDBB /* Localizable.strings */; };
|
D08E52CC257C80E300FA2C5F /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = D0C7D45724F76169001EBDBB /* Localizable.strings */; };
|
||||||
D08E52D2257C811200FA2C5F /* ShareExtensionError+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E52D1257C811200FA2C5F /* ShareExtensionError+Extensions.swift */; };
|
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 */; };
|
D08E52EE257D757100FA2C5F /* CompositionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E52ED257D757100FA2C5F /* CompositionView.swift */; };
|
||||||
D08E52EF257D757100FA2C5F /* 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 */; };
|
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 */; };
|
D0F0B126251A90F400942152 /* AccountListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F0B125251A90F400942152 /* AccountListCell.swift */; };
|
||||||
D0F0B12E251A97E400942152 /* TableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F0B12D251A97E400942152 /* TableViewController.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 */; };
|
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 */; };
|
D0F2D4DB257F018300986197 /* Array+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01C6FAB252024BD003D0300 /* Array+Extensions.swift */; };
|
||||||
D0F2D54025818C4B00986197 /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = D0F2D53F25818C4B00986197 /* Kingfisher */; };
|
D0F2D54025818C4B00986197 /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = D0F2D53F25818C4B00986197 /* Kingfisher */; };
|
||||||
D0F2D5452581ABAB00986197 /* KingfisherOptionsInfo+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D46E24F76169001EBDBB /* KingfisherOptionsInfo+Extensions.swift */; };
|
D0F2D5452581ABAB00986197 /* KingfisherOptionsInfo+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D46E24F76169001EBDBB /* KingfisherOptionsInfo+Extensions.swift */; };
|
||||||
D0F2D54B2581CF7D00986197 /* VisualEffectBlur.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F2D54A2581CF7D00986197 /* VisualEffectBlur.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 */; };
|
D0FE1C8F253686F9003EF1EB /* PlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FE1C8E253686F9003EF1EB /* PlayerView.swift */; };
|
||||||
D0FE1C9825368A9D003EF1EB /* PlayerCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FE1C9725368A9D003EF1EB /* PlayerCache.swift */; };
|
D0FE1C9825368A9D003EF1EB /* PlayerCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FE1C9725368A9D003EF1EB /* PlayerCache.swift */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
@ -197,6 +194,7 @@
|
||||||
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
||||||
|
@ -213,13 +211,9 @@
|
||||||
D08E526C257C36CA00FA2C5F /* Share Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Share Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
D0FE1C9725368A9D003EF1EB /* PlayerCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerCache.swift; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
@ -309,7 +304,7 @@
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
D0F2D54025818C4B00986197 /* Kingfisher in Frameworks */,
|
D0F2D54025818C4B00986197 /* Kingfisher in Frameworks */,
|
||||||
D08E52B8257C62D500FA2C5F /* ViewModels in Frameworks */,
|
D04F9E8E259E9C950081B0C9 /* ViewModels in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
@ -424,7 +419,6 @@
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
D0A1F4F6252E7D4B004435BF /* TableViewDataSource.swift */,
|
D0A1F4F6252E7D4B004435BF /* TableViewDataSource.swift */,
|
||||||
D0F2D4D0257EE84400986197 /* NewStatusDataSource.swift */,
|
|
||||||
D065965A25899DAE0096AC5D /* CompositionAttachmentsDataSource.swift */,
|
D065965A25899DAE0096AC5D /* CompositionAttachmentsDataSource.swift */,
|
||||||
);
|
);
|
||||||
path = "Data Sources";
|
path = "Data Sources";
|
||||||
|
@ -452,9 +446,7 @@
|
||||||
D065966025899E890096AC5D /* CompositionAttachmentCollectionViewCell.swift */,
|
D065966025899E890096AC5D /* CompositionAttachmentCollectionViewCell.swift */,
|
||||||
D080413D258D904400AD6139 /* CompositionAttachmentContentConfiguration.swift */,
|
D080413D258D904400AD6139 /* CompositionAttachmentContentConfiguration.swift */,
|
||||||
D0804132258D902900AD6139 /* CompositionAttachmentView.swift */,
|
D0804132258D902900AD6139 /* CompositionAttachmentView.swift */,
|
||||||
D08E52E2257D747400FA2C5F /* CompositionContentConfiguration.swift */,
|
|
||||||
D0E9F9A9258450B300EF503D /* CompositionInputAccessoryView.swift */,
|
D0E9F9A9258450B300EF503D /* CompositionInputAccessoryView.swift */,
|
||||||
D08E52DB257D742B00FA2C5F /* CompositionListCell.swift */,
|
|
||||||
D08E52ED257D757100FA2C5F /* CompositionView.swift */,
|
D08E52ED257D757100FA2C5F /* CompositionView.swift */,
|
||||||
D007023D25562A2800F38136 /* ConversationAvatarsView.swift */,
|
D007023D25562A2800F38136 /* ConversationAvatarsView.swift */,
|
||||||
D00702352555F4C500F38136 /* ConversationContentConfiguration.swift */,
|
D00702352555F4C500F38136 /* ConversationContentConfiguration.swift */,
|
||||||
|
@ -471,7 +463,7 @@
|
||||||
D0E569DF252931B100FA1D72 /* LoadMoreContentConfiguration.swift */,
|
D0E569DF252931B100FA1D72 /* LoadMoreContentConfiguration.swift */,
|
||||||
D0E569DA2529319100FA1D72 /* LoadMoreView.swift */,
|
D0E569DA2529319100FA1D72 /* LoadMoreView.swift */,
|
||||||
D03B1B29253818F3008F964B /* MediaPreferencesView.swift */,
|
D03B1B29253818F3008F964B /* MediaPreferencesView.swift */,
|
||||||
D08E529B257C58D600FA2C5F /* NewStatusView.swift */,
|
D0FCC10F259C4F20000B67DF /* NewStatusView.swift */,
|
||||||
D036AA0B254B612B009094DF /* NotificationContentConfiguration.swift */,
|
D036AA0B254B612B009094DF /* NotificationContentConfiguration.swift */,
|
||||||
D036AA01254B6101009094DF /* NotificationListCell.swift */,
|
D036AA01254B6101009094DF /* NotificationListCell.swift */,
|
||||||
D0C7D42D24F76169001EBDBB /* NotificationTypesPreferencesView.swift */,
|
D0C7D42D24F76169001EBDBB /* NotificationTypesPreferencesView.swift */,
|
||||||
|
@ -505,7 +497,7 @@
|
||||||
D08B8D49253FC36500B1EBEF /* ImageNavigationController.swift */,
|
D08B8D49253FC36500B1EBEF /* ImageNavigationController.swift */,
|
||||||
D08B8D41253F92B600B1EBEF /* ImagePageViewController.swift */,
|
D08B8D41253F92B600B1EBEF /* ImagePageViewController.swift */,
|
||||||
D08B8D3C253F929E00B1EBEF /* ImageViewController.swift */,
|
D08B8D3C253F929E00B1EBEF /* ImageViewController.swift */,
|
||||||
D08E5291257C53B600FA2C5F /* NewStatusViewController.swift */,
|
D0FCC104259C4E61000B67DF /* NewStatusViewController.swift */,
|
||||||
D06BC5E525202AD90079541D /* ProfileViewController.swift */,
|
D06BC5E525202AD90079541D /* ProfileViewController.swift */,
|
||||||
D0F0B12D251A97E400942152 /* TableViewController.swift */,
|
D0F0B12D251A97E400942152 /* TableViewController.swift */,
|
||||||
);
|
);
|
||||||
|
@ -538,6 +530,7 @@
|
||||||
D0C7D46E24F76169001EBDBB /* KingfisherOptionsInfo+Extensions.swift */,
|
D0C7D46E24F76169001EBDBB /* KingfisherOptionsInfo+Extensions.swift */,
|
||||||
D0C7D46B24F76169001EBDBB /* NSMutableAttributedString+Extensions.swift */,
|
D0C7D46B24F76169001EBDBB /* NSMutableAttributedString+Extensions.swift */,
|
||||||
D0B5FE9A251583DB00478838 /* ProfileCollection+Extensions.swift */,
|
D0B5FE9A251583DB00478838 /* ProfileCollection+Extensions.swift */,
|
||||||
|
D0849C7E25903C4900A5EBCC /* Status+Extensions.swift */,
|
||||||
D0C7D46A24F76169001EBDBB /* String+Extensions.swift */,
|
D0C7D46A24F76169001EBDBB /* String+Extensions.swift */,
|
||||||
D08E512025786A6600FA2C5F /* UIButton+Extensions.swift */,
|
D08E512025786A6600FA2C5F /* UIButton+Extensions.swift */,
|
||||||
D0C7D46C24F76169001EBDBB /* UIColor+Extensions.swift */,
|
D0C7D46C24F76169001EBDBB /* UIColor+Extensions.swift */,
|
||||||
|
@ -629,8 +622,8 @@
|
||||||
);
|
);
|
||||||
name = "Share Extension";
|
name = "Share Extension";
|
||||||
packageProductDependencies = (
|
packageProductDependencies = (
|
||||||
D08E52B7257C62D500FA2C5F /* ViewModels */,
|
|
||||||
D0F2D53F25818C4B00986197 /* Kingfisher */,
|
D0F2D53F25818C4B00986197 /* Kingfisher */,
|
||||||
|
D04F9E8D259E9C950081B0C9 /* ViewModels */,
|
||||||
);
|
);
|
||||||
productName = "Share Extension";
|
productName = "Share Extension";
|
||||||
productReference = D08E526C257C36CA00FA2C5F /* Share Extension.appex */;
|
productReference = D08E526C257C36CA00FA2C5F /* Share Extension.appex */;
|
||||||
|
@ -769,7 +762,6 @@
|
||||||
files = (
|
files = (
|
||||||
D0C7D4A324F7616A001EBDBB /* TabNavigationView.swift in Sources */,
|
D0C7D4A324F7616A001EBDBB /* TabNavigationView.swift in Sources */,
|
||||||
D02E1F95250B13210071AD56 /* SafariView.swift in Sources */,
|
D02E1F95250B13210071AD56 /* SafariView.swift in Sources */,
|
||||||
D0F2D4D1257EE84400986197 /* NewStatusDataSource.swift in Sources */,
|
|
||||||
D00702292555E51200F38136 /* ConversationListCell.swift in Sources */,
|
D00702292555E51200F38136 /* ConversationListCell.swift in Sources */,
|
||||||
D03B1B2A253818F3008F964B /* MediaPreferencesView.swift in Sources */,
|
D03B1B2A253818F3008F964B /* MediaPreferencesView.swift in Sources */,
|
||||||
D0C7D49C24F7616A001EBDBB /* RootView.swift in Sources */,
|
D0C7D49C24F7616A001EBDBB /* RootView.swift in Sources */,
|
||||||
|
@ -781,7 +773,6 @@
|
||||||
D08E52EE257D757100FA2C5F /* CompositionView.swift in Sources */,
|
D08E52EE257D757100FA2C5F /* CompositionView.swift in Sources */,
|
||||||
D0E569E0252931B100FA1D72 /* LoadMoreContentConfiguration.swift in Sources */,
|
D0E569E0252931B100FA1D72 /* LoadMoreContentConfiguration.swift in Sources */,
|
||||||
D0FE1C9825368A9D003EF1EB /* PlayerCache.swift in Sources */,
|
D0FE1C9825368A9D003EF1EB /* PlayerCache.swift in Sources */,
|
||||||
D08E52E3257D747400FA2C5F /* CompositionContentConfiguration.swift in Sources */,
|
|
||||||
D0F0B136251AA12700942152 /* CollectionItem+Extensions.swift in Sources */,
|
D0F0B136251AA12700942152 /* CollectionItem+Extensions.swift in Sources */,
|
||||||
D007023E25562A2800F38136 /* ConversationAvatarsView.swift in Sources */,
|
D007023E25562A2800F38136 /* ConversationAvatarsView.swift in Sources */,
|
||||||
D0E7AD3925870B13005F5E2D /* UIVIewController+Extensions.swift in Sources */,
|
D0E7AD3925870B13005F5E2D /* UIVIewController+Extensions.swift in Sources */,
|
||||||
|
@ -789,7 +780,6 @@
|
||||||
D0BEB1F324F8EE8C001B0F04 /* StatusAttachmentView.swift in Sources */,
|
D0BEB1F324F8EE8C001B0F04 /* StatusAttachmentView.swift in Sources */,
|
||||||
D0C7D49A24F7616A001EBDBB /* TableView.swift in Sources */,
|
D0C7D49A24F7616A001EBDBB /* TableView.swift in Sources */,
|
||||||
D08B8D622540DE3B00B1EBEF /* ZoomTransitionController.swift in Sources */,
|
D08B8D622540DE3B00B1EBEF /* ZoomTransitionController.swift in Sources */,
|
||||||
D08E529C257C58D600FA2C5F /* NewStatusView.swift in Sources */,
|
|
||||||
D0F0B12E251A97E400942152 /* TableViewController.swift in Sources */,
|
D0F0B12E251A97E400942152 /* TableViewController.swift in Sources */,
|
||||||
D0DD50CB256B1F24004A04F7 /* ReportView.swift in Sources */,
|
D0DD50CB256B1F24004A04F7 /* ReportView.swift in Sources */,
|
||||||
D0FE1C8F253686F9003EF1EB /* PlayerView.swift in Sources */,
|
D0FE1C8F253686F9003EF1EB /* PlayerView.swift in Sources */,
|
||||||
|
@ -800,15 +790,14 @@
|
||||||
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 */,
|
||||||
|
D0849C7F25903C4900A5EBCC /* Status+Extensions.swift in Sources */,
|
||||||
D080413E258D904400AD6139 /* CompositionAttachmentContentConfiguration.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 */,
|
||||||
D0B5FE9B251583DB00478838 /* ProfileCollection+Extensions.swift in Sources */,
|
D0B5FE9B251583DB00478838 /* ProfileCollection+Extensions.swift in Sources */,
|
||||||
D08E52DC257D742B00FA2C5F /* CompositionListCell.swift in Sources */,
|
|
||||||
D0C7D49E24F7616A001EBDBB /* SecondaryNavigationView.swift in Sources */,
|
D0C7D49E24F7616A001EBDBB /* SecondaryNavigationView.swift in Sources */,
|
||||||
D08B8D602540DE3B00B1EBEF /* ZoomAnimator.swift in Sources */,
|
D08B8D602540DE3B00B1EBEF /* ZoomAnimator.swift in Sources */,
|
||||||
D08E5292257C53B600FA2C5F /* NewStatusViewController.swift in Sources */,
|
|
||||||
D08B8D672540DEB200B1EBEF /* ZoomAnimatableView.swift in Sources */,
|
D08B8D672540DEB200B1EBEF /* ZoomAnimatableView.swift in Sources */,
|
||||||
D08B8D822544D80000B1EBEF /* PollOptionButton.swift in Sources */,
|
D08B8D822544D80000B1EBEF /* PollOptionButton.swift in Sources */,
|
||||||
D0C7D4DA24F7616A001EBDBB /* View+Extensions.swift in Sources */,
|
D0C7D4DA24F7616A001EBDBB /* View+Extensions.swift in Sources */,
|
||||||
|
@ -847,11 +836,13 @@
|
||||||
D0A1F4F7252E7D4B004435BF /* TableViewDataSource.swift in Sources */,
|
D0A1F4F7252E7D4B004435BF /* TableViewDataSource.swift in Sources */,
|
||||||
D0C7D4C424F7616A001EBDBB /* AppDelegate.swift in Sources */,
|
D0C7D4C424F7616A001EBDBB /* AppDelegate.swift in Sources */,
|
||||||
D0C7D49924F7616A001EBDBB /* AddIdentityView.swift in Sources */,
|
D0C7D49924F7616A001EBDBB /* AddIdentityView.swift in Sources */,
|
||||||
|
D0FCC105259C4E61000B67DF /* NewStatusViewController.swift in Sources */,
|
||||||
D0F2D54B2581CF7D00986197 /* VisualEffectBlur.swift in Sources */,
|
D0F2D54B2581CF7D00986197 /* VisualEffectBlur.swift in Sources */,
|
||||||
D0A7AC7325748BFF00E4E8AB /* ReportStatusView.swift in Sources */,
|
D0A7AC7325748BFF00E4E8AB /* ReportStatusView.swift in Sources */,
|
||||||
D0C7D4C324F7616A001EBDBB /* MetatextApp.swift in Sources */,
|
D0C7D4C324F7616A001EBDBB /* MetatextApp.swift in Sources */,
|
||||||
D0E1F583251F13EC00D45315 /* WebfingerIndicatorView.swift in Sources */,
|
D0E1F583251F13EC00D45315 /* WebfingerIndicatorView.swift in Sources */,
|
||||||
D0BEB20524FA1107001B0F04 /* FiltersView.swift in Sources */,
|
D0BEB20524FA1107001B0F04 /* FiltersView.swift in Sources */,
|
||||||
|
D0FCC110259C4F20000B67DF /* NewStatusView.swift in Sources */,
|
||||||
D0C7D49B24F7616A001EBDBB /* PreferencesView.swift in Sources */,
|
D0C7D49B24F7616A001EBDBB /* PreferencesView.swift in Sources */,
|
||||||
D0C7D4D724F7616A001EBDBB /* UIColor+Extensions.swift in Sources */,
|
D0C7D4D724F7616A001EBDBB /* UIColor+Extensions.swift in Sources */,
|
||||||
);
|
);
|
||||||
|
@ -869,23 +860,22 @@
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
D08E52A6257C61C000FA2C5F /* ShareExtensionNavigationViewController.swift in Sources */,
|
D08E52A6257C61C000FA2C5F /* ShareExtensionNavigationViewController.swift in Sources */,
|
||||||
D08E52BD257C635800FA2C5F /* NewStatusViewController.swift in Sources */,
|
|
||||||
D08E52D2257C811200FA2C5F /* ShareExtensionError+Extensions.swift in Sources */,
|
D08E52D2257C811200FA2C5F /* ShareExtensionError+Extensions.swift in Sources */,
|
||||||
D0E9F9AB258450B300EF503D /* CompositionInputAccessoryView.swift in Sources */,
|
D0E9F9AB258450B300EF503D /* CompositionInputAccessoryView.swift in Sources */,
|
||||||
D0F2D4DB257F018300986197 /* Array+Extensions.swift in Sources */,
|
D0F2D4DB257F018300986197 /* Array+Extensions.swift in Sources */,
|
||||||
D08E52DD257D742B00FA2C5F /* CompositionListCell.swift in Sources */,
|
|
||||||
D0E7AD4225870C79005F5E2D /* UIVIewController+Extensions.swift in Sources */,
|
D0E7AD4225870C79005F5E2D /* UIVIewController+Extensions.swift in Sources */,
|
||||||
|
D038273C259EA38F00056E0F /* NewStatusView.swift in Sources */,
|
||||||
D08E52EF257D757100FA2C5F /* CompositionView.swift in Sources */,
|
D08E52EF257D757100FA2C5F /* CompositionView.swift in Sources */,
|
||||||
D0CE9F88258B076900E3A6B6 /* AttachmentUploadView.swift in Sources */,
|
D0CE9F88258B076900E3A6B6 /* AttachmentUploadView.swift in Sources */,
|
||||||
|
D036768E2593E6DE005DF15A /* Status+Extensions.swift in Sources */,
|
||||||
D080413F258D904400AD6139 /* CompositionAttachmentContentConfiguration.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 */,
|
|
||||||
D065966725899E910096AC5D /* CompositionAttachmentsDataSource.swift in Sources */,
|
D065966725899E910096AC5D /* CompositionAttachmentsDataSource.swift in Sources */,
|
||||||
|
D0FCC106259C4E62000B67DF /* NewStatusViewController.swift in Sources */,
|
||||||
D0804134258D902900AD6139 /* CompositionAttachmentView.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 */,
|
|
||||||
D065966225899E890096AC5D /* CompositionAttachmentCollectionViewCell.swift in Sources */,
|
D065966225899E890096AC5D /* CompositionAttachmentCollectionViewCell.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
@ -1293,15 +1283,15 @@
|
||||||
/* End XCRemoteSwiftPackageReference section */
|
/* End XCRemoteSwiftPackageReference section */
|
||||||
|
|
||||||
/* Begin XCSwiftPackageProductDependency section */
|
/* Begin XCSwiftPackageProductDependency section */
|
||||||
|
D04F9E8D259E9C950081B0C9 /* ViewModels */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
productName = ViewModels;
|
||||||
|
};
|
||||||
D06B492224D4611300642749 /* KingfisherSwiftUI */ = {
|
D06B492224D4611300642749 /* KingfisherSwiftUI */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
package = D06B492124D4611300642749 /* XCRemoteSwiftPackageReference "Kingfisher" */;
|
package = D06B492124D4611300642749 /* XCRemoteSwiftPackageReference "Kingfisher" */;
|
||||||
productName = KingfisherSwiftUI;
|
productName = KingfisherSwiftUI;
|
||||||
};
|
};
|
||||||
D08E52B7257C62D500FA2C5F /* ViewModels */ = {
|
|
||||||
isa = XCSwiftPackageProductDependency;
|
|
||||||
productName = ViewModels;
|
|
||||||
};
|
|
||||||
D0BECB972501C0FC002C1B13 /* Secrets */ = {
|
D0BECB972501C0FC002C1B13 /* Secrets */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
productName = Secrets;
|
productName = Secrets;
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
import Combine
|
import Combine
|
||||||
import ServiceLayer
|
import ServiceLayer
|
||||||
import UIKit
|
import SwiftUI
|
||||||
import ViewModels
|
import ViewModels
|
||||||
|
|
||||||
@objc(ShareExtensionNavigationViewController)
|
@objc(ShareExtensionNavigationViewController)
|
||||||
|
@ -25,7 +25,7 @@ class ShareExtensionNavigationViewController: UINavigationController {
|
||||||
}
|
}
|
||||||
|
|
||||||
setViewControllers(
|
setViewControllers(
|
||||||
[NewStatusViewController(viewModel: newStatusViewModel, isShareExtension: true)],
|
[UIHostingController(rootView: NewStatusView { newStatusViewModel })],
|
||||||
animated: false)
|
animated: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,29 +6,22 @@ import PhotosUI
|
||||||
import UIKit
|
import UIKit
|
||||||
import ViewModels
|
import ViewModels
|
||||||
|
|
||||||
class NewStatusViewController: UICollectionViewController {
|
final class NewStatusViewController: UIViewController {
|
||||||
private let viewModel: NewStatusViewModel
|
private let viewModel: NewStatusViewModel
|
||||||
private let isShareExtension: Bool
|
private let scrollView = UIScrollView()
|
||||||
|
private let stackView = UIStackView()
|
||||||
private let postButton = UIBarButtonItem(
|
private let postButton = UIBarButtonItem(
|
||||||
title: NSLocalizedString("post", comment: ""),
|
title: NSLocalizedString("post", comment: ""),
|
||||||
style: .done,
|
style: .done,
|
||||||
target: nil,
|
target: nil,
|
||||||
action: nil)
|
action: nil)
|
||||||
private var attachMediaTo: CompositionViewModel?
|
private let mediaSelections = PassthroughSubject<[PHPickerResult], Never>()
|
||||||
private var cancellables = Set<AnyCancellable>()
|
private var cancellables = Set<AnyCancellable>()
|
||||||
|
|
||||||
private lazy var dataSource: NewStatusDataSource = {
|
init(viewModel: NewStatusViewModel) {
|
||||||
.init(collectionView: collectionView, viewModelProvider: viewModel.viewModel(indexPath:))
|
|
||||||
}()
|
|
||||||
|
|
||||||
init(viewModel: NewStatusViewModel, isShareExtension: Bool) {
|
|
||||||
self.viewModel = viewModel
|
self.viewModel = viewModel
|
||||||
self.isShareExtension = isShareExtension
|
|
||||||
|
|
||||||
let configuration = UICollectionLayoutListConfiguration(appearance: .plain)
|
super.init(nibName: nil, bundle: nil)
|
||||||
let layout = UICollectionViewCompositionalLayout.list(using: configuration)
|
|
||||||
|
|
||||||
super.init(collectionViewLayout: layout)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(*, unavailable)
|
@available(*, unavailable)
|
||||||
|
@ -39,15 +32,101 @@ class NewStatusViewController: UICollectionViewController {
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
|
||||||
collectionView.dataSource = dataSource
|
|
||||||
|
|
||||||
view.backgroundColor = .systemBackground
|
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
|
postButton.primaryAction = UIAction(title: NSLocalizedString("post", comment: "")) { [weak self] _ in
|
||||||
self?.viewModel.post()
|
self?.viewModel.post()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setupViewModelBindings()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func didMove(toParent parent: UIViewController?) {
|
||||||
|
super.didMove(toParent: parent)
|
||||||
|
|
||||||
setupBarButtonItems(identification: viewModel.identification)
|
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
|
viewModel.$identification
|
||||||
.sink { [weak self] in
|
.sink { [weak self] in
|
||||||
|
@ -57,33 +136,6 @@ class NewStatusViewController: UICollectionViewController {
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.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
|
viewModel.$alertItem
|
||||||
.compactMap { $0 }
|
.compactMap { $0 }
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
|
@ -91,46 +143,37 @@ class NewStatusViewController: UICollectionViewController {
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func didMove(toParent parent: UIViewController?) {
|
|
||||||
super.didMove(toParent: parent)
|
|
||||||
|
|
||||||
setupBarButtonItems(identification: viewModel.identification)
|
|
||||||
}
|
|
||||||
|
|
||||||
func setupBarButtonItems(identification: Identification) {
|
func setupBarButtonItems(identification: Identification) {
|
||||||
let target = isShareExtension ? self : parent
|
|
||||||
let closeButton = UIBarButtonItem(
|
let closeButton = UIBarButtonItem(
|
||||||
systemItem: .close,
|
systemItem: .close,
|
||||||
primaryAction: UIAction { [weak self] _ in self?.dismiss() })
|
primaryAction: UIAction { [weak self] _ in self?.dismiss() })
|
||||||
|
|
||||||
target?.navigationItem.leftBarButtonItem = closeButton
|
parent?.navigationItem.leftBarButtonItem = closeButton
|
||||||
target?.navigationItem.titleView = viewModel.canChangeIdentity
|
parent?.navigationItem.titleView = viewModel.canChangeIdentity
|
||||||
? changeIdentityButton(identification: identification)
|
? changeIdentityButton(identification: identification)
|
||||||
: nil
|
: nil
|
||||||
target?.navigationItem.rightBarButtonItem = postButton
|
parent?.navigationItem.rightBarButtonItem = postButton
|
||||||
}
|
}
|
||||||
|
|
||||||
func dismiss() {
|
func presentMediaPicker(compositionViewModel: CompositionViewModel) {
|
||||||
if isShareExtension {
|
mediaSelections.first().sink { [weak self] results in
|
||||||
extensionContext?.completeRequest(returningItems: nil)
|
guard let self = self, let result = results.first else { return }
|
||||||
} else {
|
|
||||||
presentingViewController?.dismiss(animated: true)
|
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 {
|
func changeIdentityButton(identification: Identification) -> UIButton {
|
||||||
let changeIdentityButton = UIButton()
|
let changeIdentityButton = UIButton()
|
||||||
let downsampled = KingfisherOptionsInfo.downsampled(
|
let downsampled = KingfisherOptionsInfo.downsampled(
|
||||||
|
@ -168,22 +211,4 @@ private extension NewStatusViewController {
|
||||||
|
|
||||||
return changeIdentityButton
|
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))
|
static let preview = DomainBlocksViewModel(service: .init(mastodonAPIClient: .preview))
|
||||||
}
|
}
|
||||||
|
|
||||||
public extension NewStatusViewModel {
|
|
||||||
static let preview = RootViewModel.preview.newStatusViewModel(identification: .preview)
|
|
||||||
}
|
|
||||||
|
|
||||||
// swiftlint:enable force_try
|
// swiftlint:enable force_try
|
||||||
|
|
|
@ -5,25 +5,26 @@ import Foundation
|
||||||
import Mastodon
|
import Mastodon
|
||||||
import ServiceLayer
|
import ServiceLayer
|
||||||
|
|
||||||
public final class CompositionViewModel: ObservableObject {
|
public final class CompositionViewModel: ObservableObject, Identifiable {
|
||||||
public let id = Id()
|
public let id = Id()
|
||||||
public var isPosted = false
|
public var isPosted = false
|
||||||
@Published public var text = ""
|
@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 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 attachmentUpload: AttachmentUpload?
|
@Published public private(set) var attachmentUpload: AttachmentUpload?
|
||||||
|
|
||||||
private let eventsSubject: PassthroughSubject<Event, Never>
|
|
||||||
private var cancellables = Set<AnyCancellable>()
|
private var cancellables = Set<AnyCancellable>()
|
||||||
|
|
||||||
init(identification: Identification,
|
init() {
|
||||||
identificationPublisher: AnyPublisher<Identification, Never>,
|
$text.map { !$0.isEmpty }
|
||||||
eventsSubject: PassthroughSubject<Event, Never>) {
|
.removeDuplicates()
|
||||||
self.identification = identification
|
.combineLatest($attachmentViewModels.map { !$0.isEmpty })
|
||||||
self.eventsSubject = eventsSubject
|
.map { textPresent, attachmentPresent in
|
||||||
identificationPublisher.assign(to: &$identification)
|
textPresent || attachmentPresent
|
||||||
$text.map { !$0.isEmpty }.removeDuplicates().assign(to: &$isPostable)
|
}
|
||||||
|
.assign(to: &$isPostable)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,49 +37,41 @@ public extension CompositionViewModel {
|
||||||
case error(Error)
|
case error(Error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func components(inReplyToId: Status.Id?) -> StatusComponents {
|
func components(inReplyToId: Status.Id?, visibility: Status.Visibility) -> StatusComponents {
|
||||||
StatusComponents(
|
StatusComponents(
|
||||||
inReplyToId: inReplyToId,
|
inReplyToId: inReplyToId,
|
||||||
text: text,
|
text: text,
|
||||||
mediaIds: attachmentViewModels.map(\.attachment.id))
|
spoilerText: displayContentWarning ? contentWarning : "",
|
||||||
}
|
mediaIds: attachmentViewModels.map(\.attachment.id),
|
||||||
|
visibility: visibility)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func attachmentViewModel(indexPath: IndexPath) -> CompositionAttachmentViewModel {
|
func attachmentViewModel(indexPath: IndexPath) -> CompositionAttachmentViewModel {
|
||||||
attachmentViewModels[indexPath.item]
|
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
|
import ServiceLayer
|
||||||
|
|
||||||
public final class NewStatusViewModel: ObservableObject {
|
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 identification: Identification
|
||||||
@Published public private(set) var authenticatedIdentities = [Identity]()
|
@Published public private(set) var authenticatedIdentities = [Identity]()
|
||||||
@Published public var canPost = false
|
@Published public var canPost = false
|
||||||
@Published public var canChangeIdentity = true
|
@Published public var canChangeIdentity = true
|
||||||
@Published public var alertItem: AlertItem?
|
@Published public var alertItem: AlertItem?
|
||||||
@Published public private(set) var loading = false
|
@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 allIdentitiesService: AllIdentitiesService
|
||||||
private let environment: AppEnvironment
|
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 let itemEventsSubject = PassthroughSubject<CompositionViewModel.Event, Never>()
|
||||||
private var cancellables = Set<AnyCancellable>()
|
private var cancellables = Set<AnyCancellable>()
|
||||||
|
|
||||||
|
@ -28,8 +29,7 @@ public final class NewStatusViewModel: ObservableObject {
|
||||||
self.identification = identification
|
self.identification = identification
|
||||||
self.environment = environment
|
self.environment = environment
|
||||||
events = eventsSubject.eraseToAnyPublisher()
|
events = eventsSubject.eraseToAnyPublisher()
|
||||||
compositionViewModels = [newCompositionViewModel()]
|
visibility = identification.identity.preferences.postingDefaultVisibility
|
||||||
itemEventsSubject.sink { [weak self] in self?.handle(event: $0) }.store(in: &cancellables)
|
|
||||||
allIdentitiesService.authenticatedIdentitiesPublisher()
|
allIdentitiesService.authenticatedIdentitiesPublisher()
|
||||||
.assignErrorsToAlertItem(to: \.alertItem, on: self)
|
.assignErrorsToAlertItem(to: \.alertItem, on: self)
|
||||||
.assign(to: &$authenticatedIdentities)
|
.assign(to: &$authenticatedIdentities)
|
||||||
|
@ -43,8 +43,8 @@ public final class NewStatusViewModel: ObservableObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
public extension NewStatusViewModel {
|
public extension NewStatusViewModel {
|
||||||
func viewModel(indexPath: IndexPath) -> CompositionViewModel {
|
enum Event {
|
||||||
compositionViewModels[indexPath.row]
|
case presentMediaPicker(CompositionViewModel)
|
||||||
}
|
}
|
||||||
|
|
||||||
func setIdentity(_ identity: Identity) {
|
func setIdentity(_ identity: Identity) {
|
||||||
|
@ -66,6 +66,34 @@ public extension NewStatusViewModel {
|
||||||
environment: environment)
|
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() {
|
func post() {
|
||||||
guard let unposted = compositionViewModels.first(where: { !$0.isPosted }) else { return }
|
guard let unposted = compositionViewModels.first(where: { !$0.isPosted }) else { return }
|
||||||
|
|
||||||
|
@ -74,35 +102,11 @@ public extension NewStatusViewModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
private 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?) {
|
func post(viewModel: CompositionViewModel, inReplyToId: Status.Id?) {
|
||||||
loading = true
|
loading = true
|
||||||
identification.service.post(statusComponents: viewModel.components(inReplyToId: inReplyToId))
|
identification.service.post(statusComponents: viewModel.components(
|
||||||
|
inReplyToId: inReplyToId,
|
||||||
|
visibility: visibility))
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.sink { [weak self] in
|
.sink { [weak self] in
|
||||||
guard let self = self else { return }
|
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.
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
import Combine
|
import Combine
|
||||||
|
import Mastodon
|
||||||
import UIKit
|
import UIKit
|
||||||
import ViewModels
|
import ViewModels
|
||||||
|
|
||||||
class CompositionInputAccessoryView: UIView {
|
final class CompositionInputAccessoryView: UIView {
|
||||||
private let stackView = UIStackView()
|
let visibilityButton = UIButton()
|
||||||
|
let addButton = UIButton()
|
||||||
|
let contentWarningButton = UIButton(type: .system)
|
||||||
|
|
||||||
private let viewModel: CompositionViewModel
|
private let viewModel: CompositionViewModel
|
||||||
|
private let parentViewModel: NewStatusViewModel
|
||||||
|
private let stackView = UIStackView()
|
||||||
private var cancellables = Set<AnyCancellable>()
|
private var cancellables = Set<AnyCancellable>()
|
||||||
|
|
||||||
init(viewModel: CompositionViewModel) {
|
init(viewModel: CompositionViewModel, parentViewModel: NewStatusViewModel) {
|
||||||
self.viewModel = viewModel
|
self.viewModel = viewModel
|
||||||
|
self.parentViewModel = parentViewModel
|
||||||
|
|
||||||
super.init(frame: .zero)
|
super.init(frame: .zero)
|
||||||
|
|
||||||
|
@ -28,6 +35,7 @@ class CompositionInputAccessoryView: UIView {
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension CompositionInputAccessoryView {
|
private extension CompositionInputAccessoryView {
|
||||||
|
// swiftlint:disable:next function_body_length
|
||||||
func initialSetup() {
|
func initialSetup() {
|
||||||
autoresizingMask = .flexibleHeight
|
autoresizingMask = .flexibleHeight
|
||||||
backgroundColor = .secondarySystemFill
|
backgroundColor = .secondarySystemFill
|
||||||
|
@ -44,7 +52,12 @@ private extension CompositionInputAccessoryView {
|
||||||
systemName: "photo",
|
systemName: "photo",
|
||||||
withConfiguration: UIImage.SymbolConfiguration(scale: .medium)),
|
withConfiguration: UIImage.SymbolConfiguration(scale: .medium)),
|
||||||
for: .normal)
|
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()
|
let pollButton = UIButton()
|
||||||
|
|
||||||
|
@ -55,9 +68,26 @@ private extension CompositionInputAccessoryView {
|
||||||
withConfiguration: UIImage.SymbolConfiguration(scale: .medium)),
|
withConfiguration: UIImage.SymbolConfiguration(scale: .medium)),
|
||||||
for: .normal)
|
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)
|
stackView.addArrangedSubview(addButton)
|
||||||
addButton.setImage(
|
addButton.setImage(
|
||||||
|
@ -65,20 +95,32 @@ private extension CompositionInputAccessoryView {
|
||||||
systemName: "plus.circle.fill",
|
systemName: "plus.circle.fill",
|
||||||
withConfiguration: UIImage.SymbolConfiguration(scale: .medium)),
|
withConfiguration: UIImage.SymbolConfiguration(scale: .medium)),
|
||||||
for: .normal)
|
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.heightAnchor.constraint(greaterThanOrEqualToConstant: .minimumButtonDimension).isActive = true
|
||||||
button.widthAnchor.constraint(greaterThanOrEqualToConstant: .minimumButtonDimension).isActive = true
|
button.widthAnchor.constraint(greaterThanOrEqualToConstant: .minimumButtonDimension).isActive = true
|
||||||
}
|
}
|
||||||
|
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
stackView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
stackView.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor),
|
||||||
stackView.topAnchor.constraint(equalTo: topAnchor),
|
stackView.topAnchor.constraint(equalTo: topAnchor),
|
||||||
stackView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
stackView.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor),
|
||||||
stackView.bottomAnchor.constraint(equalTo: bottomAnchor)
|
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 Combine
|
||||||
import Kingfisher
|
import Kingfisher
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import ViewModels
|
||||||
|
|
||||||
final class CompositionView: UIView {
|
final class CompositionView: UIView {
|
||||||
let avatarImageView = UIImageView()
|
let avatarImageView = UIImageView()
|
||||||
|
let spoilerTextField = UITextField()
|
||||||
let textView = UITextView()
|
let textView = UITextView()
|
||||||
let attachmentUploadView = AttachmentUploadView()
|
let attachmentUploadView = AttachmentUploadView()
|
||||||
let attachmentsCollectionView: UICollectionView
|
let attachmentsCollectionView: UICollectionView
|
||||||
|
|
||||||
private var compositionConfiguration: CompositionContentConfiguration
|
private let viewModel: CompositionViewModel
|
||||||
|
private let parentViewModel: NewStatusViewModel
|
||||||
private var cancellables = Set<AnyCancellable>()
|
private var cancellables = Set<AnyCancellable>()
|
||||||
|
|
||||||
private lazy var attachmentsDataSource: CompositionAttachmentsDataSource = {
|
private lazy var attachmentsDataSource: CompositionAttachmentsDataSource = {
|
||||||
CompositionAttachmentsDataSource(
|
CompositionAttachmentsDataSource(
|
||||||
collectionView: attachmentsCollectionView,
|
collectionView: attachmentsCollectionView) { [weak self] in
|
||||||
viewModelProvider: compositionConfiguration.viewModel.attachmentViewModel(indexPath:))
|
self?.viewModel.attachmentViewModel(indexPath: $0)
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
init(configuration: CompositionContentConfiguration) {
|
init(viewModel: CompositionViewModel, parentViewModel: NewStatusViewModel) {
|
||||||
self.compositionConfiguration = configuration
|
self.viewModel = viewModel
|
||||||
|
self.parentViewModel = parentViewModel
|
||||||
|
|
||||||
let itemSize = NSCollectionLayoutSize(
|
let itemSize = NSCollectionLayoutSize(
|
||||||
widthDimension: .fractionalWidth(0.2),
|
widthDimension: .fractionalWidth(0.2),
|
||||||
|
@ -42,7 +47,6 @@ final class CompositionView: UIView {
|
||||||
super.init(frame: .zero)
|
super.init(frame: .zero)
|
||||||
|
|
||||||
initialSetup()
|
initialSetup()
|
||||||
applyCompositionConfiguration()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(*, unavailable)
|
@available(*, unavailable)
|
||||||
|
@ -51,93 +55,124 @@ final class CompositionView: UIView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension CompositionView: UIContentView {
|
extension CompositionView {
|
||||||
var configuration: UIContentConfiguration {
|
var id: CompositionViewModel.Id { viewModel.id }
|
||||||
get { compositionConfiguration }
|
|
||||||
set {
|
|
||||||
guard let compositionConfiguration = newValue as? CompositionContentConfiguration else { return }
|
|
||||||
|
|
||||||
self.compositionConfiguration = compositionConfiguration
|
|
||||||
|
|
||||||
applyCompositionConfiguration()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension CompositionView: UITextViewDelegate {
|
extension CompositionView: UITextViewDelegate {
|
||||||
func textViewDidChange(_ textView: UITextView) {
|
func textViewDidChange(_ textView: UITextView) {
|
||||||
compositionConfiguration.viewModel.text = textView.text
|
viewModel.text = textView.text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension CompositionView {
|
private extension CompositionView {
|
||||||
static let attachmentUploadViewHeight: CGFloat = 100
|
static let attachmentUploadViewHeight: CGFloat = 100
|
||||||
|
|
||||||
|
// swiftlint:disable:next function_body_length
|
||||||
func initialSetup() {
|
func initialSetup() {
|
||||||
|
tag = viewModel.id.hashValue
|
||||||
|
|
||||||
addSubview(avatarImageView)
|
addSubview(avatarImageView)
|
||||||
avatarImageView.translatesAutoresizingMaskIntoConstraints = false
|
avatarImageView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
avatarImageView.layer.cornerRadius = .avatarDimension / 2
|
avatarImageView.layer.cornerRadius = .avatarDimension / 2
|
||||||
avatarImageView.clipsToBounds = true
|
avatarImageView.clipsToBounds = true
|
||||||
|
|
||||||
let stackView = UIStackView()
|
let stackView = UIStackView()
|
||||||
|
let inputAccessoryView = CompositionInputAccessoryView(viewModel: viewModel, parentViewModel: parentViewModel)
|
||||||
|
|
||||||
addSubview(stackView)
|
addSubview(stackView)
|
||||||
stackView.translatesAutoresizingMaskIntoConstraints = false
|
stackView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
stackView.axis = .vertical
|
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)
|
stackView.addArrangedSubview(textView)
|
||||||
|
textView.backgroundColor = .secondarySystemBackground
|
||||||
|
textView.layer.cornerRadius = .defaultCornerRadius
|
||||||
textView.isScrollEnabled = false
|
textView.isScrollEnabled = false
|
||||||
textView.adjustsFontForContentSizeCategory = true
|
textView.adjustsFontForContentSizeCategory = true
|
||||||
textView.font = .preferredFont(forTextStyle: .body)
|
textView.font = .preferredFont(forTextStyle: .body)
|
||||||
textView.textContainer.lineFragmentPadding = 0
|
// textView.textContainer.lineFragmentPadding = 0
|
||||||
textView.inputAccessoryView = CompositionInputAccessoryView(viewModel: compositionConfiguration.viewModel)
|
textView.inputAccessoryView = inputAccessoryView
|
||||||
textView.inputAccessoryView?.sizeToFit()
|
textView.inputAccessoryView?.sizeToFit()
|
||||||
textView.delegate = self
|
textView.delegate = self
|
||||||
|
textView.setContentHuggingPriority(.required, for: .vertical)
|
||||||
|
|
||||||
stackView.addArrangedSubview(attachmentsCollectionView)
|
stackView.addArrangedSubview(attachmentsCollectionView)
|
||||||
attachmentsCollectionView.dataSource = attachmentsDataSource
|
attachmentsCollectionView.dataSource = attachmentsDataSource
|
||||||
|
|
||||||
stackView.addArrangedSubview(attachmentUploadView)
|
stackView.addArrangedSubview(attachmentUploadView)
|
||||||
|
|
||||||
let constraints = [
|
textView.text = viewModel.text
|
||||||
avatarImageView.heightAnchor.constraint(equalToConstant: .avatarDimension),
|
spoilerTextField.text = viewModel.contentWarning
|
||||||
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)
|
|
||||||
]
|
|
||||||
|
|
||||||
for constraint in constraints {
|
viewModel.$displayContentWarning
|
||||||
constraint.priority = .justBelowMax
|
.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() {
|
parentViewModel.$identification.map(\.identity.image)
|
||||||
cancellables.removeAll()
|
|
||||||
|
|
||||||
compositionConfiguration.viewModel.$identification.map(\.identity.image)
|
|
||||||
.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
|
viewModel.$attachmentViewModels
|
||||||
|
.receive(on: DispatchQueue.main) // hack to punt to next run loop, consider refactoring
|
||||||
.sink { [weak self] in
|
.sink { [weak self] in
|
||||||
self?.attachmentsDataSource.apply([$0.map(\.attachment)].snapshot())
|
self?.attachmentsDataSource.apply([$0.map(\.attachment)].snapshot())
|
||||||
self?.attachmentsCollectionView.isHidden = $0.isEmpty
|
self?.attachmentsCollectionView.isHidden = $0.isEmpty
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
compositionConfiguration.viewModel.$attachmentUpload
|
viewModel.$attachmentUpload
|
||||||
.sink { [weak self] in self?.attachmentUploadView.attachmentUpload = $0 }
|
.sink { [weak self] in self?.attachmentUploadView.attachmentUpload = $0 }
|
||||||
.store(in: &cancellables)
|
.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
|
let viewModelClosure: () -> NewStatusViewModel
|
||||||
|
|
||||||
func makeUIViewController(context: Context) -> NewStatusViewController {
|
func makeUIViewController(context: Context) -> NewStatusViewController {
|
||||||
NewStatusViewController(viewModel: viewModelClosure(), isShareExtension: false)
|
NewStatusViewController(viewModel: viewModelClosure())
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateUIViewController(_ uiViewController: NewStatusViewController, context: Context) {
|
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()
|
EmptyView()
|
||||||
.fullScreenCover(isPresented: $viewModel.presentingNewStatus) {
|
.fullScreenCover(isPresented: $viewModel.presentingNewStatus) {
|
||||||
NavigationView {
|
NavigationView {
|
||||||
NewStatusView {
|
NewStatusView { rootViewModel.newStatusViewModel(identification: viewModel.identification) }
|
||||||
rootViewModel.newStatusViewModel(identification: viewModel.identification)
|
|
||||||
}
|
|
||||||
.edgesIgnoringSafeArea(.all)
|
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
}
|
}
|
||||||
.navigationViewStyle(StackNavigationViewStyle())
|
.navigationViewStyle(StackNavigationViewStyle())
|
||||||
|
|
Loading…
Reference in a new issue