chore: remove Texture pod

This commit is contained in:
CMK 2021-12-28 16:15:44 +08:00
parent b3ec9ee171
commit 7711564cdd
26 changed files with 10 additions and 3257 deletions

View File

@ -183,7 +183,6 @@
DB0140CF25C42AEE00F9F3CF /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0140CE25C42AEE00F9F3CF /* OSLog.swift */; };
DB01E23326A98F0900C3965B /* MastodonMeta in Frameworks */ = {isa = PBXBuildFile; productRef = DB01E23226A98F0900C3965B /* MastodonMeta */; };
DB01E23526A98F0900C3965B /* MetaTextKit in Frameworks */ = {isa = PBXBuildFile; productRef = DB01E23426A98F0900C3965B /* MetaTextKit */; };
DB023295267F0AB800031745 /* ASMetaEditableTextNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB023294267F0AB800031745 /* ASMetaEditableTextNode.swift */; };
DB029E95266A20430062874E /* MastodonAuthenticationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB029E94266A20430062874E /* MastodonAuthenticationController.swift */; };
DB02CDAB26256A9500D0A2AF /* ThreadReplyLoaderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB02CDAA26256A9500D0A2AF /* ThreadReplyLoaderTableViewCell.swift */; };
DB02CDBF2625AE5000D0A2AF /* AdaptiveUserInterfaceStyleBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB02CDBE2625AE5000D0A2AF /* AdaptiveUserInterfaceStyleBarButtonItem.swift */; };
@ -215,7 +214,6 @@
DB1D84382657B275000346B3 /* SegmentedControlNavigateable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1D84372657B275000346B3 /* SegmentedControlNavigateable.swift */; };
DB1E346825F518E20079D7DF /* CategoryPickerSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1E346725F518E20079D7DF /* CategoryPickerSection.swift */; };
DB1E347825F519300079D7DF /* PickServerItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1E347725F519300079D7DF /* PickServerItem.swift */; };
DB1EE7B2267F9525000CC337 /* StatusProvider+StatusNodeDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1EE7B1267F9525000CC337 /* StatusProvider+StatusNodeDelegate.swift */; };
DB1FD43625F26899004CFCFC /* MastodonPickServerViewModel+LoadIndexedServerState.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1FD43525F26899004CFCFC /* MastodonPickServerViewModel+LoadIndexedServerState.swift */; };
DB1FD44425F26CCC004CFCFC /* PickServerSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1FD44325F26CCC004CFCFC /* PickServerSection.swift */; };
DB1FD45025F26FA1004CFCFC /* MastodonPickServerViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1FD44F25F26FA1004CFCFC /* MastodonPickServerViewModel+Diffable.swift */; };
@ -482,12 +480,6 @@
DBA94440265D137600C537E1 /* Mastodon+Entity+Field.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBA9443F265D137600C537E1 /* Mastodon+Entity+Field.swift */; };
DBABE3EC25ECAC4B00879EE5 /* WelcomeIllustrationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBABE3EB25ECAC4B00879EE5 /* WelcomeIllustrationView.swift */; };
DBAC6483267D0B21007FE9FD /* DifferenceKit in Frameworks */ = {isa = PBXBuildFile; productRef = DBAC6482267D0B21007FE9FD /* DifferenceKit */; };
DBAC6485267D0F9E007FE9FD /* StatusNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAC6484267D0F9E007FE9FD /* StatusNode.swift */; };
DBAC6488267D388B007FE9FD /* ASTableNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAC6487267D388B007FE9FD /* ASTableNode.swift */; };
DBAC648F267DC84D007FE9FD /* TableNodeDiffableDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAC648E267DC84D007FE9FD /* TableNodeDiffableDataSource.swift */; };
DBAC6497267DECCB007FE9FD /* TimelineMiddleLoaderNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAC6496267DECCB007FE9FD /* TimelineMiddleLoaderNode.swift */; };
DBAC6499267DF2C4007FE9FD /* TimelineBottomLoaderNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAC6498267DF2C4007FE9FD /* TimelineBottomLoaderNode.swift */; };
DBAC649B267DF8C8007FE9FD /* ActivityIndicatorNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAC649A267DF8C8007FE9FD /* ActivityIndicatorNode.swift */; };
DBAC649E267DFE43007FE9FD /* DiffableDataSources in Frameworks */ = {isa = PBXBuildFile; productRef = DBAC649D267DFE43007FE9FD /* DiffableDataSources */; };
DBAC64A1267E6D02007FE9FD /* Fuzi in Frameworks */ = {isa = PBXBuildFile; productRef = DBAC64A0267E6D02007FE9FD /* Fuzi */; };
DBAE3F682615DD60004B8251 /* UserProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAE3F672615DD60004B8251 /* UserProvider.swift */; };
@ -569,14 +561,6 @@
DBC7A672260C897100E57475 /* StatusContentWarningEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBC7A671260C897100E57475 /* StatusContentWarningEditorView.swift */; };
DBC7A67C260DFADE00E57475 /* StatusPublishService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBC7A67B260DFADE00E57475 /* StatusPublishService.swift */; };
DBCBCBF4267CB070000F5B51 /* Decode85.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBCBCBF3267CB070000F5B51 /* Decode85.swift */; };
DBCBCBFC2680ADB7000F5B51 /* AsyncHomeTimelineViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBCBCBFB2680ADB7000F5B51 /* AsyncHomeTimelineViewController.swift */; };
DBCBCBFF2680AE98000F5B51 /* AsyncHomeTimelineViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBCBCBFE2680AE98000F5B51 /* AsyncHomeTimelineViewModel.swift */; };
DBCBCC012680AF2A000F5B51 /* AsyncHomeTimelineViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBCBCC002680AF2A000F5B51 /* AsyncHomeTimelineViewModel+Diffable.swift */; };
DBCBCC032680AF6E000F5B51 /* AsyncHomeTimelineViewController+DebugAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBCBCC022680AF6E000F5B51 /* AsyncHomeTimelineViewController+DebugAction.swift */; };
DBCBCC052680AFB9000F5B51 /* AsyncHomeTimelineViewController+Provider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBCBCC042680AFB9000F5B51 /* AsyncHomeTimelineViewController+Provider.swift */; };
DBCBCC072680AFEC000F5B51 /* AsyncHomeTimelineViewModel+LoadLatestState.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBCBCC062680AFEC000F5B51 /* AsyncHomeTimelineViewModel+LoadLatestState.swift */; };
DBCBCC092680B01B000F5B51 /* AsyncHomeTimelineViewModel+LoadMiddleState.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBCBCC082680B01B000F5B51 /* AsyncHomeTimelineViewModel+LoadMiddleState.swift */; };
DBCBCC0B2680B03F000F5B51 /* AsyncHomeTimelineViewModel+LoadOldestState.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBCBCC0A2680B03F000F5B51 /* AsyncHomeTimelineViewModel+LoadOldestState.swift */; };
DBCBCC0D2680B908000F5B51 /* HomeTimelinePreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBCBCC0C2680B908000F5B51 /* HomeTimelinePreference.swift */; };
DBCBED1726132DB500B49291 /* UserTimelineViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBCBED1626132DB500B49291 /* UserTimelineViewModel+Diffable.swift */; };
DBCBED1D26132E1A00B49291 /* StatusFetchedResultsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBCBED1C26132E1A00B49291 /* StatusFetchedResultsController.swift */; };
@ -977,7 +961,6 @@
DB0009A826AEE5DC009B9D2D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; name = Base; path = Base.lproj/Intents.intentdefinition; sourceTree = "<group>"; };
DB0009AD26AEE5E4009B9D2D /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/Intents.strings; sourceTree = "<group>"; };
DB0140CE25C42AEE00F9F3CF /* OSLog.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OSLog.swift; sourceTree = "<group>"; };
DB023294267F0AB800031745 /* ASMetaEditableTextNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ASMetaEditableTextNode.swift; sourceTree = "<group>"; };
DB029E94266A20430062874E /* MastodonAuthenticationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonAuthenticationController.swift; sourceTree = "<group>"; };
DB02CDAA26256A9500D0A2AF /* ThreadReplyLoaderTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadReplyLoaderTableViewCell.swift; sourceTree = "<group>"; };
DB02CDBE2625AE5000D0A2AF /* AdaptiveUserInterfaceStyleBarButtonItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdaptiveUserInterfaceStyleBarButtonItem.swift; sourceTree = "<group>"; };
@ -1009,7 +992,6 @@
DB1D84372657B275000346B3 /* SegmentedControlNavigateable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SegmentedControlNavigateable.swift; sourceTree = "<group>"; };
DB1E346725F518E20079D7DF /* CategoryPickerSection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CategoryPickerSection.swift; sourceTree = "<group>"; };
DB1E347725F519300079D7DF /* PickServerItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PickServerItem.swift; sourceTree = "<group>"; };
DB1EE7B1267F9525000CC337 /* StatusProvider+StatusNodeDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StatusProvider+StatusNodeDelegate.swift"; sourceTree = "<group>"; };
DB1FD43525F26899004CFCFC /* MastodonPickServerViewModel+LoadIndexedServerState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonPickServerViewModel+LoadIndexedServerState.swift"; sourceTree = "<group>"; };
DB1FD44325F26CCC004CFCFC /* PickServerSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerSection.swift; sourceTree = "<group>"; };
DB1FD44F25F26FA1004CFCFC /* MastodonPickServerViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonPickServerViewModel+Diffable.swift"; sourceTree = "<group>"; };
@ -1329,12 +1311,6 @@
DBA9443D265CFA6400C537E1 /* ProfileFieldCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileFieldCollectionViewCell.swift; sourceTree = "<group>"; };
DBA9443F265D137600C537E1 /* Mastodon+Entity+Field.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Mastodon+Entity+Field.swift"; sourceTree = "<group>"; };
DBABE3EB25ECAC4B00879EE5 /* WelcomeIllustrationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeIllustrationView.swift; sourceTree = "<group>"; };
DBAC6484267D0F9E007FE9FD /* StatusNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusNode.swift; sourceTree = "<group>"; };
DBAC6487267D388B007FE9FD /* ASTableNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ASTableNode.swift; sourceTree = "<group>"; };
DBAC648E267DC84D007FE9FD /* TableNodeDiffableDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableNodeDiffableDataSource.swift; sourceTree = "<group>"; };
DBAC6496267DECCB007FE9FD /* TimelineMiddleLoaderNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineMiddleLoaderNode.swift; sourceTree = "<group>"; };
DBAC6498267DF2C4007FE9FD /* TimelineBottomLoaderNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineBottomLoaderNode.swift; sourceTree = "<group>"; };
DBAC649A267DF8C8007FE9FD /* ActivityIndicatorNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityIndicatorNode.swift; sourceTree = "<group>"; };
DBAE3F672615DD60004B8251 /* UserProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProvider.swift; sourceTree = "<group>"; };
DBAE3F812615DDA3004B8251 /* ProfileViewController+UserProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileViewController+UserProvider.swift"; sourceTree = "<group>"; };
DBAE3F872615DDF4004B8251 /* UserProviderFacade.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProviderFacade.swift; sourceTree = "<group>"; };
@ -1385,14 +1361,6 @@
DBC7A671260C897100E57475 /* StatusContentWarningEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusContentWarningEditorView.swift; sourceTree = "<group>"; };
DBC7A67B260DFADE00E57475 /* StatusPublishService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusPublishService.swift; sourceTree = "<group>"; };
DBCBCBF3267CB070000F5B51 /* Decode85.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Decode85.swift; sourceTree = "<group>"; };
DBCBCBFB2680ADB7000F5B51 /* AsyncHomeTimelineViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncHomeTimelineViewController.swift; sourceTree = "<group>"; };
DBCBCBFE2680AE98000F5B51 /* AsyncHomeTimelineViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncHomeTimelineViewModel.swift; sourceTree = "<group>"; };
DBCBCC002680AF2A000F5B51 /* AsyncHomeTimelineViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AsyncHomeTimelineViewModel+Diffable.swift"; sourceTree = "<group>"; };
DBCBCC022680AF6E000F5B51 /* AsyncHomeTimelineViewController+DebugAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AsyncHomeTimelineViewController+DebugAction.swift"; sourceTree = "<group>"; };
DBCBCC042680AFB9000F5B51 /* AsyncHomeTimelineViewController+Provider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AsyncHomeTimelineViewController+Provider.swift"; sourceTree = "<group>"; };
DBCBCC062680AFEC000F5B51 /* AsyncHomeTimelineViewModel+LoadLatestState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AsyncHomeTimelineViewModel+LoadLatestState.swift"; sourceTree = "<group>"; };
DBCBCC082680B01B000F5B51 /* AsyncHomeTimelineViewModel+LoadMiddleState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AsyncHomeTimelineViewModel+LoadMiddleState.swift"; sourceTree = "<group>"; };
DBCBCC0A2680B03F000F5B51 /* AsyncHomeTimelineViewModel+LoadOldestState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AsyncHomeTimelineViewModel+LoadOldestState.swift"; sourceTree = "<group>"; };
DBCBCC0C2680B908000F5B51 /* HomeTimelinePreference.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeTimelinePreference.swift; sourceTree = "<group>"; };
DBCBED1626132DB500B49291 /* UserTimelineViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserTimelineViewModel+Diffable.swift"; sourceTree = "<group>"; };
DBCBED1C26132E1A00B49291 /* StatusFetchedResultsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusFetchedResultsController.swift; sourceTree = "<group>"; };
@ -1737,7 +1705,6 @@
isa = PBXGroup;
children = (
DB1F239626117C360057430E /* View */,
DBCBCBFD2680ADBA000F5B51 /* AsyncHomeTimeline */,
2D38F1D425CD465300561493 /* HomeTimelineViewController.swift */,
2D38F1DE25CD46A400561493 /* HomeTimelineViewController+Provider.swift */,
2D5A3D6125CFD9CB002347D6 /* HomeTimelineViewController+DebugAction.swift */,
@ -1756,7 +1723,6 @@
2D38F1FD25CD481700561493 /* StatusProvider.swift */,
2DF75B9A25D0E27500694EC8 /* StatusProviderFacade.swift */,
2DF75BA025D0E29D00694EC8 /* StatusProvider+StatusTableViewCellDelegate.swift */,
DB1EE7B1267F9525000CC337 /* StatusProvider+StatusNodeDelegate.swift */,
DB59F0FD25EF5D96001F1DAB /* StatusProvider+UITableViewDelegate.swift */,
DB71FD4525F8C6D200512AE1 /* StatusProvider+UITableViewDataSourcePrefetching.swift */,
DB1D843526579DB5000346B3 /* StatusProvider+TableViewControllerNavigateable.swift */,
@ -1811,7 +1777,6 @@
DB51D171262832380062B7A1 /* BlurHashEncode.swift */,
DB6180EC26391C6C0018D199 /* TransitioningMath.swift */,
DB75BF1D263C1C1B00EDBF1F /* CustomScheduler.swift */,
DBAC649A267DF8C8007FE9FD /* ActivityIndicatorNode.swift */,
DBF156E32702DB3F00EC00B7 /* HandleTapAction.swift */,
DBF156E12702DA6900EC00B7 /* UIStatusBarManager+HandleTapAction.m */,
DBF156E02702DA6800EC00B7 /* Mastodon-Bridging-Header.h */,
@ -1893,7 +1858,6 @@
2D76319D25C151F600929FB9 /* Section */,
2D7631B125C159E700929FB9 /* Item */,
DBCBED2226132E1D00B49291 /* FetchedResultsController */,
DBAC6490267DC84F007FE9FD /* DataSource */,
);
path = Diffiable;
sourceTree = "<group>";
@ -1938,7 +1902,6 @@
DB87D45C2609DE6600D12C0D /* TextField */,
DB1D187125EF5BBD003F1F23 /* TableView */,
2D7631A625C1533800929FB9 /* TableviewCell */,
DBAC6486267D0FAC007FE9FD /* Node */,
);
path = View;
sourceTree = "<group>";
@ -2120,16 +2083,6 @@
path = Onboarding;
sourceTree = "<group>";
};
DB023296267F0ABE00031745 /* Status */ = {
isa = PBXGroup;
children = (
DBAC6484267D0F9E007FE9FD /* StatusNode.swift */,
DBAC6496267DECCB007FE9FD /* TimelineMiddleLoaderNode.swift */,
DBAC6498267DF2C4007FE9FD /* TimelineBottomLoaderNode.swift */,
);
path = Status;
sourceTree = "<group>";
};
DB03F7F1268990A2007B274C /* TableViewCell */ = {
isa = PBXGroup;
children = (
@ -3044,24 +2997,6 @@
path = View;
sourceTree = "<group>";
};
DBAC6486267D0FAC007FE9FD /* Node */ = {
isa = PBXGroup;
children = (
DB023296267F0ABE00031745 /* Status */,
DB023294267F0AB800031745 /* ASMetaEditableTextNode.swift */,
);
path = Node;
sourceTree = "<group>";
};
DBAC6490267DC84F007FE9FD /* DataSource */ = {
isa = PBXGroup;
children = (
DBAC6487267D388B007FE9FD /* ASTableNode.swift */,
DBAC648E267DC84D007FE9FD /* TableNodeDiffableDataSource.swift */,
);
path = DataSource;
sourceTree = "<group>";
};
DBAE3F742615DD63004B8251 /* UserProvider */ = {
isa = PBXGroup;
children = (
@ -3187,21 +3122,6 @@
path = ShareActionExtension;
sourceTree = "<group>";
};
DBCBCBFD2680ADBA000F5B51 /* AsyncHomeTimeline */ = {
isa = PBXGroup;
children = (
DBCBCBFB2680ADB7000F5B51 /* AsyncHomeTimelineViewController.swift */,
DBCBCC022680AF6E000F5B51 /* AsyncHomeTimelineViewController+DebugAction.swift */,
DBCBCC042680AFB9000F5B51 /* AsyncHomeTimelineViewController+Provider.swift */,
DBCBCBFE2680AE98000F5B51 /* AsyncHomeTimelineViewModel.swift */,
DBCBCC002680AF2A000F5B51 /* AsyncHomeTimelineViewModel+Diffable.swift */,
DBCBCC062680AFEC000F5B51 /* AsyncHomeTimelineViewModel+LoadLatestState.swift */,
DBCBCC082680B01B000F5B51 /* AsyncHomeTimelineViewModel+LoadMiddleState.swift */,
DBCBCC0A2680B03F000F5B51 /* AsyncHomeTimelineViewModel+LoadOldestState.swift */,
);
path = AsyncHomeTimeline;
sourceTree = "<group>";
};
DBCBED2226132E1D00B49291 /* FetchedResultsController */ = {
isa = PBXGroup;
children = (
@ -3969,7 +3889,6 @@
0FAA0FDF25E0B57E0017CCDE /* WelcomeViewController.swift in Sources */,
2D206B8C25F6015000143C56 /* AudioPlaybackService.swift in Sources */,
2D59819B25E4A581000FB903 /* MastodonConfirmEmailViewController.swift in Sources */,
DBCBCBFC2680ADB7000F5B51 /* AsyncHomeTimelineViewController.swift in Sources */,
DB45FB1D25CA9D23005A8AC7 /* APIService+HomeTimeline.swift in Sources */,
DBA94440265D137600C537E1 /* Mastodon+Entity+Field.swift in Sources */,
DB49A61425FF2C5600B98345 /* EmojiService.swift in Sources */,
@ -4016,12 +3935,10 @@
DB02CDAB26256A9500D0A2AF /* ThreadReplyLoaderTableViewCell.swift in Sources */,
DB63BE7F268DD1070011D3F9 /* NotificationViewController+StatusProvider.swift in Sources */,
DBB5255E2611F07A002F1F29 /* ProfileViewModel.swift in Sources */,
DBAC648F267DC84D007FE9FD /* TableNodeDiffableDataSource.swift in Sources */,
2D8434FB25FF46B300EECE90 /* HomeTimelineNavigationBarTitleView.swift in Sources */,
0F1E2D0B2615C39400C38565 /* DoubleTitleLabelNavigationBarTitleView.swift in Sources */,
DBA1DB80268F84F80052DB59 /* NotificationType.swift in Sources */,
DB9A488A26034D40008B817C /* ComposeViewModel+PublishState.swift in Sources */,
DBAC649B267DF8C8007FE9FD /* ActivityIndicatorNode.swift in Sources */,
DB45FAD725CA6C76005A8AC7 /* UIBarButtonItem.swift in Sources */,
2DA504692601ADE7008F4E6C /* SawToothView.swift in Sources */,
DBA465952696E387002B41DB /* AppPreference.swift in Sources */,
@ -4091,12 +4008,9 @@
2D939AB525EDD8A90076FA61 /* String.swift in Sources */,
DB4481B925EE289600BEFB67 /* UITableView.swift in Sources */,
DBE3CDBB261C427900430CC6 /* TimelineHeaderTableViewCell.swift in Sources */,
DBCBCBFF2680AE98000F5B51 /* AsyncHomeTimelineViewModel.swift in Sources */,
0FAA101C25E10E760017CCDE /* UIFont.swift in Sources */,
2D38F1D525CD465300561493 /* HomeTimelineViewController.swift in Sources */,
DBAC6497267DECCB007FE9FD /* TimelineMiddleLoaderNode.swift in Sources */,
DB97131F2666078B00BD1E90 /* Date.swift in Sources */,
DBAC6485267D0F9E007FE9FD /* StatusNode.swift in Sources */,
DB98338825C945ED00AD9700 /* Assets.swift in Sources */,
DB6180E926391BDF0018D199 /* MediaHostToMediaPreviewViewControllerAnimatedTransitioning.swift in Sources */,
DB6B351E2601FAEE00DC1E11 /* ComposeStatusAttachmentCollectionViewCell.swift in Sources */,
@ -4156,7 +4070,6 @@
DBA5E7A5263BD28C004598BB /* ContextMenuImagePreviewViewModel.swift in Sources */,
DB9D7C21269824B80054B3DF /* APIService+Filter.swift in Sources */,
2D38F1E525CD46C100561493 /* HomeTimelineViewModel.swift in Sources */,
DBCBCC012680AF2A000F5B51 /* AsyncHomeTimelineViewModel+Diffable.swift in Sources */,
DBCC3B36261440BA0045B23D /* UINavigationController.swift in Sources */,
DBB525852612D6DD002F1F29 /* ProfileStatusDashboardMeterView.swift in Sources */,
2D84350525FF858100EECE90 /* UIScrollView.swift in Sources */,
@ -4185,7 +4098,6 @@
2D694A7425F9EB4E0038ADDC /* ContentWarningOverlayView.swift in Sources */,
DB4932B126F1FB5300EF46D4 /* WizardCardView.swift in Sources */,
DBAE3F682615DD60004B8251 /* UserProvider.swift in Sources */,
DBAC6488267D388B007FE9FD /* ASTableNode.swift in Sources */,
DB6D9F76263587C7008423CD /* SettingFetchedResultController.swift in Sources */,
DB9A486C26032AC1008B817C /* AttachmentContainerView+EmptyStateView.swift in Sources */,
5D0393902612D259007FE196 /* WebViewController.swift in Sources */,
@ -4220,7 +4132,6 @@
DB1D186C25EF5BA7003F1F23 /* PollTableView.swift in Sources */,
DBA94434265CBB5300C537E1 /* ProfileFieldSection.swift in Sources */,
DBF156E42702DB3F00EC00B7 /* HandleTapAction.swift in Sources */,
DB023295267F0AB800031745 /* ASMetaEditableTextNode.swift in Sources */,
2D5981A125E4A593000FB903 /* MastodonConfirmEmailViewModel.swift in Sources */,
DB6B74F6272FBCDB00C70B6E /* FollowerListViewModel+State.swift in Sources */,
DB4F096C269EFA2000D62E92 /* SearchResultViewController+StatusProvider.swift in Sources */,
@ -4248,7 +4159,6 @@
DB938EED2623F79B00E5B6C1 /* ThreadViewModel.swift in Sources */,
DBBC24AC26A53D9300398BB9 /* ComposeStatusContentTableViewCell.swift in Sources */,
DBC7A67C260DFADE00E57475 /* StatusPublishService.swift in Sources */,
DBCBCC092680B01B000F5B51 /* AsyncHomeTimelineViewModel+LoadMiddleState.swift in Sources */,
2DCB73FD2615C13900EC03D4 /* SearchRecommendCollectionHeader.swift in Sources */,
DB852D1C26FB021500FC9D81 /* RootSplitViewController.swift in Sources */,
DB02CDBF2625AE5000D0A2AF /* AdaptiveUserInterfaceStyleBarButtonItem.swift in Sources */,
@ -4264,7 +4174,6 @@
0FB3D33825E6401400AAD544 /* PickServerCell.swift in Sources */,
DB6D9F8426358EEC008423CD /* SettingsItem.swift in Sources */,
2D364F7825E66D8300204FDC /* MastodonResendEmailViewModel.swift in Sources */,
DBCBCC052680AFB9000F5B51 /* AsyncHomeTimelineViewController+Provider.swift in Sources */,
DBA465932696B495002B41DB /* APIService+WebFinger.swift in Sources */,
DB8AF54525C13647002E6C99 /* NeedsDependency.swift in Sources */,
DB9D6BF825E4F5690051B173 /* NotificationViewController.swift in Sources */,
@ -4301,7 +4210,6 @@
2DB72C8C262D764300CE6173 /* Mastodon+Entity+Notification+Type.swift in Sources */,
2D35237A26256D920031AF25 /* NotificationSection.swift in Sources */,
DB084B5725CBC56C00F898ED /* Status.swift in Sources */,
DBCBCC072680AFEC000F5B51 /* AsyncHomeTimelineViewModel+LoadLatestState.swift in Sources */,
2D4AD89C263165B500613EFC /* SuggestionAccountCollectionViewCell.swift in Sources */,
DB447691260B406600B66B82 /* CustomEmojiPickerItemCollectionViewCell.swift in Sources */,
DB9282B225F3222800823B15 /* PickServerEmptyStateView.swift in Sources */,
@ -4326,7 +4234,6 @@
DB0C947226A7D2D70088FB11 /* AvatarButton.swift in Sources */,
DB98336B25C9420100AD9700 /* APIService+App.swift in Sources */,
DBFEF07B26A6BCE8006D7ED1 /* APIService+Status+Publish.swift in Sources */,
DBCBCC032680AF6E000F5B51 /* AsyncHomeTimelineViewController+DebugAction.swift in Sources */,
DBA0A11325FB3FC10079C110 /* ComposeToolbarView.swift in Sources */,
2D32EADA25CBCC3300C9ED86 /* PublicTimelineViewModel+LoadMiddleState.swift in Sources */,
5B90C48526259BF10002E742 /* APIService+Subscriptions.swift in Sources */,
@ -4359,7 +4266,6 @@
DB71C7CD271D7F4300BE3819 /* CurveAlgorithm.swift in Sources */,
DBD376B2269302A4007FEC24 /* UITableViewCell.swift in Sources */,
DB4F0966269ED52200D62E92 /* SearchResultViewModel.swift in Sources */,
DBAC6499267DF2C4007FE9FD /* TimelineBottomLoaderNode.swift in Sources */,
2D76318325C14E8F00929FB9 /* PublicTimelineViewModel+Diffable.swift in Sources */,
DBBF1DBF2652401B00E5B703 /* AutoCompleteViewModel.swift in Sources */,
DB6180FA26391F2E0018D199 /* MediaPreviewViewModel.swift in Sources */,
@ -4390,7 +4296,6 @@
DB4F097B26A039FF00D62E92 /* SearchHistorySection.swift in Sources */,
DBB525302611EBF3002F1F29 /* ProfilePagingViewModel.swift in Sources */,
DB9F58EC26EF435000E7BBE9 /* AccountViewController.swift in Sources */,
DBCBCC0B2680B03F000F5B51 /* AsyncHomeTimelineViewModel+LoadOldestState.swift in Sources */,
2D5A3D6225CFD9CB002347D6 /* HomeTimelineViewController+DebugAction.swift in Sources */,
DB49A62525FF334C00B98345 /* EmojiService+CustomEmojiViewModel+LoadState.swift in Sources */,
DB4924E226312AB200E9DB22 /* NotificationService.swift in Sources */,
@ -4409,7 +4314,6 @@
DB3667A4268AE2370027D07F /* ComposeStatusPollTableViewCell.swift in Sources */,
DBBF1DC226524D2900E5B703 /* AutoCompleteTableViewCell.swift in Sources */,
2D38F1FE25CD481700561493 /* StatusProvider.swift in Sources */,
DB1EE7B2267F9525000CC337 /* StatusProvider+StatusNodeDelegate.swift in Sources */,
5B24BBE2262DB19100A9381B /* APIService+Report.swift in Sources */,
DBF3B7412733EB9400E21627 /* MastodonLocalCode.swift in Sources */,
DB4F096A269EDAD200D62E92 /* SearchResultViewModel+State.swift in Sources */,
@ -5232,56 +5136,6 @@
};
name = Debug;
};
DB8FABD126AEC7B2008E5AF4 /* ASDK - Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = C3789232A52F43529CA67E95 /* Pods-MastodonIntent.asdk - debug.xcconfig */;
buildSettings = {
CODE_SIGN_ENTITLEMENTS = MastodonIntent/MastodonIntent.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 88;
DEVELOPMENT_TEAM = 5Z4GVSS33P;
INFOPLIST_FILE = MastodonIntent/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.0.7;
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.MastodonIntent;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "APP_EXTENSION $(inherited)";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 1;
VERSIONING_SYSTEM = "apple-generic";
};
name = "ASDK - Debug";
};
DB8FABD226AEC7B2008E5AF4 /* ASDK - Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = F920AD4EC23B0D00F5CCA58E /* Pods-MastodonIntent.asdk - release.xcconfig */;
buildSettings = {
CODE_SIGN_ENTITLEMENTS = MastodonIntent/MastodonIntent.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 88;
DEVELOPMENT_TEAM = 5Z4GVSS33P;
INFOPLIST_FILE = MastodonIntent/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.0.7;
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.MastodonIntent;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "APP_EXTENSION $(inherited)";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 1;
VERSIONING_SYSTEM = "apple-generic";
};
name = "ASDK - Release";
};
DB8FABD326AEC7B2008E5AF4 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 159AC43EFE0A1F95FCB358A4 /* Pods-MastodonIntent.release.xcconfig */;
@ -5332,56 +5186,6 @@
};
name = Debug;
};
DBC6461E26A170AB00B0E31B /* ASDK - Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 6130CBE4B26E3C976ACC1688 /* Pods-ShareActionExtension.asdk - debug.xcconfig */;
buildSettings = {
CODE_SIGN_ENTITLEMENTS = ShareActionExtension/ShareActionExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 88;
DEVELOPMENT_TEAM = 5Z4GVSS33P;
INFOPLIST_FILE = ShareActionExtension/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.0.7;
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.ShareActionExtension;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "APP_EXTENSION $(inherited)";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 1;
VERSIONING_SYSTEM = "apple-generic";
};
name = "ASDK - Debug";
};
DBC6461F26A170AB00B0E31B /* ASDK - Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 5CE45680252519F42FEA2D13 /* Pods-ShareActionExtension.asdk - release.xcconfig */;
buildSettings = {
CODE_SIGN_ENTITLEMENTS = ShareActionExtension/ShareActionExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 88;
DEVELOPMENT_TEAM = 5Z4GVSS33P;
INFOPLIST_FILE = ShareActionExtension/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.0.7;
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.ShareActionExtension;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "APP_EXTENSION $(inherited)";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 1;
VERSIONING_SYSTEM = "apple-generic";
};
name = "ASDK - Release";
};
DBC6462026A170AB00B0E31B /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 95AD0663479892A2109EEFD0 /* Pods-ShareActionExtension.release.xcconfig */;
@ -5407,480 +5211,6 @@
};
name = Release;
};
DBCBCC0E2680BE3E000F5B51 /* ASDK - Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
INTENTS_CODEGEN_LANGUAGE = Swift;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = ASDK;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = "ASDK - Release";
};
DBCBCC0F2680BE3E000F5B51 /* ASDK - Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = BD7598A87F4497045EDEF252 /* Pods-Mastodon.asdk - release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 88;
DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets";
DEVELOPMENT_TEAM = 5Z4GVSS33P;
INFOPLIST_FILE = Mastodon/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0.7;
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Mastodon/Vender/Mastodon-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 1;
VERSIONING_SYSTEM = "apple-generic";
};
name = "ASDK - Release";
};
DBCBCC102680BE3E000F5B51 /* ASDK - Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 46DAB0EBDDFB678347CD96FF /* Pods-MastodonTests.asdk - release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = 5Z4GVSS33P;
INFOPLIST_FILE = MastodonTests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.MastodonTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Mastodon.app/Mastodon";
};
name = "ASDK - Release";
};
DBCBCC112680BE3E000F5B51 /* ASDK - Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 8850E70A1D5FF51432E43653 /* Pods-Mastodon-MastodonUITests.asdk - release.xcconfig */;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = 5Z4GVSS33P;
INFOPLIST_FILE = MastodonUITests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.MastodonUITests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_TARGET_NAME = Mastodon;
};
name = "ASDK - Release";
};
DBCBCC122680BE3E000F5B51 /* ASDK - Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
APPLICATION_EXTENSION_API_ONLY = YES;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 88;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 5Z4GVSS33P;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 88;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = CoreDataStack/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.CoreDataStack;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = "ASDK - Release";
};
DBCBCC132680BE3E000F5B51 /* ASDK - Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = 5Z4GVSS33P;
INFOPLIST_FILE = CoreDataStackTests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.CoreDataStackTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Mastodon.app/Mastodon";
};
name = "ASDK - Release";
};
DBCBCC142680BE3E000F5B51 /* ASDK - Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9CFF58FD900AC059428700E7 /* Pods-NotificationService.asdk - release.xcconfig */;
buildSettings = {
CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 88;
DEVELOPMENT_TEAM = 5Z4GVSS33P;
INFOPLIST_FILE = NotificationService/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.0.7;
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.NotificationService;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 1;
VERSIONING_SYSTEM = "apple-generic";
};
name = "ASDK - Release";
};
DBCBCC152680BE3E000F5B51 /* ASDK - Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = DDB1B139FA8EA26F510D58B6 /* Pods-AppShared.asdk - release.xcconfig */;
buildSettings = {
APPLICATION_EXTENSION_API_ONLY = YES;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 88;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 5Z4GVSS33P;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 88;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = AppShared/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.AppShared;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = "ASDK - Release";
};
DBCBCC1E26818F6F000F5B51 /* ASDK - Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
INTENTS_CODEGEN_LANGUAGE = Swift;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG ASDK";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = "ASDK - Debug";
};
DBCBCC1F26818F6F000F5B51 /* ASDK - Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 1D6D967E77A5357E2C6110D9 /* Pods-Mastodon.asdk - debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 88;
DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets";
DEVELOPMENT_TEAM = 5Z4GVSS33P;
INFOPLIST_FILE = Mastodon/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0.7;
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Mastodon/Vender/Mastodon-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 1;
VERSIONING_SYSTEM = "apple-generic";
};
name = "ASDK - Debug";
};
DBCBCC2026818F6F000F5B51 /* ASDK - Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7CEFFAE9AF9284B13C0A758D /* Pods-MastodonTests.asdk - debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = 5Z4GVSS33P;
INFOPLIST_FILE = MastodonTests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.MastodonTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Mastodon.app/Mastodon";
};
name = "ASDK - Debug";
};
DBCBCC2126818F6F000F5B51 /* ASDK - Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = E5C7236E58D14A0322FE00F2 /* Pods-Mastodon-MastodonUITests.asdk - debug.xcconfig */;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = 5Z4GVSS33P;
INFOPLIST_FILE = MastodonUITests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.MastodonUITests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_TARGET_NAME = Mastodon;
};
name = "ASDK - Debug";
};
DBCBCC2226818F6F000F5B51 /* ASDK - Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
APPLICATION_EXTENSION_API_ONLY = YES;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 88;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 5Z4GVSS33P;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 88;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = CoreDataStack/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.CoreDataStack;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = "ASDK - Debug";
};
DBCBCC2326818F6F000F5B51 /* ASDK - Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = 5Z4GVSS33P;
INFOPLIST_FILE = CoreDataStackTests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.CoreDataStackTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Mastodon.app/Mastodon";
};
name = "ASDK - Debug";
};
DBCBCC2426818F6F000F5B51 /* ASDK - Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 3B7FD8F28DDA8FBCE5562B78 /* Pods-NotificationService.asdk - debug.xcconfig */;
buildSettings = {
CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 88;
DEVELOPMENT_TEAM = 5Z4GVSS33P;
INFOPLIST_FILE = NotificationService/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.0.7;
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.NotificationService;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 1;
VERSIONING_SYSTEM = "apple-generic";
};
name = "ASDK - Debug";
};
DBCBCC2526818F6F000F5B51 /* ASDK - Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = A9B1FB898DFD6063B044298C /* Pods-AppShared.asdk - debug.xcconfig */;
buildSettings = {
APPLICATION_EXTENSION_API_ONLY = YES;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 88;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 5Z4GVSS33P;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 88;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = AppShared/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.AppShared;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = "ASDK - Debug";
};
DBF8AE1C263293E400C9C23C /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9553C689FFA9EBC880CAB78D /* Pods-NotificationService.debug.xcconfig */;
@ -5936,8 +5266,6 @@
isa = XCConfigurationList;
buildConfigurations = (
DB427DFA25BAA00100D1B89D /* Debug */,
DBCBCC1E26818F6F000F5B51 /* ASDK - Debug */,
DBCBCC0E2680BE3E000F5B51 /* ASDK - Release */,
DB427DFB25BAA00100D1B89D /* Release */,
);
defaultConfigurationIsVisible = 0;
@ -5947,8 +5275,6 @@
isa = XCConfigurationList;
buildConfigurations = (
DB427DFD25BAA00100D1B89D /* Debug */,
DBCBCC1F26818F6F000F5B51 /* ASDK - Debug */,
DBCBCC0F2680BE3E000F5B51 /* ASDK - Release */,
DB427DFE25BAA00100D1B89D /* Release */,
);
defaultConfigurationIsVisible = 0;
@ -5958,8 +5284,6 @@
isa = XCConfigurationList;
buildConfigurations = (
DB427E0025BAA00100D1B89D /* Debug */,
DBCBCC2026818F6F000F5B51 /* ASDK - Debug */,
DBCBCC102680BE3E000F5B51 /* ASDK - Release */,
DB427E0125BAA00100D1B89D /* Release */,
);
defaultConfigurationIsVisible = 0;
@ -5969,8 +5293,6 @@
isa = XCConfigurationList;
buildConfigurations = (
DB427E0325BAA00100D1B89D /* Debug */,
DBCBCC2126818F6F000F5B51 /* ASDK - Debug */,
DBCBCC112680BE3E000F5B51 /* ASDK - Release */,
DB427E0425BAA00100D1B89D /* Release */,
);
defaultConfigurationIsVisible = 0;
@ -5980,8 +5302,6 @@
isa = XCConfigurationList;
buildConfigurations = (
DB6804892637CD4C00430867 /* Debug */,
DBCBCC2526818F6F000F5B51 /* ASDK - Debug */,
DBCBCC152680BE3E000F5B51 /* ASDK - Release */,
DB68048A2637CD4C00430867 /* Release */,
);
defaultConfigurationIsVisible = 0;
@ -5991,8 +5311,6 @@
isa = XCConfigurationList;
buildConfigurations = (
DB89BA0625C10FD0008580ED /* Debug */,
DBCBCC2226818F6F000F5B51 /* ASDK - Debug */,
DBCBCC122680BE3E000F5B51 /* ASDK - Release */,
DB89BA0725C10FD0008580ED /* Release */,
);
defaultConfigurationIsVisible = 0;
@ -6002,8 +5320,6 @@
isa = XCConfigurationList;
buildConfigurations = (
DB89BA0A25C10FD0008580ED /* Debug */,
DBCBCC2326818F6F000F5B51 /* ASDK - Debug */,
DBCBCC132680BE3E000F5B51 /* ASDK - Release */,
DB89BA0B25C10FD0008580ED /* Release */,
);
defaultConfigurationIsVisible = 0;
@ -6013,8 +5329,6 @@
isa = XCConfigurationList;
buildConfigurations = (
DB8FABD026AEC7B2008E5AF4 /* Debug */,
DB8FABD126AEC7B2008E5AF4 /* ASDK - Debug */,
DB8FABD226AEC7B2008E5AF4 /* ASDK - Release */,
DB8FABD326AEC7B2008E5AF4 /* Release */,
);
defaultConfigurationIsVisible = 0;
@ -6024,8 +5338,6 @@
isa = XCConfigurationList;
buildConfigurations = (
DBC6461D26A170AB00B0E31B /* Debug */,
DBC6461E26A170AB00B0E31B /* ASDK - Debug */,
DBC6461F26A170AB00B0E31B /* ASDK - Release */,
DBC6462026A170AB00B0E31B /* Release */,
);
defaultConfigurationIsVisible = 0;
@ -6035,8 +5347,6 @@
isa = XCConfigurationList;
buildConfigurations = (
DBF8AE1C263293E400C9C23C /* Debug */,
DBCBCC2426818F6F000F5B51 /* ASDK - Debug */,
DBCBCC142680BE3E000F5B51 /* ASDK - Release */,
DBF8AE1D263293E400C9C23C /* Release */,
);
defaultConfigurationIsVisible = 0;

View File

@ -7,17 +7,17 @@
<key>AppShared.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>44</integer>
<integer>24</integer>
</dict>
<key>CoreDataStack.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>45</integer>
<integer>26</integer>
</dict>
<key>Mastodon - ASDK.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>4</integer>
<integer>2</integer>
</dict>
<key>Mastodon - RTL.xcscheme_^#shared#^_</key>
<dict>
@ -27,7 +27,7 @@
<key>Mastodon - Release.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>3</integer>
<integer>1</integer>
</dict>
<key>Mastodon - ar.xcscheme_^#shared#^_</key>
<dict>
@ -102,7 +102,7 @@
<key>MastodonIntent.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>43</integer>
<integer>27</integer>
</dict>
<key>MastodonIntents.xcscheme_^#shared#^_</key>
<dict>
@ -117,12 +117,12 @@
<key>NotificationService.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>7</integer>
<integer>3</integer>
</dict>
<key>ShareActionExtension.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>42</integer>
<integer>25</integer>
</dict>
</dict>
<key>SuppressBuildableAutocreation</key>

View File

@ -157,11 +157,6 @@ extension SceneCoordinator {
case mastodonConfirmEmail(viewModel: MastodonConfirmEmailViewModel)
case mastodonResendEmail(viewModel: MastodonResendEmailViewModel)
case mastodonWebView(viewModel:WebViewModel)
#if ASDK
// ASDK
case asyncHome
#endif
// search
case searchDetail(viewModel: SearchDetailViewModel)
@ -412,11 +407,6 @@ private extension SceneCoordinator {
let _viewController = WebViewController()
_viewController.viewModel = viewModel
viewController = _viewController
#if ASDK
case .asyncHome:
let _viewController = AsyncHomeTimelineViewController()
viewController = _viewController
#endif
case .searchDetail(let viewModel):
let _viewController = SearchDetailViewController()
_viewController.viewModel = viewModel

View File

@ -1,85 +0,0 @@
//
// ASTableNode.swift
// Mastodon
//
// Created by Cirno MainasuK on 2021-6-19.
//
#if ASDK
import UIKit
import AsyncDisplayKit
import DifferenceKit
import DiffableDataSources
extension ASTableNode: ReloadableTableView {
public func reload<C>(
using stagedChangeset: StagedChangeset<C>,
deleteSectionsAnimation: @autoclosure () -> UITableView.RowAnimation,
insertSectionsAnimation: @autoclosure () -> UITableView.RowAnimation,
reloadSectionsAnimation: @autoclosure () -> UITableView.RowAnimation,
deleteRowsAnimation: @autoclosure () -> UITableView.RowAnimation,
insertRowsAnimation: @autoclosure () -> UITableView.RowAnimation,
reloadRowsAnimation: @autoclosure () -> UITableView.RowAnimation,
interrupt: ((Changeset<C>) -> Bool)? = nil,
setData: (C) -> Void
) {
if case .none = view.window, let data = stagedChangeset.last?.data {
setData(data)
return reloadData()
}
for changeset in stagedChangeset {
if let interrupt = interrupt, interrupt(changeset), let data = stagedChangeset.last?.data {
setData(data)
return reloadData()
}
func updates() {
setData(changeset.data)
if !changeset.sectionDeleted.isEmpty {
deleteSections(IndexSet(changeset.sectionDeleted), with: deleteSectionsAnimation())
}
if !changeset.sectionInserted.isEmpty {
insertSections(IndexSet(changeset.sectionInserted), with: insertSectionsAnimation())
}
if !changeset.sectionUpdated.isEmpty {
reloadSections(IndexSet(changeset.sectionUpdated), with: reloadSectionsAnimation())
}
for (source, target) in changeset.sectionMoved {
moveSection(source, toSection: target)
}
if !changeset.elementDeleted.isEmpty {
deleteRows(at: changeset.elementDeleted.map { IndexPath(row: $0.element, section: $0.section) }, with: deleteRowsAnimation())
}
if !changeset.elementInserted.isEmpty {
insertRows(at: changeset.elementInserted.map { IndexPath(row: $0.element, section: $0.section) }, with: insertRowsAnimation())
}
if !changeset.elementUpdated.isEmpty {
reloadRows(at: changeset.elementUpdated.map { IndexPath(row: $0.element, section: $0.section) }, with: reloadRowsAnimation())
}
for (source, target) in changeset.elementMoved {
moveRow(at: IndexPath(row: source.element, section: source.section), to: IndexPath(row: target.element, section: target.section))
}
}
if isNodeLoaded {
view.beginUpdates()
updates()
view.endUpdates(animated: false, completion: nil)
} else {
updates()
}
}
}
}
#endif

View File

@ -1,115 +0,0 @@
//
// TableNodeDiffableDataSource.swift
// Mastodon
//
// Created by Cirno MainasuK on 2021-6-19.
//
#if ASDK
import UIKit
import AsyncDisplayKit
import DiffableDataSources
open class TableNodeDiffableDataSource<SectionIdentifierType: Hashable, ItemIdentifierType: Hashable>: NSObject, ASTableDataSource {
/// The type of closure providing the cell.
public typealias CellProvider = (ASTableNode, IndexPath, ItemIdentifierType) -> ASCellNodeBlock?
/// The default animation to updating the views.
public var defaultRowAnimation: UITableView.RowAnimation = .automatic
private weak var tableNode: ASTableNode?
private let cellProvider: CellProvider
private let core = DiffableDataSourceCore<SectionIdentifierType, ItemIdentifierType>()
/// Creates a new data source.
///
/// - Parameters:
/// - tableView: A table view instance to be managed.
/// - cellProvider: A closure to dequeue the cell for rows.
public init(tableNode: ASTableNode, cellProvider: @escaping CellProvider) {
self.tableNode = tableNode
self.cellProvider = cellProvider
super.init()
tableNode.delegate = self
}
/// Applies given snapshot to perform automatic diffing update.
///
/// - Parameters:
/// - snapshot: A snapshot object to be applied to data model.
/// - animatingDifferences: A Boolean value indicating whether to update with
/// diffing animation.
/// - completion: An optional completion block which is called when the complete
/// performing updates.
public func apply(_ snapshot: DiffableDataSourceSnapshot<SectionIdentifierType, ItemIdentifierType>, animatingDifferences: Bool = true, completion: (() -> Void)? = nil) {
core.apply(snapshot, view: tableNode, animatingDifferences: animatingDifferences, completion: completion)
}
/// Returns a new snapshot object of current state.
///
/// - Returns: A new snapshot object of current state.
public func snapshot() -> DiffableDataSourceSnapshot<SectionIdentifierType, ItemIdentifierType> {
return core.snapshot()
}
/// Returns an item identifier for given index path.
///
/// - Parameters:
/// - indexPath: An index path for the item identifier.
///
/// - Returns: An item identifier for given index path.
public func itemIdentifier(for indexPath: IndexPath) -> ItemIdentifierType? {
return core.itemIdentifier(for: indexPath)
}
/// Returns an index path for given item identifier.
///
/// - Parameters:
/// - itemIdentifier: An identifier of item.
///
/// - Returns: An index path for given item identifier.
public func indexPath(for itemIdentifier: ItemIdentifierType) -> IndexPath? {
return core.indexPath(for: itemIdentifier)
}
/// Returns the number of sections in the data source.
///
/// - Parameters:
/// - tableNode: A table node instance managed by `self`.
///
/// - Returns: The number of sections in the data source.
public func numberOfSections(in tableNode: ASTableNode) -> Int {
return core.numberOfSections()
}
/// Returns the number of items in the specified section.
///
/// - Parameters:
/// - tableNode: A table node instance managed by `self`.
/// - section: An index of section.
///
/// - Returns: The number of items in the specified section.
public func tableNode(_ tableNode: ASTableNode, numberOfRowsInSection section: Int) -> Int {
return core.numberOfItems(inSection: section)
}
/// Returns a cell for row at specified index path.
///
/// - Parameters:
/// - tableView: A table view instance managed by `self`.
/// - indexPath: An index path for cell.
///
/// - Returns: A cell for row at specified index path.
open func tableNode(_ tableNode: ASTableNode, nodeBlockForRowAt indexPath: IndexPath) -> ASCellNodeBlock {
let itemIdentifier = core.unsafeItemIdentifier(for: indexPath)
guard let block = cellProvider(tableNode, indexPath, itemIdentifier) else {
fatalError("UITableView dataSource returned a nil cell for row at index path: \(indexPath), tableNode: \(tableNode), itemIdentifier: \(itemIdentifier)")
}
return block
}
}
#endif

View File

@ -18,10 +18,6 @@ import NaturalLanguage
// import LinkPresentation
#if ASDK
import AsyncDisplayKit
#endif
protocol StatusCell: DisposeBagCollectable {
var statusView: StatusView { get }
var isFiltered: Bool { get set }
@ -32,33 +28,6 @@ enum StatusSection: Equatable, Hashable {
}
extension StatusSection {
#if ASDK
static func tableNodeDiffableDataSource(
tableNode: ASTableNode,
managedObjectContext: NSManagedObjectContext
) -> TableNodeDiffableDataSource<StatusSection, Item> {
TableNodeDiffableDataSource(tableNode: tableNode) { tableNode, indexPath, item in
switch item {
case .homeTimelineIndex(let objectID, let attribute):
guard let homeTimelineIndex = try? managedObjectContext.existingObject(with: objectID) as? HomeTimelineIndex else {
return { ASCellNode() }
}
let status = homeTimelineIndex.status
return { () -> ASCellNode in
let cellNode = StatusNode(status: status)
return cellNode
}
case .homeMiddleLoader:
return { TimelineMiddleLoaderNode() }
case .bottomLoader:
return { TimelineBottomLoaderNode() }
default:
return { ASCellNode() }
}
}
}
#endif
static let logger = Logger(subsystem: "StatusSection", category: "logic")

View File

@ -1,17 +0,0 @@
//
// StatusProvider+StatusNodeDelegate.swift
// Mastodon
//
// Created by Cirno MainasuK on 2021-6-20.
//
#if ASDK
import Foundation
// MARK: - StatusViewDelegate
extension StatusNodeDelegate where Self: StatusProvider {
}
#endif

View File

@ -10,10 +10,6 @@ import Combine
import CoreData
import CoreDataStack
#if ASDK
import AsyncDisplayKit
#endif
protocol StatusProvider: NeedsDependency & DisposeBagCollectable & UIViewController {
// async
func status() -> Future<Status?, Never>
@ -31,20 +27,8 @@ protocol StatusProvider: NeedsDependency & DisposeBagCollectable & UIViewControl
func items(indexPaths: [IndexPath]) -> [Item]
func statusObjectItems(indexPaths: [IndexPath]) -> [StatusObjectItem]
#if ASDK
func status(node: ASCellNode?, indexPath: IndexPath?) -> Status?
#endif
}
#if ASDK
extension StatusProvider {
func status(node: ASCellNode?, indexPath: IndexPath?) -> Status? {
fatalError("Needs implement this")
}
}
#endif
enum StatusObjectItem {
case status(objectID: NSManagedObjectID)
case homeTimelineIndex(objectID: NSManagedObjectID)

View File

@ -14,10 +14,6 @@ import MastodonSDK
import Meta
import MetaTextKit
#if ASDK
import AsyncDisplayKit
#endif
enum StatusProviderFacade { }
extension StatusProviderFacade {
@ -154,13 +150,6 @@ extension StatusProviderFacade {
}
}
#if ASDK
private static func coordinateToStatusMentionProfileScene(for target: Target, provider: StatusProvider, node: ASCellNode, mention: String) {
guard let status = provider.status(node: node, indexPath: nil) else { return }
coordinateToStatusMentionProfileScene(for: target, provider: provider, status: status, mention: mention, href: nil)
}
#endif
private static func coordinateToStatusMentionProfileScene(for target: Target, provider: StatusProvider, cell: UITableViewCell, mention: String, href: String?) {
provider.status(for: cell, indexPath: nil)
.sink { [weak provider] status in

View File

@ -1,384 +0,0 @@
//
// AsyncHomeTimelineViewController+DebugAction.swift
// Mastodon
//
// Created by MainasuK Cirno on 2021-6-21.
//
#if ASDK && DEBUG
import os.log
import UIKit
import CoreData
import CoreDataStack
import FLEX
extension AsyncHomeTimelineViewController {
var debugMenu: UIMenu {
let menu = UIMenu(
title: "Debug Tools",
image: nil,
identifier: nil,
options: .displayInline,
children: [
UIAction(title: "Show FLEX", image: nil, attributes: [], handler: { [weak self] action in
guard let self = self else { return }
self.showFLEXAction(action)
}),
moveMenu,
dropMenu,
UIAction(title: "Show Welcome", image: UIImage(systemName: "figure.walk"), attributes: []) { [weak self] action in
guard let self = self else { return }
self.showWelcomeAction(action)
},
UIAction(title: "Show Or Remove EmptyView", image: UIImage(systemName: "clear"), attributes: []) { [weak self] action in
guard let self = self else { return }
if self.emptyView.superview != nil {
self.emptyView.removeFromSuperview()
} else {
self.showEmptyView()
}
},
UIAction(title: "Show Public Timeline", image: UIImage(systemName: "list.dash"), attributes: []) { [weak self] action in
guard let self = self else { return }
self.showPublicTimelineAction(action)
},
UIAction(title: "Show Profile", image: UIImage(systemName: "person.crop.circle"), attributes: []) { [weak self] action in
guard let self = self else { return }
self.showProfileAction(action)
},
UIAction(title: "Show Thread", image: UIImage(systemName: "bubble.left.and.bubble.right"), attributes: []) { [weak self] action in
guard let self = self else { return }
self.showThreadAction(action)
},
UIAction(title: "Settings", image: UIImage(systemName: "gear"), attributes: []) { [weak self] action in
guard let self = self else { return }
self.showSettings(action)
},
UIAction(title: "Sign Out", image: UIImage(systemName: "escape"), attributes: .destructive) { [weak self] action in
guard let self = self else { return }
self.signOutAction(action)
}
]
)
return menu
}
var moveMenu: UIMenu {
return UIMenu(
title: "Move to…",
image: UIImage(systemName: "arrow.forward.circle"),
identifier: nil,
options: [],
children: [
UIAction(title: "First Gap", image: nil, attributes: [], handler: { [weak self] action in
guard let self = self else { return }
self.moveToTopGapAction(action)
}),
UIAction(title: "First Replied Status", image: nil, attributes: [], handler: { [weak self] action in
guard let self = self else { return }
self.moveToFirstRepliedStatus(action)
}),
UIAction(title: "First Reblog Status", image: nil, attributes: [], handler: { [weak self] action in
guard let self = self else { return }
self.moveToFirstReblogStatus(action)
}),
UIAction(title: "First Poll Status", image: nil, attributes: [], handler: { [weak self] action in
guard let self = self else { return }
self.moveToFirstPollStatus(action)
}),
UIAction(title: "First Audio Status", image: nil, attributes: [], handler: { [weak self] action in
guard let self = self else { return }
self.moveToFirstAudioStatus(action)
}),
UIAction(title: "First Video Status", image: nil, attributes: [], handler: { [weak self] action in
guard let self = self else { return }
self.moveToFirstVideoStatus(action)
}),
UIAction(title: "First GIF status", image: nil, attributes: [], handler: { [weak self] action in
guard let self = self else { return }
self.moveToFirstGIFStatus(action)
}),
]
)
}
var dropMenu: UIMenu {
return UIMenu(
title: "Drop…",
image: UIImage(systemName: "minus.circle"),
identifier: nil,
options: [],
children: [10, 50, 100, 150, 200, 250, 300].map { count in
UIAction(title: "Drop Recent \(count) Statuses", image: nil, attributes: [], handler: { [weak self] action in
guard let self = self else { return }
self.dropRecentStatusAction(action, count: count)
})
}
)
}
}
extension AsyncHomeTimelineViewController {
@objc private func showFLEXAction(_ sender: UIAction) {
FLEXManager.shared.showExplorer()
}
@objc private func moveToTopGapAction(_ sender: UIAction) {
guard let diffableDataSource = viewModel.diffableDataSource else { return }
let snapshotTransitioning = diffableDataSource.snapshot()
let item = snapshotTransitioning.itemIdentifiers.first(where: { item in
switch item {
case .homeMiddleLoader: return true
default: return false
}
})
if let targetItem = item, let index = snapshotTransitioning.indexOfItem(targetItem) {
tableView.scrollToRow(at: IndexPath(row: index, section: 0), at: .middle, animated: true)
}
}
@objc private func moveToFirstReblogStatus(_ sender: UIAction) {
guard let diffableDataSource = viewModel.diffableDataSource else { return }
let snapshotTransitioning = diffableDataSource.snapshot()
let item = snapshotTransitioning.itemIdentifiers.first(where: { item in
switch item {
case .homeTimelineIndex(let objectID, _):
let homeTimelineIndex = viewModel.fetchedResultsController.managedObjectContext.object(with: objectID) as! HomeTimelineIndex
return homeTimelineIndex.status.reblog != nil
default:
return false
}
})
if let targetItem = item, let index = snapshotTransitioning.indexOfItem(targetItem) {
tableView.scrollToRow(at: IndexPath(row: index, section: 0), at: .middle, animated: true)
tableView.blinkRow(at: IndexPath(row: index, section: 0))
} else {
print("Not found reblog status")
}
}
@objc private func moveToFirstPollStatus(_ sender: UIAction) {
guard let diffableDataSource = viewModel.diffableDataSource else { return }
let snapshotTransitioning = diffableDataSource.snapshot()
let item = snapshotTransitioning.itemIdentifiers.first(where: { item in
switch item {
case .homeTimelineIndex(let objectID, _):
let homeTimelineIndex = viewModel.fetchedResultsController.managedObjectContext.object(with: objectID) as! HomeTimelineIndex
let post = homeTimelineIndex.status.reblog ?? homeTimelineIndex.status
return post.poll != nil
default:
return false
}
})
if let targetItem = item, let index = snapshotTransitioning.indexOfItem(targetItem) {
tableView.scrollToRow(at: IndexPath(row: index, section: 0), at: .middle, animated: true)
tableView.blinkRow(at: IndexPath(row: index, section: 0))
} else {
print("Not found poll status")
}
}
@objc private func moveToFirstRepliedStatus(_ sender: UIAction) {
guard let diffableDataSource = viewModel.diffableDataSource else { return }
let snapshotTransitioning = diffableDataSource.snapshot()
let item = snapshotTransitioning.itemIdentifiers.first(where: { item in
switch item {
case .homeTimelineIndex(let objectID, _):
let homeTimelineIndex = viewModel.fetchedResultsController.managedObjectContext.object(with: objectID) as! HomeTimelineIndex
guard homeTimelineIndex.status.inReplyToID != nil else {
return false
}
return true
default:
return false
}
})
if let targetItem = item, let index = snapshotTransitioning.indexOfItem(targetItem) {
tableView.scrollToRow(at: IndexPath(row: index, section: 0), at: .middle, animated: true)
tableView.blinkRow(at: IndexPath(row: index, section: 0))
} else {
print("Not found replied status")
}
}
@objc private func moveToFirstAudioStatus(_ sender: UIAction) {
guard let diffableDataSource = viewModel.diffableDataSource else { return }
let snapshotTransitioning = diffableDataSource.snapshot()
let item = snapshotTransitioning.itemIdentifiers.first(where: { item in
switch item {
case .homeTimelineIndex(let objectID, _):
let homeTimelineIndex = viewModel.fetchedResultsController.managedObjectContext.object(with: objectID) as! HomeTimelineIndex
let status = homeTimelineIndex.status.reblog ?? homeTimelineIndex.status
return status.mediaAttachments?.contains(where: { $0.type == .audio }) ?? false
default:
return false
}
})
if let targetItem = item, let index = snapshotTransitioning.indexOfItem(targetItem) {
tableView.scrollToRow(at: IndexPath(row: index, section: 0), at: .middle, animated: true)
tableView.blinkRow(at: IndexPath(row: index, section: 0))
} else {
print("Not found audio status")
}
}
@objc private func moveToFirstVideoStatus(_ sender: UIAction) {
guard let diffableDataSource = viewModel.diffableDataSource else { return }
let snapshotTransitioning = diffableDataSource.snapshot()
let item = snapshotTransitioning.itemIdentifiers.first(where: { item in
switch item {
case .homeTimelineIndex(let objectID, _):
let homeTimelineIndex = viewModel.fetchedResultsController.managedObjectContext.object(with: objectID) as! HomeTimelineIndex
let status = homeTimelineIndex.status.reblog ?? homeTimelineIndex.status
return status.mediaAttachments?.contains(where: { $0.type == .video }) ?? false
default:
return false
}
})
if let targetItem = item, let index = snapshotTransitioning.indexOfItem(targetItem) {
tableView.scrollToRow(at: IndexPath(row: index, section: 0), at: .middle, animated: true)
tableView.blinkRow(at: IndexPath(row: index, section: 0))
} else {
print("Not found video status")
}
}
@objc private func moveToFirstGIFStatus(_ sender: UIAction) {
guard let diffableDataSource = viewModel.diffableDataSource else { return }
let snapshotTransitioning = diffableDataSource.snapshot()
let item = snapshotTransitioning.itemIdentifiers.first(where: { item in
switch item {
case .homeTimelineIndex(let objectID, _):
let homeTimelineIndex = viewModel.fetchedResultsController.managedObjectContext.object(with: objectID) as! HomeTimelineIndex
let status = homeTimelineIndex.status.reblog ?? homeTimelineIndex.status
return status.mediaAttachments?.contains(where: { $0.type == .gifv }) ?? false
default:
return false
}
})
if let targetItem = item, let index = snapshotTransitioning.indexOfItem(targetItem) {
tableView.scrollToRow(at: IndexPath(row: index, section: 0), at: .middle, animated: true)
tableView.blinkRow(at: IndexPath(row: index, section: 0))
} else {
print("Not found GIF status")
}
}
@objc private func dropRecentStatusAction(_ sender: UIAction, count: Int) {
guard let diffableDataSource = viewModel.diffableDataSource else { return }
let snapshotTransitioning = diffableDataSource.snapshot()
let droppingObjectIDs = snapshotTransitioning.itemIdentifiers.prefix(count).compactMap { item -> NSManagedObjectID? in
switch item {
case .homeTimelineIndex(let objectID, _): return objectID
default: return nil
}
}
var droppingStatusObjectIDs: [NSManagedObjectID] = []
context.apiService.backgroundManagedObjectContext.performChanges { [weak self] in
guard let self = self else { return }
for objectID in droppingObjectIDs {
guard let homeTimelineIndex = try? self.context.apiService.backgroundManagedObjectContext.existingObject(with: objectID) as? HomeTimelineIndex else { continue }
droppingStatusObjectIDs.append(homeTimelineIndex.status.objectID)
self.context.apiService.backgroundManagedObjectContext.delete(homeTimelineIndex)
}
}
.sink { [weak self] result in
guard let self = self else { return }
switch result {
case .success:
self.context.apiService.backgroundManagedObjectContext.performChanges { [weak self] in
guard let self = self else { return }
for objectID in droppingStatusObjectIDs {
guard let post = try? self.context.apiService.backgroundManagedObjectContext.existingObject(with: objectID) as? Status else { continue }
self.context.apiService.backgroundManagedObjectContext.delete(post)
}
}
.sink { _ in
// do nothing
}
.store(in: &self.disposeBag)
case .failure(let error):
assertionFailure(error.localizedDescription)
}
}
.store(in: &disposeBag)
}
@objc private func showWelcomeAction(_ sender: UIAction) {
coordinator.present(scene: .welcome, from: self, transition: .modal(animated: true, completion: nil))
}
@objc private func showPublicTimelineAction(_ sender: UIAction) {
coordinator.present(scene: .publicTimeline, from: self, transition: .show)
}
@objc private func showProfileAction(_ sender: UIAction) {
let alertController = UIAlertController(title: "Enter User ID", message: nil, preferredStyle: .alert)
alertController.addTextField()
let showAction = UIAlertAction(title: "Show", style: .default) { [weak self, weak alertController] _ in
guard let self = self else { return }
guard let textField = alertController?.textFields?.first else { return }
let profileViewModel = RemoteProfileViewModel(context: self.context, userID: textField.text ?? "")
self.coordinator.present(scene: .profile(viewModel: profileViewModel), from: self, transition: .show)
}
alertController.addAction(showAction)
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
alertController.addAction(cancelAction)
coordinator.present(scene: .alertController(alertController: alertController), from: self, transition: .alertController(animated: true, completion: nil))
}
@objc private func showThreadAction(_ sender: UIAction) {
let alertController = UIAlertController(title: "Enter Status ID", message: nil, preferredStyle: .alert)
alertController.addTextField()
let showAction = UIAlertAction(title: "Show", style: .default) { [weak self, weak alertController] _ in
guard let self = self else { return }
guard let textField = alertController?.textFields?.first else { return }
let threadViewModel = RemoteThreadViewModel(context: self.context, statusID: textField.text ?? "")
self.coordinator.present(scene: .thread(viewModel: threadViewModel), from: self, transition: .show)
}
alertController.addAction(showAction)
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
alertController.addAction(cancelAction)
coordinator.present(scene: .alertController(alertController: alertController), from: self, transition: .alertController(animated: true, completion: nil))
}
@objc private func showSettings(_ sender: UIAction) {
guard let currentSetting = context.settingService.currentSetting.value else { return }
let settingsViewModel = SettingsViewModel(context: context, setting: currentSetting)
coordinator.present(
scene: .settings(viewModel: settingsViewModel),
from: self,
transition: .modal(animated: true, completion: nil)
)
}
@objc func signOutAction(_ sender: UIAction) {
guard let activeMastodonAuthenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else {
return
}
context.authenticationService.signOutMastodonUser(
domain: activeMastodonAuthenticationBox.domain,
userID: activeMastodonAuthenticationBox.userID
)
.receive(on: DispatchQueue.main)
.sink { [weak self] result in
guard let self = self else { return }
switch result {
case .failure(let error):
assertionFailure(error.localizedDescription)
case .success(let isSignOut):
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: sign out %s", ((#file as NSString).lastPathComponent), #line, #function, isSignOut ? "success" : "fail")
guard isSignOut else { return }
self.coordinator.setup()
self.coordinator.setupOnboardingIfNeeds(animated: true)
}
}
.store(in: &disposeBag)
}
}
#endif

View File

@ -1,123 +0,0 @@
//
// AsyncHomeTimelineViewController+Provider.swift
// Mastodon
//
// Created by MainasuK Cirno on 2021-6-21.
//
#if ASDK
import os.log
import UIKit
import Combine
import CoreData
import CoreDataStack
import AsyncDisplayKit
// MARK: - StatusProvider
extension AsyncHomeTimelineViewController: StatusProvider {
func status() -> Future<Status?, Never> {
return Future { promise in promise(.success(nil)) }
}
func status(for cell: UITableViewCell?, indexPath: IndexPath?) -> Future<Status?, Never> {
return Future { promise in
guard let diffableDataSource = self.viewModel.diffableDataSource else {
assertionFailure()
promise(.success(nil))
return
}
guard let indexPath = indexPath ?? cell.flatMap({ self.tableView.indexPath(for: $0) }),
let item = diffableDataSource.itemIdentifier(for: indexPath) else {
promise(.success(nil))
return
}
switch item {
case .homeTimelineIndex(let objectID, _):
let managedObjectContext = self.viewModel.fetchedResultsController.managedObjectContext
managedObjectContext.perform {
let timelineIndex = managedObjectContext.object(with: objectID) as? HomeTimelineIndex
promise(.success(timelineIndex?.status))
}
default:
promise(.success(nil))
}
}
}
func status(for cell: UICollectionViewCell) -> Future<Status?, Never> {
return Future { promise in promise(.success(nil)) }
}
var managedObjectContext: NSManagedObjectContext {
return viewModel.fetchedResultsController.managedObjectContext
}
var tableViewDiffableDataSource: UITableViewDiffableDataSource<StatusSection, Item>? {
return nil
}
func item(for cell: UITableViewCell?, indexPath: IndexPath?) -> Item? {
guard let diffableDataSource = self.viewModel.diffableDataSource else {
assertionFailure()
return nil
}
guard let indexPath = indexPath ?? cell.flatMap({ self.tableView.indexPath(for: $0) }),
let item = diffableDataSource.itemIdentifier(for: indexPath) else {
return nil
}
return item
}
func items(indexPaths: [IndexPath]) -> [Item] {
guard let diffableDataSource = self.viewModel.diffableDataSource else {
assertionFailure()
return []
}
var items: [Item] = []
for indexPath in indexPaths {
guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { continue }
items.append(item)
}
return items
}
func status(node: ASCellNode?, indexPath: IndexPath?) -> Status? {
guard let diffableDataSource = self.viewModel.diffableDataSource else {
assertionFailure()
return nil
}
guard let indexPath = indexPath ?? node.flatMap({ self.node.indexPath(for: $0) }),
let item = diffableDataSource.itemIdentifier(for: indexPath) else {
return nil
}
switch item {
case .homeTimelineIndex(let objectID, _):
guard let homeTimelineIndex = try? viewModel.fetchedResultsController.managedObjectContext.existingObject(with: objectID) as? HomeTimelineIndex else {
assertionFailure()
return nil
}
return homeTimelineIndex.status
default:
return nil
}
}
func statusObjectItems(indexPaths: [IndexPath]) -> [StatusObjectItem] {
guard let diffableDataSource = self.viewModel.diffableDataSource else { return [] }
let items = indexPaths.compactMap { diffableDataSource.itemIdentifier(for: $0)?.statusObjectItem }
return items
}
}
extension AsyncHomeTimelineViewController: UserProvider {}
#endif

View File

@ -1,573 +0,0 @@
//
// AsyncHomeTimelineViewController.swift
// Mastodon
//
// Created by MainasuK Cirno on 2021-6-21.
//
#if ASDK
import os.log
import UIKit
import AVKit
import Combine
import CoreData
import CoreDataStack
import GameplayKit
import MastodonSDK
import AlamofireImage
import AsyncDisplayKit
final class AsyncHomeTimelineViewController: ASDKViewController<ASTableNode>, NeedsDependency, MediaPreviewableViewController {
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
var disposeBag = Set<AnyCancellable>()
private(set) lazy var viewModel = AsyncHomeTimelineViewModel(context: context)
let mediaPreviewTransitionController = MediaPreviewTransitionController()
lazy var emptyView: UIStackView = {
let emptyView = UIStackView()
emptyView.axis = .vertical
emptyView.distribution = .fill
emptyView.layoutMargins = UIEdgeInsets(top: 0, left: 20, bottom: 54, right: 20)
emptyView.isLayoutMarginsRelativeArrangement = true
return emptyView
}()
let titleView = HomeTimelineNavigationBarTitleView()
let settingBarButtonItem: UIBarButtonItem = {
let barButtonItem = UIBarButtonItem()
barButtonItem.tintColor = Asset.Colors.brandBlue.color
barButtonItem.image = UIImage(systemName: "gear")?.withRenderingMode(.alwaysTemplate)
return barButtonItem
}()
let composeBarButtonItem: UIBarButtonItem = {
let barButtonItem = UIBarButtonItem()
barButtonItem.tintColor = Asset.Colors.brandBlue.color
barButtonItem.image = UIImage(systemName: "square.and.pencil")?.withRenderingMode(.alwaysTemplate)
return barButtonItem
}()
var tableView: UITableView { node.view }
let publishProgressView: UIProgressView = {
let progressView = UIProgressView(progressViewStyle: .bar)
progressView.alpha = 0
return progressView
}()
let refreshControl = UIRefreshControl()
override init() {
super.init(node: ASTableNode())
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s:", ((#file as NSString).lastPathComponent), #line, #function)
}
}
extension AsyncHomeTimelineViewController {
override func viewDidLoad() {
super.viewDidLoad()
node.allowsSelection = true
title = L10n.Scene.HomeTimeline.title
view.backgroundColor = ThemeService.shared.currentTheme.value.secondarySystemBackgroundColor
navigationItem.leftBarButtonItem = settingBarButtonItem
navigationItem.titleView = titleView
titleView.delegate = self
viewModel.homeTimelineNavigationBarTitleViewModel.state
.removeDuplicates()
.receive(on: DispatchQueue.main)
.sink { [weak self] state in
guard let self = self else { return }
self.titleView.configure(state: state)
}
.store(in: &disposeBag)
#if DEBUG
// long press to trigger debug menu
settingBarButtonItem.menu = debugMenu
#else
settingBarButtonItem.target = self
settingBarButtonItem.action = #selector(AsyncHomeTimelineViewController.settingBarButtonItemPressed(_:))
#endif
navigationItem.rightBarButtonItem = composeBarButtonItem
composeBarButtonItem.target = self
composeBarButtonItem.action = #selector(AsyncHomeTimelineViewController.composeBarButtonItemPressed(_:))
node.view.refreshControl = refreshControl
refreshControl.addTarget(self, action: #selector(AsyncHomeTimelineViewController.refreshControlValueChanged(_:)), for: .valueChanged)
//
// tableView.translatesAutoresizingMaskIntoConstraints = false
// view.addSubview(tableView)
// NSLayoutConstraint.activate([
// tableView.topAnchor.constraint(equalTo: view.topAnchor),
// tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
// tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
// tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
// ])
//
// publishProgressView.translatesAutoresizingMaskIntoConstraints = false
// view.addSubview(publishProgressView)
// NSLayoutConstraint.activate([
// publishProgressView.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor),
// publishProgressView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
// publishProgressView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
// ])
//
// viewModel.tableView = tableView
viewModel.tableNode = node
viewModel.contentOffsetAdjustableTimelineViewControllerDelegate = self
node.delegate = self
viewModel.setupDiffableDataSource(
tableNode: node,
dependency: self,
statusTableViewCellDelegate: self,
timelineMiddleLoaderTableViewCellDelegate: self
)
// tableView.delegate = self
// tableView.prefetchDataSource = self
// bind refresh control
viewModel.isFetchingLatestTimeline
.receive(on: DispatchQueue.main)
.sink { [weak self] isFetching in
guard let self = self else { return }
if !isFetching {
UIView.animate(withDuration: 0.5) { [weak self] in
guard let self = self else { return }
self.refreshControl.endRefreshing()
} completion: { _ in }
}
}
.store(in: &disposeBag)
// viewModel.homeTimelineNavigationBarTitleViewModel.publishingProgress
// .receive(on: DispatchQueue.main)
// .sink { [weak self] progress in
// guard let self = self else { return }
// guard progress > 0 else {
// let dismissAnimator = UIViewPropertyAnimator(duration: 0.1, curve: .easeInOut)
// dismissAnimator.addAnimations {
// self.publishProgressView.alpha = 0
// }
// dismissAnimator.addCompletion { _ in
// self.publishProgressView.setProgress(0, animated: false)
// }
// dismissAnimator.startAnimation()
// return
// }
// if self.publishProgressView.alpha == 0 {
// let progressAnimator = UIViewPropertyAnimator(duration: 0.1, curve: .easeOut)
// progressAnimator.addAnimations {
// self.publishProgressView.alpha = 1
// }
// progressAnimator.startAnimation()
// }
//
// self.publishProgressView.setProgress(progress, animated: true)
// }
// .store(in: &disposeBag)
//
// viewModel.timelineIsEmpty
// .receive(on: DispatchQueue.main)
// .sink { [weak self] isEmpty in
// if isEmpty {
// self?.showEmptyView()
// } else {
// self?.emptyView.removeFromSuperview()
// }
// }
// .store(in: &disposeBag)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// aspectViewWillAppear(animated)
//
// // needs trigger manually after onboarding dismiss
// setNeedsStatusBarAppearanceUpdate()
//
// if (viewModel.fetchedResultsController.fetchedObjects ?? []).isEmpty {
// viewModel.loadLatestStateMachine.enter(HomeTimelineViewModel.LoadLatestState.Loading.self)
// }
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// viewModel.viewDidAppear.send()
//
// DispatchQueue.main.async { [weak self] in
// guard let self = self else { return }
// if (self.viewModel.fetchedResultsController.fetchedObjects ?? []).count == 0 {
// self.viewModel.loadLatestStateMachine.enter(HomeTimelineViewModel.LoadLatestState.Loading.self)
// }
// }
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
// aspectViewDidDisappear(animated)
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
// coordinator.animate { _ in
// // do nothing
// } completion: { _ in
// // fix AutoLayout cell height not update after rotate issue
// self.viewModel.cellFrameCache.removeAllObjects()
// self.tableView.reloadData()
// }
}
}
extension AsyncHomeTimelineViewController {
func showEmptyView() {
if emptyView.superview != nil {
return
}
view.addSubview(emptyView)
emptyView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
emptyView.leadingAnchor.constraint(equalTo: view.readableContentGuide.leadingAnchor),
emptyView.trailingAnchor.constraint(equalTo: view.readableContentGuide.trailingAnchor),
emptyView.bottomAnchor.constraint(equalTo: view.readableContentGuide.bottomAnchor)
])
if emptyView.arrangedSubviews.count > 0 {
return
}
let findPeopleButton: PrimaryActionButton = {
let button = PrimaryActionButton()
button.setTitle(L10n.Common.Controls.Actions.findPeople, for: .normal)
button.addTarget(self, action: #selector(AsyncHomeTimelineViewController.findPeopleButtonPressed(_:)), for: .touchUpInside)
return button
}()
NSLayoutConstraint.activate([
findPeopleButton.heightAnchor.constraint(equalToConstant: 46)
])
let manuallySearchButton: HighlightDimmableButton = {
let button = HighlightDimmableButton()
button.titleLabel?.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 15, weight: .semibold))
button.setTitle(L10n.Common.Controls.Actions.manuallySearch, for: .normal)
button.setTitleColor(Asset.Colors.brandBlue.color, for: .normal)
button.addTarget(self, action: #selector(AsyncHomeTimelineViewController.manuallySearchButtonPressed(_:)), for: .touchUpInside)
return button
}()
emptyView.addArrangedSubview(findPeopleButton)
emptyView.setCustomSpacing(17, after: findPeopleButton)
emptyView.addArrangedSubview(manuallySearchButton)
}
}
extension AsyncHomeTimelineViewController {
@objc private func findPeopleButtonPressed(_ sender: PrimaryActionButton) {
let viewModel = SuggestionAccountViewModel(context: context)
viewModel.delegate = self.viewModel
coordinator.present(scene: .suggestionAccount(viewModel: viewModel), from: self, transition: .modal(animated: true, completion: nil))
}
@objc private func manuallySearchButtonPressed(_ sender: UIButton) {
coordinator.switchToTabBar(tab: .search)
}
@objc private func settingBarButtonItemPressed(_ sender: UIBarButtonItem) {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
guard let setting = context.settingService.currentSetting.value else { return }
let settingsViewModel = SettingsViewModel(context: context, setting: setting)
coordinator.present(scene: .settings(viewModel: settingsViewModel), from: self, transition: .modal(animated: true, completion: nil))
}
@objc private func composeBarButtonItemPressed(_ sender: UIBarButtonItem) {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
let composeViewModel = ComposeViewModel(context: context, composeKind: .post)
coordinator.present(scene: .compose(viewModel: composeViewModel), from: self, transition: .modal(animated: true, completion: nil))
}
@objc private func refreshControlValueChanged(_ sender: UIRefreshControl) {
guard viewModel.loadLatestStateMachine.enter(HomeTimelineViewModel.LoadLatestState.Loading.self) else {
sender.endRefreshing()
return
}
}
}
// MARK: - StatusTableViewControllerAspect
//extension AsyncHomeTimelineViewController: StatusTableViewControllerAspect { }
//extension AsyncHomeTimelineViewController: TableViewCellHeightCacheableContainer {
// var cellFrameCache: NSCache<NSNumber, NSValue> { return viewModel.cellFrameCache }
//}
// MARK: - UIScrollViewDelegate
extension AsyncHomeTimelineViewController {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
//aspectScrollViewDidScroll(scrollView)
viewModel.homeTimelineNavigationBarTitleViewModel.handleScrollViewDidScroll(scrollView)
}
}
//extension AsyncHomeTimelineViewController: LoadMoreConfigurableTableViewContainer {
// typealias BottomLoaderTableViewCell = TimelineBottomLoaderTableViewCell
// typealias LoadingState = HomeTimelineViewModel.LoadOldestState.Loading
// var loadMoreConfigurableTableView: UITableView { return tableView }
// var loadMoreConfigurableStateMachine: GKStateMachine { return viewModel.loadOldestStateMachine }
//}
// MARK: - UITableViewDelegate
//extension AsyncHomeTimelineViewController: UITableViewDelegate {
//
// func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
// aspectTableView(tableView, estimatedHeightForRowAt: indexPath)
// }
//
// func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
// aspectTableView(tableView, willDisplay: cell, forRowAt: indexPath)
// }
//
// func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
// aspectTableView(tableView, didEndDisplaying: cell, forRowAt: indexPath)
// }
//
// func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// aspectTableView(tableView, didSelectRowAt: indexPath)
// }
//
// func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
// return aspectTableView(tableView, contextMenuConfigurationForRowAt: indexPath, point: point)
// }
//
// func tableView(_ tableView: UITableView, previewForHighlightingContextMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview? {
// return aspectTableView(tableView, previewForHighlightingContextMenuWithConfiguration: configuration)
// }
//
// func tableView(_ tableView: UITableView, previewForDismissingContextMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview? {
// return aspectTableView(tableView, previewForDismissingContextMenuWithConfiguration: configuration)
// }
//
// func tableView(_ tableView: UITableView, willPerformPreviewActionForMenuWith configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionCommitAnimating) {
// aspectTableView(tableView, willPerformPreviewActionForMenuWith: configuration, animator: animator)
// }
//
//}
// MARK: - UITableViewDataSourcePrefetching
//extension AsyncHomeTimelineViewController: UITableViewDataSourcePrefetching {
// func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
// aspectTableView(tableView, prefetchRowsAt: indexPaths)
// }
//}
// MARK: - ContentOffsetAdjustableTimelineViewControllerDelegate
extension AsyncHomeTimelineViewController: ContentOffsetAdjustableTimelineViewControllerDelegate {
func navigationBar() -> UINavigationBar? {
return navigationController?.navigationBar
}
}
// MARK: - TimelineMiddleLoaderTableViewCellDelegate
extension AsyncHomeTimelineViewController: TimelineMiddleLoaderTableViewCellDelegate {
func configure(cell: TimelineMiddleLoaderTableViewCell, upperTimelineStatusID: String?, timelineIndexobjectID: NSManagedObjectID?) {
guard let upperTimelineIndexObjectID = timelineIndexobjectID else {
return
}
viewModel.loadMiddleSateMachineList
.receive(on: DispatchQueue.main)
.sink { [weak self] ids in
guard let _ = self else { return }
if let stateMachine = ids[upperTimelineIndexObjectID] {
guard let state = stateMachine.currentState else {
assertionFailure()
return
}
// make success state same as loading due to snapshot updating delay
let isLoading = state is HomeTimelineViewModel.LoadMiddleState.Loading || state is HomeTimelineViewModel.LoadMiddleState.Success
if isLoading {
cell.startAnimating()
} else {
cell.stopAnimating()
}
} else {
cell.stopAnimating()
}
}
.store(in: &cell.disposeBag)
var dict = viewModel.loadMiddleSateMachineList.value
if let _ = dict[upperTimelineIndexObjectID] {
// do nothing
} else {
let stateMachine = GKStateMachine(states: [
AsyncHomeTimelineViewModel.LoadMiddleState.Initial(viewModel: viewModel, upperTimelineIndexObjectID: upperTimelineIndexObjectID),
AsyncHomeTimelineViewModel.LoadMiddleState.Loading(viewModel: viewModel, upperTimelineIndexObjectID: upperTimelineIndexObjectID),
AsyncHomeTimelineViewModel.LoadMiddleState.Fail(viewModel: viewModel, upperTimelineIndexObjectID: upperTimelineIndexObjectID),
AsyncHomeTimelineViewModel.LoadMiddleState.Success(viewModel: viewModel, upperTimelineIndexObjectID: upperTimelineIndexObjectID),
])
stateMachine.enter(HomeTimelineViewModel.LoadMiddleState.Initial.self)
dict[upperTimelineIndexObjectID] = stateMachine
viewModel.loadMiddleSateMachineList.value = dict
}
}
func timelineMiddleLoaderTableViewCell(_ cell: TimelineMiddleLoaderTableViewCell, loadMoreButtonDidPressed button: UIButton) {
guard let diffableDataSource = viewModel.diffableDataSource else { return }
guard let indexPath = tableView.indexPath(for: cell) else { return }
guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return }
switch item {
case .homeMiddleLoader(let upper):
guard let stateMachine = viewModel.loadMiddleSateMachineList.value[upper] else {
assertionFailure()
return
}
stateMachine.enter(HomeTimelineViewModel.LoadMiddleState.Loading.self)
default:
assertionFailure()
}
}
}
// MARK: - ScrollViewContainer
extension AsyncHomeTimelineViewController: ScrollViewContainer {
var scrollView: UIScrollView { return tableView }
func scrollToTop(animated: Bool) {
if scrollView.contentOffset.y < scrollView.frame.height,
viewModel.loadLatestStateMachine.canEnterState(HomeTimelineViewModel.LoadLatestState.Loading.self),
(scrollView.contentOffset.y + scrollView.adjustedContentInset.top) == 0.0,
!refreshControl.isRefreshing {
scrollView.scrollRectToVisible(CGRect(origin: CGPoint(x: 0, y: -refreshControl.frame.height), size: CGSize(width: 1, height: 1)), animated: animated)
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
self.refreshControl.beginRefreshing()
self.refreshControl.sendActions(for: .valueChanged)
}
} else {
let indexPath = IndexPath(row: 0, section: 0)
guard viewModel.diffableDataSource?.itemIdentifier(for: indexPath) != nil else { return }
node.scrollToRow(at: indexPath, at: .top, animated: true)
}
}
}
// MARK: - AVPlayerViewControllerDelegate
extension AsyncHomeTimelineViewController: AVPlayerViewControllerDelegate {
func playerViewController(_ playerViewController: AVPlayerViewController, willBeginFullScreenPresentationWithAnimationCoordinator coordinator: UIViewControllerTransitionCoordinator) {
handlePlayerViewController(playerViewController, willBeginFullScreenPresentationWithAnimationCoordinator: coordinator)
}
func playerViewController(_ playerViewController: AVPlayerViewController, willEndFullScreenPresentationWithAnimationCoordinator coordinator: UIViewControllerTransitionCoordinator) {
handlePlayerViewController(playerViewController, willEndFullScreenPresentationWithAnimationCoordinator: coordinator)
}
}
// MARK: - StatusTableViewCellDelegate
extension AsyncHomeTimelineViewController: StatusTableViewCellDelegate {
weak var playerViewControllerDelegate: AVPlayerViewControllerDelegate? { return self }
func parent() -> UIViewController { return self }
}
// MARK: - HomeTimelineNavigationBarTitleViewDelegate
extension AsyncHomeTimelineViewController: HomeTimelineNavigationBarTitleViewDelegate {
func homeTimelineNavigationBarTitleView(_ titleView: HomeTimelineNavigationBarTitleView, logoButtonDidPressed sender: UIButton) {
scrollToTop(animated: true)
}
func homeTimelineNavigationBarTitleView(_ titleView: HomeTimelineNavigationBarTitleView, buttonDidPressed sender: UIButton) {
switch titleView.state {
case .newPostButton:
guard let diffableDataSource = viewModel.diffableDataSource else { return }
let indexPath = IndexPath(row: 0, section: 0)
guard diffableDataSource.itemIdentifier(for: indexPath) != nil else { return }
node.scrollToRow(at: indexPath, at: .top, animated: true)
case .offlineButton:
// TODO: retry
break
case .publishedButton:
break
default:
break
}
}
}
extension AsyncHomeTimelineViewController {
override var keyCommands: [UIKeyCommand]? {
return navigationKeyCommands + statusNavigationKeyCommands
}
}
// MARK: - StatusTableViewControllerNavigateable
extension AsyncHomeTimelineViewController: StatusTableViewControllerNavigateable {
@objc func navigateKeyCommandHandlerRelay(_ sender: UIKeyCommand) {
navigateKeyCommandHandler(sender)
}
@objc func statusKeyCommandHandlerRelay(_ sender: UIKeyCommand) {
statusKeyCommandHandler(sender)
}
}
// MARK: - ASTableDelegate
extension AsyncHomeTimelineViewController: ASTableDelegate {
func shouldBatchFetch(for tableNode: ASTableNode) -> Bool {
switch viewModel.loadLatestStateMachine.currentState {
case is HomeTimelineViewModel.LoadOldestState.NoMore:
return false
default:
return true
}
}
func tableNode(_ tableNode: ASTableNode, willBeginBatchFetchWith context: ASBatchContext) {
viewModel.loadLatestStateMachine.enter(HomeTimelineViewModel.LoadOldestState.Loading.self)
context.completeBatchFetching(true)
}
func tableNode(_ tableNode: ASTableNode, willDisplayRowWith node: ASCellNode) {
if let statusNode = node as? StatusNode {
statusNode.delegate = self
}
}
}
// MARK: - StatusNodeDelegate
extension AsyncHomeTimelineViewController: StatusNodeDelegate { }
#endif

View File

@ -1,159 +0,0 @@
//
// AsyncHomeTimelineViewModel+Diffable.swift
// Mastodon
//
// Created by MainasuK Cirno on 2021-6-21.
//
#if ASDK
import os.log
import UIKit
import CoreData
import CoreDataStack
import AsyncDisplayKit
import DifferenceKit
import DiffableDataSources
extension AsyncHomeTimelineViewModel {
func setupDiffableDataSource(
tableNode: ASTableNode,
dependency: NeedsDependency,
statusTableViewCellDelegate: StatusTableViewCellDelegate,
timelineMiddleLoaderTableViewCellDelegate: TimelineMiddleLoaderTableViewCellDelegate
) {
tableNode.automaticallyAdjustsContentOffset = true
diffableDataSource = StatusSection.tableNodeDiffableDataSource(
tableNode: tableNode,
managedObjectContext: fetchedResultsController.managedObjectContext
)
var snapshot = DiffableDataSourceSnapshot<StatusSection, Item>()
snapshot.appendSections([.main])
diffableDataSource?.apply(snapshot)
}
}
// MARK: - NSFetchedResultsControllerDelegate
extension AsyncHomeTimelineViewModel: NSFetchedResultsControllerDelegate {
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
os_log("%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference) {
os_log("%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
guard let diffableDataSource = self.diffableDataSource else { return }
let oldSnapshot = diffableDataSource.snapshot()
let predicate = fetchedResultsController.fetchRequest.predicate
let parentManagedObjectContext = fetchedResultsController.managedObjectContext
let managedObjectContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
managedObjectContext.parent = parentManagedObjectContext
managedObjectContext.perform {
var shouldAddBottomLoader = false
let timelineIndexes: [HomeTimelineIndex] = {
let request = HomeTimelineIndex.sortedFetchRequest
request.returnsObjectsAsFaults = false
request.predicate = predicate
do {
return try managedObjectContext.fetch(request)
} catch {
assertionFailure(error.localizedDescription)
return []
}
}()
// that's will be the most fastest fetch because of upstream just update and no modify needs consider
var oldSnapshotAttributeDict: [NSManagedObjectID : Item.StatusAttribute] = [:]
for item in oldSnapshot.itemIdentifiers {
guard case let .homeTimelineIndex(objectID, attribute) = item else { continue }
oldSnapshotAttributeDict[objectID] = attribute
}
var newTimelineItems: [Item] = []
for (i, timelineIndex) in timelineIndexes.enumerated() {
let attribute = oldSnapshotAttributeDict[timelineIndex.objectID] ?? Item.StatusAttribute()
attribute.isSeparatorLineHidden = false
// append new item into snapshot
newTimelineItems.append(.homeTimelineIndex(objectID: timelineIndex.objectID, attribute: attribute))
let isLast = i == timelineIndexes.count - 1
switch (isLast, timelineIndex.hasMore) {
case (false, true):
newTimelineItems.append(.homeMiddleLoader(upperTimelineIndexAnchorObjectID: timelineIndex.objectID))
attribute.isSeparatorLineHidden = true
case (true, true):
shouldAddBottomLoader = true
default:
break
}
} // end for
var newSnapshot = DiffableDataSourceSnapshot<StatusSection, Item>()
newSnapshot.appendSections([.main])
newSnapshot.appendItems(newTimelineItems, toSection: .main)
let endSnapshot = CACurrentMediaTime()
if shouldAddBottomLoader, !(self.loadLatestStateMachine.currentState is LoadOldestState.NoMore) {
newSnapshot.appendItems([.bottomLoader], toSection: .main)
}
diffableDataSource.apply(newSnapshot, animatingDifferences: false) {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
self.isFetchingLatestTimeline.value = false
}
let end = CACurrentMediaTime()
os_log("%{public}s[%{public}ld], %{public}s: calculate home timeline layout cost %.2fs", ((#file as NSString).lastPathComponent), #line, #function, end - endSnapshot)
}
} // end perform
}
private struct Difference<T> {
let item: T
let sourceIndexPath: IndexPath
let targetIndexPath: IndexPath
let offset: CGFloat
}
private func calculateReloadSnapshotDifference<T: Hashable>(
navigationBar: UINavigationBar,
tableView: UITableView,
oldSnapshot: DiffableDataSourceSnapshot<StatusSection, T>,
newSnapshot: DiffableDataSourceSnapshot<StatusSection, T>
) -> Difference<T>? {
guard oldSnapshot.numberOfItems != 0 else { return nil }
// old snapshot not empty. set source index path to first item if not match
let sourceIndexPath = UIViewController.topVisibleTableViewCellIndexPath(in: tableView, navigationBar: navigationBar) ?? IndexPath(row: 0, section: 0)
guard sourceIndexPath.row < oldSnapshot.itemIdentifiers(inSection: .main).count else { return nil }
let timelineItem = oldSnapshot.itemIdentifiers(inSection: .main)[sourceIndexPath.row]
guard let itemIndex = newSnapshot.itemIdentifiers(inSection: .main).firstIndex(of: timelineItem) else { return nil }
let targetIndexPath = IndexPath(row: itemIndex, section: 0)
let offset = UIViewController.tableViewCellOriginOffsetToWindowTop(in: tableView, at: sourceIndexPath, navigationBar: navigationBar)
return Difference(
item: timelineItem,
sourceIndexPath: sourceIndexPath,
targetIndexPath: targetIndexPath,
offset: offset
)
}
}
#endif

View File

@ -1,134 +0,0 @@
//
// AsyncHomeTimelineViewModel+LoadLatestState.swift
// Mastodon
//
// Created by MainasuK Cirno on 2021-6-21.
//
//
#if ASDK
import os.log
import func QuartzCore.CACurrentMediaTime
import Foundation
import CoreData
import CoreDataStack
import GameplayKit
extension AsyncHomeTimelineViewModel {
class LoadLatestState: GKState {
weak var viewModel: AsyncHomeTimelineViewModel?
init(viewModel: AsyncHomeTimelineViewModel) {
self.viewModel = viewModel
}
override func didEnter(from previousState: GKState?) {
os_log("%{public}s[%{public}ld], %{public}s: enter %s, previous: %s", ((#file as NSString).lastPathComponent), #line, #function, self.debugDescription, previousState.debugDescription)
viewModel?.loadLatestStateMachinePublisher.send(self)
}
}
}
extension AsyncHomeTimelineViewModel.LoadLatestState {
class Initial: AsyncHomeTimelineViewModel.LoadLatestState {
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
return stateClass == Loading.self
}
}
class Loading: AsyncHomeTimelineViewModel.LoadLatestState {
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
return stateClass == Fail.self || stateClass == Idle.self
}
override func didEnter(from previousState: GKState?) {
super.didEnter(from: previousState)
guard let viewModel = viewModel, let stateMachine = stateMachine else { return }
guard let activeMastodonAuthenticationBox = viewModel.context.authenticationService.activeMastodonAuthenticationBox.value else {
// sign out when loading will enter here
stateMachine.enter(Fail.self)
return
}
let predicate = viewModel.fetchedResultsController.fetchRequest.predicate
let parentManagedObjectContext = viewModel.fetchedResultsController.managedObjectContext
let managedObjectContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
managedObjectContext.parent = parentManagedObjectContext
managedObjectContext.perform {
let start = CACurrentMediaTime()
let latestStatusIDs: [Status.ID]
let request = HomeTimelineIndex.sortedFetchRequest
request.returnsObjectsAsFaults = false
request.predicate = predicate
do {
let timelineIndexes = try managedObjectContext.fetch(request)
let endFetch = CACurrentMediaTime()
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: collect timelineIndexes cost: %.2fs", ((#file as NSString).lastPathComponent), #line, #function, endFetch - start)
latestStatusIDs = timelineIndexes
.prefix(APIService.onceRequestStatusMaxCount) // avoid performance issue
.compactMap { timelineIndex in
timelineIndex.value(forKeyPath: #keyPath(HomeTimelineIndex.status.id)) as? Status.ID
}
} catch {
stateMachine.enter(Fail.self)
return
}
let end = CACurrentMediaTime()
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: collect statuses id cost: %.2fs", ((#file as NSString).lastPathComponent), #line, #function, end - start)
// TODO: only set large count when using Wi-Fi
viewModel.context.apiService.homeTimeline(domain: activeMastodonAuthenticationBox.domain, authorizationBox: activeMastodonAuthenticationBox)
.receive(on: DispatchQueue.main)
.sink { completion in
viewModel.homeTimelineNavigationBarTitleViewModel.receiveLoadingStateCompletion(completion)
switch completion {
case .failure(let error):
// TODO: handle error
viewModel.isFetchingLatestTimeline.value = false
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: fetch statuses failed. %s", ((#file as NSString).lastPathComponent), #line, #function, error.localizedDescription)
case .finished:
// handle isFetchingLatestTimeline in fetch controller delegate
break
}
stateMachine.enter(Idle.self)
} receiveValue: { response in
// stop refresher if no new statuses
let statuses = response.value
let newStatuses = statuses.filter { !latestStatusIDs.contains($0.id) }
os_log("%{public}s[%{public}ld], %{public}s: load %{public}ld new statuses", ((#file as NSString).lastPathComponent), #line, #function, newStatuses.count)
if newStatuses.isEmpty {
viewModel.isFetchingLatestTimeline.value = false
} else {
if !latestStatusIDs.isEmpty {
viewModel.homeTimelineNavigationBarTitleViewModel.newPostsIncoming()
}
}
viewModel.timelineIsEmpty.value = latestStatusIDs.isEmpty && statuses.isEmpty
}
.store(in: &viewModel.disposeBag)
}
}
}
class Fail: AsyncHomeTimelineViewModel.LoadLatestState {
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
return stateClass == Loading.self || stateClass == Idle.self
}
}
class Idle: AsyncHomeTimelineViewModel.LoadLatestState {
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
return stateClass == Loading.self
}
}
}
#endif

View File

@ -1,112 +0,0 @@
//
// AsyncHomeTimelineViewModel+LoadMiddleState.swift
// Mastodon
//
// Created by MainasuK Cirno on 2021-6-21.
//
#if ASDK
import os.log
import Foundation
import GameplayKit
import CoreData
import CoreDataStack
extension AsyncHomeTimelineViewModel {
class LoadMiddleState: GKState {
weak var viewModel: AsyncHomeTimelineViewModel?
let upperTimelineIndexObjectID: NSManagedObjectID
init(viewModel: AsyncHomeTimelineViewModel, upperTimelineIndexObjectID: NSManagedObjectID) {
self.viewModel = viewModel
self.upperTimelineIndexObjectID = upperTimelineIndexObjectID
}
override func didEnter(from previousState: GKState?) {
os_log("%{public}s[%{public}ld], %{public}s: enter %s, previous: %s", ((#file as NSString).lastPathComponent), #line, #function, self.debugDescription, previousState.debugDescription)
guard let viewModel = viewModel, let stateMachine = stateMachine else { return }
var dict = viewModel.loadMiddleSateMachineList.value
dict[upperTimelineIndexObjectID] = stateMachine
viewModel.loadMiddleSateMachineList.value = dict // trigger value change
}
}
}
extension AsyncHomeTimelineViewModel.LoadMiddleState {
class Initial: AsyncHomeTimelineViewModel.LoadMiddleState {
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
return stateClass == Loading.self
}
}
class Loading: AsyncHomeTimelineViewModel.LoadMiddleState {
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
// guard let viewModel = viewModel else { return false }
return stateClass == Success.self || stateClass == Fail.self
}
override func didEnter(from previousState: GKState?) {
super.didEnter(from: previousState)
guard let viewModel = viewModel, let stateMachine = stateMachine else { return }
guard let activeMastodonAuthenticationBox = viewModel.context.authenticationService.activeMastodonAuthenticationBox.value else {
stateMachine.enter(Fail.self)
return
}
guard let timelineIndex = (viewModel.fetchedResultsController.fetchedObjects ?? []).first(where: { $0.objectID == upperTimelineIndexObjectID }) else {
stateMachine.enter(Fail.self)
return
}
let statusIDs = (viewModel.fetchedResultsController.fetchedObjects ?? []).compactMap { timelineIndex in
timelineIndex.status.id
}
// TODO: only set large count when using Wi-Fi
let maxID = timelineIndex.status.id
viewModel.context.apiService.homeTimeline(domain: activeMastodonAuthenticationBox.domain,maxID: maxID, authorizationBox: activeMastodonAuthenticationBox)
.delay(for: .seconds(1), scheduler: DispatchQueue.main)
.receive(on: DispatchQueue.main)
.sink { completion in
viewModel.homeTimelineNavigationBarTitleViewModel.receiveLoadingStateCompletion(completion)
switch completion {
case .failure(let error):
// TODO: handle error
os_log("%{public}s[%{public}ld], %{public}s: fetch statuses failed. %s", ((#file as NSString).lastPathComponent), #line, #function, error.localizedDescription)
stateMachine.enter(Fail.self)
case .finished:
break
}
} receiveValue: { response in
let statuses = response.value
let newStatuses = statuses.filter { !statusIDs.contains($0.id) }
os_log("%{public}s[%{public}ld], %{public}s: load %{public}ld statuses, %{public}%ld new statuses", ((#file as NSString).lastPathComponent), #line, #function, statuses.count, newStatuses.count)
if newStatuses.isEmpty {
stateMachine.enter(Fail.self)
} else {
stateMachine.enter(Success.self)
}
}
.store(in: &viewModel.disposeBag)
}
}
class Fail: AsyncHomeTimelineViewModel.LoadMiddleState {
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
// guard let viewModel = viewModel else { return false }
return stateClass == Loading.self
}
}
class Success: AsyncHomeTimelineViewModel.LoadMiddleState {
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
// guard let viewModel = viewModel else { return false }
return false
}
}
}
#endif

View File

@ -1,117 +0,0 @@
//
// AsyncHomeTimelineViewModel+LoadOldestState.swift
// Mastodon
//
// Created by MainasuK Cirno on 2021-6-21.
//
#if ASDK
import os.log
import Foundation
import GameplayKit
extension AsyncHomeTimelineViewModel {
class LoadOldestState: GKState {
weak var viewModel: AsyncHomeTimelineViewModel?
init(viewModel: AsyncHomeTimelineViewModel) {
self.viewModel = viewModel
}
override func didEnter(from previousState: GKState?) {
os_log("%{public}s[%{public}ld], %{public}s: enter %s, previous: %s", ((#file as NSString).lastPathComponent), #line, #function, self.debugDescription, previousState.debugDescription)
viewModel?.loadOldestStateMachinePublisher.send(self)
}
}
}
extension AsyncHomeTimelineViewModel.LoadOldestState {
class Initial: AsyncHomeTimelineViewModel.LoadOldestState {
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
guard let viewModel = viewModel else { return false }
guard !(viewModel.fetchedResultsController.fetchedObjects ?? []).isEmpty else { return false }
return stateClass == Loading.self
}
}
class Loading: AsyncHomeTimelineViewModel.LoadOldestState {
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
return stateClass == Fail.self || stateClass == Idle.self || stateClass == NoMore.self
}
override func didEnter(from previousState: GKState?) {
super.didEnter(from: previousState)
guard let viewModel = viewModel, let stateMachine = stateMachine else { return }
guard let activeMastodonAuthenticationBox = viewModel.context.authenticationService.activeMastodonAuthenticationBox.value else {
assertionFailure()
stateMachine.enter(Fail.self)
return
}
guard let last = viewModel.fetchedResultsController.fetchedObjects?.last else {
stateMachine.enter(Idle.self)
return
}
// TODO: only set large count when using Wi-Fi
let maxID = last.status.id
viewModel.context.apiService.homeTimeline(domain: activeMastodonAuthenticationBox.domain, maxID: maxID, authorizationBox: activeMastodonAuthenticationBox)
.delay(for: .seconds(1), scheduler: DispatchQueue.main)
.receive(on: DispatchQueue.main)
.sink { completion in
viewModel.homeTimelineNavigationBarTitleViewModel.receiveLoadingStateCompletion(completion)
switch completion {
case .failure(let error):
os_log("%{public}s[%{public}ld], %{public}s: fetch statuses failed. %s", ((#file as NSString).lastPathComponent), #line, #function, error.localizedDescription)
case .finished:
// handle isFetchingLatestTimeline in fetch controller delegate
break
}
} receiveValue: { response in
let statuses = response.value
// enter no more state when no new statuses
if statuses.isEmpty || (statuses.count == 1 && statuses[0].id == maxID) {
stateMachine.enter(NoMore.self)
} else {
stateMachine.enter(Idle.self)
}
}
.store(in: &viewModel.disposeBag)
}
}
class Fail: AsyncHomeTimelineViewModel.LoadOldestState {
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
return stateClass == Loading.self || stateClass == Idle.self
}
}
class Idle: AsyncHomeTimelineViewModel.LoadOldestState {
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
return stateClass == Loading.self
}
}
class NoMore: AsyncHomeTimelineViewModel.LoadOldestState {
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
// reset state if needs
return stateClass == Idle.self
}
override func didEnter(from previousState: GKState?) {
guard let viewModel = viewModel else { return }
guard let diffableDataSource = viewModel.diffableDataSource else {
assertionFailure()
return
}
DispatchQueue.main.async {
var snapshot = diffableDataSource.snapshot()
snapshot.deleteItems([.bottomLoader])
diffableDataSource.apply(snapshot)
}
}
}
}
#endif

View File

@ -1,151 +0,0 @@
//
// AsyncHomeTimelineViewModel.swift
// Mastodon
//
// Created by MainasuK Cirno on 2021-6-21.
//
//
#if ASDK
import os.log
import func AVFoundation.AVMakeRect
import UIKit
import AVKit
import Combine
import CoreData
import CoreDataStack
import GameplayKit
import AlamofireImage
import DateToolsSwift
import AsyncDisplayKit
final class AsyncHomeTimelineViewModel: NSObject {
var disposeBag = Set<AnyCancellable>()
var observations = Set<NSKeyValueObservation>()
// input
let context: AppContext
let timelinePredicate = CurrentValueSubject<NSPredicate?, Never>(nil)
let fetchedResultsController: NSFetchedResultsController<HomeTimelineIndex>
let isFetchingLatestTimeline = CurrentValueSubject<Bool, Never>(false)
let viewDidAppear = PassthroughSubject<Void, Never>()
let homeTimelineNavigationBarTitleViewModel: HomeTimelineNavigationBarTitleViewModel
weak var tableNode: ASTableNode?
weak var contentOffsetAdjustableTimelineViewControllerDelegate: ContentOffsetAdjustableTimelineViewControllerDelegate?
//weak var tableView: UITableView?
weak var timelineMiddleLoaderTableViewCellDelegate: TimelineMiddleLoaderTableViewCellDelegate?
let timelineIsEmpty = CurrentValueSubject<Bool, Never>(false)
let homeTimelineNeedRefresh = PassthroughSubject<Void, Never>()
// output
var diffableDataSource: TableNodeDiffableDataSource<StatusSection, Item>?
// top loader
private(set) lazy var loadLatestStateMachine: GKStateMachine = {
// exclude timeline middle fetcher state
let stateMachine = GKStateMachine(states: [
LoadLatestState.Initial(viewModel: self),
LoadLatestState.Loading(viewModel: self),
LoadLatestState.Fail(viewModel: self),
LoadLatestState.Idle(viewModel: self),
])
stateMachine.enter(LoadLatestState.Initial.self)
return stateMachine
}()
lazy var loadLatestStateMachinePublisher = CurrentValueSubject<LoadLatestState?, Never>(nil)
// bottom loader
private(set) lazy var loadOldestStateMachine: GKStateMachine = {
// exclude timeline middle fetcher state
let stateMachine = GKStateMachine(states: [
LoadOldestState.Initial(viewModel: self),
LoadOldestState.Loading(viewModel: self),
LoadOldestState.Fail(viewModel: self),
LoadOldestState.Idle(viewModel: self),
LoadOldestState.NoMore(viewModel: self),
])
stateMachine.enter(LoadOldestState.Initial.self)
return stateMachine
}()
lazy var loadOldestStateMachinePublisher = CurrentValueSubject<LoadOldestState?, Never>(nil)
// middle loader
let loadMiddleSateMachineList = CurrentValueSubject<[NSManagedObjectID: GKStateMachine], Never>([:]) // TimelineIndex.objectID : middle loading state machine
// var diffableDataSource: UITableViewDiffableDataSource<StatusSection, Item>?
var cellFrameCache = NSCache<NSNumber, NSValue>()
init(context: AppContext) {
self.context = context
self.fetchedResultsController = {
let fetchRequest = HomeTimelineIndex.sortedFetchRequest
fetchRequest.fetchBatchSize = 20
fetchRequest.returnsObjectsAsFaults = false
fetchRequest.relationshipKeyPathsForPrefetching = [#keyPath(HomeTimelineIndex.status)]
let controller = NSFetchedResultsController(
fetchRequest: fetchRequest,
managedObjectContext: context.managedObjectContext,
sectionNameKeyPath: nil,
cacheName: nil
)
return controller
}()
self.homeTimelineNavigationBarTitleViewModel = HomeTimelineNavigationBarTitleViewModel(context: context)
super.init()
fetchedResultsController.delegate = self
timelinePredicate
.receive(on: DispatchQueue.main)
.compactMap { $0 }
.first() // set once
.sink { [weak self] predicate in
guard let self = self else { return }
self.fetchedResultsController.fetchRequest.predicate = predicate
do {
try self.fetchedResultsController.performFetch()
} catch {
assertionFailure(error.localizedDescription)
}
}
.store(in: &disposeBag)
context.authenticationService.activeMastodonAuthentication
.sink { [weak self] activeMastodonAuthentication in
guard let self = self else { return }
guard let mastodonAuthentication = activeMastodonAuthentication else { return }
let activeMastodonUserID = mastodonAuthentication.userID
let predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [
HomeTimelineIndex.predicate(userID: activeMastodonUserID),
HomeTimelineIndex.notDeleted()
])
self.timelinePredicate.value = predicate
}
.store(in: &disposeBag)
homeTimelineNeedRefresh
.sink { [weak self] _ in
self?.loadLatestStateMachine.enter(LoadLatestState.Loading.self)
}
.store(in: &disposeBag)
homeTimelineNavigationBarTitleViewModel.isPublished
.sink { [weak self] isPublished in
guard let self = self else { return }
self.homeTimelineNeedRefresh.send()
}
.store(in: &disposeBag)
}
deinit {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s:", ((#file as NSString).lastPathComponent), #line, #function)
}
}
extension AsyncHomeTimelineViewModel: SuggestionAccountViewModelDelegate { }
#endif

View File

@ -74,11 +74,7 @@ class MainTabBarController: UITabBarController {
let viewController: UIViewController
switch self {
case .home:
#if ASDK
let _viewController: NeedsDependency & UIViewController = UserDefaults.shared.preferAsyncHomeTimeline ? AsyncHomeTimelineViewController() : HomeTimelineViewController()
#else
let _viewController = HomeTimelineViewController()
#endif
_viewController.context = context
_viewController.coordinator = coordinator
viewController = _viewController
@ -596,33 +592,3 @@ extension MainTabBarController {
}
}
#if ASDK
extension MainTabBarController {
override func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
guard let event = event else { return }
switch event.subtype {
case .motionShake:
let alertController = UIAlertController(title: "ASDK Debug Panel", message: nil, preferredStyle: .alert)
let toggleHomeAction = UIAlertAction(title: "Toggle Home", style: .default) { [weak self] _ in
guard let self = self else { return }
MainTabBarController.toggleAsyncHome()
let okAlertController = UIAlertController(title: "Success", message: "Please restart the app", preferredStyle: .alert)
let okAction = UIAlertAction(title: "OK", style: .default, handler: nil)
okAlertController.addAction(okAction)
self.coordinator.present(scene: .alertController(alertController: okAlertController), from: nil, transition: .alertController(animated: true, completion: nil))
}
alertController.addAction(toggleHomeAction)
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
alertController.addAction(cancelAction)
self.coordinator.present(scene: .alertController(alertController: alertController), from: nil, transition: .alertController(animated: true, completion: nil))
default:
break
}
}
static func toggleAsyncHome() {
UserDefaults.shared.preferAsyncHomeTimeline.toggle()
}
}
#endif

View File

@ -1,25 +0,0 @@
//
// ASMetaEditableTextNode.swift
// Mastodon
//
// Created by Cirno MainasuK on 2021-6-20.
//
#if ASDK
import UIKit
import AsyncDisplayKit
protocol ASMetaEditableTextNodeDelegate: AnyObject {
func metaEditableTextNode(_ textNode: ASMetaEditableTextNode, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool
}
final class ASMetaEditableTextNode: ASEditableTextNode, UITextViewDelegate {
weak var metaEditableTextNodeDelegate: ASMetaEditableTextNodeDelegate?
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
return metaEditableTextNodeDelegate?.metaEditableTextNode(self, shouldInteractWith: URL, in: characterRange, interaction: interaction) ?? false
}
}
#endif

View File

@ -1,234 +0,0 @@
//
// StatusNNode.swift
// Mastodon
//
// Created by Cirno MainasuK on 2021-6-19.
//
#if ASDK
import UIKit
import Combine
import AsyncDisplayKit
import CoreDataStack
import func AVFoundation.AVMakeRect
protocol StatusNodeDelegate: AnyObject {
//func statusNode(_ node: StatusNode, statusContentTextNode: ASMetaEditableTextNode, didSelectActiveEntityType type: ActiveEntityType)
}
final class StatusNode: ASCellNode {
var disposeBag = Set<AnyCancellable>()
var timestamp: Date
var timestampSubscription: AnyCancellable?
weak var delegate: StatusNodeDelegate? // needs assign on main queue
static let avatarImageSize = CGSize(width: 42, height: 42)
static let avatarImageCornerRadius: CGFloat = 4
// static let statusContentAppearance: MastodonStatusContent.Appearance = {
// let linkAttributes: [NSAttributedString.Key: Any] = [
// .font: UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 17, weight: .semibold)),
// .foregroundColor: Asset.Colors.brandBlue.color
// ]
// return MastodonStatusContent.Appearance(
// attributes: [
// .font: UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 17, weight: .regular)),
// .foregroundColor: Asset.Colors.Label.primary.color
// ],
// urlAttributes: linkAttributes,
// hashtagAttributes: linkAttributes,
// mentionAttributes: linkAttributes
// )
// }()
let avatarImageNode: ASNetworkImageNode = {
let node = ASNetworkImageNode()
node.contentMode = .scaleAspectFill
node.defaultImage = UIImage.placeholder(color: .systemFill)
node.forcedSize = StatusNode.avatarImageSize
node.cornerRadius = StatusNode.avatarImageCornerRadius
// node.cornerRoundingType = .precomposited
// node.shouldRenderProgressImages = true
return node
}()
let nameTextNode = ASTextNode()
let nameDotTextNode = ASTextNode()
let dateTextNode = ASTextNode()
let usernameTextNode = ASTextNode()
let statusContentTextNode: ASMetaEditableTextNode = {
let node = ASMetaEditableTextNode()
node.scrollEnabled = false
return node
}()
let mosaicImageViewModel: MosaicImageViewModel
let mediaMultiplexImageNodes: [ASMultiplexImageNode]
init(status: Status) {
timestamp = (status.reblog ?? status).createdAt
let _mosaicImageViewModel: MosaicImageViewModel = {
let mediaAttachments = Array((status.reblog ?? status).mediaAttachments ?? []).sorted { $0.index.compare($1.index) == .orderedAscending }
return MosaicImageViewModel(mediaAttachments: mediaAttachments)
}()
mosaicImageViewModel = _mosaicImageViewModel
mediaMultiplexImageNodes = {
var imageNodes: [ASMultiplexImageNode] = []
for _ in 0..<_mosaicImageViewModel.metas.count {
let imageNode = ASMultiplexImageNode() // TODO: adapt downloader
imageNode.downloadsIntermediateImages = true
imageNode.imageIdentifiers = ["url", "previewURL"].map { $0 as NSString } // quality in descending order
imageNodes.append(imageNode)
}
return imageNodes
}()
super.init()
automaticallyManagesSubnodes = true
if let url = (status.reblog ?? status).author.avatarImageURL() {
avatarImageNode.url = url
}
nameTextNode.attributedText = NSAttributedString(string: status.author.displayNameWithFallback, attributes: [
.foregroundColor: Asset.Colors.Label.primary.color,
.font: UIFont.systemFont(ofSize: 17, weight: .semibold)
])
nameDotTextNode.attributedText = NSAttributedString(string: "·", attributes: [
.foregroundColor: Asset.Colors.Label.secondary.color,
.font: UIFont.systemFont(ofSize: 13, weight: .regular)
])
// set date
dateTextNode.attributedText = NSAttributedString(string: timestamp.localizedSlowedTimeAgoSinceNow, attributes: [
.foregroundColor: Asset.Colors.Label.secondary.color,
.font: UIFont.systemFont(ofSize: 13, weight: .regular)
])
usernameTextNode.attributedText = NSAttributedString(string: "@" + status.author.acct, attributes: [
.foregroundColor: Asset.Colors.Label.secondary.color,
.font: UIFont.systemFont(ofSize: 15, weight: .regular)
])
// FIXME:
// statusContentTextNode.metaEditableTextNodeDelegate = self
// if let parseResult = try? MastodonStatusContent.parse(
// content: (status.reblog ?? status).content,
// emojiDict: (status.reblog ?? status).emojiDict
// ) {
// statusContentTextNode.attributedText = parseResult.trimmedAttributedString(appearance: StatusNode.statusContentAppearance)
// }
for imageNode in mediaMultiplexImageNodes {
imageNode.delegate = self
}
}
override func didEnterDisplayState() {
super.didEnterDisplayState()
timestampSubscription = AppContext.shared.timestampUpdatePublisher
.sink { [weak self] _ in
guard let self = self else { return }
self.dateTextNode.attributedText = NSAttributedString(string: self.timestamp.localizedSlowedTimeAgoSinceNow, attributes: [
.foregroundColor: Asset.Colors.Label.secondary.color,
.font: UIFont.systemFont(ofSize: 13, weight: .regular)
])
}
// FIXME: needs move to other only once called callback in life cycle like: `viewDidLoad`
statusContentTextNode.textView.isEditable = false
statusContentTextNode.textView.textDragInteraction?.isEnabled = false
statusContentTextNode.textView.linkTextAttributes = [
.foregroundColor: Asset.Colors.brandBlue.color
]
}
override func didExitVisibleState() {
super.didExitVisibleState()
timestampSubscription = nil
}
override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec {
let headerStack = ASStackLayoutSpec.horizontal()
headerStack.alignItems = .center
headerStack.spacing = 5
var headerStackChildren: [ASLayoutElement] = []
avatarImageNode.style.preferredSize = StatusNode.avatarImageSize
headerStackChildren.append(avatarImageNode)
let authorMetaHeaderStack = ASStackLayoutSpec.horizontal()
authorMetaHeaderStack.alignItems = .center
authorMetaHeaderStack.spacing = 4
authorMetaHeaderStack.children = [
nameTextNode,
nameDotTextNode,
dateTextNode,
]
let authorMetaStack = ASStackLayoutSpec.vertical()
authorMetaStack.children = [
authorMetaHeaderStack,
usernameTextNode,
]
headerStackChildren.append(authorMetaStack)
headerStack.children = headerStackChildren
let verticalStack = ASStackLayoutSpec.vertical()
verticalStack.spacing = 10
var verticalStackChildren: [ASLayoutElement] = [
headerStack,
statusContentTextNode,
]
if !mediaMultiplexImageNodes.isEmpty {
for (imageNode, meta) in zip(mediaMultiplexImageNodes, mosaicImageViewModel.metas) {
imageNode.style.preferredSize = AVMakeRect(aspectRatio: meta.size, insideRect: CGRect(origin: .zero, size: constrainedSize.max)).size
let layout = ASRatioLayoutSpec(ratio: meta.size.height / meta.size.width, child: imageNode)
verticalStackChildren.append(layout)
}
}
verticalStack.children = verticalStackChildren
return ASInsetLayoutSpec(
insets: UIEdgeInsets(top: 12, left: 16, bottom: 12, right: 16),
child: verticalStack
)
}
}
// MARK: - ASEditableTextNodeDelegate
//extension StatusNode: ASMetaEditableTextNodeDelegate {
// func metaEditableTextNode(_ textNode: ASMetaEditableTextNode, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
// guard let activityEntityType = ActiveEntityType(url: URL) else {
// return false
// }
// defer {
// delegate?.statusNode(self, statusContentTextNode: textNode, didSelectActiveEntityType: activityEntityType)
// }
// return false
// }
//}
// MARK: - ASMultiplexImageNodeDataSource
extension StatusNode: ASMultiplexImageNodeDataSource {
func multiplexImageNode(_ imageNode: ASMultiplexImageNode, urlForImageIdentifier imageIdentifier: ASImageIdentifier) -> URL? {
guard let imageNodeIndex = mediaMultiplexImageNodes.firstIndex(of: imageNode) else { return nil }
guard imageNodeIndex < mosaicImageViewModel.metas.count else { return nil }
let meta = mosaicImageViewModel.metas[imageNodeIndex]
switch imageIdentifier {
case "url" as NSString:
return meta.url
case "previewURL" as NSString:
return meta.previewURL
default:
assertionFailure()
return nil
}
}
}
#endif

View File

@ -1,41 +0,0 @@
//
// TimelineBottomLoaderNode.swift
// Mastodon
//
// Created by Cirno MainasuK on 2021-6-19.
//
#if ASDK
import UIKit
import AsyncDisplayKit
final class TimelineBottomLoaderNode: ASCellNode {
let activityIndicatorNode = ActivityIndicatorNode()
override init() {
super.init()
automaticallyManagesSubnodes = true
activityIndicatorNode.bounds = CGRect(x: 0, y: 0, width: 40, height: 40)
}
override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec {
let contentStack = ASStackLayoutSpec.horizontal()
contentStack.alignItems = .center
contentStack.spacing = 7
contentStack.children = [activityIndicatorNode]
return contentStack
}
override func didEnterDisplayState() {
super.didEnterDisplayState()
activityIndicatorNode.animating = true
}
}
#endif

View File

@ -1,54 +0,0 @@
//
// TimelineMiddleLoaderNode.swift
// Mastodon
//
// Created by Cirno MainasuK on 2021-6-19.
//
#if ASDK
import UIKit
import AsyncDisplayKit
final class TimelineMiddleLoaderNode: ASCellNode {
static let loadButtonFont = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 17, weight: .medium))
let activityIndicatorNode = ASDisplayNode(viewBlock: {
let view = UIActivityIndicatorView(style: .medium)
view.hidesWhenStopped = true
return view
})
let loadButtonNode = ASButtonNode()
override init() {
super.init()
automaticallyManagesSubnodes = true
loadButtonNode.setAttributedTitle(
NSAttributedString(
string: L10n.Common.Controls.Timeline.Loader.loadMissingPosts,
attributes: [
.foregroundColor: Asset.Colors.brandBlue.color,
.font: TimelineMiddleLoaderNode.loadButtonFont
]),
for: .normal
)
}
override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec {
let contentStack = ASStackLayoutSpec.horizontal()
contentStack.alignItems = .center
contentStack.spacing = 7
contentStack.children = [loadButtonNode]
return contentStack
}
}
#endif

View File

@ -12,10 +12,6 @@ import AppShared
import AVFoundation
@_exported import MastodonUI
#if ASDK
import AsyncDisplayKit
#endif
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
@ -41,13 +37,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
count += 1 // Int64. could ignore overflow here
UserDefaults.shared.processCompletedCount = count
#if ASDK && DEBUG
// PerformanceMonitor.shared().start()
// ASDisplayNode.shouldShowRangeDebugOverlay = true
// ASControlNode.enableHitTestDebug = true
// ASImageNode.shouldShowImageScalingOverlay = true
#endif
return true
}

View File

@ -1,75 +0,0 @@
// ref: https://github.com/Adlai-Holler/ASDKPlaceholderTest/blob/eea9fa7cff2d16a57efb47d208422ea9b49a630a/ASDKPlaceholderTest/ASDisplayNodeSubclasses.swift
#if ASDK
import Foundation
import AsyncDisplayKit
import UIKit
/**
A node that shows a `UIActivityIndicatorView`. Does not support layer backing.
Note: You must not change the style to or from `.WhiteLarge` after init, or the node's size will not update.
*/
class ActivityIndicatorNode: ASDisplayNode {
private static let defaultSize = CGSize(width: 20, height: 20)
private static let largeSize = CGSize(width: 37, height: 37)
init(style: UIActivityIndicatorView.Style = .medium) {
super.init()
setViewBlock {
UIActivityIndicatorView(style: style)
}
self.style.preferredSize = style == .large ? ActivityIndicatorNode.defaultSize : ActivityIndicatorNode.largeSize
}
var activityIndicatorView: UIActivityIndicatorView {
return view as! UIActivityIndicatorView
}
override func didLoad() {
super.didLoad()
if animating {
activityIndicatorView.startAnimating()
}
activityIndicatorView.color = color
activityIndicatorView.hidesWhenStopped = hidesWhenStopped
}
/// Wrapper for `UIActivityIndicatorView.hidesWhenStopped`. NOTE: You must respect thread affinity.
var hidesWhenStopped = true {
didSet {
if isNodeLoaded {
assert(Thread.isMainThread)
activityIndicatorView.hidesWhenStopped = hidesWhenStopped
}
}
}
/// Wrapper for `UIActivityIndicatorView.color`. NOTE: You must respect thread affinity.
var color: UIColor? {
didSet {
if isNodeLoaded {
assert(Thread.isMainThread)
activityIndicatorView.color = color
}
}
}
/// Wrapper for `UIActivityIndicatorView.animating`. NOTE: You must respect thread affinity.
var animating = false {
didSet {
if isNodeLoaded {
assert(Thread.isMainThread)
if animating {
activityIndicatorView.startAnimating()
} else {
activityIndicatorView.stopAnimating()
}
}
}
}
}
#endif

View File

@ -8,7 +8,6 @@ target 'Mastodon' do
# UI
pod 'UITextField+Shake', '~> 1.2'
pod 'Texture', '~> 3.0.0', :configurations => ['ASDK - Debug', 'ASDK - Release']
# misc
pod 'SwiftGen', '~> 6.4.0'
@ -16,7 +15,7 @@ target 'Mastodon' do
pod 'Kanna', '~> 5.2.2'
# DEBUG
pod 'FLEX', '~> 4.4.0', :configurations => ['Debug', 'ASDK - Debug']
pod 'FLEX', '~> 4.4.0', :configurations => ['Debug']
target 'MastodonTests' do
inherit! :search_paths
@ -63,4 +62,4 @@ post_install do |installer|
config.build_settings.delete 'IPHONEOS_DEPLOYMENT_TARGET'
end
end
end
end

View File

@ -3,42 +3,7 @@ PODS:
- FLEX (4.4.1)
- Kanna (5.2.7)
- Keys (1.0.1)
- PINCache (3.0.3):
- PINCache/Arc-exception-safe (= 3.0.3)
- PINCache/Core (= 3.0.3)
- PINCache/Arc-exception-safe (3.0.3):
- PINCache/Core
- PINCache/Core (3.0.3):
- PINOperation (~> 1.2.1)
- PINOperation (1.2.1)
- PINRemoteImage/Core (3.0.3):
- PINOperation
- PINRemoteImage/iOS (3.0.3):
- PINRemoteImage/Core
- PINRemoteImage/PINCache (3.0.3):
- PINCache (~> 3.0.3)
- PINRemoteImage/Core
- SwiftGen (6.4.0)
- Texture (3.0.0):
- Texture/AssetsLibrary (= 3.0.0)
- Texture/Core (= 3.0.0)
- Texture/MapKit (= 3.0.0)
- Texture/Photos (= 3.0.0)
- Texture/PINRemoteImage (= 3.0.0)
- Texture/Video (= 3.0.0)
- Texture/AssetsLibrary (3.0.0):
- Texture/Core
- Texture/Core (3.0.0)
- Texture/MapKit (3.0.0):
- Texture/Core
- Texture/Photos (3.0.0):
- Texture/Core
- Texture/PINRemoteImage (3.0.0):
- PINRemoteImage/iOS (~> 3.0.0)
- PINRemoteImage/PINCache
- Texture/Core
- Texture/Video (3.0.0):
- Texture/Core
- "UITextField+Shake (1.2.1)"
DEPENDENCIES:
@ -47,7 +12,6 @@ DEPENDENCIES:
- Kanna (~> 5.2.2)
- Keys (from `Pods/CocoaPodsKeys`)
- SwiftGen (~> 6.4.0)
- Texture (~> 3.0.0)
- "UITextField+Shake (~> 1.2)"
SPEC REPOS:
@ -55,11 +19,7 @@ SPEC REPOS:
- DateToolsSwift
- FLEX
- Kanna
- PINCache
- PINOperation
- PINRemoteImage
- SwiftGen
- Texture
- "UITextField+Shake"
EXTERNAL SOURCES:
@ -71,13 +31,9 @@ SPEC CHECKSUMS:
FLEX: 7ca2c8cd3a435ff501ff6d2f2141e9bdc934eaab
Kanna: 01cfbddc127f5ff0963692f285fcbc8a9d62d234
Keys: a576f4c9c1c641ca913a959a9c62ed3f215a8de9
PINCache: 7a8fc1a691173d21dbddbf86cd515de6efa55086
PINOperation: 00c935935f1e8cf0d1e2d6b542e75b88fc3e5e20
PINRemoteImage: f1295b29f8c5e640e25335a1b2bd9d805171bd01
SwiftGen: 67860cc7c3cfc2ed25b9b74cfd55495fc89f9108
Texture: 2f109e937850d94d1d07232041c9c7313ccddb81
"UITextField+Shake": 298ac5a0f239d731bdab999b19b628c956ca0ac3
PODFILE CHECKSUM: 4db0bdf969729c5758bd923e33d9e097cb892086
PODFILE CHECKSUM: 37aa3ed14a767c806ece40b6c99ab3c59b9f8475
COCOAPODS: 1.11.2