mirror of
https://github.com/metabolist/metatext.git
synced 2024-11-25 09:41:00 +00:00
Refactoring
This commit is contained in:
parent
55ba5f856a
commit
b13f4b89a8
20 changed files with 410 additions and 111 deletions
14
Extensions/CollectionItemKind+Extensions.swift
Normal file
14
Extensions/CollectionItemKind+Extensions.swift
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import ViewModels
|
||||||
|
|
||||||
|
extension CollectionItem.Kind {
|
||||||
|
var cellClass: AnyClass {
|
||||||
|
switch self {
|
||||||
|
case .status:
|
||||||
|
return StatusListCell.self
|
||||||
|
case .account:
|
||||||
|
return AccountListCell.self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -30,14 +30,13 @@
|
||||||
D0C7D49724F7616A001EBDBB /* IdentitiesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D42224F76169001EBDBB /* IdentitiesView.swift */; };
|
D0C7D49724F7616A001EBDBB /* IdentitiesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D42224F76169001EBDBB /* IdentitiesView.swift */; };
|
||||||
D0C7D49824F7616A001EBDBB /* CustomEmojiText.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D42324F76169001EBDBB /* CustomEmojiText.swift */; };
|
D0C7D49824F7616A001EBDBB /* CustomEmojiText.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D42324F76169001EBDBB /* CustomEmojiText.swift */; };
|
||||||
D0C7D49924F7616A001EBDBB /* AddIdentityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D42424F76169001EBDBB /* AddIdentityView.swift */; };
|
D0C7D49924F7616A001EBDBB /* AddIdentityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D42424F76169001EBDBB /* AddIdentityView.swift */; };
|
||||||
D0C7D49A24F7616A001EBDBB /* StatusListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D42524F76169001EBDBB /* StatusListView.swift */; };
|
D0C7D49A24F7616A001EBDBB /* CollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D42524F76169001EBDBB /* CollectionView.swift */; };
|
||||||
D0C7D49B24F7616A001EBDBB /* PreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D42624F76169001EBDBB /* PreferencesView.swift */; };
|
D0C7D49B24F7616A001EBDBB /* PreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D42624F76169001EBDBB /* PreferencesView.swift */; };
|
||||||
D0C7D49C24F7616A001EBDBB /* RootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D42724F76169001EBDBB /* RootView.swift */; };
|
D0C7D49C24F7616A001EBDBB /* RootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D42724F76169001EBDBB /* RootView.swift */; };
|
||||||
D0C7D49D24F7616A001EBDBB /* PostingReadingPreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D42824F76169001EBDBB /* PostingReadingPreferencesView.swift */; };
|
D0C7D49D24F7616A001EBDBB /* PostingReadingPreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D42824F76169001EBDBB /* PostingReadingPreferencesView.swift */; };
|
||||||
D0C7D49E24F7616A001EBDBB /* SecondaryNavigationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D42924F76169001EBDBB /* SecondaryNavigationView.swift */; };
|
D0C7D49E24F7616A001EBDBB /* SecondaryNavigationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D42924F76169001EBDBB /* SecondaryNavigationView.swift */; };
|
||||||
D0C7D4A224F7616A001EBDBB /* NotificationTypesPreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D42D24F76169001EBDBB /* NotificationTypesPreferencesView.swift */; };
|
D0C7D4A224F7616A001EBDBB /* NotificationTypesPreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D42D24F76169001EBDBB /* NotificationTypesPreferencesView.swift */; };
|
||||||
D0C7D4A324F7616A001EBDBB /* TabNavigationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D42E24F76169001EBDBB /* TabNavigationView.swift */; };
|
D0C7D4A324F7616A001EBDBB /* TabNavigationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D42E24F76169001EBDBB /* TabNavigationView.swift */; };
|
||||||
D0C7D4A524F7616A001EBDBB /* StatusListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D43124F76169001EBDBB /* StatusListViewController.swift */; };
|
|
||||||
D0C7D4C224F7616A001EBDBB /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D0C7D45224F76169001EBDBB /* Assets.xcassets */; };
|
D0C7D4C224F7616A001EBDBB /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D0C7D45224F76169001EBDBB /* Assets.xcassets */; };
|
||||||
D0C7D4C324F7616A001EBDBB /* MetatextApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D45424F76169001EBDBB /* MetatextApp.swift */; };
|
D0C7D4C324F7616A001EBDBB /* MetatextApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D45424F76169001EBDBB /* MetatextApp.swift */; };
|
||||||
D0C7D4C424F7616A001EBDBB /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D45524F76169001EBDBB /* AppDelegate.swift */; };
|
D0C7D4C424F7616A001EBDBB /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D45524F76169001EBDBB /* AppDelegate.swift */; };
|
||||||
|
@ -51,6 +50,11 @@
|
||||||
D0E2C1D124FD97F000854680 /* ViewModels in Frameworks */ = {isa = PBXBuildFile; productRef = D0E2C1D024FD97F000854680 /* ViewModels */; };
|
D0E2C1D124FD97F000854680 /* ViewModels in Frameworks */ = {isa = PBXBuildFile; productRef = D0E2C1D024FD97F000854680 /* ViewModels */; };
|
||||||
D0E5361C24E3EB4D00FB1CE1 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E5361B24E3EB4D00FB1CE1 /* NotificationService.swift */; };
|
D0E5361C24E3EB4D00FB1CE1 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E5361B24E3EB4D00FB1CE1 /* NotificationService.swift */; };
|
||||||
D0E5362024E3EB4D00FB1CE1 /* Notification Service Extension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = D0E5361924E3EB4D00FB1CE1 /* Notification Service Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
D0E5362024E3EB4D00FB1CE1 /* Notification Service Extension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = D0E5361924E3EB4D00FB1CE1 /* Notification Service Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
|
D0F0B10E251A868200942152 /* AccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F0B10D251A868200942152 /* AccountView.swift */; };
|
||||||
|
D0F0B113251A86A000942152 /* AccountContentConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F0B112251A86A000942152 /* AccountContentConfiguration.swift */; };
|
||||||
|
D0F0B126251A90F400942152 /* AccountListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F0B125251A90F400942152 /* AccountListCell.swift */; };
|
||||||
|
D0F0B12E251A97E400942152 /* CollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F0B12D251A97E400942152 /* CollectionViewController.swift */; };
|
||||||
|
D0F0B136251AA12700942152 /* CollectionItemKind+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F0B135251AA12700942152 /* CollectionItemKind+Extensions.swift */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
|
@ -116,14 +120,13 @@
|
||||||
D0C7D42224F76169001EBDBB /* IdentitiesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IdentitiesView.swift; sourceTree = "<group>"; };
|
D0C7D42224F76169001EBDBB /* IdentitiesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IdentitiesView.swift; sourceTree = "<group>"; };
|
||||||
D0C7D42324F76169001EBDBB /* CustomEmojiText.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomEmojiText.swift; sourceTree = "<group>"; };
|
D0C7D42324F76169001EBDBB /* CustomEmojiText.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomEmojiText.swift; sourceTree = "<group>"; };
|
||||||
D0C7D42424F76169001EBDBB /* AddIdentityView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddIdentityView.swift; sourceTree = "<group>"; };
|
D0C7D42424F76169001EBDBB /* AddIdentityView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddIdentityView.swift; sourceTree = "<group>"; };
|
||||||
D0C7D42524F76169001EBDBB /* StatusListView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusListView.swift; sourceTree = "<group>"; };
|
D0C7D42524F76169001EBDBB /* CollectionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionView.swift; sourceTree = "<group>"; };
|
||||||
D0C7D42624F76169001EBDBB /* PreferencesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencesView.swift; sourceTree = "<group>"; };
|
D0C7D42624F76169001EBDBB /* PreferencesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencesView.swift; sourceTree = "<group>"; };
|
||||||
D0C7D42724F76169001EBDBB /* RootView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RootView.swift; sourceTree = "<group>"; };
|
D0C7D42724F76169001EBDBB /* RootView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RootView.swift; sourceTree = "<group>"; };
|
||||||
D0C7D42824F76169001EBDBB /* PostingReadingPreferencesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PostingReadingPreferencesView.swift; sourceTree = "<group>"; };
|
D0C7D42824F76169001EBDBB /* PostingReadingPreferencesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PostingReadingPreferencesView.swift; sourceTree = "<group>"; };
|
||||||
D0C7D42924F76169001EBDBB /* SecondaryNavigationView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecondaryNavigationView.swift; sourceTree = "<group>"; };
|
D0C7D42924F76169001EBDBB /* SecondaryNavigationView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecondaryNavigationView.swift; sourceTree = "<group>"; };
|
||||||
D0C7D42D24F76169001EBDBB /* NotificationTypesPreferencesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationTypesPreferencesView.swift; sourceTree = "<group>"; };
|
D0C7D42D24F76169001EBDBB /* NotificationTypesPreferencesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationTypesPreferencesView.swift; sourceTree = "<group>"; };
|
||||||
D0C7D42E24F76169001EBDBB /* TabNavigationView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabNavigationView.swift; sourceTree = "<group>"; };
|
D0C7D42E24F76169001EBDBB /* TabNavigationView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabNavigationView.swift; sourceTree = "<group>"; };
|
||||||
D0C7D43124F76169001EBDBB /* StatusListViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusListViewController.swift; sourceTree = "<group>"; };
|
|
||||||
D0C7D45224F76169001EBDBB /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
D0C7D45224F76169001EBDBB /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
D0C7D45424F76169001EBDBB /* MetatextApp.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MetatextApp.swift; sourceTree = "<group>"; };
|
D0C7D45424F76169001EBDBB /* MetatextApp.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MetatextApp.swift; sourceTree = "<group>"; };
|
||||||
D0C7D45524F76169001EBDBB /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
D0C7D45524F76169001EBDBB /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
|
@ -141,6 +144,11 @@
|
||||||
D0E5361B24E3EB4D00FB1CE1 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = "<group>"; };
|
D0E5361B24E3EB4D00FB1CE1 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = "<group>"; };
|
||||||
D0E5361D24E3EB4D00FB1CE1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
D0E5361D24E3EB4D00FB1CE1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
D0E5362824E4A06B00FB1CE1 /* Notification Service Extension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Notification Service Extension.entitlements"; sourceTree = "<group>"; };
|
D0E5362824E4A06B00FB1CE1 /* Notification Service Extension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Notification Service Extension.entitlements"; sourceTree = "<group>"; };
|
||||||
|
D0F0B10D251A868200942152 /* AccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountView.swift; sourceTree = "<group>"; };
|
||||||
|
D0F0B112251A86A000942152 /* AccountContentConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountContentConfiguration.swift; sourceTree = "<group>"; };
|
||||||
|
D0F0B125251A90F400942152 /* AccountListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountListCell.swift; sourceTree = "<group>"; };
|
||||||
|
D0F0B12D251A97E400942152 /* CollectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionViewController.swift; sourceTree = "<group>"; };
|
||||||
|
D0F0B135251AA12700942152 /* CollectionItemKind+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CollectionItemKind+Extensions.swift"; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
|
@ -257,9 +265,13 @@
|
||||||
D0C7D42024F76169001EBDBB /* Views */ = {
|
D0C7D42024F76169001EBDBB /* Views */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
D0F0B112251A86A000942152 /* AccountContentConfiguration.swift */,
|
||||||
D01EF22325182B1F00650C6B /* AccountHeaderView.swift */,
|
D01EF22325182B1F00650C6B /* AccountHeaderView.swift */,
|
||||||
|
D0F0B125251A90F400942152 /* AccountListCell.swift */,
|
||||||
|
D0F0B10D251A868200942152 /* AccountView.swift */,
|
||||||
D0C7D42424F76169001EBDBB /* AddIdentityView.swift */,
|
D0C7D42424F76169001EBDBB /* AddIdentityView.swift */,
|
||||||
D01F41E024F8885900D55A2D /* Attachments */,
|
D01F41E024F8885900D55A2D /* Attachments */,
|
||||||
|
D0C7D42524F76169001EBDBB /* CollectionView.swift */,
|
||||||
D0C7D42324F76169001EBDBB /* CustomEmojiText.swift */,
|
D0C7D42324F76169001EBDBB /* CustomEmojiText.swift */,
|
||||||
D0BEB21024FA2A90001B0F04 /* EditFilterView.swift */,
|
D0BEB21024FA2A90001B0F04 /* EditFilterView.swift */,
|
||||||
D0BEB20424FA1107001B0F04 /* FiltersView.swift */,
|
D0BEB20424FA1107001B0F04 /* FiltersView.swift */,
|
||||||
|
@ -274,7 +286,6 @@
|
||||||
D02E1F94250B13210071AD56 /* SafariView.swift */,
|
D02E1F94250B13210071AD56 /* SafariView.swift */,
|
||||||
D0C7D42924F76169001EBDBB /* SecondaryNavigationView.swift */,
|
D0C7D42924F76169001EBDBB /* SecondaryNavigationView.swift */,
|
||||||
D0625E55250F086B00502611 /* Status */,
|
D0625E55250F086B00502611 /* Status */,
|
||||||
D0C7D42524F76169001EBDBB /* StatusListView.swift */,
|
|
||||||
D0C7D42E24F76169001EBDBB /* TabNavigationView.swift */,
|
D0C7D42E24F76169001EBDBB /* TabNavigationView.swift */,
|
||||||
D01F41D624F880C400D55A2D /* TouchFallthroughTextView.swift */,
|
D01F41D624F880C400D55A2D /* TouchFallthroughTextView.swift */,
|
||||||
);
|
);
|
||||||
|
@ -284,7 +295,7 @@
|
||||||
D0C7D43024F76169001EBDBB /* View Controllers */ = {
|
D0C7D43024F76169001EBDBB /* View Controllers */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
D0C7D43124F76169001EBDBB /* StatusListViewController.swift */,
|
D0F0B12D251A97E400942152 /* CollectionViewController.swift */,
|
||||||
);
|
);
|
||||||
path = "View Controllers";
|
path = "View Controllers";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -311,6 +322,7 @@
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
D0B5FE9A251583DB00478838 /* AccountStatusCollection+Extensions.swift */,
|
D0B5FE9A251583DB00478838 /* AccountStatusCollection+Extensions.swift */,
|
||||||
|
D0F0B135251AA12700942152 /* CollectionItemKind+Extensions.swift */,
|
||||||
D0C7D46E24F76169001EBDBB /* KingfisherOptionsInfo+Extensions.swift */,
|
D0C7D46E24F76169001EBDBB /* KingfisherOptionsInfo+Extensions.swift */,
|
||||||
D0C7D46B24F76169001EBDBB /* NSMutableAttributedString+Extensions.swift */,
|
D0C7D46B24F76169001EBDBB /* NSMutableAttributedString+Extensions.swift */,
|
||||||
D0C7D46A24F76169001EBDBB /* String+Extensions.swift */,
|
D0C7D46A24F76169001EBDBB /* String+Extensions.swift */,
|
||||||
|
@ -502,12 +514,15 @@
|
||||||
D0C7D4A324F7616A001EBDBB /* TabNavigationView.swift in Sources */,
|
D0C7D4A324F7616A001EBDBB /* TabNavigationView.swift in Sources */,
|
||||||
D02E1F95250B13210071AD56 /* SafariView.swift in Sources */,
|
D02E1F95250B13210071AD56 /* SafariView.swift in Sources */,
|
||||||
D0C7D49C24F7616A001EBDBB /* RootView.swift in Sources */,
|
D0C7D49C24F7616A001EBDBB /* RootView.swift in Sources */,
|
||||||
|
D0F0B126251A90F400942152 /* AccountListCell.swift in Sources */,
|
||||||
D0B32F50250B373600311912 /* RegistrationView.swift in Sources */,
|
D0B32F50250B373600311912 /* RegistrationView.swift in Sources */,
|
||||||
|
D0F0B136251AA12700942152 /* CollectionItemKind+Extensions.swift in Sources */,
|
||||||
D0625E5D250F0B5C00502611 /* StatusContentConfiguration.swift in Sources */,
|
D0625E5D250F0B5C00502611 /* StatusContentConfiguration.swift in Sources */,
|
||||||
D0BEB1F324F8EE8C001B0F04 /* AttachmentView.swift in Sources */,
|
D0BEB1F324F8EE8C001B0F04 /* AttachmentView.swift in Sources */,
|
||||||
D0C7D49A24F7616A001EBDBB /* StatusListView.swift in Sources */,
|
D0C7D49A24F7616A001EBDBB /* CollectionView.swift in Sources */,
|
||||||
|
D0F0B12E251A97E400942152 /* CollectionViewController.swift in Sources */,
|
||||||
|
D0F0B113251A86A000942152 /* AccountContentConfiguration.swift in Sources */,
|
||||||
D01F41D924F880C400D55A2D /* TouchFallthroughTextView.swift in Sources */,
|
D01F41D924F880C400D55A2D /* TouchFallthroughTextView.swift in Sources */,
|
||||||
D0C7D4A524F7616A001EBDBB /* StatusListViewController.swift in Sources */,
|
|
||||||
D0C7D4D624F7616A001EBDBB /* NSMutableAttributedString+Extensions.swift in Sources */,
|
D0C7D4D624F7616A001EBDBB /* NSMutableAttributedString+Extensions.swift in Sources */,
|
||||||
D0625E5F250F0CFF00502611 /* StatusView.swift in Sources */,
|
D0625E5F250F0CFF00502611 /* StatusView.swift in Sources */,
|
||||||
D0625E59250F092900502611 /* StatusListCell.swift in Sources */,
|
D0625E59250F092900502611 /* StatusListCell.swift in Sources */,
|
||||||
|
@ -519,6 +534,7 @@
|
||||||
D0C7D4A224F7616A001EBDBB /* NotificationTypesPreferencesView.swift in Sources */,
|
D0C7D4A224F7616A001EBDBB /* NotificationTypesPreferencesView.swift in Sources */,
|
||||||
D0BEB1F724F9A84B001B0F04 /* LoadingTableFooterView.swift in Sources */,
|
D0BEB1F724F9A84B001B0F04 /* LoadingTableFooterView.swift in Sources */,
|
||||||
D0C7D4D924F7616A001EBDBB /* KingfisherOptionsInfo+Extensions.swift in Sources */,
|
D0C7D4D924F7616A001EBDBB /* KingfisherOptionsInfo+Extensions.swift in Sources */,
|
||||||
|
D0F0B10E251A868200942152 /* AccountView.swift in Sources */,
|
||||||
D0BEB1FF24F9E5BB001B0F04 /* ListsView.swift in Sources */,
|
D0BEB1FF24F9E5BB001B0F04 /* ListsView.swift in Sources */,
|
||||||
D0C7D49724F7616A001EBDBB /* IdentitiesView.swift in Sources */,
|
D0C7D49724F7616A001EBDBB /* IdentitiesView.swift in Sources */,
|
||||||
D01EF22425182B1F00650C6B /* AccountHeaderView.swift in Sources */,
|
D01EF22425182B1F00650C6B /* AccountHeaderView.swift in Sources */,
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import Combine
|
||||||
|
import DB
|
||||||
|
import Foundation
|
||||||
|
import Mastodon
|
||||||
|
import MastodonAPI
|
||||||
|
|
||||||
|
public struct AccountListService {
|
||||||
|
public let accountSections: AnyPublisher<[[Account]], Error>
|
||||||
|
public let paginates: Bool
|
||||||
|
|
||||||
|
private let mastodonAPIClient: MastodonAPIClient
|
||||||
|
private let contentDatabase: ContentDatabase
|
||||||
|
private let requestClosure: (_ maxID: String?, _ minID: String?) -> AnyPublisher<Never, Error>
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AccountListService {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension AccountListService {
|
||||||
|
func request(maxID: String?, minID: String?) -> AnyPublisher<Never, Error> {
|
||||||
|
requestClosure(maxID, minID)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import Combine
|
||||||
|
import DB
|
||||||
|
import Foundation
|
||||||
|
import Mastodon
|
||||||
|
import MastodonAPI
|
||||||
|
|
||||||
|
public struct AccountService {
|
||||||
|
public let account: Account
|
||||||
|
public let urlService: URLService
|
||||||
|
private let mastodonAPIClient: MastodonAPIClient
|
||||||
|
private let contentDatabase: ContentDatabase
|
||||||
|
|
||||||
|
init(account: Account, mastodonAPIClient: MastodonAPIClient, contentDatabase: ContentDatabase) {
|
||||||
|
self.account = account
|
||||||
|
self.urlService = URLService(
|
||||||
|
status: nil,
|
||||||
|
mastodonAPIClient: mastodonAPIClient,
|
||||||
|
contentDatabase: contentDatabase)
|
||||||
|
self.mastodonAPIClient = mastodonAPIClient
|
||||||
|
self.contentDatabase = contentDatabase
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,30 +5,36 @@ import SafariServices
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import ViewModels
|
import ViewModels
|
||||||
|
|
||||||
class StatusListViewController: UITableViewController {
|
class CollectionViewController: UITableViewController {
|
||||||
private let viewModel: StatusListViewModel
|
private let viewModel: CollectionViewModel
|
||||||
private let loadingTableFooterView = LoadingTableFooterView()
|
private let loadingTableFooterView = LoadingTableFooterView()
|
||||||
private var cancellables = Set<AnyCancellable>()
|
private var cancellables = Set<AnyCancellable>()
|
||||||
private var cellHeightCaches = [CGFloat: [String: CGFloat]]()
|
private var cellHeightCaches = [CGFloat: [CollectionItem: CGFloat]]()
|
||||||
private let dataSourceQueue =
|
private let dataSourceQueue =
|
||||||
DispatchQueue(label: "com.metabolist.metatext.status-list.data-source-queue")
|
DispatchQueue(label: "com.metabolist.metatext.collection.data-source-queue")
|
||||||
|
|
||||||
|
private lazy var dataSource: UITableViewDiffableDataSource<Int, CollectionItem> = {
|
||||||
|
UITableViewDiffableDataSource(tableView: tableView) { [weak self] tableView, indexPath, item in
|
||||||
|
guard let self = self, let cellViewModel = self.viewModel.viewModel(item: item) else { return nil }
|
||||||
|
|
||||||
private lazy var dataSource: UITableViewDiffableDataSource<Int, String> = {
|
|
||||||
UITableViewDiffableDataSource(tableView: tableView) { [weak self] tableView, indexPath, statusID in
|
|
||||||
guard
|
|
||||||
let self = self,
|
|
||||||
let cell = tableView.dequeueReusableCell(
|
let cell = tableView.dequeueReusableCell(
|
||||||
withIdentifier: String(describing: StatusListCell.self),
|
withIdentifier: String(describing: item.kind.cellClass),
|
||||||
for: indexPath) as? StatusListCell
|
for: indexPath)
|
||||||
else { return nil }
|
|
||||||
|
|
||||||
cell.viewModel = self.viewModel.statusViewModel(id: statusID)
|
switch (cell, cellViewModel) {
|
||||||
|
case (let statusListCell as StatusListCell, let statusViewModel as StatusViewModel):
|
||||||
|
statusListCell.viewModel = statusViewModel
|
||||||
|
case (let accountListCell as AccountListCell, let accountViewModel as AccountViewModel):
|
||||||
|
accountListCell.viewModel = accountViewModel
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
return cell
|
return cell
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
init(viewModel: StatusListViewModel) {
|
init(viewModel: CollectionViewModel) {
|
||||||
self.viewModel = viewModel
|
self.viewModel = viewModel
|
||||||
|
|
||||||
super.init(style: .plain)
|
super.init(style: .plain)
|
||||||
|
@ -42,33 +48,35 @@ class StatusListViewController: UITableViewController {
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
|
||||||
tableView.register(StatusListCell.self, forCellReuseIdentifier: String(describing: StatusListCell.self))
|
for kind in CollectionItem.Kind.allCases {
|
||||||
|
tableView.register(kind.cellClass, forCellReuseIdentifier: String(describing: kind.cellClass))
|
||||||
|
}
|
||||||
|
|
||||||
tableView.dataSource = dataSource
|
tableView.dataSource = dataSource
|
||||||
tableView.prefetchDataSource = self
|
tableView.prefetchDataSource = self
|
||||||
tableView.cellLayoutMarginsFollowReadableWidth = true
|
tableView.cellLayoutMarginsFollowReadableWidth = true
|
||||||
tableView.tableFooterView = UIView()
|
tableView.tableFooterView = UIView()
|
||||||
|
|
||||||
navigationItem.title = viewModel.title
|
// navigationItem.title = viewModel.title
|
||||||
|
|
||||||
viewModel.$statusIDs
|
viewModel.collectionItems
|
||||||
.sink { [weak self] in self?.update(statusIDs: $0) }
|
.sink { [weak self] in self?.update(items: $0) }
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
viewModel.events.sink { [weak self] in
|
viewModel.navigationEvents.sink { [weak self] in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
switch $0 {
|
switch $0 {
|
||||||
case let .share(url):
|
case let .share(url):
|
||||||
self.share(url: url)
|
self.share(url: url)
|
||||||
case let .statusListNavigation(statusListViewModel):
|
case let .collectionNavigation(collectionViewModel):
|
||||||
self.show(StatusListViewController(viewModel: statusListViewModel), sender: self)
|
self.show(CollectionViewController(viewModel: collectionViewModel), sender: self)
|
||||||
case let .urlNavigation(url):
|
case let .urlNavigation(url):
|
||||||
self.present(SFSafariViewController(url: url), animated: true)
|
self.present(SFSafariViewController(url: url), animated: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
viewModel.$loading
|
viewModel.loading
|
||||||
.receive(on: RunLoop.main)
|
.receive(on: RunLoop.main)
|
||||||
.sink { [weak self] in
|
.sink { [weak self] in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
|
@ -97,7 +105,7 @@ class StatusListViewController: UITableViewController {
|
||||||
override func viewWillAppear(_ animated: Bool) {
|
override func viewWillAppear(_ animated: Bool) {
|
||||||
super.viewWillAppear(animated)
|
super.viewWillAppear(animated)
|
||||||
|
|
||||||
viewModel.request()
|
viewModel.request(maxID: nil, minID: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tableView(_ tableView: UITableView,
|
override func tableView(_ tableView: UITableView,
|
||||||
|
@ -105,7 +113,7 @@ class StatusListViewController: UITableViewController {
|
||||||
forRowAt indexPath: IndexPath) {
|
forRowAt indexPath: IndexPath) {
|
||||||
guard let item = dataSource.itemIdentifier(for: indexPath) else { return }
|
guard let item = dataSource.itemIdentifier(for: indexPath) else { return }
|
||||||
|
|
||||||
var heightCache = cellHeightCaches[tableView.frame.width] ?? [String: CGFloat]()
|
var heightCache = cellHeightCaches[tableView.frame.width] ?? [CollectionItem: CGFloat]()
|
||||||
|
|
||||||
heightCache[item] = cell.frame.height
|
heightCache[item] = cell.frame.height
|
||||||
cellHeightCaches[tableView.frame.width] = heightCache
|
cellHeightCaches[tableView.frame.width] = heightCache
|
||||||
|
@ -118,15 +126,15 @@ class StatusListViewController: UITableViewController {
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool {
|
override func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool {
|
||||||
guard let id = dataSource.itemIdentifier(for: indexPath) else { return true }
|
guard let item = dataSource.itemIdentifier(for: indexPath) else { return true }
|
||||||
|
|
||||||
return id != viewModel.contextParentID
|
return viewModel.canSelect(item: item)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||||
guard let id = dataSource.itemIdentifier(for: indexPath) else { return }
|
guard let item = dataSource.itemIdentifier(for: indexPath) else { return }
|
||||||
|
|
||||||
show(StatusListViewController(viewModel: viewModel.contextViewModel(id: id)), sender: self)
|
viewModel.itemSelected(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewDidLayoutSubviews() {
|
override func viewDidLayoutSubviews() {
|
||||||
|
@ -136,27 +144,27 @@ class StatusListViewController: UITableViewController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension StatusListViewController: UITableViewDataSourcePrefetching {
|
extension CollectionViewController: UITableViewDataSourcePrefetching {
|
||||||
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
|
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
|
||||||
guard
|
guard
|
||||||
viewModel.paginates,
|
viewModel.paginates,
|
||||||
let indexPath = indexPaths.last,
|
let indexPath = indexPaths.last,
|
||||||
indexPath.section == dataSource.numberOfSections(in: tableView) - 1,
|
indexPath.section == dataSource.numberOfSections(in: tableView) - 1,
|
||||||
indexPath.row == dataSource.tableView(tableView, numberOfRowsInSection: indexPath.section) - 1,
|
indexPath.row == dataSource.tableView(tableView, numberOfRowsInSection: indexPath.section) - 1,
|
||||||
let maxID = dataSource.itemIdentifier(for: indexPath)
|
let maxID = dataSource.itemIdentifier(for: indexPath)?.id
|
||||||
else { return }
|
else { return }
|
||||||
|
|
||||||
viewModel.request(maxID: maxID)
|
viewModel.request(maxID: maxID, minID: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension StatusListViewController {
|
private extension CollectionViewController {
|
||||||
func update(statusIDs: [[String]]) {
|
func update(items: [[CollectionItem]]) {
|
||||||
var offsetFromNavigationBar: CGFloat?
|
var offsetFromNavigationBar: CGFloat?
|
||||||
|
|
||||||
if
|
if
|
||||||
let id = viewModel.maintainScrollPositionOfStatusID,
|
let item = viewModel.maintainScrollPositionOfItem,
|
||||||
let indexPath = dataSource.indexPath(for: id),
|
let indexPath = dataSource.indexPath(for: item),
|
||||||
let navigationBar = navigationController?.navigationBar {
|
let navigationBar = navigationController?.navigationBar {
|
||||||
let navigationBarMaxY = tableView.convert(navigationBar.bounds, from: navigationBar).maxY
|
let navigationBarMaxY = tableView.convert(navigationBar.bounds, from: navigationBar).maxY
|
||||||
offsetFromNavigationBar = tableView.rectForRow(at: indexPath).origin.y - navigationBarMaxY
|
offsetFromNavigationBar = tableView.rectForRow(at: indexPath).origin.y - navigationBarMaxY
|
||||||
|
@ -165,10 +173,10 @@ private extension StatusListViewController {
|
||||||
dataSourceQueue.async { [weak self] in
|
dataSourceQueue.async { [weak self] in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
|
|
||||||
self.dataSource.apply(statusIDs.snapshot(), animatingDifferences: false) {
|
self.dataSource.apply(items.snapshot(), animatingDifferences: false) {
|
||||||
if
|
if
|
||||||
let id = self.viewModel.maintainScrollPositionOfStatusID,
|
let item = self.viewModel.maintainScrollPositionOfItem,
|
||||||
let indexPath = self.dataSource.indexPath(for: id) {
|
let indexPath = self.dataSource.indexPath(for: item) {
|
||||||
self.tableView.scrollToRow(at: indexPath, at: .top, animated: false)
|
self.tableView.scrollToRow(at: indexPath, at: .top, animated: false)
|
||||||
|
|
||||||
if let offsetFromNavigationBar = offsetFromNavigationBar {
|
if let offsetFromNavigationBar = offsetFromNavigationBar {
|
7
ViewModels/Sources/ViewModels/AccountListViewModel.swift
Normal file
7
ViewModels/Sources/ViewModels/AccountListViewModel.swift
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public class AccountListViewModel: ObservableObject {
|
||||||
|
|
||||||
|
}
|
|
@ -39,7 +39,7 @@ public class AccountStatusesViewModel: StatusListViewModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
override func isPinned(status: Status) -> Bool {
|
override func isPinned(status: Status) -> Bool {
|
||||||
collection == .statuses && statusIDs.first?.contains(status.id) ?? false
|
collection == .statuses && items.first?.contains(CollectionItem(id: status.id, kind: .status)) ?? false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
24
ViewModels/Sources/ViewModels/AccountViewModel.swift
Normal file
24
ViewModels/Sources/ViewModels/AccountViewModel.swift
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import Combine
|
||||||
|
import Foundation
|
||||||
|
import Mastodon
|
||||||
|
import ServiceLayer
|
||||||
|
|
||||||
|
public class AccountViewModel: ObservableObject {
|
||||||
|
private let accountService: AccountService
|
||||||
|
|
||||||
|
init(accountService: AccountService) {
|
||||||
|
self.accountService = accountService
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension AccountViewModel {
|
||||||
|
var avatarURL: URL {
|
||||||
|
accountService.account.avatar
|
||||||
|
}
|
||||||
|
|
||||||
|
var note: NSAttributedString {
|
||||||
|
accountService.account.note.attributed
|
||||||
|
}
|
||||||
|
}
|
17
ViewModels/Sources/ViewModels/CollectionViewModel.swift
Normal file
17
ViewModels/Sources/ViewModels/CollectionViewModel.swift
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import Combine
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public protocol CollectionViewModel {
|
||||||
|
var collectionItems: AnyPublisher<[[CollectionItem]], Never> { get }
|
||||||
|
var alertItems: AnyPublisher<AlertItem, Never> { get }
|
||||||
|
var loading: AnyPublisher<Bool, Never> { get }
|
||||||
|
var navigationEvents: AnyPublisher<NavigationEvent, Never> { get }
|
||||||
|
var paginates: Bool { get }
|
||||||
|
var maintainScrollPositionOfItem: CollectionItem? { get }
|
||||||
|
func request(maxID: String?, minID: String?)
|
||||||
|
func itemSelected(_ item: CollectionItem)
|
||||||
|
func canSelect(item: CollectionItem) -> Bool
|
||||||
|
func viewModel(item: CollectionItem) -> Any?
|
||||||
|
}
|
13
ViewModels/Sources/ViewModels/Entities/CollectionItem.swift
Normal file
13
ViewModels/Sources/ViewModels/Entities/CollectionItem.swift
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
public struct CollectionItem: Hashable {
|
||||||
|
public let id: String
|
||||||
|
public let kind: Kind
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension CollectionItem {
|
||||||
|
enum Kind: Hashable, CaseIterable {
|
||||||
|
case status
|
||||||
|
case account
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public enum NavigationEvent {
|
||||||
|
case collectionNavigation(CollectionViewModel)
|
||||||
|
case urlNavigation(URL)
|
||||||
|
case share(URL)
|
||||||
|
}
|
|
@ -6,21 +6,22 @@ import Mastodon
|
||||||
import ServiceLayer
|
import ServiceLayer
|
||||||
|
|
||||||
public class StatusListViewModel: ObservableObject {
|
public class StatusListViewModel: ObservableObject {
|
||||||
@Published public private(set) var statusIDs = [[String]]()
|
@Published public private(set) var items = [[CollectionItem]]()
|
||||||
@Published public var alertItem: AlertItem?
|
@Published public var alertItem: AlertItem?
|
||||||
@Published public private(set) var loading = false
|
public let navigationEvents: AnyPublisher<NavigationEvent, Never>
|
||||||
public let events: AnyPublisher<Event, Never>
|
public private(set) var maintainScrollPositionOfItem: CollectionItem?
|
||||||
public private(set) var maintainScrollPositionOfStatusID: String?
|
|
||||||
|
|
||||||
private var statuses = [String: Status]()
|
private var statuses = [String: Status]()
|
||||||
|
private var flatStatusIDs = [String]()
|
||||||
private let statusListService: StatusListService
|
private let statusListService: StatusListService
|
||||||
private var statusViewModelCache = [Status: (StatusViewModel, AnyCancellable)]()
|
private var statusViewModelCache = [Status: (StatusViewModel, AnyCancellable)]()
|
||||||
private let eventsSubject = PassthroughSubject<Event, Never>()
|
private let navigationEventsSubject = PassthroughSubject<NavigationEvent, Never>()
|
||||||
|
private let loadingSubject = PassthroughSubject<Bool, Never>()
|
||||||
private var cancellables = Set<AnyCancellable>()
|
private var cancellables = Set<AnyCancellable>()
|
||||||
|
|
||||||
init(statusListService: StatusListService) {
|
init(statusListService: StatusListService) {
|
||||||
self.statusListService = statusListService
|
self.statusListService = statusListService
|
||||||
events = eventsSubject.eraseToAnyPublisher()
|
navigationEvents = navigationEventsSubject.eraseToAnyPublisher()
|
||||||
|
|
||||||
statusListService.statusSections
|
statusListService.statusSections
|
||||||
.combineLatest(statusListService.filters.map { $0.regularExpression() })
|
.combineLatest(statusListService.filters.map { $0.regularExpression() })
|
||||||
|
@ -29,11 +30,12 @@ public class StatusListViewModel: ObservableObject {
|
||||||
self?.determineIfScrollPositionShouldBeMaintained(newStatusSections: $0)
|
self?.determineIfScrollPositionShouldBeMaintained(newStatusSections: $0)
|
||||||
self?.cleanViewModelCache(newStatusSections: $0)
|
self?.cleanViewModelCache(newStatusSections: $0)
|
||||||
self?.statuses = Dictionary(uniqueKeysWithValues: Set($0.reduce([], +)).map { ($0.id, $0) })
|
self?.statuses = Dictionary(uniqueKeysWithValues: Set($0.reduce([], +)).map { ($0.id, $0) })
|
||||||
|
self?.flatStatusIDs = $0.reduce([], +).map(\.id)
|
||||||
})
|
})
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.assignErrorsToAlertItem(to: \.alertItem, on: self)
|
.assignErrorsToAlertItem(to: \.alertItem, on: self)
|
||||||
.map { $0.map { $0.map(\.id) } }
|
.map { $0.map { $0.map { CollectionItem(id: $0.id, kind: .status) } } }
|
||||||
.assign(to: &$statusIDs)
|
.assign(to: &$items)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func request(maxID: String? = nil, minID: String? = nil) {
|
public func request(maxID: String? = nil, minID: String? = nil) {
|
||||||
|
@ -41,8 +43,8 @@ public class StatusListViewModel: ObservableObject {
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.assignErrorsToAlertItem(to: \.alertItem, on: self)
|
.assignErrorsToAlertItem(to: \.alertItem, on: self)
|
||||||
.handleEvents(
|
.handleEvents(
|
||||||
receiveSubscription: { [weak self] _ in self?.loading = true },
|
receiveSubscription: { [weak self] _ in self?.loadingSubject.send(true) },
|
||||||
receiveCompletion: { [weak self] _ in self?.loading = false })
|
receiveCompletion: { [weak self] _ in self?.loadingSubject.send(false) })
|
||||||
.sink { _ in }
|
.sink { _ in }
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
|
@ -50,11 +52,44 @@ public class StatusListViewModel: ObservableObject {
|
||||||
func isPinned(status: Status) -> Bool { false }
|
func isPinned(status: Status) -> Bool { false }
|
||||||
}
|
}
|
||||||
|
|
||||||
public extension StatusListViewModel {
|
extension StatusListViewModel: CollectionViewModel {
|
||||||
enum Event {
|
public var collectionItems: AnyPublisher<[[CollectionItem]], Never> { $items.eraseToAnyPublisher() }
|
||||||
case statusListNavigation(StatusListViewModel)
|
|
||||||
case urlNavigation(URL)
|
public var alertItems: AnyPublisher<AlertItem, Never> { $alertItem.compactMap { $0 }.eraseToAnyPublisher() }
|
||||||
case share(URL)
|
|
||||||
|
public var loading: AnyPublisher<Bool, Never> {
|
||||||
|
loadingSubject.eraseToAnyPublisher()
|
||||||
|
}
|
||||||
|
|
||||||
|
public func itemSelected(_ item: CollectionItem) {
|
||||||
|
switch item.kind {
|
||||||
|
case .status:
|
||||||
|
let displayStatusID = statuses[item.id]?.displayStatus.id ?? item.id
|
||||||
|
|
||||||
|
navigationEventsSubject.send(
|
||||||
|
.collectionNavigation(
|
||||||
|
StatusListViewModel(
|
||||||
|
statusListService: statusListService.contextService(statusID: displayStatusID))))
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func canSelect(item: CollectionItem) -> Bool {
|
||||||
|
if case .status = item.kind, item.id == statusListService.contextParentID {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
public func viewModel(item: CollectionItem) -> Any? {
|
||||||
|
switch item.kind {
|
||||||
|
case .status:
|
||||||
|
return statusViewModel(id: item.id)
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,9 +115,9 @@ public extension StatusListViewModel {
|
||||||
.assignErrorsToAlertItem(to: \.alertItem, on: self)
|
.assignErrorsToAlertItem(to: \.alertItem, on: self)
|
||||||
.sink { [weak self] in
|
.sink { [weak self] in
|
||||||
guard let self = self,
|
guard let self = self,
|
||||||
let event = self.event(statusEvent: $0)
|
let event = self.navigationEvent(statusEvent: $0)
|
||||||
else { return }
|
else { return }
|
||||||
self.eventsSubject.send(event)
|
self.navigationEventsSubject.send(event)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,12 +128,6 @@ public extension StatusListViewModel {
|
||||||
|
|
||||||
return statusViewModel
|
return statusViewModel
|
||||||
}
|
}
|
||||||
|
|
||||||
func contextViewModel(id: String) -> StatusListViewModel {
|
|
||||||
let displayStatusID = statuses[id]?.displayStatus.id ?? id
|
|
||||||
|
|
||||||
return StatusListViewModel(statusListService: statusListService.contextService(statusID: displayStatusID))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension StatusListViewModel {
|
private extension StatusListViewModel {
|
||||||
|
@ -110,7 +139,7 @@ private extension StatusListViewModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func event(statusEvent: StatusViewModel.Event) -> Event? {
|
func navigationEvent(statusEvent: StatusViewModel.Event) -> NavigationEvent? {
|
||||||
switch statusEvent {
|
switch statusEvent {
|
||||||
case .ignorableOutput:
|
case .ignorableOutput:
|
||||||
return nil
|
return nil
|
||||||
|
@ -119,30 +148,31 @@ private extension StatusListViewModel {
|
||||||
case let .url(url):
|
case let .url(url):
|
||||||
return .urlNavigation(url)
|
return .urlNavigation(url)
|
||||||
case let .accountID(id):
|
case let .accountID(id):
|
||||||
return .statusListNavigation(
|
return .collectionNavigation(
|
||||||
AccountStatusesViewModel(accountStatusesService: statusListService.service(accountID: id)))
|
AccountStatusesViewModel(accountStatusesService: statusListService.service(accountID: id)))
|
||||||
case let .statusID(id):
|
case let .statusID(id):
|
||||||
return .statusListNavigation(
|
return .collectionNavigation(
|
||||||
StatusListViewModel(
|
StatusListViewModel(
|
||||||
statusListService: statusListService.contextService(statusID: id)))
|
statusListService: statusListService.contextService(statusID: id)))
|
||||||
case let .tag(tag):
|
case let .tag(tag):
|
||||||
return .statusListNavigation(
|
return .collectionNavigation(
|
||||||
StatusListViewModel(
|
StatusListViewModel(
|
||||||
statusListService: statusListService.service(timeline: Timeline.tag(tag))))
|
statusListService: statusListService.service(timeline: Timeline.tag(tag))))
|
||||||
}
|
}
|
||||||
|
case let .accountListNavigation(accountListViewModel):
|
||||||
|
// return .collectionNavigation(accountListViewModel)
|
||||||
|
return nil
|
||||||
case let .share(url):
|
case let .share(url):
|
||||||
return .share(url)
|
return .share(url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func determineIfScrollPositionShouldBeMaintained(newStatusSections: [[Status]]) {
|
func determineIfScrollPositionShouldBeMaintained(newStatusSections: [[Status]]) {
|
||||||
maintainScrollPositionOfStatusID = nil // clear old value
|
maintainScrollPositionOfItem = nil // clear old value
|
||||||
|
|
||||||
let flatStatusIDs = statusIDs.reduce([], +)
|
|
||||||
|
|
||||||
// Maintain scroll position of parent after initial load of context
|
// Maintain scroll position of parent after initial load of context
|
||||||
if let contextParentID = contextParentID, flatStatusIDs == [contextParentID] || flatStatusIDs == [] {
|
if let contextParentID = contextParentID, flatStatusIDs == [contextParentID] || flatStatusIDs == [] {
|
||||||
maintainScrollPositionOfStatusID = contextParentID
|
maintainScrollPositionOfItem = CollectionItem(id: contextParentID, kind: .status)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,8 +183,6 @@ private extension StatusListViewModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
func isReplyInContext(status: Status) -> Bool {
|
func isReplyInContext(status: Status) -> Bool {
|
||||||
let flatStatusIDs = statusIDs.reduce([], +)
|
|
||||||
|
|
||||||
guard
|
guard
|
||||||
let index = flatStatusIDs.firstIndex(where: { $0 == status.id }),
|
let index = flatStatusIDs.firstIndex(where: { $0 == status.id }),
|
||||||
index > 0
|
index > 0
|
||||||
|
@ -166,8 +194,6 @@ private extension StatusListViewModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
func hasReplyFollowing(status: Status) -> Bool {
|
func hasReplyFollowing(status: Status) -> Bool {
|
||||||
let flatStatusIDs = statusIDs.reduce([], +)
|
|
||||||
|
|
||||||
guard
|
guard
|
||||||
let index = flatStatusIDs.firstIndex(where: { $0 == status.id }),
|
let index = flatStatusIDs.firstIndex(where: { $0 == status.id }),
|
||||||
flatStatusIDs.count > index + 1,
|
flatStatusIDs.count > index + 1,
|
||||||
|
|
|
@ -53,6 +53,7 @@ public extension StatusViewModel {
|
||||||
enum Event {
|
enum Event {
|
||||||
case ignorableOutput
|
case ignorableOutput
|
||||||
case navigation(URLItem)
|
case navigation(URLItem)
|
||||||
|
case accountListNavigation(AccountListViewModel)
|
||||||
case share(URL)
|
case share(URL)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -130,6 +131,10 @@ public extension StatusViewModel {
|
||||||
.eraseToAnyPublisher())
|
.eraseToAnyPublisher())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func favoritedBySelected() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func toggleFavorited() {
|
func toggleFavorited() {
|
||||||
eventsSubject.send(statusService.toggleFavorited().map { _ in Event.ignorableOutput }.eraseToAnyPublisher())
|
eventsSubject.send(statusService.toggleFavorited().map { _ in Event.ignorableOutput }.eraseToAnyPublisher())
|
||||||
}
|
}
|
||||||
|
|
18
Views/AccountContentConfiguration.swift
Normal file
18
Views/AccountContentConfiguration.swift
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import ViewModels
|
||||||
|
|
||||||
|
struct AccountContentConfiguration {
|
||||||
|
let viewModel: AccountViewModel
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AccountContentConfiguration: UIContentConfiguration {
|
||||||
|
func makeContentView() -> UIView & UIContentView {
|
||||||
|
AccountView(configuration: self)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updated(for state: UIConfigurationState) -> AccountContentConfiguration {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
23
Views/AccountListCell.swift
Normal file
23
Views/AccountListCell.swift
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import ViewModels
|
||||||
|
|
||||||
|
class AccountListCell: UITableViewCell {
|
||||||
|
var viewModel: AccountViewModel?
|
||||||
|
|
||||||
|
override func updateConfiguration(using state: UICellConfigurationState) {
|
||||||
|
guard let viewModel = viewModel else { return }
|
||||||
|
|
||||||
|
contentConfiguration = AccountContentConfiguration(viewModel: viewModel).updated(for: state)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func layoutSubviews() {
|
||||||
|
super.layoutSubviews()
|
||||||
|
|
||||||
|
let isPhoneIdiom = UIDevice.current.userInterfaceIdiom == .phone
|
||||||
|
|
||||||
|
separatorInset.right = isPhoneIdiom ? 0 : layoutMargins.right
|
||||||
|
separatorInset.left = isPhoneIdiom ? 0 : layoutMargins.left
|
||||||
|
}
|
||||||
|
}
|
64
Views/AccountView.swift
Normal file
64
Views/AccountView.swift
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import Kingfisher
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class AccountView: UIView {
|
||||||
|
let avatarImageView = AnimatedImageView()
|
||||||
|
let noteTextView = TouchFallthroughTextView()
|
||||||
|
|
||||||
|
private var accountConfiguration: AccountContentConfiguration
|
||||||
|
|
||||||
|
init(configuration: AccountContentConfiguration) {
|
||||||
|
self.accountConfiguration = configuration
|
||||||
|
|
||||||
|
super.init(frame: .zero)
|
||||||
|
|
||||||
|
initialSetup()
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable)
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AccountView: UIContentView {
|
||||||
|
var configuration: UIContentConfiguration {
|
||||||
|
get { accountConfiguration }
|
||||||
|
set {
|
||||||
|
guard let accountConfiguration = newValue as? AccountContentConfiguration else { return }
|
||||||
|
|
||||||
|
self.accountConfiguration = accountConfiguration
|
||||||
|
|
||||||
|
avatarImageView.kf.cancelDownloadTask()
|
||||||
|
applyAccountConfiguration()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension AccountView {
|
||||||
|
func initialSetup() {
|
||||||
|
let baseStackView = UIStackView()
|
||||||
|
|
||||||
|
addSubview(baseStackView)
|
||||||
|
baseStackView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
baseStackView.addArrangedSubview(avatarImageView)
|
||||||
|
baseStackView.addArrangedSubview(noteTextView)
|
||||||
|
noteTextView.isScrollEnabled = false
|
||||||
|
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
baseStackView.topAnchor.constraint(equalTo: readableContentGuide.topAnchor),
|
||||||
|
baseStackView.leadingAnchor.constraint(equalTo: readableContentGuide.leadingAnchor),
|
||||||
|
baseStackView.trailingAnchor.constraint(equalTo: readableContentGuide.trailingAnchor),
|
||||||
|
baseStackView.bottomAnchor.constraint(equalTo: readableContentGuide.bottomAnchor)
|
||||||
|
])
|
||||||
|
|
||||||
|
applyAccountConfiguration()
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyAccountConfiguration() {
|
||||||
|
avatarImageView.kf.setImage(with: accountConfiguration.viewModel.avatarURL)
|
||||||
|
noteTextView.attributedText = accountConfiguration.viewModel.note
|
||||||
|
}
|
||||||
|
}
|
26
Views/CollectionView.swift
Normal file
26
Views/CollectionView.swift
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import ViewModels
|
||||||
|
|
||||||
|
struct CollectionView: UIViewControllerRepresentable {
|
||||||
|
let viewModel: CollectionViewModel
|
||||||
|
|
||||||
|
func makeUIViewController(context: Context) -> CollectionViewController {
|
||||||
|
CollectionViewController(viewModel: viewModel)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateUIViewController(_ uiViewController: CollectionViewController, context: Context) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
import PreviewViewModels
|
||||||
|
|
||||||
|
struct StatusListView_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
CollectionView(viewModel: NavigationViewModel(identification: .preview).viewModel(timeline: .home))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
|
@ -148,15 +148,20 @@ private extension StatusView {
|
||||||
let accountAction = UIAction { [weak self] _ in self?.statusConfiguration.viewModel.accountSelected() }
|
let accountAction = UIAction { [weak self] _ in self?.statusConfiguration.viewModel.accountSelected() }
|
||||||
|
|
||||||
avatarButton.addAction(accountAction, for: .touchUpInside)
|
avatarButton.addAction(accountAction, for: .touchUpInside)
|
||||||
|
contextParentAvatarButton.addAction(accountAction, for: .touchUpInside)
|
||||||
|
|
||||||
let favoriteAction = UIAction { [weak self] _ in self?.statusConfiguration.viewModel.toggleFavorited() }
|
let favoriteAction = UIAction { [weak self] _ in self?.statusConfiguration.viewModel.toggleFavorited() }
|
||||||
|
|
||||||
favoriteButton.addAction(favoriteAction, for: .touchUpInside)
|
favoriteButton.addAction(favoriteAction, for: .touchUpInside)
|
||||||
contextParentFavoriteButton.addAction(favoriteAction, for: .touchUpInside)
|
contextParentFavoriteButton.addAction(favoriteAction, for: .touchUpInside)
|
||||||
|
|
||||||
let shareAction = UIAction { [weak self] _ in self?.statusConfiguration.viewModel.shareStatus() }
|
shareButton.addAction(
|
||||||
|
UIAction { [weak self] _ in self?.statusConfiguration.viewModel.shareStatus() },
|
||||||
|
for: .touchUpInside)
|
||||||
|
|
||||||
shareButton.addAction(shareAction, for: .touchUpInside)
|
contextParentFavoritedByButton.addAction(
|
||||||
|
UIAction { [weak self] _ in self?.statusConfiguration.viewModel.favoritedBySelected() },
|
||||||
|
for: .touchUpInside)
|
||||||
|
|
||||||
applyStatusConfiguration()
|
applyStatusConfiguration()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,26 +0,0 @@
|
||||||
// Copyright © 2020 Metabolist. All rights reserved.
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
import ViewModels
|
|
||||||
|
|
||||||
struct StatusListView: UIViewControllerRepresentable {
|
|
||||||
let viewModel: StatusListViewModel
|
|
||||||
|
|
||||||
func makeUIViewController(context: Context) -> StatusListViewController {
|
|
||||||
StatusListViewController(viewModel: viewModel)
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateUIViewController(_ uiViewController: StatusListViewController, context: Context) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
import PreviewViewModels
|
|
||||||
|
|
||||||
struct StatusListView_Previews: PreviewProvider {
|
|
||||||
static var previews: some View {
|
|
||||||
StatusListView(viewModel: NavigationViewModel(identification: .preview).viewModel(timeline: .home))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
|
@ -61,7 +61,7 @@ private extension TabNavigationView {
|
||||||
func view(tab: NavigationViewModel.Tab) -> some View {
|
func view(tab: NavigationViewModel.Tab) -> some View {
|
||||||
switch tab {
|
switch tab {
|
||||||
case .timelines:
|
case .timelines:
|
||||||
StatusListView(viewModel: viewModel.viewModel(timeline: viewModel.timeline))
|
CollectionView(viewModel: viewModel.viewModel(timeline: viewModel.timeline))
|
||||||
.id(viewModel.timeline.id)
|
.id(viewModel.timeline.id)
|
||||||
.edgesIgnoringSafeArea(.all)
|
.edgesIgnoringSafeArea(.all)
|
||||||
.navigationTitle(viewModel.timeline.title)
|
.navigationTitle(viewModel.timeline.title)
|
||||||
|
|
Loading…
Reference in a new issue