Refactoring
This commit is contained in:
parent
b5e128a1b0
commit
4426f84df4
|
@ -1,34 +0,0 @@
|
||||||
// Copyright © 2020 Metabolist. All rights reserved.
|
|
||||||
|
|
||||||
import Mastodon
|
|
||||||
import UIKit
|
|
||||||
import ViewModels
|
|
||||||
|
|
||||||
final class CompositionAttachmentsDataSource: UICollectionViewDiffableDataSource<Int, Attachment> {
|
|
||||||
private let updateQueue =
|
|
||||||
DispatchQueue(label: "com.metabolist.metatext.composition-attachments-data-source.update-queue")
|
|
||||||
|
|
||||||
init(collectionView: UICollectionView,
|
|
||||||
viewModelProvider: @escaping (IndexPath) -> (CompositionAttachmentViewModel, CompositionViewModel)) {
|
|
||||||
let registration = UICollectionView.CellRegistration
|
|
||||||
<CompositionAttachmentCollectionViewCell, (CompositionAttachmentViewModel, CompositionViewModel)> {
|
|
||||||
$0.viewModel = $2.0
|
|
||||||
$0.parentViewModel = $2.1
|
|
||||||
}
|
|
||||||
|
|
||||||
super.init(collectionView: collectionView) { collectionView, indexPath, _ in
|
|
||||||
collectionView.dequeueConfiguredReusableCell(
|
|
||||||
using: registration,
|
|
||||||
for: indexPath,
|
|
||||||
item: viewModelProvider(indexPath))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override func apply(_ snapshot: NSDiffableDataSourceSnapshot<Int, Attachment>,
|
|
||||||
animatingDifferences: Bool = true,
|
|
||||||
completion: (() -> Void)? = nil) {
|
|
||||||
updateQueue.async {
|
|
||||||
super.apply(snapshot, animatingDifferences: animatingDifferences, completion: completion)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -14,10 +14,14 @@
|
||||||
D007023E25562A2800F38136 /* ConversationAvatarsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D007023D25562A2800F38136 /* ConversationAvatarsView.swift */; };
|
D007023E25562A2800F38136 /* ConversationAvatarsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D007023D25562A2800F38136 /* ConversationAvatarsView.swift */; };
|
||||||
D0070252255921B100F38136 /* AccountFieldView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0070251255921B100F38136 /* AccountFieldView.swift */; };
|
D0070252255921B100F38136 /* AccountFieldView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0070251255921B100F38136 /* AccountFieldView.swift */; };
|
||||||
D00CB2ED2533ACC00080096B /* StatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00CB2EC2533ACC00080096B /* StatusView.swift */; };
|
D00CB2ED2533ACC00080096B /* StatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00CB2EC2533ACC00080096B /* StatusView.swift */; };
|
||||||
|
D015B13525A812DD006D88A8 /* AttachmentsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01F41E224F8889700D55A2D /* AttachmentsView.swift */; };
|
||||||
|
D015B13A25A812E6006D88A8 /* AttachmentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEB1F224F8EE8C001B0F04 /* AttachmentView.swift */; };
|
||||||
|
D015B13F25A812EC006D88A8 /* PlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FE1C8E253686F9003EF1EB /* PlayerView.swift */; };
|
||||||
|
D015B14425A812F6006D88A8 /* PlayerCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FE1C9725368A9D003EF1EB /* PlayerCache.swift */; };
|
||||||
D01C6FAC252024BD003D0300 /* Array+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01C6FAB252024BD003D0300 /* Array+Extensions.swift */; };
|
D01C6FAC252024BD003D0300 /* Array+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01C6FAB252024BD003D0300 /* Array+Extensions.swift */; };
|
||||||
D01EF22425182B1F00650C6B /* AccountHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01EF22325182B1F00650C6B /* AccountHeaderView.swift */; };
|
D01EF22425182B1F00650C6B /* AccountHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01EF22325182B1F00650C6B /* AccountHeaderView.swift */; };
|
||||||
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 /* AttachmentsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01F41E224F8889700D55A2D /* AttachmentsView.swift */; };
|
||||||
D02E1F95250B13210071AD56 /* SafariView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02E1F94250B13210071AD56 /* SafariView.swift */; };
|
D02E1F95250B13210071AD56 /* SafariView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02E1F94250B13210071AD56 /* SafariView.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 */; };
|
||||||
|
@ -34,15 +38,7 @@
|
||||||
D04F9E8E259E9C950081B0C9 /* ViewModels in Frameworks */ = {isa = PBXBuildFile; productRef = D04F9E8D259E9C950081B0C9 /* ViewModels */; };
|
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 */; };
|
|
||||||
D065966125899E890096AC5D /* CompositionAttachmentCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D065966025899E890096AC5D /* CompositionAttachmentCollectionViewCell.swift */; };
|
|
||||||
D065966225899E890096AC5D /* CompositionAttachmentCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D065966025899E890096AC5D /* CompositionAttachmentCollectionViewCell.swift */; };
|
|
||||||
D065966725899E910096AC5D /* CompositionAttachmentsDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D065965A25899DAE0096AC5D /* CompositionAttachmentsDataSource.swift */; };
|
|
||||||
D06BC5E625202AD90079541D /* ProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06BC5E525202AD90079541D /* ProfileViewController.swift */; };
|
D06BC5E625202AD90079541D /* ProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06BC5E525202AD90079541D /* ProfileViewController.swift */; };
|
||||||
D0804133258D902900AD6139 /* CompositionAttachmentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0804132258D902900AD6139 /* CompositionAttachmentView.swift */; };
|
|
||||||
D0804134258D902900AD6139 /* CompositionAttachmentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0804132258D902900AD6139 /* CompositionAttachmentView.swift */; };
|
|
||||||
D080413E258D904400AD6139 /* CompositionAttachmentContentConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D080413D258D904400AD6139 /* CompositionAttachmentContentConfiguration.swift */; };
|
|
||||||
D080413F258D904400AD6139 /* CompositionAttachmentContentConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D080413D258D904400AD6139 /* CompositionAttachmentContentConfiguration.swift */; };
|
|
||||||
D0849C7F25903C4900A5EBCC /* Status+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0849C7E25903C4900A5EBCC /* Status+Extensions.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 */; };
|
||||||
|
@ -69,7 +65,7 @@
|
||||||
D0B32F50250B373600311912 /* RegistrationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B32F4F250B373600311912 /* RegistrationView.swift */; };
|
D0B32F50250B373600311912 /* RegistrationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B32F4F250B373600311912 /* RegistrationView.swift */; };
|
||||||
D0B5FE9B251583DB00478838 /* ProfileCollection+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B5FE9A251583DB00478838 /* ProfileCollection+Extensions.swift */; };
|
D0B5FE9B251583DB00478838 /* ProfileCollection+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B5FE9A251583DB00478838 /* ProfileCollection+Extensions.swift */; };
|
||||||
D0B8510C25259E56004E0744 /* LoadMoreCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B8510B25259E56004E0744 /* LoadMoreCell.swift */; };
|
D0B8510C25259E56004E0744 /* LoadMoreCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B8510B25259E56004E0744 /* LoadMoreCell.swift */; };
|
||||||
D0BEB1F324F8EE8C001B0F04 /* StatusAttachmentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEB1F224F8EE8C001B0F04 /* StatusAttachmentView.swift */; };
|
D0BEB1F324F8EE8C001B0F04 /* AttachmentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEB1F224F8EE8C001B0F04 /* AttachmentView.swift */; };
|
||||||
D0BEB1F724F9A84B001B0F04 /* LoadingTableFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEB1F624F9A84B001B0F04 /* LoadingTableFooterView.swift */; };
|
D0BEB1F724F9A84B001B0F04 /* LoadingTableFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEB1F624F9A84B001B0F04 /* LoadingTableFooterView.swift */; };
|
||||||
D0BEB1FF24F9E5BB001B0F04 /* ListsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEB1FE24F9E5BB001B0F04 /* ListsView.swift */; };
|
D0BEB1FF24F9E5BB001B0F04 /* ListsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEB1FE24F9E5BB001B0F04 /* ListsView.swift */; };
|
||||||
D0BEB20524FA1107001B0F04 /* FiltersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEB20424FA1107001B0F04 /* FiltersView.swift */; };
|
D0BEB20524FA1107001B0F04 /* FiltersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEB20424FA1107001B0F04 /* FiltersView.swift */; };
|
||||||
|
@ -176,7 +172,7 @@
|
||||||
D01C6FAB252024BD003D0300 /* Array+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+Extensions.swift"; sourceTree = "<group>"; };
|
D01C6FAB252024BD003D0300 /* Array+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+Extensions.swift"; sourceTree = "<group>"; };
|
||||||
D01EF22325182B1F00650C6B /* AccountHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountHeaderView.swift; sourceTree = "<group>"; };
|
D01EF22325182B1F00650C6B /* AccountHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountHeaderView.swift; sourceTree = "<group>"; };
|
||||||
D01F41D624F880C400D55A2D /* TouchFallthroughTextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TouchFallthroughTextView.swift; sourceTree = "<group>"; };
|
D01F41D624F880C400D55A2D /* TouchFallthroughTextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TouchFallthroughTextView.swift; sourceTree = "<group>"; };
|
||||||
D01F41E224F8889700D55A2D /* StatusAttachmentsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusAttachmentsView.swift; sourceTree = "<group>"; };
|
D01F41E224F8889700D55A2D /* AttachmentsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AttachmentsView.swift; sourceTree = "<group>"; };
|
||||||
D02E1F94250B13210071AD56 /* SafariView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariView.swift; sourceTree = "<group>"; };
|
D02E1F94250B13210071AD56 /* SafariView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariView.swift; sourceTree = "<group>"; };
|
||||||
D036AA01254B6101009094DF /* NotificationListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationListCell.swift; sourceTree = "<group>"; };
|
D036AA01254B6101009094DF /* NotificationListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationListCell.swift; sourceTree = "<group>"; };
|
||||||
D036AA06254B6118009094DF /* NotificationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationView.swift; sourceTree = "<group>"; };
|
D036AA06254B6118009094DF /* NotificationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationView.swift; sourceTree = "<group>"; };
|
||||||
|
@ -187,13 +183,9 @@
|
||||||
D047FA8C24C3E21200AF17C5 /* Metatext.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Metatext.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
D047FA8C24C3E21200AF17C5 /* Metatext.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Metatext.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
D0625E58250F092900502611 /* StatusListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusListCell.swift; sourceTree = "<group>"; };
|
D0625E58250F092900502611 /* StatusListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusListCell.swift; sourceTree = "<group>"; };
|
||||||
D0625E5C250F0B5C00502611 /* StatusContentConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusContentConfiguration.swift; sourceTree = "<group>"; };
|
D0625E5C250F0B5C00502611 /* StatusContentConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusContentConfiguration.swift; sourceTree = "<group>"; };
|
||||||
D065965A25899DAE0096AC5D /* CompositionAttachmentsDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompositionAttachmentsDataSource.swift; sourceTree = "<group>"; };
|
|
||||||
D065966025899E890096AC5D /* CompositionAttachmentCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompositionAttachmentCollectionViewCell.swift; sourceTree = "<group>"; };
|
|
||||||
D0666A2124C677B400F3F04B /* Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
D0666A2124C677B400F3F04B /* Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
D0666A2524C677B400F3F04B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
D0666A2524C677B400F3F04B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
D06BC5E525202AD90079541D /* ProfileViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileViewController.swift; sourceTree = "<group>"; };
|
D06BC5E525202AD90079541D /* ProfileViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileViewController.swift; sourceTree = "<group>"; };
|
||||||
D0804132258D902900AD6139 /* CompositionAttachmentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompositionAttachmentView.swift; sourceTree = "<group>"; };
|
|
||||||
D080413D258D904400AD6139 /* CompositionAttachmentContentConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompositionAttachmentContentConfiguration.swift; sourceTree = "<group>"; };
|
|
||||||
D0849C7E25903C4900A5EBCC /* Status+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Status+Extensions.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>"; };
|
||||||
|
@ -222,7 +214,7 @@
|
||||||
D0B5FE9A251583DB00478838 /* ProfileCollection+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileCollection+Extensions.swift"; sourceTree = "<group>"; };
|
D0B5FE9A251583DB00478838 /* ProfileCollection+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileCollection+Extensions.swift"; sourceTree = "<group>"; };
|
||||||
D0B8510B25259E56004E0744 /* LoadMoreCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadMoreCell.swift; sourceTree = "<group>"; };
|
D0B8510B25259E56004E0744 /* LoadMoreCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadMoreCell.swift; sourceTree = "<group>"; };
|
||||||
D0BDF66524FD7A6400C7FA1C /* ServiceLayer */ = {isa = PBXFileReference; lastKnownFileType = folder; path = ServiceLayer; sourceTree = "<group>"; };
|
D0BDF66524FD7A6400C7FA1C /* ServiceLayer */ = {isa = PBXFileReference; lastKnownFileType = folder; path = ServiceLayer; sourceTree = "<group>"; };
|
||||||
D0BEB1F224F8EE8C001B0F04 /* StatusAttachmentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusAttachmentView.swift; sourceTree = "<group>"; };
|
D0BEB1F224F8EE8C001B0F04 /* AttachmentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentView.swift; sourceTree = "<group>"; };
|
||||||
D0BEB1F624F9A84B001B0F04 /* LoadingTableFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingTableFooterView.swift; sourceTree = "<group>"; };
|
D0BEB1F624F9A84B001B0F04 /* LoadingTableFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingTableFooterView.swift; sourceTree = "<group>"; };
|
||||||
D0BEB1FE24F9E5BB001B0F04 /* ListsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListsView.swift; sourceTree = "<group>"; };
|
D0BEB1FE24F9E5BB001B0F04 /* ListsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListsView.swift; sourceTree = "<group>"; };
|
||||||
D0BEB20424FA1107001B0F04 /* FiltersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FiltersView.swift; sourceTree = "<group>"; };
|
D0BEB20424FA1107001B0F04 /* FiltersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FiltersView.swift; sourceTree = "<group>"; };
|
||||||
|
@ -366,8 +358,6 @@
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
D0EA593F2522AC8700804347 /* CardView.swift */,
|
D0EA593F2522AC8700804347 /* CardView.swift */,
|
||||||
D01F41E224F8889700D55A2D /* StatusAttachmentsView.swift */,
|
|
||||||
D0BEB1F224F8EE8C001B0F04 /* StatusAttachmentView.swift */,
|
|
||||||
D036AA16254CA823009094DF /* StatusBodyView.swift */,
|
D036AA16254CA823009094DF /* StatusBodyView.swift */,
|
||||||
D0625E5C250F0B5C00502611 /* StatusContentConfiguration.swift */,
|
D0625E5C250F0B5C00502611 /* StatusContentConfiguration.swift */,
|
||||||
D0625E58250F092900502611 /* StatusListCell.swift */,
|
D0625E58250F092900502611 /* StatusListCell.swift */,
|
||||||
|
@ -419,7 +409,6 @@
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
D0A1F4F6252E7D4B004435BF /* TableViewDataSource.swift */,
|
D0A1F4F6252E7D4B004435BF /* TableViewDataSource.swift */,
|
||||||
D065965A25899DAE0096AC5D /* CompositionAttachmentsDataSource.swift */,
|
|
||||||
);
|
);
|
||||||
path = "Data Sources";
|
path = "Data Sources";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -442,10 +431,9 @@
|
||||||
D0F0B125251A90F400942152 /* AccountListCell.swift */,
|
D0F0B125251A90F400942152 /* AccountListCell.swift */,
|
||||||
D0F0B10D251A868200942152 /* AccountView.swift */,
|
D0F0B10D251A868200942152 /* AccountView.swift */,
|
||||||
D0C7D42424F76169001EBDBB /* AddIdentityView.swift */,
|
D0C7D42424F76169001EBDBB /* AddIdentityView.swift */,
|
||||||
|
D01F41E224F8889700D55A2D /* AttachmentsView.swift */,
|
||||||
D0CE9F86258B076900E3A6B6 /* AttachmentUploadView.swift */,
|
D0CE9F86258B076900E3A6B6 /* AttachmentUploadView.swift */,
|
||||||
D065966025899E890096AC5D /* CompositionAttachmentCollectionViewCell.swift */,
|
D0BEB1F224F8EE8C001B0F04 /* AttachmentView.swift */,
|
||||||
D080413D258D904400AD6139 /* CompositionAttachmentContentConfiguration.swift */,
|
|
||||||
D0804132258D902900AD6139 /* CompositionAttachmentView.swift */,
|
|
||||||
D0E9F9A9258450B300EF503D /* CompositionInputAccessoryView.swift */,
|
D0E9F9A9258450B300EF503D /* CompositionInputAccessoryView.swift */,
|
||||||
D08E52ED257D757100FA2C5F /* CompositionView.swift */,
|
D08E52ED257D757100FA2C5F /* CompositionView.swift */,
|
||||||
D007023D25562A2800F38136 /* ConversationAvatarsView.swift */,
|
D007023D25562A2800F38136 /* ConversationAvatarsView.swift */,
|
||||||
|
@ -767,7 +755,6 @@
|
||||||
D0C7D49C24F7616A001EBDBB /* RootView.swift in Sources */,
|
D0C7D49C24F7616A001EBDBB /* RootView.swift in Sources */,
|
||||||
D0F0B126251A90F400942152 /* AccountListCell.swift in Sources */,
|
D0F0B126251A90F400942152 /* AccountListCell.swift in Sources */,
|
||||||
D0B32F50250B373600311912 /* RegistrationView.swift in Sources */,
|
D0B32F50250B373600311912 /* RegistrationView.swift in Sources */,
|
||||||
D0804133258D902900AD6139 /* CompositionAttachmentView.swift in Sources */,
|
|
||||||
D08B8D612540DE3B00B1EBEF /* ZoomDismissalInteractionController.swift in Sources */,
|
D08B8D612540DE3B00B1EBEF /* ZoomDismissalInteractionController.swift in Sources */,
|
||||||
D036AA07254B6118009094DF /* NotificationView.swift in Sources */,
|
D036AA07254B6118009094DF /* NotificationView.swift in Sources */,
|
||||||
D08E52EE257D757100FA2C5F /* CompositionView.swift in Sources */,
|
D08E52EE257D757100FA2C5F /* CompositionView.swift in Sources */,
|
||||||
|
@ -777,7 +764,7 @@
|
||||||
D007023E25562A2800F38136 /* ConversationAvatarsView.swift in Sources */,
|
D007023E25562A2800F38136 /* ConversationAvatarsView.swift in Sources */,
|
||||||
D0E7AD3925870B13005F5E2D /* UIVIewController+Extensions.swift in Sources */,
|
D0E7AD3925870B13005F5E2D /* UIVIewController+Extensions.swift in Sources */,
|
||||||
D0625E5D250F0B5C00502611 /* StatusContentConfiguration.swift in Sources */,
|
D0625E5D250F0B5C00502611 /* StatusContentConfiguration.swift in Sources */,
|
||||||
D0BEB1F324F8EE8C001B0F04 /* StatusAttachmentView.swift in Sources */,
|
D0BEB1F324F8EE8C001B0F04 /* AttachmentView.swift in Sources */,
|
||||||
D0C7D49A24F7616A001EBDBB /* TableView.swift in Sources */,
|
D0C7D49A24F7616A001EBDBB /* TableView.swift in Sources */,
|
||||||
D08B8D622540DE3B00B1EBEF /* ZoomTransitionController.swift in Sources */,
|
D08B8D622540DE3B00B1EBEF /* ZoomTransitionController.swift in Sources */,
|
||||||
D0F0B12E251A97E400942152 /* TableViewController.swift in Sources */,
|
D0F0B12E251A97E400942152 /* TableViewController.swift in Sources */,
|
||||||
|
@ -791,7 +778,6 @@
|
||||||
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 */,
|
D0849C7F25903C4900A5EBCC /* Status+Extensions.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 */,
|
||||||
|
@ -818,16 +804,14 @@
|
||||||
D036AA17254CA824009094DF /* StatusBodyView.swift in Sources */,
|
D036AA17254CA824009094DF /* StatusBodyView.swift in Sources */,
|
||||||
D08E512125786A6600FA2C5F /* UIButton+Extensions.swift in Sources */,
|
D08E512125786A6600FA2C5F /* UIButton+Extensions.swift in Sources */,
|
||||||
D0EA59482522B8B600804347 /* ViewConstants.swift in Sources */,
|
D0EA59482522B8B600804347 /* ViewConstants.swift in Sources */,
|
||||||
D065965B25899DAE0096AC5D /* CompositionAttachmentsDataSource.swift in Sources */,
|
|
||||||
D04226FD2546AC0B000980A3 /* StartupAndSyncingPreferencesView.swift in Sources */,
|
D04226FD2546AC0B000980A3 /* StartupAndSyncingPreferencesView.swift in Sources */,
|
||||||
D036AA0C254B612B009094DF /* NotificationContentConfiguration.swift in Sources */,
|
D036AA0C254B612B009094DF /* NotificationContentConfiguration.swift in Sources */,
|
||||||
D0C7D49824F7616A001EBDBB /* CustomEmojiText.swift in Sources */,
|
D0C7D49824F7616A001EBDBB /* CustomEmojiText.swift in Sources */,
|
||||||
D08B8D3D253F929E00B1EBEF /* ImageViewController.swift in Sources */,
|
D08B8D3D253F929E00B1EBEF /* ImageViewController.swift in Sources */,
|
||||||
D0B8510C25259E56004E0744 /* LoadMoreCell.swift in Sources */,
|
D0B8510C25259E56004E0744 /* LoadMoreCell.swift in Sources */,
|
||||||
D08E52612579D2E100FA2C5F /* DomainBlocksView.swift in Sources */,
|
D08E52612579D2E100FA2C5F /* DomainBlocksView.swift in Sources */,
|
||||||
D01F41E424F8889700D55A2D /* StatusAttachmentsView.swift in Sources */,
|
D01F41E424F8889700D55A2D /* AttachmentsView.swift in Sources */,
|
||||||
D00702312555F4AE00F38136 /* ConversationView.swift in Sources */,
|
D00702312555F4AE00F38136 /* ConversationView.swift in Sources */,
|
||||||
D065966125899E890096AC5D /* CompositionAttachmentCollectionViewCell.swift in Sources */,
|
|
||||||
D0BEB21124FA2A91001B0F04 /* EditFilterView.swift in Sources */,
|
D0BEB21124FA2A91001B0F04 /* EditFilterView.swift in Sources */,
|
||||||
D08B8D4A253FC36500B1EBEF /* ImageNavigationController.swift in Sources */,
|
D08B8D4A253FC36500B1EBEF /* ImageNavigationController.swift in Sources */,
|
||||||
D0070252255921B100F38136 /* AccountFieldView.swift in Sources */,
|
D0070252255921B100F38136 /* AccountFieldView.swift in Sources */,
|
||||||
|
@ -866,16 +850,16 @@
|
||||||
D038273C259EA38F00056E0F /* NewStatusView.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 */,
|
||||||
D080413F258D904400AD6139 /* CompositionAttachmentContentConfiguration.swift in Sources */,
|
|
||||||
D08E52C7257C7AEE00FA2C5F /* ShareErrorViewController.swift in Sources */,
|
D08E52C7257C7AEE00FA2C5F /* ShareErrorViewController.swift in Sources */,
|
||||||
|
D015B14425A812F6006D88A8 /* PlayerCache.swift in Sources */,
|
||||||
|
D015B13F25A812EC006D88A8 /* PlayerView.swift in Sources */,
|
||||||
|
D015B13A25A812E6006D88A8 /* AttachmentView.swift in Sources */,
|
||||||
D08E52F8257D78BE00FA2C5F /* ViewConstants.swift in Sources */,
|
D08E52F8257D78BE00FA2C5F /* ViewConstants.swift in Sources */,
|
||||||
D036EBC2259FE2AD00EC1CFC /* UIVIewController+Extensions.swift in Sources */,
|
D036EBC2259FE2AD00EC1CFC /* UIVIewController+Extensions.swift in Sources */,
|
||||||
D065966725899E910096AC5D /* CompositionAttachmentsDataSource.swift in Sources */,
|
D015B13525A812DD006D88A8 /* AttachmentsView.swift in Sources */,
|
||||||
D0FCC106259C4E62000B67DF /* NewStatusViewController.swift in Sources */,
|
D0FCC106259C4E62000B67DF /* NewStatusViewController.swift in Sources */,
|
||||||
D036EBBD259FE2A100EC1CFC /* Array+Extensions.swift in Sources */,
|
D036EBBD259FE2A100EC1CFC /* Array+Extensions.swift in Sources */,
|
||||||
D036EBB3259FE28800EC1CFC /* UIColor+Extensions.swift in Sources */,
|
D036EBB3259FE28800EC1CFC /* UIColor+Extensions.swift in Sources */,
|
||||||
D0804134258D902900AD6139 /* CompositionAttachmentView.swift in Sources */,
|
|
||||||
D065966225899E890096AC5D /* CompositionAttachmentCollectionViewCell.swift in Sources */,
|
|
||||||
D036EBB8259FE29800EC1CFC /* Status+Extensions.swift in Sources */,
|
D036EBB8259FE29800EC1CFC /* Status+Extensions.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
|
|
@ -6,7 +6,6 @@ import SafariServices
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import ViewModels
|
import ViewModels
|
||||||
|
|
||||||
// swiftlint:disable file_length
|
|
||||||
class TableViewController: UITableViewController {
|
class TableViewController: UITableViewController {
|
||||||
var transitionViewTag = -1
|
var transitionViewTag = -1
|
||||||
|
|
||||||
|
@ -16,6 +15,7 @@ class TableViewController: UITableViewController {
|
||||||
private let webfingerIndicatorView = WebfingerIndicatorView()
|
private let webfingerIndicatorView = WebfingerIndicatorView()
|
||||||
private var cancellables = Set<AnyCancellable>()
|
private var cancellables = Set<AnyCancellable>()
|
||||||
private var cellHeightCaches = [CGFloat: [CollectionItem: CGFloat]]()
|
private var cellHeightCaches = [CGFloat: [CollectionItem: CGFloat]]()
|
||||||
|
private var shouldKeepPlayingVideoAfterDismissal = false
|
||||||
|
|
||||||
private lazy var dataSource: TableViewDataSource = {
|
private lazy var dataSource: TableViewDataSource = {
|
||||||
.init(tableView: tableView, viewModelProvider: viewModel.viewModel(indexPath:))
|
.init(tableView: tableView, viewModelProvider: viewModel.viewModel(indexPath:))
|
||||||
|
@ -59,18 +59,6 @@ class TableViewController: UITableViewController {
|
||||||
viewModel.request(maxId: nil, minId: nil)
|
viewModel.request(maxId: nil, minId: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewDidAppear(_ animated: Bool) {
|
|
||||||
super.viewDidAppear(animated)
|
|
||||||
|
|
||||||
updateAutoplayViews()
|
|
||||||
}
|
|
||||||
|
|
||||||
override func viewDidDisappear(_ animated: Bool) {
|
|
||||||
super.viewDidDisappear(animated)
|
|
||||||
|
|
||||||
updateAutoplayViews()
|
|
||||||
}
|
|
||||||
|
|
||||||
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||||
guard scrollView.isDragging else { return }
|
guard scrollView.isDragging else { return }
|
||||||
|
|
||||||
|
@ -79,8 +67,6 @@ class TableViewController: UITableViewController {
|
||||||
for loadMoreView in visibleLoadMoreViews {
|
for loadMoreView in visibleLoadMoreViews {
|
||||||
loadMoreView.directionChanged(up: up)
|
loadMoreView.directionChanged(up: up)
|
||||||
}
|
}
|
||||||
|
|
||||||
updateAutoplayViews()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
|
override func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
|
||||||
|
@ -124,10 +110,6 @@ class TableViewController: UITableViewController {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension TableViewController {
|
extension TableViewController {
|
||||||
static let autoplayableAttachmentsView = PassthroughSubject<StatusAttachmentsView?, Never>()
|
|
||||||
static let autoplayableAttachmentsViewNotification =
|
|
||||||
Notification.Name("com.metabolist.metatext.attachment-view-became-autoplayable")
|
|
||||||
|
|
||||||
func report(viewModel: ReportViewModel) {
|
func report(viewModel: ReportViewModel) {
|
||||||
let reportViewController = ReportViewController(viewModel: viewModel)
|
let reportViewController = ReportViewController(viewModel: viewModel)
|
||||||
let navigationController = UINavigationController(rootViewController: reportViewController)
|
let navigationController = UINavigationController(rootViewController: reportViewController)
|
||||||
|
@ -188,7 +170,12 @@ extension TableViewController: AVPlayerViewControllerDelegate {
|
||||||
willEndFullScreenPresentationWithAnimationCoordinator coordinator: UIViewControllerTransitionCoordinator) {
|
willEndFullScreenPresentationWithAnimationCoordinator coordinator: UIViewControllerTransitionCoordinator) {
|
||||||
try? AVAudioSession.sharedInstance().setCategory(.ambient, mode: .default)
|
try? AVAudioSession.sharedInstance().setCategory(.ambient, mode: .default)
|
||||||
playerViewController.player?.isMuted = true
|
playerViewController.player?.isMuted = true
|
||||||
updateAutoplayViews()
|
|
||||||
|
coordinator.animate(alongsideTransition: nil) { _ in
|
||||||
|
if self.shouldKeepPlayingVideoAfterDismissal {
|
||||||
|
playerViewController.player?.play()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -267,17 +254,6 @@ private extension TableViewController {
|
||||||
.compactMap { [weak self] _ in self?.tableView.indexPathsForVisibleRows?.first }
|
.compactMap { [weak self] _ in self?.tableView.indexPathsForVisibleRows?.first }
|
||||||
.sink { [weak self] in self?.viewModel.viewedAtTop(indexPath: $0) }
|
.sink { [weak self] in self?.viewModel.viewedAtTop(indexPath: $0) }
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
Self.autoplayableAttachmentsView
|
|
||||||
.removeDuplicates()
|
|
||||||
.sink {
|
|
||||||
let notification = Notification(
|
|
||||||
name: Self.autoplayableAttachmentsViewNotification,
|
|
||||||
object: $0,
|
|
||||||
userInfo: nil)
|
|
||||||
NotificationCenter.default.post(notification)
|
|
||||||
}
|
|
||||||
.store(in: &cancellables)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(_ update: CollectionUpdate) {
|
func update(_ update: CollectionUpdate) {
|
||||||
|
@ -313,8 +289,6 @@ private extension TableViewController {
|
||||||
self.tableView.contentOffset.y -= offsetFromNavigationBar
|
self.tableView.contentOffset.y -= offsetFromNavigationBar
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.updateAutoplayViews()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -369,6 +343,8 @@ private extension TableViewController {
|
||||||
playerViewController.delegate = self
|
playerViewController.delegate = self
|
||||||
playerViewController.player = player
|
playerViewController.player = player
|
||||||
|
|
||||||
|
shouldKeepPlayingVideoAfterDismissal = attachmentViewModel.shouldAutoplay
|
||||||
|
|
||||||
present(playerViewController, animated: true) {
|
present(playerViewController, animated: true) {
|
||||||
try? AVAudioSession.sharedInstance().setCategory(.playback, mode: .default)
|
try? AVAudioSession.sharedInstance().setCategory(.playback, mode: .default)
|
||||||
player.isMuted = false
|
player.isMuted = false
|
||||||
|
@ -411,22 +387,4 @@ private extension TableViewController {
|
||||||
|
|
||||||
present(activityViewController, animated: true, completion: nil)
|
present(activityViewController, animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateAutoplayViews() {
|
|
||||||
if let visibleView = navigationController?.visibleViewController?.view,
|
|
||||||
view.isDescendant(of: visibleView),
|
|
||||||
let superview = view.superview,
|
|
||||||
let attachmentsViewClosestToCenter = tableView.visibleCells
|
|
||||||
.compactMap({ ($0.contentView as? StatusView)?.bodyView.attachmentsView })
|
|
||||||
.filter(\.shouldAutoplay)
|
|
||||||
.min(by: {
|
|
||||||
abs(superview.convert($0.frame, from: $0.superview).midY - view.frame.midY)
|
|
||||||
< abs(superview.convert($1.frame, from: $1.superview).midY - view.frame.midY)
|
|
||||||
}) {
|
|
||||||
Self.autoplayableAttachmentsView.send(attachmentsViewClosestToCenter)
|
|
||||||
} else {
|
|
||||||
Self.autoplayableAttachmentsView.send(nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// swiftlint:enable file_length
|
|
||||||
|
|
|
@ -4,22 +4,22 @@ import Foundation
|
||||||
import Mastodon
|
import Mastodon
|
||||||
import Network
|
import Network
|
||||||
|
|
||||||
public struct AttachmentViewModel {
|
public final class AttachmentViewModel: ObservableObject {
|
||||||
public let attachment: Attachment
|
public let attachment: Attachment
|
||||||
|
|
||||||
private let status: Status
|
|
||||||
private let identification: Identification
|
private let identification: Identification
|
||||||
|
private let status: Status?
|
||||||
|
|
||||||
init(attachment: Attachment, status: Status, identification: Identification) {
|
init(attachment: Attachment, identification: Identification, status: Status? = nil) {
|
||||||
self.attachment = attachment
|
self.attachment = attachment
|
||||||
self.status = status
|
|
||||||
self.identification = identification
|
self.identification = identification
|
||||||
|
self.status = status
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public extension AttachmentViewModel {
|
public extension AttachmentViewModel {
|
||||||
var tag: Int {
|
var tag: Int {
|
||||||
attachment.id.appending(status.id).hashValue
|
attachment.id.appending(status?.id ?? "").hashValue
|
||||||
}
|
}
|
||||||
|
|
||||||
var shouldAutoplay: Bool {
|
var shouldAutoplay: Bool {
|
||||||
|
@ -38,5 +38,11 @@ public extension AttachmentViewModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension AttachmentViewModel {
|
private extension AttachmentViewModel {
|
||||||
static let wifiMonitor = NWPathMonitor(requiredInterfaceType: .wifi)
|
static var wifiMonitor: NWPathMonitor = {
|
||||||
|
let monitor = NWPathMonitor(requiredInterfaceType: .wifi)
|
||||||
|
|
||||||
|
monitor.start(queue: .main)
|
||||||
|
|
||||||
|
return monitor
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public protocol AttachmentsRenderingViewModel {
|
||||||
|
var attachmentViewModels: [AttachmentViewModel] { get }
|
||||||
|
var shouldShowAttachments: Bool { get }
|
||||||
|
var shouldShowHideAttachmentsButton: Bool { get }
|
||||||
|
var sensitive: Bool { get }
|
||||||
|
var canRemoveAttachments: Bool { get }
|
||||||
|
func attachmentSelected(viewModel: AttachmentViewModel)
|
||||||
|
func removeAttachment(viewModel: AttachmentViewModel)
|
||||||
|
func toggleShowAttachments()
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension AttachmentsRenderingViewModel {
|
||||||
|
var shouldShowAttachments: Bool { true }
|
||||||
|
var shouldShowHideAttachmentsButton: Bool { false }
|
||||||
|
var sensitive: Bool { false }
|
||||||
|
var canRemoveAttachments: Bool { false }
|
||||||
|
func removeAttachment(viewModel: AttachmentViewModel) {}
|
||||||
|
func toggleShowAttachments() {}
|
||||||
|
}
|
|
@ -1,14 +0,0 @@
|
||||||
// Copyright © 2020 Metabolist. All rights reserved.
|
|
||||||
|
|
||||||
import Combine
|
|
||||||
import Foundation
|
|
||||||
import Mastodon
|
|
||||||
import ServiceLayer
|
|
||||||
|
|
||||||
public final class CompositionAttachmentViewModel: ObservableObject {
|
|
||||||
public let attachment: Attachment
|
|
||||||
|
|
||||||
init(attachment: Attachment) {
|
|
||||||
self.attachment = attachment
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -5,18 +5,19 @@ import Foundation
|
||||||
import Mastodon
|
import Mastodon
|
||||||
import ServiceLayer
|
import ServiceLayer
|
||||||
|
|
||||||
public final class CompositionViewModel: ObservableObject, Identifiable {
|
public final class CompositionViewModel: AttachmentsRenderingViewModel, 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 contentWarning = ""
|
||||||
@Published public var displayContentWarning = false
|
@Published public var displayContentWarning = false
|
||||||
@Published public private(set) var attachmentViewModels = [CompositionAttachmentViewModel]()
|
@Published public private(set) var attachmentViewModels = [AttachmentViewModel]()
|
||||||
@Published public private(set) var attachmentUpload: AttachmentUpload?
|
@Published public private(set) var attachmentUpload: AttachmentUpload?
|
||||||
@Published public private(set) var isPostable = false
|
@Published public private(set) var isPostable = false
|
||||||
@Published public private(set) var canAddAttachment = true
|
@Published public private(set) var canAddAttachment = true
|
||||||
@Published public private(set) var canAddNonImageAttachment = true
|
@Published public private(set) var canAddNonImageAttachment = true
|
||||||
@Published public private(set) var remainingCharacters = CompositionViewModel.maxCharacters
|
@Published public private(set) var remainingCharacters = CompositionViewModel.maxCharacters
|
||||||
|
public let canRemoveAttachments = true
|
||||||
|
|
||||||
private var attachmentUploadCancellable: AnyCancellable?
|
private var attachmentUploadCancellable: AnyCancellable?
|
||||||
|
|
||||||
|
@ -42,6 +43,14 @@ public final class CompositionViewModel: ObservableObject, Identifiable {
|
||||||
.map { Self.maxCharacters - ($0 + ($1 ? $2.count : 0)) }
|
.map { Self.maxCharacters - ($0 + ($1 ? $2.count : 0)) }
|
||||||
.assign(to: &$remainingCharacters)
|
.assign(to: &$remainingCharacters)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func attachmentSelected(viewModel: AttachmentViewModel) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public func removeAttachment(viewModel: AttachmentViewModel) {
|
||||||
|
attachmentViewModels.removeAll { $0 === viewModel }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public extension CompositionViewModel {
|
public extension CompositionViewModel {
|
||||||
|
@ -64,10 +73,6 @@ public extension CompositionViewModel {
|
||||||
visibility: visibility)
|
visibility: visibility)
|
||||||
}
|
}
|
||||||
|
|
||||||
func remove(attachmentViewModel: CompositionAttachmentViewModel) {
|
|
||||||
attachmentViewModels.removeAll { $0 === attachmentViewModel }
|
|
||||||
}
|
|
||||||
|
|
||||||
func cancelUpload() {
|
func cancelUpload() {
|
||||||
attachmentUploadCancellable?.cancel()
|
attachmentUploadCancellable?.cancel()
|
||||||
}
|
}
|
||||||
|
@ -96,7 +101,10 @@ extension CompositionViewModel {
|
||||||
.sink { [weak self] _ in
|
.sink { [weak self] _ in
|
||||||
self?.attachmentUpload = nil
|
self?.attachmentUpload = nil
|
||||||
} receiveValue: { [weak self] in
|
} receiveValue: { [weak self] in
|
||||||
self?.attachmentViewModels.append(CompositionAttachmentViewModel(attachment: $0))
|
self?.attachmentViewModels.append(
|
||||||
|
AttachmentViewModel(
|
||||||
|
attachment: $0,
|
||||||
|
identification: parentViewModel.identification))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ import Foundation
|
||||||
import Mastodon
|
import Mastodon
|
||||||
import ServiceLayer
|
import ServiceLayer
|
||||||
|
|
||||||
public final class StatusViewModel: CollectionItemViewModel, ObservableObject {
|
public final class StatusViewModel: CollectionItemViewModel, AttachmentsRenderingViewModel, ObservableObject {
|
||||||
public let content: NSAttributedString
|
public let content: NSAttributedString
|
||||||
public let contentEmoji: [Emoji]
|
public let contentEmoji: [Emoji]
|
||||||
public let displayName: String
|
public let displayName: String
|
||||||
|
@ -40,7 +40,7 @@ public final class StatusViewModel: CollectionItemViewModel, ObservableObject {
|
||||||
: statusService.status.account.displayName
|
: statusService.status.account.displayName
|
||||||
rebloggedByDisplayNameEmoji = statusService.status.account.emojis
|
rebloggedByDisplayNameEmoji = statusService.status.account.emojis
|
||||||
attachmentViewModels = statusService.status.displayStatus.mediaAttachments
|
attachmentViewModels = statusService.status.displayStatus.mediaAttachments
|
||||||
.map { AttachmentViewModel(attachment: $0, status: statusService.status, identification: identification) }
|
.map { AttachmentViewModel(attachment: $0, identification: identification, status: statusService.status) }
|
||||||
pollEmoji = statusService.status.displayStatus.poll?.emojis ?? []
|
pollEmoji = statusService.status.displayStatus.poll?.emojis ?? []
|
||||||
events = eventsSubject.eraseToAnyPublisher()
|
events = eventsSubject.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,10 +5,11 @@ import Kingfisher
|
||||||
import UIKit
|
import UIKit
|
||||||
import ViewModels
|
import ViewModels
|
||||||
|
|
||||||
final class StatusAttachmentView: UIView {
|
final class AttachmentView: UIView {
|
||||||
let playerView = PlayerView()
|
let playerView = PlayerView()
|
||||||
let imageView = AnimatedImageView()
|
let imageView = AnimatedImageView()
|
||||||
let button = UIButton()
|
let removeButton = UIButton(type: .close)
|
||||||
|
let selectionButton = UIButton()
|
||||||
|
|
||||||
var playing: Bool = false {
|
var playing: Bool = false {
|
||||||
didSet {
|
didSet {
|
||||||
|
@ -25,10 +26,12 @@ final class StatusAttachmentView: UIView {
|
||||||
}
|
}
|
||||||
|
|
||||||
private let viewModel: AttachmentViewModel
|
private let viewModel: AttachmentViewModel
|
||||||
|
private let parentViewModel: AttachmentsRenderingViewModel
|
||||||
private var playerLooper: AVPlayerLooper?
|
private var playerLooper: AVPlayerLooper?
|
||||||
|
|
||||||
init(viewModel: AttachmentViewModel) {
|
init(viewModel: AttachmentViewModel, parentViewModel: AttachmentsRenderingViewModel) {
|
||||||
self.viewModel = viewModel
|
self.viewModel = viewModel
|
||||||
|
self.parentViewModel = parentViewModel
|
||||||
|
|
||||||
super.init(frame: .zero)
|
super.init(frame: .zero)
|
||||||
|
|
||||||
|
@ -72,7 +75,7 @@ final class StatusAttachmentView: UIView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension StatusAttachmentView {
|
extension AttachmentView {
|
||||||
func play() {
|
func play() {
|
||||||
let player = PlayerCache.shared.player(url: viewModel.attachment.url)
|
let player = PlayerCache.shared.player(url: viewModel.attachment.url)
|
||||||
|
|
||||||
|
@ -105,7 +108,7 @@ extension StatusAttachmentView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension StatusAttachmentView {
|
private extension AttachmentView {
|
||||||
static var playerLooperCache = [AVQueuePlayer: AVPlayerLooper]()
|
static var playerLooperCache = [AVQueuePlayer: AVPlayerLooper]()
|
||||||
|
|
||||||
// swiftlint:disable:next function_body_length
|
// swiftlint:disable:next function_body_length
|
||||||
|
@ -139,9 +142,30 @@ private extension StatusAttachmentView {
|
||||||
playerView.videoGravity = .resizeAspectFill
|
playerView.videoGravity = .resizeAspectFill
|
||||||
playerView.isHidden = true
|
playerView.isHidden = true
|
||||||
|
|
||||||
addSubview(button)
|
addSubview(selectionButton)
|
||||||
button.translatesAutoresizingMaskIntoConstraints = false
|
selectionButton.translatesAutoresizingMaskIntoConstraints = false
|
||||||
button.setBackgroundImage(.highlightedButtonBackground, for: .highlighted)
|
selectionButton.setBackgroundImage(.highlightedButtonBackground, for: .highlighted)
|
||||||
|
selectionButton.addAction(
|
||||||
|
UIAction { [weak self] _ in
|
||||||
|
guard let self = self else { return }
|
||||||
|
|
||||||
|
self.parentViewModel.attachmentSelected(viewModel: self.viewModel)
|
||||||
|
},
|
||||||
|
for: .touchUpInside)
|
||||||
|
|
||||||
|
addSubview(removeButton)
|
||||||
|
removeButton.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
removeButton.showsMenuAsPrimaryAction = true
|
||||||
|
removeButton.menu = UIMenu(
|
||||||
|
children: [
|
||||||
|
UIAction(
|
||||||
|
title: NSLocalizedString("remove", comment: ""),
|
||||||
|
image: UIImage(systemName: "trash"),
|
||||||
|
attributes: .destructive) { [weak self] _ in
|
||||||
|
guard let self = self else { return }
|
||||||
|
|
||||||
|
self.parentViewModel.removeAttachment(viewModel: self.viewModel)
|
||||||
|
}])
|
||||||
|
|
||||||
switch viewModel.attachment.type {
|
switch viewModel.attachment.type {
|
||||||
case .image, .video, .gifv:
|
case .image, .video, .gifv:
|
||||||
|
@ -183,10 +207,12 @@ private extension StatusAttachmentView {
|
||||||
equalTo: playView.topAnchor, constant: .compactSpacing),
|
equalTo: playView.topAnchor, constant: .compactSpacing),
|
||||||
playImageView.leadingAnchor.constraint(
|
playImageView.leadingAnchor.constraint(
|
||||||
equalTo: playView.leadingAnchor, constant: .compactSpacing),
|
equalTo: playView.leadingAnchor, constant: .compactSpacing),
|
||||||
button.leadingAnchor.constraint(equalTo: leadingAnchor),
|
selectionButton.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||||
button.trailingAnchor.constraint(equalTo: trailingAnchor),
|
selectionButton.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||||
button.topAnchor.constraint(equalTo: topAnchor),
|
selectionButton.topAnchor.constraint(equalTo: topAnchor),
|
||||||
button.bottomAnchor.constraint(equalTo: bottomAnchor)
|
selectionButton.bottomAnchor.constraint(equalTo: bottomAnchor),
|
||||||
|
removeButton.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor),
|
||||||
|
removeButton.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor)
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -4,7 +4,7 @@ import Combine
|
||||||
import UIKit
|
import UIKit
|
||||||
import ViewModels
|
import ViewModels
|
||||||
|
|
||||||
final class StatusAttachmentsView: UIView {
|
final class AttachmentsView: UIView {
|
||||||
private let containerStackView = UIStackView()
|
private let containerStackView = UIStackView()
|
||||||
private let leftStackView = UIStackView()
|
private let leftStackView = UIStackView()
|
||||||
private let rightStackView = UIStackView()
|
private let rightStackView = UIStackView()
|
||||||
|
@ -15,7 +15,7 @@ final class StatusAttachmentsView: UIView {
|
||||||
private var aspectRatioConstraint: NSLayoutConstraint?
|
private var aspectRatioConstraint: NSLayoutConstraint?
|
||||||
private var cancellables = Set<AnyCancellable>()
|
private var cancellables = Set<AnyCancellable>()
|
||||||
|
|
||||||
var viewModel: StatusViewModel? {
|
var viewModel: AttachmentsRenderingViewModel? {
|
||||||
didSet {
|
didSet {
|
||||||
for stackView in [leftStackView, rightStackView] {
|
for stackView in [leftStackView, rightStackView] {
|
||||||
for view in stackView.arrangedSubviews {
|
for view in stackView.arrangedSubviews {
|
||||||
|
@ -24,21 +24,18 @@ final class StatusAttachmentsView: UIView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let attachmentViewModels = viewModel?.attachmentViewModels ?? []
|
guard let viewModel = viewModel else { return }
|
||||||
let attachmentCount = attachmentViewModels.count
|
|
||||||
|
|
||||||
rightStackView.isHidden = attachmentCount == 1
|
rightStackView.isHidden = viewModel.attachmentViewModels.count == 1
|
||||||
|
|
||||||
for (index, attachmentViewModel) in attachmentViewModels.enumerated() {
|
for (index, attachmentViewModel) in viewModel.attachmentViewModels.enumerated() {
|
||||||
let attachmentView = StatusAttachmentView(viewModel: attachmentViewModel)
|
let attachmentView = AttachmentView(viewModel: attachmentViewModel, parentViewModel: viewModel)
|
||||||
|
attachmentView.playing = viewModel.shouldShowAttachments && attachmentViewModel.shouldAutoplay
|
||||||
|
attachmentView.removeButton.isHidden = !viewModel.canRemoveAttachments
|
||||||
|
|
||||||
attachmentView.button.addAction(
|
if viewModel.attachmentViewModels.count == 2 && index == 1
|
||||||
UIAction { [weak self] _ in self?.viewModel?.attachmentSelected(viewModel: attachmentViewModel) },
|
|| viewModel.attachmentViewModels.count == 3 && index != 0
|
||||||
for: .touchUpInside)
|
|| viewModel.attachmentViewModels.count > 3 && index % 2 != 0 {
|
||||||
|
|
||||||
if attachmentCount == 2 && index == 1
|
|
||||||
|| attachmentCount == 3 && index != 0
|
|
||||||
|| attachmentCount > 3 && index % 2 != 0 {
|
|
||||||
rightStackView.addArrangedSubview(attachmentView)
|
rightStackView.addArrangedSubview(attachmentView)
|
||||||
} else {
|
} else {
|
||||||
leftStackView.addArrangedSubview(attachmentView)
|
leftStackView.addArrangedSubview(attachmentView)
|
||||||
|
@ -47,7 +44,8 @@ final class StatusAttachmentsView: UIView {
|
||||||
|
|
||||||
let newAspectRatio: CGFloat
|
let newAspectRatio: CGFloat
|
||||||
|
|
||||||
if attachmentCount == 1, let aspectRatio = attachmentViewModels.first?.attachment.aspectRatio {
|
if viewModel.attachmentViewModels.count == 1,
|
||||||
|
let aspectRatio = viewModel.attachmentViewModels.first?.attachment.aspectRatio {
|
||||||
newAspectRatio = max(CGFloat(aspectRatio), 16 / 9)
|
newAspectRatio = max(CGFloat(aspectRatio), 16 / 9)
|
||||||
} else {
|
} else {
|
||||||
newAspectRatio = 16 / 9
|
newAspectRatio = 16 / 9
|
||||||
|
@ -58,14 +56,14 @@ final class StatusAttachmentsView: UIView {
|
||||||
aspectRatioConstraint?.priority = .justBelowMax
|
aspectRatioConstraint?.priority = .justBelowMax
|
||||||
aspectRatioConstraint?.isActive = true
|
aspectRatioConstraint?.isActive = true
|
||||||
|
|
||||||
curtain.isHidden = viewModel?.shouldShowAttachments ?? false
|
curtain.isHidden = viewModel.shouldShowAttachments
|
||||||
curtainButton.setTitle(
|
curtainButton.setTitle(
|
||||||
NSLocalizedString((viewModel?.sensitive ?? false)
|
NSLocalizedString((viewModel.sensitive)
|
||||||
? "attachment.sensitive-content"
|
? "attachment.sensitive-content"
|
||||||
: "attachment.media-hidden",
|
: "attachment.media-hidden",
|
||||||
comment: ""),
|
comment: ""),
|
||||||
for: .normal)
|
for: .normal)
|
||||||
hideButtonBackground.isHidden = !(viewModel?.shouldShowHideAttachmentsButton ?? false)
|
hideButtonBackground.isHidden = !viewModel.shouldShowHideAttachmentsButton
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,7 +79,7 @@ final class StatusAttachmentsView: UIView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension StatusAttachmentsView {
|
extension AttachmentsView {
|
||||||
var shouldAutoplay: Bool {
|
var shouldAutoplay: Bool {
|
||||||
guard !isHidden, let viewModel = viewModel, viewModel.shouldShowAttachments else { return false }
|
guard !isHidden, let viewModel = viewModel, viewModel.shouldShowAttachments else { return false }
|
||||||
|
|
||||||
|
@ -89,7 +87,7 @@ extension StatusAttachmentsView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension StatusAttachmentsView {
|
private extension AttachmentsView {
|
||||||
// swiftlint:disable:next function_body_length
|
// swiftlint:disable:next function_body_length
|
||||||
func initialSetup() {
|
func initialSetup() {
|
||||||
backgroundColor = .clear
|
backgroundColor = .clear
|
||||||
|
@ -161,20 +159,10 @@ private extension StatusAttachmentsView {
|
||||||
curtainButton.trailingAnchor.constraint(equalTo: curtain.contentView.trailingAnchor),
|
curtainButton.trailingAnchor.constraint(equalTo: curtain.contentView.trailingAnchor),
|
||||||
curtainButton.bottomAnchor.constraint(equalTo: curtain.contentView.bottomAnchor)
|
curtainButton.bottomAnchor.constraint(equalTo: curtain.contentView.bottomAnchor)
|
||||||
])
|
])
|
||||||
|
|
||||||
NotificationCenter.default.publisher(for: TableViewController.autoplayableAttachmentsViewNotification)
|
|
||||||
.sink { [weak self] in
|
|
||||||
guard let self = self else { return }
|
|
||||||
|
|
||||||
for attachmentView in self.attachmentViews {
|
|
||||||
attachmentView.playing = $0.object as? Self === self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.store(in: &cancellables)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var attachmentViews: [StatusAttachmentView] {
|
var attachmentViews: [AttachmentView] {
|
||||||
(leftStackView.arrangedSubviews + rightStackView.arrangedSubviews)
|
(leftStackView.arrangedSubviews + rightStackView.arrangedSubviews)
|
||||||
.compactMap { $0 as? StatusAttachmentView }
|
.compactMap { $0 as? AttachmentView }
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,19 +0,0 @@
|
||||||
// Copyright © 2020 Metabolist. All rights reserved.
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
import ViewModels
|
|
||||||
|
|
||||||
class CompositionAttachmentCollectionViewCell: UICollectionViewCell {
|
|
||||||
var viewModel: CompositionAttachmentViewModel?
|
|
||||||
var parentViewModel: CompositionViewModel?
|
|
||||||
|
|
||||||
override func updateConfiguration(using state: UICellConfigurationState) {
|
|
||||||
guard let viewModel = viewModel, let parentViewModel = parentViewModel else { return }
|
|
||||||
|
|
||||||
contentConfiguration = CompositionAttachmentContentConfiguration(
|
|
||||||
viewModel: viewModel,
|
|
||||||
parentViewModel: parentViewModel)
|
|
||||||
.updated(for: state)
|
|
||||||
backgroundConfiguration = UIBackgroundConfiguration.clear().updated(for: state)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
// Copyright © 2020 Metabolist. All rights reserved.
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
import ViewModels
|
|
||||||
|
|
||||||
struct CompositionAttachmentContentConfiguration {
|
|
||||||
let viewModel: CompositionAttachmentViewModel
|
|
||||||
let parentViewModel: CompositionViewModel
|
|
||||||
}
|
|
||||||
|
|
||||||
extension CompositionAttachmentContentConfiguration: UIContentConfiguration {
|
|
||||||
func makeContentView() -> UIView & UIContentView {
|
|
||||||
CompositionAttachmentView(configuration: self)
|
|
||||||
}
|
|
||||||
|
|
||||||
func updated(for state: UIConfigurationState) -> CompositionAttachmentContentConfiguration {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,112 +0,0 @@
|
||||||
// Copyright © 2020 Metabolist. All rights reserved.
|
|
||||||
|
|
||||||
import Kingfisher
|
|
||||||
import UIKit
|
|
||||||
import ViewModels
|
|
||||||
|
|
||||||
class CompositionAttachmentView: UIView {
|
|
||||||
let imageView = UIImageView()
|
|
||||||
let removeButton = UIButton()
|
|
||||||
let editButton = UIButton()
|
|
||||||
private var compositionAttachmentConfiguration: CompositionAttachmentContentConfiguration
|
|
||||||
private var aspectRatioConstraint: NSLayoutConstraint
|
|
||||||
|
|
||||||
init(configuration: CompositionAttachmentContentConfiguration) {
|
|
||||||
self.compositionAttachmentConfiguration = configuration
|
|
||||||
|
|
||||||
aspectRatioConstraint = imageView.widthAnchor.constraint(equalTo: imageView.heightAnchor, multiplier: 2)
|
|
||||||
|
|
||||||
super.init(frame: .zero)
|
|
||||||
|
|
||||||
initialSetup()
|
|
||||||
applyCompositionAttachmentConfiguration()
|
|
||||||
}
|
|
||||||
|
|
||||||
@available(*, unavailable)
|
|
||||||
required init?(coder: NSCoder) {
|
|
||||||
fatalError("init(coder:) has not been implemented")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension CompositionAttachmentView: UIContentView {
|
|
||||||
var configuration: UIContentConfiguration {
|
|
||||||
get { compositionAttachmentConfiguration }
|
|
||||||
set {
|
|
||||||
guard let compositionAttachmentConfiguration = newValue as? CompositionAttachmentContentConfiguration
|
|
||||||
else { return }
|
|
||||||
|
|
||||||
self.compositionAttachmentConfiguration = compositionAttachmentConfiguration
|
|
||||||
|
|
||||||
applyCompositionAttachmentConfiguration()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private extension CompositionAttachmentView {
|
|
||||||
// swiftlint:disable:next function_body_length
|
|
||||||
func initialSetup() {
|
|
||||||
backgroundColor = .secondarySystemBackground
|
|
||||||
layer.cornerRadius = .defaultCornerRadius
|
|
||||||
clipsToBounds = true
|
|
||||||
|
|
||||||
addSubview(imageView)
|
|
||||||
imageView.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
imageView.contentMode = .scaleAspectFill
|
|
||||||
imageView.kf.indicatorType = .activity
|
|
||||||
|
|
||||||
addSubview(removeButton)
|
|
||||||
removeButton.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
removeButton.setImage(
|
|
||||||
UIImage(
|
|
||||||
systemName: "xmark.circle.fill",
|
|
||||||
withConfiguration: UIImage.SymbolConfiguration(scale: .large)),
|
|
||||||
for: .normal)
|
|
||||||
removeButton.showsMenuAsPrimaryAction = true
|
|
||||||
removeButton.menu = UIMenu(
|
|
||||||
children: [
|
|
||||||
UIAction(
|
|
||||||
title: NSLocalizedString("remove", comment: ""),
|
|
||||||
image: UIImage(systemName: "trash"),
|
|
||||||
attributes: .destructive, handler: { [weak self] _ in
|
|
||||||
guard let self = self else { return }
|
|
||||||
|
|
||||||
self.compositionAttachmentConfiguration.parentViewModel.remove(
|
|
||||||
attachmentViewModel: self.compositionAttachmentConfiguration.viewModel)
|
|
||||||
})])
|
|
||||||
|
|
||||||
addSubview(editButton)
|
|
||||||
editButton.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
editButton.setImage(
|
|
||||||
UIImage(
|
|
||||||
systemName: "pencil.circle.fill",
|
|
||||||
withConfiguration: UIImage.SymbolConfiguration(scale: .large)),
|
|
||||||
for: .normal)
|
|
||||||
editButton.addAction(UIAction { [weak self] _ in }, for: .touchUpInside)
|
|
||||||
|
|
||||||
NSLayoutConstraint.activate([
|
|
||||||
aspectRatioConstraint,
|
|
||||||
imageView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
|
||||||
imageView.topAnchor.constraint(equalTo: topAnchor),
|
|
||||||
imageView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
|
||||||
imageView.bottomAnchor.constraint(equalTo: bottomAnchor),
|
|
||||||
removeButton.topAnchor.constraint(equalTo: topAnchor),
|
|
||||||
removeButton.trailingAnchor.constraint(equalTo: trailingAnchor),
|
|
||||||
removeButton.heightAnchor.constraint(equalToConstant: .minimumButtonDimension),
|
|
||||||
removeButton.widthAnchor.constraint(equalToConstant: .minimumButtonDimension),
|
|
||||||
editButton.trailingAnchor.constraint(equalTo: trailingAnchor),
|
|
||||||
editButton.bottomAnchor.constraint(equalTo: bottomAnchor),
|
|
||||||
editButton.heightAnchor.constraint(equalToConstant: .minimumButtonDimension),
|
|
||||||
editButton.widthAnchor.constraint(equalToConstant: .minimumButtonDimension)
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
func applyCompositionAttachmentConfiguration() {
|
|
||||||
imageView.kf.setImage(with: compositionAttachmentConfiguration.viewModel.attachment.previewUrl)
|
|
||||||
aspectRatioConstraint.isActive = false
|
|
||||||
aspectRatioConstraint = imageView.widthAnchor.constraint(
|
|
||||||
equalTo: imageView.heightAnchor,
|
|
||||||
multiplier: CGFloat(compositionAttachmentConfiguration.viewModel.attachment.aspectRatio ?? 1))
|
|
||||||
aspectRatioConstraint.priority = .justBelowMax
|
|
||||||
aspectRatioConstraint.isActive = true
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -10,46 +10,17 @@ final class CompositionView: UIView {
|
||||||
let spoilerTextField = UITextField()
|
let spoilerTextField = UITextField()
|
||||||
let textView = UITextView()
|
let textView = UITextView()
|
||||||
let textViewPlaceholder = UILabel()
|
let textViewPlaceholder = UILabel()
|
||||||
let attachmentsCollectionView: UICollectionView
|
let attachmentsView = AttachmentsView()
|
||||||
let attachmentUploadView: AttachmentUploadView
|
let attachmentUploadView: AttachmentUploadView
|
||||||
|
|
||||||
private let viewModel: CompositionViewModel
|
private let viewModel: CompositionViewModel
|
||||||
private let parentViewModel: NewStatusViewModel
|
private let parentViewModel: NewStatusViewModel
|
||||||
private var cancellables = Set<AnyCancellable>()
|
private var cancellables = Set<AnyCancellable>()
|
||||||
|
|
||||||
private lazy var attachmentsDataSource: CompositionAttachmentsDataSource = {
|
|
||||||
let vm = viewModel
|
|
||||||
|
|
||||||
return .init(collectionView: attachmentsCollectionView) {
|
|
||||||
(vm.attachmentViewModels[$0.item], vm)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
init(viewModel: CompositionViewModel, parentViewModel: NewStatusViewModel) {
|
init(viewModel: CompositionViewModel, parentViewModel: NewStatusViewModel) {
|
||||||
self.viewModel = viewModel
|
self.viewModel = viewModel
|
||||||
self.parentViewModel = parentViewModel
|
self.parentViewModel = parentViewModel
|
||||||
|
|
||||||
let itemSize = NSCollectionLayoutSize(
|
|
||||||
widthDimension: .estimated(Self.attachmentCollectionViewHeight),
|
|
||||||
heightDimension: .fractionalHeight(1))
|
|
||||||
let item = NSCollectionLayoutItem(layoutSize: itemSize)
|
|
||||||
let groupSize = NSCollectionLayoutSize(
|
|
||||||
widthDimension: .estimated(Self.attachmentCollectionViewHeight),
|
|
||||||
heightDimension: .fractionalHeight(1))
|
|
||||||
let group = NSCollectionLayoutGroup.horizontal(
|
|
||||||
layoutSize: groupSize,
|
|
||||||
subitems: [item])
|
|
||||||
let section = NSCollectionLayoutSection(group: group)
|
|
||||||
|
|
||||||
section.interGroupSpacing = .defaultSpacing
|
|
||||||
|
|
||||||
let configuration = UICollectionViewCompositionalLayoutConfiguration()
|
|
||||||
|
|
||||||
configuration.scrollDirection = .horizontal
|
|
||||||
|
|
||||||
let attachmentsLayout = UICollectionViewCompositionalLayout(section: section, configuration: configuration)
|
|
||||||
|
|
||||||
attachmentsCollectionView = UICollectionView(frame: .zero, collectionViewLayout: attachmentsLayout)
|
|
||||||
attachmentUploadView = AttachmentUploadView(viewModel: viewModel)
|
attachmentUploadView = AttachmentUploadView(viewModel: viewModel)
|
||||||
|
|
||||||
super.init(frame: .zero)
|
super.init(frame: .zero)
|
||||||
|
@ -126,10 +97,7 @@ private extension CompositionView {
|
||||||
textViewPlaceholder.textColor = .secondaryLabel
|
textViewPlaceholder.textColor = .secondaryLabel
|
||||||
textViewPlaceholder.text = NSLocalizedString("compose.prompt", comment: "")
|
textViewPlaceholder.text = NSLocalizedString("compose.prompt", comment: "")
|
||||||
|
|
||||||
stackView.addArrangedSubview(attachmentsCollectionView)
|
stackView.addArrangedSubview(attachmentsView)
|
||||||
attachmentsCollectionView.dataSource = attachmentsDataSource
|
|
||||||
attachmentsCollectionView.backgroundColor = .clear
|
|
||||||
|
|
||||||
stackView.addArrangedSubview(attachmentUploadView)
|
stackView.addArrangedSubview(attachmentUploadView)
|
||||||
|
|
||||||
textView.text = viewModel.text
|
textView.text = viewModel.text
|
||||||
|
@ -163,9 +131,10 @@ private extension CompositionView {
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
viewModel.$attachmentViewModels
|
viewModel.$attachmentViewModels
|
||||||
|
.receive(on: RunLoop.main)
|
||||||
.sink { [weak self] in
|
.sink { [weak self] in
|
||||||
self?.attachmentsDataSource.apply($0.map(\.attachment).snapshot())
|
self?.attachmentsView.viewModel = self?.viewModel
|
||||||
self?.attachmentsCollectionView.isHidden = $0.isEmpty
|
self?.attachmentsView.isHidden = $0.isEmpty
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
@ -182,8 +151,7 @@ private extension CompositionView {
|
||||||
stackView.bottomAnchor.constraint(lessThanOrEqualTo: guide.bottomAnchor),
|
stackView.bottomAnchor.constraint(lessThanOrEqualTo: guide.bottomAnchor),
|
||||||
textViewPlaceholder.leadingAnchor.constraint(equalTo: textView.leadingAnchor),
|
textViewPlaceholder.leadingAnchor.constraint(equalTo: textView.leadingAnchor),
|
||||||
textViewPlaceholder.topAnchor.constraint(equalTo: textView.topAnchor),
|
textViewPlaceholder.topAnchor.constraint(equalTo: textView.topAnchor),
|
||||||
textViewPlaceholder.trailingAnchor.constraint(equalTo: textView.trailingAnchor),
|
textViewPlaceholder.trailingAnchor.constraint(equalTo: textView.trailingAnchor)
|
||||||
attachmentsCollectionView.heightAnchor.constraint(equalToConstant: Self.attachmentCollectionViewHeight)
|
|
||||||
]
|
]
|
||||||
|
|
||||||
if UIDevice.current.userInterfaceIdiom == .pad {
|
if UIDevice.current.userInterfaceIdiom == .pad {
|
||||||
|
|
|
@ -7,7 +7,7 @@ final class StatusBodyView: UIView {
|
||||||
let spoilerTextLabel = UILabel()
|
let spoilerTextLabel = UILabel()
|
||||||
let toggleShowContentButton = UIButton(type: .system)
|
let toggleShowContentButton = UIButton(type: .system)
|
||||||
let contentTextView = TouchFallthroughTextView()
|
let contentTextView = TouchFallthroughTextView()
|
||||||
let attachmentsView = StatusAttachmentsView()
|
let attachmentsView = AttachmentsView()
|
||||||
let pollView = PollView()
|
let pollView = PollView()
|
||||||
let cardView = CardView()
|
let cardView = CardView()
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue