diff --git a/.github/scripts/build.sh b/.github/scripts/build.sh
index 76e65f49..f5894901 100755
--- a/.github/scripts/build.sh
+++ b/.github/scripts/build.sh
@@ -7,7 +7,6 @@ set -eo pipefail
xcodebuild -workspace Mastodon.xcworkspace \
-scheme Mastodon \
- -disableAutomaticPackageResolution \
-destination "platform=iOS Simulator,name=iPhone SE (2nd generation)" \
clean \
- build | xcpretty
\ No newline at end of file
+ build | xcpretty
diff --git a/AppShared/Info.plist b/AppShared/Info.plist
index 9fe845c6..f652792e 100644
--- a/AppShared/Info.plist
+++ b/AppShared/Info.plist
@@ -15,8 +15,8 @@
CFBundlePackageType
$(PRODUCT_BUNDLE_PACKAGE_TYPE)
CFBundleShortVersionString
- 1.2.0
+ 1.3.0
CFBundleVersion
- 88
+ 90
diff --git a/CoreDataStack/Info.plist b/CoreDataStack/Info.plist
index 9fe845c6..f652792e 100644
--- a/CoreDataStack/Info.plist
+++ b/CoreDataStack/Info.plist
@@ -15,8 +15,8 @@
CFBundlePackageType
$(PRODUCT_BUNDLE_PACKAGE_TYPE)
CFBundleShortVersionString
- 1.2.0
+ 1.3.0
CFBundleVersion
- 88
+ 90
diff --git a/CoreDataStackTests/Info.plist b/CoreDataStackTests/Info.plist
index 9fe845c6..f652792e 100644
--- a/CoreDataStackTests/Info.plist
+++ b/CoreDataStackTests/Info.plist
@@ -15,8 +15,8 @@
CFBundlePackageType
$(PRODUCT_BUNDLE_PACKAGE_TYPE)
CFBundleShortVersionString
- 1.2.0
+ 1.3.0
CFBundleVersion
- 88
+ 90
diff --git a/Localization/app.json b/Localization/app.json
index 6d3b2fcc..0071f6f9 100644
--- a/Localization/app.json
+++ b/Localization/app.json
@@ -193,10 +193,14 @@
},
"scene": {
"welcome": {
- "slogan": "Social networking\nback in your hands."
+ "slogan": "Social networking\nback in your hands.",
+ "get_started": "Get Started",
+ "log_in": "Log In"
},
"server_picker": {
- "title": "Pick a server,\nany server.",
+ "title": "Mastodon is made of users in different communities.",
+ "subtitle": "Pick a community based on your interests, region, or a general purpose one.",
+ "subtitle_extend": "Pick a community based on your interests, region, or a general purpose one. Each community is operated by an entirely independent organization or individual.",
"button": {
"category": {
"all": "All",
@@ -223,7 +227,7 @@
"category": "CATEGORY"
},
"input": {
- "placeholder": "Find a server or join your own..."
+ "placeholder": "Search communities"
},
"empty_state": {
"finding_servers": "Finding available servers...",
@@ -232,7 +236,7 @@
}
},
"register": {
- "title": "Tell us about you.",
+ "title": "Let’s get you set up on %s",
"input": {
"avatar": {
"delete": "Delete"
@@ -249,6 +253,12 @@
},
"password": {
"placeholder": "password",
+ "require": "Your password needs at least:",
+ "character_limit": "8 characters",
+ "accessibility": {
+ "checked": "checked",
+ "unchecked": "unchecked"
+ },
"hint": "Your password needs at least eight characters"
},
"invite": {
@@ -286,7 +296,7 @@
},
"server_rules": {
"title": "Some ground rules.",
- "subtitle": "These rules are set by the admins of %s.",
+ "subtitle": "These are set and enforced by the %s moderators.",
"prompt": "By continuing, you’re subject to the terms of service and privacy policy for %s.",
"terms_of_service": "terms of service",
"privacy_policy": "privacy policy",
@@ -296,10 +306,10 @@
},
"confirm_email": {
"title": "One last thing.",
- "subtitle": "We just sent an email to %s,\ntap the link to confirm your account.",
+ "subtitle": "Tap the link we emailed to you to verify your account.",
"button": {
"open_email_app": "Open Email App",
- "dont_receive_email": "I never got an email"
+ "resend": "Resend"
},
"dont_receive_email": {
"title": "Check your email",
@@ -554,4 +564,4 @@
"accessibility_hint": "Double tap to dismiss this wizard"
}
}
-}
\ No newline at end of file
+}
diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj
index 6b7644e3..b79fb901 100644
--- a/Mastodon.xcodeproj/project.pbxproj
+++ b/Mastodon.xcodeproj/project.pbxproj
@@ -22,7 +22,7 @@
0FAA101C25E10E760017CCDE /* UIFont.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FAA101B25E10E760017CCDE /* UIFont.swift */; };
0FAA102725E1126A0017CCDE /* MastodonPickServerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FAA102625E1126A0017CCDE /* MastodonPickServerViewController.swift */; };
0FB3D2F725E4C24D00AAD544 /* MastodonPickServerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FB3D2F625E4C24D00AAD544 /* MastodonPickServerViewModel.swift */; };
- 0FB3D2FE25E4CB6400AAD544 /* PickServerTitleCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FB3D2FD25E4CB6400AAD544 /* PickServerTitleCell.swift */; };
+ 0FB3D2FE25E4CB6400AAD544 /* OnboardingHeadlineTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FB3D2FD25E4CB6400AAD544 /* OnboardingHeadlineTableViewCell.swift */; };
0FB3D30825E524C600AAD544 /* PickServerCategoriesCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FB3D30725E524C600AAD544 /* PickServerCategoriesCell.swift */; };
0FB3D30F25E525CD00AAD544 /* PickServerCategoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FB3D30E25E525CD00AAD544 /* PickServerCategoryView.swift */; };
0FB3D31E25E534C700AAD544 /* PickServerCategoryCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FB3D31D25E534C700AAD544 /* PickServerCategoryCollectionViewCell.swift */; };
@@ -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 */; };
@@ -192,6 +191,18 @@
DB03F7F32689AEA3007B274C /* ComposeRepliedToStatusContentTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB03F7F22689AEA3007B274C /* ComposeRepliedToStatusContentTableViewCell.swift */; };
DB03F7F52689B782007B274C /* ComposeTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB03F7F42689B782007B274C /* ComposeTableView.swift */; };
DB040ED126538E3D00BEE9D8 /* Trie.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB040ED026538E3C00BEE9D8 /* Trie.swift */; };
+ DB0617EB277EF3820030EE79 /* GradientBorderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0617EA277EF3820030EE79 /* GradientBorderView.swift */; };
+ DB0617ED277F02C50030EE79 /* OnboardingNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0617EC277F02C50030EE79 /* OnboardingNavigationController.swift */; };
+ DB0617EF277F12720030EE79 /* NavigationActionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0617EE277F12720030EE79 /* NavigationActionView.swift */; };
+ DB0617F1278413D00030EE79 /* PickServerServerSectionTableHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0617F0278413D00030EE79 /* PickServerServerSectionTableHeaderView.swift */; };
+ DB0617F527855AB90030EE79 /* ServerRuleSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0617F427855AB90030EE79 /* ServerRuleSection.swift */; };
+ DB0617FD27855BFE0030EE79 /* ServerRuleItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0617FC27855BFE0030EE79 /* ServerRuleItem.swift */; };
+ DB0617FF27855D6C0030EE79 /* MastodonServerRulesViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0617FE27855D6C0030EE79 /* MastodonServerRulesViewModel+Diffable.swift */; };
+ DB0618012785732C0030EE79 /* ServerRulesTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0618002785732C0030EE79 /* ServerRulesTableViewCell.swift */; };
+ DB0618032785A7100030EE79 /* RegisterSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0618022785A7100030EE79 /* RegisterSection.swift */; };
+ DB0618052785A73D0030EE79 /* RegisterItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0618042785A73D0030EE79 /* RegisterItem.swift */; };
+ DB0618072785A8880030EE79 /* MastodonRegisterViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0618062785A8880030EE79 /* MastodonRegisterViewModel+Diffable.swift */; };
+ DB06180A2785B2AB0030EE79 /* MastodonRegisterAvatarTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0618092785B2AB0030EE79 /* MastodonRegisterAvatarTableViewCell.swift */; };
DB084B5725CBC56C00F898ED /* Status.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB084B5625CBC56C00F898ED /* Status.swift */; };
DB0AC6FC25CD02E600D75117 /* APIService+Instance.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0AC6FB25CD02E600D75117 /* APIService+Instance.swift */; };
DB0C946526A6FD4D0088FB11 /* AlamofireImage in Frameworks */ = {isa = PBXBuildFile; productRef = DB0C946426A6FD4D0088FB11 /* AlamofireImage */; };
@@ -215,7 +226,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 */; };
@@ -242,7 +252,6 @@
DB427DE225BAA00100D1B89D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DB427DE025BAA00100D1B89D /* LaunchScreen.storyboard */; };
DB427DED25BAA00100D1B89D /* MastodonTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB427DEC25BAA00100D1B89D /* MastodonTests.swift */; };
DB427DF825BAA00100D1B89D /* MastodonUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB427DF725BAA00100D1B89D /* MastodonUITests.swift */; };
- DB44384F25E8C1FA008912A2 /* CALayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB44384E25E8C1FA008912A2 /* CALayer.swift */; };
DB443CD42694627B00159B29 /* AppearanceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB443CD32694627B00159B29 /* AppearanceView.swift */; };
DB44767B260B3B8C00B66B82 /* CustomEmojiPickerInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB44767A260B3B8C00B66B82 /* CustomEmojiPickerInputView.swift */; };
DB447681260B3ED600B66B82 /* CustomEmojiPickerSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB447680260B3ED600B66B82 /* CustomEmojiPickerSection.swift */; };
@@ -391,6 +400,8 @@
DB789A1225F9F2CC0071ACA0 /* ComposeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB789A1125F9F2CC0071ACA0 /* ComposeViewModel.swift */; };
DB7F48452620241000796008 /* ProfileHeaderViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7F48442620241000796008 /* ProfileHeaderViewModel.swift */; };
DB8190C62601FF0400020C08 /* AttachmentContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB8190C52601FF0400020C08 /* AttachmentContainerView.swift */; };
+ DB8481152788121200BBEABA /* MastodonRegisterTextFieldTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB8481142788121200BBEABA /* MastodonRegisterTextFieldTableViewCell.swift */; };
+ DB84811727883C2600BBEABA /* MastodonRegisterPasswordHintTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB84811627883C2600BBEABA /* MastodonRegisterPasswordHintTableViewCell.swift */; };
DB852D1926FAEB6B00FC9D81 /* SidebarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB852D1826FAEB6B00FC9D81 /* SidebarViewController.swift */; };
DB852D1C26FB021500FC9D81 /* RootSplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB852D1B26FB021500FC9D81 /* RootSplitViewController.swift */; };
DB852D1F26FB037800FC9D81 /* SidebarViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB852D1E26FB037800FC9D81 /* SidebarViewModel.swift */; };
@@ -482,12 +493,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 +574,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 */; };
@@ -794,7 +791,7 @@
0FAA101B25E10E760017CCDE /* UIFont.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIFont.swift; sourceTree = ""; };
0FAA102625E1126A0017CCDE /* MastodonPickServerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonPickServerViewController.swift; sourceTree = ""; };
0FB3D2F625E4C24D00AAD544 /* MastodonPickServerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonPickServerViewModel.swift; sourceTree = ""; };
- 0FB3D2FD25E4CB6400AAD544 /* PickServerTitleCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerTitleCell.swift; sourceTree = ""; };
+ 0FB3D2FD25E4CB6400AAD544 /* OnboardingHeadlineTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingHeadlineTableViewCell.swift; sourceTree = ""; };
0FB3D30725E524C600AAD544 /* PickServerCategoriesCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerCategoriesCell.swift; sourceTree = ""; };
0FB3D30E25E525CD00AAD544 /* PickServerCategoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerCategoryView.swift; sourceTree = ""; };
0FB3D31D25E534C700AAD544 /* PickServerCategoryCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerCategoryCollectionViewCell.swift; sourceTree = ""; };
@@ -977,7 +974,6 @@
DB0009A826AEE5DC009B9D2D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; name = Base; path = Base.lproj/Intents.intentdefinition; sourceTree = ""; };
DB0009AD26AEE5E4009B9D2D /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/Intents.strings; sourceTree = ""; };
DB0140CE25C42AEE00F9F3CF /* OSLog.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OSLog.swift; sourceTree = ""; };
- DB023294267F0AB800031745 /* ASMetaEditableTextNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ASMetaEditableTextNode.swift; sourceTree = ""; };
DB029E94266A20430062874E /* MastodonAuthenticationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonAuthenticationController.swift; sourceTree = ""; };
DB02CDAA26256A9500D0A2AF /* ThreadReplyLoaderTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadReplyLoaderTableViewCell.swift; sourceTree = ""; };
DB02CDBE2625AE5000D0A2AF /* AdaptiveUserInterfaceStyleBarButtonItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdaptiveUserInterfaceStyleBarButtonItem.swift; sourceTree = ""; };
@@ -986,6 +982,18 @@
DB03F7F22689AEA3007B274C /* ComposeRepliedToStatusContentTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeRepliedToStatusContentTableViewCell.swift; sourceTree = ""; };
DB03F7F42689B782007B274C /* ComposeTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeTableView.swift; sourceTree = ""; };
DB040ED026538E3C00BEE9D8 /* Trie.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Trie.swift; sourceTree = ""; };
+ DB0617EA277EF3820030EE79 /* GradientBorderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradientBorderView.swift; sourceTree = ""; };
+ DB0617EC277F02C50030EE79 /* OnboardingNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingNavigationController.swift; sourceTree = ""; };
+ DB0617EE277F12720030EE79 /* NavigationActionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationActionView.swift; sourceTree = ""; };
+ DB0617F0278413D00030EE79 /* PickServerServerSectionTableHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerServerSectionTableHeaderView.swift; sourceTree = ""; };
+ DB0617F427855AB90030EE79 /* ServerRuleSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerRuleSection.swift; sourceTree = ""; };
+ DB0617FC27855BFE0030EE79 /* ServerRuleItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerRuleItem.swift; sourceTree = ""; };
+ DB0617FE27855D6C0030EE79 /* MastodonServerRulesViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonServerRulesViewModel+Diffable.swift"; sourceTree = ""; };
+ DB0618002785732C0030EE79 /* ServerRulesTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerRulesTableViewCell.swift; sourceTree = ""; };
+ DB0618022785A7100030EE79 /* RegisterSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegisterSection.swift; sourceTree = ""; };
+ DB0618042785A73D0030EE79 /* RegisterItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegisterItem.swift; sourceTree = ""; };
+ DB0618062785A8880030EE79 /* MastodonRegisterViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonRegisterViewModel+Diffable.swift"; sourceTree = ""; };
+ DB0618092785B2AB0030EE79 /* MastodonRegisterAvatarTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonRegisterAvatarTableViewCell.swift; sourceTree = ""; };
DB084B5625CBC56C00F898ED /* Status.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Status.swift; sourceTree = ""; };
DB0AC6FB25CD02E600D75117 /* APIService+Instance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Instance.swift"; sourceTree = ""; };
DB0C946A26A700AB0088FB11 /* MastodonUser+Property.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonUser+Property.swift"; sourceTree = ""; };
@@ -1009,7 +1017,6 @@
DB1D84372657B275000346B3 /* SegmentedControlNavigateable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SegmentedControlNavigateable.swift; sourceTree = ""; };
DB1E346725F518E20079D7DF /* CategoryPickerSection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CategoryPickerSection.swift; sourceTree = ""; };
DB1E347725F519300079D7DF /* PickServerItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PickServerItem.swift; sourceTree = ""; };
- DB1EE7B1267F9525000CC337 /* StatusProvider+StatusNodeDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StatusProvider+StatusNodeDelegate.swift"; sourceTree = ""; };
DB1FD43525F26899004CFCFC /* MastodonPickServerViewModel+LoadIndexedServerState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonPickServerViewModel+LoadIndexedServerState.swift"; sourceTree = ""; };
DB1FD44325F26CCC004CFCFC /* PickServerSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerSection.swift; sourceTree = ""; };
DB1FD44F25F26FA1004CFCFC /* MastodonPickServerViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonPickServerViewModel+Diffable.swift"; sourceTree = ""; };
@@ -1043,7 +1050,6 @@
DB427DF325BAA00100D1B89D /* MastodonUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MastodonUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
DB427DF725BAA00100D1B89D /* MastodonUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonUITests.swift; sourceTree = ""; };
DB427DF925BAA00100D1B89D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
- DB44384E25E8C1FA008912A2 /* CALayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CALayer.swift; sourceTree = ""; };
DB443CD32694627B00159B29 /* AppearanceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppearanceView.swift; sourceTree = ""; };
DB44767A260B3B8C00B66B82 /* CustomEmojiPickerInputView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomEmojiPickerInputView.swift; sourceTree = ""; };
DB447680260B3ED600B66B82 /* CustomEmojiPickerSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomEmojiPickerSection.swift; sourceTree = ""; };
@@ -1210,6 +1216,8 @@
DB789A1125F9F2CC0071ACA0 /* ComposeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeViewModel.swift; sourceTree = ""; };
DB7F48442620241000796008 /* ProfileHeaderViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileHeaderViewModel.swift; sourceTree = ""; };
DB8190C52601FF0400020C08 /* AttachmentContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentContainerView.swift; sourceTree = ""; };
+ DB8481142788121200BBEABA /* MastodonRegisterTextFieldTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonRegisterTextFieldTableViewCell.swift; sourceTree = ""; };
+ DB84811627883C2600BBEABA /* MastodonRegisterPasswordHintTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonRegisterPasswordHintTableViewCell.swift; sourceTree = ""; };
DB852D1826FAEB6B00FC9D81 /* SidebarViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarViewController.swift; sourceTree = ""; };
DB852D1B26FB021500FC9D81 /* RootSplitViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootSplitViewController.swift; sourceTree = ""; };
DB852D1E26FB037800FC9D81 /* SidebarViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarViewModel.swift; sourceTree = ""; };
@@ -1329,12 +1337,6 @@
DBA9443D265CFA6400C537E1 /* ProfileFieldCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileFieldCollectionViewCell.swift; sourceTree = ""; };
DBA9443F265D137600C537E1 /* Mastodon+Entity+Field.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Mastodon+Entity+Field.swift"; sourceTree = ""; };
DBABE3EB25ECAC4B00879EE5 /* WelcomeIllustrationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeIllustrationView.swift; sourceTree = ""; };
- DBAC6484267D0F9E007FE9FD /* StatusNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusNode.swift; sourceTree = ""; };
- DBAC6487267D388B007FE9FD /* ASTableNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ASTableNode.swift; sourceTree = ""; };
- DBAC648E267DC84D007FE9FD /* TableNodeDiffableDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableNodeDiffableDataSource.swift; sourceTree = ""; };
- DBAC6496267DECCB007FE9FD /* TimelineMiddleLoaderNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineMiddleLoaderNode.swift; sourceTree = ""; };
- DBAC6498267DF2C4007FE9FD /* TimelineBottomLoaderNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineBottomLoaderNode.swift; sourceTree = ""; };
- DBAC649A267DF8C8007FE9FD /* ActivityIndicatorNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityIndicatorNode.swift; sourceTree = ""; };
DBAE3F672615DD60004B8251 /* UserProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProvider.swift; sourceTree = ""; };
DBAE3F812615DDA3004B8251 /* ProfileViewController+UserProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileViewController+UserProvider.swift"; sourceTree = ""; };
DBAE3F872615DDF4004B8251 /* UserProviderFacade.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProviderFacade.swift; sourceTree = ""; };
@@ -1385,14 +1387,6 @@
DBC7A671260C897100E57475 /* StatusContentWarningEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusContentWarningEditorView.swift; sourceTree = ""; };
DBC7A67B260DFADE00E57475 /* StatusPublishService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusPublishService.swift; sourceTree = ""; };
DBCBCBF3267CB070000F5B51 /* Decode85.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Decode85.swift; sourceTree = ""; };
- DBCBCBFB2680ADB7000F5B51 /* AsyncHomeTimelineViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncHomeTimelineViewController.swift; sourceTree = ""; };
- DBCBCBFE2680AE98000F5B51 /* AsyncHomeTimelineViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncHomeTimelineViewModel.swift; sourceTree = ""; };
- DBCBCC002680AF2A000F5B51 /* AsyncHomeTimelineViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AsyncHomeTimelineViewModel+Diffable.swift"; sourceTree = ""; };
- DBCBCC022680AF6E000F5B51 /* AsyncHomeTimelineViewController+DebugAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AsyncHomeTimelineViewController+DebugAction.swift"; sourceTree = ""; };
- DBCBCC042680AFB9000F5B51 /* AsyncHomeTimelineViewController+Provider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AsyncHomeTimelineViewController+Provider.swift"; sourceTree = ""; };
- DBCBCC062680AFEC000F5B51 /* AsyncHomeTimelineViewModel+LoadLatestState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AsyncHomeTimelineViewModel+LoadLatestState.swift"; sourceTree = ""; };
- DBCBCC082680B01B000F5B51 /* AsyncHomeTimelineViewModel+LoadMiddleState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AsyncHomeTimelineViewModel+LoadMiddleState.swift"; sourceTree = ""; };
- DBCBCC0A2680B03F000F5B51 /* AsyncHomeTimelineViewModel+LoadOldestState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AsyncHomeTimelineViewModel+LoadOldestState.swift"; sourceTree = ""; };
DBCBCC0C2680B908000F5B51 /* HomeTimelinePreference.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeTimelinePreference.swift; sourceTree = ""; };
DBCBED1626132DB500B49291 /* UserTimelineViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserTimelineViewModel+Diffable.swift"; sourceTree = ""; };
DBCBED1C26132E1A00B49291 /* StatusFetchedResultsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusFetchedResultsController.swift; sourceTree = ""; };
@@ -1622,9 +1616,6 @@
0FB3D2FC25E4CB4B00AAD544 /* TableViewCell */ = {
isa = PBXGroup;
children = (
- 0FB3D2FD25E4CB6400AAD544 /* PickServerTitleCell.swift */,
- 0FB3D30725E524C600AAD544 /* PickServerCategoriesCell.swift */,
- 0FB3D33125E5F50E00AAD544 /* PickServerSearchCell.swift */,
0FB3D33725E6401400AAD544 /* PickServerCell.swift */,
DB0F814F264D1E2500F2A12B /* PickServerLoaderTableViewCell.swift */,
);
@@ -1636,6 +1627,7 @@
children = (
0FB3D30E25E525CD00AAD544 /* PickServerCategoryView.swift */,
DB9282B125F3222800823B15 /* PickServerEmptyStateView.swift */,
+ DB0617F0278413D00030EE79 /* PickServerServerSectionTableHeaderView.swift */,
);
path = View;
sourceTree = "";
@@ -1737,7 +1729,6 @@
isa = PBXGroup;
children = (
DB1F239626117C360057430E /* View */,
- DBCBCBFD2680ADBA000F5B51 /* AsyncHomeTimeline */,
2D38F1D425CD465300561493 /* HomeTimelineViewController.swift */,
2D38F1DE25CD46A400561493 /* HomeTimelineViewController+Provider.swift */,
2D5A3D6125CFD9CB002347D6 /* HomeTimelineViewController+DebugAction.swift */,
@@ -1756,7 +1747,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 +1801,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 */,
@@ -1890,29 +1879,21 @@
2D76319C25C151DE00929FB9 /* Diffiable */ = {
isa = PBXGroup;
children = (
- 2D76319D25C151F600929FB9 /* Section */,
- 2D7631B125C159E700929FB9 /* Item */,
+ DB4F097826A039B400D62E92 /* Onboarding */,
+ DB0617FB27855B740030EE79 /* Account */,
+ DB0617F827855B170030EE79 /* User */,
+ DB0617F927855B460030EE79 /* Profile */,
+ DB4F097926A039C400D62E92 /* Status */,
+ DB0617F627855AF30030EE79 /* Poll */,
+ DB4F097626A0398000D62E92 /* Compose */,
+ DB0617F727855B010030EE79 /* Notification */,
+ DB4F097726A039A200D62E92 /* Search */,
+ DB0617FA27855B660030EE79 /* Settings */,
DBCBED2226132E1D00B49291 /* FetchedResultsController */,
- DBAC6490267DC84F007FE9FD /* DataSource */,
);
path = Diffiable;
sourceTree = "";
};
- 2D76319D25C151F600929FB9 /* Section */ = {
- isa = PBXGroup;
- children = (
- DB4F097926A039C400D62E92 /* Status */,
- DB4F097826A039B400D62E92 /* Onboarding */,
- DB4F097726A039A200D62E92 /* Search */,
- DB4F097626A0398000D62E92 /* Compose */,
- 2D4AD8A126316CD200613EFC /* SelectedAccountSection.swift */,
- DB6D9F7C26358ED4008423CD /* SettingsSection.swift */,
- DBA94433265CBB5300C537E1 /* ProfileFieldSection.swift */,
- DB6B74FB272FF55800C70B6E /* UserSection.swift */,
- );
- path = Section;
- sourceTree = "";
- };
2D7631A425C1532200929FB9 /* Share */ = {
isa = PBXGroup;
children = (
@@ -1938,7 +1919,6 @@
DB87D45C2609DE6600D12C0D /* TextField */,
DB1D187125EF5BBD003F1F23 /* TableView */,
2D7631A625C1533800929FB9 /* TableviewCell */,
- DBAC6486267D0FAC007FE9FD /* Node */,
);
path = View;
sourceTree = "";
@@ -1960,29 +1940,6 @@
path = TableviewCell;
sourceTree = "";
};
- 2D7631B125C159E700929FB9 /* Item */ = {
- isa = PBXGroup;
- children = (
- 2D7631B225C159F700929FB9 /* Item.swift */,
- DB6B74FD272FF59000C70B6E /* UserItem.swift */,
- 2D198642261BF09500F0B013 /* SearchResultItem.swift */,
- DB4F097C26A03A5B00D62E92 /* SearchHistoryItem.swift */,
- 2D4AD8A726316D3500613EFC /* SelectedAccountItem.swift */,
- 2D7867182625B77500211898 /* NotificationItem.swift */,
- DB4481CB25EE2AFE00BEFB67 /* PollItem.swift */,
- DB1E347725F519300079D7DF /* PickServerItem.swift */,
- DB1FD45925F27898004CFCFC /* CategoryPickerItem.swift */,
- DB66729B25F9F91F00D60309 /* ComposeStatusItem.swift */,
- DB3667A0268ABB2E0027D07F /* ComposeStatusAttachmentItem.swift */,
- DB3667A7268AE2900027D07F /* ComposeStatusPollItem.swift */,
- DB44768A260B3F2100B66B82 /* CustomEmojiPickerItem.swift */,
- DB6D9F8326358EEC008423CD /* SettingsItem.swift */,
- DBBF1DCA2652539E00E5B703 /* AutoCompleteItem.swift */,
- DBA94435265CBB7400C537E1 /* ProfileFieldItem.swift */,
- );
- path = Item;
- sourceTree = "";
- };
2DA504672601ADBA008F4E6C /* Decoration */ = {
isa = PBXGroup;
children = (
@@ -2112,24 +2069,14 @@
DB68A03825E900CC00CFDF14 /* Share */,
0FAA0FDD25E0B5700017CCDE /* Welcome */,
0FAA102525E1125D0017CCDE /* PickServer */,
- DBE0821A25CD382900FD6BBD /* Register */,
DB72602125E36A2500235243 /* ServerRules */,
+ DBE0821A25CD382900FD6BBD /* Register */,
2D364F7025E66D5B00204FDC /* ResendEmail */,
2D59819925E4A55C000FB903 /* ConfirmEmail */,
);
path = Onboarding;
sourceTree = "";
};
- DB023296267F0ABE00031745 /* Status */ = {
- isa = PBXGroup;
- children = (
- DBAC6484267D0F9E007FE9FD /* StatusNode.swift */,
- DBAC6496267DECCB007FE9FD /* TimelineMiddleLoaderNode.swift */,
- DBAC6498267DF2C4007FE9FD /* TimelineBottomLoaderNode.swift */,
- );
- path = Status;
- sourceTree = "";
- };
DB03F7F1268990A2007B274C /* TableViewCell */ = {
isa = PBXGroup;
children = (
@@ -2140,6 +2087,87 @@
path = TableViewCell;
sourceTree = "";
};
+ DB0617F3278436360030EE79 /* Deprecated */ = {
+ isa = PBXGroup;
+ children = (
+ 0FB3D33125E5F50E00AAD544 /* PickServerSearchCell.swift */,
+ 0FB3D30725E524C600AAD544 /* PickServerCategoriesCell.swift */,
+ );
+ path = Deprecated;
+ sourceTree = "";
+ };
+ DB0617F627855AF30030EE79 /* Poll */ = {
+ isa = PBXGroup;
+ children = (
+ DB4481C525EE2ADA00BEFB67 /* PollSection.swift */,
+ DB4481CB25EE2AFE00BEFB67 /* PollItem.swift */,
+ );
+ path = Poll;
+ sourceTree = "";
+ };
+ DB0617F727855B010030EE79 /* Notification */ = {
+ isa = PBXGroup;
+ children = (
+ 2D35237926256D920031AF25 /* NotificationSection.swift */,
+ 2D7867182625B77500211898 /* NotificationItem.swift */,
+ );
+ path = Notification;
+ sourceTree = "";
+ };
+ DB0617F827855B170030EE79 /* User */ = {
+ isa = PBXGroup;
+ children = (
+ DB6B74FB272FF55800C70B6E /* UserSection.swift */,
+ DB6B74FD272FF59000C70B6E /* UserItem.swift */,
+ );
+ path = User;
+ sourceTree = "";
+ };
+ DB0617F927855B460030EE79 /* Profile */ = {
+ isa = PBXGroup;
+ children = (
+ DBA94433265CBB5300C537E1 /* ProfileFieldSection.swift */,
+ DBA94435265CBB7400C537E1 /* ProfileFieldItem.swift */,
+ );
+ path = Profile;
+ sourceTree = "";
+ };
+ DB0617FA27855B660030EE79 /* Settings */ = {
+ isa = PBXGroup;
+ children = (
+ DB6D9F7C26358ED4008423CD /* SettingsSection.swift */,
+ DB6D9F8326358EEC008423CD /* SettingsItem.swift */,
+ );
+ path = Settings;
+ sourceTree = "";
+ };
+ DB0617FB27855B740030EE79 /* Account */ = {
+ isa = PBXGroup;
+ children = (
+ 2D4AD8A126316CD200613EFC /* SelectedAccountSection.swift */,
+ 2D4AD8A726316D3500613EFC /* SelectedAccountItem.swift */,
+ );
+ path = Account;
+ sourceTree = "";
+ };
+ DB0618082785B2790030EE79 /* Cell */ = {
+ isa = PBXGroup;
+ children = (
+ DB0618002785732C0030EE79 /* ServerRulesTableViewCell.swift */,
+ );
+ path = Cell;
+ sourceTree = "";
+ };
+ DB06180B2785B2AF0030EE79 /* Cell */ = {
+ isa = PBXGroup;
+ children = (
+ DB0618092785B2AB0030EE79 /* MastodonRegisterAvatarTableViewCell.swift */,
+ DB8481142788121200BBEABA /* MastodonRegisterTextFieldTableViewCell.swift */,
+ DB84811627883C2600BBEABA /* MastodonRegisterPasswordHintTableViewCell.swift */,
+ );
+ path = Cell;
+ sourceTree = "";
+ };
DB084B5125CBC56300F898ED /* CoreDataStack */ = {
isa = PBXGroup;
children = (
@@ -2271,6 +2299,7 @@
children = (
DB427DE325BAA00100D1B89D /* Info.plist */,
DB89BA1025C10FF5008580ED /* Mastodon.entitlements */,
+ DB0617F3278436360030EE79 /* Deprecated */,
2D76319C25C151DE00929FB9 /* Diffiable */,
DB8AF52A25C13561002E6C99 /* State */,
2D61335525C1886800CAE157 /* Service */,
@@ -2390,10 +2419,15 @@
isa = PBXGroup;
children = (
DB66729525F9F91600D60309 /* ComposeStatusSection.swift */,
+ DB66729B25F9F91F00D60309 /* ComposeStatusItem.swift */,
DB36679E268ABAF20027D07F /* ComposeStatusAttachmentSection.swift */,
+ DB3667A0268ABB2E0027D07F /* ComposeStatusAttachmentItem.swift */,
DB3667A5268AE2620027D07F /* ComposeStatusPollSection.swift */,
+ DB3667A7268AE2900027D07F /* ComposeStatusPollItem.swift */,
DB447680260B3ED600B66B82 /* CustomEmojiPickerSection.swift */,
+ DB44768A260B3F2100B66B82 /* CustomEmojiPickerItem.swift */,
DBBF1DC82652538500E5B703 /* AutoCompleteSection.swift */,
+ DBBF1DCA2652539E00E5B703 /* AutoCompleteItem.swift */,
);
path = Compose;
sourceTree = "";
@@ -2404,7 +2438,9 @@
2DE0FAC02615F04D00CDF649 /* RecommendHashTagSection.swift */,
2DE0FACD2615F7AD00CDF649 /* RecommendAccountSection.swift */,
2D198648261C0B8500F0B013 /* SearchResultSection.swift */,
+ 2D198642261BF09500F0B013 /* SearchResultItem.swift */,
DB4F097A26A039FF00D62E92 /* SearchHistorySection.swift */,
+ DB4F097C26A03A5B00D62E92 /* SearchHistoryItem.swift */,
);
path = Search;
sourceTree = "";
@@ -2413,7 +2449,13 @@
isa = PBXGroup;
children = (
DB1FD44325F26CCC004CFCFC /* PickServerSection.swift */,
+ DB1E347725F519300079D7DF /* PickServerItem.swift */,
DB1E346725F518E20079D7DF /* CategoryPickerSection.swift */,
+ DB1FD45925F27898004CFCFC /* CategoryPickerItem.swift */,
+ DB0617F427855AB90030EE79 /* ServerRuleSection.swift */,
+ DB0617FC27855BFE0030EE79 /* ServerRuleItem.swift */,
+ DB0618022785A7100030EE79 /* RegisterSection.swift */,
+ DB0618042785A73D0030EE79 /* RegisterItem.swift */,
);
path = Onboarding;
sourceTree = "";
@@ -2422,8 +2464,7 @@
isa = PBXGroup;
children = (
2D76319E25C1521200929FB9 /* StatusSection.swift */,
- DB4481C525EE2ADA00BEFB67 /* PollSection.swift */,
- 2D35237926256D920031AF25 /* NotificationSection.swift */,
+ 2D7631B225C159F700929FB9 /* Item.swift */,
5BB04FF4262F0E6D0043BFF6 /* ReportSection.swift */,
);
path = Status;
@@ -2563,9 +2604,12 @@
DB68A03825E900CC00CFDF14 /* Share */ = {
isa = PBXGroup;
children = (
- 2D82B9FE25E7863200E36F0F /* OnboardingViewControllerAppearance.swift */,
DB2F073325E8ECF000957B2D /* AuthenticationViewModel.swift */,
DB029E94266A20430062874E /* MastodonAuthenticationController.swift */,
+ 2D82B9FE25E7863200E36F0F /* OnboardingViewControllerAppearance.swift */,
+ DB0617EC277F02C50030EE79 /* OnboardingNavigationController.swift */,
+ 0FB3D2FD25E4CB6400AAD544 /* OnboardingHeadlineTableViewCell.swift */,
+ DB0617EE277F12720030EE79 /* NavigationActionView.swift */,
);
path = Share;
sourceTree = "";
@@ -2621,8 +2665,10 @@
DB72602125E36A2500235243 /* ServerRules */ = {
isa = PBXGroup;
children = (
+ DB0618082785B2790030EE79 /* Cell */,
DB72601B25E36A2100235243 /* MastodonServerRulesViewController.swift */,
DB72602625E36A6F00235243 /* MastodonServerRulesViewModel.swift */,
+ DB0617FE27855D6C0030EE79 /* MastodonServerRulesViewModel+Diffable.swift */,
);
path = ServerRules;
sourceTree = "";
@@ -2825,7 +2871,6 @@
2DF123A625C3B0210020F248 /* ActiveLabel.swift */,
5DF1056325F887CB00D6C0D4 /* AVPlayer.swift */,
0F20223826146553000C64BF /* Array.swift */,
- DB44384E25E8C1FA008912A2 /* CALayer.swift */,
2D206B8525F5FB0900143C56 /* Double.swift */,
DB97131E2666078B00BD1E90 /* Date.swift */,
DBB3BA2926A81C020004F2D4 /* FLAnimatedImageView.swift */,
@@ -3040,28 +3085,11 @@
children = (
DBABE3EB25ECAC4B00879EE5 /* WelcomeIllustrationView.swift */,
DB4932B026F1FB5300EF46D4 /* WizardCardView.swift */,
+ DB0617EA277EF3820030EE79 /* GradientBorderView.swift */,
);
path = View;
sourceTree = "";
};
- DBAC6486267D0FAC007FE9FD /* Node */ = {
- isa = PBXGroup;
- children = (
- DB023296267F0ABE00031745 /* Status */,
- DB023294267F0AB800031745 /* ASMetaEditableTextNode.swift */,
- );
- path = Node;
- sourceTree = "";
- };
- DBAC6490267DC84F007FE9FD /* DataSource */ = {
- isa = PBXGroup;
- children = (
- DBAC6487267D388B007FE9FD /* ASTableNode.swift */,
- DBAC648E267DC84D007FE9FD /* TableNodeDiffableDataSource.swift */,
- );
- path = DataSource;
- sourceTree = "";
- };
DBAE3F742615DD63004B8251 /* UserProvider */ = {
isa = PBXGroup;
children = (
@@ -3187,21 +3215,6 @@
path = ShareActionExtension;
sourceTree = "";
};
- 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 = "";
- };
DBCBED2226132E1D00B49291 /* FetchedResultsController */ = {
isa = PBXGroup;
children = (
@@ -3216,9 +3229,11 @@
DBE0821A25CD382900FD6BBD /* Register */ = {
isa = PBXGroup;
children = (
+ DB06180B2785B2AF0030EE79 /* Cell */,
DBE0821425CD382600FD6BBD /* MastodonRegisterViewController.swift */,
2D939AE725EE1CF80076FA61 /* MastodonRegisterViewController+Avatar.swift */,
DBE0822325CD3F1E00FD6BBD /* MastodonRegisterViewModel.swift */,
+ DB0618062785A8880030EE79 /* MastodonRegisterViewModel+Diffable.swift */,
);
path = Register;
sourceTree = "";
@@ -3969,7 +3984,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 */,
@@ -3994,6 +4008,7 @@
DBB525502611ED6D002F1F29 /* ProfileHeaderView.swift in Sources */,
0FB3D33225E5F50E00AAD544 /* PickServerSearchCell.swift in Sources */,
DB71FD5225F8CCAA00512AE1 /* APIService+Status.swift in Sources */,
+ DB8481152788121200BBEABA /* MastodonRegisterTextFieldTableViewCell.swift in Sources */,
5D0393962612D266007FE196 /* WebViewModel.swift in Sources */,
5B24BBDA262DB14800A9381B /* ReportViewModel.swift in Sources */,
2D5A3D3825CF8D9F002347D6 /* ScrollViewContainer.swift in Sources */,
@@ -4015,13 +4030,12 @@
DB68A06325E905E000CFDF14 /* UIApplication.swift in Sources */,
DB02CDAB26256A9500D0A2AF /* ThreadReplyLoaderTableViewCell.swift in Sources */,
DB63BE7F268DD1070011D3F9 /* NotificationViewController+StatusProvider.swift in Sources */,
+ DB0617FF27855D6C0030EE79 /* MastodonServerRulesViewModel+Diffable.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 */,
@@ -4041,7 +4055,9 @@
DB03A795272A981400EE37C5 /* ContentSplitViewController.swift in Sources */,
2D152A8C25C295CC009AA50C /* StatusView.swift in Sources */,
DBBC24DE26A54BCB00398BB9 /* MastodonMetricFormatter.swift in Sources */,
+ DB06180A2785B2AB0030EE79 /* MastodonRegisterAvatarTableViewCell.swift in Sources */,
DBB3BA2A26A81C020004F2D4 /* FLAnimatedImageView.swift in Sources */,
+ DB0618032785A7100030EE79 /* RegisterSection.swift in Sources */,
2DE0FAC82615F5F000CDF649 /* SearchRecommendAccountsCollectionViewCell.swift in Sources */,
DBF1D251269DB01200C1C08A /* SearchHistoryViewController.swift in Sources */,
2D42FF8525C8224F004A627A /* HitTestExpandedButton.swift in Sources */,
@@ -4049,8 +4065,11 @@
DBB5250E2611EBAF002F1F29 /* ProfileSegmentedViewController.swift in Sources */,
2D42FF8F25C8228A004A627A /* UIButton.swift in Sources */,
DBF156DF2701B17600EC00B7 /* SidebarAddAccountCollectionViewCell.swift in Sources */,
+ DB0617F1278413D00030EE79 /* PickServerServerSectionTableHeaderView.swift in Sources */,
DB789A0B25F9F2950071ACA0 /* ComposeViewController.swift in Sources */,
DB938F0926240F3C00E5B6C1 /* RemoteThreadViewModel.swift in Sources */,
+ DB0617ED277F02C50030EE79 /* OnboardingNavigationController.swift in Sources */,
+ DB0617F527855AB90030EE79 /* ServerRuleSection.swift in Sources */,
DBBC24AE26A53DC100398BB9 /* ReplicaStatusView.swift in Sources */,
DB75BF1E263C1C1B00EDBF1F /* CustomScheduler.swift in Sources */,
0FAA102725E1126A0017CCDE /* MastodonPickServerViewController.swift in Sources */,
@@ -4091,12 +4110,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 +4172,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 */,
@@ -4166,7 +4181,7 @@
DB71FD3C25F8A1C500512AE1 /* APIService+Persist+PersistCache.swift in Sources */,
2DA6055125F74407006356F9 /* AudioContainerViewModel.swift in Sources */,
DB4F0968269ED8AD00D62E92 /* SearchHistoryTableHeaderView.swift in Sources */,
- 0FB3D2FE25E4CB6400AAD544 /* PickServerTitleCell.swift in Sources */,
+ 0FB3D2FE25E4CB6400AAD544 /* OnboardingHeadlineTableViewCell.swift in Sources */,
5DA732CC2629CEF500A92342 /* UIView+Remove.swift in Sources */,
DBAEDE5C267A058D00D25FF5 /* BlurhashImageCacheService.swift in Sources */,
2D38F1DF25CD46A400561493 /* HomeTimelineViewController+Provider.swift in Sources */,
@@ -4185,7 +4200,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 */,
@@ -4201,6 +4215,7 @@
2DF75BA125D0E29D00694EC8 /* StatusProvider+StatusTableViewCellDelegate.swift in Sources */,
5DF1056425F887CB00D6C0D4 /* AVPlayer.swift in Sources */,
DBBF1DCB2652539E00E5B703 /* AutoCompleteItem.swift in Sources */,
+ DB84811727883C2600BBEABA /* MastodonRegisterPasswordHintTableViewCell.swift in Sources */,
2DA6054725F716A2006356F9 /* PlaybackState.swift in Sources */,
DB35FC1F2612F1D9006193C9 /* ProfileRelationshipActionButton.swift in Sources */,
DBC7A672260C897100E57475 /* StatusContentWarningEditorView.swift in Sources */,
@@ -4212,7 +4227,9 @@
2D34D9D126148D9E0081BFC0 /* APIService+Recommend.swift in Sources */,
DBB525562611EDCA002F1F29 /* UserTimelineViewModel.swift in Sources */,
2D42FF7E25C82218004A627A /* ActionToolBarContainer.swift in Sources */,
+ DB0618012785732C0030EE79 /* ServerRulesTableViewCell.swift in Sources */,
DB221B16260C395900AEFE46 /* CustomEmojiPickerInputViewModel.swift in Sources */,
+ DB0617EF277F12720030EE79 /* NavigationActionView.swift in Sources */,
DB1FD43625F26899004CFCFC /* MastodonPickServerViewModel+LoadIndexedServerState.swift in Sources */,
2D939AE825EE1CF80076FA61 /* MastodonRegisterViewController+Avatar.swift in Sources */,
2D084B8D26258EA3003AA3AF /* NotificationViewModel+Diffable.swift in Sources */,
@@ -4220,7 +4237,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 */,
@@ -4237,6 +4253,7 @@
DB6C8C0F25F0A6AE00AAA452 /* Mastodon+Entity+Error.swift in Sources */,
DB1E346825F518E20079D7DF /* CategoryPickerSection.swift in Sources */,
DB7274F4273BB9B200577D95 /* ListBatchFetchViewModel.swift in Sources */,
+ DB0618052785A73D0030EE79 /* RegisterItem.swift in Sources */,
2D61254D262547C200299647 /* APIService+Notification.swift in Sources */,
DB040ED126538E3D00BEE9D8 /* Trie.swift in Sources */,
DB73BF4B27140C0800781945 /* UITableViewDiffableDataSource.swift in Sources */,
@@ -4248,7 +4265,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 +4280,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 */,
@@ -4284,7 +4299,6 @@
DB6180F826391D660018D199 /* MediaPreviewingViewController.swift in Sources */,
DB0140CF25C42AEE00F9F3CF /* OSLog.swift in Sources */,
DB6B75022730060700C70B6E /* UserProviderFacade+UITableViewDelegate.swift in Sources */,
- DB44384F25E8C1FA008912A2 /* CALayer.swift in Sources */,
2D34D9CB261489930081BFC0 /* SearchViewController+Recommend.swift in Sources */,
DB71C7CB271D5A0300BE3819 /* LineChartView.swift in Sources */,
DB938F1526241FDF00E5B6C1 /* APIService+Thread.swift in Sources */,
@@ -4301,7 +4315,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 +4339,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 */,
@@ -4349,6 +4361,7 @@
DBE54AC62636C89F004E7C0B /* NotificationPreference.swift in Sources */,
2D5A3D2825CF8BC9002347D6 /* HomeTimelineViewModel+Diffable.swift in Sources */,
DB98339C25C96DE600AD9700 /* APIService+Account.swift in Sources */,
+ DB0618072785A8880030EE79 /* MastodonRegisterViewModel+Diffable.swift in Sources */,
DB6B74FC272FF55800C70B6E /* UserSection.swift in Sources */,
2DF75BA725D10E1000694EC8 /* APIService+Favorite.swift in Sources */,
DB9D6C3825E508BE0051B173 /* Attachment.swift in Sources */,
@@ -4359,7 +4372,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 +4402,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 */,
@@ -4402,6 +4413,7 @@
DB4932B326F2054200EF46D4 /* CircleAvatarButton.swift in Sources */,
0FB3D30825E524C600AAD544 /* PickServerCategoriesCell.swift in Sources */,
2D4AD8A226316CD200613EFC /* SelectedAccountSection.swift in Sources */,
+ DB0617EB277EF3820030EE79 /* GradientBorderView.swift in Sources */,
DB789A1225F9F2CC0071ACA0 /* ComposeViewModel.swift in Sources */,
DBB525362611ECEB002F1F29 /* UserTimelineViewController.swift in Sources */,
DB6D1B3D2636857500ACB481 /* AppearancePreference.swift in Sources */,
@@ -4409,7 +4421,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 */,
@@ -4430,6 +4441,7 @@
DBAFB7352645463500371D5F /* Emojis.swift in Sources */,
DBCCC71E25F73297007E1AB6 /* APIService+Reblog.swift in Sources */,
DBE3CE13261D7D4200430CC6 /* StatusTableViewControllerAspect.swift in Sources */,
+ DB0617FD27855BFE0030EE79 /* ServerRuleItem.swift in Sources */,
5BB04FD5262E7AFF0043BFF6 /* ReportViewController.swift in Sources */,
DBAE3F942616E28B004B8251 /* APIService+Follow.swift in Sources */,
);
@@ -4919,7 +4931,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements;
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 88;
+ CURRENT_PROJECT_VERSION = 90;
DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets";
DEVELOPMENT_TEAM = 5Z4GVSS33P;
INFOPLIST_FILE = Mastodon/Info.plist;
@@ -4934,7 +4946,7 @@
SWIFT_OBJC_BRIDGING_HEADER = "Mastodon/Vender/Mastodon-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
- TARGETED_DEVICE_FAMILY = 1;
+ TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
@@ -4948,7 +4960,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements;
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 88;
+ CURRENT_PROJECT_VERSION = 90;
DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets";
DEVELOPMENT_TEAM = 5Z4GVSS33P;
INFOPLIST_FILE = Mastodon/Info.plist;
@@ -4962,7 +4974,7 @@
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Mastodon/Vender/Mastodon-Bridging-Header.h";
SWIFT_VERSION = 5.0;
- TARGETED_DEVICE_FAMILY = 1;
+ TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
@@ -5056,11 +5068,11 @@
APPLICATION_EXTENSION_API_ONLY = YES;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 88;
+ CURRENT_PROJECT_VERSION = 90;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 5Z4GVSS33P;
DYLIB_COMPATIBILITY_VERSION = 1;
- DYLIB_CURRENT_VERSION = 88;
+ DYLIB_CURRENT_VERSION = 90;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = AppShared/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
@@ -5087,11 +5099,11 @@
APPLICATION_EXTENSION_API_ONLY = YES;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 88;
+ CURRENT_PROJECT_VERSION = 90;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 5Z4GVSS33P;
DYLIB_COMPATIBILITY_VERSION = 1;
- DYLIB_CURRENT_VERSION = 88;
+ DYLIB_CURRENT_VERSION = 90;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = AppShared/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
@@ -5116,11 +5128,11 @@
APPLICATION_EXTENSION_API_ONLY = YES;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 88;
+ CURRENT_PROJECT_VERSION = 90;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 5Z4GVSS33P;
DYLIB_COMPATIBILITY_VERSION = 1;
- DYLIB_CURRENT_VERSION = 88;
+ DYLIB_CURRENT_VERSION = 90;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = CoreDataStack/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
@@ -5146,11 +5158,11 @@
APPLICATION_EXTENSION_API_ONLY = YES;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 88;
+ CURRENT_PROJECT_VERSION = 90;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 5Z4GVSS33P;
DYLIB_COMPATIBILITY_VERSION = 1;
- DYLIB_CURRENT_VERSION = 88;
+ DYLIB_CURRENT_VERSION = 90;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = CoreDataStack/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
@@ -5213,7 +5225,7 @@
buildSettings = {
CODE_SIGN_ENTITLEMENTS = MastodonIntent/MastodonIntent.entitlements;
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 88;
+ CURRENT_PROJECT_VERSION = 90;
DEVELOPMENT_TEAM = 5Z4GVSS33P;
INFOPLIST_FILE = MastodonIntent/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
@@ -5227,68 +5239,18 @@
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "APP_EXTENSION $(inherited)";
SWIFT_VERSION = 5.0;
- TARGETED_DEVICE_FAMILY = 1;
+ TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
};
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 */;
buildSettings = {
CODE_SIGN_ENTITLEMENTS = MastodonIntent/MastodonIntent.entitlements;
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 88;
+ CURRENT_PROJECT_VERSION = 90;
DEVELOPMENT_TEAM = 5Z4GVSS33P;
INFOPLIST_FILE = MastodonIntent/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
@@ -5302,7 +5264,7 @@
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "APP_EXTENSION $(inherited)";
SWIFT_VERSION = 5.0;
- TARGETED_DEVICE_FAMILY = 1;
+ TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
@@ -5313,7 +5275,7 @@
buildSettings = {
CODE_SIGN_ENTITLEMENTS = ShareActionExtension/ShareActionExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 88;
+ CURRENT_PROJECT_VERSION = 90;
DEVELOPMENT_TEAM = 5Z4GVSS33P;
INFOPLIST_FILE = ShareActionExtension/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
@@ -5327,68 +5289,18 @@
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "APP_EXTENSION $(inherited)";
SWIFT_VERSION = 5.0;
- TARGETED_DEVICE_FAMILY = 1;
+ TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
};
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 */;
buildSettings = {
CODE_SIGN_ENTITLEMENTS = ShareActionExtension/ShareActionExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 88;
+ CURRENT_PROJECT_VERSION = 90;
DEVELOPMENT_TEAM = 5Z4GVSS33P;
INFOPLIST_FILE = ShareActionExtension/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
@@ -5402,492 +5314,18 @@
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "APP_EXTENSION $(inherited)";
SWIFT_VERSION = 5.0;
- TARGETED_DEVICE_FAMILY = 1;
+ TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
};
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 */;
buildSettings = {
CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements;
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 88;
+ CURRENT_PROJECT_VERSION = 90;
DEVELOPMENT_TEAM = 5Z4GVSS33P;
INFOPLIST_FILE = NotificationService/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
@@ -5900,7 +5338,7 @@
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_VERSION = 5.0;
- TARGETED_DEVICE_FAMILY = 1;
+ TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
@@ -5911,7 +5349,7 @@
buildSettings = {
CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements;
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 88;
+ CURRENT_PROJECT_VERSION = 90;
DEVELOPMENT_TEAM = 5Z4GVSS33P;
INFOPLIST_FILE = NotificationService/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
@@ -5924,7 +5362,7 @@
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_VERSION = 5.0;
- TARGETED_DEVICE_FAMILY = 1;
+ TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
@@ -5936,8 +5374,6 @@
isa = XCConfigurationList;
buildConfigurations = (
DB427DFA25BAA00100D1B89D /* Debug */,
- DBCBCC1E26818F6F000F5B51 /* ASDK - Debug */,
- DBCBCC0E2680BE3E000F5B51 /* ASDK - Release */,
DB427DFB25BAA00100D1B89D /* Release */,
);
defaultConfigurationIsVisible = 0;
@@ -5947,8 +5383,6 @@
isa = XCConfigurationList;
buildConfigurations = (
DB427DFD25BAA00100D1B89D /* Debug */,
- DBCBCC1F26818F6F000F5B51 /* ASDK - Debug */,
- DBCBCC0F2680BE3E000F5B51 /* ASDK - Release */,
DB427DFE25BAA00100D1B89D /* Release */,
);
defaultConfigurationIsVisible = 0;
@@ -5958,8 +5392,6 @@
isa = XCConfigurationList;
buildConfigurations = (
DB427E0025BAA00100D1B89D /* Debug */,
- DBCBCC2026818F6F000F5B51 /* ASDK - Debug */,
- DBCBCC102680BE3E000F5B51 /* ASDK - Release */,
DB427E0125BAA00100D1B89D /* Release */,
);
defaultConfigurationIsVisible = 0;
@@ -5969,8 +5401,6 @@
isa = XCConfigurationList;
buildConfigurations = (
DB427E0325BAA00100D1B89D /* Debug */,
- DBCBCC2126818F6F000F5B51 /* ASDK - Debug */,
- DBCBCC112680BE3E000F5B51 /* ASDK - Release */,
DB427E0425BAA00100D1B89D /* Release */,
);
defaultConfigurationIsVisible = 0;
@@ -5980,8 +5410,6 @@
isa = XCConfigurationList;
buildConfigurations = (
DB6804892637CD4C00430867 /* Debug */,
- DBCBCC2526818F6F000F5B51 /* ASDK - Debug */,
- DBCBCC152680BE3E000F5B51 /* ASDK - Release */,
DB68048A2637CD4C00430867 /* Release */,
);
defaultConfigurationIsVisible = 0;
@@ -5991,8 +5419,6 @@
isa = XCConfigurationList;
buildConfigurations = (
DB89BA0625C10FD0008580ED /* Debug */,
- DBCBCC2226818F6F000F5B51 /* ASDK - Debug */,
- DBCBCC122680BE3E000F5B51 /* ASDK - Release */,
DB89BA0725C10FD0008580ED /* Release */,
);
defaultConfigurationIsVisible = 0;
@@ -6002,8 +5428,6 @@
isa = XCConfigurationList;
buildConfigurations = (
DB89BA0A25C10FD0008580ED /* Debug */,
- DBCBCC2326818F6F000F5B51 /* ASDK - Debug */,
- DBCBCC132680BE3E000F5B51 /* ASDK - Release */,
DB89BA0B25C10FD0008580ED /* Release */,
);
defaultConfigurationIsVisible = 0;
@@ -6013,8 +5437,6 @@
isa = XCConfigurationList;
buildConfigurations = (
DB8FABD026AEC7B2008E5AF4 /* Debug */,
- DB8FABD126AEC7B2008E5AF4 /* ASDK - Debug */,
- DB8FABD226AEC7B2008E5AF4 /* ASDK - Release */,
DB8FABD326AEC7B2008E5AF4 /* Release */,
);
defaultConfigurationIsVisible = 0;
@@ -6024,8 +5446,6 @@
isa = XCConfigurationList;
buildConfigurations = (
DBC6461D26A170AB00B0E31B /* Debug */,
- DBC6461E26A170AB00B0E31B /* ASDK - Debug */,
- DBC6461F26A170AB00B0E31B /* ASDK - Release */,
DBC6462026A170AB00B0E31B /* Release */,
);
defaultConfigurationIsVisible = 0;
@@ -6035,8 +5455,6 @@
isa = XCConfigurationList;
buildConfigurations = (
DBF8AE1C263293E400C9C23C /* Debug */,
- DBCBCC2426818F6F000F5B51 /* ASDK - Debug */,
- DBCBCC142680BE3E000F5B51 /* ASDK - Release */,
DBF8AE1D263293E400C9C23C /* Release */,
);
defaultConfigurationIsVisible = 0;
diff --git a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist
index 5c99e944..56e26925 100644
--- a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist
+++ b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist
@@ -7,17 +7,17 @@
AppShared.xcscheme_^#shared#^_
orderHint
- 44
+ 26
CoreDataStack.xcscheme_^#shared#^_
orderHint
- 45
+ 27
Mastodon - ASDK.xcscheme_^#shared#^_
orderHint
- 4
+ 2
Mastodon - RTL.xcscheme_^#shared#^_
@@ -27,7 +27,7 @@
Mastodon - Release.xcscheme_^#shared#^_
orderHint
- 3
+ 1
Mastodon - ar.xcscheme_^#shared#^_
@@ -102,7 +102,7 @@
MastodonIntent.xcscheme_^#shared#^_
orderHint
- 43
+ 25
MastodonIntents.xcscheme_^#shared#^_
@@ -117,12 +117,12 @@
NotificationService.xcscheme_^#shared#^_
orderHint
- 7
+ 3
ShareActionExtension.xcscheme_^#shared#^_
orderHint
- 42
+ 24
SuppressBuildableAutocreation
diff --git a/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved
index 11dde726..e52bb1d9 100644
--- a/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved
+++ b/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -6,8 +6,8 @@
"repositoryURL": "https://github.com/Alamofire/Alamofire.git",
"state": {
"branch": null,
- "revision": "d120af1e8638c7da36c8481fd61a66c0c08dc4fc",
- "version": "5.4.4"
+ "revision": "f82c23a8a7ef8dc1a49a8bfc6a96883e79121864",
+ "version": "5.5.0"
}
},
{
@@ -141,8 +141,8 @@
"repositoryURL": "https://github.com/SDWebImage/SDWebImage.git",
"state": {
"branch": null,
- "revision": "a72df4849408da7e5d3c1b586797b7c601c41d1b",
- "version": "5.12.1"
+ "revision": "0fff0d7505b5306348263ea64fcc561253bbeb21",
+ "version": "5.12.2"
}
},
{
diff --git a/Mastodon/Coordinator/SceneCoordinator.swift b/Mastodon/Coordinator/SceneCoordinator.swift
index 9fbb2b77..49504fd1 100644
--- a/Mastodon/Coordinator/SceneCoordinator.swift
+++ b/Mastodon/Coordinator/SceneCoordinator.swift
@@ -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)
@@ -260,7 +255,7 @@ extension SceneCoordinator {
DispatchQueue.main.async {
self.present(
scene: .welcome,
- from: nil,
+ from: self.sceneDelegate.window?.rootViewController,
transition: .modal(animated: animated, completion: nil)
)
}
@@ -311,7 +306,7 @@ extension SceneCoordinator {
case .modal(let animated, let completion):
let modalNavigationController: UINavigationController = {
if scene.isOnboarding {
- return AdaptiveStatusBarStyleNavigationController(rootViewController: viewController)
+ return OnboardingNavigationController(rootViewController: viewController)
} else {
return UINavigationController(rootViewController: viewController)
}
@@ -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
diff --git a/Mastodon/Deprecated/PickServerCategoriesCell.swift b/Mastodon/Deprecated/PickServerCategoriesCell.swift
new file mode 100644
index 00000000..b2ca1cc7
--- /dev/null
+++ b/Mastodon/Deprecated/PickServerCategoriesCell.swift
@@ -0,0 +1,145 @@
+//
+// PickServerCategoriesCell.swift
+// Mastodon
+//
+// Created by BradGao on 2021/2/23.
+//
+
+//import os.log
+//import UIKit
+//import MastodonSDK
+//
+//protocol PickServerCategoriesCellDelegate: AnyObject {
+// func pickServerCategoriesCell(_ cell: PickServerCategoriesCell, collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
+//}
+//
+//final class PickServerCategoriesCell: UITableViewCell {
+//
+// weak var delegate: PickServerCategoriesCellDelegate?
+//
+// var diffableDataSource: UICollectionViewDiffableDataSource?
+//
+// let metricView = UIView()
+//
+// let collectionView: UICollectionView = {
+// let flowLayout = UICollectionViewFlowLayout()
+// flowLayout.scrollDirection = .horizontal
+// let view = ControlContainableCollectionView(frame: .zero, collectionViewLayout: flowLayout)
+// view.register(PickServerCategoryCollectionViewCell.self, forCellWithReuseIdentifier: String(describing: PickServerCategoryCollectionViewCell.self))
+// view.backgroundColor = .clear
+// view.showsHorizontalScrollIndicator = false
+// view.showsVerticalScrollIndicator = false
+// view.layer.masksToBounds = false
+// view.translatesAutoresizingMaskIntoConstraints = false
+// return view
+// }()
+//
+// override func prepareForReuse() {
+// super.prepareForReuse()
+//
+// delegate = nil
+// }
+//
+// override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
+// super.init(style: style, reuseIdentifier: reuseIdentifier)
+// _init()
+// }
+//
+// required init?(coder: NSCoder) {
+// super.init(coder: coder)
+// _init()
+// }
+//}
+//
+//extension PickServerCategoriesCell {
+//
+// private func _init() {
+// selectionStyle = .none
+// backgroundColor = Asset.Theme.Mastodon.systemGroupedBackground.color
+// configureMargin()
+//
+// metricView.translatesAutoresizingMaskIntoConstraints = false
+// contentView.addSubview(metricView)
+// NSLayoutConstraint.activate([
+// metricView.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor),
+// metricView.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor),
+// metricView.topAnchor.constraint(equalTo: contentView.topAnchor),
+// metricView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
+// metricView.heightAnchor.constraint(equalToConstant: 80).priority(.defaultHigh),
+// ])
+//
+// contentView.addSubview(collectionView)
+// NSLayoutConstraint.activate([
+// collectionView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
+// collectionView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
+// collectionView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 10),
+// contentView.bottomAnchor.constraint(equalTo: collectionView.bottomAnchor, constant: 20),
+// collectionView.heightAnchor.constraint(equalToConstant: 80).priority(.defaultHigh),
+// ])
+//
+// collectionView.delegate = self
+// }
+//
+// override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
+// super.traitCollectionDidChange(previousTraitCollection)
+//
+// configureMargin()
+// }
+//
+// override func layoutSubviews() {
+// super.layoutSubviews()
+//
+// collectionView.collectionViewLayout.invalidateLayout()
+// }
+//
+//}
+//
+//extension PickServerCategoriesCell {
+// private func configureMargin() {
+// switch traitCollection.horizontalSizeClass {
+// case .regular:
+// let margin = MastodonPickServerViewController.viewEdgeMargin
+// contentView.layoutMargins = UIEdgeInsets(top: 0, left: margin, bottom: 0, right: margin)
+// default:
+// contentView.layoutMargins = .zero
+// }
+// }
+//}
+//
+//// MARK: - UICollectionViewDelegateFlowLayout
+//extension PickServerCategoriesCell: UICollectionViewDelegateFlowLayout {
+//
+// func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
+// os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: indexPath: %s", ((#file as NSString).lastPathComponent), #line, #function, indexPath.debugDescription)
+// collectionView.selectItem(at: indexPath, animated: true, scrollPosition: .centeredHorizontally)
+// delegate?.pickServerCategoriesCell(self, collectionView: collectionView, didSelectItemAt: indexPath)
+// }
+//
+// func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
+// layoutIfNeeded()
+// return UIEdgeInsets(top: 0, left: metricView.frame.minX - collectionView.frame.minX, bottom: 0, right: collectionView.frame.maxX - metricView.frame.maxX)
+// }
+//
+// func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
+// return 16
+// }
+//
+// func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
+// return CGSize(width: 60, height: 80)
+// }
+//
+//}
+//
+//extension PickServerCategoriesCell {
+//
+// override func accessibilityElementCount() -> Int {
+// guard let diffableDataSource = diffableDataSource else { return 0 }
+// return diffableDataSource.snapshot().itemIdentifiers.count
+// }
+//
+// override func accessibilityElement(at index: Int) -> Any? {
+// guard let item = collectionView.cellForItem(at: IndexPath(item: index, section: 0)) else { return nil }
+// return item
+// }
+//
+//}
diff --git a/Mastodon/Deprecated/PickServerSearchCell.swift b/Mastodon/Deprecated/PickServerSearchCell.swift
new file mode 100644
index 00000000..465e7ae2
--- /dev/null
+++ b/Mastodon/Deprecated/PickServerSearchCell.swift
@@ -0,0 +1,171 @@
+//
+// PickServerSearchCell.swift
+// Mastodon
+//
+// Created by BradGao on 2021/2/24.
+//
+
+import UIKit
+
+//protocol PickServerSearchCellDelegate: AnyObject {
+// func pickServerSearchCell(_ cell: PickServerSearchCell, searchTextDidChange searchText: String?)
+//}
+//
+//class PickServerSearchCell: UITableViewCell {
+//
+// weak var delegate: PickServerSearchCellDelegate?
+//
+// private var bgView: UIView = {
+// let view = UIView()
+// view.backgroundColor = Asset.Theme.Mastodon.secondaryGroupedSystemBackground.color
+// view.translatesAutoresizingMaskIntoConstraints = false
+// view.layer.maskedCorners = [
+// .layerMinXMinYCorner,
+// .layerMaxXMinYCorner
+// ]
+// view.layer.cornerCurve = .continuous
+// view.layer.cornerRadius = MastodonPickServerAppearance.tableViewCornerRadius
+// return view
+// }()
+//
+// private var textFieldBgView: UIView = {
+// let view = UIView()
+// view.backgroundColor = Asset.Colors.TextField.background.color
+// view.translatesAutoresizingMaskIntoConstraints = false
+// view.layer.masksToBounds = true
+// view.layer.cornerRadius = 6
+// view.layer.cornerCurve = .continuous
+// return view
+// }()
+//
+// let searchTextField: UITextField = {
+// let textField = UITextField()
+// textField.translatesAutoresizingMaskIntoConstraints = false
+// textField.leftView = {
+// let imageView = UIImageView(
+// image: UIImage(
+// systemName: "magnifyingglass",
+// withConfiguration: UIImage.SymbolConfiguration(pointSize: 15, weight: .regular)
+// )
+// )
+// imageView.tintColor = Asset.Colors.Label.secondary.color.withAlphaComponent(0.6)
+//
+// let containerView = UIView()
+// imageView.translatesAutoresizingMaskIntoConstraints = false
+// containerView.addSubview(imageView)
+// NSLayoutConstraint.activate([
+// imageView.topAnchor.constraint(equalTo: containerView.topAnchor),
+// imageView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
+// imageView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
+// ])
+//
+// let paddingView = UIView()
+// paddingView.translatesAutoresizingMaskIntoConstraints = false
+// containerView.addSubview(paddingView)
+// NSLayoutConstraint.activate([
+// paddingView.topAnchor.constraint(equalTo: containerView.topAnchor),
+// paddingView.leadingAnchor.constraint(equalTo: imageView.trailingAnchor),
+// paddingView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
+// paddingView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
+// paddingView.widthAnchor.constraint(equalToConstant: 4).priority(.defaultHigh),
+// ])
+// return containerView
+// }()
+// textField.leftViewMode = .always
+// textField.font = .systemFont(ofSize: 15, weight: .regular)
+// textField.tintColor = Asset.Colors.Label.primary.color
+// textField.textColor = Asset.Colors.Label.primary.color
+// textField.adjustsFontForContentSizeCategory = true
+// textField.attributedPlaceholder =
+// NSAttributedString(string: L10n.Scene.ServerPicker.Input.placeholder,
+// attributes: [.font: UIFont.systemFont(ofSize: 15, weight: .regular),
+// .foregroundColor: Asset.Colors.Label.secondary.color.withAlphaComponent(0.6)])
+// textField.clearButtonMode = .whileEditing
+// textField.autocapitalizationType = .none
+// textField.autocorrectionType = .no
+// textField.returnKeyType = .done
+// textField.keyboardType = .URL
+// return textField
+// }()
+//
+// override func prepareForReuse() {
+// super.prepareForReuse()
+//
+// delegate = nil
+// }
+//
+// override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
+// super.init(style: style, reuseIdentifier: reuseIdentifier)
+// _init()
+// }
+//
+// required init?(coder: NSCoder) {
+// super.init(coder: coder)
+// _init()
+// }
+//}
+//
+//extension PickServerSearchCell {
+// private func _init() {
+// selectionStyle = .none
+// backgroundColor = Asset.Theme.Mastodon.systemGroupedBackground.color
+// configureMargin()
+//
+// searchTextField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged)
+// searchTextField.delegate = self
+//
+// contentView.addSubview(bgView)
+// contentView.addSubview(textFieldBgView)
+// contentView.addSubview(searchTextField)
+//
+// NSLayoutConstraint.activate([
+// bgView.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor),
+// bgView.topAnchor.constraint(equalTo: contentView.topAnchor),
+// bgView.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor),
+// bgView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
+//
+// textFieldBgView.leadingAnchor.constraint(equalTo: bgView.leadingAnchor, constant: 14),
+// textFieldBgView.topAnchor.constraint(equalTo: bgView.topAnchor, constant: 12),
+// bgView.trailingAnchor.constraint(equalTo: textFieldBgView.trailingAnchor, constant: 14),
+// bgView.bottomAnchor.constraint(equalTo: textFieldBgView.bottomAnchor, constant: 13),
+//
+// searchTextField.leadingAnchor.constraint(equalTo: textFieldBgView.leadingAnchor, constant: 11),
+// searchTextField.topAnchor.constraint(equalTo: textFieldBgView.topAnchor, constant: 4),
+// textFieldBgView.trailingAnchor.constraint(equalTo: searchTextField.trailingAnchor, constant: 11),
+// textFieldBgView.bottomAnchor.constraint(equalTo: searchTextField.bottomAnchor, constant: 4),
+// ])
+// }
+//
+// override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
+// super.traitCollectionDidChange(previousTraitCollection)
+//
+// configureMargin()
+// }
+//}
+//
+//extension PickServerSearchCell {
+// private func configureMargin() {
+// switch traitCollection.horizontalSizeClass {
+// case .regular:
+// let margin = MastodonPickServerViewController.viewEdgeMargin
+// contentView.layoutMargins = UIEdgeInsets(top: 0, left: margin, bottom: 0, right: margin)
+// default:
+// contentView.layoutMargins = .zero
+// }
+// }
+//}
+//
+//extension PickServerSearchCell {
+// @objc private func textFieldDidChange(_ textField: UITextField) {
+// delegate?.pickServerSearchCell(self, searchTextDidChange: textField.text)
+// }
+//}
+//
+//// MARK: - UITextFieldDelegate
+//extension PickServerSearchCell: UITextFieldDelegate {
+//
+// func textFieldShouldReturn(_ textField: UITextField) -> Bool {
+// textField.resignFirstResponder()
+// return false
+// }
+//}
diff --git a/Mastodon/Diffiable/Item/SelectedAccountItem.swift b/Mastodon/Diffiable/Account/SelectedAccountItem.swift
similarity index 100%
rename from Mastodon/Diffiable/Item/SelectedAccountItem.swift
rename to Mastodon/Diffiable/Account/SelectedAccountItem.swift
diff --git a/Mastodon/Diffiable/Section/SelectedAccountSection.swift b/Mastodon/Diffiable/Account/SelectedAccountSection.swift
similarity index 100%
rename from Mastodon/Diffiable/Section/SelectedAccountSection.swift
rename to Mastodon/Diffiable/Account/SelectedAccountSection.swift
diff --git a/Mastodon/Diffiable/Item/AutoCompleteItem.swift b/Mastodon/Diffiable/Compose/AutoCompleteItem.swift
similarity index 100%
rename from Mastodon/Diffiable/Item/AutoCompleteItem.swift
rename to Mastodon/Diffiable/Compose/AutoCompleteItem.swift
diff --git a/Mastodon/Diffiable/Section/Compose/AutoCompleteSection.swift b/Mastodon/Diffiable/Compose/AutoCompleteSection.swift
similarity index 100%
rename from Mastodon/Diffiable/Section/Compose/AutoCompleteSection.swift
rename to Mastodon/Diffiable/Compose/AutoCompleteSection.swift
diff --git a/Mastodon/Diffiable/Item/ComposeStatusAttachmentItem.swift b/Mastodon/Diffiable/Compose/ComposeStatusAttachmentItem.swift
similarity index 100%
rename from Mastodon/Diffiable/Item/ComposeStatusAttachmentItem.swift
rename to Mastodon/Diffiable/Compose/ComposeStatusAttachmentItem.swift
diff --git a/Mastodon/Diffiable/Section/Compose/ComposeStatusAttachmentSection.swift b/Mastodon/Diffiable/Compose/ComposeStatusAttachmentSection.swift
similarity index 100%
rename from Mastodon/Diffiable/Section/Compose/ComposeStatusAttachmentSection.swift
rename to Mastodon/Diffiable/Compose/ComposeStatusAttachmentSection.swift
diff --git a/Mastodon/Diffiable/Item/ComposeStatusItem.swift b/Mastodon/Diffiable/Compose/ComposeStatusItem.swift
similarity index 100%
rename from Mastodon/Diffiable/Item/ComposeStatusItem.swift
rename to Mastodon/Diffiable/Compose/ComposeStatusItem.swift
diff --git a/Mastodon/Diffiable/Item/ComposeStatusPollItem.swift b/Mastodon/Diffiable/Compose/ComposeStatusPollItem.swift
similarity index 100%
rename from Mastodon/Diffiable/Item/ComposeStatusPollItem.swift
rename to Mastodon/Diffiable/Compose/ComposeStatusPollItem.swift
diff --git a/Mastodon/Diffiable/Section/Compose/ComposeStatusPollSection.swift b/Mastodon/Diffiable/Compose/ComposeStatusPollSection.swift
similarity index 100%
rename from Mastodon/Diffiable/Section/Compose/ComposeStatusPollSection.swift
rename to Mastodon/Diffiable/Compose/ComposeStatusPollSection.swift
diff --git a/Mastodon/Diffiable/Section/Compose/ComposeStatusSection.swift b/Mastodon/Diffiable/Compose/ComposeStatusSection.swift
similarity index 100%
rename from Mastodon/Diffiable/Section/Compose/ComposeStatusSection.swift
rename to Mastodon/Diffiable/Compose/ComposeStatusSection.swift
diff --git a/Mastodon/Diffiable/Item/CustomEmojiPickerItem.swift b/Mastodon/Diffiable/Compose/CustomEmojiPickerItem.swift
similarity index 100%
rename from Mastodon/Diffiable/Item/CustomEmojiPickerItem.swift
rename to Mastodon/Diffiable/Compose/CustomEmojiPickerItem.swift
diff --git a/Mastodon/Diffiable/Section/Compose/CustomEmojiPickerSection.swift b/Mastodon/Diffiable/Compose/CustomEmojiPickerSection.swift
similarity index 100%
rename from Mastodon/Diffiable/Section/Compose/CustomEmojiPickerSection.swift
rename to Mastodon/Diffiable/Compose/CustomEmojiPickerSection.swift
diff --git a/Mastodon/Diffiable/DataSource/ASTableNode.swift b/Mastodon/Diffiable/DataSource/ASTableNode.swift
deleted file mode 100644
index 36ff1fb0..00000000
--- a/Mastodon/Diffiable/DataSource/ASTableNode.swift
+++ /dev/null
@@ -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(
- using stagedChangeset: StagedChangeset,
- 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) -> 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
diff --git a/Mastodon/Diffiable/DataSource/TableNodeDiffableDataSource.swift b/Mastodon/Diffiable/DataSource/TableNodeDiffableDataSource.swift
deleted file mode 100644
index 54ab22a4..00000000
--- a/Mastodon/Diffiable/DataSource/TableNodeDiffableDataSource.swift
+++ /dev/null
@@ -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: 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()
-
- /// 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, 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 {
- 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
diff --git a/Mastodon/Diffiable/Item/NotificationItem.swift b/Mastodon/Diffiable/Notification/NotificationItem.swift
similarity index 100%
rename from Mastodon/Diffiable/Item/NotificationItem.swift
rename to Mastodon/Diffiable/Notification/NotificationItem.swift
diff --git a/Mastodon/Diffiable/Section/Status/NotificationSection.swift b/Mastodon/Diffiable/Notification/NotificationSection.swift
similarity index 100%
rename from Mastodon/Diffiable/Section/Status/NotificationSection.swift
rename to Mastodon/Diffiable/Notification/NotificationSection.swift
diff --git a/Mastodon/Diffiable/Item/CategoryPickerItem.swift b/Mastodon/Diffiable/Onboarding/CategoryPickerItem.swift
similarity index 69%
rename from Mastodon/Diffiable/Item/CategoryPickerItem.swift
rename to Mastodon/Diffiable/Onboarding/CategoryPickerItem.swift
index 0f2cdcc2..53f9c9ab 100644
--- a/Mastodon/Diffiable/Item/CategoryPickerItem.swift
+++ b/Mastodon/Diffiable/Onboarding/CategoryPickerItem.swift
@@ -15,10 +15,11 @@ enum CategoryPickerItem {
}
extension CategoryPickerItem {
- var title: String {
+
+ var emoji: String {
switch self {
case .all:
- return L10n.Scene.ServerPicker.Button.Category.all
+ return "💬"
case .category(let category):
switch category.category {
case .academia:
@@ -32,7 +33,7 @@ extension CategoryPickerItem {
case .games:
return "🕹"
case .general:
- return "💬"
+ return "🐘"
case .journalism:
return "📰"
case .lgbt:
@@ -50,6 +51,41 @@ extension CategoryPickerItem {
}
}
}
+ var title: String {
+ switch self {
+ case .all:
+ return L10n.Scene.ServerPicker.Button.Category.all
+ case .category(let category):
+ switch category.category {
+ case .academia:
+ return L10n.Scene.ServerPicker.Button.Category.academia
+ case .activism:
+ return L10n.Scene.ServerPicker.Button.Category.activism
+ case .food:
+ return L10n.Scene.ServerPicker.Button.Category.food
+ case .furry:
+ return L10n.Scene.ServerPicker.Button.Category.furry
+ case .games:
+ return L10n.Scene.ServerPicker.Button.Category.games
+ case .general:
+ return L10n.Scene.ServerPicker.Button.Category.general
+ case .journalism:
+ return L10n.Scene.ServerPicker.Button.Category.journalism
+ case .lgbt:
+ return L10n.Scene.ServerPicker.Button.Category.lgbt
+ case .regional:
+ return L10n.Scene.ServerPicker.Button.Category.regional
+ case .art:
+ return L10n.Scene.ServerPicker.Button.Category.art
+ case .music:
+ return L10n.Scene.ServerPicker.Button.Category.music
+ case .tech:
+ return L10n.Scene.ServerPicker.Button.Category.tech
+ case ._other:
+ return "-" // FIXME:
+ }
+ }
+ }
var accessibilityDescription: String {
switch self {
@@ -82,7 +118,7 @@ extension CategoryPickerItem {
case .tech:
return L10n.Scene.ServerPicker.Button.Category.tech
case ._other:
- return "❓" // FIXME:
+ return "-" // FIXME:
}
}
}
diff --git a/Mastodon/Diffiable/Section/Onboarding/CategoryPickerSection.swift b/Mastodon/Diffiable/Onboarding/CategoryPickerSection.swift
similarity index 52%
rename from Mastodon/Diffiable/Section/Onboarding/CategoryPickerSection.swift
rename to Mastodon/Diffiable/Onboarding/CategoryPickerSection.swift
index 732813c0..525d7720 100644
--- a/Mastodon/Diffiable/Section/Onboarding/CategoryPickerSection.swift
+++ b/Mastodon/Diffiable/Onboarding/CategoryPickerSection.swift
@@ -19,27 +19,11 @@ extension CategoryPickerSection {
UICollectionViewDiffableDataSource(collectionView: collectionView) { [weak dependency] collectionView, indexPath, item -> UICollectionViewCell? in
guard let _ = dependency else { return nil }
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: PickServerCategoryCollectionViewCell.self), for: indexPath) as! PickServerCategoryCollectionViewCell
- switch item {
- case .all:
- cell.categoryView.titleLabel.font = .systemFont(ofSize: 17)
- case .category:
- cell.categoryView.titleLabel.font = .systemFont(ofSize: 28)
- }
+ cell.categoryView.emojiLabel.text = item.emoji
cell.categoryView.titleLabel.text = item.title
cell.observe(\.isSelected, options: [.initial, .new]) { cell, _ in
- if cell.isSelected {
- cell.categoryView.bgView.backgroundColor = Asset.Colors.brandBlue.color
- cell.categoryView.bgView.applyShadow(color: Asset.Colors.brandBlue.color, alpha: 1, x: 0, y: 0, blur: 4.0)
- if case .all = item {
- cell.categoryView.titleLabel.textColor = .white
- }
- } else {
- cell.categoryView.bgView.backgroundColor = Asset.Theme.Mastodon.secondaryGroupedSystemBackground.color
- cell.categoryView.bgView.applyShadow(color: Asset.Colors.brandBlue.color, alpha: 0, x: 0, y: 0, blur: 0.0)
- if case .all = item {
- cell.categoryView.titleLabel.textColor = Asset.Colors.brandBlue.color
- }
- }
+ cell.categoryView.highlightedIndicatorView.alpha = cell.isSelected ? 1 : 0
+ cell.categoryView.titleLabel.textColor = cell.isSelected ? Asset.Colors.Label.primary.color : Asset.Colors.Label.secondary.color
}
.store(in: &cell.observations)
diff --git a/Mastodon/Diffiable/Item/PickServerItem.swift b/Mastodon/Diffiable/Onboarding/PickServerItem.swift
similarity index 85%
rename from Mastodon/Diffiable/Item/PickServerItem.swift
rename to Mastodon/Diffiable/Onboarding/PickServerItem.swift
index 7db2c958..ba693ad7 100644
--- a/Mastodon/Diffiable/Item/PickServerItem.swift
+++ b/Mastodon/Diffiable/Onboarding/PickServerItem.swift
@@ -12,8 +12,6 @@ import MastodonSDK
/// Note: update Equatable when change case
enum PickServerItem {
case header
- case categoryPicker(items: [CategoryPickerItem])
- case search
case server(server: Mastodon.Entity.Server, attribute: ServerItemAttribute)
case loader(attribute: LoaderItemAttribute)
}
@@ -63,10 +61,6 @@ extension PickServerItem: Equatable {
switch (lhs, rhs) {
case (.header, .header):
return true
- case (.categoryPicker(let itemsLeft), .categoryPicker(let itemsRight)):
- return itemsLeft == itemsRight
- case (.search, .search):
- return true
case (.server(let serverLeft, _), .server(let serverRight, _)):
return serverLeft.domain == serverRight.domain
case (.loader(let attributeLeft), loader(let attributeRight)):
@@ -82,10 +76,6 @@ extension PickServerItem: Hashable {
switch self {
case .header:
hasher.combine(String(describing: PickServerItem.header.self))
- case .categoryPicker(let items):
- hasher.combine(items)
- case .search:
- hasher.combine(String(describing: PickServerItem.search.self))
case .server(let server, _):
hasher.combine(server.domain)
case .loader(let attribute):
diff --git a/Mastodon/Diffiable/Section/Onboarding/PickServerSection.swift b/Mastodon/Diffiable/Onboarding/PickServerSection.swift
similarity index 51%
rename from Mastodon/Diffiable/Section/Onboarding/PickServerSection.swift
rename to Mastodon/Diffiable/Onboarding/PickServerSection.swift
index 28b1ded3..9f74bad5 100644
--- a/Mastodon/Diffiable/Section/Onboarding/PickServerSection.swift
+++ b/Mastodon/Diffiable/Onboarding/PickServerSection.swift
@@ -12,8 +12,6 @@ import AlamofireImage
enum PickServerSection: Equatable, Hashable {
case header
- case category
- case search
case servers
}
@@ -21,36 +19,16 @@ extension PickServerSection {
static func tableViewDiffableDataSource(
for tableView: UITableView,
dependency: NeedsDependency,
- pickServerCategoriesCellDelegate: PickServerCategoriesCellDelegate,
- pickServerSearchCellDelegate: PickServerSearchCellDelegate,
pickServerCellDelegate: PickServerCellDelegate
) -> UITableViewDiffableDataSource {
UITableViewDiffableDataSource(tableView: tableView) { [
weak dependency,
- weak pickServerCategoriesCellDelegate,
- weak pickServerSearchCellDelegate,
weak pickServerCellDelegate
] tableView, indexPath, item -> UITableViewCell? in
guard let dependency = dependency else { return nil }
switch item {
case .header:
- let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: PickServerTitleCell.self), for: indexPath) as! PickServerTitleCell
- return cell
- case .categoryPicker(let items):
- let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: PickServerCategoriesCell.self), for: indexPath) as! PickServerCategoriesCell
- cell.delegate = pickServerCategoriesCellDelegate
- cell.diffableDataSource = CategoryPickerSection.collectionViewDiffableDataSource(
- for: cell.collectionView,
- dependency: dependency
- )
- var snapshot = NSDiffableDataSourceSnapshot()
- snapshot.appendSections([.main])
- snapshot.appendItems(items, toSection: .main)
- cell.diffableDataSource?.apply(snapshot, animatingDifferences: false, completion: nil)
- return cell
- case .search:
- let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: PickServerSearchCell.self), for: indexPath) as! PickServerSearchCell
- cell.delegate = pickServerSearchCellDelegate
+ let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: OnboardingHeadlineTableViewCell.self), for: indexPath) as! OnboardingHeadlineTableViewCell
return cell
case .server(let server, let attribute):
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: PickServerCell.self), for: indexPath) as! PickServerCell
@@ -70,19 +48,63 @@ extension PickServerSection {
static func configure(cell: PickServerCell, server: Mastodon.Entity.Server, attribute: PickServerItem.ServerItemAttribute) {
cell.domainLabel.text = server.domain
- cell.descriptionLabel.text = {
- guard let html = try? HTML(html: server.description, encoding: .utf8) else {
- return server.description
- }
+ cell.descriptionLabel.attributedText = {
+ let content: String = {
+ guard let html = try? HTML(html: server.description, encoding: .utf8) else {
+ return server.description
+ }
+ return html.text ?? server.description
+ }()
- return html.text ?? server.description
+ let paragraphStyle = NSMutableParagraphStyle()
+ paragraphStyle.lineHeightMultiple = 1.16
+
+ return NSAttributedString(
+ string: content,
+ attributes: [
+ .paragraphStyle: paragraphStyle
+ ]
+ )
}()
- cell.langValueLabel.text = server.language.uppercased()
- cell.usersValueLabel.text = parseUsersCount(server.totalUsers)
- cell.categoryValueLabel.text = server.category.uppercased()
-
- cell.updateExpandMode(mode: attribute.isExpand ? .expand : .collapse)
-
+ cell.usersValueLabel.attributedText = {
+ let attributedString = NSMutableAttributedString()
+ let attachment = NSTextAttachment(image: UIImage(systemName: "person.2.fill")!)
+ let attachmentAttributedString = NSAttributedString(attachment: attachment)
+ attributedString.append(attachmentAttributedString)
+ attributedString.append(NSAttributedString(string: " "))
+
+ let paragraphStyle = NSMutableParagraphStyle()
+ paragraphStyle.lineHeightMultiple = 1.12
+ let valueAttributedString = NSAttributedString(
+ string: parseUsersCount(server.totalUsers),
+ attributes: [
+ .paragraphStyle: paragraphStyle
+ ]
+ )
+ attributedString.append(valueAttributedString)
+
+ return attributedString
+ }()
+ cell.langValueLabel.attributedText = {
+ let attributedString = NSMutableAttributedString()
+ let attachment = NSTextAttachment(image: UIImage(systemName: "text.bubble.fill")!)
+ let attachmentAttributedString = NSAttributedString(attachment: attachment)
+ attributedString.append(attachmentAttributedString)
+ attributedString.append(NSAttributedString(string: " "))
+
+ let paragraphStyle = NSMutableParagraphStyle()
+ paragraphStyle.lineHeightMultiple = 1.12
+ let valueAttributedString = NSAttributedString(
+ string: server.language.uppercased(),
+ attributes: [
+ .paragraphStyle: paragraphStyle
+ ]
+ )
+ attributedString.append(valueAttributedString)
+
+ return attributedString
+ }()
+
attribute.isLast
.receive(on: DispatchQueue.main)
.sink { [weak cell] isLast in
@@ -101,41 +123,6 @@ extension PickServerSection {
}
}
.store(in: &cell.disposeBag)
-
- cell.expandMode
- .receive(on: DispatchQueue.main)
- .sink { mode in
- switch mode {
- case .collapse:
- // do nothing
- break
- case .expand:
- let placeholderImage = UIImage.placeholder(size: cell.thumbnailImageView.frame.size, color: .systemFill)
- .af.imageRounded(withCornerRadius: 3.0, divideRadiusByImageScale: false)
- guard let proxiedThumbnail = server.proxiedThumbnail,
- let url = URL(string: proxiedThumbnail) else {
- cell.thumbnailImageView.image = placeholderImage
- cell.thumbnailActivityIndicator.stopAnimating()
- return
- }
- cell.thumbnailImageView.isHidden = false
- cell.thumbnailActivityIndicator.startAnimating()
-
- cell.thumbnailImageView.af.setImage(
- withURL: url,
- placeholderImage: placeholderImage,
- filter: AspectScaledToFillSizeWithRoundedCornersFilter(size: cell.thumbnailImageView.frame.size, radius: 3),
- imageTransition: .crossDissolve(0.33),
- completion: { [weak cell] response in
- switch response.result {
- case .success, .failure:
- cell?.thumbnailActivityIndicator.stopAnimating()
- }
- }
- )
- }
- }
- .store(in: &cell.disposeBag)
}
private static func parseUsersCount(_ usersCount: Int) -> String {
diff --git a/Mastodon/Diffiable/Onboarding/RegisterItem.swift b/Mastodon/Diffiable/Onboarding/RegisterItem.swift
new file mode 100644
index 00000000..0fb0aead
--- /dev/null
+++ b/Mastodon/Diffiable/Onboarding/RegisterItem.swift
@@ -0,0 +1,19 @@
+//
+// RegisterItem.swift
+// Mastodon
+//
+// Created by MainasuK on 2022-1-5.
+//
+
+import Foundation
+
+enum RegisterItem: Hashable {
+ case header
+ case avatar
+ case name
+ case username
+ case email
+ case password
+ case hint
+ case reason
+}
diff --git a/Mastodon/Diffiable/Onboarding/RegisterSection.swift b/Mastodon/Diffiable/Onboarding/RegisterSection.swift
new file mode 100644
index 00000000..efb67f69
--- /dev/null
+++ b/Mastodon/Diffiable/Onboarding/RegisterSection.swift
@@ -0,0 +1,12 @@
+//
+// RegisterSection.swift
+// Mastodon
+//
+// Created by MainasuK on 2022-1-5.
+//
+
+import UIKit
+
+enum RegisterSection: Hashable {
+ case main
+}
diff --git a/Mastodon/Diffiable/Onboarding/ServerRuleItem.swift b/Mastodon/Diffiable/Onboarding/ServerRuleItem.swift
new file mode 100644
index 00000000..37d8b6ee
--- /dev/null
+++ b/Mastodon/Diffiable/Onboarding/ServerRuleItem.swift
@@ -0,0 +1,21 @@
+//
+// ServerRuleItem.swift
+// Mastodon
+//
+// Created by MainasuK on 2022-1-5.
+//
+
+import Foundation
+import MastodonSDK
+
+enum ServerRuleItem: Hashable {
+ case header(domain: String)
+ case rule(RuleContext)
+}
+
+extension ServerRuleItem {
+ struct RuleContext: Hashable {
+ let index: Int
+ let rule: Mastodon.Entity.Instance.Rule
+ }
+}
diff --git a/Mastodon/Diffiable/Onboarding/ServerRuleSection.swift b/Mastodon/Diffiable/Onboarding/ServerRuleSection.swift
new file mode 100644
index 00000000..66abec44
--- /dev/null
+++ b/Mastodon/Diffiable/Onboarding/ServerRuleSection.swift
@@ -0,0 +1,34 @@
+//
+// ServerRuleSection.swift
+// Mastodon
+//
+// Created by MainasuK on 2022-1-5.
+//
+
+import UIKit
+
+enum ServerRuleSection: Hashable {
+ case header
+ case rules
+}
+
+extension ServerRuleSection {
+ static func tableViewDiffableDataSource(
+ tableView: UITableView
+ ) -> UITableViewDiffableDataSource {
+ return UITableViewDiffableDataSource(tableView: tableView) { tableView, indexPath, item in
+ switch item {
+ case .header(let domain):
+ let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: OnboardingHeadlineTableViewCell.self), for: indexPath) as! OnboardingHeadlineTableViewCell
+ cell.titleLabel.text = L10n.Scene.ServerRules.title
+ cell.subTitleLabel.text = L10n.Scene.ServerRules.subtitle(domain)
+ return cell
+ case .rule(let ruleContext):
+ let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: ServerRulesTableViewCell.self), for: indexPath) as! ServerRulesTableViewCell
+ cell.indexImageView.image = UIImage(systemName: "\(ruleContext.index + 1).circle.fill") ?? UIImage(systemName: "questionmark.circle.fill")
+ cell.ruleLabel.text = ruleContext.rule.text
+ return cell
+ }
+ }
+ }
+}
diff --git a/Mastodon/Diffiable/Item/PollItem.swift b/Mastodon/Diffiable/Poll/PollItem.swift
similarity index 100%
rename from Mastodon/Diffiable/Item/PollItem.swift
rename to Mastodon/Diffiable/Poll/PollItem.swift
diff --git a/Mastodon/Diffiable/Section/Status/PollSection.swift b/Mastodon/Diffiable/Poll/PollSection.swift
similarity index 100%
rename from Mastodon/Diffiable/Section/Status/PollSection.swift
rename to Mastodon/Diffiable/Poll/PollSection.swift
diff --git a/Mastodon/Diffiable/Item/ProfileFieldItem.swift b/Mastodon/Diffiable/Profile/ProfileFieldItem.swift
similarity index 100%
rename from Mastodon/Diffiable/Item/ProfileFieldItem.swift
rename to Mastodon/Diffiable/Profile/ProfileFieldItem.swift
diff --git a/Mastodon/Diffiable/Section/ProfileFieldSection.swift b/Mastodon/Diffiable/Profile/ProfileFieldSection.swift
similarity index 100%
rename from Mastodon/Diffiable/Section/ProfileFieldSection.swift
rename to Mastodon/Diffiable/Profile/ProfileFieldSection.swift
diff --git a/Mastodon/Diffiable/Section/Search/RecommendAccountSection.swift b/Mastodon/Diffiable/Search/RecommendAccountSection.swift
similarity index 100%
rename from Mastodon/Diffiable/Section/Search/RecommendAccountSection.swift
rename to Mastodon/Diffiable/Search/RecommendAccountSection.swift
diff --git a/Mastodon/Diffiable/Section/Search/RecommendHashTagSection.swift b/Mastodon/Diffiable/Search/RecommendHashTagSection.swift
similarity index 100%
rename from Mastodon/Diffiable/Section/Search/RecommendHashTagSection.swift
rename to Mastodon/Diffiable/Search/RecommendHashTagSection.swift
diff --git a/Mastodon/Diffiable/Item/SearchHistoryItem.swift b/Mastodon/Diffiable/Search/SearchHistoryItem.swift
similarity index 100%
rename from Mastodon/Diffiable/Item/SearchHistoryItem.swift
rename to Mastodon/Diffiable/Search/SearchHistoryItem.swift
diff --git a/Mastodon/Diffiable/Section/Search/SearchHistorySection.swift b/Mastodon/Diffiable/Search/SearchHistorySection.swift
similarity index 100%
rename from Mastodon/Diffiable/Section/Search/SearchHistorySection.swift
rename to Mastodon/Diffiable/Search/SearchHistorySection.swift
diff --git a/Mastodon/Diffiable/Item/SearchResultItem.swift b/Mastodon/Diffiable/Search/SearchResultItem.swift
similarity index 100%
rename from Mastodon/Diffiable/Item/SearchResultItem.swift
rename to Mastodon/Diffiable/Search/SearchResultItem.swift
diff --git a/Mastodon/Diffiable/Section/Search/SearchResultSection.swift b/Mastodon/Diffiable/Search/SearchResultSection.swift
similarity index 100%
rename from Mastodon/Diffiable/Section/Search/SearchResultSection.swift
rename to Mastodon/Diffiable/Search/SearchResultSection.swift
diff --git a/Mastodon/Diffiable/Item/SettingsItem.swift b/Mastodon/Diffiable/Settings/SettingsItem.swift
similarity index 100%
rename from Mastodon/Diffiable/Item/SettingsItem.swift
rename to Mastodon/Diffiable/Settings/SettingsItem.swift
diff --git a/Mastodon/Diffiable/Section/SettingsSection.swift b/Mastodon/Diffiable/Settings/SettingsSection.swift
similarity index 100%
rename from Mastodon/Diffiable/Section/SettingsSection.swift
rename to Mastodon/Diffiable/Settings/SettingsSection.swift
diff --git a/Mastodon/Diffiable/Item/Item.swift b/Mastodon/Diffiable/Status/Item.swift
similarity index 100%
rename from Mastodon/Diffiable/Item/Item.swift
rename to Mastodon/Diffiable/Status/Item.swift
diff --git a/Mastodon/Diffiable/Section/Status/ReportSection.swift b/Mastodon/Diffiable/Status/ReportSection.swift
similarity index 100%
rename from Mastodon/Diffiable/Section/Status/ReportSection.swift
rename to Mastodon/Diffiable/Status/ReportSection.swift
diff --git a/Mastodon/Diffiable/Section/Status/StatusSection.swift b/Mastodon/Diffiable/Status/StatusSection.swift
similarity index 98%
rename from Mastodon/Diffiable/Section/Status/StatusSection.swift
rename to Mastodon/Diffiable/Status/StatusSection.swift
index 61217c79..918b8b45 100644
--- a/Mastodon/Diffiable/Section/Status/StatusSection.swift
+++ b/Mastodon/Diffiable/Status/StatusSection.swift
@@ -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 {
- 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")
diff --git a/Mastodon/Diffiable/Item/UserItem.swift b/Mastodon/Diffiable/User/UserItem.swift
similarity index 100%
rename from Mastodon/Diffiable/Item/UserItem.swift
rename to Mastodon/Diffiable/User/UserItem.swift
diff --git a/Mastodon/Diffiable/Section/UserSection.swift b/Mastodon/Diffiable/User/UserSection.swift
similarity index 100%
rename from Mastodon/Diffiable/Section/UserSection.swift
rename to Mastodon/Diffiable/User/UserSection.swift
diff --git a/Mastodon/Generated/Assets.swift b/Mastodon/Generated/Assets.swift
index 906dd74e..410d81a2 100644
--- a/Mastodon/Generated/Assets.swift
+++ b/Mastodon/Generated/Assets.swift
@@ -47,6 +47,7 @@ internal enum Asset {
}
internal enum Label {
internal static let primary = ColorAsset(name: "Colors/Label/primary")
+ internal static let primaryReverse = ColorAsset(name: "Colors/Label/primary.reverse")
internal static let secondary = ColorAsset(name: "Colors/Label/secondary")
internal static let tertiary = ColorAsset(name: "Colors/Label/tertiary")
}
@@ -89,6 +90,16 @@ internal enum Asset {
internal static let faceSmilingAdaptive = ImageAsset(name: "Human/face.smiling.adaptive")
}
internal enum Scene {
+ internal enum Onboarding {
+ internal static let avatarPlaceholder = ImageAsset(name: "Scene/Onboarding/avatar.placeholder")
+ internal static let navigationBackButtonBackground = ColorAsset(name: "Scene/Onboarding/navigation.back.button.background")
+ internal static let navigationBackButtonBackgroundHighlighted = ColorAsset(name: "Scene/Onboarding/navigation.back.button.background.highlighted")
+ internal static let navigationNextButtonBackground = ColorAsset(name: "Scene/Onboarding/navigation.next.button.background")
+ internal static let navigationNextButtonBackgroundHighlighted = ColorAsset(name: "Scene/Onboarding/navigation.next.button.background.highlighted")
+ internal static let onboardingBackground = ColorAsset(name: "Scene/Onboarding/onboarding.background")
+ internal static let searchBarBackground = ColorAsset(name: "Scene/Onboarding/search.bar.background")
+ internal static let textFieldBackground = ColorAsset(name: "Scene/Onboarding/textField.background")
+ }
internal enum Profile {
internal enum Banner {
internal static let bioEditBackgroundGray = ColorAsset(name: "Scene/Profile/Banner/bio.edit.background.gray")
@@ -102,8 +113,10 @@ internal enum Asset {
internal enum Welcome {
internal enum Illustration {
internal static let backgroundCyan = ColorAsset(name: "Scene/Welcome/illustration/background.cyan")
+ internal static let cloudBaseExtend = ImageAsset(name: "Scene/Welcome/illustration/cloud.base.extend")
internal static let cloudBase = ImageAsset(name: "Scene/Welcome/illustration/cloud.base")
internal static let elephantOnAirplaneWithContrail = ImageAsset(name: "Scene/Welcome/illustration/elephant.on.airplane.with.contrail")
+ internal static let elephantThreeOnGrassExtend = ImageAsset(name: "Scene/Welcome/illustration/elephant.three.on.grass.extend")
internal static let elephantThreeOnGrass = ImageAsset(name: "Scene/Welcome/illustration/elephant.three.on.grass")
internal static let elephantThreeOnGrassWithTreeThree = ImageAsset(name: "Scene/Welcome/illustration/elephant.three.on.grass.with.tree.three")
internal static let elephantThreeOnGrassWithTreeTwo = ImageAsset(name: "Scene/Welcome/illustration/elephant.three.on.grass.with.tree.two")
@@ -112,6 +125,7 @@ internal enum Asset {
internal static let mastodonLogoBlackLarge = ImageAsset(name: "Scene/Welcome/mastodon.logo.black.large")
internal static let mastodonLogo = ImageAsset(name: "Scene/Welcome/mastodon.logo")
internal static let mastodonLogoLarge = ImageAsset(name: "Scene/Welcome/mastodon.logo.large")
+ internal static let signInButtonBackground = ColorAsset(name: "Scene/Welcome/sign.in.button.background")
}
}
internal enum Settings {
diff --git a/Mastodon/Info.plist b/Mastodon/Info.plist
index affa5b05..8f0b7211 100644
--- a/Mastodon/Info.plist
+++ b/Mastodon/Info.plist
@@ -17,7 +17,7 @@
CFBundlePackageType
$(PRODUCT_BUNDLE_PACKAGE_TYPE)
CFBundleShortVersionString
- 1.2.0
+ 1.3.0
CFBundleURLTypes
@@ -30,7 +30,7 @@
CFBundleVersion
- 88
+ 90
ITSAppUsesNonExemptEncryption
LSApplicationQueriesSchemes
diff --git a/Mastodon/Protocol/StatusProvider/StatusProvider+StatusNodeDelegate.swift b/Mastodon/Protocol/StatusProvider/StatusProvider+StatusNodeDelegate.swift
deleted file mode 100644
index 3c6d7da1..00000000
--- a/Mastodon/Protocol/StatusProvider/StatusProvider+StatusNodeDelegate.swift
+++ /dev/null
@@ -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
diff --git a/Mastodon/Protocol/StatusProvider/StatusProvider.swift b/Mastodon/Protocol/StatusProvider/StatusProvider.swift
index 3497fd7a..2f13b8d5 100644
--- a/Mastodon/Protocol/StatusProvider/StatusProvider.swift
+++ b/Mastodon/Protocol/StatusProvider/StatusProvider.swift
@@ -10,10 +10,6 @@ import Combine
import CoreData
import CoreDataStack
-#if ASDK
-import AsyncDisplayKit
-#endif
-
protocol StatusProvider: NeedsDependency & DisposeBagCollectable & UIViewController {
// async
func status() -> Future
@@ -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)
diff --git a/Mastodon/Protocol/StatusProvider/StatusProviderFacade.swift b/Mastodon/Protocol/StatusProvider/StatusProviderFacade.swift
index d11870ed..68987c30 100644
--- a/Mastodon/Protocol/StatusProvider/StatusProviderFacade.swift
+++ b/Mastodon/Protocol/StatusProvider/StatusProviderFacade.swift
@@ -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
diff --git a/Mastodon/Resources/Assets.xcassets/Colors/Label/primary.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/Colors/Label/primary.colorset/Contents.json
index 202a1c04..ee70bcc1 100644
--- a/Mastodon/Resources/Assets.xcassets/Colors/Label/primary.colorset/Contents.json
+++ b/Mastodon/Resources/Assets.xcassets/Colors/Label/primary.colorset/Contents.json
@@ -5,9 +5,9 @@
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
- "blue" : "0x00",
- "green" : "0x00",
- "red" : "0x00"
+ "blue" : "0.216",
+ "green" : "0.173",
+ "red" : "0.157"
}
},
"idiom" : "universal"
@@ -23,9 +23,9 @@
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
- "blue" : "1.000",
- "green" : "1.000",
- "red" : "1.000"
+ "blue" : "0xEE",
+ "green" : "0xEE",
+ "red" : "0xEE"
}
},
"idiom" : "universal"
diff --git a/Mastodon/Resources/Assets.xcassets/Colors/Label/primary.reverse.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/Colors/Label/primary.reverse.colorset/Contents.json
new file mode 100644
index 00000000..8f42a585
--- /dev/null
+++ b/Mastodon/Resources/Assets.xcassets/Colors/Label/primary.reverse.colorset/Contents.json
@@ -0,0 +1,38 @@
+{
+ "colors" : [
+ {
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "0.933",
+ "green" : "0.933",
+ "red" : "0.933"
+ }
+ },
+ "idiom" : "universal"
+ },
+ {
+ "appearances" : [
+ {
+ "appearance" : "luminosity",
+ "value" : "dark"
+ }
+ ],
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "0.216",
+ "green" : "0.173",
+ "red" : "0.157"
+ }
+ },
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Mastodon/Resources/Assets.xcassets/Colors/Label/secondary.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/Colors/Label/secondary.colorset/Contents.json
index 70b1446d..104dfd02 100644
--- a/Mastodon/Resources/Assets.xcassets/Colors/Label/secondary.colorset/Contents.json
+++ b/Mastodon/Resources/Assets.xcassets/Colors/Label/secondary.colorset/Contents.json
@@ -22,10 +22,10 @@
"color" : {
"color-space" : "srgb",
"components" : {
- "alpha" : "0.600",
- "blue" : "0xF5",
- "green" : "0xEB",
- "red" : "0xEB"
+ "alpha" : "1.000",
+ "blue" : "0xAD",
+ "green" : "0x9D",
+ "red" : "0x97"
}
},
"idiom" : "universal"
diff --git a/Mastodon/Resources/Assets.xcassets/Scene/Onboarding/Contents.json b/Mastodon/Resources/Assets.xcassets/Scene/Onboarding/Contents.json
new file mode 100644
index 00000000..6e965652
--- /dev/null
+++ b/Mastodon/Resources/Assets.xcassets/Scene/Onboarding/Contents.json
@@ -0,0 +1,9 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "provides-namespace" : true
+ }
+}
diff --git a/Mastodon/Resources/Assets.xcassets/Scene/Onboarding/avatar.placeholder.imageset/Contents.json b/Mastodon/Resources/Assets.xcassets/Scene/Onboarding/avatar.placeholder.imageset/Contents.json
new file mode 100644
index 00000000..2b84d06b
--- /dev/null
+++ b/Mastodon/Resources/Assets.xcassets/Scene/Onboarding/avatar.placeholder.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "filename" : "Frame 82.jpg",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "Frame 82@2x.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "Frame 82@3x.png",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Mastodon/Resources/Assets.xcassets/Scene/Onboarding/avatar.placeholder.imageset/Frame 82.jpg b/Mastodon/Resources/Assets.xcassets/Scene/Onboarding/avatar.placeholder.imageset/Frame 82.jpg
new file mode 100644
index 00000000..7819c97b
Binary files /dev/null and b/Mastodon/Resources/Assets.xcassets/Scene/Onboarding/avatar.placeholder.imageset/Frame 82.jpg differ
diff --git a/Mastodon/Resources/Assets.xcassets/Scene/Onboarding/avatar.placeholder.imageset/Frame 82@2x.png b/Mastodon/Resources/Assets.xcassets/Scene/Onboarding/avatar.placeholder.imageset/Frame 82@2x.png
new file mode 100644
index 00000000..31f1bdf6
Binary files /dev/null and b/Mastodon/Resources/Assets.xcassets/Scene/Onboarding/avatar.placeholder.imageset/Frame 82@2x.png differ
diff --git a/Mastodon/Resources/Assets.xcassets/Scene/Onboarding/avatar.placeholder.imageset/Frame 82@3x.png b/Mastodon/Resources/Assets.xcassets/Scene/Onboarding/avatar.placeholder.imageset/Frame 82@3x.png
new file mode 100644
index 00000000..68603b22
Binary files /dev/null and b/Mastodon/Resources/Assets.xcassets/Scene/Onboarding/avatar.placeholder.imageset/Frame 82@3x.png differ
diff --git a/Mastodon/Resources/Assets.xcassets/Scene/Onboarding/navigation.back.button.background.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/Scene/Onboarding/navigation.back.button.background.colorset/Contents.json
new file mode 100644
index 00000000..b7d63ece
--- /dev/null
+++ b/Mastodon/Resources/Assets.xcassets/Scene/Onboarding/navigation.back.button.background.colorset/Contents.json
@@ -0,0 +1,38 @@
+{
+ "colors" : [
+ {
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "0xFF",
+ "green" : "0xFF",
+ "red" : "0xFF"
+ }
+ },
+ "idiom" : "universal"
+ },
+ {
+ "appearances" : [
+ {
+ "appearance" : "luminosity",
+ "value" : "dark"
+ }
+ ],
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "0.200",
+ "blue" : "0x80",
+ "green" : "0x78",
+ "red" : "0x78"
+ }
+ },
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Mastodon/Resources/Assets.xcassets/Scene/Onboarding/navigation.back.button.background.highlighted.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/Scene/Onboarding/navigation.back.button.background.highlighted.colorset/Contents.json
new file mode 100644
index 00000000..7136040b
--- /dev/null
+++ b/Mastodon/Resources/Assets.xcassets/Scene/Onboarding/navigation.back.button.background.highlighted.colorset/Contents.json
@@ -0,0 +1,38 @@
+{
+ "colors" : [
+ {
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "0xE5",
+ "green" : "0xE5",
+ "red" : "0xE5"
+ }
+ },
+ "idiom" : "universal"
+ },
+ {
+ "appearances" : [
+ {
+ "appearance" : "luminosity",
+ "value" : "dark"
+ }
+ ],
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "0.400",
+ "blue" : "0x80",
+ "green" : "0x78",
+ "red" : "0x78"
+ }
+ },
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Mastodon/Resources/Assets.xcassets/Scene/Onboarding/navigation.next.button.background.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/Scene/Onboarding/navigation.next.button.background.colorset/Contents.json
new file mode 100644
index 00000000..17ed9364
--- /dev/null
+++ b/Mastodon/Resources/Assets.xcassets/Scene/Onboarding/navigation.next.button.background.colorset/Contents.json
@@ -0,0 +1,38 @@
+{
+ "colors" : [
+ {
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "0x37",
+ "green" : "0x2C",
+ "red" : "0x28"
+ }
+ },
+ "idiom" : "universal"
+ },
+ {
+ "appearances" : [
+ {
+ "appearance" : "luminosity",
+ "value" : "dark"
+ }
+ ],
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "0xEE",
+ "green" : "0xEE",
+ "red" : "0xEE"
+ }
+ },
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Mastodon/Resources/Assets.xcassets/Scene/Onboarding/navigation.next.button.background.highlighted.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/Scene/Onboarding/navigation.next.button.background.highlighted.colorset/Contents.json
new file mode 100644
index 00000000..706cd755
--- /dev/null
+++ b/Mastodon/Resources/Assets.xcassets/Scene/Onboarding/navigation.next.button.background.highlighted.colorset/Contents.json
@@ -0,0 +1,38 @@
+{
+ "colors" : [
+ {
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "0x1B",
+ "green" : "0x15",
+ "red" : "0x13"
+ }
+ },
+ "idiom" : "universal"
+ },
+ {
+ "appearances" : [
+ {
+ "appearance" : "luminosity",
+ "value" : "dark"
+ }
+ ],
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "0xBA",
+ "green" : "0xBA",
+ "red" : "0xBA"
+ }
+ },
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Mastodon/Resources/Assets.xcassets/Scene/Onboarding/onboarding.background.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/Scene/Onboarding/onboarding.background.colorset/Contents.json
new file mode 100644
index 00000000..0b219c90
--- /dev/null
+++ b/Mastodon/Resources/Assets.xcassets/Scene/Onboarding/onboarding.background.colorset/Contents.json
@@ -0,0 +1,38 @@
+{
+ "colors" : [
+ {
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "0xF7",
+ "green" : "0xF2",
+ "red" : "0xF2"
+ }
+ },
+ "idiom" : "universal"
+ },
+ {
+ "appearances" : [
+ {
+ "appearance" : "luminosity",
+ "value" : "dark"
+ }
+ ],
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "0x21",
+ "green" : "0x1B",
+ "red" : "0x19"
+ }
+ },
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Mastodon/Resources/Assets.xcassets/Scene/Onboarding/search.bar.background.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/Scene/Onboarding/search.bar.background.colorset/Contents.json
new file mode 100644
index 00000000..f16bb02f
--- /dev/null
+++ b/Mastodon/Resources/Assets.xcassets/Scene/Onboarding/search.bar.background.colorset/Contents.json
@@ -0,0 +1,38 @@
+{
+ "colors" : [
+ {
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "0.200",
+ "blue" : "0x80",
+ "green" : "0x78",
+ "red" : "0x78"
+ }
+ },
+ "idiom" : "universal"
+ },
+ {
+ "appearances" : [
+ {
+ "appearance" : "luminosity",
+ "value" : "dark"
+ }
+ ],
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "0.240",
+ "blue" : "0x80",
+ "green" : "0x76",
+ "red" : "0x76"
+ }
+ },
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Mastodon/Resources/Assets.xcassets/Scene/Onboarding/textField.background.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/Scene/Onboarding/textField.background.colorset/Contents.json
new file mode 100644
index 00000000..147cca83
--- /dev/null
+++ b/Mastodon/Resources/Assets.xcassets/Scene/Onboarding/textField.background.colorset/Contents.json
@@ -0,0 +1,38 @@
+{
+ "colors" : [
+ {
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "1.000",
+ "green" : "1.000",
+ "red" : "1.000"
+ }
+ },
+ "idiom" : "universal"
+ },
+ {
+ "appearances" : [
+ {
+ "appearance" : "luminosity",
+ "value" : "dark"
+ }
+ ],
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "0x37",
+ "green" : "0x2C",
+ "red" : "0x28"
+ }
+ },
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Mastodon/Resources/Assets.xcassets/Scene/Welcome/illustration/cloud.base.extend.imageset/Contents.json b/Mastodon/Resources/Assets.xcassets/Scene/Welcome/illustration/cloud.base.extend.imageset/Contents.json
new file mode 100644
index 00000000..421e01a3
--- /dev/null
+++ b/Mastodon/Resources/Assets.xcassets/Scene/Welcome/illustration/cloud.base.extend.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "filename" : "cloud.base.extend.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "cloud.base.extend@2x.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "cloud.base.extend@3x.png",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Mastodon/Resources/Assets.xcassets/Scene/Welcome/illustration/cloud.base.extend.imageset/cloud.base.extend.png b/Mastodon/Resources/Assets.xcassets/Scene/Welcome/illustration/cloud.base.extend.imageset/cloud.base.extend.png
new file mode 100644
index 00000000..3c8443c9
Binary files /dev/null and b/Mastodon/Resources/Assets.xcassets/Scene/Welcome/illustration/cloud.base.extend.imageset/cloud.base.extend.png differ
diff --git a/Mastodon/Resources/Assets.xcassets/Scene/Welcome/illustration/cloud.base.extend.imageset/cloud.base.extend@2x.png b/Mastodon/Resources/Assets.xcassets/Scene/Welcome/illustration/cloud.base.extend.imageset/cloud.base.extend@2x.png
new file mode 100644
index 00000000..b03b6720
Binary files /dev/null and b/Mastodon/Resources/Assets.xcassets/Scene/Welcome/illustration/cloud.base.extend.imageset/cloud.base.extend@2x.png differ
diff --git a/Mastodon/Resources/Assets.xcassets/Scene/Welcome/illustration/cloud.base.extend.imageset/cloud.base.extend@3x.png b/Mastodon/Resources/Assets.xcassets/Scene/Welcome/illustration/cloud.base.extend.imageset/cloud.base.extend@3x.png
new file mode 100644
index 00000000..f7747685
Binary files /dev/null and b/Mastodon/Resources/Assets.xcassets/Scene/Welcome/illustration/cloud.base.extend.imageset/cloud.base.extend@3x.png differ
diff --git a/Mastodon/Resources/Assets.xcassets/Scene/Welcome/illustration/elephant.three.on.grass.extend.imageset/Contents.json b/Mastodon/Resources/Assets.xcassets/Scene/Welcome/illustration/elephant.three.on.grass.extend.imageset/Contents.json
new file mode 100644
index 00000000..9c3ea2de
--- /dev/null
+++ b/Mastodon/Resources/Assets.xcassets/Scene/Welcome/illustration/elephant.three.on.grass.extend.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "filename" : "elephant.three.on.grass.extend.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "elephant.three.on.grass.extend@2x.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "elephant.three.on.grass.extend@3x.png",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Mastodon/Resources/Assets.xcassets/Scene/Welcome/illustration/elephant.three.on.grass.extend.imageset/elephant.three.on.grass.extend.png b/Mastodon/Resources/Assets.xcassets/Scene/Welcome/illustration/elephant.three.on.grass.extend.imageset/elephant.three.on.grass.extend.png
new file mode 100644
index 00000000..97ef8df6
Binary files /dev/null and b/Mastodon/Resources/Assets.xcassets/Scene/Welcome/illustration/elephant.three.on.grass.extend.imageset/elephant.three.on.grass.extend.png differ
diff --git a/Mastodon/Resources/Assets.xcassets/Scene/Welcome/illustration/elephant.three.on.grass.extend.imageset/elephant.three.on.grass.extend@2x.png b/Mastodon/Resources/Assets.xcassets/Scene/Welcome/illustration/elephant.three.on.grass.extend.imageset/elephant.three.on.grass.extend@2x.png
new file mode 100644
index 00000000..63580f3c
Binary files /dev/null and b/Mastodon/Resources/Assets.xcassets/Scene/Welcome/illustration/elephant.three.on.grass.extend.imageset/elephant.three.on.grass.extend@2x.png differ
diff --git a/Mastodon/Resources/Assets.xcassets/Scene/Welcome/illustration/elephant.three.on.grass.extend.imageset/elephant.three.on.grass.extend@3x.png b/Mastodon/Resources/Assets.xcassets/Scene/Welcome/illustration/elephant.three.on.grass.extend.imageset/elephant.three.on.grass.extend@3x.png
new file mode 100644
index 00000000..8799a731
Binary files /dev/null and b/Mastodon/Resources/Assets.xcassets/Scene/Welcome/illustration/elephant.three.on.grass.extend.imageset/elephant.three.on.grass.extend@3x.png differ
diff --git a/Mastodon/Resources/Assets.xcassets/Scene/Welcome/sign.in.button.background.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/Scene/Welcome/sign.in.button.background.colorset/Contents.json
new file mode 100644
index 00000000..7bf1f1e4
--- /dev/null
+++ b/Mastodon/Resources/Assets.xcassets/Scene/Welcome/sign.in.button.background.colorset/Contents.json
@@ -0,0 +1,20 @@
+{
+ "colors" : [
+ {
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "0x81",
+ "green" : "0xAC",
+ "red" : "0x58"
+ }
+ },
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Mastodon/Resources/Assets.xcassets/Theme/system/Background/content.warning.overlay.background.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/Theme/system/Background/content.warning.overlay.background.colorset/Contents.json
index d211d7df..c8aa45b5 100644
--- a/Mastodon/Resources/Assets.xcassets/Theme/system/Background/content.warning.overlay.background.colorset/Contents.json
+++ b/Mastodon/Resources/Assets.xcassets/Theme/system/Background/content.warning.overlay.background.colorset/Contents.json
@@ -5,9 +5,9 @@
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
- "blue" : "0.922",
- "green" : "0.898",
- "red" : "0.867"
+ "blue" : "0xEB",
+ "green" : "0xE4",
+ "red" : "0xDD"
}
},
"idiom" : "universal"
diff --git a/Mastodon/Resources/Assets.xcassets/Theme/system/Background/secondary.system.background.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/Theme/system/Background/secondary.system.background.colorset/Contents.json
index 77d24b11..14441ef0 100644
--- a/Mastodon/Resources/Assets.xcassets/Theme/system/Background/secondary.system.background.colorset/Contents.json
+++ b/Mastodon/Resources/Assets.xcassets/Theme/system/Background/secondary.system.background.colorset/Contents.json
@@ -5,9 +5,9 @@
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
- "blue" : "0.910",
- "green" : "0.882",
- "red" : "0.851"
+ "blue" : "0xE8",
+ "green" : "0xE0",
+ "red" : "0xD9"
}
},
"idiom" : "universal"
diff --git a/Mastodon/Resources/Assets.xcassets/Theme/system/Background/system.grouped.background.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/Theme/system/Background/system.grouped.background.colorset/Contents.json
index 370a745e..daac70e0 100644
--- a/Mastodon/Resources/Assets.xcassets/Theme/system/Background/system.grouped.background.colorset/Contents.json
+++ b/Mastodon/Resources/Assets.xcassets/Theme/system/Background/system.grouped.background.colorset/Contents.json
@@ -5,9 +5,9 @@
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
- "blue" : "0.910",
- "green" : "0.882",
- "red" : "0.851"
+ "blue" : "0xE8",
+ "green" : "0xE0",
+ "red" : "0xD9"
}
},
"idiom" : "universal"
diff --git a/Mastodon/Scene/HomeTimeline/AsyncHomeTimeline/AsyncHomeTimelineViewController+DebugAction.swift b/Mastodon/Scene/HomeTimeline/AsyncHomeTimeline/AsyncHomeTimelineViewController+DebugAction.swift
deleted file mode 100644
index 19c3244c..00000000
--- a/Mastodon/Scene/HomeTimeline/AsyncHomeTimeline/AsyncHomeTimelineViewController+DebugAction.swift
+++ /dev/null
@@ -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
diff --git a/Mastodon/Scene/HomeTimeline/AsyncHomeTimeline/AsyncHomeTimelineViewController+Provider.swift b/Mastodon/Scene/HomeTimeline/AsyncHomeTimeline/AsyncHomeTimelineViewController+Provider.swift
deleted file mode 100644
index 5f97ebea..00000000
--- a/Mastodon/Scene/HomeTimeline/AsyncHomeTimeline/AsyncHomeTimelineViewController+Provider.swift
+++ /dev/null
@@ -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 {
- return Future { promise in promise(.success(nil)) }
- }
-
- func status(for cell: UITableViewCell?, indexPath: IndexPath?) -> Future {
- 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 {
- return Future { promise in promise(.success(nil)) }
- }
-
- var managedObjectContext: NSManagedObjectContext {
- return viewModel.fetchedResultsController.managedObjectContext
- }
-
- var tableViewDiffableDataSource: UITableViewDiffableDataSource? {
- 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
diff --git a/Mastodon/Scene/HomeTimeline/AsyncHomeTimeline/AsyncHomeTimelineViewController.swift b/Mastodon/Scene/HomeTimeline/AsyncHomeTimeline/AsyncHomeTimelineViewController.swift
deleted file mode 100644
index c90b703e..00000000
--- a/Mastodon/Scene/HomeTimeline/AsyncHomeTimeline/AsyncHomeTimelineViewController.swift
+++ /dev/null
@@ -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, NeedsDependency, MediaPreviewableViewController {
-
- weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
- weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
-
- var disposeBag = Set()
- 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 { 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
diff --git a/Mastodon/Scene/HomeTimeline/AsyncHomeTimeline/AsyncHomeTimelineViewModel+Diffable.swift b/Mastodon/Scene/HomeTimeline/AsyncHomeTimeline/AsyncHomeTimelineViewModel+Diffable.swift
deleted file mode 100644
index 7799c216..00000000
--- a/Mastodon/Scene/HomeTimeline/AsyncHomeTimeline/AsyncHomeTimelineViewModel+Diffable.swift
+++ /dev/null
@@ -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()
- snapshot.appendSections([.main])
- diffableDataSource?.apply(snapshot)
- }
-
-}
-
-// MARK: - NSFetchedResultsControllerDelegate
-extension AsyncHomeTimelineViewModel: NSFetchedResultsControllerDelegate {
-
- func controllerWillChangeContent(_ controller: NSFetchedResultsController) {
- os_log("%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
- }
-
- func controller(_ controller: NSFetchedResultsController, 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()
- 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 {
- let item: T
- let sourceIndexPath: IndexPath
- let targetIndexPath: IndexPath
- let offset: CGFloat
- }
-
- private func calculateReloadSnapshotDifference(
- navigationBar: UINavigationBar,
- tableView: UITableView,
- oldSnapshot: DiffableDataSourceSnapshot,
- newSnapshot: DiffableDataSourceSnapshot
- ) -> Difference? {
- 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
diff --git a/Mastodon/Scene/HomeTimeline/AsyncHomeTimeline/AsyncHomeTimelineViewModel+LoadLatestState.swift b/Mastodon/Scene/HomeTimeline/AsyncHomeTimeline/AsyncHomeTimelineViewModel+LoadLatestState.swift
deleted file mode 100644
index 4d73eae5..00000000
--- a/Mastodon/Scene/HomeTimeline/AsyncHomeTimeline/AsyncHomeTimelineViewModel+LoadLatestState.swift
+++ /dev/null
@@ -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
diff --git a/Mastodon/Scene/HomeTimeline/AsyncHomeTimeline/AsyncHomeTimelineViewModel+LoadMiddleState.swift b/Mastodon/Scene/HomeTimeline/AsyncHomeTimeline/AsyncHomeTimelineViewModel+LoadMiddleState.swift
deleted file mode 100644
index f568a6aa..00000000
--- a/Mastodon/Scene/HomeTimeline/AsyncHomeTimeline/AsyncHomeTimelineViewModel+LoadMiddleState.swift
+++ /dev/null
@@ -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
diff --git a/Mastodon/Scene/HomeTimeline/AsyncHomeTimeline/AsyncHomeTimelineViewModel+LoadOldestState.swift b/Mastodon/Scene/HomeTimeline/AsyncHomeTimeline/AsyncHomeTimelineViewModel+LoadOldestState.swift
deleted file mode 100644
index 5743ab29..00000000
--- a/Mastodon/Scene/HomeTimeline/AsyncHomeTimeline/AsyncHomeTimelineViewModel+LoadOldestState.swift
+++ /dev/null
@@ -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
diff --git a/Mastodon/Scene/HomeTimeline/AsyncHomeTimeline/AsyncHomeTimelineViewModel.swift b/Mastodon/Scene/HomeTimeline/AsyncHomeTimeline/AsyncHomeTimelineViewModel.swift
deleted file mode 100644
index d7ed0b10..00000000
--- a/Mastodon/Scene/HomeTimeline/AsyncHomeTimeline/AsyncHomeTimelineViewModel.swift
+++ /dev/null
@@ -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()
- var observations = Set()
-
- // input
- let context: AppContext
- let timelinePredicate = CurrentValueSubject(nil)
- let fetchedResultsController: NSFetchedResultsController
- let isFetchingLatestTimeline = CurrentValueSubject(false)
- let viewDidAppear = PassthroughSubject()
- let homeTimelineNavigationBarTitleViewModel: HomeTimelineNavigationBarTitleViewModel
-
- weak var tableNode: ASTableNode?
- weak var contentOffsetAdjustableTimelineViewControllerDelegate: ContentOffsetAdjustableTimelineViewControllerDelegate?
- //weak var tableView: UITableView?
- weak var timelineMiddleLoaderTableViewCellDelegate: TimelineMiddleLoaderTableViewCellDelegate?
-
- let timelineIsEmpty = CurrentValueSubject(false)
- let homeTimelineNeedRefresh = PassthroughSubject()
-
- // output
- var diffableDataSource: TableNodeDiffableDataSource?
-
- // 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(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(nil)
- // middle loader
- let loadMiddleSateMachineList = CurrentValueSubject<[NSManagedObjectID: GKStateMachine], Never>([:]) // TimelineIndex.objectID : middle loading state machine
- // var diffableDataSource: UITableViewDiffableDataSource?
- var cellFrameCache = NSCache()
-
-
- 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
diff --git a/Mastodon/Scene/Onboarding/ConfirmEmail/MastodonConfirmEmailViewController.swift b/Mastodon/Scene/Onboarding/ConfirmEmail/MastodonConfirmEmailViewController.swift
index 0718938f..585dcb31 100644
--- a/Mastodon/Scene/Onboarding/ConfirmEmail/MastodonConfirmEmailViewController.swift
+++ b/Mastodon/Scene/Onboarding/ConfirmEmail/MastodonConfirmEmailViewController.swift
@@ -46,21 +46,11 @@ final class MastodonConfirmEmailViewController: UIViewController, NeedsDependenc
imageView.contentMode = .scaleAspectFit
return imageView
}()
-
- let openEmailButton: UIButton = {
- let button = PrimaryActionButton()
- button.setTitle(L10n.Scene.ConfirmEmail.Button.openEmailApp, for: .normal)
- button.addTarget(self, action: #selector(openEmailButtonPressed(_:)), for: UIControl.Event.touchUpInside)
- return button
- }()
-
- let dontReceiveButton: UIButton = {
- let button = UIButton(type: .system)
- button.titleLabel?.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: UIFont.boldSystemFont(ofSize: 15))
- button.setTitleColor(Asset.Colors.brandBlue.color, for: .normal)
- button.setTitle(L10n.Scene.ConfirmEmail.Button.dontReceiveEmail, for: .normal)
- button.addTarget(self, action: #selector(dontReceiveButtonPressed(_:)), for: UIControl.Event.touchUpInside)
- return button
+
+ let navigationActionView: NavigationActionView = {
+ let navigationActionView = NavigationActionView()
+ navigationActionView.backgroundColor = Asset.Scene.Onboarding.onboardingBackground.color
+ return navigationActionView
}()
deinit {
@@ -73,6 +63,8 @@ extension MastodonConfirmEmailViewController {
override func viewDidLoad() {
+ navigationItem.leftBarButtonItem = UIBarButtonItem()
+
setupOnboardingAppearance()
configureTitleLabel()
configureMargin()
@@ -83,13 +75,12 @@ extension MastodonConfirmEmailViewController {
stackView.spacing = 10
stackView.layoutMargins = UIEdgeInsets(top: 10, left: 0, bottom: 23, right: 0)
stackView.isLayoutMarginsRelativeArrangement = true
- stackView.addArrangedSubview(self.largeTitleLabel)
- stackView.addArrangedSubview(self.subtitleLabel)
- stackView.addArrangedSubview(self.emailImageView)
+ stackView.addArrangedSubview(largeTitleLabel)
+ stackView.addArrangedSubview(subtitleLabel)
+ stackView.addArrangedSubview(emailImageView)
emailImageView.setContentHuggingPriority(.defaultLow, for: .vertical)
emailImageView.setContentCompressionResistancePriority(.defaultLow, for: .vertical)
- stackView.addArrangedSubview(self.openEmailButton)
- stackView.addArrangedSubview(self.dontReceiveButton)
+ stackView.addArrangedSubview(navigationActionView)
view.addSubview(stackView)
stackView.translatesAutoresizingMaskIntoConstraints = false
@@ -99,10 +90,7 @@ extension MastodonConfirmEmailViewController {
stackView.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor),
stackView.bottomAnchor.constraint(equalTo: view.readableContentGuide.bottomAnchor),
])
- NSLayoutConstraint.activate([
- self.openEmailButton.heightAnchor.constraint(equalToConstant: 46),
- ])
-
+
self.viewModel.timestampUpdatePublisher
.sink { [weak self] _ in
guard let self = self else { return }
@@ -140,6 +128,13 @@ extension MastodonConfirmEmailViewController {
.store(in: &self.disposeBag)
}
.store(in: &self.disposeBag)
+
+
+ navigationActionView.backButton.setTitle("Resend", for: .normal) // TODO: i18n
+ navigationActionView.backButton.addTarget(self, action: #selector(MastodonConfirmEmailViewController.resendButtonPressed(_:)), for: .touchUpInside)
+
+ navigationActionView.nextButton.setTitle(L10n.Scene.ConfirmEmail.Button.openEmailApp, for: .normal)
+ navigationActionView.nextButton.addTarget(self, action: #selector(MastodonConfirmEmailViewController.openEmailButtonPressed(_:)), for: .touchUpInside)
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
@@ -190,7 +185,7 @@ extension MastodonConfirmEmailViewController {
self.coordinator.present(scene: .alertController(alertController: alertController), from: self, transition: .alertController(animated: true, completion: nil))
}
- @objc private func dontReceiveButtonPressed(_ sender: UIButton) {
+ @objc private func resendButtonPressed(_ sender: UIButton) {
let alertController = UIAlertController(title: L10n.Scene.ConfirmEmail.DontReceiveEmail.title, message: L10n.Scene.ConfirmEmail.DontReceiveEmail.description, preferredStyle: .alert)
let resendAction = UIAlertAction(title: L10n.Scene.ConfirmEmail.DontReceiveEmail.resendEmail, style: .default) { _ in
let url = Mastodon.API.resendEmailURL(domain: self.viewModel.authenticateInfo.domain)
diff --git a/Mastodon/Scene/Onboarding/PickServer/CollectionViewCell/PickServerCategoryCollectionViewCell.swift b/Mastodon/Scene/Onboarding/PickServer/CollectionViewCell/PickServerCategoryCollectionViewCell.swift
index 9793d40f..89ca8267 100644
--- a/Mastodon/Scene/Onboarding/PickServer/CollectionViewCell/PickServerCategoryCollectionViewCell.swift
+++ b/Mastodon/Scene/Onboarding/PickServer/CollectionViewCell/PickServerCategoryCollectionViewCell.swift
@@ -8,14 +8,10 @@
import UIKit
class PickServerCategoryCollectionViewCell: UICollectionViewCell {
-
+
var observations = Set()
- var categoryView: PickServerCategoryView = {
- let view = PickServerCategoryView()
- view.translatesAutoresizingMaskIntoConstraints = false
- return view
- }()
+ var categoryView = PickServerCategoryView()
override func prepareForReuse() {
super.prepareForReuse()
@@ -35,13 +31,15 @@ class PickServerCategoryCollectionViewCell: UICollectionViewCell {
extension PickServerCategoryCollectionViewCell {
private func configure() {
- contentView.addSubview(categoryView)
+ backgroundColor = .clear
+ categoryView.translatesAutoresizingMaskIntoConstraints = false
+ contentView.addSubview(categoryView)
NSLayoutConstraint.activate([
+ categoryView.topAnchor.constraint(equalTo: contentView.topAnchor),
categoryView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
categoryView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
- categoryView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 10),
- contentView.bottomAnchor.constraint(equalTo: categoryView.bottomAnchor, constant: 10),
+ contentView.bottomAnchor.constraint(equalTo: categoryView.bottomAnchor),
])
}
}
diff --git a/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewController.swift b/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewController.swift
index f3570c6c..c4bbd5bc 100644
--- a/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewController.swift
+++ b/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewController.swift
@@ -14,6 +14,7 @@ import AuthenticationServices
final class MastodonPickServerViewController: UIViewController, NeedsDependency {
private var disposeBag = Set()
+ private var observations = Set()
private var tableViewObservation: NSKeyValueObservation?
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
@@ -31,21 +32,16 @@ final class MastodonPickServerViewController: UIViewController, NeedsDependency
private let emptyStateView = PickServerEmptyStateView()
private var emptyStateViewLeadingLayoutConstraint: NSLayoutConstraint!
private var emptyStateViewTrailingLayoutConstraint: NSLayoutConstraint!
- let tableViewTopPaddingView = UIView() // fix empty state view background display when tableView bounce scrolling
- var tableViewTopPaddingViewHeightLayoutConstraint: NSLayoutConstraint!
let tableView: UITableView = {
let tableView = ControlContainableTableView()
- tableView.register(PickServerTitleCell.self, forCellReuseIdentifier: String(describing: PickServerTitleCell.self))
- tableView.register(PickServerCategoriesCell.self, forCellReuseIdentifier: String(describing: PickServerCategoriesCell.self))
- tableView.register(PickServerSearchCell.self, forCellReuseIdentifier: String(describing: PickServerSearchCell.self))
+ tableView.register(OnboardingHeadlineTableViewCell.self, forCellReuseIdentifier: String(describing: OnboardingHeadlineTableViewCell.self))
tableView.register(PickServerCell.self, forCellReuseIdentifier: String(describing: PickServerCell.self))
tableView.register(PickServerLoaderTableViewCell.self, forCellReuseIdentifier: String(describing: PickServerLoaderTableViewCell.self))
tableView.rowHeight = UITableView.automaticDimension
tableView.separatorStyle = .none
tableView.backgroundColor = .clear
tableView.keyboardDismissMode = .onDrag
- tableView.translatesAutoresizingMaskIntoConstraints = false
if #available(iOS 15.0, *) {
tableView.sectionHeaderTopPadding = .leastNonzeroMagnitude
} else {
@@ -54,14 +50,11 @@ final class MastodonPickServerViewController: UIViewController, NeedsDependency
return tableView
}()
- let buttonContainer = UIView()
- let nextStepButton: PrimaryActionButton = {
- let button = PrimaryActionButton()
- button.setTitle(L10n.Common.Controls.Actions.signUp, for: .normal)
- button.translatesAutoresizingMaskIntoConstraints = false
- return button
+ let navigationActionView: NavigationActionView = {
+ let navigationActionView = NavigationActionView()
+ navigationActionView.backgroundColor = Asset.Scene.Onboarding.onboardingBackground.color
+ return navigationActionView
}()
- var buttonContainerBottomLayoutConstraint: NSLayoutConstraint!
var mastodonAuthenticationController: MastodonAuthenticationController?
@@ -72,16 +65,15 @@ final class MastodonPickServerViewController: UIViewController, NeedsDependency
}
-extension MastodonPickServerViewController {
-
+extension MastodonPickServerViewController {
override func viewDidLoad() {
super.viewDidLoad()
+ navigationItem.leftBarButtonItem = UIBarButtonItem()
+
setupOnboardingAppearance()
defer { setupNavigationBarBackgroundView() }
- configureTitleLabel()
- configureMargin()
#if DEBUG
navigationItem.rightBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "ellipsis.circle"), style: .plain, target: nil, action: nil)
@@ -94,26 +86,35 @@ extension MastodonPickServerViewController {
navigationItem.rightBarButtonItem?.menu = UIMenu(title: "Debug Tool", image: nil, identifier: nil, options: [], children: children)
#endif
- buttonContainer.translatesAutoresizingMaskIntoConstraints = false
- buttonContainer.preservesSuperviewLayoutMargins = true
- view.addSubview(buttonContainer)
- buttonContainerBottomLayoutConstraint = view.bottomAnchor.constraint(equalTo: buttonContainer.bottomAnchor, constant: 0).priority(.defaultHigh)
+ tableView.translatesAutoresizingMaskIntoConstraints = false
+ view.addSubview(tableView)
NSLayoutConstraint.activate([
- buttonContainer.leadingAnchor.constraint(equalTo: view.leadingAnchor),
- buttonContainer.trailingAnchor.constraint(equalTo: view.trailingAnchor),
- view.safeAreaLayoutGuide.bottomAnchor.constraint(greaterThanOrEqualTo: buttonContainer.bottomAnchor, constant: WelcomeViewController.viewBottomPaddingHeight),
- buttonContainerBottomLayoutConstraint,
+ tableView.topAnchor.constraint(equalTo: view.topAnchor),
+ tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
+ tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
+ tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
])
- view.addSubview(nextStepButton)
+ navigationActionView.translatesAutoresizingMaskIntoConstraints = false
+ view.addSubview(navigationActionView)
+ defer {
+ view.bringSubviewToFront(navigationActionView)
+ }
NSLayoutConstraint.activate([
- nextStepButton.topAnchor.constraint(equalTo: buttonContainer.topAnchor),
- nextStepButton.leadingAnchor.constraint(equalTo: buttonContainer.layoutMarginsGuide.leadingAnchor),
- buttonContainer.layoutMarginsGuide.trailingAnchor.constraint(equalTo: nextStepButton.trailingAnchor),
- nextStepButton.bottomAnchor.constraint(equalTo: buttonContainer.bottomAnchor),
- nextStepButton.heightAnchor.constraint(equalToConstant: MastodonPickServerViewController.actionButtonHeight).priority(.defaultHigh),
+ navigationActionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
+ navigationActionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
+ view.bottomAnchor.constraint(equalTo: navigationActionView.bottomAnchor),
])
-
+
+ navigationActionView
+ .observe(\.bounds, options: [.initial, .new]) { [weak self] navigationActionView, _ in
+ guard let self = self else { return }
+ let inset = navigationActionView.frame.height
+ self.tableView.contentInset.bottom = inset
+ }
+ .store(in: &observations)
+
+
// fix AutoLayout warning when observe before view appear
viewModel.viewWillAppear
.receive(on: DispatchQueue.main)
@@ -125,26 +126,7 @@ extension MastodonPickServerViewController {
}
}
.store(in: &disposeBag)
-
- tableViewTopPaddingView.translatesAutoresizingMaskIntoConstraints = false
- view.addSubview(tableViewTopPaddingView)
- tableViewTopPaddingViewHeightLayoutConstraint = tableViewTopPaddingView.heightAnchor.constraint(equalToConstant: 0.0).priority(.defaultHigh)
- NSLayoutConstraint.activate([
- tableViewTopPaddingView.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor),
- tableViewTopPaddingView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
- tableViewTopPaddingView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
- tableViewTopPaddingViewHeightLayoutConstraint,
- ])
- tableViewTopPaddingView.backgroundColor = Asset.Theme.Mastodon.systemGroupedBackground.color
-
- view.addSubview(tableView)
- NSLayoutConstraint.activate([
- tableView.topAnchor.constraint(equalTo: view.topAnchor),
- tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
- tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
- buttonContainer.topAnchor.constraint(equalTo: tableView.bottomAnchor, constant: 7),
- ])
-
+
emptyStateView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(emptyStateView)
emptyStateViewLeadingLayoutConstraint = emptyStateView.leadingAnchor.constraint(equalTo: tableView.leadingAnchor)
@@ -153,64 +135,24 @@ extension MastodonPickServerViewController {
emptyStateView.topAnchor.constraint(equalTo: view.topAnchor),
emptyStateViewLeadingLayoutConstraint,
emptyStateViewTrailingLayoutConstraint,
- buttonContainer.topAnchor.constraint(equalTo: emptyStateView.bottomAnchor, constant: 21),
+ navigationActionView.topAnchor.constraint(equalTo: emptyStateView.bottomAnchor, constant: 21),
])
view.sendSubviewToBack(emptyStateView)
-
- // update layout when keyboard show/dismiss
- let keyboardEventPublishers = Publishers.CombineLatest3(
- KeyboardResponderService.shared.isShow,
- KeyboardResponderService.shared.state,
- KeyboardResponderService.shared.endFrame
- )
-
- keyboardEventPublishers
- .sink { [weak self] keyboardEvents in
- guard let self = self else { return }
- let (isShow, state, endFrame) = keyboardEvents
-
- // guard external keyboard connected
- guard isShow, state == .dock, GCKeyboard.coalesced != nil else {
- self.buttonContainerBottomLayoutConstraint.constant = WelcomeViewController.viewBottomPaddingHeight
- return
- }
-
- let externalKeyboardToolbarHeight = self.view.frame.maxY - endFrame.minY
- guard externalKeyboardToolbarHeight > 0 else {
- self.buttonContainerBottomLayoutConstraint.constant = WelcomeViewController.viewBottomPaddingHeight
- return
- }
-
- UIView.animate(withDuration: 0.3) {
- self.buttonContainerBottomLayoutConstraint.constant = externalKeyboardToolbarHeight + 16
- self.view.layoutIfNeeded()
- }
- }
- .store(in: &disposeBag)
-
- switch viewModel.mode {
- case .signIn:
- nextStepButton.setTitle(L10n.Common.Controls.Actions.signIn, for: .normal)
- case .signUp:
- nextStepButton.setTitle(L10n.Common.Controls.Actions.continue, for: .normal)
- }
- nextStepButton.addTarget(self, action: #selector(nextStepButtonDidClicked(_:)), for: .touchUpInside)
-
+
tableView.delegate = self
viewModel.setupDiffableDataSource(
for: tableView,
dependency: self,
- pickServerCategoriesCellDelegate: self,
- pickServerSearchCellDelegate: self,
+ pickServerServerSectionTableHeaderViewDelegate: self,
pickServerCellDelegate: self
)
-
+
viewModel
.selectedServer
.map { $0 != nil }
- .assign(to: \.isEnabled, on: nextStepButton)
+ .assign(to: \.isEnabled, on: navigationActionView.nextButton)
.store(in: &disposeBag)
-
+
Publishers.Merge(
viewModel.error,
authenticationViewModel.error
@@ -229,7 +171,7 @@ extension MastodonPickServerViewController {
)
}
.store(in: &disposeBag)
-
+
authenticationViewModel
.authenticated
.flatMap { [weak self] (domain, user) -> AnyPublisher, Never> in
@@ -249,17 +191,17 @@ extension MastodonPickServerViewController {
}
}
.store(in: &disposeBag)
-
+
authenticationViewModel.isAuthenticating
.receive(on: DispatchQueue.main)
.sink { [weak self] isAuthenticating in
guard let self = self else { return }
- isAuthenticating ? self.nextStepButton.showLoading() : self.nextStepButton.stopLoading()
+ isAuthenticating ? self.navigationActionView.nextButton.showLoading() : self.navigationActionView.nextButton.stopLoading()
}
.store(in: &disposeBag)
-
+
viewModel.emptyStateViewState
- .receive(on: RunLoop.main)
+ .receive(on: DispatchQueue.main)
.sink { [weak self] state in
guard let self = self else { return }
switch state {
@@ -284,6 +226,9 @@ extension MastodonPickServerViewController {
}
}
.store(in: &disposeBag)
+
+ navigationActionView.backButton.addTarget(self, action: #selector(MastodonPickServerViewController.backButtonDidPressed(_:)), for: .touchUpInside)
+ navigationActionView.nextButton.addTarget(self, action: #selector(MastodonPickServerViewController.nextButtonDidPressed(_:)), for: .touchUpInside)
}
override func viewWillAppear(_ animated: Bool) {
@@ -291,43 +236,31 @@ extension MastodonPickServerViewController {
viewModel.viewWillAppear.send()
}
+ override func viewDidAppear(_ animated: Bool) {
+ super.viewDidAppear(animated)
+
+ tableView.flashScrollIndicators()
+ }
+
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
setupNavigationBarAppearance()
updateEmptyStateViewLayout()
- configureTitleLabel()
- configureMargin()
}
}
-extension MastodonPickServerViewController {
- private func configureTitleLabel() {
- guard UIDevice.current.userInterfaceIdiom == .pad else {
- return
- }
-
- switch traitCollection.horizontalSizeClass {
- case .regular:
- navigationItem.largeTitleDisplayMode = .always
- navigationItem.title = L10n.Scene.ServerPicker.title.replacingOccurrences(of: "\n", with: " ")
- default:
- navigationItem.largeTitleDisplayMode = .never
- navigationItem.title = nil
- }
- }
-}
-
extension MastodonPickServerViewController {
- @objc
- private func nextStepButtonDidClicked(_ sender: UIButton) {
+ @objc private func backButtonDidPressed(_ sender: UIButton) {
+ navigationController?.popViewController(animated: true)
+ }
+
+ @objc private func nextButtonDidPressed(_ sender: UIButton) {
switch viewModel.mode {
- case .signIn:
- doSignIn()
- case .signUp:
- doSignUp()
+ case .signIn: doSignIn()
+ case .signUp: doSignUp()
}
}
@@ -442,8 +375,8 @@ extension MastodonPickServerViewController {
self.coordinator.present(scene: .mastodonServerRules(viewModel: mastodonServerRulesViewModel), from: self, transition: .show)
} else {
let mastodonRegisterViewModel = MastodonRegisterViewModel(
- domain: server.domain,
context: self.context,
+ domain: server.domain,
authenticateInfo: response.authenticateInfo,
instance: response.instance.value,
applicationToken: response.applicationToken.value
@@ -458,16 +391,6 @@ extension MastodonPickServerViewController {
// MARK: - UITableViewDelegate
extension MastodonPickServerViewController: UITableViewDelegate {
- func scrollViewDidScroll(_ scrollView: UIScrollView) {
- guard scrollView === tableView else { return }
- let offsetY = scrollView.contentOffset.y + scrollView.safeAreaInsets.top
- if offsetY < 0 {
- tableViewTopPaddingViewHeightLayoutConstraint.constant = abs(offsetY)
- } else {
- tableViewTopPaddingViewHeightLayoutConstraint.constant = 0
- }
- }
-
func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
guard let diffableDataSource = viewModel.diffableDataSource else { return nil }
guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return nil }
@@ -500,87 +423,89 @@ extension MastodonPickServerViewController: UITableViewDelegate {
guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return }
switch item {
- case .categoryPicker:
- guard let cell = cell as? PickServerCategoriesCell else { return }
- guard let diffableDataSource = cell.diffableDataSource else { return }
- let snapshot = diffableDataSource.snapshot()
-
- let item = viewModel.selectCategoryItem.value
- guard let section = snapshot.indexOfSection(.main),
- let row = snapshot.indexOfItem(item) else { return }
- cell.collectionView.selectItem(at: IndexPath(item: row, section: section), animated: false, scrollPosition: .centeredHorizontally)
- case .search:
- guard let cell = cell as? PickServerSearchCell else { return }
- cell.searchTextField.text = viewModel.searchText.value
+// case .categoryPicker:
+// guard let cell = cell as? PickServerCategoriesCell else { return }
+// guard let diffableDataSource = cell.diffableDataSource else { return }
+// let snapshot = diffableDataSource.snapshot()
+//
+// let item = viewModel.selectCategoryItem.value
+// guard let section = snapshot.indexOfSection(.main),
+// let row = snapshot.indexOfItem(item) else { return }
+// cell.collectionView.selectItem(at: IndexPath(item: row, section: section), animated: false, scrollPosition: .centeredHorizontally)
+// case .search:
+// guard let cell = cell as? PickServerSearchCell else { return }
+// cell.searchTextField.text = viewModel.searchText.value
default:
break
}
}
+ func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
+ guard let diffableDataSource = viewModel.diffableDataSource else { return nil }
+ let snapshot = diffableDataSource.snapshot()
+ guard section < snapshot.numberOfSections else { return nil }
+ let section = snapshot.sectionIdentifiers[section]
+
+ switch section {
+ case .servers:
+ return viewModel.serverSectionHeaderView
+ default:
+ return UIView()
+ }
+ }
+
+ func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
+ guard let diffableDataSource = viewModel.diffableDataSource else { return .leastNonzeroMagnitude }
+ let snapshot = diffableDataSource.snapshot()
+ guard section < snapshot.numberOfSections else { return .leastNonzeroMagnitude }
+ let section = snapshot.sectionIdentifiers[section]
+
+ switch section {
+ case .servers:
+ return PickServerServerSectionTableHeaderView.height
+ default:
+ return .leastNonzeroMagnitude
+ }
+ }
+
}
extension MastodonPickServerViewController {
private func updateEmptyStateViewLayout() {
- guard let diffableDataSource = self.viewModel.diffableDataSource else { return }
- guard let indexPath = diffableDataSource.indexPath(for: .search) else { return }
- let rectInTableView = tableView.rectForRow(at: indexPath)
-
- emptyStateView.topPaddingViewTopLayoutConstraint.constant = rectInTableView.maxY
-
- switch traitCollection.horizontalSizeClass {
- case .regular:
- emptyStateViewLeadingLayoutConstraint.constant = MastodonPickServerViewController.viewEdgeMargin
- emptyStateViewTrailingLayoutConstraint.constant = MastodonPickServerViewController.viewEdgeMargin
- default:
- let margin = tableView.layoutMarginsGuide.layoutFrame.origin.x
- emptyStateViewLeadingLayoutConstraint.constant = margin
- emptyStateViewTrailingLayoutConstraint.constant = margin
- }
- }
-
- private func configureMargin() {
- switch traitCollection.horizontalSizeClass {
- case .regular:
- let margin = MastodonPickServerViewController.viewEdgeMargin
- buttonContainer.layoutMargins = UIEdgeInsets(top: 0, left: margin, bottom: 0, right: margin)
- default:
- buttonContainer.layoutMargins = .zero
- }
+// guard let diffableDataSource = self.viewModel.diffableDataSource else { return }
+// guard let indexPath = diffableDataSource.indexPath(for: .search) else { return }
+// let rectInTableView = tableView.rectForRow(at: indexPath)
+//
+// emptyStateView.topPaddingViewTopLayoutConstraint.constant = rectInTableView.maxY
+//
+// switch traitCollection.horizontalSizeClass {
+// case .regular:
+// emptyStateViewLeadingLayoutConstraint.constant = MastodonPickServerViewController.viewEdgeMargin
+// emptyStateViewTrailingLayoutConstraint.constant = MastodonPickServerViewController.viewEdgeMargin
+// default:
+// let margin = tableView.layoutMarginsGuide.layoutFrame.origin.x
+// emptyStateViewLeadingLayoutConstraint.constant = margin
+// emptyStateViewTrailingLayoutConstraint.constant = margin
+// }
}
}
-// MARK: - PickServerCategoriesCellDelegate
-extension MastodonPickServerViewController: PickServerCategoriesCellDelegate {
- func pickServerCategoriesCell(_ cell: PickServerCategoriesCell, collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
- guard let diffableDataSource = cell.diffableDataSource else { return }
+// MARK: - PickServerServerSectionTableHeaderViewDelegate
+extension MastodonPickServerViewController: PickServerServerSectionTableHeaderViewDelegate {
+ func pickServerServerSectionTableHeaderView(_ headerView: PickServerServerSectionTableHeaderView, collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
+ guard let diffableDataSource = headerView.diffableDataSource else { return }
let item = diffableDataSource.itemIdentifier(for: indexPath)
viewModel.selectCategoryItem.value = item ?? .all
}
-}
-
-// MARK: - PickServerSearchCellDelegate
-extension MastodonPickServerViewController: PickServerSearchCellDelegate {
- func pickServerSearchCell(_ cell: PickServerSearchCell, searchTextDidChange searchText: String?) {
+
+ func pickServerServerSectionTableHeaderView(_ headerView: PickServerServerSectionTableHeaderView, searchTextDidChange searchText: String?) {
viewModel.searchText.send(searchText ?? "")
}
}
// MARK: - PickServerCellDelegate
extension MastodonPickServerViewController: PickServerCellDelegate {
- func pickServerCell(_ cell: PickServerCell, expandButtonPressed 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 }
- guard case let .server(_, attribute) = item else { return }
-
- attribute.isExpand.toggle()
- tableView.beginUpdates()
- cell.updateExpandMode(mode: attribute.isExpand ? .expand : .collapse)
- tableView.endUpdates()
-
- // expand attribute change do not needs apply snapshot to diffable data source
- // but should I block the viewModel data binding during tableView.beginUpdates/endUpdates?
- }
+
}
// MARK: - OnboardingViewControllerAppearance
diff --git a/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewModel+Diffable.swift b/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewModel+Diffable.swift
index 9da0399e..35de40b8 100644
--- a/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewModel+Diffable.swift
+++ b/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewModel+Diffable.swift
@@ -6,32 +6,105 @@
//
import UIKit
+import Combine
extension MastodonPickServerViewModel {
func setupDiffableDataSource(
for tableView: UITableView,
dependency: NeedsDependency,
- pickServerCategoriesCellDelegate: PickServerCategoriesCellDelegate,
- pickServerSearchCellDelegate: PickServerSearchCellDelegate,
+ pickServerServerSectionTableHeaderViewDelegate: PickServerServerSectionTableHeaderViewDelegate,
pickServerCellDelegate: PickServerCellDelegate
) {
+ // set section header
+ serverSectionHeaderView.diffableDataSource = CategoryPickerSection.collectionViewDiffableDataSource(
+ for: serverSectionHeaderView.collectionView,
+ dependency: dependency
+ )
+ var sectionHeaderSnapshot = NSDiffableDataSourceSnapshot()
+ sectionHeaderSnapshot.appendSections([.main])
+ sectionHeaderSnapshot.appendItems(categoryPickerItems, toSection: .main)
+ serverSectionHeaderView.delegate = pickServerServerSectionTableHeaderViewDelegate
+ serverSectionHeaderView.diffableDataSource?.applySnapshot(sectionHeaderSnapshot, animated: false) { [weak self] in
+ guard let self = self else { return }
+ guard let indexPath = self.serverSectionHeaderView.diffableDataSource?.indexPath(for: .all) else { return }
+ self.serverSectionHeaderView.collectionView.selectItem(at: indexPath, animated: false, scrollPosition: .centeredHorizontally)
+ }
+
+ // set tableView
diffableDataSource = PickServerSection.tableViewDiffableDataSource(
for: tableView,
dependency: dependency,
- pickServerCategoriesCellDelegate: pickServerCategoriesCellDelegate,
- pickServerSearchCellDelegate: pickServerSearchCellDelegate,
pickServerCellDelegate: pickServerCellDelegate
)
var snapshot = NSDiffableDataSourceSnapshot()
- snapshot.appendSections([.header, .category, .search, .servers])
+ snapshot.appendSections([.header, .servers])
snapshot.appendItems([.header], toSection: .header)
- snapshot.appendItems([.categoryPicker(items: categoryPickerItems)], toSection: .category)
- snapshot.appendItems([.search], toSection: .search)
diffableDataSource?.apply(snapshot, animatingDifferences: false, completion: nil)
loadIndexedServerStateMachine.enter(LoadIndexedServerState.Loading.self)
+
+ Publishers.CombineLatest(
+ filteredIndexedServers,
+ unindexedServers
+ )
+ .receive(on: DispatchQueue.main)
+ .sink(receiveValue: { [weak self] indexedServers, unindexedServers in
+ guard let self = self else { return }
+ guard let diffableDataSource = self.diffableDataSource else { return }
+
+ let oldSnapshot = diffableDataSource.snapshot()
+ var oldSnapshotServerItemAttributeDict: [String : PickServerItem.ServerItemAttribute] = [:]
+ for item in oldSnapshot.itemIdentifiers {
+ guard case let .server(server, attribute) = item else { continue }
+ oldSnapshotServerItemAttributeDict[server.domain] = attribute
+ }
+
+ var snapshot = NSDiffableDataSourceSnapshot()
+ snapshot.appendSections([.header, .servers])
+ snapshot.appendItems([.header], toSection: .header)
+
+ // TODO: handle filter
+ var serverItems: [PickServerItem] = []
+ for server in indexedServers {
+ let attribute = oldSnapshotServerItemAttributeDict[server.domain] ?? PickServerItem.ServerItemAttribute(isLast: false, isExpand: false)
+ attribute.isLast.value = false
+ let item = PickServerItem.server(server: server, attribute: attribute)
+ guard !serverItems.contains(item) else { continue }
+ serverItems.append(item)
+ }
+
+ if let unindexedServers = unindexedServers {
+ if !unindexedServers.isEmpty {
+ for server in unindexedServers {
+ let attribute = oldSnapshotServerItemAttributeDict[server.domain] ?? PickServerItem.ServerItemAttribute(isLast: false, isExpand: false)
+ attribute.isLast.value = false
+ let item = PickServerItem.server(server: server, attribute: attribute)
+ guard !serverItems.contains(item) else { continue }
+ serverItems.append(item)
+ }
+ } else {
+ if indexedServers.isEmpty && !self.isLoadingIndexedServers.value {
+ serverItems.append(.loader(attribute: PickServerItem.LoaderItemAttribute(isLast: false, isEmptyResult: true)))
+ }
+ }
+ } else {
+ serverItems.append(.loader(attribute: PickServerItem.LoaderItemAttribute(isLast: false, isEmptyResult: false)))
+ }
+
+ if case let .server(_, attribute) = serverItems.last {
+ attribute.isLast.value = true
+ }
+ if case let .loader(attribute) = serverItems.last {
+ attribute.isLast = true
+ }
+ snapshot.appendItems(serverItems, toSection: .servers)
+
+ diffableDataSource.defaultRowAnimation = .fade
+ diffableDataSource.apply(snapshot, animatingDifferences: true, completion: nil)
+ })
+ .store(in: &disposeBag)
}
}
diff --git a/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewModel.swift b/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewModel.swift
index 7a648011..af38b110 100644
--- a/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewModel.swift
+++ b/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewModel.swift
@@ -12,6 +12,7 @@ import GameplayKit
import MastodonSDK
import CoreDataStack
import OrderedCollections
+import Tabman
class MastodonPickServerViewModel: NSObject {
@@ -27,6 +28,8 @@ class MastodonPickServerViewModel: NSObject {
}
var disposeBag = Set()
+
+ let serverSectionHeaderView = PickServerServerSectionTableHeaderView()
// input
let mode: PickServerMode
@@ -82,68 +85,6 @@ class MastodonPickServerViewModel: NSObject {
extension MastodonPickServerViewModel {
private func configure() {
- Publishers.CombineLatest(
- filteredIndexedServers,
- unindexedServers
- )
- .receive(on: DispatchQueue.main)
- .sink(receiveValue: { [weak self] indexedServers, unindexedServers in
- guard let self = self else { return }
- guard let diffableDataSource = self.diffableDataSource else { return }
-
- let oldSnapshot = diffableDataSource.snapshot()
- var oldSnapshotServerItemAttributeDict: [String : PickServerItem.ServerItemAttribute] = [:]
- for item in oldSnapshot.itemIdentifiers {
- guard case let .server(server, attribute) = item else { continue }
- oldSnapshotServerItemAttributeDict[server.domain] = attribute
- }
-
- var snapshot = NSDiffableDataSourceSnapshot()
- snapshot.appendSections([.header, .category, .search, .servers])
- snapshot.appendItems([.header], toSection: .header)
- snapshot.appendItems([.categoryPicker(items: self.categoryPickerItems)], toSection: .category)
- snapshot.appendItems([.search], toSection: .search)
- // TODO: handle filter
- var serverItems: [PickServerItem] = []
- for server in indexedServers {
- let attribute = oldSnapshotServerItemAttributeDict[server.domain] ?? PickServerItem.ServerItemAttribute(isLast: false, isExpand: false)
- attribute.isLast.value = false
- let item = PickServerItem.server(server: server, attribute: attribute)
- guard !serverItems.contains(item) else { continue }
- serverItems.append(item)
- }
-
- if let unindexedServers = unindexedServers {
- if !unindexedServers.isEmpty {
- for server in unindexedServers {
- let attribute = oldSnapshotServerItemAttributeDict[server.domain] ?? PickServerItem.ServerItemAttribute(isLast: false, isExpand: false)
- attribute.isLast.value = false
- let item = PickServerItem.server(server: server, attribute: attribute)
- guard !serverItems.contains(item) else { continue }
- serverItems.append(item)
- }
- } else {
- if indexedServers.isEmpty && !self.isLoadingIndexedServers.value {
- serverItems.append(.loader(attribute: PickServerItem.LoaderItemAttribute(isLast: false, isEmptyResult: true)))
- }
- }
- } else {
- serverItems.append(.loader(attribute: PickServerItem.LoaderItemAttribute(isLast: false, isEmptyResult: false)))
- }
-
- if case let .server(_, attribute) = serverItems.last {
- attribute.isLast.value = true
- }
- if case let .loader(attribute) = serverItems.last {
- attribute.isLast = true
- }
- snapshot.appendItems(serverItems, toSection: .servers)
-
- diffableDataSource.defaultRowAnimation = .fade
- diffableDataSource.apply(snapshot, animatingDifferences: true, completion: nil)
- })
- .store(in: &disposeBag)
-
Publishers.CombineLatest(
isLoadingIndexedServers,
loadingIndexedServersError
@@ -301,3 +242,12 @@ extension MastodonPickServerViewModel {
let applicationToken: Mastodon.Response.Content
}
}
+
+// MARK: - TMBarDataSource
+extension MastodonPickServerViewModel: TMBarDataSource {
+ func barItem(for bar: TMBar, at index: Int) -> TMBarItemable {
+ let item = categoryPickerItems[index]
+ let barItem = TMBarItem(title: item.title)
+ return barItem
+ }
+}
diff --git a/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerCategoriesCell.swift b/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerCategoriesCell.swift
deleted file mode 100644
index 65931775..00000000
--- a/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerCategoriesCell.swift
+++ /dev/null
@@ -1,145 +0,0 @@
-//
-// PickServerCategoriesCell.swift
-// Mastodon
-//
-// Created by BradGao on 2021/2/23.
-//
-
-import os.log
-import UIKit
-import MastodonSDK
-
-protocol PickServerCategoriesCellDelegate: AnyObject {
- func pickServerCategoriesCell(_ cell: PickServerCategoriesCell, collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
-}
-
-final class PickServerCategoriesCell: UITableViewCell {
-
- weak var delegate: PickServerCategoriesCellDelegate?
-
- var diffableDataSource: UICollectionViewDiffableDataSource?
-
- let metricView = UIView()
-
- let collectionView: UICollectionView = {
- let flowLayout = UICollectionViewFlowLayout()
- flowLayout.scrollDirection = .horizontal
- let view = ControlContainableCollectionView(frame: .zero, collectionViewLayout: flowLayout)
- view.register(PickServerCategoryCollectionViewCell.self, forCellWithReuseIdentifier: String(describing: PickServerCategoryCollectionViewCell.self))
- view.backgroundColor = .clear
- view.showsHorizontalScrollIndicator = false
- view.showsVerticalScrollIndicator = false
- view.layer.masksToBounds = false
- view.translatesAutoresizingMaskIntoConstraints = false
- return view
- }()
-
- override func prepareForReuse() {
- super.prepareForReuse()
-
- delegate = nil
- }
-
- override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
- super.init(style: style, reuseIdentifier: reuseIdentifier)
- _init()
- }
-
- required init?(coder: NSCoder) {
- super.init(coder: coder)
- _init()
- }
-}
-
-extension PickServerCategoriesCell {
-
- private func _init() {
- selectionStyle = .none
- backgroundColor = Asset.Theme.Mastodon.systemGroupedBackground.color
- configureMargin()
-
- metricView.translatesAutoresizingMaskIntoConstraints = false
- contentView.addSubview(metricView)
- NSLayoutConstraint.activate([
- metricView.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor),
- metricView.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor),
- metricView.topAnchor.constraint(equalTo: contentView.topAnchor),
- metricView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
- metricView.heightAnchor.constraint(equalToConstant: 80).priority(.defaultHigh),
- ])
-
- contentView.addSubview(collectionView)
- NSLayoutConstraint.activate([
- collectionView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
- collectionView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
- collectionView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 10),
- contentView.bottomAnchor.constraint(equalTo: collectionView.bottomAnchor, constant: 20),
- collectionView.heightAnchor.constraint(equalToConstant: 80).priority(.defaultHigh),
- ])
-
- collectionView.delegate = self
- }
-
- override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
- super.traitCollectionDidChange(previousTraitCollection)
-
- configureMargin()
- }
-
- override func layoutSubviews() {
- super.layoutSubviews()
-
- collectionView.collectionViewLayout.invalidateLayout()
- }
-
-}
-
-extension PickServerCategoriesCell {
- private func configureMargin() {
- switch traitCollection.horizontalSizeClass {
- case .regular:
- let margin = MastodonPickServerViewController.viewEdgeMargin
- contentView.layoutMargins = UIEdgeInsets(top: 0, left: margin, bottom: 0, right: margin)
- default:
- contentView.layoutMargins = .zero
- }
- }
-}
-
-// MARK: - UICollectionViewDelegateFlowLayout
-extension PickServerCategoriesCell: UICollectionViewDelegateFlowLayout {
-
- func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
- os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: indexPath: %s", ((#file as NSString).lastPathComponent), #line, #function, indexPath.debugDescription)
- collectionView.selectItem(at: indexPath, animated: true, scrollPosition: .centeredHorizontally)
- delegate?.pickServerCategoriesCell(self, collectionView: collectionView, didSelectItemAt: indexPath)
- }
-
- func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
- layoutIfNeeded()
- return UIEdgeInsets(top: 0, left: metricView.frame.minX - collectionView.frame.minX, bottom: 0, right: collectionView.frame.maxX - metricView.frame.maxX)
- }
-
- func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
- return 16
- }
-
- func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
- return CGSize(width: 60, height: 80)
- }
-
-}
-
-extension PickServerCategoriesCell {
-
- override func accessibilityElementCount() -> Int {
- guard let diffableDataSource = diffableDataSource else { return 0 }
- return diffableDataSource.snapshot().itemIdentifiers.count
- }
-
- override func accessibilityElement(at index: Int) -> Any? {
- guard let item = collectionView.cellForItem(at: IndexPath(item: index, section: 0)) else { return nil }
- return item
- }
-
-}
diff --git a/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerCell.swift b/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerCell.swift
index 2f60a520..6dd0f197 100644
--- a/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerCell.swift
+++ b/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerCell.swift
@@ -13,7 +13,7 @@ import AlamofireImage
import Kanna
protocol PickServerCellDelegate: AnyObject {
- func pickServerCell(_ cell: PickServerCell, expandButtonPressed button: UIButton)
+// func pickServerCell(_ cell: PickServerCell, expandButtonPressed button: UIButton)
}
class PickServerCell: UITableViewCell {
@@ -21,20 +21,17 @@ class PickServerCell: UITableViewCell {
weak var delegate: PickServerCellDelegate?
var disposeBag = Set()
-
- let expandMode = CurrentValueSubject(.collapse)
-
- let containerView: UIView = {
- let view = UIView()
- view.layoutMargins = UIEdgeInsets(top: 16, left: 16, bottom: 10, right: 16)
- view.backgroundColor = Asset.Theme.Mastodon.secondaryGroupedSystemBackground.color
- view.translatesAutoresizingMaskIntoConstraints = false
+
+ let containerView: UIStackView = {
+ let view = UIStackView()
+ view.axis = .vertical
+ view.spacing = 4
return view
}()
let domainLabel: UILabel = {
let label = UILabel()
- label.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 17, weight: .semibold), maximumPointSize: 22)
+ label.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 20, weight: .semibold))
label.textColor = Asset.Colors.Label.primary.color
label.adjustsFontForContentSizeCategory = true
label.translatesAutoresizingMaskIntoConstraints = false
@@ -52,7 +49,7 @@ class PickServerCell: UITableViewCell {
let descriptionLabel: UILabel = {
let label = UILabel()
- label.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 15, weight: .regular))
+ label.font = UIFontMetrics(forTextStyle: .caption1).scaledFont(for: .systemFont(ofSize: 13, weight: .regular))
label.numberOfLines = 0
label.textColor = Asset.Colors.Label.primary.color
label.adjustsFontForContentSizeCategory = true
@@ -60,112 +57,33 @@ class PickServerCell: UITableViewCell {
return label
}()
- let thumbnailActivityIndicator = UIActivityIndicatorView(style: .medium)
-
- let thumbnailImageView: UIImageView = {
- let imageView = UIImageView()
- imageView.clipsToBounds = true
- imageView.contentMode = .scaleAspectFill
- imageView.translatesAutoresizingMaskIntoConstraints = false
- return imageView
- }()
-
let infoStackView: UIStackView = {
let stackView = UIStackView()
stackView.axis = .horizontal
- stackView.alignment = .fill
- stackView.distribution = .fillEqually
- stackView.translatesAutoresizingMaskIntoConstraints = false
+ stackView.spacing = 16
return stackView
}()
- let expandBox: UIView = {
- let view = UIView()
- view.backgroundColor = .clear
- view.translatesAutoresizingMaskIntoConstraints = false
- return view
- }()
-
- let expandButton: UIButton = {
- let button = HitTestExpandedButton(type: .custom)
- button.setImage(UIImage(systemName: "chevron.down", withConfiguration: UIImage.SymbolConfiguration(pointSize: 13)), for: .normal)
- button.setTitle(L10n.Scene.ServerPicker.Button.seeMore, for: .normal)
- button.setTitleColor(Asset.Colors.brandBlue.color, for: .normal)
- button.titleLabel?.font = .systemFont(ofSize: 13, weight: .regular)
- button.translatesAutoresizingMaskIntoConstraints = false
- button.imageView?.transform = CGAffineTransform(scaleX: -1, y: 1)
- button.titleLabel?.transform = CGAffineTransform(scaleX: -1, y: 1)
- button.transform = CGAffineTransform(scaleX: -1, y: 1)
- return button
- }()
-
let separator: UIView = {
let view = UIView()
- view.backgroundColor = Asset.Theme.Mastodon.systemGroupedBackground.color
- view.translatesAutoresizingMaskIntoConstraints = false
+ view.backgroundColor = Asset.Theme.System.separator.color
return view
}()
let langValueLabel: UILabel = {
let label = UILabel()
label.textColor = Asset.Colors.Label.primary.color
- label.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 22, weight: .semibold), maximumPointSize: 27)
+ label.font = UIFontMetrics(forTextStyle: .caption1).scaledFont(for: .systemFont(ofSize: 12, weight: .regular))
label.textAlignment = .center
label.adjustsFontForContentSizeCategory = true
- label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
let usersValueLabel: UILabel = {
let label = UILabel()
label.textColor = Asset.Colors.Label.primary.color
- label.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 22, weight: .semibold), maximumPointSize: 27)
- label.textAlignment = .center
+ label.font = UIFontMetrics(forTextStyle: .caption1).scaledFont(for: .systemFont(ofSize: 12, weight: .regular))
label.adjustsFontForContentSizeCategory = true
- label.translatesAutoresizingMaskIntoConstraints = false
- return label
- }()
-
- let categoryValueLabel: UILabel = {
- let label = UILabel()
- label.textColor = Asset.Colors.Label.primary.color
- label.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 22, weight: .semibold), maximumPointSize: 27)
- label.textAlignment = .center
- label.adjustsFontForContentSizeCategory = true
- label.translatesAutoresizingMaskIntoConstraints = false
- return label
- }()
-
- let langTitleLabel: UILabel = {
- let label = UILabel()
- label.textColor = Asset.Colors.Label.primary.color
- label.font = UIFontMetrics(forTextStyle: .subheadline).scaledFont(for: .systemFont(ofSize: 11, weight: .regular), maximumPointSize: 16)
- label.text = L10n.Scene.ServerPicker.Label.language
- label.textAlignment = .center
- label.adjustsFontForContentSizeCategory = true
- label.translatesAutoresizingMaskIntoConstraints = false
- return label
- }()
-
- let usersTitleLabel: UILabel = {
- let label = UILabel()
- label.textColor = Asset.Colors.Label.primary.color
- label.font = UIFontMetrics(forTextStyle: .subheadline).scaledFont(for: .systemFont(ofSize: 11, weight: .regular), maximumPointSize: 16)
- label.text = L10n.Scene.ServerPicker.Label.users
- label.textAlignment = .center
- label.adjustsFontForContentSizeCategory = true
- label.translatesAutoresizingMaskIntoConstraints = false
- return label
- }()
-
- let categoryTitleLabel: UILabel = {
- let label = UILabel()
- label.textColor = Asset.Colors.Label.primary.color
- label.font = UIFontMetrics(forTextStyle: .subheadline).scaledFont(for: .systemFont(ofSize: 11, weight: .regular), maximumPointSize: 16)
- label.text = L10n.Scene.ServerPicker.Label.category
- label.textAlignment = .center
- label.adjustsFontForContentSizeCategory = true
- label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
@@ -175,9 +93,6 @@ class PickServerCell: UITableViewCell {
override func prepareForReuse() {
super.prepareForReuse()
- thumbnailImageView.isHidden = false
- thumbnailImageView.af.cancelImageRequest()
- thumbnailActivityIndicator.stopAnimating()
disposeBag.removeAll()
}
@@ -197,172 +112,55 @@ class PickServerCell: UITableViewCell {
extension PickServerCell {
private func _init() {
selectionStyle = .none
- backgroundColor = .clear
- configureMargin()
+ backgroundColor = Asset.Scene.Onboarding.onboardingBackground.color
+
+ checkbox.translatesAutoresizingMaskIntoConstraints = false
+ contentView.addSubview(checkbox)
+ NSLayoutConstraint.activate([
+ checkbox.leadingAnchor.constraint(equalTo: contentView.readableContentGuide.leadingAnchor, constant: 1),
+ checkbox.heightAnchor.constraint(equalToConstant: 32).priority(.required - 1),
+ checkbox.widthAnchor.constraint(equalToConstant: 32).priority(.required - 1),
+ ])
+ containerView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(containerView)
- containerView.addSubview(domainLabel)
- containerView.addSubview(checkbox)
- containerView.addSubview(descriptionLabel)
- containerView.addSubview(separator)
-
- containerView.addSubview(expandButton)
-
- // Always add the expandbox which contains elements only visible in expand mode
- containerView.addSubview(expandBox)
- expandBox.addSubview(thumbnailImageView)
- expandBox.addSubview(infoStackView)
- expandBox.isHidden = true
-
- let verticalInfoStackViewLang = makeVerticalInfoStackView(arrangedView: langValueLabel, langTitleLabel)
- let verticalInfoStackViewUsers = makeVerticalInfoStackView(arrangedView: usersValueLabel, usersTitleLabel)
- let verticalInfoStackViewCategory = makeVerticalInfoStackView(arrangedView: categoryValueLabel, categoryTitleLabel)
- infoStackView.addArrangedSubview(verticalInfoStackViewLang)
- infoStackView.addArrangedSubview(verticalInfoStackViewUsers)
- infoStackView.addArrangedSubview(verticalInfoStackViewCategory)
-
- let expandButtonTopConstraintInCollapse = expandButton.topAnchor.constraint(equalTo: descriptionLabel.lastBaselineAnchor, constant: 12).priority(.required - 1)
- collapseConstraints.append(expandButtonTopConstraintInCollapse)
-
- let expandButtonTopConstraintInExpand = expandButton.topAnchor.constraint(equalTo: expandBox.bottomAnchor, constant: 8).priority(.defaultHigh)
- expandConstraints.append(expandButtonTopConstraintInExpand)
-
NSLayoutConstraint.activate([
- // Set background view
- containerView.topAnchor.constraint(equalTo: contentView.topAnchor),
- containerView.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor),
- contentView.layoutMarginsGuide.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
- contentView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
-
- // Set bottom separator
- separator.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
- containerView.trailingAnchor.constraint(equalTo: separator.trailingAnchor),
- containerView.topAnchor.constraint(equalTo: separator.topAnchor),
- separator.heightAnchor.constraint(equalToConstant: 1).priority(.defaultHigh),
-
- domainLabel.topAnchor.constraint(equalTo: containerView.layoutMarginsGuide.topAnchor),
- domainLabel.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor),
-
- checkbox.widthAnchor.constraint(equalToConstant: 23),
- checkbox.heightAnchor.constraint(equalToConstant: 22),
- containerView.layoutMarginsGuide.trailingAnchor.constraint(equalTo: checkbox.trailingAnchor),
- checkbox.leadingAnchor.constraint(equalTo: domainLabel.trailingAnchor, constant: 16),
- checkbox.centerYAnchor.constraint(equalTo: domainLabel.centerYAnchor),
-
- descriptionLabel.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor),
- descriptionLabel.topAnchor.constraint(equalTo: domainLabel.bottomAnchor, constant: 8),
- containerView.layoutMarginsGuide.trailingAnchor.constraint(equalTo: descriptionLabel.trailingAnchor),
-
- // Set expandBox constraints
- expandBox.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor),
- containerView.layoutMarginsGuide.trailingAnchor.constraint(equalTo: expandBox.trailingAnchor),
- expandBox.topAnchor.constraint(equalTo: descriptionLabel.bottomAnchor, constant: 8),
- expandBox.bottomAnchor.constraint(equalTo: infoStackView.bottomAnchor).priority(.defaultHigh),
-
- thumbnailImageView.topAnchor.constraint(equalTo: expandBox.topAnchor),
- thumbnailImageView.leadingAnchor.constraint(equalTo: expandBox.leadingAnchor),
- expandBox.trailingAnchor.constraint(equalTo: thumbnailImageView.trailingAnchor),
- thumbnailImageView.heightAnchor.constraint(equalTo: thumbnailImageView.widthAnchor, multiplier: 151.0 / 303.0).priority(.defaultHigh),
-
- infoStackView.leadingAnchor.constraint(equalTo: expandBox.leadingAnchor),
- expandBox.trailingAnchor.constraint(equalTo: infoStackView.trailingAnchor),
- infoStackView.topAnchor.constraint(equalTo: thumbnailImageView.bottomAnchor, constant: 16),
-
- expandButton.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor),
- containerView.layoutMarginsGuide.trailingAnchor.constraint(equalTo: expandButton.trailingAnchor),
- containerView.layoutMarginsGuide.bottomAnchor.constraint(equalTo: expandButton.bottomAnchor),
+ containerView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 11),
+ containerView.leadingAnchor.constraint(equalTo: checkbox.trailingAnchor, constant: 22),
+ containerView.trailingAnchor.constraint(equalTo: contentView.readableContentGuide.trailingAnchor),
+ contentView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: 11),
+ checkbox.centerYAnchor.constraint(equalTo: containerView.centerYAnchor),
])
- thumbnailActivityIndicator.translatesAutoresizingMaskIntoConstraints = false
- thumbnailImageView.addSubview(thumbnailActivityIndicator)
+ containerView.addArrangedSubview(domainLabel)
+ containerView.addArrangedSubview(descriptionLabel)
+ containerView.setCustomSpacing(6, after: descriptionLabel)
+ containerView.addArrangedSubview(infoStackView)
+
+ infoStackView.addArrangedSubview(usersValueLabel)
+ infoStackView.addArrangedSubview(langValueLabel)
+ infoStackView.addArrangedSubview(UIView())
+
+ separator.translatesAutoresizingMaskIntoConstraints = false
+ contentView.addSubview(separator)
NSLayoutConstraint.activate([
- thumbnailActivityIndicator.centerXAnchor.constraint(equalTo: thumbnailImageView.centerXAnchor),
- thumbnailActivityIndicator.centerYAnchor.constraint(equalTo: thumbnailImageView.centerYAnchor),
+ separator.leadingAnchor.constraint(equalTo: contentView.readableContentGuide.leadingAnchor),
+ contentView.readableContentGuide.trailingAnchor.constraint(equalTo: separator.trailingAnchor),
+ separator.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
+ separator.heightAnchor.constraint(equalToConstant: UIView.separatorLineHeight(of: contentView)).priority(.required - 1),
])
- thumbnailActivityIndicator.hidesWhenStopped = true
- thumbnailActivityIndicator.stopAnimating()
-
- NSLayoutConstraint.activate(collapseConstraints)
-
- domainLabel.setContentHuggingPriority(.required - 1, for: .vertical)
- domainLabel.setContentCompressionResistancePriority(.required - 1, for: .vertical)
- descriptionLabel.setContentHuggingPriority(.required - 2, for: .vertical)
- descriptionLabel.setContentCompressionResistancePriority(.required - 2, for: .vertical)
-
- expandButton.addTarget(self, action: #selector(expandButtonDidPressed(_:)), for: .touchUpInside)
}
- override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
- super.traitCollectionDidChange(previousTraitCollection)
-
- configureMargin()
- }
-
- private func makeVerticalInfoStackView(arrangedView: UIView...) -> UIStackView {
- let stackView = UIStackView()
- stackView.translatesAutoresizingMaskIntoConstraints = false
- stackView.axis = .vertical
- stackView.alignment = .center
- stackView.distribution = .equalCentering
- stackView.spacing = 2
- arrangedView.forEach { stackView.addArrangedSubview($0) }
- return stackView
- }
-
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
if selected {
checkbox.image = UIImage(systemName: "checkmark.circle.fill")
+ checkbox.tintColor = Asset.Colors.Label.primary.color
} else {
checkbox.image = UIImage(systemName: "circle")
+ checkbox.tintColor = Asset.Colors.Label.secondary.color
}
}
-
- @objc
- private func expandButtonDidPressed(_ sender: UIButton) {
- os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
- delegate?.pickServerCell(self, expandButtonPressed: sender)
- }
+
}
-extension PickServerCell {
- private func configureMargin() {
- switch traitCollection.horizontalSizeClass {
- case .regular:
- let margin = MastodonPickServerViewController.viewEdgeMargin
- contentView.layoutMargins = UIEdgeInsets(top: 0, left: margin, bottom: 0, right: margin)
- default:
- contentView.layoutMargins = .zero
- }
- }
-}
-
-extension PickServerCell {
-
- enum ExpandMode {
- case collapse
- case expand
- }
-
- func updateExpandMode(mode: ExpandMode) {
- switch mode {
- case .collapse:
- expandButton.setImage(UIImage(systemName: "chevron.down", withConfiguration: UIImage.SymbolConfiguration(pointSize: 13)), for: .normal)
- expandButton.setTitle(L10n.Scene.ServerPicker.Button.seeMore, for: .normal)
- expandBox.isHidden = true
- expandButton.isSelected = false
- NSLayoutConstraint.deactivate(expandConstraints)
- NSLayoutConstraint.activate(collapseConstraints)
- case .expand:
- expandButton.setImage(UIImage(systemName: "chevron.up", withConfiguration: UIImage.SymbolConfiguration(pointSize: 13)), for: .normal)
- expandButton.setTitle(L10n.Scene.ServerPicker.Button.seeLess, for: .normal)
- expandBox.isHidden = false
- expandButton.isSelected = true
- NSLayoutConstraint.activate(expandConstraints)
- NSLayoutConstraint.deactivate(collapseConstraints)
- }
-
- expandMode.value = mode
- }
-
-}
diff --git a/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerLoaderTableViewCell.swift b/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerLoaderTableViewCell.swift
index 945ecac6..eb0b619d 100644
--- a/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerLoaderTableViewCell.swift
+++ b/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerLoaderTableViewCell.swift
@@ -13,15 +13,7 @@ final class PickServerLoaderTableViewCell: TimelineLoaderTableViewCell {
let containerView: UIView = {
let view = UIView()
view.layoutMargins = UIEdgeInsets(top: 16, left: 16, bottom: 10, right: 16)
- view.backgroundColor = Asset.Theme.Mastodon.secondaryGroupedSystemBackground.color
- view.translatesAutoresizingMaskIntoConstraints = false
- return view
- }()
-
- let seperator: UIView = {
- let view = UIView()
- view.backgroundColor = Asset.Theme.Mastodon.systemGroupedBackground.color
- view.translatesAutoresizingMaskIntoConstraints = false
+ view.backgroundColor = .clear
return view
}()
@@ -30,30 +22,22 @@ final class PickServerLoaderTableViewCell: TimelineLoaderTableViewCell {
label.text = L10n.Scene.ServerPicker.EmptyState.noResults
label.textColor = Asset.Colors.Label.secondary.color
label.textAlignment = .center
- label.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 14, weight: .semibold), maximumPointSize: 19)
+ label.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 14, weight: .semibold))
return label
}()
override func _init() {
super._init()
-
- configureMargin()
-
- contentView.addSubview(containerView)
- contentView.addSubview(seperator)
+
+ // Set background view
+ containerView.translatesAutoresizingMaskIntoConstraints = false
+ contentView.addSubview(containerView)
NSLayoutConstraint.activate([
- // Set background view
containerView.topAnchor.constraint(equalTo: contentView.topAnchor),
containerView.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor),
contentView.layoutMarginsGuide.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
- contentView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: 1),
-
- // Set bottom separator
- seperator.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
- containerView.trailingAnchor.constraint(equalTo: seperator.trailingAnchor),
- containerView.topAnchor.constraint(equalTo: seperator.topAnchor),
- seperator.heightAnchor.constraint(equalToConstant: 1).priority(.defaultHigh),
+ contentView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
])
emptyStatusLabel.translatesAutoresizingMaskIntoConstraints = false
@@ -69,24 +53,7 @@ final class PickServerLoaderTableViewCell: TimelineLoaderTableViewCell {
activityIndicatorView.isHidden = false
startAnimating()
}
-
- override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
- super.traitCollectionDidChange(previousTraitCollection)
-
- configureMargin()
- }
-}
-extension PickServerLoaderTableViewCell {
- private func configureMargin() {
- switch traitCollection.horizontalSizeClass {
- case .regular:
- let margin = MastodonPickServerViewController.viewEdgeMargin
- contentView.layoutMargins = UIEdgeInsets(top: 0, left: margin, bottom: 0, right: margin)
- default:
- contentView.layoutMargins = .zero
- }
- }
}
#if canImport(SwiftUI) && DEBUG
diff --git a/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerSearchCell.swift b/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerSearchCell.swift
deleted file mode 100644
index 0a64103d..00000000
--- a/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerSearchCell.swift
+++ /dev/null
@@ -1,171 +0,0 @@
-//
-// PickServerSearchCell.swift
-// Mastodon
-//
-// Created by BradGao on 2021/2/24.
-//
-
-import UIKit
-
-protocol PickServerSearchCellDelegate: AnyObject {
- func pickServerSearchCell(_ cell: PickServerSearchCell, searchTextDidChange searchText: String?)
-}
-
-class PickServerSearchCell: UITableViewCell {
-
- weak var delegate: PickServerSearchCellDelegate?
-
- private var bgView: UIView = {
- let view = UIView()
- view.backgroundColor = Asset.Theme.Mastodon.secondaryGroupedSystemBackground.color
- view.translatesAutoresizingMaskIntoConstraints = false
- view.layer.maskedCorners = [
- .layerMinXMinYCorner,
- .layerMaxXMinYCorner
- ]
- view.layer.cornerCurve = .continuous
- view.layer.cornerRadius = MastodonPickServerAppearance.tableViewCornerRadius
- return view
- }()
-
- private var textFieldBgView: UIView = {
- let view = UIView()
- view.backgroundColor = Asset.Colors.TextField.background.color
- view.translatesAutoresizingMaskIntoConstraints = false
- view.layer.masksToBounds = true
- view.layer.cornerRadius = 6
- view.layer.cornerCurve = .continuous
- return view
- }()
-
- let searchTextField: UITextField = {
- let textField = UITextField()
- textField.translatesAutoresizingMaskIntoConstraints = false
- textField.leftView = {
- let imageView = UIImageView(
- image: UIImage(
- systemName: "magnifyingglass",
- withConfiguration: UIImage.SymbolConfiguration(pointSize: 15, weight: .regular)
- )
- )
- imageView.tintColor = Asset.Colors.Label.secondary.color.withAlphaComponent(0.6)
-
- let containerView = UIView()
- imageView.translatesAutoresizingMaskIntoConstraints = false
- containerView.addSubview(imageView)
- NSLayoutConstraint.activate([
- imageView.topAnchor.constraint(equalTo: containerView.topAnchor),
- imageView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
- imageView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
- ])
-
- let paddingView = UIView()
- paddingView.translatesAutoresizingMaskIntoConstraints = false
- containerView.addSubview(paddingView)
- NSLayoutConstraint.activate([
- paddingView.topAnchor.constraint(equalTo: containerView.topAnchor),
- paddingView.leadingAnchor.constraint(equalTo: imageView.trailingAnchor),
- paddingView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
- paddingView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
- paddingView.widthAnchor.constraint(equalToConstant: 4).priority(.defaultHigh),
- ])
- return containerView
- }()
- textField.leftViewMode = .always
- textField.font = .systemFont(ofSize: 15, weight: .regular)
- textField.tintColor = Asset.Colors.Label.primary.color
- textField.textColor = Asset.Colors.Label.primary.color
- textField.adjustsFontForContentSizeCategory = true
- textField.attributedPlaceholder =
- NSAttributedString(string: L10n.Scene.ServerPicker.Input.placeholder,
- attributes: [.font: UIFont.systemFont(ofSize: 15, weight: .regular),
- .foregroundColor: Asset.Colors.Label.secondary.color.withAlphaComponent(0.6)])
- textField.clearButtonMode = .whileEditing
- textField.autocapitalizationType = .none
- textField.autocorrectionType = .no
- textField.returnKeyType = .done
- textField.keyboardType = .URL
- return textField
- }()
-
- override func prepareForReuse() {
- super.prepareForReuse()
-
- delegate = nil
- }
-
- override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
- super.init(style: style, reuseIdentifier: reuseIdentifier)
- _init()
- }
-
- required init?(coder: NSCoder) {
- super.init(coder: coder)
- _init()
- }
-}
-
-extension PickServerSearchCell {
- private func _init() {
- selectionStyle = .none
- backgroundColor = Asset.Theme.Mastodon.systemGroupedBackground.color
- configureMargin()
-
- searchTextField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged)
- searchTextField.delegate = self
-
- contentView.addSubview(bgView)
- contentView.addSubview(textFieldBgView)
- contentView.addSubview(searchTextField)
-
- NSLayoutConstraint.activate([
- bgView.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor),
- bgView.topAnchor.constraint(equalTo: contentView.topAnchor),
- bgView.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor),
- bgView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
-
- textFieldBgView.leadingAnchor.constraint(equalTo: bgView.leadingAnchor, constant: 14),
- textFieldBgView.topAnchor.constraint(equalTo: bgView.topAnchor, constant: 12),
- bgView.trailingAnchor.constraint(equalTo: textFieldBgView.trailingAnchor, constant: 14),
- bgView.bottomAnchor.constraint(equalTo: textFieldBgView.bottomAnchor, constant: 13),
-
- searchTextField.leadingAnchor.constraint(equalTo: textFieldBgView.leadingAnchor, constant: 11),
- searchTextField.topAnchor.constraint(equalTo: textFieldBgView.topAnchor, constant: 4),
- textFieldBgView.trailingAnchor.constraint(equalTo: searchTextField.trailingAnchor, constant: 11),
- textFieldBgView.bottomAnchor.constraint(equalTo: searchTextField.bottomAnchor, constant: 4),
- ])
- }
-
- override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
- super.traitCollectionDidChange(previousTraitCollection)
-
- configureMargin()
- }
-}
-
-extension PickServerSearchCell {
- private func configureMargin() {
- switch traitCollection.horizontalSizeClass {
- case .regular:
- let margin = MastodonPickServerViewController.viewEdgeMargin
- contentView.layoutMargins = UIEdgeInsets(top: 0, left: margin, bottom: 0, right: margin)
- default:
- contentView.layoutMargins = .zero
- }
- }
-}
-
-extension PickServerSearchCell {
- @objc private func textFieldDidChange(_ textField: UITextField) {
- delegate?.pickServerSearchCell(self, searchTextDidChange: textField.text)
- }
-}
-
-// MARK: - UITextFieldDelegate
-extension PickServerSearchCell: UITextFieldDelegate {
-
- func textFieldShouldReturn(_ textField: UITextField) -> Bool {
- textField.resignFirstResponder()
- return false
- }
-}
diff --git a/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerTitleCell.swift b/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerTitleCell.swift
deleted file mode 100644
index f0d78eb4..00000000
--- a/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerTitleCell.swift
+++ /dev/null
@@ -1,82 +0,0 @@
-//
-// PickServerTitleCell.swift
-// Mastodon
-//
-// Created by BradGao on 2021/2/23.
-//
-
-import UIKit
-
-final class PickServerTitleCell: UITableViewCell {
-
- let titleLabel: UILabel = {
- let label = UILabel()
- label.font = UIFontMetrics(forTextStyle: .largeTitle).scaledFont(for: .systemFont(ofSize: 34, weight: .bold))
- label.textColor = Asset.Colors.Label.primary.color
- label.text = L10n.Scene.ServerPicker.title
- label.adjustsFontForContentSizeCategory = true
- label.translatesAutoresizingMaskIntoConstraints = false
- label.numberOfLines = 0
- return label
- }()
-
- var containerHeightLayoutConstraint: NSLayoutConstraint!
-
- override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
- super.init(style: style, reuseIdentifier: reuseIdentifier)
- _init()
- }
-
- required init?(coder: NSCoder) {
- super.init(coder: coder)
- _init()
- }
-}
-
-extension PickServerTitleCell {
-
- private func _init() {
- selectionStyle = .none
- backgroundColor = Asset.Theme.Mastodon.systemGroupedBackground.color
-
- let container = UIStackView()
- container.axis = .vertical
- container.translatesAutoresizingMaskIntoConstraints = false
- containerHeightLayoutConstraint = container.heightAnchor.constraint(equalToConstant: .leastNonzeroMagnitude)
- contentView.addSubview(container)
- NSLayoutConstraint.activate([
- container.topAnchor.constraint(equalTo: contentView.topAnchor),
- container.leadingAnchor.constraint(equalTo: contentView.readableContentGuide.leadingAnchor),
- container.trailingAnchor.constraint(equalTo: contentView.readableContentGuide.trailingAnchor),
- container.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
- ])
-
- container.addArrangedSubview(titleLabel)
-
- configureTitleLabelDisplay()
- }
-
- override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
- super.traitCollectionDidChange(previousTraitCollection)
-
- configureTitleLabelDisplay()
- }
-}
-
-extension PickServerTitleCell {
- private func configureTitleLabelDisplay() {
- guard traitCollection.userInterfaceIdiom == .pad else {
- titleLabel.isHidden = false
- return
- }
-
- switch traitCollection.horizontalSizeClass {
- case .regular:
- titleLabel.isHidden = true
- containerHeightLayoutConstraint.isActive = true
- default:
- titleLabel.isHidden = false
- containerHeightLayoutConstraint.isActive = false
- }
- }
-}
diff --git a/Mastodon/Scene/Onboarding/PickServer/View/PickServerCategoryView.swift b/Mastodon/Scene/Onboarding/PickServer/View/PickServerCategoryView.swift
index 6565fbcf..f3bc3994 100644
--- a/Mastodon/Scene/Onboarding/PickServer/View/PickServerCategoryView.swift
+++ b/Mastodon/Scene/Onboarding/PickServer/View/PickServerCategoryView.swift
@@ -10,24 +10,24 @@ import MastodonSDK
class PickServerCategoryView: UIView {
- var bgShadowView: UIView = {
+ let highlightedIndicatorView: UIView = {
let view = UIView()
- view.translatesAutoresizingMaskIntoConstraints = false
+ view.backgroundColor = Asset.Colors.Label.primary.color
return view
}()
-
- var bgView: UIView = {
- let view = UIView()
- view.translatesAutoresizingMaskIntoConstraints = false
- view.layer.masksToBounds = true
- view.layer.cornerRadius = 30
- return view
- }()
-
- var titleLabel: UILabel = {
+
+ let emojiLabel: UILabel = {
let label = UILabel()
label.textAlignment = .center
- label.translatesAutoresizingMaskIntoConstraints = false
+ label.font = .systemFont(ofSize: 34, weight: .regular)
+ return label
+ }()
+
+ let titleLabel: UILabel = {
+ let label = UILabel()
+ label.textAlignment = .center
+ label.font = .systemFont(ofSize: 17, weight: .semibold)
+ label.textColor = Asset.Colors.Label.secondary.color
return label
}()
@@ -45,20 +45,27 @@ class PickServerCategoryView: UIView {
extension PickServerCategoryView {
private func configure() {
- addSubview(bgView)
- addSubview(titleLabel)
-
- bgView.backgroundColor = Asset.Theme.Mastodon.secondaryGroupedSystemBackground.color
-
+ let container = UIStackView()
+ container.axis = .vertical
+ container.distribution = .fillProportionally
+
+ container.translatesAutoresizingMaskIntoConstraints = false
+ addSubview(container)
NSLayoutConstraint.activate([
- bgView.leadingAnchor.constraint(equalTo: self.leadingAnchor),
- bgView.trailingAnchor.constraint(equalTo: self.trailingAnchor),
- bgView.topAnchor.constraint(equalTo: self.topAnchor),
- bgView.bottomAnchor.constraint(equalTo: self.bottomAnchor),
-
- titleLabel.centerXAnchor.constraint(equalTo: self.centerXAnchor),
- titleLabel.centerYAnchor.constraint(equalTo: self.centerYAnchor),
+ container.topAnchor.constraint(equalTo: topAnchor),
+ container.leadingAnchor.constraint(equalTo: leadingAnchor),
+ container.trailingAnchor.constraint(equalTo: trailingAnchor),
+ container.bottomAnchor.constraint(equalTo: bottomAnchor),
])
+
+ container.addArrangedSubview(emojiLabel)
+ container.addArrangedSubview(titleLabel)
+ highlightedIndicatorView.translatesAutoresizingMaskIntoConstraints = false
+ container.addArrangedSubview(highlightedIndicatorView)
+ NSLayoutConstraint.activate([
+ highlightedIndicatorView.heightAnchor.constraint(equalToConstant: UIView.separatorLineHeight(of: self) * 3).priority(.required - 1),
+ ])
+ titleLabel.setContentHuggingPriority(.required - 1, for: .vertical)
}
}
diff --git a/Mastodon/Scene/Onboarding/PickServer/View/PickServerEmptyStateView.swift b/Mastodon/Scene/Onboarding/PickServer/View/PickServerEmptyStateView.swift
index 1d2c17c7..c5682143 100644
--- a/Mastodon/Scene/Onboarding/PickServer/View/PickServerEmptyStateView.swift
+++ b/Mastodon/Scene/Onboarding/PickServer/View/PickServerEmptyStateView.swift
@@ -44,13 +44,7 @@ final class PickServerEmptyStateView: UIView {
extension PickServerEmptyStateView {
private func _init() {
- backgroundColor = Asset.Theme.Mastodon.secondaryGroupedSystemBackground.color
- layer.maskedCorners = [
- .layerMinXMaxYCorner,
- .layerMaxXMaxYCorner
- ]
- layer.cornerCurve = .continuous
- layer.cornerRadius = MastodonPickServerAppearance.tableViewCornerRadius
+ backgroundColor = .clear
let topPaddingView = UIView()
topPaddingView.translatesAutoresizingMaskIntoConstraints = false
@@ -101,7 +95,7 @@ extension PickServerEmptyStateView {
])
NSLayoutConstraint.activate([
- bottomPaddingView.heightAnchor.constraint(equalTo: topPaddingView.heightAnchor, multiplier: 1.0).priority(.defaultHigh),
+ topPaddingView.heightAnchor.constraint(equalTo: bottomPaddingView.heightAnchor, multiplier: 2.5).priority(.defaultHigh), // magic scale
])
activityIndicatorView.hidesWhenStopped = true
diff --git a/Mastodon/Scene/Onboarding/PickServer/View/PickServerServerSectionTableHeaderView.swift b/Mastodon/Scene/Onboarding/PickServer/View/PickServerServerSectionTableHeaderView.swift
new file mode 100644
index 00000000..4afa31aa
--- /dev/null
+++ b/Mastodon/Scene/Onboarding/PickServer/View/PickServerServerSectionTableHeaderView.swift
@@ -0,0 +1,204 @@
+//
+// PickServerServerSectionTableHeaderView.swift
+// Mastodon
+//
+// Created by MainasuK on 2022-1-4.
+//
+
+import os.log
+import UIKit
+import Tabman
+
+protocol PickServerServerSectionTableHeaderViewDelegate: AnyObject {
+ func pickServerServerSectionTableHeaderView(_ headerView: PickServerServerSectionTableHeaderView, collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
+ func pickServerServerSectionTableHeaderView(_ headerView: PickServerServerSectionTableHeaderView, searchTextDidChange searchText: String?)
+}
+
+final class PickServerServerSectionTableHeaderView: UIView {
+
+ static let collectionViewHeight: CGFloat = 88
+ static let searchTextFieldHeight: CGFloat = 38
+ static let spacing: CGFloat = 11
+
+ static let height: CGFloat = collectionViewHeight + spacing + searchTextFieldHeight + spacing
+
+ weak var delegate: PickServerServerSectionTableHeaderViewDelegate?
+
+ var diffableDataSource: UICollectionViewDiffableDataSource?
+
+ static func createCollectionViewLayout() -> UICollectionViewLayout {
+ let itemSize = NSCollectionLayoutSize(widthDimension: .estimated(88), heightDimension: .absolute(PickServerServerSectionTableHeaderView.collectionViewHeight))
+ let item = NSCollectionLayoutItem(layoutSize: itemSize)
+
+ let groupSize = NSCollectionLayoutSize(widthDimension: itemSize.widthDimension, heightDimension: itemSize.heightDimension)
+ let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
+
+ let section = NSCollectionLayoutSection(group: group)
+ section.orthogonalScrollingBehavior = .continuous
+ section.contentInsetsReference = .readableContent
+ section.interGroupSpacing = 16
+
+ return UICollectionViewCompositionalLayout(section: section)
+ }
+
+ let collectionView: UICollectionView = {
+ let collectionViewLayout = PickServerServerSectionTableHeaderView.createCollectionViewLayout()
+ let view = ControlContainableCollectionView(
+ frame: CGRect(origin: .zero, size: CGSize(width: 100, height: PickServerServerSectionTableHeaderView.collectionViewHeight)),
+ collectionViewLayout: collectionViewLayout
+ )
+ view.register(PickServerCategoryCollectionViewCell.self, forCellWithReuseIdentifier: String(describing: PickServerCategoryCollectionViewCell.self))
+ view.backgroundColor = .clear
+ view.alwaysBounceVertical = false
+ view.showsHorizontalScrollIndicator = false
+ view.showsVerticalScrollIndicator = false
+ view.layer.masksToBounds = false
+ return view
+ }()
+
+ let searchTextField: UITextField = {
+ let textField = UITextField()
+ textField.backgroundColor = Asset.Scene.Onboarding.searchBarBackground.color
+ textField.leftView = {
+ let imageView = UIImageView(
+ image: UIImage(
+ systemName: "magnifyingglass",
+ withConfiguration: UIImage.SymbolConfiguration(pointSize: 15, weight: .regular)
+ )
+ )
+ imageView.tintColor = Asset.Colors.Label.secondary.color.withAlphaComponent(0.6)
+
+ let containerView = UIView()
+ imageView.translatesAutoresizingMaskIntoConstraints = false
+ containerView.addSubview(imageView)
+ NSLayoutConstraint.activate([
+ imageView.topAnchor.constraint(equalTo: containerView.topAnchor),
+ imageView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 8),
+ imageView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
+ ])
+
+ let paddingView = UIView()
+ paddingView.translatesAutoresizingMaskIntoConstraints = false
+ containerView.addSubview(paddingView)
+ NSLayoutConstraint.activate([
+ paddingView.topAnchor.constraint(equalTo: containerView.topAnchor),
+ paddingView.leadingAnchor.constraint(equalTo: imageView.trailingAnchor),
+ paddingView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
+ paddingView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
+ paddingView.widthAnchor.constraint(equalToConstant: 4).priority(.defaultHigh),
+ ])
+ return containerView
+ }()
+ textField.leftViewMode = .always
+ textField.font = .systemFont(ofSize: 15, weight: .regular)
+ textField.tintColor = Asset.Colors.Label.primary.color
+ textField.textColor = Asset.Colors.Label.primary.color
+ textField.adjustsFontForContentSizeCategory = true
+ textField.attributedPlaceholder =
+ NSAttributedString(
+ string: L10n.Scene.ServerPicker.Input.placeholder,
+ attributes: [.font: UIFont.systemFont(ofSize: 15, weight: .regular),
+ .foregroundColor: Asset.Colors.Label.secondary.color.withAlphaComponent(0.6)])
+ textField.clearButtonMode = .whileEditing
+ textField.autocapitalizationType = .none
+ textField.autocorrectionType = .no
+ textField.returnKeyType = .done
+ textField.keyboardType = .URL
+ textField.borderStyle = .none
+
+ textField.layer.masksToBounds = true
+ textField.layer.cornerRadius = 10
+ textField.layer.cornerCurve = .continuous
+
+ return textField
+ }()
+
+ override init(frame: CGRect) {
+ super.init(frame: frame)
+ _init()
+ }
+
+ required init?(coder: NSCoder) {
+ super.init(coder: coder)
+ _init()
+ }
+
+ override func layoutSubviews() {
+ super.layoutSubviews()
+
+ collectionView.invalidateIntrinsicContentSize()
+ }
+
+}
+
+extension PickServerServerSectionTableHeaderView {
+ private func _init() {
+ preservesSuperviewLayoutMargins = true
+ backgroundColor = Asset.Scene.Onboarding.onboardingBackground.color
+
+ collectionView.translatesAutoresizingMaskIntoConstraints = false
+ collectionView.preservesSuperviewLayoutMargins = true
+ addSubview(collectionView)
+ NSLayoutConstraint.activate([
+ collectionView.topAnchor.constraint(equalTo: topAnchor),
+ collectionView.leadingAnchor.constraint(equalTo: leadingAnchor),
+ collectionView.trailingAnchor.constraint(equalTo: trailingAnchor),
+ collectionView.heightAnchor.constraint(equalToConstant: PickServerServerSectionTableHeaderView.collectionViewHeight).priority(.required - 1),
+ ])
+
+ searchTextField.translatesAutoresizingMaskIntoConstraints = false
+ addSubview(searchTextField)
+ NSLayoutConstraint.activate([
+ searchTextField.topAnchor.constraint(equalTo: collectionView.bottomAnchor, constant: PickServerServerSectionTableHeaderView.spacing),
+ searchTextField.leadingAnchor.constraint(equalTo: readableContentGuide.leadingAnchor),
+ searchTextField.trailingAnchor.constraint(equalTo: readableContentGuide.trailingAnchor),
+ bottomAnchor.constraint(equalTo: searchTextField.bottomAnchor, constant: PickServerServerSectionTableHeaderView.spacing),
+ searchTextField.heightAnchor.constraint(equalToConstant: PickServerServerSectionTableHeaderView.searchTextFieldHeight).priority(.required - 1),
+ ])
+
+ collectionView.delegate = self
+ searchTextField.delegate = self
+ searchTextField.addTarget(self, action: #selector(PickServerServerSectionTableHeaderView.textFieldDidChange(_:)), for: .editingChanged)
+ }
+}
+
+extension PickServerServerSectionTableHeaderView {
+ @objc private func textFieldDidChange(_ textField: UITextField) {
+ delegate?.pickServerServerSectionTableHeaderView(self, searchTextDidChange: textField.text)
+ }
+}
+
+// MARK: - UICollectionViewDelegate
+extension PickServerServerSectionTableHeaderView: UICollectionViewDelegate {
+
+ func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
+ os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: indexPath: %s", ((#file as NSString).lastPathComponent), #line, #function, indexPath.debugDescription)
+ collectionView.selectItem(at: indexPath, animated: true, scrollPosition: .centeredHorizontally)
+ delegate?.pickServerServerSectionTableHeaderView(self, collectionView: collectionView, didSelectItemAt: indexPath)
+ }
+
+}
+
+extension PickServerServerSectionTableHeaderView {
+
+ override func accessibilityElementCount() -> Int {
+ guard let diffableDataSource = diffableDataSource else { return 0 }
+ return diffableDataSource.snapshot().itemIdentifiers.count
+ }
+
+ override func accessibilityElement(at index: Int) -> Any? {
+ guard let item = collectionView.cellForItem(at: IndexPath(item: index, section: 0)) else { return nil }
+ return item
+ }
+
+}
+
+// MARK: - UITextFieldDelegate
+extension PickServerServerSectionTableHeaderView: UITextFieldDelegate {
+
+ func textFieldShouldReturn(_ textField: UITextField) -> Bool {
+ textField.resignFirstResponder()
+ return false
+ }
+
+}
diff --git a/Mastodon/Scene/Onboarding/Register/Cell/MastodonRegisterAvatarTableViewCell.swift b/Mastodon/Scene/Onboarding/Register/Cell/MastodonRegisterAvatarTableViewCell.swift
new file mode 100644
index 00000000..304bd02d
--- /dev/null
+++ b/Mastodon/Scene/Onboarding/Register/Cell/MastodonRegisterAvatarTableViewCell.swift
@@ -0,0 +1,114 @@
+//
+// MastodonRegisterAvatarTableViewCell.swift
+// Mastodon
+//
+// Created by MainasuK on 2022-1-5.
+//
+
+import UIKit
+import Combine
+
+final class MastodonRegisterAvatarTableViewCell: UITableViewCell {
+
+ static let containerSize = CGSize(width: 88, height: 88)
+
+ var disposeBag = Set()
+
+ let containerView: UIView = {
+ let view = UIView()
+ view.backgroundColor = .clear
+ view.layer.masksToBounds = true
+ view.layer.cornerCurve = .continuous
+ view.layer.cornerRadius = 22
+ return view
+ }()
+
+ let avatarButton: HighlightDimmableButton = {
+ let button = HighlightDimmableButton()
+ button.backgroundColor = Asset.Theme.Mastodon.secondaryGroupedSystemBackground.color
+ button.setImage(Asset.Scene.Onboarding.avatarPlaceholder.image, for: .normal)
+ return button
+ }()
+
+ let editBannerView: UIView = {
+ let bannerView = UIView()
+ bannerView.backgroundColor = UIColor.black.withAlphaComponent(0.5)
+ bannerView.isUserInteractionEnabled = false
+
+ let label: UILabel = {
+ let label = UILabel()
+ label.textColor = .white
+ label.text = L10n.Common.Controls.Actions.edit
+ label.font = .systemFont(ofSize: 13, weight: .semibold)
+ label.textAlignment = .center
+ label.minimumScaleFactor = 0.5
+ label.adjustsFontSizeToFitWidth = true
+ return label
+ }()
+
+ label.translatesAutoresizingMaskIntoConstraints = false
+ bannerView.addSubview(label)
+ NSLayoutConstraint.activate([
+ label.topAnchor.constraint(equalTo: bannerView.topAnchor),
+ label.leadingAnchor.constraint(equalTo: bannerView.leadingAnchor),
+ label.trailingAnchor.constraint(equalTo: bannerView.trailingAnchor),
+ label.bottomAnchor.constraint(equalTo: bannerView.bottomAnchor),
+ ])
+
+ return bannerView
+ }()
+
+ override func prepareForReuse() {
+ super.prepareForReuse()
+
+ disposeBag.removeAll()
+ }
+
+ override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
+ super.init(style: style, reuseIdentifier: reuseIdentifier)
+ _init()
+ }
+
+ required init?(coder: NSCoder) {
+ super.init(coder: coder)
+ _init()
+ }
+
+}
+
+extension MastodonRegisterAvatarTableViewCell {
+
+ private func _init() {
+ selectionStyle = .none
+ backgroundColor = .clear
+
+ containerView.translatesAutoresizingMaskIntoConstraints = false
+ contentView.addSubview(containerView)
+ NSLayoutConstraint.activate([
+ containerView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 22),
+ containerView.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
+ contentView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: 8),
+ containerView.widthAnchor.constraint(equalToConstant: MastodonRegisterAvatarTableViewCell.containerSize.width).priority(.required - 1),
+ containerView.heightAnchor.constraint(equalToConstant: MastodonRegisterAvatarTableViewCell.containerSize.height).priority(.required - 1),
+ ])
+
+ avatarButton.translatesAutoresizingMaskIntoConstraints = false
+ containerView.addSubview(avatarButton)
+ NSLayoutConstraint.activate([
+ avatarButton.topAnchor.constraint(equalTo: containerView.topAnchor),
+ avatarButton.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
+ avatarButton.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
+ avatarButton.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
+ ])
+
+ editBannerView.translatesAutoresizingMaskIntoConstraints = false
+ containerView.addSubview(editBannerView)
+ NSLayoutConstraint.activate([
+ editBannerView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
+ editBannerView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
+ editBannerView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
+ editBannerView.heightAnchor.constraint(equalToConstant: 22),
+ ])
+ }
+
+}
diff --git a/Mastodon/Scene/Onboarding/Register/Cell/MastodonRegisterPasswordHintTableViewCell.swift b/Mastodon/Scene/Onboarding/Register/Cell/MastodonRegisterPasswordHintTableViewCell.swift
new file mode 100644
index 00000000..829c70a7
--- /dev/null
+++ b/Mastodon/Scene/Onboarding/Register/Cell/MastodonRegisterPasswordHintTableViewCell.swift
@@ -0,0 +1,48 @@
+//
+// MastodonRegisterPasswordHintTableViewCell.swift
+// Mastodon
+//
+// Created by MainasuK on 2022-1-7.
+//
+
+import UIKit
+
+final class MastodonRegisterPasswordHintTableViewCell: UITableViewCell {
+
+ let passwordRuleLabel: UILabel = {
+ let label = UILabel()
+ label.font = .preferredFont(forTextStyle: .footnote)
+ label.textColor = Asset.Colors.Label.secondary.color
+ label.text = L10n.Scene.Register.Input.Password.hint
+ return label
+ }()
+
+ override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
+ super.init(style: style, reuseIdentifier: reuseIdentifier)
+ _init()
+ }
+
+ required init?(coder: NSCoder) {
+ super.init(coder: coder)
+ _init()
+ }
+
+}
+
+extension MastodonRegisterPasswordHintTableViewCell {
+
+ private func _init() {
+ selectionStyle = .none
+ backgroundColor = .clear
+
+ passwordRuleLabel.translatesAutoresizingMaskIntoConstraints = false
+ contentView.addSubview(passwordRuleLabel)
+ NSLayoutConstraint.activate([
+ passwordRuleLabel.topAnchor.constraint(equalTo: contentView.topAnchor),
+ passwordRuleLabel.leadingAnchor.constraint(equalTo: contentView.readableContentGuide.leadingAnchor),
+ passwordRuleLabel.trailingAnchor.constraint(equalTo: contentView.readableContentGuide.trailingAnchor),
+ passwordRuleLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
+ ])
+ }
+
+}
diff --git a/Mastodon/Scene/Onboarding/Register/Cell/MastodonRegisterTextFieldTableViewCell.swift b/Mastodon/Scene/Onboarding/Register/Cell/MastodonRegisterTextFieldTableViewCell.swift
new file mode 100644
index 00000000..8e54d1ff
--- /dev/null
+++ b/Mastodon/Scene/Onboarding/Register/Cell/MastodonRegisterTextFieldTableViewCell.swift
@@ -0,0 +1,136 @@
+//
+// MastodonRegisterTextFieldTableViewCell.swift
+// Mastodon
+//
+// Created by MainasuK on 2022-1-7.
+//
+
+import UIKit
+import Combine
+import MastodonUI
+
+final class MastodonRegisterTextFieldTableViewCell: UITableViewCell {
+
+ static let textFieldHeight: CGFloat = 50
+ static let textFieldLabelFont = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 17, weight: .semibold), maximumPointSize: 22)
+
+ var disposeBag = Set()
+
+ let textFieldShadowContainer = ShadowBackgroundContainer()
+ let textField: UITextField = {
+ let textField = UITextField()
+ textField.font = MastodonRegisterTextFieldTableViewCell.textFieldLabelFont
+ textField.backgroundColor = Asset.Scene.Onboarding.textFieldBackground.color
+ textField.layer.masksToBounds = true
+ textField.layer.cornerRadius = 10
+ textField.layer.cornerCurve = .continuous
+ return textField
+ }()
+
+ override func prepareForReuse() {
+ super.prepareForReuse()
+
+ disposeBag.removeAll()
+ textFieldShadowContainer.shadowColor = .black
+ textFieldShadowContainer.shadowAlpha = 0.25
+ resetTextField()
+ }
+
+ override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
+ super.init(style: style, reuseIdentifier: reuseIdentifier)
+ _init()
+ }
+
+ required init?(coder: NSCoder) {
+ super.init(coder: coder)
+ _init()
+ }
+
+}
+
+extension MastodonRegisterTextFieldTableViewCell {
+
+ private func _init() {
+ selectionStyle = .none
+ backgroundColor = .clear
+
+ textFieldShadowContainer.translatesAutoresizingMaskIntoConstraints = false
+ contentView.addSubview(textFieldShadowContainer)
+ NSLayoutConstraint.activate([
+ textFieldShadowContainer.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 6),
+ textFieldShadowContainer.leadingAnchor.constraint(equalTo: contentView.readableContentGuide.leadingAnchor),
+ textFieldShadowContainer.trailingAnchor.constraint(equalTo: contentView.readableContentGuide.trailingAnchor),
+ contentView.bottomAnchor.constraint(equalTo: textFieldShadowContainer.bottomAnchor, constant: 6),
+ ])
+
+ textField.translatesAutoresizingMaskIntoConstraints = false
+ textFieldShadowContainer.addSubview(textField)
+ NSLayoutConstraint.activate([
+ textField.topAnchor.constraint(equalTo: textFieldShadowContainer.topAnchor),
+ textField.leadingAnchor.constraint(equalTo: textFieldShadowContainer.leadingAnchor),
+ textField.trailingAnchor.constraint(equalTo: textFieldShadowContainer.trailingAnchor),
+ textField.bottomAnchor.constraint(equalTo: textFieldShadowContainer.bottomAnchor),
+ textField.heightAnchor.constraint(equalToConstant: MastodonRegisterTextFieldTableViewCell.textFieldHeight).priority(.required - 1),
+ ])
+
+ resetTextField()
+ }
+
+}
+
+extension MastodonRegisterTextFieldTableViewCell {
+ func resetTextField() {
+ textField.keyboardType = .default
+ textField.autocorrectionType = .default
+ textField.autocapitalizationType = .none
+ textField.attributedPlaceholder = nil
+ textField.isSecureTextEntry = false
+
+ let paddingRect = CGRect(x: 0, y: 0, width: 16, height: 10)
+ textField.leftView = UIView(frame: paddingRect)
+ textField.leftViewMode = .always
+ textField.rightView = UIView(frame: paddingRect)
+ textField.rightViewMode = .always
+ }
+
+ func setupTextViewRightView(text: String) {
+ textField.rightView = {
+ let containerView = UIView()
+
+ let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: 8, height: MastodonRegisterTextFieldTableViewCell.textFieldHeight))
+ paddingView.translatesAutoresizingMaskIntoConstraints = false
+ containerView.addSubview(paddingView)
+ NSLayoutConstraint.activate([
+ paddingView.topAnchor.constraint(equalTo: containerView.topAnchor),
+ paddingView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
+ paddingView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
+ paddingView.widthAnchor.constraint(equalToConstant: 8).priority(.defaultHigh),
+ ])
+
+ let label = UILabel()
+ label.font = MastodonRegisterTextFieldTableViewCell.textFieldLabelFont
+ label.textColor = Asset.Colors.Label.primary.color
+ label.text = text
+
+ label.translatesAutoresizingMaskIntoConstraints = false
+ containerView.addSubview(label)
+ NSLayoutConstraint.activate([
+ label.topAnchor.constraint(equalTo: containerView.topAnchor),
+ label.leadingAnchor.constraint(equalTo: paddingView.trailingAnchor),
+ containerView.trailingAnchor.constraint(equalTo: label.trailingAnchor, constant: 16),
+ label.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
+ ])
+ return containerView
+ }()
+ }
+
+ func setupTextViewPlaceholder(text: String) {
+ textField.attributedPlaceholder = NSAttributedString(
+ string: text,
+ attributes: [
+ .foregroundColor: Asset.Colors.Label.secondary.color,
+ .font: MastodonRegisterTextFieldTableViewCell.textFieldLabelFont
+ ]
+ )
+ }
+}
diff --git a/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewController+Avatar.swift b/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewController+Avatar.swift
index b1fa1b43..0add10dc 100644
--- a/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewController+Avatar.swift
+++ b/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewController+Avatar.swift
@@ -12,36 +12,6 @@ import PhotosUI
import UIKit
extension MastodonRegisterViewController {
- func createMediaContextMenu() -> UIMenu {
- var children: [UIMenuElement] = []
- let photoLibraryAction = UIAction(title: L10n.Scene.Compose.MediaSelection.photoLibrary, image: UIImage(systemName: "rectangle.on.rectangle"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak self] _ in
- guard let self = self else { return }
- self.present(self.imagePicker, animated: true, completion: nil)
- }
- children.append(photoLibraryAction)
- if UIImagePickerController.isSourceTypeAvailable(.camera) {
- let cameraAction = UIAction(title: L10n.Scene.Compose.MediaSelection.camera, image: UIImage(systemName: "camera"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off, handler: { [weak self] _ in
- guard let self = self else { return }
- self.present(self.imagePickerController, animated: true, completion: nil)
- })
- children.append(cameraAction)
- }
- let browseAction = UIAction(title: L10n.Scene.Compose.MediaSelection.browse, image: UIImage(systemName: "ellipsis"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak self] _ in
- guard let self = self else { return }
- self.present(self.documentPickerController, animated: true, completion: nil)
- }
- children.append(browseAction)
- if self.viewModel.avatarImage.value != nil {
- let deleteAction = UIAction(title: L10n.Scene.Register.Input.Avatar.delete, image: UIImage(systemName: "delete.left"), identifier: nil, discoverabilityTitle: nil, attributes: [.destructive], state: .off) { [weak self] _ in
- guard let self = self else { return }
- self.viewModel.avatarImage.value = nil
- }
- children.append(deleteAction)
- }
-
- return UIMenu(title: "", image: nil, identifier: nil, options: .displayInline, children: children)
- }
-
private func cropImage(image: UIImage, pickerViewController: UIViewController) {
DispatchQueue.main.async {
let cropController = CropViewController(croppingStyle: .default, image: image)
@@ -49,6 +19,12 @@ extension MastodonRegisterViewController {
cropController.setAspectRatioPreset(.presetSquare, animated: true)
cropController.aspectRatioPickerButtonHidden = true
cropController.aspectRatioLockEnabled = true
+
+ // fix iPad compatibility issue
+ // ref: https://github.com/TimOliver/TOCropViewController/issues/365#issuecomment-550239604
+ cropController.modalTransitionStyle = .crossDissolve
+ cropController.transitioningDelegate = nil
+
pickerViewController.dismiss(animated: true, completion: {
self.present(cropController, animated: true, completion: nil)
})
@@ -57,7 +33,6 @@ extension MastodonRegisterViewController {
}
// MARK: - PHPickerViewControllerDelegate
-
extension MastodonRegisterViewController: PHPickerViewControllerDelegate {
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
guard let itemProvider = results.first?.itemProvider, itemProvider.canLoadObject(ofClass: UIImage.self) else {
@@ -86,7 +61,6 @@ extension MastodonRegisterViewController: PHPickerViewControllerDelegate {
}
// MARK: - UIImagePickerControllerDelegate
-
extension MastodonRegisterViewController: UIImagePickerControllerDelegate & UINavigationControllerDelegate {
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
picker.dismiss(animated: true, completion: nil)
@@ -103,7 +77,6 @@ extension MastodonRegisterViewController: UIImagePickerControllerDelegate & UINa
}
// MARK: - UIDocumentPickerDelegate
-
extension MastodonRegisterViewController: UIDocumentPickerDelegate {
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
guard let url = urls.first else { return }
@@ -121,10 +94,9 @@ extension MastodonRegisterViewController: UIDocumentPickerDelegate {
}
// MARK: - CropViewControllerDelegate
-
extension MastodonRegisterViewController: CropViewControllerDelegate {
public func cropViewController(_ cropViewController: CropViewController, didCropToImage image: UIImage, withRect cropRect: CGRect, angle: Int) {
- self.viewModel.avatarImage.value = image
+ self.viewModel.avatarImage = image
cropViewController.dismiss(animated: true, completion: nil)
}
}
diff --git a/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewController.swift b/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewController.swift
index 8428aaa7..a1fd9742 100644
--- a/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewController.swift
+++ b/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewController.swift
@@ -16,11 +16,11 @@ final class MastodonRegisterViewController: UIViewController, NeedsDependency, O
static let avatarImageMaxSizeInPixel = CGSize(width: 400, height: 400)
- static let textFieldLabelFont = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 17, weight: .semibold), maximumPointSize: 22)
- static let errorPromptLabelFont = UIFontMetrics(forTextStyle: .caption1).scaledFont(for: .systemFont(ofSize: 13, weight: .semibold), maximumPointSize: 18)
+ let logger = Logger(subsystem: "MastodonRegisterViewController", category: "ViewController")
var disposeBag = Set()
-
+ private var observations = Set()
+
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
@@ -51,236 +51,30 @@ final class MastodonRegisterViewController: UIViewController, NeedsDependency, O
let tapGestureRecognizer = UITapGestureRecognizer.singleTapGestureRecognizer
- let scrollView: UIScrollView = {
- let scrollview = UIScrollView()
- scrollview.showsVerticalScrollIndicator = false
- scrollview.keyboardDismissMode = .interactive
- scrollview.alwaysBounceVertical = true
- scrollview.clipsToBounds = false // make content could display over bleeding
- scrollview.translatesAutoresizingMaskIntoConstraints = false
- return scrollview
+ let tableView: UITableView = {
+ let tableView = UITableView()
+ tableView.rowHeight = UITableView.automaticDimension
+ tableView.separatorStyle = .none
+ tableView.backgroundColor = .clear
+ tableView.keyboardDismissMode = .onDrag
+ if #available(iOS 15.0, *) {
+ tableView.sectionHeaderTopPadding = .leastNonzeroMagnitude
+ } else {
+ // Fallback on earlier versions
+ }
+ return tableView
}()
- let stackView = UIStackView()
-
- let largeTitleLabel: UILabel = {
- let label = UILabel()
- label.font = UIFontMetrics(forTextStyle: .largeTitle).scaledFont(for: .systemFont(ofSize: 34, weight: .bold))
- label.textColor = Asset.Colors.Label.primary.color
- label.text = L10n.Scene.Register.title
- label.numberOfLines = 0
- return label
- }()
-
- let avatarView: UIView = {
- let view = UIView()
- view.backgroundColor = .clear
- return view
- }()
-
- let avatarButton: UIButton = {
- let button = HighlightDimmableButton()
- let boldFont = UIFont.systemFont(ofSize: 42)
- let configuration = UIImage.SymbolConfiguration(font: boldFont)
- let image = UIImage(systemName: "person.fill.viewfinder", withConfiguration: configuration)
-
- button.setImage(image?.withRenderingMode(UIImage.RenderingMode.alwaysTemplate), for: UIControl.State.normal)
- button.imageView?.tintColor = Asset.Colors.Label.secondary.color
- button.backgroundColor = Asset.Theme.Mastodon.secondaryGroupedSystemBackground.color
- button.layer.cornerRadius = 10
- button.clipsToBounds = true
-
- return button
- }()
-
- let plusIconImageView: UIImageView = {
- let icon = UIImageView()
- let image = Asset.Circles.plusCircleFill.image.withRenderingMode(.alwaysTemplate)
- icon.image = image
- icon.tintColor = Asset.Colors.Icon.plus.color
- icon.backgroundColor = UIColor(dynamicProvider: { collection in
- switch collection.userInterfaceStyle {
- case .dark:
- return Asset.Theme.Mastodon.secondaryGroupedSystemBackground.color
- default:
- return .white
- }
- })
- return icon
- }()
-
- let domainLabel: UILabel = {
- let label = UILabel()
- label.font = MastodonRegisterViewController.textFieldLabelFont
- label.textColor = Asset.Colors.Label.primary.color
- return label
- }()
-
- let usernameTextField: UITextField = {
- let textField = UITextField()
- textField.returnKeyType = .next
- textField.autocapitalizationType = .none
- textField.autocorrectionType = .no
- textField.backgroundColor = Asset.Theme.Mastodon.secondaryGroupedSystemBackground.color
- textField.textColor = Asset.Colors.Label.primary.color
- textField.attributedPlaceholder = NSAttributedString(string: L10n.Scene.Register.Input.Username.placeholder,
- attributes: [NSAttributedString.Key.foregroundColor: Asset.Colors.Label.secondary.color,
- NSAttributedString.Key.font: MastodonRegisterViewController.textFieldLabelFont])
- textField.borderStyle = UITextField.BorderStyle.roundedRect
- textField.font = MastodonRegisterViewController.textFieldLabelFont
- textField.leftView = {
- let containerView = UIView()
-
- let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: 5, height: textField.frame.height))
- paddingView.translatesAutoresizingMaskIntoConstraints = false
- containerView.addSubview(paddingView)
- NSLayoutConstraint.activate([
- paddingView.topAnchor.constraint(equalTo: containerView.topAnchor),
- paddingView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
- paddingView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
- paddingView.widthAnchor.constraint(equalToConstant: 5).priority(.defaultHigh),
- ])
-
- let label = UILabel()
- label.font = MastodonRegisterViewController.textFieldLabelFont
- label.textColor = Asset.Colors.Label.primary.color
- label.text = " @"
-
- label.translatesAutoresizingMaskIntoConstraints = false
- containerView.addSubview(label)
- NSLayoutConstraint.activate([
- label.topAnchor.constraint(equalTo: containerView.topAnchor),
- label.leadingAnchor.constraint(equalTo: paddingView.trailingAnchor),
- label.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
- label.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
- ])
- return containerView
- }()
- textField.leftViewMode = .always
- return textField
- }()
-
- let usernameErrorPromptLabel: UILabel = {
- let label = UILabel()
- let color = Asset.Colors.danger.color
- let font = MastodonRegisterViewController.errorPromptLabelFont
- return label
- }()
-
- let displayNameTextField: UITextField = {
- let textField = UITextField()
- textField.returnKeyType = .next
- textField.autocapitalizationType = .none
- textField.autocorrectionType = .no
- textField.backgroundColor = Asset.Theme.Mastodon.secondaryGroupedSystemBackground.color
- textField.textColor = Asset.Colors.Label.primary.color
- textField.attributedPlaceholder = NSAttributedString(string: L10n.Scene.Register.Input.DisplayName.placeholder,
- attributes: [NSAttributedString.Key.foregroundColor: Asset.Colors.Label.secondary.color,
- NSAttributedString.Key.font: MastodonRegisterViewController.textFieldLabelFont])
- textField.borderStyle = UITextField.BorderStyle.roundedRect
- let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: 5, height: textField.frame.height))
- textField.leftView = paddingView
- textField.leftViewMode = .always
- textField.font = MastodonRegisterViewController.textFieldLabelFont
- return textField
- }()
-
- let emailTextField: UITextField = {
- let textField = UITextField()
- textField.returnKeyType = .next
- textField.autocapitalizationType = .none
- textField.autocorrectionType = .no
- textField.keyboardType = .emailAddress
- textField.backgroundColor = Asset.Theme.Mastodon.secondaryGroupedSystemBackground.color
- textField.textColor = Asset.Colors.Label.primary.color
- textField.attributedPlaceholder = NSAttributedString(string: L10n.Scene.Register.Input.Email.placeholder,
- attributes: [NSAttributedString.Key.foregroundColor: Asset.Colors.Label.secondary.color,
- NSAttributedString.Key.font: MastodonRegisterViewController.textFieldLabelFont])
- textField.borderStyle = UITextField.BorderStyle.roundedRect
- let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: 5, height: textField.frame.height))
- textField.leftView = paddingView
- textField.leftViewMode = .always
- textField.font = MastodonRegisterViewController.textFieldLabelFont
- return textField
- }()
-
- let emailErrorPromptLabel: UILabel = {
- let label = UILabel()
- let color = Asset.Colors.danger.color
- let font = MastodonRegisterViewController.errorPromptLabelFont
- return label
- }()
-
- let passwordTextField: UITextField = {
- let textField = UITextField()
- textField.returnKeyType = .next // set to "Return" depends on if the last input field or not
- textField.autocapitalizationType = .none
- textField.autocorrectionType = .no
- textField.keyboardType = .asciiCapable
- textField.isSecureTextEntry = true
- textField.backgroundColor = Asset.Theme.Mastodon.secondaryGroupedSystemBackground.color
- textField.textColor = Asset.Colors.Label.primary.color
- textField.attributedPlaceholder = NSAttributedString(string: L10n.Scene.Register.Input.Password.placeholder,
- attributes: [NSAttributedString.Key.foregroundColor: Asset.Colors.Label.secondary.color,
- NSAttributedString.Key.font: MastodonRegisterViewController.textFieldLabelFont])
- textField.borderStyle = UITextField.BorderStyle.roundedRect
- let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: 5, height: textField.frame.height))
- textField.leftView = paddingView
- textField.leftViewMode = .always
- textField.font = MastodonRegisterViewController.textFieldLabelFont
- return textField
- }()
-
- let passwordCheckLabel: UILabel = {
- let label = UILabel()
- label.numberOfLines = 0
- return label
- }()
-
- let passwordErrorPromptLabel: UILabel = {
- let label = UILabel()
- let color = Asset.Colors.danger.color
- let font = MastodonRegisterViewController.errorPromptLabelFont
- return label
- }()
-
-
- lazy var reasonTextField: UITextField = {
- let textField = UITextField()
- textField.returnKeyType = .next // set to "Return" depends on if the last input field or not
- textField.autocapitalizationType = .none
- textField.autocorrectionType = .no
- textField.backgroundColor = Asset.Theme.Mastodon.secondaryGroupedSystemBackground.color
- textField.textColor = Asset.Colors.Label.primary.color
- textField.attributedPlaceholder = NSAttributedString(string: L10n.Scene.Register.Input.Invite.registrationUserInviteRequest,
- attributes: [NSAttributedString.Key.foregroundColor: Asset.Colors.Label.secondary.color,
- NSAttributedString.Key.font: MastodonRegisterViewController.textFieldLabelFont])
- textField.borderStyle = UITextField.BorderStyle.roundedRect
- let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: 5, height: textField.frame.height))
- textField.leftView = paddingView
- textField.leftViewMode = .always
- textField.font = MastodonRegisterViewController.textFieldLabelFont
- return textField
- }()
-
- let reasonErrorPromptLabel: UILabel = {
- let label = UILabel()
- let color = Asset.Colors.danger.color
- let font = MastodonRegisterViewController.errorPromptLabelFont
- return label
- }()
-
- let buttonContainer = UIView()
- let signUpButton: PrimaryActionButton = {
- let button = PrimaryActionButton()
- button.isEnabled = false
- button.setTitle(L10n.Common.Controls.Actions.continue, for: .normal)
- return button
+ let navigationActionView: NavigationActionView = {
+ let navigationActionView = NavigationActionView()
+ navigationActionView.backgroundColor = Asset.Scene.Onboarding.onboardingBackground.color
+ return navigationActionView
}()
deinit {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", (#file as NSString).lastPathComponent, #line, #function)
}
+
}
extension MastodonRegisterViewController {
@@ -288,518 +82,203 @@ extension MastodonRegisterViewController {
override func viewDidLoad() {
super.viewDidLoad()
+ navigationItem.leftBarButtonItem = UIBarButtonItem()
+
setupOnboardingAppearance()
- configureTitleLabel()
defer {
setupNavigationBarBackgroundView()
- configureFormLayout()
}
- avatarButton.menu = createMediaContextMenu()
- avatarButton.showsMenuAsPrimaryAction = true
+ 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),
+ ])
- domainLabel.text = "@" + viewModel.domain + " "
- domainLabel.sizeToFit()
- passwordCheckLabel.attributedText = MastodonRegisterViewModel.attributeStringForPassword(validateState: .empty)
- usernameTextField.rightView = domainLabel
- usernameTextField.rightViewMode = .always
- usernameTextField.delegate = self
- displayNameTextField.delegate = self
- emailTextField.delegate = self
- passwordTextField.delegate = self
-
- // gesture
- view.addGestureRecognizer(tapGestureRecognizer)
- tapGestureRecognizer.addTarget(self, action: #selector(tapGestureRecognizerHandler))
-
- // stackView
- stackView.axis = .vertical
- stackView.distribution = .fill
- stackView.spacing = 40
- stackView.layoutMargins = UIEdgeInsets(top: 20, left: 0, bottom: 26, right: 0)
- stackView.isLayoutMarginsRelativeArrangement = true
- stackView.addArrangedSubview(largeTitleLabel)
- stackView.addArrangedSubview(avatarView)
-
- let formTableStackView = UIStackView()
- stackView.addArrangedSubview(formTableStackView)
- formTableStackView.axis = .vertical
- formTableStackView.distribution = .fill
- formTableStackView.spacing = 40
-
- formTableStackView.addArrangedSubview(usernameTextField)
- formTableStackView.addArrangedSubview(displayNameTextField)
- formTableStackView.addArrangedSubview(emailTextField)
- formTableStackView.addArrangedSubview(passwordTextField)
- formTableStackView.addArrangedSubview(passwordCheckLabel)
- if viewModel.approvalRequired {
- formTableStackView.addArrangedSubview(reasonTextField)
+ navigationActionView.translatesAutoresizingMaskIntoConstraints = false
+ view.addSubview(navigationActionView)
+ defer {
+ view.bringSubviewToFront(navigationActionView)
}
-
- usernameErrorPromptLabel.translatesAutoresizingMaskIntoConstraints = false
- formTableStackView.addSubview(usernameErrorPromptLabel)
NSLayoutConstraint.activate([
- usernameErrorPromptLabel.topAnchor.constraint(equalTo: usernameTextField.bottomAnchor, constant: 6),
- usernameErrorPromptLabel.leadingAnchor.constraint(equalTo: usernameTextField.leadingAnchor),
- usernameErrorPromptLabel.trailingAnchor.constraint(equalTo: usernameTextField.trailingAnchor),
+ navigationActionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
+ navigationActionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
+ view.bottomAnchor.constraint(equalTo: navigationActionView.bottomAnchor),
])
- emailErrorPromptLabel.translatesAutoresizingMaskIntoConstraints = false
- formTableStackView.addSubview(emailErrorPromptLabel)
- NSLayoutConstraint.activate([
- emailErrorPromptLabel.topAnchor.constraint(equalTo: emailTextField.bottomAnchor, constant: 6),
- emailErrorPromptLabel.leadingAnchor.constraint(equalTo: emailTextField.leadingAnchor),
- emailErrorPromptLabel.trailingAnchor.constraint(equalTo: emailTextField.trailingAnchor),
- ])
+ navigationActionView
+ .observe(\.bounds, options: [.initial, .new]) { [weak self] navigationActionView, _ in
+ guard let self = self else { return }
+ let inset = navigationActionView.frame.height
+ self.tableView.contentInset.bottom = inset
+ }
+ .store(in: &observations)
- passwordErrorPromptLabel.translatesAutoresizingMaskIntoConstraints = false
- formTableStackView.addSubview(passwordErrorPromptLabel)
- NSLayoutConstraint.activate([
- passwordErrorPromptLabel.topAnchor.constraint(equalTo: passwordCheckLabel.bottomAnchor, constant: 2),
- passwordErrorPromptLabel.leadingAnchor.constraint(equalTo: passwordTextField.leadingAnchor),
- passwordErrorPromptLabel.trailingAnchor.constraint(equalTo: passwordTextField.trailingAnchor),
- ])
-
- // scrollView
- view.addSubview(scrollView)
- NSLayoutConstraint.activate([
- scrollView.frameLayoutGuide.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor),
- scrollView.frameLayoutGuide.leadingAnchor.constraint(equalTo: view.readableContentGuide.leadingAnchor),
- view.readableContentGuide.trailingAnchor.constraint(equalTo: scrollView.frameLayoutGuide.trailingAnchor),
- scrollView.frameLayoutGuide.bottomAnchor.constraint(equalTo: view.layoutMarginsGuide.bottomAnchor),
- scrollView.frameLayoutGuide.widthAnchor.constraint(equalTo: scrollView.contentLayoutGuide.widthAnchor),
- ])
-
- // stackView
- scrollView.addSubview(stackView)
- stackView.translatesAutoresizingMaskIntoConstraints = false
- NSLayoutConstraint.activate([
- stackView.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor),
- stackView.leadingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor),
- stackView.trailingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor),
- stackView.widthAnchor.constraint(equalTo: scrollView.contentLayoutGuide.widthAnchor),
- scrollView.contentLayoutGuide.bottomAnchor.constraint(equalTo: stackView.bottomAnchor),
- ])
-
- // photoview
- avatarView.translatesAutoresizingMaskIntoConstraints = false
- avatarView.addSubview(avatarButton)
- NSLayoutConstraint.activate([
- avatarView.heightAnchor.constraint(equalToConstant: 92).priority(.required - 1),
- ])
- avatarButton.translatesAutoresizingMaskIntoConstraints = false
- NSLayoutConstraint.activate([
- avatarButton.heightAnchor.constraint(equalToConstant: 92).priority(.required - 1),
- avatarButton.widthAnchor.constraint(equalToConstant: 92).priority(.required - 1),
- avatarButton.leadingAnchor.constraint(greaterThanOrEqualTo: avatarView.leadingAnchor).priority(.required - 1),
- avatarView.trailingAnchor.constraint(greaterThanOrEqualTo: avatarButton.trailingAnchor).priority(.required - 1),
- avatarButton.centerXAnchor.constraint(equalTo: avatarView.centerXAnchor),
- avatarButton.centerYAnchor.constraint(equalTo: avatarView.centerYAnchor),
- ])
-
- plusIconImageView.translatesAutoresizingMaskIntoConstraints = false
- avatarView.addSubview(plusIconImageView)
- NSLayoutConstraint.activate([
- plusIconImageView.centerXAnchor.constraint(equalTo: avatarButton.trailingAnchor),
- plusIconImageView.centerYAnchor.constraint(equalTo: avatarButton.bottomAnchor),
- ])
-
- // textfield
- NSLayoutConstraint.activate([
- usernameTextField.heightAnchor.constraint(equalToConstant: 50).priority(.required - 1),
- displayNameTextField.heightAnchor.constraint(equalToConstant: 50).priority(.required - 1),
- emailTextField.heightAnchor.constraint(equalToConstant: 50).priority(.required - 1),
- passwordTextField.heightAnchor.constraint(equalToConstant: 50).priority(.required - 1),
- ])
-
- // password
- formTableStackView.setCustomSpacing(6, after: passwordTextField)
- formTableStackView.setCustomSpacing(32, after: passwordCheckLabel)
+ navigationActionView.backButton.addTarget(self, action: #selector(MastodonRegisterViewController.backButtonPressed(_:)), for: .touchUpInside)
+ navigationActionView.nextButton.addTarget(self, action: #selector(MastodonRegisterViewController.nextButtonPressed(_:)), for: .touchUpInside)
- // return
- if viewModel.approvalRequired {
- reasonTextField.returnKeyType = .done
- } else {
- passwordTextField.returnKeyType = .done
- }
-
- // button
- formTableStackView.addArrangedSubview(buttonContainer)
- signUpButton.translatesAutoresizingMaskIntoConstraints = false
- buttonContainer.addSubview(signUpButton)
- NSLayoutConstraint.activate([
- signUpButton.topAnchor.constraint(equalTo: buttonContainer.topAnchor),
- signUpButton.leadingAnchor.constraint(equalTo: buttonContainer.leadingAnchor),
- buttonContainer.trailingAnchor.constraint(equalTo: signUpButton.trailingAnchor),
- buttonContainer.bottomAnchor.constraint(equalTo: signUpButton.bottomAnchor),
- signUpButton.heightAnchor.constraint(equalToConstant: MastodonRegisterViewController.actionButtonHeight).priority(.required - 1),
- buttonContainer.heightAnchor.constraint(equalToConstant: MastodonRegisterViewController.actionButtonHeight).priority(.required - 1),
- ])
- signUpButton.setContentHuggingPriority(.defaultLow, for: .horizontal)
- signUpButton.setContentHuggingPriority(.defaultLow, for: .vertical)
- signUpButton.setContentCompressionResistancePriority(.required - 1, for: .vertical)
- signUpButton.setContentCompressionResistancePriority(.required - 1, for: .horizontal)
- buttonContainer.setContentCompressionResistancePriority(.required - 1, for: .vertical)
-
- Publishers.CombineLatest(
- KeyboardResponderService.shared.state.eraseToAnyPublisher(),
- KeyboardResponderService.shared.endFrame.eraseToAnyPublisher()
- )
- .sink(receiveValue: { [weak self] state, endFrame in
- guard let self = self else { return }
-
- guard state == .dock else {
- self.scrollView.contentInset.bottom = 0.0
- self.scrollView.verticalScrollIndicatorInsets.bottom = 0.0
- return
- }
-
- let contentFrame = self.view.convert(self.scrollView.frame, to: nil)
- let padding = contentFrame.maxY - endFrame.minY
- guard padding > 0 else {
- self.scrollView.contentInset.bottom = 0.0
- self.scrollView.verticalScrollIndicatorInsets.bottom = 0.0
- return
- }
-
- self.scrollView.contentInset.bottom = padding + 16
- self.scrollView.verticalScrollIndicatorInsets.bottom = padding + 16
-
- if self.passwordTextField.isFirstResponder {
- let contentFrame = self.buttonContainer.convert(self.signUpButton.frame, to: nil)
- let labelPadding = contentFrame.maxY - endFrame.minY
- let contentOffsetY = self.scrollView.contentOffset.y
- DispatchQueue.main.async {
- self.scrollView.setContentOffset(CGPoint(x: 0, y: contentOffsetY + labelPadding + 16.0), animated: true)
- }
- }
- })
- .store(in: &disposeBag)
-
- avatarButton.publisher(for: \.isHighlighted, options: .new)
- .receive(on: DispatchQueue.main)
- .sink { [weak self] isHighlighted in
- guard let self = self else { return }
- let alpha: CGFloat = isHighlighted ? 0.6 : 1
- self.plusIconImageView.alpha = alpha
- }
- .store(in: &disposeBag)
-
- viewModel.isRegistering
- .receive(on: DispatchQueue.main)
- .sink { [weak self] isRegistering in
- guard let self = self else { return }
- isRegistering ? self.signUpButton.showLoading() : self.signUpButton.stopLoading()
- }
- .store(in: &disposeBag)
-
- viewModel.usernameValidateState
- .receive(on: DispatchQueue.main)
- .sink { [weak self] validateState in
- guard let self = self else { return }
- self.setTextFieldValidAppearance(self.usernameTextField, validateState: validateState)
- }
- .store(in: &disposeBag)
- viewModel.usernameErrorPrompt
- .receive(on: DispatchQueue.main)
- .sink { [weak self] prompt in
- guard let self = self else { return }
- self.usernameErrorPromptLabel.attributedText = prompt
- }
- .store(in: &disposeBag)
- viewModel.displayNameValidateState
- .receive(on: DispatchQueue.main)
- .sink { [weak self] validateState in
- guard let self = self else { return }
- self.setTextFieldValidAppearance(self.displayNameTextField, validateState: validateState)
- }
- .store(in: &disposeBag)
- viewModel.emailValidateState
- .receive(on: DispatchQueue.main)
- .sink { [weak self] validateState in
- guard let self = self else { return }
- self.setTextFieldValidAppearance(self.emailTextField, validateState: validateState)
- }
- .store(in: &disposeBag)
- viewModel.emailErrorPrompt
- .receive(on: DispatchQueue.main)
- .sink { [weak self] prompt in
- guard let self = self else { return }
- self.emailErrorPromptLabel.attributedText = prompt
- }
- .store(in: &disposeBag)
- viewModel.passwordValidateState
- .receive(on: DispatchQueue.main)
- .sink { [weak self] validateState in
- guard let self = self else { return }
- self.setTextFieldValidAppearance(self.passwordTextField, validateState: validateState)
- self.passwordCheckLabel.attributedText = MastodonRegisterViewModel.attributeStringForPassword(validateState: validateState)
- }
- .store(in: &disposeBag)
- viewModel.passwordErrorPrompt
- .receive(on: DispatchQueue.main)
- .sink { [weak self] prompt in
- guard let self = self else { return }
- self.passwordErrorPromptLabel.attributedText = prompt
- }
- .store(in: &disposeBag)
- viewModel.reasonErrorPrompt
- .receive(on: DispatchQueue.main)
- .sink { [weak self] prompt in
- guard let self = self else { return }
- self.reasonErrorPromptLabel.attributedText = prompt
- }
- .store(in: &disposeBag)
-
- viewModel.isAllValid
+ viewModel.$isAllValid
.receive(on: DispatchQueue.main)
.sink { [weak self] isAllValid in
guard let self = self else { return }
- self.signUpButton.isEnabled = isAllValid
+ self.navigationActionView.nextButton.isEnabled = isAllValid
}
.store(in: &disposeBag)
+
+ viewModel.setupDiffableDataSource(tableView: tableView)
+
+// KeyboardResponderService
+// .configure(
+// scrollView: tableView,
+// layoutNeedsUpdate: viewModel.viewDidAppear.eraseToAnyPublisher()
+// )
+// .store(in: &disposeBag)
- viewModel.error
- .receive(on: DispatchQueue.main)
- .sink { [weak self] error in
- guard let self = self else { return }
- guard let error = error as? Mastodon.API.Error else { return }
- let alertController = UIAlertController(for: error, title: "Sign Up Failure", preferredStyle: .alert)
- let okAction = UIAlertAction(title: L10n.Common.Controls.Actions.ok, style: .default, handler: nil)
- alertController.addAction(okAction)
- self.coordinator.present(
- scene: .alertController(alertController: alertController),
- from: nil,
- transition: .alertController(animated: true, completion: nil)
- )
- }
- .store(in: &disposeBag)
+ // gesture
+ view.addGestureRecognizer(tapGestureRecognizer)
+ tapGestureRecognizer.addTarget(self, action: #selector(tapGestureRecognizerHandler))
- viewModel.avatarImage
+// // return
+// if viewModel.approvalRequired {
+// reasonTextField.returnKeyType = .done
+// } else {
+// passwordTextField.returnKeyType = .done
+// }
+//
+// viewModel.usernameValidateState
+// .receive(on: DispatchQueue.main)
+// .sink { [weak self] validateState in
+// guard let self = self else { return }
+// self.setTextFieldValidAppearance(self.usernameTextField, validateState: validateState)
+// }
+// .store(in: &disposeBag)
+// viewModel.usernameErrorPrompt
+// .receive(on: DispatchQueue.main)
+// .sink { [weak self] prompt in
+// guard let self = self else { return }
+// self.usernameErrorPromptLabel.attributedText = prompt
+// }
+// .store(in: &disposeBag)
+// viewModel.displayNameValidateState
+// .receive(on: DispatchQueue.main)
+// .sink { [weak self] validateState in
+// guard let self = self else { return }
+// self.setTextFieldValidAppearance(self.displayNameTextField, validateState: validateState)
+// }
+// .store(in: &disposeBag)
+// viewModel.emailValidateState
+// .receive(on: DispatchQueue.main)
+// .sink { [weak self] validateState in
+// guard let self = self else { return }
+// self.setTextFieldValidAppearance(self.emailTextField, validateState: validateState)
+// }
+// .store(in: &disposeBag)
+// viewModel.emailErrorPrompt
+// .receive(on: DispatchQueue.main)
+// .sink { [weak self] prompt in
+// guard let self = self else { return }
+// self.emailErrorPromptLabel.attributedText = prompt
+// }
+// .store(in: &disposeBag)
+// viewModel.passwordValidateState
+// .receive(on: DispatchQueue.main)
+// .sink { [weak self] validateState in
+// guard let self = self else { return }
+// self.setTextFieldValidAppearance(self.passwordTextField, validateState: validateState)
+// self.passwordCheckLabel.attributedText = MastodonRegisterViewModel.attributeStringForPassword(validateState: validateState)
+// }
+// .store(in: &disposeBag)
+// viewModel.passwordErrorPrompt
+// .receive(on: DispatchQueue.main)
+// .sink { [weak self] prompt in
+// guard let self = self else { return }
+// self.passwordErrorPromptLabel.attributedText = prompt
+// }
+// .store(in: &disposeBag)
+// viewModel.reasonErrorPrompt
+// .receive(on: DispatchQueue.main)
+// .sink { [weak self] prompt in
+// guard let self = self else { return }
+// self.reasonErrorPromptLabel.attributedText = prompt
+// }
+// .store(in: &disposeBag)
+// viewModel.error
+// .receive(on: DispatchQueue.main)
+// .sink { [weak self] error in
+// guard let self = self else { return }
+// guard let error = error as? Mastodon.API.Error else { return }
+// let alertController = UIAlertController(for: error, title: "Sign Up Failure", preferredStyle: .alert)
+// let okAction = UIAlertAction(title: L10n.Common.Controls.Actions.ok, style: .default, handler: nil)
+// alertController.addAction(okAction)
+// self.coordinator.present(
+// scene: .alertController(alertController: alertController),
+// from: nil,
+// transition: .alertController(animated: true, completion: nil)
+// )
+// }
+// .store(in: &disposeBag)
+//
+
+ viewModel.avatarMediaMenuActionPublisher
.receive(on: DispatchQueue.main)
- .sink{ [weak self] image in
+ .sink { [weak self] action in
guard let self = self else { return }
- self.avatarButton.menu = self.createMediaContextMenu()
- if let avatar = image {
- self.avatarButton.setImage(avatar, for: .normal)
- } else {
- let boldFont = UIFont.systemFont(ofSize: 42)
- let configuration = UIImage.SymbolConfiguration(font: boldFont)
- let image = UIImage(systemName: "person.fill.viewfinder", withConfiguration: configuration)
- self.avatarButton.setImage(image?.withRenderingMode(UIImage.RenderingMode.alwaysTemplate), for: UIControl.State.normal)
+ switch action {
+ case .photoLibrary:
+ self.present(self.imagePicker, animated: true, completion: nil)
+ case .camera:
+ self.present(self.imagePickerController, animated: true, completion: nil)
+ case .browse:
+ self.present(self.documentPickerController, animated: true, completion: nil)
+ case .delete:
+ self.viewModel.avatarImage = nil
}
}
.store(in: &disposeBag)
- NotificationCenter.default
- .publisher(for: UITextField.textDidChangeNotification, object: usernameTextField)
+
+ viewModel.$isRegistering
.receive(on: DispatchQueue.main)
- .sink { [weak self] _ in
+ .sink { [weak self] isRegistering in
guard let self = self else { return }
- self.viewModel.username.value = self.usernameTextField.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
+ isRegistering ? self.navigationActionView.nextButton.showLoading() : self.navigationActionView.nextButton.stopLoading()
}
.store(in: &disposeBag)
-
- NotificationCenter.default
- .publisher(for: UITextField.textDidChangeNotification, object: displayNameTextField)
- .receive(on: DispatchQueue.main)
- .sink { [weak self] _ in
- guard let self = self else { return }
- self.viewModel.displayName.value = self.displayNameTextField.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
- }
- .store(in: &disposeBag)
-
- NotificationCenter.default
- .publisher(for: UITextField.textDidChangeNotification, object: emailTextField)
- .receive(on: DispatchQueue.main)
- .sink { [weak self] _ in
- guard let self = self else { return }
- self.viewModel.email.value = self.emailTextField.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
- }
- .store(in: &disposeBag)
-
- NotificationCenter.default
- .publisher(for: UITextField.textDidChangeNotification, object: passwordTextField)
- .receive(on: DispatchQueue.main)
- .sink { [weak self] _ in
- guard let self = self else { return }
- self.viewModel.password.value = self.passwordTextField.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
- }
- .store(in: &disposeBag)
-
- if viewModel.approvalRequired {
- reasonTextField.delegate = self
- NSLayoutConstraint.activate([
- reasonTextField.heightAnchor.constraint(equalToConstant: 50).priority(.defaultHigh),
- ])
- reasonErrorPromptLabel.translatesAutoresizingMaskIntoConstraints = false
- stackView.addSubview(reasonErrorPromptLabel)
- NSLayoutConstraint.activate([
- reasonErrorPromptLabel.topAnchor.constraint(equalTo: reasonTextField.bottomAnchor, constant: 6),
- reasonErrorPromptLabel.leadingAnchor.constraint(equalTo: reasonTextField.leadingAnchor),
- reasonErrorPromptLabel.trailingAnchor.constraint(equalTo: reasonTextField.trailingAnchor),
- ])
-
- viewModel.reasonValidateState
- .receive(on: DispatchQueue.main)
- .sink { [weak self] validateState in
- guard let self = self else { return }
- self.setTextFieldValidAppearance(self.reasonTextField, validateState: validateState)
- }
- .store(in: &disposeBag)
- NotificationCenter.default
- .publisher(for: UITextField.textDidChangeNotification, object: reasonTextField)
- .receive(on: DispatchQueue.main)
- .sink { [weak self] _ in
- guard let self = self else { return }
- self.viewModel.reason.value = self.reasonTextField.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
- }
- .store(in: &disposeBag)
- }
-
- signUpButton.addTarget(self, action: #selector(MastodonRegisterViewController.signUpButtonPressed(_:)), for: .touchUpInside)
}
- override func viewDidLayoutSubviews() {
- super.viewDidLayoutSubviews()
- plusIconImageView.layer.cornerRadius = plusIconImageView.frame.width / 2
- plusIconImageView.layer.masksToBounds = true
- }
-
- override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
- super.traitCollectionDidChange(previousTraitCollection)
+ override func viewDidAppear(_ animated: Bool) {
+ super.viewDidAppear(animated)
- configureTitleLabel()
- configureFormLayout()
- }
-}
-
-extension MastodonRegisterViewController: UITextFieldDelegate {
- func textFieldDidBeginEditing(_ textField: UITextField) {
- let text = textField.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
-
- switch textField {
- case usernameTextField:
- viewModel.username.value = text
- case displayNameTextField:
- viewModel.displayName.value = text
- case emailTextField:
- viewModel.email.value = text
- case passwordTextField:
- viewModel.password.value = text
- case reasonTextField:
- viewModel.reason.value = text
- default:
- break
- }
+ viewModel.viewDidAppear.send()
}
- func textFieldDidEndEditing(_ textField: UITextField) {
- let text = textField.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
-
- switch textField {
- case usernameTextField:
- viewModel.username.value = text
- case displayNameTextField:
- viewModel.displayName.value = text
- case emailTextField:
- viewModel.email.value = text
- case passwordTextField:
- viewModel.password.value = text
- case reasonTextField:
- viewModel.reason.value = text
- default:
- break
- }
- }
-
- func textFieldShouldReturn(_ textField: UITextField) -> Bool {
- switch textField {
- case usernameTextField:
- displayNameTextField.becomeFirstResponder()
- case displayNameTextField:
- emailTextField.becomeFirstResponder()
- case emailTextField:
- passwordTextField.becomeFirstResponder()
- case passwordTextField:
- if viewModel.approvalRequired {
- reasonTextField.becomeFirstResponder()
- } else {
- passwordTextField.resignFirstResponder()
- }
- case reasonTextField:
- reasonTextField.resignFirstResponder()
- default:
- break
- }
- return true
- }
-
- func showShadowWithColor(color: UIColor, textField: UITextField) {
- // To apply Shadow
- textField.layer.shadowOpacity = 1
- textField.layer.shadowRadius = 2.0
- textField.layer.shadowOffset = CGSize.zero
- textField.layer.shadowColor = color.cgColor
- // textField.layer.shadowPath = UIBezierPath(roundedRect: textField.bounds, byRoundingCorners: .allCorners, cornerRadii: CGSize(width: 2.0, height: 2.0)).cgPath
- }
-
- private func setTextFieldValidAppearance(_ textField: UITextField, validateState: MastodonRegisterViewModel.ValidateState) {
- switch validateState {
- case .empty:
- showShadowWithColor(color: textField.isFirstResponder ? Asset.Colors.brandBlue.color : .clear, textField: textField)
- case .valid:
- showShadowWithColor(color: Asset.Colors.TextField.valid.color, textField: textField)
- case .invalid:
- showShadowWithColor(color: Asset.Colors.TextField.invalid.color, textField: textField)
- }
- }
}
extension MastodonRegisterViewController {
- private func configureTitleLabel() {
- switch traitCollection.horizontalSizeClass {
- case .regular:
- navigationItem.largeTitleDisplayMode = .always
- navigationItem.title = L10n.Scene.ServerPicker.title.replacingOccurrences(of: "\n", with: " ")
- largeTitleLabel.isHidden = true
- default:
- navigationItem.largeTitleDisplayMode = .never
- navigationItem.title = nil
- largeTitleLabel.isHidden = false
- }
- }
- private func configureFormLayout() {
- switch traitCollection.horizontalSizeClass {
- case .regular:
- stackView.axis = .horizontal
- stackView.distribution = .fillProportionally
- default:
- stackView.axis = .vertical
- stackView.distribution = .fill
- }
- }
-
- private func configureMargin() {
-
- }
-}
-
-extension MastodonRegisterViewController {
@objc private func tapGestureRecognizerHandler(_ sender: UITapGestureRecognizer) {
view.endEditing(true)
}
- @objc private func signUpButtonPressed(_ sender: UIButton) {
- os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", (#file as NSString).lastPathComponent, #line, #function)
- guard viewModel.isAllValid.value else { return }
-
- guard !viewModel.isRegistering.value else { return }
- viewModel.isRegistering.value = true
+ @objc private func backButtonPressed(_ sender: UIButton) {
+ logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)")
+ navigationController?.popViewController(animated: true)
+ }
- let username = viewModel.username.value
- let email = viewModel.email.value
- let password = viewModel.password.value
+ @objc private func nextButtonPressed(_ sender: UIButton) {
+ logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)")
+
+ guard viewModel.isAllValid else { return }
+
+ guard !viewModel.isRegistering else { return }
+ viewModel.isRegistering = true
+
+ let username = viewModel.username
+ let email = viewModel.email
+ let password = viewModel.password
+ let reason = viewModel.reason
let locale: String = {
guard let url = Bundle.main.url(forResource: "local-codes", withExtension: "json"),
@@ -814,7 +293,7 @@ extension MastodonRegisterViewController {
guard localCode[code] != nil else { return "en" }
return code
}()
-
+
// pick device preferred language
guard let identifier = Locale.preferredLanguages.first else {
return fallbackLanguageCode
@@ -843,19 +322,19 @@ extension MastodonRegisterViewController {
return languageCode
}
return firstMatchExtendCode
-
+
}()
let query = Mastodon.API.Account.RegisterQuery(
- reason: viewModel.reason.value,
+ reason: reason,
username: username,
email: email,
password: password,
agreement: true, // user confirmed in the server rules scene
locale: locale
)
-
+
var retryCount = 0
-
+
// register without show server rules
context.apiService.accountRegister(
domain: viewModel.domain,
@@ -864,7 +343,7 @@ extension MastodonRegisterViewController {
)
.tryCatch { [weak self] error -> AnyPublisher, Error> in
guard let self = self else { throw error }
- guard let error = self.viewModel.error.value as? Mastodon.API.Error,
+ guard let error = self.viewModel.error as? Mastodon.API.Error,
case let .generic(errorEntity) = error.mastodonError,
errorEntity.error == "Validation failed: Locale is not included in the list"
else {
@@ -891,10 +370,10 @@ extension MastodonRegisterViewController {
.receive(on: DispatchQueue.main)
.sink { [weak self] completion in
guard let self = self else { return }
- self.viewModel.isRegistering.value = false
+ self.viewModel.isRegistering = false
switch completion {
case .failure(let error):
- self.viewModel.error.send(error)
+ self.viewModel.error = error
case .finished:
break
}
@@ -902,9 +381,9 @@ extension MastodonRegisterViewController {
guard let self = self else { return }
let userToken = response.value
let updateCredentialQuery: Mastodon.API.Account.UpdateCredentialQuery = {
- let displayName: String? = self.viewModel.displayName.value.isEmpty ? nil : self.viewModel.displayName.value
+ let displayName: String? = self.viewModel.name.isEmpty ? nil : self.viewModel.name
let avatar: Mastodon.Query.MediaAttachment? = {
- guard let avatarImage = self.viewModel.avatarImage.value else { return nil }
+ guard let avatarImage = self.viewModel.avatarImage else { return nil }
guard avatarImage.size.width <= MastodonRegisterViewController.avatarImageMaxSizeInPixel.width else {
return .png(avatarImage.af.imageScaled(to: MastodonRegisterViewController.avatarImageMaxSizeInPixel).pngData())
}
@@ -920,4 +399,67 @@ extension MastodonRegisterViewController {
}
.store(in: &disposeBag)
}
+
+}
+
+extension MastodonRegisterViewController: UITextFieldDelegate {
+// func textFieldDidBeginEditing(_ textField: UITextField) {
+// let text = textField.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
+//
+// switch textField {
+// case usernameTextField:
+// viewModel.username.value = text
+// case displayNameTextField:
+// viewModel.displayName.value = text
+// case emailTextField:
+// viewModel.email.value = text
+// case passwordTextField:
+// viewModel.password.value = text
+// case reasonTextField:
+// viewModel.reason.value = text
+// default:
+// break
+// }
+// }
+//
+// func textFieldDidEndEditing(_ textField: UITextField) {
+// let text = textField.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
+//
+// switch textField {
+// case usernameTextField:
+// viewModel.username.value = text
+// case displayNameTextField:
+// viewModel.displayName.value = text
+// case emailTextField:
+// viewModel.email.value = text
+// case passwordTextField:
+// viewModel.password.value = text
+// case reasonTextField:
+// viewModel.reason.value = text
+// default:
+// break
+// }
+// }
+//
+// func textFieldShouldReturn(_ textField: UITextField) -> Bool {
+// switch textField {
+// case usernameTextField:
+// displayNameTextField.becomeFirstResponder()
+// case displayNameTextField:
+// emailTextField.becomeFirstResponder()
+// case emailTextField:
+// passwordTextField.becomeFirstResponder()
+// case passwordTextField:
+// if viewModel.approvalRequired {
+// reasonTextField.becomeFirstResponder()
+// } else {
+// passwordTextField.resignFirstResponder()
+// }
+// case reasonTextField:
+// reasonTextField.resignFirstResponder()
+// default:
+// break
+// }
+// return true
+// }
}
diff --git a/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewModel+Diffable.swift b/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewModel+Diffable.swift
new file mode 100644
index 00000000..e075f47c
--- /dev/null
+++ b/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewModel+Diffable.swift
@@ -0,0 +1,231 @@
+//
+// MastodonRegisterViewModel+Diffable.swift
+// Mastodon
+//
+// Created by MainasuK on 2022-1-5.
+//
+
+import UIKit
+import Combine
+
+extension MastodonRegisterViewModel {
+ func setupDiffableDataSource(
+ tableView: UITableView
+ ) {
+ tableView.register(OnboardingHeadlineTableViewCell.self, forCellReuseIdentifier: String(describing: OnboardingHeadlineTableViewCell.self))
+ tableView.register(MastodonRegisterAvatarTableViewCell.self, forCellReuseIdentifier: String(describing: MastodonRegisterAvatarTableViewCell.self))
+ tableView.register(MastodonRegisterTextFieldTableViewCell.self, forCellReuseIdentifier: String(describing: MastodonRegisterTextFieldTableViewCell.self))
+ tableView.register(MastodonRegisterPasswordHintTableViewCell.self, forCellReuseIdentifier: String(describing: MastodonRegisterPasswordHintTableViewCell.self))
+
+ diffableDataSource = UITableViewDiffableDataSource(tableView: tableView) { tableView, indexPath, item in
+ switch item {
+ case .header:
+ let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: OnboardingHeadlineTableViewCell.self), for: indexPath) as! OnboardingHeadlineTableViewCell
+ cell.titleLabel.text = L10n.Scene.Register.title
+ cell.subTitleLabel.isHidden = true
+ return cell
+ case .avatar:
+ let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: MastodonRegisterAvatarTableViewCell.self), for: indexPath) as! MastodonRegisterAvatarTableViewCell
+ self.configureAvatar(cell: cell)
+ return cell
+ case .name:
+ let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: MastodonRegisterTextFieldTableViewCell.self), for: indexPath) as! MastodonRegisterTextFieldTableViewCell
+ cell.setupTextViewPlaceholder(text: L10n.Scene.Register.Input.DisplayName.placeholder)
+ cell.textField.keyboardType = .default
+ cell.textField.autocapitalizationType = .words
+ cell.textField.text = self.name
+ NotificationCenter.default.publisher(for: UITextField.textDidChangeNotification, object: cell.textField)
+ .receive(on: DispatchQueue.main)
+ .compactMap { notification in
+ guard let textField = notification.object as? UITextField else {
+ assertionFailure()
+ return nil
+ }
+ return textField.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
+ }
+ .assign(to: \.name, on: self)
+ .store(in: &cell.disposeBag)
+ return cell
+ case .username:
+ let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: MastodonRegisterTextFieldTableViewCell.self), for: indexPath) as! MastodonRegisterTextFieldTableViewCell
+ cell.setupTextViewRightView(text: "@" + self.domain)
+ cell.setupTextViewPlaceholder(text: L10n.Scene.Register.Input.Username.placeholder)
+ cell.textField.keyboardType = .alphabet
+ cell.textField.autocorrectionType = .no
+ cell.textField.text = self.username
+ NotificationCenter.default.publisher(for: UITextField.textDidChangeNotification, object: cell.textField)
+ .receive(on: DispatchQueue.main)
+ .compactMap { notification in
+ guard let textField = notification.object as? UITextField else {
+ assertionFailure()
+ return nil
+ }
+ return textField.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
+ }
+ .assign(to: \.username, on: self)
+ .store(in: &cell.disposeBag)
+ self.configureTextFieldCell(cell: cell, validateState: self.$usernameValidateState)
+ return cell
+ case .email:
+ let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: MastodonRegisterTextFieldTableViewCell.self), for: indexPath) as! MastodonRegisterTextFieldTableViewCell
+ cell.setupTextViewPlaceholder(text: L10n.Scene.Register.Input.Email.placeholder)
+ cell.textField.keyboardType = .emailAddress
+ cell.textField.autocorrectionType = .no
+ cell.textField.text = self.email
+ NotificationCenter.default.publisher(for: UITextField.textDidChangeNotification, object: cell.textField)
+ .receive(on: DispatchQueue.main)
+ .compactMap { notification in
+ guard let textField = notification.object as? UITextField else {
+ assertionFailure()
+ return nil
+ }
+ return textField.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
+ }
+ .assign(to: \.email, on: self)
+ .store(in: &cell.disposeBag)
+ self.configureTextFieldCell(cell: cell, validateState: self.$emailValidateState)
+ return cell
+ case .password:
+ let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: MastodonRegisterTextFieldTableViewCell.self), for: indexPath) as! MastodonRegisterTextFieldTableViewCell
+ cell.setupTextViewPlaceholder(text: L10n.Scene.Register.Input.Password.placeholder)
+ cell.textField.keyboardType = .alphabet
+ cell.textField.autocorrectionType = .no
+ cell.textField.isSecureTextEntry = true
+ cell.textField.text = self.password
+ NotificationCenter.default.publisher(for: UITextField.textDidChangeNotification, object: cell.textField)
+ .receive(on: DispatchQueue.main)
+ .compactMap { notification in
+ guard let textField = notification.object as? UITextField else {
+ assertionFailure()
+ return nil
+ }
+ return textField.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
+ }
+ .assign(to: \.password, on: self)
+ .store(in: &cell.disposeBag)
+ self.configureTextFieldCell(cell: cell, validateState: self.$passwordValidateState)
+ return cell
+ case .hint:
+ let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: MastodonRegisterPasswordHintTableViewCell.self), for: indexPath) as! MastodonRegisterPasswordHintTableViewCell
+ return cell
+ case .reason:
+ let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: MastodonRegisterTextFieldTableViewCell.self), for: indexPath) as! MastodonRegisterTextFieldTableViewCell
+ cell.setupTextViewPlaceholder(text: L10n.Scene.Register.Input.Invite.registrationUserInviteRequest)
+ cell.textField.keyboardType = .default
+ cell.textField.text = self.reason
+ NotificationCenter.default.publisher(for: UITextField.textDidChangeNotification, object: cell.textField)
+ .receive(on: DispatchQueue.main)
+ .compactMap { notification in
+ guard let textField = notification.object as? UITextField else {
+ assertionFailure()
+ return nil
+ }
+ return textField.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
+ }
+ .assign(to: \.reason, on: self)
+ .store(in: &cell.disposeBag)
+ self.configureTextFieldCell(cell: cell, validateState: self.$reasonValidateState)
+ return cell
+ default:
+ assertionFailure()
+ return UITableViewCell()
+ }
+ }
+
+ var snapshot = NSDiffableDataSourceSnapshot()
+ snapshot.appendSections([.main])
+ snapshot.appendItems([.header], toSection: .main)
+ snapshot.appendItems([.avatar, .name, .username, .email, .password, .hint], toSection: .main)
+ if approvalRequired {
+ snapshot.appendItems([.reason], toSection: .main)
+ }
+ diffableDataSource?.applySnapshot(snapshot, animated: false, completion: nil)
+ }
+}
+
+extension MastodonRegisterViewModel {
+ private func configureAvatar(cell: MastodonRegisterAvatarTableViewCell) {
+ self.$avatarImage
+ .receive(on: DispatchQueue.main)
+ .sink { [weak self, weak cell] image in
+ guard let self = self else { return }
+ guard let cell = cell else { return }
+ let image = image ?? Asset.Scene.Onboarding.avatarPlaceholder.image
+ cell.avatarButton.setImage(image, for: .normal)
+ cell.avatarButton.menu = self.createAvatarMediaContextMenu()
+ cell.avatarButton.showsMenuAsPrimaryAction = true
+ }
+ .store(in: &cell.disposeBag)
+ }
+
+ enum AvatarMediaMenuAction {
+ case photoLibrary
+ case camera
+ case browse
+ case delete
+ }
+
+ private func createAvatarMediaContextMenu() -> UIMenu {
+ var children: [UIMenuElement] = []
+
+ // Photo Library
+ let photoLibraryAction = UIAction(title: L10n.Scene.Compose.MediaSelection.photoLibrary, image: UIImage(systemName: "rectangle.on.rectangle"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak self] _ in
+ guard let self = self else { return }
+ self.avatarMediaMenuActionPublisher.send(.photoLibrary)
+ }
+ children.append(photoLibraryAction)
+
+ // Camera
+ if UIImagePickerController.isSourceTypeAvailable(.camera) {
+ let cameraAction = UIAction(title: L10n.Scene.Compose.MediaSelection.camera, image: UIImage(systemName: "camera"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off, handler: { [weak self] _ in
+ guard let self = self else { return }
+ self.avatarMediaMenuActionPublisher.send(.camera)
+ })
+ children.append(cameraAction)
+ }
+
+ // Browse
+ let browseAction = UIAction(title: L10n.Scene.Compose.MediaSelection.browse, image: UIImage(systemName: "ellipsis"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak self] _ in
+ guard let self = self else { return }
+ self.avatarMediaMenuActionPublisher.send(.browse)
+ }
+ children.append(browseAction)
+
+ // Delete
+ if avatarImage != nil {
+ let deleteAction = UIAction(title: L10n.Scene.Register.Input.Avatar.delete, image: UIImage(systemName: "delete.left"), identifier: nil, discoverabilityTitle: nil, attributes: [.destructive], state: .off) { [weak self] _ in
+ guard let self = self else { return }
+ self.avatarMediaMenuActionPublisher.send(.delete)
+ }
+ children.append(deleteAction)
+ }
+
+ return UIMenu(title: "", image: nil, identifier: nil, options: .displayInline, children: children)
+ }
+
+ private func configureTextFieldCell(
+ cell: MastodonRegisterTextFieldTableViewCell,
+ validateState: Published.Publisher
+ ) {
+ Publishers.CombineLatest(
+ validateState,
+ cell.textField.publisher(for: \.isFirstResponder)
+ )
+ .receive(on: DispatchQueue.main)
+ .sink { [weak cell] validateState, isFirstResponder in
+ guard let cell = cell else { return }
+ switch validateState {
+ case .empty:
+ cell.textFieldShadowContainer.shadowColor = isFirstResponder ? Asset.Colors.brandBlue.color : .black
+ cell.textFieldShadowContainer.shadowAlpha = isFirstResponder ? 1 : 0.25
+ case .valid:
+ cell.textFieldShadowContainer.shadowColor = Asset.Colors.TextField.valid.color
+ cell.textFieldShadowContainer.shadowAlpha = 1
+ case .invalid:
+ cell.textFieldShadowContainer.shadowColor = Asset.Colors.TextField.invalid.color
+ cell.textFieldShadowContainer.shadowAlpha = 1
+ }
+ }
+ .store(in: &cell.disposeBag)
+ }
+}
diff --git a/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewModel.swift b/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewModel.swift
index 6c9e0754..5971cc74 100644
--- a/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewModel.swift
+++ b/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewModel.swift
@@ -14,18 +14,19 @@ final class MastodonRegisterViewModel {
var disposeBag = Set()
// input
+ let context: AppContext
let domain: String
let authenticateInfo: AuthenticationViewModel.AuthenticateInfo
let instance: Mastodon.Entity.Instance
let applicationToken: Mastodon.Entity.Token
- let context: AppContext
-
- let username = CurrentValueSubject("")
- let displayName = CurrentValueSubject("")
- let email = CurrentValueSubject("")
- let password = CurrentValueSubject("")
- let reason = CurrentValueSubject("")
- let avatarImage = CurrentValueSubject(nil)
+ let viewDidAppear = CurrentValueSubject(Void())
+
+ @Published var avatarImage: UIImage? = nil
+ @Published var name = ""
+ @Published var username = ""
+ @Published var email = ""
+ @Published var password = ""
+ @Published var reason = ""
let usernameErrorPrompt = CurrentValueSubject(nil)
let emailErrorPrompt = CurrentValueSubject(nil)
@@ -33,21 +34,25 @@ final class MastodonRegisterViewModel {
let reasonErrorPrompt = CurrentValueSubject(nil)
// output
+ var diffableDataSource: UITableViewDiffableDataSource?
let approvalRequired: Bool
let applicationAuthorization: Mastodon.API.OAuth.Authorization
- let usernameValidateState = CurrentValueSubject(.empty)
- let displayNameValidateState = CurrentValueSubject(.empty)
- let emailValidateState = CurrentValueSubject(.empty)
- let passwordValidateState = CurrentValueSubject(.empty)
- let reasonValidateState = CurrentValueSubject(.empty)
+
+ @Published var usernameValidateState: ValidateState = .empty
+ @Published var displayNameValidateState: ValidateState = .empty
+ @Published var emailValidateState: ValidateState = .empty
+ @Published var passwordValidateState: ValidateState = .empty
+ @Published var reasonValidateState: ValidateState = .empty
- let isRegistering = CurrentValueSubject(false)
- let isAllValid = CurrentValueSubject(false)
- let error = CurrentValueSubject(nil)
+ @Published var isRegistering = false
+ @Published var isAllValid = false
+ @Published var error: Error? = nil
+
+ let avatarMediaMenuActionPublisher = PassthroughSubject()
init(
- domain: String,
context: AppContext,
+ domain: String,
authenticateInfo: AuthenticationViewModel.AuthenticateInfo,
instance: Mastodon.Entity.Instance,
applicationToken: Mastodon.Entity.Token
@@ -60,7 +65,15 @@ final class MastodonRegisterViewModel {
self.approvalRequired = instance.approvalRequired ?? false
self.applicationAuthorization = Mastodon.API.OAuth.Authorization(accessToken: applicationToken.accessToken)
- username
+ $name
+ .map { name in
+ guard !name.isEmpty else { return .empty }
+ return .valid
+ }
+ .assign(to: \.displayNameValidateState, on: self)
+ .store(in: &disposeBag)
+
+ $username
.map { username in
guard !username.isEmpty else { return .empty }
var isValid = true
@@ -79,114 +92,120 @@ final class MastodonRegisterViewModel {
}
return isValid ? .valid : .invalid
}
- .assign(to: \.value, on: usernameValidateState)
+ .assign(to: \.usernameValidateState, on: self)
.store(in: &disposeBag)
- username
- .filter { !$0.isEmpty }
- .debounce(for: .milliseconds(300), scheduler: DispatchQueue.main)
- .removeDuplicates()
- .compactMap { [weak self] text -> AnyPublisher, Error>, Never>? in
- guard let self = self else { return nil }
- let query = Mastodon.API.Account.AccountLookupQuery(acct: text)
- return context.apiService.accountLookup(domain: domain, query: query, authorization: self.applicationAuthorization)
- .map {
- response -> Result, Error> in
- Result.success(response)
- }
- .catch { error in
- Just(Result.failure(error))
- }
- .eraseToAnyPublisher()
- }
- .switchToLatest()
- .sink { [weak self] result in
- guard let self = self else { return }
- switch result {
- case .success:
- let text = L10n.Scene.Register.Error.Reason.taken(L10n.Scene.Register.Error.Item.username)
- self.usernameErrorPrompt.value = MastodonRegisterViewModel.errorPromptAttributedString(for: text)
- self.usernameValidateState.value = .invalid
- case .failure:
- break
- }
- }
- .store(in: &disposeBag)
-
- usernameValidateState
- .sink { [weak self] validateState in
- if validateState == .valid {
- self?.usernameErrorPrompt.value = nil
- }
- }
- .store(in: &disposeBag)
+ // TODO: check username available
+// username
+// .filter { !$0.isEmpty }
+// .debounce(for: .milliseconds(300), scheduler: DispatchQueue.main)
+// .removeDuplicates()
+// .compactMap { [weak self] text -> AnyPublisher, Error>, Never>? in
+// guard let self = self else { return nil }
+// let query = Mastodon.API.Account.AccountLookupQuery(acct: text)
+// return context.apiService.accountLookup(domain: domain, query: query, authorization: self.applicationAuthorization)
+// .map {
+// response -> Result, Error> in
+// Result.success(response)
+// }
+// .catch { error in
+// Just(Result.failure(error))
+// }
+// .eraseToAnyPublisher()
+// }
+// .switchToLatest()
+// .sink { [weak self] result in
+// guard let self = self else { return }
+// switch result {
+// case .success:
+// let text = L10n.Scene.Register.Error.Reason.taken(L10n.Scene.Register.Error.Item.username)
+// self.usernameErrorPrompt.value = MastodonRegisterViewModel.errorPromptAttributedString(for: text)
+// self.usernameValidateState.value = .invalid
+// case .failure:
+// break
+// }
+// }
+// .store(in: &disposeBag)
+//
+// usernameValidateState
+// .sink { [weak self] validateState in
+// if validateState == .valid {
+// self?.usernameErrorPrompt.value = nil
+// }
+// }
+// .store(in: &disposeBag)
- displayName
- .map { displayname in
- guard !displayname.isEmpty else { return .empty }
- return .valid
- }
- .assign(to: \.value, on: displayNameValidateState)
- .store(in: &disposeBag)
- email
+ $email
.map { email in
guard !email.isEmpty else { return .empty }
return MastodonRegisterViewModel.isValidEmail(email) ? .valid : .invalid
}
- .assign(to: \.value, on: emailValidateState)
+ .assign(to: \.emailValidateState, on: self)
.store(in: &disposeBag)
- password
+
+ $password
.map { password in
guard !password.isEmpty else { return .empty }
return password.count >= 8 ? .valid : .invalid
}
- .assign(to: \.value, on: passwordValidateState)
+ .assign(to: \.passwordValidateState, on: self)
.store(in: &disposeBag)
+
if approvalRequired {
- reason
+ $reason
.map { invite in
guard !invite.isEmpty else { return .empty }
return .valid
}
- .assign(to: \.value, on: reasonValidateState)
+ .assign(to: \.reasonValidateState, on: self)
.store(in: &disposeBag)
}
- error
- .sink { [weak self] error in
- guard let self = self else { return }
- let error = error as? Mastodon.API.Error
- let mastodonError = error?.mastodonError
- if case let .generic(genericMastodonError) = mastodonError,
- let details = genericMastodonError.details
- {
- self.usernameErrorPrompt.value = details.usernameErrorDescriptions.first.flatMap { MastodonRegisterViewModel.errorPromptAttributedString(for: $0) }
- self.emailErrorPrompt.value = details.emailErrorDescriptions.first.flatMap { MastodonRegisterViewModel.errorPromptAttributedString(for: $0) }
- self.passwordErrorPrompt.value = details.passwordErrorDescriptions.first.flatMap { MastodonRegisterViewModel.errorPromptAttributedString(for: $0) }
- self.reasonErrorPrompt.value = details.reasonErrorDescriptions.first.flatMap { MastodonRegisterViewModel.errorPromptAttributedString(for: $0) }
- } else {
- self.usernameErrorPrompt.value = nil
- self.emailErrorPrompt.value = nil
- self.passwordErrorPrompt.value = nil
- self.reasonErrorPrompt.value = nil
- }
- }
- .store(in: &disposeBag)
-
+// error
+// .sink { [weak self] error in
+// guard let self = self else { return }
+// let error = error as? Mastodon.API.Error
+// let mastodonError = error?.mastodonError
+// if case let .generic(genericMastodonError) = mastodonError,
+// let details = genericMastodonError.details
+// {
+// self.usernameErrorPrompt.value = details.usernameErrorDescriptions.first.flatMap { MastodonRegisterViewModel.errorPromptAttributedString(for: $0) }
+// self.emailErrorPrompt.value = details.emailErrorDescriptions.first.flatMap { MastodonRegisterViewModel.errorPromptAttributedString(for: $0) }
+// self.passwordErrorPrompt.value = details.passwordErrorDescriptions.first.flatMap { MastodonRegisterViewModel.errorPromptAttributedString(for: $0) }
+// self.reasonErrorPrompt.value = details.reasonErrorDescriptions.first.flatMap { MastodonRegisterViewModel.errorPromptAttributedString(for: $0) }
+// } else {
+// self.usernameErrorPrompt.value = nil
+// self.emailErrorPrompt.value = nil
+// self.passwordErrorPrompt.value = nil
+// self.reasonErrorPrompt.value = nil
+// }
+// }
+// .store(in: &disposeBag)
+//
let publisherOne = Publishers.CombineLatest4(
- usernameValidateState.eraseToAnyPublisher(),
- displayNameValidateState.eraseToAnyPublisher(),
- emailValidateState.eraseToAnyPublisher(),
- passwordValidateState.eraseToAnyPublisher()
+ $usernameValidateState,
+ $displayNameValidateState,
+ $emailValidateState,
+ $passwordValidateState
)
- .map { $0.0 == .valid && $0.1 == .valid && $0.2 == .valid && $0.3 == .valid }
+ .map {
+ $0.0 == .valid &&
+ $0.1 == .valid &&
+ $0.2 == .valid &&
+ $0.3 == .valid
+ }
+
+ let publisherTwo = $reasonValidateState.map { reasonValidateState -> Bool in
+ guard self.approvalRequired else { return true }
+ return reasonValidateState == .valid
+ }
Publishers.CombineLatest(
publisherOne,
- approvalRequired ? reasonValidateState.map { $0 == .valid }.eraseToAnyPublisher() : Just(true).eraseToAnyPublisher()
+ publisherTwo
)
.map { $0 && $1 }
- .assign(to: \.value, on: isAllValid)
+ .assign(to: \.isAllValid, on: self)
.store(in: &disposeBag)
}
}
diff --git a/Mastodon/Scene/Onboarding/ServerRules/Cell/ServerRulesTableViewCell.swift b/Mastodon/Scene/Onboarding/ServerRules/Cell/ServerRulesTableViewCell.swift
new file mode 100644
index 00000000..83378b99
--- /dev/null
+++ b/Mastodon/Scene/Onboarding/ServerRules/Cell/ServerRulesTableViewCell.swift
@@ -0,0 +1,83 @@
+//
+// ServerRulesTableViewCell.swift
+// Mastodon
+//
+// Created by MainasuK on 2022-1-5.
+//
+
+import UIKit
+
+final class ServerRulesTableViewCell: UITableViewCell {
+
+ static let margin: CGFloat = 23
+
+ let indexImageView: UIImageView = {
+ let imageView = UIImageView()
+ imageView.tintColor = Asset.Colors.Label.primary.color
+ return imageView
+ }()
+
+ let ruleLabel: UILabel = {
+ let label = UILabel()
+ label.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 17, weight: .semibold))
+ label.textColor = Asset.Colors.Label.primary.color
+ label.numberOfLines = 0
+ return label
+ }()
+
+ let separalerLine: UIView = {
+ let view = UIView()
+ view.backgroundColor = Asset.Theme.System.separator.color
+ return view
+ }()
+
+ override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
+ super.init(style: style, reuseIdentifier: reuseIdentifier)
+ _init()
+ }
+
+ required init?(coder: NSCoder) {
+ super.init(coder: coder)
+ _init()
+ }
+
+}
+
+extension ServerRulesTableViewCell {
+
+ private func _init() {
+ selectionStyle = .none
+ backgroundColor = .clear
+
+ indexImageView.translatesAutoresizingMaskIntoConstraints = false
+ contentView.addSubview(indexImageView)
+ NSLayoutConstraint.activate([
+ indexImageView.topAnchor.constraint(greaterThanOrEqualTo: contentView.topAnchor, constant: ServerRulesTableViewCell.margin),
+ indexImageView.leadingAnchor.constraint(equalTo: contentView.readableContentGuide.leadingAnchor),
+ contentView.bottomAnchor.constraint(greaterThanOrEqualTo: indexImageView.bottomAnchor, constant: ServerRulesTableViewCell.margin),
+ indexImageView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
+ indexImageView.widthAnchor.constraint(equalToConstant: 32).priority(.required - 1),
+ indexImageView.heightAnchor.constraint(equalToConstant: 32).priority(.required - 1),
+ ])
+
+ ruleLabel.translatesAutoresizingMaskIntoConstraints = false
+ contentView.addSubview(ruleLabel)
+ NSLayoutConstraint.activate([
+ ruleLabel.topAnchor.constraint(greaterThanOrEqualTo: contentView.topAnchor, constant: ServerRulesTableViewCell.margin),
+ ruleLabel.leadingAnchor.constraint(equalTo: indexImageView.trailingAnchor, constant: 16),
+ ruleLabel.trailingAnchor.constraint(equalTo: contentView.readableContentGuide.trailingAnchor),
+ contentView.bottomAnchor.constraint(greaterThanOrEqualTo: ruleLabel.bottomAnchor, constant: ServerRulesTableViewCell.margin),
+ ruleLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
+ ])
+
+ separalerLine.translatesAutoresizingMaskIntoConstraints = false
+ contentView.addSubview(separalerLine)
+ NSLayoutConstraint.activate([
+ separalerLine.leadingAnchor.constraint(equalTo: contentView.readableContentGuide.leadingAnchor),
+ separalerLine.trailingAnchor.constraint(equalTo: contentView.readableContentGuide.trailingAnchor),
+ separalerLine.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
+ separalerLine.heightAnchor.constraint(equalToConstant: UIView.separatorLineHeight(of: contentView)).priority(.required - 1),
+ ])
+ }
+
+}
diff --git a/Mastodon/Scene/Onboarding/ServerRules/MastodonServerRulesViewController.swift b/Mastodon/Scene/Onboarding/ServerRules/MastodonServerRulesViewController.swift
index e93d06e1..f6369282 100644
--- a/Mastodon/Scene/Onboarding/ServerRules/MastodonServerRulesViewController.swift
+++ b/Mastodon/Scene/Onboarding/ServerRules/MastodonServerRulesViewController.swift
@@ -14,8 +14,11 @@ import MetaTextKit
final class MastodonServerRulesViewController: UIViewController, NeedsDependency {
- var disposeBag = Set()
+ let logger = Logger(subsystem: "MastodonServerRulesViewController", category: "ViewController")
+ var disposeBag = Set()
+ private var observations = Set()
+
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
@@ -23,67 +26,26 @@ final class MastodonServerRulesViewController: UIViewController, NeedsDependency
let stackView = UIStackView()
- let largeTitleLabel: UILabel = {
- let label = UILabel()
- label.font = UIFontMetrics(forTextStyle: .largeTitle).scaledFont(for: .systemFont(ofSize: 34, weight: .bold))
- label.textColor = .label
- label.text = L10n.Scene.ServerRules.title
- label.numberOfLines = 0
- return label
+ let tableView: UITableView = {
+ let tableView = UITableView()
+ tableView.register(OnboardingHeadlineTableViewCell.self, forCellReuseIdentifier: String(describing: OnboardingHeadlineTableViewCell.self))
+ tableView.register(ServerRulesTableViewCell.self, forCellReuseIdentifier: String(describing: ServerRulesTableViewCell.self))
+ tableView.rowHeight = UITableView.automaticDimension
+ tableView.separatorStyle = .none
+ tableView.backgroundColor = .clear
+ tableView.keyboardDismissMode = .onDrag
+ if #available(iOS 15.0, *) {
+ tableView.sectionHeaderTopPadding = 0
+ } else {
+ // Fallback on earlier versions
+ }
+ return tableView
}()
-
- private(set) lazy var subtitleLabel: UILabel = {
- let label = UILabel()
- label.font = UIFontMetrics(forTextStyle: .title1).scaledFont(for: UIFont.systemFont(ofSize: 20))
- label.textColor = .secondaryLabel
- label.text = L10n.Scene.ServerRules.subtitle(viewModel.domain)
- label.numberOfLines = 0
- return label
- }()
-
- let rulesLabel: UILabel = {
- let label = UILabel()
- label.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 17, weight: .semibold))
- label.textColor = Asset.Colors.Label.primary.color
- label.text = "Rules"
- label.numberOfLines = 0
- return label
- }()
-
- let bottomContainerView: UIView = {
- let view = UIView()
- view.backgroundColor = Asset.Theme.Mastodon.systemGroupedBackground.color
- return view
- }()
-
- private(set) lazy var bottomPromptMetaText: MetaText = {
- let metaText = MetaText()
- metaText.textAttributes = [
- .font: UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 17, weight: .regular), maximumPointSize: 22),
- .foregroundColor: UIColor.label,
- ]
- metaText.linkAttributes = [
- .font: UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 17, weight: .regular), maximumPointSize: 22),
- .foregroundColor: Asset.Colors.brandBlue.color,
- ]
- metaText.textView.isEditable = false
- metaText.textView.isSelectable = false
- metaText.textView.isScrollEnabled = false
- metaText.textView.backgroundColor = Asset.Theme.Mastodon.systemGroupedBackground.color // needs background color to prevent server rules text overlap
- return metaText
- }()
-
- let confirmButton: PrimaryActionButton = {
- let button = PrimaryActionButton()
- button.setTitle(L10n.Scene.ServerRules.Button.confirm, for: .normal)
- return button
- }()
-
- let scrollView: UIScrollView = {
- let scrollView = UIScrollView()
- scrollView.alwaysBounceVertical = true
- scrollView.showsVerticalScrollIndicator = false
- return scrollView
+
+ let navigationActionView: NavigationActionView = {
+ let navigationActionView = NavigationActionView()
+ navigationActionView.backgroundColor = Asset.Scene.Onboarding.onboardingBackground.color
+ return navigationActionView
}()
deinit {
@@ -97,224 +59,96 @@ extension MastodonServerRulesViewController {
override func viewDidLoad() {
super.viewDidLoad()
+ navigationItem.leftBarButtonItem = UIBarButtonItem()
+
setupOnboardingAppearance()
- configureTitleLabel()
- configureMargin()
- configTextView()
-
defer { setupNavigationBarBackgroundView() }
- bottomContainerView.translatesAutoresizingMaskIntoConstraints = false
- view.addSubview(bottomContainerView)
+ tableView.translatesAutoresizingMaskIntoConstraints = false
+ view.addSubview(tableView)
NSLayoutConstraint.activate([
- view.bottomAnchor.constraint(equalTo: bottomContainerView.bottomAnchor),
- bottomContainerView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
- bottomContainerView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
+ tableView.topAnchor.constraint(equalTo: view.topAnchor),
+ tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
+ tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
+ tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
])
- bottomContainerView.preservesSuperviewLayoutMargins = true
+
+ navigationActionView.translatesAutoresizingMaskIntoConstraints = false
+ view.addSubview(navigationActionView)
defer {
- view.bringSubviewToFront(bottomContainerView)
+ view.bringSubviewToFront(navigationActionView)
}
-
- confirmButton.translatesAutoresizingMaskIntoConstraints = false
- bottomContainerView.addSubview(confirmButton)
NSLayoutConstraint.activate([
- bottomContainerView.layoutMarginsGuide.bottomAnchor.constraint(equalTo: confirmButton.bottomAnchor, constant: MastodonServerRulesViewController.viewBottomPaddingHeight),
- confirmButton.leadingAnchor.constraint(equalTo: bottomContainerView.layoutMarginsGuide.leadingAnchor),
- bottomContainerView.layoutMarginsGuide.trailingAnchor.constraint(equalTo: confirmButton.trailingAnchor),
- confirmButton.heightAnchor.constraint(equalToConstant: MastodonServerRulesViewController.actionButtonHeight).priority(.defaultHigh),
+ navigationActionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
+ navigationActionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
+ view.bottomAnchor.constraint(equalTo: navigationActionView.bottomAnchor),
])
- bottomPromptMetaText.textView.translatesAutoresizingMaskIntoConstraints = false
- bottomContainerView.addSubview(bottomPromptMetaText.textView)
- NSLayoutConstraint.activate([
- bottomPromptMetaText.textView.frameLayoutGuide.topAnchor.constraint(equalTo: bottomContainerView.topAnchor, constant: 20),
- bottomPromptMetaText.textView.frameLayoutGuide.leadingAnchor.constraint(equalTo: bottomContainerView.layoutMarginsGuide.leadingAnchor),
- bottomPromptMetaText.textView.frameLayoutGuide.trailingAnchor.constraint(equalTo: bottomContainerView.layoutMarginsGuide.trailingAnchor),
- confirmButton.topAnchor.constraint(equalTo: bottomPromptMetaText.textView.frameLayoutGuide.bottomAnchor, constant: 20),
- ])
+ navigationActionView
+ .observe(\.bounds, options: [.initial, .new]) { [weak self] navigationActionView, _ in
+ guard let self = self else { return }
+ let inset = navigationActionView.frame.height
+ self.tableView.contentInset.bottom = inset
+ }
+ .store(in: &observations)
- scrollView.translatesAutoresizingMaskIntoConstraints = false
- view.addSubview(scrollView)
- NSLayoutConstraint.activate([
- scrollView.frameLayoutGuide.topAnchor.constraint(equalTo: view.topAnchor),
- scrollView.frameLayoutGuide.leadingAnchor.constraint(equalTo: view.readableContentGuide.leadingAnchor),
- scrollView.frameLayoutGuide.trailingAnchor.constraint(equalTo: view.readableContentGuide.trailingAnchor),
- scrollView.frameLayoutGuide.bottomAnchor.constraint(equalTo: view.bottomAnchor),
- scrollView.frameLayoutGuide.widthAnchor.constraint(equalTo: scrollView.contentLayoutGuide.widthAnchor),
- ])
-
- stackView.axis = .vertical
- stackView.distribution = .fill
- stackView.spacing = 10
- stackView.isLayoutMarginsRelativeArrangement = true
- stackView.layoutMargins = UIEdgeInsets(top: 20, left: 0, bottom: 20, right: 0)
- stackView.addArrangedSubview(largeTitleLabel)
- stackView.addArrangedSubview(subtitleLabel)
- stackView.addArrangedSubview(rulesLabel)
+ tableView.delegate = self
+ viewModel.setupDiffableDataSource(tableView: tableView)
- stackView.translatesAutoresizingMaskIntoConstraints = false
- scrollView.addSubview(stackView)
- NSLayoutConstraint.activate([
- stackView.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor),
- stackView.leadingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor),
- stackView.trailingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor),
- scrollView.contentLayoutGuide.bottomAnchor.constraint(equalTo: stackView.bottomAnchor),
- ])
-
- rulesLabel.attributedText = viewModel.rulesAttributedString
- confirmButton.addTarget(self, action: #selector(MastodonServerRulesViewController.confirmButtonPressed(_:)), for: .touchUpInside)
+ navigationActionView.backButton.addTarget(self, action: #selector(MastodonServerRulesViewController.backButtonPressed(_:)), for: .touchUpInside)
+ navigationActionView.nextButton.addTarget(self, action: #selector(MastodonServerRulesViewController.nextButtonPressed(_:)), for: .touchUpInside)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
- scrollView.flashScrollIndicators()
- }
-
- override func viewDidLayoutSubviews() {
- super.viewDidLayoutSubviews()
- updateScrollViewContentInset()
- }
-
- override func viewSafeAreaInsetsDidChange() {
- super.viewSafeAreaInsetsDidChange()
- updateScrollViewContentInset()
- }
-
- override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
- super.traitCollectionDidChange(previousTraitCollection)
-
- setupNavigationBarAppearance()
- configureTitleLabel()
- configureMargin()
+ tableView.flashScrollIndicators()
}
}
extension MastodonServerRulesViewController {
- private func configureTitleLabel() {
- guard UIDevice.current.userInterfaceIdiom == .pad else {
- return
- }
-
- switch traitCollection.horizontalSizeClass {
- case .regular:
- navigationItem.largeTitleDisplayMode = .always
- navigationItem.title = L10n.Scene.ServerRules.title.replacingOccurrences(of: "\n", with: " ")
- largeTitleLabel.isHidden = true
- default:
- navigationItem.leftBarButtonItem = nil
- navigationItem.largeTitleDisplayMode = .never
- navigationItem.title = nil
- largeTitleLabel.isHidden = false
- }
+
+ @objc private func backButtonPressed(_ sender: UIButton) {
+ logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)")
+ navigationController?.popViewController(animated: true)
}
- private func configureMargin() {
- switch traitCollection.horizontalSizeClass {
- case .regular:
- let margin = MastodonPickServerViewController.viewEdgeMargin
- stackView.layoutMargins = UIEdgeInsets(top: 32, left: margin, bottom: 20, right: margin)
- bottomContainerView.layoutMargins = UIEdgeInsets(top: 0, left: margin, bottom: 0, right: margin)
- default:
- stackView.layoutMargins = UIEdgeInsets(top: 20, left: 0, bottom: 20, right: 0)
- bottomContainerView.layoutMargins = .zero
- }
- }
-}
+ @objc private func nextButtonPressed(_ sender: UIButton) {
+ logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)")
-extension MastodonServerRulesViewController {
- func updateScrollViewContentInset() {
- view.layoutIfNeeded()
- scrollView.contentInset.bottom = bottomContainerView.frame.height
- scrollView.verticalScrollIndicatorInsets.bottom = bottomContainerView.frame.height
+ let viewModel = MastodonRegisterViewModel(
+ context: context,
+ domain: viewModel.domain,
+ authenticateInfo: viewModel.authenticateInfo,
+ instance: viewModel.instance,
+ applicationToken: viewModel.applicationToken
+ )
+ coordinator.present(scene: .mastodonRegister(viewModel: viewModel), from: self, transition: .show)
}
- func configTextView() {
- let metaContent = ServerRulesPromptMetaContent(domain: viewModel.domain)
- bottomPromptMetaText.configure(content: metaContent)
- bottomPromptMetaText.textView.linkDelegate = self
- }
-
- struct ServerRulesPromptMetaContent: MetaContent {
- let string: String
- let entities: [Meta.Entity]
-
- init(domain: String) {
- let _string = L10n.Scene.ServerRules.prompt(domain)
- self.string = _string
-
- var _entities: [Meta.Entity] = []
-
- let termsOfServiceText = L10n.Scene.ServerRules.termsOfService
- if let termsOfServiceRange = _string.range(of: termsOfServiceText) {
- let url = Mastodon.API.serverRulesURL(domain: domain)
- let entity = Meta.Entity(range: NSRange(termsOfServiceRange, in: _string), meta: .url(termsOfServiceText, trimmed: termsOfServiceText, url: url.absoluteString, userInfo: nil))
- _entities.append(entity)
- }
-
- let privacyPolicyText = L10n.Scene.ServerRules.privacyPolicy
- if let privacyPolicyRange = _string.range(of: privacyPolicyText) {
- let url = Mastodon.API.privacyURL(domain: domain)
- let entity = Meta.Entity(range: NSRange(privacyPolicyRange, in: _string), meta: .url(privacyPolicyText, trimmed: privacyPolicyText, url: url.absoluteString, userInfo: nil))
- _entities.append(entity)
- }
-
- self.entities = _entities
- }
-
- func metaAttachment(for entity: Meta.Entity) -> MetaAttachment? {
- return nil
- }
- }
-
-}
-
-extension MastodonServerRulesViewController: UITextViewDelegate {
- func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
- return false
- }
-}
-
-// MARK: - MetaTextViewDelegate
-extension MastodonServerRulesViewController: MetaTextViewDelegate {
- func metaTextView(_ metaTextView: MetaTextView, didSelectMeta meta: Meta) {
- switch meta {
- case .url(_, _, let url, _):
- guard let url = URL(string: url) else { return }
- coordinator.present(scene: .safari(url: url), from: nil, transition: .safariPresent(animated: true, completion: nil))
- default:
- break
- }
- }
-}
-
-extension MastodonServerRulesViewController {
- @objc private func confirmButtonPressed(_ sender: UIButton) {
- os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
-
- let viewModel = MastodonRegisterViewModel(domain: self.viewModel.domain, context: self.context, authenticateInfo: self.viewModel.authenticateInfo, instance: self.viewModel.instance, applicationToken: self.viewModel.applicationToken)
- self.coordinator.present(scene: .mastodonRegister(viewModel: viewModel), from: self, transition: .show)
- }
}
// MARK: - OnboardingViewControllerAppearance
extension MastodonServerRulesViewController: OnboardingViewControllerAppearance { }
-#if canImport(SwiftUI) && DEBUG
-import SwiftUI
-
-struct ServerRulesViewController_Previews: PreviewProvider {
-
- static var previews: some View {
- UIViewControllerPreview {
- let viewController = MastodonServerRulesViewController()
- return viewController
- }
- .previewLayout(.fixed(width: 375, height: 800))
+// MARK: - UITableViewDelegate
+extension MastodonServerRulesViewController: UITableViewDelegate {
+ func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
+ return UIView()
}
+ func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
+ guard let diffableDataSource = viewModel.diffableDataSource,
+ section < diffableDataSource.snapshot().numberOfSections
+ else { return .leastNonzeroMagnitude }
+
+ let sectionItem = diffableDataSource.snapshot().sectionIdentifiers[section]
+ switch sectionItem {
+ case .header:
+ return .leastNonzeroMagnitude
+ case .rules:
+ return 16
+ }
+ }
}
-
-#endif
diff --git a/Mastodon/Scene/Onboarding/ServerRules/MastodonServerRulesViewModel+Diffable.swift b/Mastodon/Scene/Onboarding/ServerRules/MastodonServerRulesViewModel+Diffable.swift
new file mode 100644
index 00000000..f6385a52
--- /dev/null
+++ b/Mastodon/Scene/Onboarding/ServerRules/MastodonServerRulesViewModel+Diffable.swift
@@ -0,0 +1,26 @@
+//
+// MastodonServerRulesViewModel+Diffable.swift
+// Mastodon
+//
+// Created by MainasuK on 2022-1-5.
+//
+
+import UIKit
+
+extension MastodonServerRulesViewModel {
+ func setupDiffableDataSource(
+ tableView: UITableView
+ ) {
+ diffableDataSource = ServerRuleSection.tableViewDiffableDataSource(tableView: tableView)
+
+ var snapshot = NSDiffableDataSourceSnapshot()
+ snapshot.appendSections([.header, .rules])
+ snapshot.appendItems([.header(domain: domain)], toSection: .header)
+ let ruleItems: [ServerRuleItem] = rules.enumerated().map { i, rule in
+ let ruleContext = ServerRuleItem.RuleContext(index: i, rule: rule)
+ return ServerRuleItem.rule(ruleContext)
+ }
+ snapshot.appendItems(ruleItems, toSection: .rules)
+ diffableDataSource?.applySnapshot(snapshot, animated: false, completion: nil)
+ }
+}
diff --git a/Mastodon/Scene/Onboarding/ServerRules/MastodonServerRulesViewModel.swift b/Mastodon/Scene/Onboarding/ServerRules/MastodonServerRulesViewModel.swift
index 5936a2c0..f2664e0e 100644
--- a/Mastodon/Scene/Onboarding/ServerRules/MastodonServerRulesViewModel.swift
+++ b/Mastodon/Scene/Onboarding/ServerRules/MastodonServerRulesViewModel.swift
@@ -18,6 +18,9 @@ final class MastodonServerRulesViewModel {
let instance: Mastodon.Entity.Instance
let applicationToken: Mastodon.Entity.Token
+ // output
+ var diffableDataSource: UITableViewDiffableDataSource?
+
init(
domain: String,
authenticateInfo: AuthenticationViewModel.AuthenticateInfo,
diff --git a/Mastodon/Scene/Onboarding/Share/NavigationActionView.swift b/Mastodon/Scene/Onboarding/Share/NavigationActionView.swift
new file mode 100644
index 00000000..dc30227c
--- /dev/null
+++ b/Mastodon/Scene/Onboarding/Share/NavigationActionView.swift
@@ -0,0 +1,93 @@
+//
+// NavigationActionView.swift
+// Mastodon
+//
+// Created by MainasuK on 2021-12-31.
+//
+
+import UIKit
+import MastodonUI
+
+final class NavigationActionView: UIView {
+
+ static let buttonHeight: CGFloat = 50
+
+ private var observations = Set()
+
+ let buttonContainer: UIStackView = {
+ let stackView = UIStackView()
+ stackView.axis = .horizontal
+ stackView.spacing = 18
+ return stackView
+ }()
+
+ let backButtonShadowContainer = ShadowBackgroundContainer()
+ let backButton: PrimaryActionButton = {
+ let button = PrimaryActionButton()
+ button.action = .back
+ button.setTitle(L10n.Common.Controls.Actions.back, for: .normal)
+ return button
+ }()
+
+ let nextButtonShadowContainer = ShadowBackgroundContainer()
+ let nextButton: PrimaryActionButton = {
+ let button = PrimaryActionButton()
+ button.action = .next
+ button.setTitle(L10n.Common.Controls.Actions.next, for: .normal)
+ return button
+ }()
+
+ override init(frame: CGRect) {
+ super.init(frame: frame)
+ _init()
+ }
+
+ required init?(coder: NSCoder) {
+ super.init(coder: coder)
+ _init()
+ }
+
+}
+
+extension NavigationActionView {
+ private func _init() {
+ buttonContainer.translatesAutoresizingMaskIntoConstraints = false
+ buttonContainer.preservesSuperviewLayoutMargins = true
+ addSubview(buttonContainer)
+ NSLayoutConstraint.activate([
+ buttonContainer.topAnchor.constraint(equalTo: topAnchor, constant: 16),
+ buttonContainer.leadingAnchor.constraint(equalTo: readableContentGuide.leadingAnchor),
+ buttonContainer.trailingAnchor.constraint(equalTo: readableContentGuide.trailingAnchor),
+ safeAreaLayoutGuide.bottomAnchor.constraint(equalTo: buttonContainer.bottomAnchor, constant: 8),
+ ])
+
+ backButtonShadowContainer.translatesAutoresizingMaskIntoConstraints = false
+ buttonContainer.addArrangedSubview(backButtonShadowContainer)
+ nextButtonShadowContainer.translatesAutoresizingMaskIntoConstraints = false
+ buttonContainer.addArrangedSubview(nextButtonShadowContainer)
+ NSLayoutConstraint.activate([
+ backButtonShadowContainer.heightAnchor.constraint(equalToConstant: NavigationActionView.buttonHeight).priority(.required - 1),
+ nextButtonShadowContainer.heightAnchor.constraint(equalToConstant: NavigationActionView.buttonHeight).priority(.required - 1),
+ nextButtonShadowContainer.widthAnchor.constraint(equalTo: backButtonShadowContainer.widthAnchor, multiplier: 2).priority(.required - 1),
+ ])
+
+ backButton.translatesAutoresizingMaskIntoConstraints = false
+ backButtonShadowContainer.addSubview(backButton)
+ NSLayoutConstraint.activate([
+ backButton.topAnchor.constraint(equalTo: backButtonShadowContainer.topAnchor),
+ backButton.leadingAnchor.constraint(equalTo: backButtonShadowContainer.leadingAnchor),
+ backButton.trailingAnchor.constraint(equalTo: backButtonShadowContainer.trailingAnchor),
+ backButton.bottomAnchor.constraint(equalTo: backButtonShadowContainer.bottomAnchor),
+ ])
+
+ nextButton.translatesAutoresizingMaskIntoConstraints = false
+ nextButtonShadowContainer.addSubview(nextButton)
+ NSLayoutConstraint.activate([
+ nextButton.topAnchor.constraint(equalTo: nextButtonShadowContainer.topAnchor),
+ nextButton.leadingAnchor.constraint(equalTo: nextButtonShadowContainer.leadingAnchor),
+ nextButton.trailingAnchor.constraint(equalTo: nextButtonShadowContainer.trailingAnchor),
+ nextButton.bottomAnchor.constraint(equalTo: nextButtonShadowContainer.bottomAnchor),
+ ])
+ }
+
+}
diff --git a/Mastodon/Scene/Onboarding/Share/OnboardingHeadlineTableViewCell.swift b/Mastodon/Scene/Onboarding/Share/OnboardingHeadlineTableViewCell.swift
new file mode 100644
index 00000000..f8090734
--- /dev/null
+++ b/Mastodon/Scene/Onboarding/Share/OnboardingHeadlineTableViewCell.swift
@@ -0,0 +1,65 @@
+//
+// OnboardingHeadlineTableViewCell.swift
+// Mastodon
+//
+// Created by BradGao on 2021/2/23.
+//
+
+import UIKit
+
+final class OnboardingHeadlineTableViewCell: UITableViewCell {
+
+ let titleLabel: UILabel = {
+ let label = UILabel()
+ label.font = MastodonPickServerViewController.largeTitleFont
+ label.textColor = MastodonPickServerViewController.largeTitleTextColor
+ label.text = L10n.Scene.ServerPicker.title
+ label.adjustsFontForContentSizeCategory = true
+ label.numberOfLines = 0
+ return label
+ }()
+
+ let subTitleLabel: UILabel = {
+ let label = UILabel()
+ label.font = MastodonPickServerViewController.subTitleFont
+ label.textColor = MastodonPickServerViewController.subTitleTextColor
+ label.text = "Pick a community based on your interests, region, or a general purpose one. Each community is operated by an entirely independent organization or individual."
+ label.adjustsFontForContentSizeCategory = true
+ label.numberOfLines = 0
+ return label
+ }()
+
+ override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
+ super.init(style: style, reuseIdentifier: reuseIdentifier)
+ _init()
+ }
+
+ required init?(coder: NSCoder) {
+ super.init(coder: coder)
+ _init()
+ }
+}
+
+extension OnboardingHeadlineTableViewCell {
+
+ private func _init() {
+ selectionStyle = .none
+ backgroundColor = Asset.Scene.Onboarding.onboardingBackground.color
+
+ let container = UIStackView()
+ container.axis = .vertical
+ container.spacing = 16
+ container.translatesAutoresizingMaskIntoConstraints = false
+ contentView.addSubview(container)
+ NSLayoutConstraint.activate([
+ container.topAnchor.constraint(equalTo: contentView.topAnchor),
+ container.leadingAnchor.constraint(equalTo: contentView.readableContentGuide.leadingAnchor),
+ container.trailingAnchor.constraint(equalTo: contentView.readableContentGuide.trailingAnchor),
+ contentView.bottomAnchor.constraint(equalTo: container.bottomAnchor, constant: 11),
+ ])
+
+ container.addArrangedSubview(titleLabel)
+ container.addArrangedSubview(subTitleLabel)
+ }
+
+}
diff --git a/Mastodon/Scene/Onboarding/Share/OnboardingNavigationController.swift b/Mastodon/Scene/Onboarding/Share/OnboardingNavigationController.swift
new file mode 100644
index 00000000..537102dc
--- /dev/null
+++ b/Mastodon/Scene/Onboarding/Share/OnboardingNavigationController.swift
@@ -0,0 +1,51 @@
+//
+// OnboardingNavigationController.swift
+// Mastodon
+//
+// Created by MainasuK on 2021-12-31.
+//
+
+import UIKit
+
+final class OnboardingNavigationController: AdaptiveStatusBarStyleNavigationController {
+
+ private(set) lazy var gradientBorderView = GradientBorderView(frame: view.bounds)
+
+}
+
+extension OnboardingNavigationController {
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+
+ gradientBorderView.translatesAutoresizingMaskIntoConstraints = false
+ view.addSubview(gradientBorderView)
+ NSLayoutConstraint.activate([
+ gradientBorderView.topAnchor.constraint(equalTo: view.topAnchor),
+ gradientBorderView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
+ gradientBorderView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
+ gradientBorderView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
+ ])
+
+ updateBorderViewDisplay()
+ }
+
+ override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
+ super.traitCollectionDidChange(previousTraitCollection)
+
+ }
+
+}
+
+extension OnboardingNavigationController {
+
+ private func updateBorderViewDisplay() {
+ switch traitCollection.userInterfaceIdiom {
+ case .phone:
+ gradientBorderView.isHidden = true
+ default:
+ gradientBorderView.isHidden = false
+ }
+ }
+
+}
diff --git a/Mastodon/Scene/Onboarding/Share/OnboardingViewControllerAppearance.swift b/Mastodon/Scene/Onboarding/Share/OnboardingViewControllerAppearance.swift
index 17c4699e..aef6a8ab 100644
--- a/Mastodon/Scene/Onboarding/Share/OnboardingViewControllerAppearance.swift
+++ b/Mastodon/Scene/Onboarding/Share/OnboardingViewControllerAppearance.swift
@@ -15,12 +15,30 @@ protocol OnboardingViewControllerAppearance: UIViewController {
extension OnboardingViewControllerAppearance {
- static var actionButtonHeight: CGFloat { return 46 }
+ static var actionButtonHeight: CGFloat { return 50 }
static var actionButtonMargin: CGFloat { return 12 }
+ static var actionButtonMarginExtend: CGFloat { return 80 }
static var viewBottomPaddingHeight: CGFloat { return 11 }
+ static var viewBottomPaddingHeightExtend: CGFloat { return 22 }
+
+ static var largeTitleFont: UIFont {
+ return UIFontMetrics(forTextStyle: .largeTitle).scaledFont(for: .systemFont(ofSize: 28, weight: .bold))
+ }
+
+ static var largeTitleTextColor: UIColor {
+ return Asset.Colors.Label.primary.color
+ }
+
+ static var subTitleFont: UIFont {
+ return UIFontMetrics(forTextStyle: .largeTitle).scaledFont(for: .systemFont(ofSize: 17, weight: .regular))
+ }
+
+ static var subTitleTextColor: UIColor {
+ return Asset.Colors.Label.secondary.color
+ }
func setupOnboardingAppearance() {
- view.backgroundColor = Asset.Theme.Mastodon.systemGroupedBackground.color
+ view.backgroundColor = Asset.Scene.Onboarding.onboardingBackground.color
setupNavigationBarAppearance()
@@ -37,31 +55,22 @@ extension OnboardingViewControllerAppearance {
// use TransparentBackground so view push / dismiss will be more visual nature
// please add opaque background for status bar manually if needs
- switch traitCollection.userInterfaceIdiom {
- case .pad:
- if traitCollection.horizontalSizeClass == .regular {
- // do nothing
- } else {
- fallthrough
- }
- default:
- let barAppearance = UINavigationBarAppearance()
- barAppearance.configureWithTransparentBackground()
- navigationItem.standardAppearance = barAppearance
- navigationItem.compactAppearance = barAppearance
- navigationItem.scrollEdgeAppearance = barAppearance
- if #available(iOS 15.0, *) {
- navigationItem.compactScrollEdgeAppearance = barAppearance
- } else {
- // Fallback on earlier versions
- }
+ let barAppearance = UINavigationBarAppearance()
+ barAppearance.configureWithTransparentBackground()
+ navigationItem.standardAppearance = barAppearance
+ navigationItem.compactAppearance = barAppearance
+ navigationItem.scrollEdgeAppearance = barAppearance
+ if #available(iOS 15.0, *) {
+ navigationItem.compactScrollEdgeAppearance = barAppearance
+ } else {
+ // Fallback on earlier versions
}
}
func setupNavigationBarBackgroundView() {
let navigationBarBackgroundView: UIView = {
let view = UIView()
- view.backgroundColor = Asset.Theme.Mastodon.systemGroupedBackground.color
+ view.backgroundColor = Asset.Scene.Onboarding.onboardingBackground.color
return view
}()
diff --git a/Mastodon/Scene/Onboarding/Welcome/View/GradientBorderView.swift b/Mastodon/Scene/Onboarding/Welcome/View/GradientBorderView.swift
new file mode 100644
index 00000000..68e7968b
--- /dev/null
+++ b/Mastodon/Scene/Onboarding/Welcome/View/GradientBorderView.swift
@@ -0,0 +1,63 @@
+//
+// GradientBorderView.swift
+// Mastodon
+//
+// Created by MainasuK on 2021-12-31.
+//
+
+import UIKit
+
+final class GradientBorderView: UIView {
+
+ let gradientLayer = CAGradientLayer()
+ let maskLayer = CAShapeLayer()
+
+ var cornerRadius: CGFloat = 9 {
+ didSet { setNeedsLayout() }
+ }
+
+ override init(frame: CGRect) {
+ super.init(frame: frame)
+ _init()
+ }
+
+ required init?(coder: NSCoder) {
+ super.init(coder: coder)
+ _init()
+ }
+
+}
+
+extension GradientBorderView {
+ private func _init() {
+ isUserInteractionEnabled = false
+
+ gradientLayer.frame = bounds
+
+ gradientLayer.colors = [
+ UIColor.white.cgColor,
+ UIColor.white.withAlphaComponent(0.0).cgColor,
+ ]
+
+ gradientLayer.startPoint = CGPoint(x: 0.5, y: 0)
+ gradientLayer.endPoint = CGPoint(x: 0.5, y: 1)
+
+ layer.addSublayer(gradientLayer)
+
+ // set blend mode to "Soft Light"
+ layer.compositingFilter = "softLightBlendMode"
+ }
+
+ override func layoutSubviews() {
+ super.layoutSubviews()
+
+ let bezierPath = UIBezierPath(rect: bounds)
+ bezierPath.append(UIBezierPath(roundedRect: bounds.insetBy(dx: 3, dy: 3), cornerRadius: cornerRadius))
+
+ maskLayer.fillRule = .evenOdd
+ maskLayer.path = bezierPath.cgPath
+
+ gradientLayer.frame = bounds
+ gradientLayer.mask = maskLayer
+ }
+}
diff --git a/Mastodon/Scene/Onboarding/Welcome/View/WelcomeIllustrationView.swift b/Mastodon/Scene/Onboarding/Welcome/View/WelcomeIllustrationView.swift
index f5d8c41c..23fa1505 100644
--- a/Mastodon/Scene/Onboarding/Welcome/View/WelcomeIllustrationView.swift
+++ b/Mastodon/Scene/Onboarding/Welcome/View/WelcomeIllustrationView.swift
@@ -8,18 +8,18 @@
import UIKit
final class WelcomeIllustrationView: UIView {
-
- static let artworkImageSize = CGSize(width: 375, height: 1500)
-
+
let cloudBaseImageView = UIImageView()
let rightHillImageView = UIImageView()
let leftHillImageView = UIImageView()
let centerHillImageView = UIImageView()
private let cloudBaseImage = Asset.Scene.Welcome.Illustration.cloudBase.image
+ private let cloudBaseExtendImage = Asset.Scene.Welcome.Illustration.cloudBaseExtend.image
private let elephantThreeOnGrassWithTreeTwoImage = Asset.Scene.Welcome.Illustration.elephantThreeOnGrassWithTreeTwo.image
private let elephantThreeOnGrassWithTreeThreeImage = Asset.Scene.Welcome.Illustration.elephantThreeOnGrassWithTreeThree.image
private let elephantThreeOnGrassImage = Asset.Scene.Welcome.Illustration.elephantThreeOnGrass.image
+ private let elephantThreeOnGrassExtendImage = Asset.Scene.Welcome.Illustration.elephantThreeOnGrassExtend.image
// layout outside
let elephantOnAirplaneWithContrailImageView: UIImageView = {
@@ -27,6 +27,13 @@ final class WelcomeIllustrationView: UIView {
imageView.contentMode = .scaleAspectFill
return imageView
}()
+
+ var layout: Layout = .compact {
+ didSet {
+ setNeedsLayout()
+ }
+ }
+ var aspectLayoutConstraint: NSLayoutConstraint!
override init(frame: CGRect) {
super.init(frame: frame)
@@ -40,6 +47,20 @@ final class WelcomeIllustrationView: UIView {
}
+extension WelcomeIllustrationView {
+ enum Layout {
+ case compact
+ case regular
+
+ var artworkImageSize: CGSize {
+ switch self {
+ case .compact: return CGSize(width: 375, height: 1500)
+ case .regular: return CGSize(width: 547, height: 3000)
+ }
+ }
+ }
+}
+
extension WelcomeIllustrationView {
private func _init() {
@@ -62,7 +83,6 @@ extension WelcomeIllustrationView {
cloudBaseImageView.leadingAnchor.constraint(equalTo: leadingAnchor),
cloudBaseImageView.trailingAnchor.constraint(equalTo: trailingAnchor),
cloudBaseImageView.bottomAnchor.constraint(equalTo: bottomAnchor),
- cloudBaseImageView.widthAnchor.constraint(equalTo: cloudBaseImageView.heightAnchor, multiplier: WelcomeIllustrationView.artworkImageSize.width / WelcomeIllustrationView.artworkImageSize.height),
])
[
@@ -79,15 +99,28 @@ extension WelcomeIllustrationView {
imageView.bottomAnchor.constraint(equalTo: cloudBaseImageView.bottomAnchor),
])
}
+
+ aspectLayoutConstraint = cloudBaseImageView.widthAnchor.constraint(equalTo: cloudBaseImageView.heightAnchor, multiplier: layout.artworkImageSize.width / layout.artworkImageSize.height)
+ aspectLayoutConstraint.isActive = true
}
override func layoutSubviews() {
super.layoutSubviews()
- updateImage()
+
+ switch layout {
+ case .compact:
+ layoutCompact()
+ case .regular:
+ layoutRegular()
+ }
+
+ aspectLayoutConstraint.isActive = false
+ aspectLayoutConstraint = cloudBaseImageView.widthAnchor.constraint(equalTo: cloudBaseImageView.heightAnchor, multiplier: layout.artworkImageSize.width / layout.artworkImageSize.height)
+ aspectLayoutConstraint.isActive = true
}
- private func updateImage() {
- let size = WelcomeIllustrationView.artworkImageSize
+ private func layoutCompact() {
+ let size = layout.artworkImageSize
let width = size.width
let height = size.height
@@ -130,6 +163,50 @@ extension WelcomeIllustrationView {
}
}
+ private func layoutRegular() {
+ let size = layout.artworkImageSize
+ let width = size.width
+ let height = size.height
+
+ cloudBaseImageView.image = UIGraphicsImageRenderer(size: size).image { context in
+ // clear background
+ UIColor.clear.setFill()
+ context.fill(CGRect(origin: .zero, size: size))
+
+ // draw cloud
+ cloudBaseExtendImage.draw(at: CGPoint(x: 0, y: height - cloudBaseExtendImage.size.height))
+
+ rightHillImageView.image = UIGraphicsImageRenderer(size: size).image { context in
+ // clear background
+ UIColor.clear.setFill()
+ context.fill(CGRect(origin: .zero, size: size))
+
+ // draw elephantThreeOnGrassWithTreeTwoImage
+ // elephantThreeOnGrassWithTreeTwo.bottomY - 25 align to elephantThreeOnGrassImage.centerY
+ elephantThreeOnGrassWithTreeTwoImage.draw(at: CGPoint(x: width - elephantThreeOnGrassWithTreeTwoImage.size.width, y: height - 0.5 * elephantThreeOnGrassImage.size.height - elephantThreeOnGrassWithTreeTwoImage.size.height - 20))
+ }
+
+ leftHillImageView.image = UIGraphicsImageRenderer(size: size).image { context in
+ // clear background
+ UIColor.clear.setFill()
+ context.fill(CGRect(origin: .zero, size: size))
+
+ // draw elephantThreeOnGrassWithTreeThree
+ // elephantThreeOnGrassWithTreeThree.bottomY + 30 align to elephantThreeOnGrassImage.centerY
+ elephantThreeOnGrassWithTreeThreeImage.draw(at: CGPoint(x: -160, y: height - 0.5 * elephantThreeOnGrassImage.size.height - elephantThreeOnGrassWithTreeThreeImage.size.height - 80))
+ }
+
+ centerHillImageView.image = UIGraphicsImageRenderer(size: size).image { context in
+ // clear background
+ UIColor.clear.setFill()
+ context.fill(CGRect(origin: .zero, size: size))
+
+ // draw elephantThreeOnGrass
+ elephantThreeOnGrassExtendImage.draw(at: CGPoint(x: 0, y: height - elephantThreeOnGrassExtendImage.size.height))
+ }
+ }
+ }
+
}
#if canImport(SwiftUI) && DEBUG
@@ -140,13 +217,17 @@ struct WelcomeIllustrationView_Previews: PreviewProvider {
static var previews: some View {
Group {
UIViewPreview(width: 375) {
- WelcomeIllustrationView()
+ let view = WelcomeIllustrationView()
+ view.layout = .compact
+ return view
}
.previewLayout(.fixed(width: 375, height: 1500))
- UIViewPreview(width: 1125) {
- WelcomeIllustrationView()
+ UIViewPreview(width: 547) {
+ let view = WelcomeIllustrationView()
+ view.layout = .regular
+ return view
}
- .previewLayout(.fixed(width: 1125, height: 5000))
+ .previewLayout(.fixed(width: 547, height: 1500))
}
}
diff --git a/Mastodon/Scene/Onboarding/Welcome/WelcomeViewController.swift b/Mastodon/Scene/Onboarding/Welcome/WelcomeViewController.swift
index bf33ea13..1dff6965 100644
--- a/Mastodon/Scene/Onboarding/Welcome/WelcomeViewController.swift
+++ b/Mastodon/Scene/Onboarding/Welcome/WelcomeViewController.swift
@@ -11,6 +11,8 @@ import Combine
final class WelcomeViewController: UIViewController, NeedsDependency {
+ let logger = Logger(subsystem: "WelcomeViewController", category: "ViewController")
+
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
@@ -41,29 +43,35 @@ final class WelcomeViewController: UIViewController, NeedsDependency {
return label
}()
+ let buttonContainer = UIStackView()
+
private(set) lazy var signUpButton: PrimaryActionButton = {
let button = PrimaryActionButton()
button.adjustsBackgroundImageWhenUserInterfaceStyleChanges = false
- button.setTitle(L10n.Common.Controls.Actions.signUp, for: .normal)
+ button.setTitle("Get Started", for: .normal) // TODO: i18n
let backgroundImageColor: UIColor = .white
let backgroundImageHighlightedColor: UIColor = UIColor(white: 0.8, alpha: 1.0)
button.setBackgroundImage(.placeholder(color: backgroundImageColor), for: .normal)
button.setBackgroundImage(.placeholder(color: backgroundImageHighlightedColor), for: .highlighted)
- let titleColor: UIColor = Asset.Colors.brandBlue.color
- button.setTitleColor(titleColor, for: .normal)
- button.translatesAutoresizingMaskIntoConstraints = false
+ button.setTitleColor(.black, for: .normal)
return button
}()
+ let signUpButtonShadowView = UIView()
- private(set) lazy var signInButton: UIButton = {
- let button = UIButton(type: .system)
+ private(set) lazy var signInButton: PrimaryActionButton = {
+ let button = PrimaryActionButton()
+ button.adjustsBackgroundImageWhenUserInterfaceStyleChanges = false
button.titleLabel?.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 15, weight: .semibold))
- button.setTitle(L10n.Common.Controls.Actions.signIn, for: .normal)
- let titleColor: UIColor = UIColor.white.withAlphaComponent(0.8)
+ button.setTitle("Log In", for: .normal)
+ let backgroundImageColor = Asset.Scene.Welcome.signInButtonBackground.color
+ let backgroundImageHighlightedColor = Asset.Scene.Welcome.signInButtonBackground.color.withAlphaComponent(0.8)
+ button.setBackgroundImage(.placeholder(color: backgroundImageColor), for: .normal)
+ button.setBackgroundImage(.placeholder(color: backgroundImageHighlightedColor), for: .highlighted)
+ let titleColor: UIColor = UIColor.white.withAlphaComponent(0.9)
button.setTitleColor(titleColor, for: .normal)
- button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
+ let signInButtonShadowView = UIView()
deinit {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
@@ -76,7 +84,8 @@ extension WelcomeViewController {
override func viewDidLoad() {
super.viewDidLoad()
- // preferredContentSize = CGSize(width: 547, height: 678)
+ definesPresentationContext = true
+ preferredContentSize = CGSize(width: 547, height: 678)
navigationController?.navigationBar.prefersLargeTitles = true
navigationItem.largeTitleDisplayMode = .never
@@ -84,19 +93,48 @@ extension WelcomeViewController {
setupOnboardingAppearance()
setupIllustrationLayout()
-
- view.addSubview(signInButton)
- view.addSubview(signUpButton)
+
+ buttonContainer.axis = .vertical
+ buttonContainer.spacing = 12
+ buttonContainer.isLayoutMarginsRelativeArrangement = true
+
+ buttonContainer.translatesAutoresizingMaskIntoConstraints = false
+ view.addSubview(buttonContainer)
NSLayoutConstraint.activate([
- signInButton.leadingAnchor.constraint(equalTo: view.readableContentGuide.leadingAnchor, constant: WelcomeViewController.actionButtonMargin),
- view.readableContentGuide.trailingAnchor.constraint(equalTo: signInButton.trailingAnchor, constant: WelcomeViewController.actionButtonMargin),
- view.layoutMarginsGuide.bottomAnchor.constraint(equalTo: signInButton.bottomAnchor, constant: WelcomeViewController.viewBottomPaddingHeight),
- signInButton.heightAnchor.constraint(equalToConstant: WelcomeViewController.actionButtonHeight).priority(.defaultHigh),
-
- signInButton.topAnchor.constraint(equalTo: signUpButton.bottomAnchor, constant: 9),
- signUpButton.leadingAnchor.constraint(equalTo: view.readableContentGuide.leadingAnchor, constant: WelcomeViewController.actionButtonMargin),
- view.readableContentGuide.trailingAnchor.constraint(equalTo: signUpButton.trailingAnchor, constant: WelcomeViewController.actionButtonMargin),
- signUpButton.heightAnchor.constraint(equalToConstant: WelcomeViewController.actionButtonHeight).priority(.defaultHigh),
+ buttonContainer.leadingAnchor.constraint(equalTo: view.readableContentGuide.leadingAnchor),
+ buttonContainer.trailingAnchor.constraint(equalTo: view.readableContentGuide.trailingAnchor),
+ view.layoutMarginsGuide.bottomAnchor.constraint(equalTo: buttonContainer.bottomAnchor),
+ ])
+
+ signUpButton.translatesAutoresizingMaskIntoConstraints = false
+ buttonContainer.addArrangedSubview(signUpButton)
+ NSLayoutConstraint.activate([
+ signUpButton.heightAnchor.constraint(equalToConstant: WelcomeViewController.actionButtonHeight).priority(.required - 1),
+ ])
+ signInButton.translatesAutoresizingMaskIntoConstraints = false
+ buttonContainer.addArrangedSubview(signInButton)
+ NSLayoutConstraint.activate([
+ signInButton.heightAnchor.constraint(equalToConstant: WelcomeViewController.actionButtonHeight).priority(.required - 1),
+ ])
+
+ signUpButtonShadowView.translatesAutoresizingMaskIntoConstraints = false
+ buttonContainer.addSubview(signUpButtonShadowView)
+ buttonContainer.sendSubviewToBack(signUpButtonShadowView)
+ NSLayoutConstraint.activate([
+ signUpButtonShadowView.topAnchor.constraint(equalTo: signUpButton.topAnchor),
+ signUpButtonShadowView.leadingAnchor.constraint(equalTo: signUpButton.leadingAnchor),
+ signUpButtonShadowView.trailingAnchor.constraint(equalTo: signUpButton.trailingAnchor),
+ signUpButtonShadowView.bottomAnchor.constraint(equalTo: signUpButton.bottomAnchor),
+ ])
+
+ signInButtonShadowView.translatesAutoresizingMaskIntoConstraints = false
+ buttonContainer.addSubview(signInButtonShadowView)
+ buttonContainer.sendSubviewToBack(signInButtonShadowView)
+ NSLayoutConstraint.activate([
+ signInButtonShadowView.topAnchor.constraint(equalTo: signInButton.topAnchor),
+ signInButtonShadowView.leadingAnchor.constraint(equalTo: signInButton.leadingAnchor),
+ signInButtonShadowView.trailingAnchor.constraint(equalTo: signInButton.trailingAnchor),
+ signInButtonShadowView.bottomAnchor.constraint(equalTo: signInButton.bottomAnchor),
])
signUpButton.addTarget(self, action: #selector(signUpButtonDidClicked(_:)), for: .touchUpInside)
@@ -109,17 +147,12 @@ extension WelcomeViewController {
self.navigationItem.leftBarButtonItem = needsShowDismissEntry ? self.dismissBarButtonItem : nil
}
.store(in: &disposeBag)
-
- view.observe(\.frame, options: [.initial, .new]) { [weak self] view, _ in
- guard let self = self else { return }
- switch view.traitCollection.userInterfaceIdiom {
- case .phone:
- break
- default:
- self.welcomeIllustrationView.elephantOnAirplaneWithContrailImageView.isHidden = view.frame.height < 800
- }
- }
- .store(in: &observations)
+ }
+
+ override func viewDidLayoutSubviews() {
+ super.viewDidLayoutSubviews()
+
+ setupButtonShadowView()
}
override func viewSafeAreaInsetsDidChange() {
@@ -130,18 +163,75 @@ extension WelcomeViewController {
if view.safeAreaInsets.bottom == 0 {
overlap += 56
}
- // shift illustration down for iPad modal
- if UIDevice.current.userInterfaceIdiom != .phone {
- overlap += 20
- }
welcomeIllustrationViewBottomAnchorLayoutConstraint?.constant = overlap
}
+
+ override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
+ super.traitCollectionDidChange(previousTraitCollection)
+
+ setupIllustrationLayout()
+ setupButtonShadowView()
+ }
}
extension WelcomeViewController {
+ private func setupButtonShadowView() {
+ signUpButtonShadowView.layer.setupShadow(
+ color: .black,
+ alpha: 0.25,
+ x: 0,
+ y: 1,
+ blur: 2,
+ spread: 0,
+ roundedRect: signInButtonShadowView.bounds,
+ byRoundingCorners: .allCorners,
+ cornerRadii: CGSize(width: 10, height: 10)
+ )
+ signInButtonShadowView.layer.setupShadow(
+ color: .black,
+ alpha: 0.25,
+ x: 0,
+ y: 1,
+ blur: 2,
+ spread: 0,
+ roundedRect: signInButtonShadowView.bounds,
+ byRoundingCorners: .allCorners,
+ cornerRadii: CGSize(width: 10, height: 10)
+ )
+ }
+
+ private func updateButtonContainerLayoutMargins(traitCollection: UITraitCollection) {
+ switch traitCollection.userInterfaceIdiom {
+ case .phone:
+ buttonContainer.layoutMargins = UIEdgeInsets(
+ top: 0,
+ left: WelcomeViewController.actionButtonMargin,
+ bottom: WelcomeViewController.viewBottomPaddingHeight,
+ right: WelcomeViewController.actionButtonMargin
+ )
+ default:
+ let margin = traitCollection.horizontalSizeClass == .regular ? WelcomeViewController.actionButtonMarginExtend : WelcomeViewController.actionButtonMargin
+ buttonContainer.layoutMargins = UIEdgeInsets(
+ top: 0,
+ left: margin,
+ bottom: WelcomeViewController.viewBottomPaddingHeightExtend,
+ right: margin
+ )
+ }
+ }
+
private func setupIllustrationLayout() {
+ welcomeIllustrationView.layout = {
+ switch traitCollection.userInterfaceIdiom {
+ case .phone:
+ return .compact
+ default:
+ return .regular
+ }
+ }()
+
// set logo
if logoImageView.superview == nil {
view.addSubview(logoImageView)
@@ -154,10 +244,11 @@ extension WelcomeViewController {
logoImageView.setContentHuggingPriority(.defaultHigh, for: .vertical)
}
- // set illustration for phone
+ // set illustration
guard welcomeIllustrationView.superview == nil else {
return
}
+ welcomeIllustrationView.contentMode = .scaleAspectFit
welcomeIllustrationView.translatesAutoresizingMaskIntoConstraints = false
welcomeIllustrationViewBottomAnchorLayoutConstraint = welcomeIllustrationView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 5)
@@ -166,7 +257,7 @@ extension WelcomeViewController {
NSLayoutConstraint.activate([
view.leftAnchor.constraint(equalTo: welcomeIllustrationView.leftAnchor, constant: 15),
welcomeIllustrationView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: 15),
- welcomeIllustrationViewBottomAnchorLayoutConstraint!
+ welcomeIllustrationViewBottomAnchorLayoutConstraint!.priority(.required - 1),
])
welcomeIllustrationView.cloudBaseImageView.addMotionEffect(
@@ -268,21 +359,36 @@ extension WelcomeViewController: OnboardingViewControllerAppearance {
// MARK: - UIAdaptivePresentationControllerDelegate
extension WelcomeViewController: UIAdaptivePresentationControllerDelegate {
+
func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle {
+ logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)")
+
+ // update button layout
+ updateButtonContainerLayoutMargins(traitCollection: traitCollection)
+
+ let navigationController = navigationController as? OnboardingNavigationController
+
switch traitCollection.userInterfaceIdiom {
case .phone:
+ navigationController?.gradientBorderView.isHidden = true
// make underneath view controller alive to fix layout issue due to view life cycle
return .fullScreen
default:
- return .formSheet
-// switch traitCollection.horizontalSizeClass {
-// case .regular:
-// default:
-// return .fullScreen
-// }
+ switch traitCollection.horizontalSizeClass {
+ case .compact:
+ navigationController?.gradientBorderView.isHidden = true
+ return .fullScreen
+ default:
+ navigationController?.gradientBorderView.isHidden = false
+ return .formSheet
+ }
}
}
+ func presentationController(_ controller: UIPresentationController, viewControllerForAdaptivePresentationStyle style: UIModalPresentationStyle) -> UIViewController? {
+ return nil
+ }
+
func presentationControllerShouldDismiss(_ presentationController: UIPresentationController) -> Bool {
return false
}
diff --git a/Mastodon/Scene/Profile/Header/ProfileHeaderViewModel.swift b/Mastodon/Scene/Profile/Header/ProfileHeaderViewModel.swift
index e8405b6a..40883120 100644
--- a/Mastodon/Scene/Profile/Header/ProfileHeaderViewModel.swift
+++ b/Mastodon/Scene/Profile/Header/ProfileHeaderViewModel.swift
@@ -14,6 +14,7 @@ import MastodonMeta
final class ProfileHeaderViewModel {
+ static let avatarImageMaxSizeInPixel = CGSize(width: 400, height: 400)
static let maxProfileFieldCount = 4
var disposeBag = Set()
@@ -190,8 +191,8 @@ extension ProfileHeaderViewModel {
let image: UIImage? = {
guard case let .image(_image) = editProfileInfo.avatarImageResource.value else { return nil }
guard let image = _image else { return nil }
- guard image.size.width <= MastodonRegisterViewController.avatarImageMaxSizeInPixel.width else {
- return image.af.imageScaled(to: MastodonRegisterViewController.avatarImageMaxSizeInPixel)
+ guard image.size.width <= ProfileHeaderViewModel.avatarImageMaxSizeInPixel.width else {
+ return image.af.imageScaled(to: ProfileHeaderViewModel.avatarImageMaxSizeInPixel)
}
return image
}()
diff --git a/Mastodon/Scene/Root/MainTab/MainTabBarController.swift b/Mastodon/Scene/Root/MainTab/MainTabBarController.swift
index 4b803bc4..058a0fc3 100644
--- a/Mastodon/Scene/Root/MainTab/MainTabBarController.swift
+++ b/Mastodon/Scene/Root/MainTab/MainTabBarController.swift
@@ -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
diff --git a/Mastodon/Scene/Share/NavigationController/AdaptiveStatusBarStyleNavigationController.swift b/Mastodon/Scene/Share/NavigationController/AdaptiveStatusBarStyleNavigationController.swift
index eb260853..aac23285 100644
--- a/Mastodon/Scene/Share/NavigationController/AdaptiveStatusBarStyleNavigationController.swift
+++ b/Mastodon/Scene/Share/NavigationController/AdaptiveStatusBarStyleNavigationController.swift
@@ -9,7 +9,7 @@ import UIKit
// Make status bar style adaptive for child view controller
// SeeAlso: `modalPresentationCapturesStatusBarAppearance`
-final class AdaptiveStatusBarStyleNavigationController: UINavigationController {
+class AdaptiveStatusBarStyleNavigationController: UINavigationController {
override var childForStatusBarStyle: UIViewController? {
visibleViewController
}
diff --git a/Mastodon/Scene/Share/View/Button/PrimaryActionButton.swift b/Mastodon/Scene/Share/View/Button/PrimaryActionButton.swift
index 326dfa12..676d558a 100644
--- a/Mastodon/Scene/Share/View/Button/PrimaryActionButton.swift
+++ b/Mastodon/Scene/Share/View/Button/PrimaryActionButton.swift
@@ -9,8 +9,8 @@ import UIKit
class PrimaryActionButton: UIButton {
- var isLoading: Bool = false
-
+ private var originalButtonTitle: String?
+
lazy var activityIndicator: UIActivityIndicatorView = {
let indicator = UIActivityIndicatorView(style: .medium)
indicator.color = .white
@@ -18,10 +18,13 @@ class PrimaryActionButton: UIButton {
indicator.translatesAutoresizingMaskIntoConstraints = false
return indicator
}()
-
- private var originalButtonTitle: String?
-
var adjustsBackgroundImageWhenUserInterfaceStyleChanges = true
+ var action: Action = .next {
+ didSet {
+ setupAppearance(action: action)
+ }
+ }
+ var isLoading: Bool = false
override init(frame: CGRect) {
super.init(frame: frame)
@@ -35,26 +38,44 @@ class PrimaryActionButton: UIButton {
}
+extension PrimaryActionButton {
+
+ public enum Action {
+ case back
+ case next
+ }
+
+}
+
extension PrimaryActionButton {
private func _init() {
titleLabel?.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 17, weight: .semibold))
setTitleColor(.white, for: .normal)
- setupBackgroundAppearance()
+ setupAppearance(action: action)
applyCornerRadius(radius: 10)
}
- func setupBackgroundAppearance() {
- setBackgroundImage(UIImage.placeholder(color: Asset.Colors.brandBlue.color), for: .normal)
- setBackgroundImage(UIImage.placeholder(color: Asset.Colors.brandBlueDarken20.color), for: .highlighted)
- setBackgroundImage(UIImage.placeholder(color: Asset.Colors.disabled.color), for: .disabled)
+ func setupAppearance(action: Action) {
+ switch action {
+ case .back:
+ setTitleColor(Asset.Colors.Label.primary.color, for: .normal)
+ setBackgroundImage(UIImage.placeholder(color: Asset.Scene.Onboarding.navigationBackButtonBackground.color), for: .normal)
+ setBackgroundImage(UIImage.placeholder(color: Asset.Scene.Onboarding.navigationBackButtonBackgroundHighlighted.color), for: .highlighted)
+ setBackgroundImage(UIImage.placeholder(color: Asset.Colors.disabled.color), for: .disabled)
+ case .next:
+ setTitleColor(Asset.Colors.Label.primaryReverse.color, for: .normal)
+ setBackgroundImage(UIImage.placeholder(color: Asset.Scene.Onboarding.navigationNextButtonBackground.color), for: .normal)
+ setBackgroundImage(UIImage.placeholder(color: Asset.Scene.Onboarding.navigationNextButtonBackgroundHighlighted.color), for: .highlighted)
+ setBackgroundImage(UIImage.placeholder(color: Asset.Colors.disabled.color), for: .disabled)
+ }
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
if adjustsBackgroundImageWhenUserInterfaceStyleChanges {
- setupBackgroundAppearance()
+ setupAppearance(action: action)
}
}
diff --git a/Mastodon/Scene/Share/View/Container/TouchBlockingView.swift b/Mastodon/Scene/Share/View/Container/TouchBlockingView.swift
index b86137f1..5a151812 100644
--- a/Mastodon/Scene/Share/View/Container/TouchBlockingView.swift
+++ b/Mastodon/Scene/Share/View/Container/TouchBlockingView.swift
@@ -7,7 +7,7 @@
import UIKit
-final class TouchBlockingView: UIView {
+class TouchBlockingView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
diff --git a/Mastodon/Scene/Share/View/Content/TimelineHeaderView.swift b/Mastodon/Scene/Share/View/Content/TimelineHeaderView.swift
index 2948af4c..50518e59 100644
--- a/Mastodon/Scene/Share/View/Content/TimelineHeaderView.swift
+++ b/Mastodon/Scene/Share/View/Content/TimelineHeaderView.swift
@@ -126,10 +126,10 @@ struct TimelineHeaderView_Previews: PreviewProvider {
static var previews: some View {
Group {
UIViewPreview(width: 375) {
- let headerView = TimelineHeaderView()
- headerView.iconImageView.image = Item.EmptyStateHeaderAttribute.Reason.blocking(name: nil).iconImage
- headerView.messageLabel.text = Item.EmptyStateHeaderAttribute.Reason.blocking(name: nil).message
- return headerView
+ let serverSectionHeaderView = TimelineHeaderView()
+ serverSectionHeaderView.iconImageView.image = Item.EmptyStateHeaderAttribute.Reason.blocking(name: nil).iconImage
+ serverSectionHeaderView.messageLabel.text = Item.EmptyStateHeaderAttribute.Reason.blocking(name: nil).message
+ return serverSectionHeaderView
}
.previewLayout(.fixed(width: 375, height: 400))
}
diff --git a/Mastodon/Scene/Share/View/Node/ASMetaEditableTextNode.swift b/Mastodon/Scene/Share/View/Node/ASMetaEditableTextNode.swift
deleted file mode 100644
index e5037fdf..00000000
--- a/Mastodon/Scene/Share/View/Node/ASMetaEditableTextNode.swift
+++ /dev/null
@@ -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
diff --git a/Mastodon/Scene/Share/View/Node/Status/StatusNode.swift b/Mastodon/Scene/Share/View/Node/Status/StatusNode.swift
deleted file mode 100644
index 17054348..00000000
--- a/Mastodon/Scene/Share/View/Node/Status/StatusNode.swift
+++ /dev/null
@@ -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()
- 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
diff --git a/Mastodon/Scene/Share/View/Node/Status/TimelineBottomLoaderNode.swift b/Mastodon/Scene/Share/View/Node/Status/TimelineBottomLoaderNode.swift
deleted file mode 100644
index 0ec83dfe..00000000
--- a/Mastodon/Scene/Share/View/Node/Status/TimelineBottomLoaderNode.swift
+++ /dev/null
@@ -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
diff --git a/Mastodon/Scene/Share/View/Node/Status/TimelineMiddleLoaderNode.swift b/Mastodon/Scene/Share/View/Node/Status/TimelineMiddleLoaderNode.swift
deleted file mode 100644
index bd662ad7..00000000
--- a/Mastodon/Scene/Share/View/Node/Status/TimelineMiddleLoaderNode.swift
+++ /dev/null
@@ -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
diff --git a/Mastodon/Scene/Wizard/WizardViewController.swift b/Mastodon/Scene/Wizard/WizardViewController.swift
index 2678c712..9152e64f 100644
--- a/Mastodon/Scene/Wizard/WizardViewController.swift
+++ b/Mastodon/Scene/Wizard/WizardViewController.swift
@@ -35,7 +35,7 @@ class WizardViewController: UIViewController {
let backgroundView: UIView = {
let view = UIView()
- view.backgroundColor = UIColor.black.withAlphaComponent(0.7)
+ view.backgroundColor = UIColor.black.withAlphaComponent(0.5)
return view
}()
diff --git a/Mastodon/Supporting Files/AppDelegate.swift b/Mastodon/Supporting Files/AppDelegate.swift
index e2cb7c41..6d7919c6 100644
--- a/Mastodon/Supporting Files/AppDelegate.swift
+++ b/Mastodon/Supporting Files/AppDelegate.swift
@@ -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
}
diff --git a/Mastodon/Vender/ActivityIndicatorNode.swift b/Mastodon/Vender/ActivityIndicatorNode.swift
deleted file mode 100644
index 8778f5ec..00000000
--- a/Mastodon/Vender/ActivityIndicatorNode.swift
+++ /dev/null
@@ -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
diff --git a/MastodonIntent/Info.plist b/MastodonIntent/Info.plist
index 8ac3d165..78d3b58e 100644
--- a/MastodonIntent/Info.plist
+++ b/MastodonIntent/Info.plist
@@ -17,9 +17,9 @@
CFBundlePackageType
$(PRODUCT_BUNDLE_PACKAGE_TYPE)
CFBundleShortVersionString
- 1.2.0
+ 1.3.0
CFBundleVersion
- 88
+ 90
NSExtension
NSExtensionAttributes
diff --git a/Mastodon/Extension/CALayer.swift b/MastodonSDK/Sources/MastodonExtension/CALayer.swift
similarity index 95%
rename from Mastodon/Extension/CALayer.swift
rename to MastodonSDK/Sources/MastodonExtension/CALayer.swift
index 41ce739e..684a4a70 100644
--- a/Mastodon/Extension/CALayer.swift
+++ b/MastodonSDK/Sources/MastodonExtension/CALayer.swift
@@ -9,7 +9,7 @@ import UIKit
extension CALayer {
- func setupShadow(
+ public func setupShadow(
color: UIColor = .black,
alpha: Float = 0.5,
x: CGFloat = 0,
@@ -43,9 +43,8 @@ extension CALayer {
}
}
- func removeShadow() {
+ public func removeShadow() {
shadowRadius = 0
}
-
-
+
}
diff --git a/MastodonSDK/Sources/MastodonExtension/UIImage.swift b/MastodonSDK/Sources/MastodonExtension/UIImage.swift
index 178d289d..e3560af6 100644
--- a/MastodonSDK/Sources/MastodonExtension/UIImage.swift
+++ b/MastodonSDK/Sources/MastodonExtension/UIImage.swift
@@ -10,12 +10,28 @@ import CoreImage.CIFilterBuiltins
import UIKit
extension UIImage {
- public static func placeholder(size: CGSize = CGSize(width: 1, height: 1), color: UIColor) -> UIImage {
+ public static func placeholder(
+ size: CGSize = CGSize(width: 1, height: 1),
+ color: UIColor,
+ cornerRadius: CGFloat = 0
+ ) -> UIImage {
let render = UIGraphicsImageRenderer(size: size)
return render.image { (context: UIGraphicsImageRendererContext) in
+ // set clear fill
context.cgContext.setFillColor(color.cgColor)
- context.fill(CGRect(origin: .zero, size: size))
+
+ let rect = CGRect(origin: .zero, size: size)
+
+ // clip corner if needs
+ if cornerRadius > 0 {
+ let path = UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius).cgPath
+ context.cgContext.addPath(path)
+ context.cgContext.clip(using: .evenOdd)
+ }
+
+ // set fill
+ context.fill(rect)
}
}
}
diff --git a/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Instance.swift b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Instance.swift
index d0d16ee4..f245d741 100644
--- a/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Instance.swift
+++ b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Instance.swift
@@ -86,7 +86,7 @@ extension Mastodon.Entity.Instance {
}
extension Mastodon.Entity.Instance {
- public struct Rule: Codable {
+ public struct Rule: Codable, Hashable {
public let id: String
public let text: String
}
diff --git a/MastodonSDK/Sources/MastodonUI/Service/KeyboardResponderService.swift b/MastodonSDK/Sources/MastodonUI/Service/KeyboardResponderService.swift
index 65328afa..db600a67 100644
--- a/MastodonSDK/Sources/MastodonUI/Service/KeyboardResponderService.swift
+++ b/MastodonSDK/Sources/MastodonUI/Service/KeyboardResponderService.swift
@@ -90,3 +90,47 @@ extension KeyboardResponderService {
case dock
}
}
+
+extension KeyboardResponderService {
+ public static func configure(
+ scrollView: UIScrollView,
+ layoutNeedsUpdate: AnyPublisher,
+ additionalSafeAreaInsets: AnyPublisher = CurrentValueSubject(.zero).eraseToAnyPublisher()
+ ) -> AnyCancellable {
+ let tuple = Publishers.CombineLatest3(
+ KeyboardResponderService.shared.isShow,
+ KeyboardResponderService.shared.state,
+ KeyboardResponderService.shared.endFrame
+ )
+
+ return Publishers.CombineLatest3(
+ tuple,
+ layoutNeedsUpdate,
+ additionalSafeAreaInsets
+ )
+ .sink(receiveValue: { [weak scrollView] tuple, _, additionalSafeAreaInsets in
+ guard let scrollView = scrollView else { return }
+ guard let view = scrollView.superview else { return }
+
+ let (isShow, state, endFrame) = tuple
+
+ guard isShow, state == .dock else {
+ scrollView.contentInset.bottom = additionalSafeAreaInsets.bottom
+ scrollView.verticalScrollIndicatorInsets.bottom = additionalSafeAreaInsets.bottom
+ return
+ }
+
+ // isShow AND dock state
+ let contentFrame = view.convert(scrollView.frame, to: nil)
+ let padding = contentFrame.maxY - endFrame.minY
+ guard padding > 0 else {
+ scrollView.contentInset.bottom = additionalSafeAreaInsets.bottom
+ scrollView.verticalScrollIndicatorInsets.bottom = additionalSafeAreaInsets.bottom
+ return
+ }
+
+ scrollView.contentInset.bottom = padding - scrollView.safeAreaInsets.bottom + additionalSafeAreaInsets.bottom
+ scrollView.verticalScrollIndicatorInsets.bottom = padding - scrollView.safeAreaInsets.bottom + additionalSafeAreaInsets.bottom
+ })
+ }
+}
diff --git a/MastodonSDK/Sources/MastodonUI/View/Container/ShadowBackgroundContainer.swift b/MastodonSDK/Sources/MastodonUI/View/Container/ShadowBackgroundContainer.swift
new file mode 100644
index 00000000..3f2f5df4
--- /dev/null
+++ b/MastodonSDK/Sources/MastodonUI/View/Container/ShadowBackgroundContainer.swift
@@ -0,0 +1,60 @@
+//
+// ShadowBackgroundContainer.swift
+//
+//
+// Created by MainasuK on 2022-1-5.
+//
+
+import UIKit
+import MastodonExtension
+
+public final class ShadowBackgroundContainer: UIView {
+
+ public var shadowAlpha: CGFloat = 0.25 {
+ didSet { setNeedsLayout() }
+ }
+
+ public var shadowColor: UIColor = .black {
+ didSet { setNeedsLayout() }
+ }
+
+ public var cornerRadius: CGFloat = 10 {
+ didSet { setNeedsLayout() }
+ }
+
+ public let shadowLayer = CALayer()
+
+ override init(frame: CGRect) {
+ super.init(frame: frame)
+ _init()
+ }
+
+ required init?(coder: NSCoder) {
+ super.init(coder: coder)
+ _init()
+ }
+
+}
+
+extension ShadowBackgroundContainer {
+ private func _init() {
+ layer.insertSublayer(shadowLayer, at: 0)
+ }
+
+ public override func layoutSubviews() {
+ super.layoutSubviews()
+
+ shadowLayer.frame = bounds
+ shadowLayer.setupShadow(
+ color: shadowColor,
+ alpha: Float(shadowAlpha),
+ x: 0,
+ y: 1,
+ blur: 2,
+ spread: 0,
+ roundedRect: bounds,
+ byRoundingCorners: .allCorners,
+ cornerRadii: CGSize(width: cornerRadius, height: cornerRadius)
+ )
+ }
+}
diff --git a/MastodonTests/Info.plist b/MastodonTests/Info.plist
index 9fe845c6..f652792e 100644
--- a/MastodonTests/Info.plist
+++ b/MastodonTests/Info.plist
@@ -15,8 +15,8 @@
CFBundlePackageType
$(PRODUCT_BUNDLE_PACKAGE_TYPE)
CFBundleShortVersionString
- 1.2.0
+ 1.3.0
CFBundleVersion
- 88
+ 90
diff --git a/MastodonUITests/Info.plist b/MastodonUITests/Info.plist
index 9fe845c6..f652792e 100644
--- a/MastodonUITests/Info.plist
+++ b/MastodonUITests/Info.plist
@@ -15,8 +15,8 @@
CFBundlePackageType
$(PRODUCT_BUNDLE_PACKAGE_TYPE)
CFBundleShortVersionString
- 1.2.0
+ 1.3.0
CFBundleVersion
- 88
+ 90
diff --git a/NotificationService/Info.plist b/NotificationService/Info.plist
index 8e14f3a2..77c7421d 100644
--- a/NotificationService/Info.plist
+++ b/NotificationService/Info.plist
@@ -17,9 +17,9 @@
CFBundlePackageType
$(PRODUCT_BUNDLE_PACKAGE_TYPE)
CFBundleShortVersionString
- 1.2.0
+ 1.3.0
CFBundleVersion
- 88
+ 90
NSExtension
NSExtensionPointIdentifier
diff --git a/Podfile b/Podfile
index 868af1a9..4a2e7bc6 100644
--- a/Podfile
+++ b/Podfile
@@ -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
\ No newline at end of file
+end
diff --git a/Podfile.lock b/Podfile.lock
index 3541289d..ea4ef823 100644
--- a/Podfile.lock
+++ b/Podfile.lock
@@ -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
diff --git a/ShareActionExtension/Info.plist b/ShareActionExtension/Info.plist
index 1b302547..ae948488 100644
--- a/ShareActionExtension/Info.plist
+++ b/ShareActionExtension/Info.plist
@@ -17,9 +17,9 @@
CFBundlePackageType
$(PRODUCT_BUNDLE_PACKAGE_TYPE)
CFBundleShortVersionString
- 1.2.0
+ 1.3.0
CFBundleVersion
- 88
+ 90
NSExtension
NSExtensionAttributes