Refactoring

This commit is contained in:
Justin Mazzocchi 2021-01-07 22:11:33 -08:00
parent b5e128a1b0
commit 4426f84df4
No known key found for this signature in database
GPG Key ID: E223E6937AAFB01C
15 changed files with 142 additions and 379 deletions

View File

@ -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)
}
}
}

View File

@ -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;

View File

@ -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

View File

@ -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
}()
} }

View File

@ -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() {}
}

View File

@ -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
}
}

View File

@ -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))
} }
} }
} }

View File

@ -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()
} }

View File

@ -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)
]) ])
} }
} }

View File

@ -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 }
} }
} }

View File

@ -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)
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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 {

View File

@ -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()